LLFIO  v2.00
llfio_v2_xxx::fs_handle Class Referenceabstract

A handle to something with a device and inode number. More...

#include "fs_handle.hpp"

Inheritance diagram for llfio_v2_xxx::fs_handle:
llfio_v2_xxx::directory_handle llfio_v2_xxx::file_handle llfio_v2_xxx::pipe_handle llfio_v2_xxx::symlink_handle llfio_v2_xxx::fast_random_file_handle llfio_v2_xxx::mapped_file_handle

Public Types

using dev_t = uint64_t
 
using ino_t = uint64_t
 
using path_view_type = path_view
 The path view type used by this handle.
 
using unique_id_type = QUICKCPPLIB_NAMESPACE::integers128::uint128
 The unique identifier type used by this handle.
 
using unique_id_type_hasher = QUICKCPPLIB_NAMESPACE::integers128::uint128_hasher
 A hasher for the unique identifier type used by this handle.
 

Public Member Functions

 fs_handle (const fs_handle &)=delete
 No copy construction (use clone())
 
fs_handleoperator= (const fs_handle &o)=delete
 No copy assignment.
 
dev_t st_dev () const noexcept
 Unless flag::disable_safety_unlinks is set, the device id of the file when opened.
 
ino_t st_ino () const noexcept
 Unless flag::disable_safety_unlinks is set, the inode of the file when opened. When combined with st_dev(), forms a unique identifer on this system.
 
unique_id_type unique_id () const noexcept
 A unique identifier for this handle across the entire system. Can be used in hash tables etc.
 
virtual result< path_handleparent_path_handle (deadline d=std::chrono::seconds(30)) const noexcept
 Obtain a handle to the path currently containing this handle's file entry. More...
 
template<class... Args>
bool try_parent_path_handle (Args &&... args) noexcept
 
template<class... Args, class Rep , class Period >
bool try_parent_path_handle_for (Args &&... args, const std::chrono::duration< Rep, Period > &duration) noexcept
 
template<class... Args, class Clock , class Duration >
bool try_parent_path_handle_until (Args &&... args, const std::chrono::time_point< Clock, Duration > &timeout) noexcept
 
virtual result< void > relink (const path_handle &base, path_view_type path, bool atomic_replace=true, deadline d=std::chrono::seconds(30)) noexcept
 Relinks the current path of this open handle to the new path specified. If atomic_replace is true, the relink atomically and silently replaces any item at the new path specified. This operation is both atomic and matching POSIX behaviour even on Microsoft Windows where no Win32 API can match POSIX semantics. More...
 
template<class... Args>
bool try_relink (Args &&... args) noexcept
 
template<class... Args, class Rep , class Period >
bool try_relink_for (Args &&... args, const std::chrono::duration< Rep, Period > &duration) noexcept
 
template<class... Args, class Clock , class Duration >
bool try_relink_until (Args &&... args, const std::chrono::time_point< Clock, Duration > &timeout) noexcept
 
virtual result< void > link (const path_handle &base, path_view_type path, deadline d=std::chrono::seconds(30)) noexcept
 Links the inode referred to by this open handle to the path specified. The current path of this open handle is not changed, unless it has no current path due to being unlinked. More...
 
template<class... Args>
bool try_link (Args &&... args) noexcept
 
template<class... Args, class Rep , class Period >
bool try_link_for (Args &&... args, const std::chrono::duration< Rep, Period > &duration) noexcept
 
template<class... Args, class Clock , class Duration >
bool try_link_until (Args &&... args, const std::chrono::time_point< Clock, Duration > &timeout) noexcept
 
virtual result< void > unlink (deadline d=std::chrono::seconds(30)) noexcept
 Unlinks the current path of this open handle, causing its entry to immediately disappear from the filing system. More...
 
template<class... Args>
bool try_unlink (Args &&... args) noexcept
 
template<class... Args, class Rep , class Period >
bool try_unlink_for (Args &&... args, const std::chrono::duration< Rep, Period > &duration) noexcept
 
template<class... Args, class Clock , class Duration >
bool try_unlink_until (Args &&... args, const std::chrono::time_point< Clock, Duration > &timeout) noexcept
 
virtual result< span< path_view_component > > list_extended_attributes (span< byte > tofill) const noexcept
 Fill the supplied buffer with the names of all extended attributes set on this file or directory, returning a span of path view components. More...
 
virtual result< span< byte > > get_extended_attribute (span< byte > tofill, path_view_component name) const noexcept
 Retrieve the value of an extended attribute set on this file or directory. More...
 
virtual result< void > set_extended_attribute (path_view_component name, span< const byte > value) noexcept
 Sets the value of an extended attribute on this file or directory. More...
 
virtual result< void > remove_extended_attribute (path_view_component) noexcept
 Removes the extended attribute set on this file or directory. More...
 
