123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305 |
- /*************************************************************************
- *
- * Copyright 2016 Realm Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- **************************************************************************/
- #ifndef REALM_UTIL_FILE_HPP
- #define REALM_UTIL_FILE_HPP
- #include <cstddef>
- #include <cstdint>
- #include <ctime>
- #include <functional>
- #include <memory>
- #include <streambuf>
- #include <string>
- #ifdef _WIN32
- #include <Windows.h>
- #else
- #include <dirent.h> // POSIX.1-2001
- #endif
- #include <realm/utilities.hpp>
- #include <realm/util/assert.hpp>
- #include <realm/exceptions.hpp>
- #include <realm/util/features.h>
- #include <realm/util/function_ref.hpp>
- #include <realm/util/safe_int_ops.hpp>
- #if defined(_MSVC_LANG) // compiling with MSVC
- #include <filesystem>
- #define REALM_HAVE_STD_FILESYSTEM 1
- #else
- #define REALM_HAVE_STD_FILESYSTEM 0
- #endif
- #if REALM_APPLE_DEVICE && !REALM_TVOS && !REALM_MACCATALYST
- #define REALM_FILELOCK_EMULATION
- #endif
- namespace realm::util {
- class EncryptedFileMapping;
- class WriteObserver;
- /// Create the specified directory in the file system.
- ///
- /// \throw FileAccessError If the directory could not be created. If
- /// the reason corresponds to one of the exception types that are
- /// derived from FileAccessError, the derived exception type is
- /// thrown (as long as the underlying system provides the information
- /// to unambiguously distinguish that particular reason).
- void make_dir(const std::string& path);
- /// Same as make_dir() except that this one returns false, rather than throwing
- /// an exception, if the specified directory already existed. If the directory
- /// did not already exist and was newly created, this returns true.
- bool try_make_dir(const std::string& path);
- /// Recursively create each of the directories in the given absolute path. Existing directories are ignored, and
- /// FileAccessError is thrown for any other errors that occur.
- void make_dir_recursive(std::string path);
- /// Remove the specified empty directory path from the file system. It is an
- /// error if the specified path is not a directory, or if it is a nonempty
- /// directory. In so far as the specified path is a directory, std::remove(const
- /// char*) is equivalent to this function.
- ///
- /// \throw FileAccessError If the directory could not be removed. If the
- /// reason corresponds to one of the exception types that are derived from
- /// FileAccessError, the derived exception type is thrown (as long as the
- /// underlying system provides the information to unambiguously distinguish that
- /// particular reason).
- void remove_dir(const std::string& path);
- /// Same as remove_dir() except that this one returns false, rather
- /// than throwing an exception, if the specified directory did not
- /// exist. If the directory did exist, and was deleted, this function
- /// returns true.
- bool try_remove_dir(const std::string& path);
- /// Remove the specified directory after removing all its contents. Files
- /// (nondirectory entries) will be removed as if by a call to File::remove(),
- /// and empty directories as if by a call to remove_dir().
- ///
- /// Returns false if the directory already did not exist and true otherwise.
- ///
- /// \throw FileAccessError If the directory existed and removal of the directory or any of its contents fails.
- ///
- /// remove_dir_recursive() assumes that no other process or thread is making
- /// simultaneous changes in the directory.
- bool try_remove_dir_recursive(const std::string& path);
- /// Create a new unique directory for temporary files. The absolute
- /// path to the new directory is returned without a trailing slash.
- std::string make_temp_dir();
- /// Create a new temporary file.
- std::string make_temp_file(const char* prefix);
- size_t page_size();
- /// This class provides a RAII abstraction over the concept of a file
- /// descriptor (or file handle).
- ///
- /// Locks are automatically and immediately released when the File
- /// instance is closed.
- ///
- /// You can use CloseGuard and UnlockGuard to acheive exception-safe
- /// closing or unlocking prior to the File instance being detroyed.
- ///
- /// A single File instance must never be accessed concurrently by
- /// multiple threads.
- ///
- /// You can write to a file via an std::ostream as follows:
- ///
- /// \code{.cpp}
- ///
- /// File::Streambuf my_streambuf(&my_file);
- /// std::ostream out(&my_strerambuf);
- /// out << 7945.9;
- ///
- /// \endcode
- class File {
- public:
- enum Mode {
- mode_Read, ///< access_ReadOnly, create_Never (fopen: rb)
- mode_Update, ///< access_ReadWrite, create_Never (fopen: rb+)
- mode_Write, ///< access_ReadWrite, create_Auto, flag_Trunc (fopen: wb+)
- mode_Append ///< access_ReadWrite, create_Auto, flag_Append (fopen: ab+)
- };
- /// Equivalent to calling open(const std::string&, Mode) on a
- /// default constructed instance.
- explicit File(const std::string& path, Mode = mode_Read);
- /// Create an instance that is not initially attached to an open
- /// file.
- File() = default;
- ~File() noexcept;
- File(File&&) noexcept;
- File& operator=(File&&) noexcept;
- // Disable copying by l-value. Copying an open file will create a scenario
- // where the same file descriptor will be opened once but closed twice.
- File(const File&) = delete;
- File& operator=(const File&) = delete;
- /// Calling this function on an instance that is already attached
- /// to an open file has undefined behavior.
- ///
- /// \throw AccessError If the file could not be opened. If the
- /// reason corresponds to one of the exception types that are
- /// derived from AccessError, the derived exception type is thrown
- /// (as long as the underlying system provides the information to
- /// unambiguously distinguish that particular reason).
- void open(const std::string& path, Mode = mode_Read);
- /// This function is idempotent, that is, it is valid to call it
- /// regardless of whether this instance currently is attached to
- /// an open file.
- void close() noexcept;
- /// Check whether this File instance is currently attached to an
- /// open file.
- bool is_attached() const noexcept;
- enum AccessMode {
- access_ReadOnly,
- access_ReadWrite,
- };
- enum CreateMode {
- create_Auto, ///< Create the file if it does not already exist.
- create_Never, ///< Fail if the file does not already exist.
- create_Must ///< Fail if the file already exists.
- };
- enum {
- flag_Trunc = 1, ///< Truncate the file if it already exists.
- flag_Append = 2 ///< Move to end of file before each write.
- };
- /// See open(const std::string&, Mode).
- ///
- /// Specifying access_ReadOnly together with a create mode that is
- /// not create_Never, or together with a non-zero \a flags
- /// argument, results in undefined behavior. Specifying flag_Trunc
- /// together with create_Must results in undefined behavior.
- void open(const std::string& path, AccessMode, CreateMode, int flags);
- /// Same as open(path, access_ReadWrite, create_Auto, 0), except
- /// that this one returns an indication of whether a new file was
- /// created, or an existing file was opened.
- void open(const std::string& path, bool& was_created);
- /// Read data into the specified buffer and return the number of
- /// bytes read. If the returned number of bytes is less than \a
- /// size, then the end of the file has been reached.
- ///
- /// Calling this function on an instance, that is not currently
- /// attached to an open file, has undefined behavior.
- size_t read(char* data, size_t size);
- static size_t read_static(FileDesc fd, char* data, size_t size);
- /// Write the specified data to this file.
- ///
- /// Calling this function on an instance, that is not currently
- /// attached to an open file, has undefined behavior.
- ///
- /// Calling this function on an instance, that was opened in
- /// read-only mode, has undefined behavior.
- void write(const char* data, size_t size);
- static void write_static(FileDesc fd, const char* data, size_t size);
- // Tells current file pointer of fd
- static uint64_t get_file_pos(FileDesc fd);
- /// Calls write(s.data(), s.size()).
- void write(const std::string& s)
- {
- write(s.data(), s.size());
- }
- /// Calls read(data, N).
- template <size_t N>
- size_t read(char (&data)[N])
- {
- return read(data, N);
- }
- /// Calls write(data(), N).
- template <size_t N>
- void write(const char (&data)[N])
- {
- write(data, N);
- }
- /// Plays the same role as off_t in POSIX
- typedef int_fast64_t SizeType;
- /// Calling this function on an instance that is not attached to
- /// an open file has undefined behavior.
- SizeType get_size() const;
- static SizeType get_size_static(FileDesc fd);
- static SizeType get_size_static(const std::string& path);
- /// If this causes the file to grow, then the new section will
- /// have undefined contents. Setting the size with this function
- /// does not necessarily allocate space on the target device. If
- /// you want to ensure allocation, call alloc(). Calling this
- /// function will generally affect the read/write offset
- /// associated with this File instance.
- ///
- /// Calling this function on an instance that is not attached to
- /// an open file has undefined behavior. Calling this function on
- /// a file that is opened in read-only mode, is an error.
- void resize(SizeType);
- /// Same effect as prealloc_if_supported(original_size, new_size);
- ///
- /// The downside is that this function is not guaranteed to have
- /// atomic behaviour on all systems, that is, two processes, or
- /// two threads should never call this function concurrently for
- /// the same underlying file even though they access the file
- /// through distinct File instances.
- ///
- /// \sa prealloc_if_supported()
- void prealloc(size_t new_size);
- /// When supported by the system, allocate space on the target
- /// device for the specified region of the file. If the region
- /// extends beyond the current end of the file, the file size is
- /// increased as necessary.
- ///
- /// On systems that do not support this operation, this function
- /// has no effect. You may call is_prealloc_supported() to
- /// determine if it is supported on your system.
- ///
- /// Calling this function on an instance, that is not attached to
- /// an open file, has undefined behavior. Calling this function on
- /// a file, that is opened in read-only mode, is an error.
- ///
- /// This function is guaranteed to have atomic behaviour, that is,
- /// there is never any risk of the file size being reduced even
- /// with concurrently executing invocations.
- ///
- /// \sa prealloc()
- /// \sa is_prealloc_supported()
- bool prealloc_if_supported(SizeType offset, size_t size);
- /// See prealloc_if_supported().
- static bool is_prealloc_supported();
- /// Reposition the read/write offset of this File
- /// instance. Distinct File instances have separate independent
- /// offsets, as long as the cucrrent process is not forked.
- void seek(SizeType);
- static void seek_static(FileDesc, SizeType);
- /// Flush in-kernel buffers to disk. This blocks the caller until the
- /// synchronization operation is complete. On POSIX systems this function
- /// calls `fsync()`. On Apple platforms if calls `fcntl()` with command
- /// `F_FULLFSYNC`.
- void sync();
- /// Issue a write barrier which forbids ordering writes after this call
- /// before writes performed before this call. Equivalent to `sync()` on
- /// non-Apple platforms.
- void barrier();
- /// Place an exclusive lock on this file. This blocks the caller
- /// until all other locks have been released.
- ///
- /// Locks acquired on distinct File instances have fully recursive
- /// behavior, even if they are acquired in the same process (or
- /// thread) and are attached to the same underlying file.
- ///
- /// Calling this function on an instance that is not attached to
- /// an open file, or on an instance that is already locked has
- /// undefined behavior.
- void lock();
- /// Non-blocking version of `lock()`. Returns true if the lock was acquired
- /// and false otherwise.
- bool try_lock();
- /// Release a previously acquired lock on this file which was acquired with
- /// `lock()` or `try_lock()`. Calling this without holding the lock or
- /// while holding a lock acquired with one of the `rw` functions is
- /// undefined behavior.
- void unlock() noexcept;
- /// Place an shared lock on this file. This blocks the caller
- /// until all other locks have been released.
- ///
- /// Locks acquired on distinct File instances have fully recursive
- /// behavior, even if they are acquired in the same process (or
- /// thread) and are attached to the same underlying file.
- ///
- /// Calling this function on an instance that is not attached to an open
- /// file, on an instance that is already locked, or on a file which
- /// `lock()` (rather than `try_rw_lock_exclusive()` has been called on has
- /// undefined behavior.
- void rw_lock_shared();
- /// Attempt to place an exclusive lock on this file. Returns true if the
- /// lock could be acquired, and false if an exclusive or shared lock exists
- /// for the file.
- ///
- /// Locks acquired on distinct File instances have fully recursive
- /// behavior, even if they are acquired in the same process (or
- /// thread) and are attached to the same underlying file.
- ///
- /// Calling this function on an instance that is not attached to
- /// an open file, or on an instance that is already locked has
- /// undefined behavior.
- bool try_rw_lock_exclusive();
- /// Non-blocking version of lock_shared(). Returns true iff it
- /// succeeds.
- bool try_rw_lock_shared();
- /// Release a previously acquired read-write lock on this file acquired
- /// with `rw_lock_shared()`, `try_rw_lock_exclusive()` or
- /// `try_rw_lock_shared()`. Calling this after a call to `lock()` or
- /// without holding the lock is undefined behavior.
- void rw_unlock() noexcept;
- /// Set the encryption key used for this file. Must be called before any
- /// mappings are created or any data is read from or written to the file.
- ///
- /// \param key A 64-byte encryption key, or null to disable encryption.
- void set_encryption_key(const char* key);
- /// Get the encryption key set by set_encryption_key(),
- /// null_ptr if no key set.
- const char* get_encryption_key() const;
- /// Set the path used for emulating file locks. If not set explicitly,
- /// the emulation will use the path of the file itself suffixed by ".fifo"
- void set_fifo_path(const std::string& fifo_dir_path, const std::string& fifo_file_name);
- enum {
- /// If possible, disable opportunistic flushing of dirted
- /// pages of a memory mapped file to physical medium. On some
- /// systems this cannot be disabled. On other systems it is
- /// the default behavior. An explicit call to sync_map() will
- /// flush the buffers regardless of whether this flag is
- /// specified or not.
- map_NoSync = 1
- };
- /// Map this file into memory. The file is mapped as shared
- /// memory. This allows two processes to interact under exatly the
- /// same rules as applies to the interaction via regular memory of
- /// multiple threads inside a single process.
- ///
- /// This File instance does not need to remain in existence after
- /// the mapping is established.
- ///
- /// Multiple concurrent mappings may be created from the same File
- /// instance.
- ///
- /// Specifying access_ReadWrite for a file that is opened in
- /// read-only mode, is an error.
- ///
- /// Calling this function on an instance that is not attached to
- /// an open file, or one that is attached to an empty file has
- /// undefined behavior.
- ///
- /// Calling this function with a size that is greater than the
- /// size of the file has undefined behavior.
- void* map(AccessMode, size_t size, int map_flags = 0, size_t offset = 0) const;
- void* map_fixed(AccessMode, void* address, size_t size, int map_flags = 0, size_t offset = 0) const;
- void* map_reserve(AccessMode, size_t size, size_t offset) const;
- /// The same as unmap(old_addr, old_size) followed by map(a,
- /// new_size, map_flags), but more efficient on some systems.
- ///
- /// The old address range must have been acquired by a call to
- /// map() or remap() on this File instance, the specified access
- /// mode and flags must be the same as the ones specified
- /// previously, and this File instance must not have been reopend
- /// in the meantime. Failing to adhere to these rules will result
- /// in undefined behavior.
- ///
- /// If this function throws, the old address range will remain
- /// mapped.
- void* remap(void* old_addr, size_t old_size, AccessMode a, size_t new_size, int map_flags = 0,
- size_t file_offset = 0) const;
- #if REALM_ENABLE_ENCRYPTION
- void* map(AccessMode, size_t size, EncryptedFileMapping*& mapping, int map_flags = 0, size_t offset = 0) const;
- void* map_fixed(AccessMode, void* address, size_t size, EncryptedFileMapping* mapping, int map_flags = 0,
- size_t offset = 0) const;
- void* map_reserve(AccessMode, size_t size, size_t offset, EncryptedFileMapping*& mapping) const;
- #endif
- /// Unmap the specified address range which must have been
- /// previously returned by map().
- static void unmap(void* addr, size_t size) noexcept;
- /// Flush in-kernel buffers to disk. This blocks the caller until
- /// the synchronization operation is complete. The specified
- /// address range must be (a subset of) one that was previously returned by
- /// map().
- static void sync_map(FileDesc fd, void* addr, size_t size);
- /// Check whether the specified file or directory exists. Note
- /// that a file or directory that resides in a directory that the
- /// calling process has no access to, will necessarily be reported
- /// as not existing.
- static bool exists(const std::string& path);
- /// Get the time of last modification made to the file
- static time_t last_write_time(const std::string& path);
- /// Get freespace (in bytes) of filesystem containing path
- static SizeType get_free_space(const std::string& path);
- /// Check whether the specified path exists and refers to a directory. If
- /// the referenced file system object resides in an inaccessible directory,
- /// this function returns false.
- static bool is_dir(const std::string& path);
- /// Remove the specified file path from the file system. It is an error if
- /// the specified path is a directory. If the specified file is a symbolic
- /// link, the link is removed, leaving the liked file intact. In so far as
- /// the specified path is not a directory, std::remove(const char*) is
- /// equivalent to this function.
- ///
- /// The specified file must not be open by the calling process. If
- /// it is, this function has undefined behaviour. Note that an
- /// open memory map of the file counts as "the file being open".
- ///
- /// \throw AccessError If the specified directory entry could not
- /// be removed. If the reason corresponds to one of the exception
- /// types that are derived from AccessError, the derived exception
- /// type is thrown (as long as the underlying system provides the
- /// information to unambiguously distinguish that particular
- /// reason).
- static void remove(const std::string& path);
- /// Same as remove() except that this one returns false, rather
- /// than throwing an exception, if the specified file does not
- /// exist. If the file did exist, and was deleted, this function
- /// returns true.
- static bool try_remove(const std::string& path);
- /// Change the path of a directory entry. This can be used to
- /// rename a file, and/or to move it from one directory to
- /// another. This function is equivalent to std::rename(const
- /// char*, const char*).
- ///
- /// \throw AccessError If the path of the directory entry could
- /// not be changed. If the reason corresponds to one of the
- /// exception types that are derived from AccessError, the derived
- /// exception type is thrown (as long as the underlying system
- /// provides the information to unambiguously distinguish that
- /// particular reason).
- static void move(const std::string& old_path, const std::string& new_path);
- /// Copy the file at the specified origin path to the specified target path.
- static void copy(const std::string& origin_path, const std::string& target_path);
- /// Compare the two files at the specified paths for equality. Returns true
- /// if, and only if they are equal.
- static bool compare(const std::string& path_1, const std::string& path_2);
- /// Check whether two open file descriptors refer to the same
- /// underlying file, that is, if writing via one of them, will
- /// affect what is read from the other. In UNIX this boils down to
- /// comparing inode numbers.
- ///
- /// Both instances have to be attached to open files. If they are
- /// not, this function has undefined behavior.
- bool is_same_file(const File&) const;
- static bool is_same_file_static(FileDesc f1, FileDesc f2);
- /// Resolve the specified path against the specified base directory.
- ///
- /// If \a path is absolute, or if \a base_dir is empty, \p path is returned
- /// unmodified, otherwise \a path is resolved against \a base_dir.
- ///
- /// Examples (assuming POSIX):
- ///
- /// resolve("file", "dir") -> "dir/file"
- /// resolve("../baz", "/foo/bar") -> "/foo/baz"
- /// resolve("foo", ".") -> "./foo"
- /// resolve(".", "/foo/") -> "/foo"
- /// resolve("..", "foo") -> "."
- /// resolve("../..", "foo") -> ".."
- /// resolve("..", "..") -> "../.."
- /// resolve("", "") -> "."
- /// resolve("", "/") -> "/."
- /// resolve("..", "/") -> "/."
- /// resolve("..", "foo//bar") -> "foo"
- ///
- /// This function does not access the file system.
- ///
- /// \param path The path to be resolved. An empty string produces the same
- /// result as as if "." was passed. The result has a trailing directory
- /// separator (`/`) if, and only if this path has a trailing directory
- /// separator.
- ///
- /// \param base_dir The base directory path, which may be relative or
- /// absolute. A final directory separator (`/`) is optional. The empty
- /// string is interpreted as a relative path.
- static std::string resolve(const std::string& path, const std::string& base_dir);
- /// Same effect as std::filesystem::path::parent_path().
- static std::string parent_dir(const std::string& path);
- using ForEachHandler = util::FunctionRef<bool(const std::string& file, const std::string& dir)>;
- /// Scan the specified directory recursivle, and report each file
- /// (nondirectory entry) via the specified handler.
- ///
- /// The first argument passed to the handler is the name of a file (not the
- /// whole path), and the second argument is the directory in which that file
- /// resides. The directory will be specified as a path, and relative to \a
- /// dir_path. The directory will be the empty string for files residing
- /// directly in \a dir_path.
- ///
- /// If the handler returns false, scanning will be aborted immediately, and
- /// for_each() will return false. Otherwise for_each() will return true.
- ///
- /// Scanning is done as if by a recursive set of DirScanner objects.
- static bool for_each(const std::string& dir_path, ForEachHandler handler);
- struct UniqueID {
- #ifdef _WIN32 // Windows version
- FILE_ID_INFO id_info;
- #else
- UniqueID()
- : device(0)
- , inode(0)
- {
- }
- UniqueID(dev_t d, ino_t i)
- : device(d)
- , inode(i)
- {
- }
- // NDK r10e has a bug in sys/stat.h dev_t ino_t are 4 bytes,
- // but stat.st_dev and st_ino are 8 bytes. So we just use uint64 instead.
- dev_t device;
- uint_fast64_t inode;
- #endif
- };
- // Return the unique id for the current opened file descriptor.
- // Same UniqueID means they are the same file.
- UniqueID get_unique_id() const;
- // Return the file descriptor for the file
- FileDesc get_descriptor() const;
- // Return the path of the open file, or an empty string if
- // this file has never been opened.
- std::string get_path() const;
- // Return none if the file doesn't exist. Throws on other errors.
- static std::optional<UniqueID> get_unique_id(const std::string& path);
- // Return the unique id for the file descriptor. Throws if the underlying stat operation fails.
- static UniqueID get_unique_id(FileDesc file);
- template <class>
- class Map;
- class CloseGuard;
- class UnlockGuard;
- class UnmapGuard;
- class Streambuf;
- private:
- #ifdef _WIN32
- HANDLE m_fd = nullptr;
- bool m_have_lock = false; // Only valid when m_fd is not null
- #else
- int m_fd = -1;
- #ifdef REALM_FILELOCK_EMULATION
- int m_pipe_fd = -1; // -1 if no pipe has been allocated for emulation
- bool m_has_exclusive_lock = false;
- std::string m_fifo_dir_path;
- std::string m_fifo_path;
- #endif
- #endif
- std::unique_ptr<const char[]> m_encryption_key = nullptr;
- std::string m_path;
- bool lock(bool exclusive, bool non_blocking);
- bool rw_lock(bool exclusive, bool non_blocking);
- void open_internal(const std::string& path, AccessMode, CreateMode, int flags, bool* success);
- #ifdef REALM_FILELOCK_EMULATION
- bool has_shared_lock() const noexcept
- {
- return m_pipe_fd != -1;
- }
- #endif
- struct MapBase {
- void* m_addr = nullptr;
- mutable size_t m_size = 0;
- size_t m_reservation_size = 0;
- size_t m_offset = 0;
- FileDesc m_fd;
- AccessMode m_access_mode = access_ReadOnly;
- MapBase() noexcept = default;
- ~MapBase() noexcept;
- // Disable copying. Copying an opened MapBase will create a scenario
- // where the same memory will be mapped once but unmapped twice.
- MapBase(const MapBase&) = delete;
- MapBase& operator=(const MapBase&) = delete;
- // Use
- void map(const File&, AccessMode, size_t size, int map_flags, size_t offset = 0,
- util::WriteObserver* observer = nullptr);
- // reserve address space for later mapping operations.
- // returns false if reservation can't be done.
- bool try_reserve(const File&, AccessMode, size_t size, size_t offset = 0,
- util::WriteObserver* observer = nullptr);
- void remap(const File&, AccessMode, size_t size, int map_flags);
- void unmap() noexcept;
- // fully update any process shared representation (e.g. buffer cache).
- // other processes will be able to see changes, but a full platform crash
- // may loose data
- void flush();
- // try to extend the mapping in-place. Virtual address space must have
- // been set aside earlier by a call to reserve()
- bool try_extend_to(size_t size) noexcept;
- // fully synchronize any underlying storage. After completion, a full platform
- // crash will *not* have lost data.
- void sync();
- #if REALM_ENABLE_ENCRYPTION
- mutable util::EncryptedFileMapping* m_encrypted_mapping = nullptr;
- inline util::EncryptedFileMapping* get_encrypted_mapping() const
- {
- return m_encrypted_mapping;
- }
- #else
- inline util::EncryptedFileMapping* get_encrypted_mapping() const
- {
- return nullptr;
- }
- #endif
- };
- };
- /// This class provides a RAII abstraction over the concept of a
- /// memory mapped file.
- ///
- /// Once created, the Map instance makes no reference to the File
- /// instance that it was based upon, and that File instance may be
- /// destroyed before the Map instance is destroyed.
- ///
- /// Multiple concurrent mappings may be created from the same File
- /// instance.
- ///
- /// You can use UnmapGuard to acheive exception-safe unmapping prior
- /// to the Map instance being detroyed.
- ///
- /// A single Map instance must never be accessed concurrently by
- /// multiple threads.
- template <class T>
- class File::Map : private MapBase {
- public:
- /// Equivalent to calling map() on a default constructed instance.
- explicit Map(const File&, AccessMode = access_ReadOnly, size_t size = sizeof(T), int map_flags = 0,
- util::WriteObserver* observer = nullptr);
- explicit Map(const File&, size_t offset, AccessMode = access_ReadOnly, size_t size = sizeof(T), int map_flags = 0,
- util::WriteObserver* observer = nullptr);
- /// Create an instance that is not initially attached to a memory
- /// mapped file.
- Map() noexcept;
- // Disable copying. Copying an opened Map will create a scenario
- // where the same memory will be mapped once but unmapped twice.
- Map(const Map&) = delete;
- Map& operator=(const Map&) = delete;
- /// Move the mapping from another Map object to this Map object
- File::Map<T>& operator=(File::Map<T>&& other) noexcept
- {
- REALM_ASSERT(this != &other);
- if (m_addr)
- unmap();
- m_addr = other.get_addr();
- m_size = other.m_size;
- m_access_mode = other.m_access_mode;
- m_reservation_size = other.m_reservation_size;
- m_offset = other.m_offset;
- m_fd = other.m_fd;
- other.m_offset = 0;
- other.m_addr = nullptr;
- other.m_size = other.m_reservation_size = 0;
- #if REALM_ENABLE_ENCRYPTION
- m_encrypted_mapping = other.m_encrypted_mapping;
- other.m_encrypted_mapping = nullptr;
- #endif
- return *this;
- }
- Map(Map&& other) noexcept
- {
- *this = std::move(other);
- }
- /// See File::map().
- ///
- /// Calling this function on a Map instance that is already
- /// attached to a memory mapped file has undefined behavior. The
- /// returned pointer is the same as what will subsequently be
- /// returned by get_addr().
- T* map(const File&, AccessMode = access_ReadOnly, size_t size = sizeof(T), int map_flags = 0, size_t offset = 0,
- util::WriteObserver* observer = nullptr);
- /// See File::unmap(). This function is idempotent, that is, it is
- /// valid to call it regardless of whether this instance is
- /// currently attached to a memory mapped file.
- void unmap() noexcept;
- bool try_reserve(const File&, AccessMode a = access_ReadOnly, size_t size = sizeof(T), size_t offset = 0,
- util::WriteObserver* observer = nullptr);
- /// See File::remap().
- ///
- /// Calling this function on a Map instance that is not currently attached
- /// to a memory mapped file is equivalent to calling map(). The returned
- /// pointer is the same as what will subsequently be returned by
- /// get_addr().
- T* remap(const File&, AccessMode = access_ReadOnly, size_t size = sizeof(T), int map_flags = 0);
- /// Try to extend the existing mapping to a given size
- bool try_extend_to(size_t size) noexcept;
- /// See File::sync_map().
- ///
- /// Calling this function on an instance that is not currently
- /// attached to a memory mapped file, has undefined behavior.
- void sync();
- void flush();
- /// Check whether this Map instance is currently attached to a
- /// memory mapped file.
- bool is_attached() const noexcept;
- /// Returns a pointer to the beginning of the memory mapped file,
- /// or null if this instance is not currently attached.
- T* get_addr() const noexcept;
- /// Returns the size of the mapped region, or zero if this
- /// instance does not currently refer to a memory mapped
- /// file. When this instance refers to a memory mapped file, the
- /// returned value will always be identical to the size passed to
- /// the constructor or to map().
- size_t get_size() const noexcept;
- /// Release the currently attached memory mapped file from this
- /// Map instance. The address range may then be unmapped later by
- /// a call to File::unmap().
- T* release() noexcept;
- bool is_writeable() const noexcept
- {
- return m_access_mode == access_ReadWrite;
- }
- #if REALM_ENABLE_ENCRYPTION
- /// Get the encrypted file mapping corresponding to this mapping
- inline EncryptedFileMapping* get_encrypted_mapping() const
- {
- return m_encrypted_mapping;
- }
- #else
- inline EncryptedFileMapping* get_encrypted_mapping() const
- {
- return nullptr;
- }
- #endif
- friend class UnmapGuard;
- };
- class File::CloseGuard {
- public:
- CloseGuard(File& f) noexcept
- : m_file(&f)
- {
- }
- ~CloseGuard() noexcept
- {
- if (m_file)
- m_file->close();
- }
- void release() noexcept
- {
- m_file = nullptr;
- }
- // Disallow the default implementation of copy/assign, this is not how this
- // class is intended to be used. For example we could get unexpected
- // behaviour if one CloseGuard is copied and released but the other is not.
- CloseGuard(const CloseGuard&) = delete;
- CloseGuard& operator=(const CloseGuard&) = delete;
- private:
- File* m_file;
- };
- class File::UnlockGuard {
- public:
- UnlockGuard(File& f) noexcept
- : m_file(&f)
- {
- }
- ~UnlockGuard() noexcept
- {
- if (m_file)
- m_file->rw_unlock();
- }
- void release() noexcept
- {
- m_file = nullptr;
- }
- // Disallow the default implementation of copy/assign, this is not how this
- // class is intended to be used. For example we could get unexpected
- // behaviour if one UnlockGuard is copied and released but the other is not.
- UnlockGuard(const UnlockGuard&) = delete;
- UnlockGuard& operator=(const UnlockGuard&) = delete;
- private:
- File* m_file;
- };
- class File::UnmapGuard {
- public:
- template <class T>
- UnmapGuard(Map<T>& m) noexcept
- : m_map(&m)
- {
- }
- ~UnmapGuard() noexcept
- {
- if (m_map)
- m_map->unmap();
- }
- void release() noexcept
- {
- m_map = nullptr;
- }
- // Disallow the default implementation of copy/assign, this is not how this
- // class is intended to be used. For example we could get unexpected
- // behaviour if one UnmapGuard is copied and released but the other is not.
- UnmapGuard(const UnmapGuard&) = delete;
- UnmapGuard& operator=(const UnmapGuard&) = delete;
- private:
- MapBase* m_map;
- };
- /// Only output is supported at this point.
- class File::Streambuf : public std::streambuf {
- public:
- explicit Streambuf(File*, size_t = 4096);
- ~Streambuf() noexcept;
- // Disable copying
- Streambuf(const Streambuf&) = delete;
- Streambuf& operator=(const Streambuf&) = delete;
- private:
- File& m_file;
- std::unique_ptr<char[]> const m_buffer;
- int_type overflow(int_type) override;
- int sync() override;
- pos_type seekpos(pos_type, std::ios_base::openmode) override;
- void flush();
- };
- class DirScanner {
- public:
- DirScanner(const std::string& path, bool allow_missing = false);
- ~DirScanner() noexcept;
- bool next(std::string& name);
- private:
- #ifndef _WIN32
- DIR* m_dirp;
- #elif REALM_HAVE_STD_FILESYSTEM
- std::filesystem::directory_iterator m_iterator;
- #endif
- };
- // Implementation:
- inline File::File(const std::string& path, Mode m)
- {
- open(path, m);
- }
- inline File::~File() noexcept
- {
- close();
- }
- inline void File::set_fifo_path(const std::string& fifo_dir_path, const std::string& fifo_file_name)
- {
- #ifdef REALM_FILELOCK_EMULATION
- m_fifo_dir_path = fifo_dir_path;
- m_fifo_path = fifo_dir_path + "/" + fifo_file_name;
- #else
- static_cast<void>(fifo_dir_path);
- static_cast<void>(fifo_file_name);
- #endif
- }
- inline File::File(File&& f) noexcept
- {
- #ifdef _WIN32
- m_fd = f.m_fd;
- m_have_lock = f.m_have_lock;
- f.m_fd = nullptr;
- #else
- m_fd = f.m_fd;
- #ifdef REALM_FILELOCK_EMULATION
- m_pipe_fd = f.m_pipe_fd;
- m_has_exclusive_lock = f.m_has_exclusive_lock;
- f.m_has_exclusive_lock = false;
- f.m_pipe_fd = -1;
- #endif
- f.m_fd = -1;
- #endif
- m_encryption_key = std::move(f.m_encryption_key);
- }
- inline File& File::operator=(File&& f) noexcept
- {
- close();
- #ifdef _WIN32
- m_fd = f.m_fd;
- m_have_lock = f.m_have_lock;
- f.m_fd = nullptr;
- #else
- m_fd = f.m_fd;
- f.m_fd = -1;
- #ifdef REALM_FILELOCK_EMULATION
- m_pipe_fd = f.m_pipe_fd;
- f.m_pipe_fd = -1;
- m_has_exclusive_lock = f.m_has_exclusive_lock;
- f.m_has_exclusive_lock = false;
- #endif
- #endif
- m_encryption_key = std::move(f.m_encryption_key);
- return *this;
- }
- inline void File::open(const std::string& path, Mode m)
- {
- AccessMode a = access_ReadWrite;
- CreateMode c = create_Auto;
- int flags = 0;
- switch (m) {
- case mode_Read:
- a = access_ReadOnly;
- c = create_Never;
- break;
- case mode_Update:
- c = create_Never;
- break;
- case mode_Write:
- flags = flag_Trunc;
- break;
- case mode_Append:
- flags = flag_Append;
- break;
- }
- open(path, a, c, flags);
- }
- inline void File::open(const std::string& path, AccessMode am, CreateMode cm, int flags)
- {
- open_internal(path, am, cm, flags, nullptr);
- }
- inline void File::open(const std::string& path, bool& was_created)
- {
- while (1) {
- bool success;
- open_internal(path, access_ReadWrite, create_Must, 0, &success);
- if (success) {
- was_created = true;
- return;
- }
- open_internal(path, access_ReadWrite, create_Never, 0, &success);
- if (success) {
- was_created = false;
- return;
- }
- }
- }
- inline bool File::is_attached() const noexcept
- {
- #ifdef _WIN32
- return (m_fd != nullptr);
- #else
- return 0 <= m_fd;
- #endif
- }
- inline void File::rw_lock_shared()
- {
- rw_lock(false, false);
- }
- inline bool File::try_rw_lock_exclusive()
- {
- return rw_lock(true, true);
- }
- inline bool File::try_rw_lock_shared()
- {
- return rw_lock(false, true);
- }
- inline void File::lock()
- {
- lock(true, false);
- }
- inline bool File::try_lock()
- {
- return lock(true, true);
- }
- inline File::MapBase::~MapBase() noexcept
- {
- unmap();
- }
- template <class T>
- inline File::Map<T>::Map(const File& f, AccessMode a, size_t size, int map_flags, util::WriteObserver* observer)
- {
- map(f, a, size, map_flags, 0, observer);
- }
- template <class T>
- inline File::Map<T>::Map(const File& f, size_t offset, AccessMode a, size_t size, int map_flags,
- util::WriteObserver* observer)
- {
- map(f, a, size, map_flags, offset, observer);
- }
- template <class T>
- inline File::Map<T>::Map() noexcept
- {
- }
- template <class T>
- inline T* File::Map<T>::map(const File& f, AccessMode a, size_t size, int map_flags, size_t offset,
- util::WriteObserver* observer)
- {
- MapBase::map(f, a, size, map_flags, offset, observer);
- return static_cast<T*>(m_addr);
- }
- template <class T>
- inline bool File::Map<T>::try_reserve(const File& f, AccessMode a, size_t size, size_t offset,
- util::WriteObserver* observer)
- {
- return MapBase::try_reserve(f, a, size, offset, observer);
- }
- template <class T>
- inline void File::Map<T>::unmap() noexcept
- {
- MapBase::unmap();
- }
- template <class T>
- inline T* File::Map<T>::remap(const File& f, AccessMode a, size_t size, int map_flags)
- {
- // MapBase::remap(f, a, size, map_flags);
- // missing sync() here?
- unmap();
- map(f, a, size, map_flags);
- return static_cast<T*>(m_addr);
- }
- template <class T>
- inline bool File::Map<T>::try_extend_to(size_t size) noexcept
- {
- return MapBase::try_extend_to(sizeof(T) * size);
- }
- template <class T>
- inline void File::Map<T>::sync()
- {
- MapBase::sync();
- }
- template <class T>
- inline void File::Map<T>::flush()
- {
- MapBase::flush();
- }
- template <class T>
- inline bool File::Map<T>::is_attached() const noexcept
- {
- return (m_addr != nullptr);
- }
- template <class T>
- inline T* File::Map<T>::get_addr() const noexcept
- {
- return static_cast<T*>(m_addr);
- }
- template <class T>
- inline size_t File::Map<T>::get_size() const noexcept
- {
- return m_addr ? m_size : 0;
- }
- template <class T>
- inline T* File::Map<T>::release() noexcept
- {
- T* addr = static_cast<T*>(m_addr);
- m_addr = nullptr;
- m_fd = 0;
- return addr;
- }
- inline File::Streambuf::Streambuf(File* f, size_t buffer_size)
- : m_file(*f)
- , m_buffer(new char[buffer_size])
- {
- char* b = m_buffer.get();
- setp(b, b + buffer_size);
- }
- inline File::Streambuf::~Streambuf() noexcept
- {
- try {
- if (m_file.is_attached())
- flush();
- }
- catch (...) {
- // Errors deliberately ignored
- }
- }
- inline File::Streambuf::int_type File::Streambuf::overflow(int_type c)
- {
- flush();
- if (c == traits_type::eof())
- return traits_type::not_eof(c);
- *pptr() = traits_type::to_char_type(c);
- pbump(1);
- return c;
- }
- inline int File::Streambuf::sync()
- {
- flush();
- return 0;
- }
- inline File::Streambuf::pos_type File::Streambuf::seekpos(pos_type pos, std::ios_base::openmode)
- {
- flush();
- SizeType pos2 = 0;
- if (int_cast_with_overflow_detect(std::streamsize(pos), pos2))
- throw RuntimeError(ErrorCodes::RangeError, "Seek position overflow");
- m_file.seek(pos2);
- return pos;
- }
- inline void File::Streambuf::flush()
- {
- size_t n = pptr() - pbase();
- if (n > 0) {
- m_file.write(pbase(), n);
- setp(m_buffer.get(), epptr());
- }
- }
- inline bool operator==(const File::UniqueID& lhs, const File::UniqueID& rhs)
- {
- #ifdef _WIN32 // Windows version
- return lhs.id_info.VolumeSerialNumber == rhs.id_info.VolumeSerialNumber &&
- memcmp(&lhs.id_info.FileId, &rhs.id_info.FileId, sizeof(lhs.id_info.FileId)) == 0;
- #else // POSIX version
- return lhs.device == rhs.device && lhs.inode == rhs.inode;
- #endif
- }
- inline bool operator!=(const File::UniqueID& lhs, const File::UniqueID& rhs)
- {
- return !(lhs == rhs);
- }
- inline bool operator<(const File::UniqueID& lhs, const File::UniqueID& rhs)
- {
- #ifdef _WIN32 // Windows version
- if (lhs.id_info.VolumeSerialNumber != rhs.id_info.VolumeSerialNumber)
- return lhs.id_info.VolumeSerialNumber < rhs.id_info.VolumeSerialNumber;
- return memcmp(&lhs.id_info.FileId, &rhs.id_info.FileId, sizeof(lhs.id_info.FileId)) < 0;
- #else // POSIX version
- if (lhs.device < rhs.device)
- return true;
- if (lhs.device > rhs.device)
- return false;
- if (lhs.inode < rhs.inode)
- return true;
- return false;
- #endif
- }
- inline bool operator>(const File::UniqueID& lhs, const File::UniqueID& rhs)
- {
- return rhs < lhs;
- }
- inline bool operator<=(const File::UniqueID& lhs, const File::UniqueID& rhs)
- {
- return !(lhs > rhs);
- }
- inline bool operator>=(const File::UniqueID& lhs, const File::UniqueID& rhs)
- {
- return !(lhs < rhs);
- }
- } // namespace realm::util
- #endif // REALM_UTIL_FILE_HPP
|