result< size_t > copy_extended_attributes (const fs_handle &src, bool replace_all_local_attributes=false) noexcept
 Copies the extended attributes from one entity to another, optionally replacing all the extended attributes on the destination. More...
 

Protected Member Functions

result< void > _fetch_inode () const noexcept
 Fill in _devid and _inode from the handle via fstat()
 
virtual const handle_get_handle () const noexcept=0
 
virtual result< void > _replace_handle (handle &&o) noexcept=0
 
constexpr fs_handle ()
 Default constructor.
 
constexpr fs_handle (dev_t devid, ino_t inode)
 Construct a handle.
 
constexpr fs_handle (fs_handle &&o) noexcept
 Implicit move construction of fs_handle permitted.
 
fs_handleoperator= (fs_handle &&o) noexcept
 Move assignment of fs_handle permitted.
 

Protected Attributes

dev_t _devid {0}
 
ino_t _inode {0}
 

Friends

result< filesystem::path > to_win32_path (const fs_handle &h, win32_path_namespace mapping) noexcept
 Maps the current path of h into a form suitable for Win32 APIs. Passes through unmodified on POSIX, so you can use this in portable code. More...
 

Detailed Description

A handle to something with a device and inode number.

See also
algorithm::cached_parent_handle_adapter<T>

Member Function Documentation

◆ copy_extended_attributes()

result<size_t> llfio_v2_xxx::fs_handle::copy_extended_attributes ( const fs_handle src,
bool  replace_all_local_attributes = false 
)
inlinenoexcept

Copies the extended attributes from one entity to another, optionally replacing all the extended attributes on the destination.

This convenience function is implemented using the APIs above, and therefore is racy with respect to concurrent users. If you specifiy an invalid source with replace_all_local_attributes = true, then this is a convenient way to remove all extended attributes on the local inode.

Note
This function uses 130Kb of stack and cannot handle attribute values larger than 64Kb.
446  {
447  auto &h = _get_handle();
448  (void) h;
449  LLFIO_LOG_FUNCTION_CALL(&h);
450  byte buffer[65536 + 4096];
451  if(replace_all_local_attributes)
452  {
453  for(;;)
454  {
455  OUTCOME_TRY(auto &&attribs, list_extended_attributes(buffer));
456  if(attribs.empty())
457  {
458  break;
459  }
460  for(auto &attrib : attribs)
461  {
462  OUTCOME_TRY(remove_extended_attribute(attrib));
463  }
464  }
465  }
466  if(src._get_handle().is_valid())
467  {
468  OUTCOME_TRY(auto &&attribs, src.list_extended_attributes(buffer));
469  for(auto &attrib : attribs)
470  {
471  byte buffer2[65536];
472  OUTCOME_TRY(auto &&value, src.get_extended_attribute(buffer2, attrib));
473  OUTCOME_TRY(set_extended_attribute(attrib, value));
474  }
475  }
476  return success();
477  }
virtual result< span< path_view_component > > list_extended_attributes(span< byte > tofill) const noexcept
Fill the supplied buffer with the names of all extended attributes set on this file or directory,...
virtual result< void > set_extended_attribute(path_view_component name, span< const byte > value) noexcept
Sets the value of an extended attribute on this file or directory.
virtual result< void > remove_extended_attribute(path_view_component) noexcept
Removes the extended attribute set on this file or directory.

◆ get_extended_attribute()

virtual result<span<byte> > llfio_v2_xxx::fs_handle::get_extended_attribute ( span< byte >  tofill,
path_view_component  name 
) const
inlinevirtualnoexcept

Retrieve the value of an extended attribute set on this file or directory.

Note
On Windows, this is the list of alternate streams on a file, NOT NTFS extended attributes.

◆ link()

virtual result<void> llfio_v2_xxx::fs_handle::link ( const path_handle base,
path_view_type  path,
deadline  d = std::chrono::seconds(30) 
)
inlinevirtualnoexcept

Links the inode referred to by this open handle to the path specified. The current path of this open handle is not changed, unless it has no current path due to being unlinked.

Warning
Some operating systems provide a race free syscall for linking an open handle to a new location (Linux, Windows). On all other operating systems this call is racy and can result in the wrong inode being linked. Note that unless flag::disable_safety_unlinks is set, this implementation opens a path_handle to the source containing directory first, then checks before linking that the item about to be hard linked has the same inode as the open file handle. It will retry this matching until success until the deadline given. This should prevent most unmalicious accidental loss of data.
Parameters
baseBase for any relative path.
pathThe relative or absolute new path to hard link to.
dThe deadline by which the matching of the containing directory to the open handle's inode must succeed, else errc::timed_out will be returned.
Memory Allocations\n Except on platforms with race free syscalls for renaming open handles (Windows), calls
current_path() via parent_path_handle() and thus is both expensive and calls malloc many times.

◆ list_extended_attributes()

virtual result<span<path_view_component> > llfio_v2_xxx::fs_handle::list_extended_attributes ( span< byte >  tofill) const
inlinevirtualnoexcept

Fill the supplied buffer with the names of all extended attributes set on this file or directory, returning a span of path view components.

Note that this routine is a very thin wrap of listxattr() on POSIX and NtQueryInformationFile() on Windows. If the supplied buffer is too small, the syscall typically returns failure rather than do a partial fill. Most implementations do not support more than 64Kb of extended attribute information per inode so maybe 70Kb is a safe default (to account for the return value storage), however properly written code will detect the buffer being too small and will auto-expand it until success.

Note
On Windows, this is the list of alternate streams on a file, NOT NTFS extended attributes.
Race Guarantees\n The list of extended attributes is fetched in a single syscall. This may be an
atomically consistent snapshot.

◆ parent_path_handle()

virtual result<path_handle> llfio_v2_xxx::fs_handle::parent_path_handle ( deadline  d = std::chrono::seconds(30)) const
inlinevirtualnoexcept

Obtain a handle to the path currently containing this handle's file entry.

Warning
This call is racy and can result in the wrong path handle being returned. Note that unless flag::disable_safety_unlinks is set, this implementation opens a path_handle to the source containing directory, then checks if the file entry within has the same inode as the open file handle. It will retry this matching until success until the deadline given.
Memory Allocations\n Calls current_path() and thus is both expensive and calls malloc many times.
See also
algorithm::cached_parent_handle_adapter<T> which overrides this with a zero cost implementation, thus making unlinking and relinking very considerably quicker.

◆ relink()

virtual result<void> llfio_v2_xxx::fs_handle::relink ( const path_handle base,
path_view_type  path,
bool  atomic_replace = true,
deadline  d = std::chrono::seconds(30) 
)
inlinevirtualnoexcept

Relinks the current path of this open handle to the new path specified. If atomic_replace is true, the relink atomically and silently replaces any item at the new path specified. This operation is both atomic and matching POSIX behaviour even on Microsoft Windows where no Win32 API can match POSIX semantics.

Note that if atomic_replace is false, the operation may be implemented as creating a hard link to the destination (which fails if the destination exists), opening a new file descriptor to the destination, closing the existing file descriptor, replacing the existing file descriptor with the new one (this is to ensure path tracking continues to work), then unlinking the previous link. Thus native_handle()'s value may change. This is not the case on Microsoft Windows nor Linux, both of which provide syscalls capable of refusing to rename if the destination exists.

If the handle refers to a pipe, on Microsoft Windows the base path handle is ignored as there is a single global named pipe namespace. Unless the path fragment begins with \, the string \??\ is prefixed to the name before passing it to the NT kernel API which performs the rename. This is because \\.\ in Win32 maps onto \??\ in the NT kernel.

Warning
Some operating systems provide a race free syscall for renaming an open handle (Windows). On all other operating systems this call is racy and can result in the wrong file entry being relinked. Note that unless flag::disable_safety_unlinks is set, this implementation opens a path_handle to the source containing directory first, then checks before relinking that the item about to be relinked has the same inode as the open file handle. It will retry this matching until success until the deadline given. This should prevent most unmalicious accidental loss of data.
Parameters
baseBase for any relative path.
pathThe relative or absolute new path to relink to.
atomic_replaceAtomically replace the destination if a file entry already is present there. Choosing false for this will fail if a file entry is already present at the destination, and may not be an atomic operation on some platforms (i.e. both the old and new names may be linked to the same inode for a very short period of time). Windows and recent Linuxes are always atomic.
dThe deadline by which the matching of the containing directory to the open handle's inode must succeed, else errc::timed_out will be returned.
Memory Allocations\n Except on platforms with race free syscalls for renaming open handles (Windows), calls
current_path() via parent_path_handle() and thus is both expensive and calls malloc many times.

Reimplemented in llfio_v2_xxx::symlink_handle, and llfio_v2_xxx::mapped_file_handle.

◆ remove_extended_attribute()

virtual result<void> llfio_v2_xxx::fs_handle::remove_extended_attribute ( path_view_component  )
inlinevirtualnoexcept

Removes the extended attribute set on this file or directory.

Note
On Windows, this is the list of alternate streams on a file, NOT NTFS extended attributes. We do not prevent you trying to remove internal alternate streams, either.

◆ set_extended_attribute()

virtual result<void> llfio_v2_xxx::fs_handle::set_extended_attribute ( path_view_component  name,
span< const byte >  value 
)
inlinevirtualnoexcept

Sets the value of an extended attribute on this file or directory.

To prevent collision in a globally visible resource, there is a convention whereby you ought to namespace the names of your values as namespace.attribute e.g. appname.setting to prevent unintentional collision with other programs. Obviously, do choose a unique appname if there is any chance another program might use the same namespace name.

On POSIX, there are additional namespacing requirements: before your value name, you need to prefix one of user or system, so the actual name you might set would be user.appname.propname. Windows does not have the user/system prefix requirement, but it does no harm to do the exact same on Windows as on POSIX.

The host OS and target filing system choose the limits on value size, and will fail accordingly. Some impose a maximum of 64Kb for all names and values per inode, others have a 4Kb maximum value size, there are lots of combinations. You are probably safest not setting many names, and keep the values short.

Warning
Extended attributes are 'brittle' because they can get silently wiped at any moment. Never store anything in extended attributes which cannot be recalculated if missing. The ideal use case for extended attributes is as a cache of additional metadata about a file or directory e.g. "I last checked this directory at timestamp X", or "the MD5 hash at last modified timestamp X for this file was Y". Also remember that other processes can and do arbitrarily modify extended attributes concurrent to you.

Windows only

This API is implemented as file alternate data streams, rather than the Extended Attributes API as accessed via NtSetEaFile() and NtQueryEaFile() (which actually modify the file alternate data stream $EA in any case).

The reason why is that NtSetEaFile() can only append new records to EA storage. It cannot deallocate any existing EA records, if you try to do so you will get STATUS_EA_CORRUPT_ERROR. You can append setting the same name to a different value, which can include a null value which then appears as if the name is no longer there. But there is a cap of 64kB for the EA record, and once it is consumed, it is gone forever for that inode.

Obviously that doesn't map at all well onto POSIX extended attributes, where you can set the value of an attribute as frequently as you like. The closest equivalent on Windows is therefore file alternate data streams, even though the attribute's value is then worked with as a whole proper file with all the attendant performance consequences.

As a result, name must be a valid filename and not contain any characters not permitted in a filename. We use the NT API here, so the character restrictions are far fewer than for the Win32 API e.g. single character names do NOT cause misoperation like on Win32.

◆ unlink()

virtual result<void> llfio_v2_xxx::fs_handle::unlink ( deadline  d = std::chrono::seconds(30))
inlinevirtualnoexcept

Unlinks the current path of this open handle, causing its entry to immediately disappear from the filing system.

On Windows before Windows 10 1709 unless flag::win_disable_unlink_emulation is set, this behaviour is simulated by renaming the file to something random and setting its delete-on-last-close flag. Note that Windows may prevent the renaming of a file in use by another process, if so it will NOT be renamed. After the next handle to that file closes, it will become permanently unopenable by anyone else until the last handle is closed, whereupon the entry will be eventually removed by the operating system.

Warning
Some operating systems provide a race free syscall for unlinking an open handle (Windows). On all other operating systems this call is racy and can result in the wrong file entry being unlinked. Note that unless flag::disable_safety_unlinks is set, this implementation opens a path_handle to the containing directory first, then checks that the item about to be unlinked has the same inode as the open file handle. It will retry this matching until success until the deadline given. This should prevent most unmalicious accidental loss of data.
Parameters
dThe deadline by which the matching of the containing directory to the open handle's inode must succeed, else errc::timed_out will be returned.
Memory Allocations\n Except on platforms with race free syscalls for unlinking open handles (Windows), calls
current_path() and thus is both expensive and calls malloc many times. On Windows, also calls current_path() if flag::disable_safety_unlinks is not set.

Reimplemented in llfio_v2_xxx::symlink_handle.

Friends And Related Function Documentation

◆ to_win32_path

result<filesystem::path> to_win32_path ( const fs_handle h,
win32_path_namespace  mapping 
)
friend

Maps the current path of h into a form suitable for Win32 APIs. Passes through unmodified on POSIX, so you can use this in portable code.

Returns
The mapped current path of h, which may have been validated to refer to the exact same inode via .unique_id() (see below).
Parameters
hThe handle whose .current_path() is to be mapped into a form suitable for Win32 APIs.
mappingWhich Win32 path namespace to map onto.

This implementation may need to validate that the mapping of the current path of h onto the desired Win32 path namespace does indeed refer to the same file:

  • win32_path_namespace::device transforms \!!\Device\... => \\.\... and ensures that the mapped file's unique id matches the original, otherwise returning failure.
  • win32_path_namespace::dos enumerates all the DOS devices on the system and what those map onto within the NT kernel namespace. This mapping is for obvious reasons quite slow.
  • win32_path_namespace::guid_volume simply fetches the GUID of the volume of the handle, and constructs a valid Win32 path from that.
  • win32_path_namespace::any means attempt guid_volume first, and if it fails (e.g. your file is on a network share) then it attempts dos. This semantic may change in the future, however any path emitted will always be a valid Win32 path.
145  {
146  (void) mapping;
147  return h._get_handle().current_path();
148  }

The documentation for this class was generated from the following file: