From 5b1c20f2315cd7fc624ffd31abdcc03b409bfcb2 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Sat, 2 Jul 2016 17:21:54 +0300 Subject: Add cpfile() --- butl/fdstream | 51 ++++++++++++++++++++- butl/fdstream.cxx | 130 +++++++++++++++++++++++++++++++++++++++++++++++++--- butl/fdstream.ixx | 25 ++++++++++ butl/filesystem | 50 ++++++++++++++++++++ butl/filesystem.cxx | 105 ++++++++++++++++++++++++++++++++++++++++-- butl/filesystem.ixx | 18 ++++++++ 6 files changed, 367 insertions(+), 12 deletions(-) create mode 100644 butl/fdstream.ixx (limited to 'butl') diff --git a/butl/fdstream b/butl/fdstream index 21df3f8..23cc58c 100644 --- a/butl/fdstream +++ b/butl/fdstream @@ -7,6 +7,10 @@ #include #include +#include // uint16_t + +#include +#include // permissions namespace butl { @@ -73,7 +77,7 @@ namespace butl private: int fd_ = -1; - char buf_[2048]; + char buf_[4096]; }; // File descriptor translation mode. It has the same semantics as the @@ -129,6 +133,49 @@ namespace butl bool is_open () const {return buf_.is_open ();} }; + // File open flags. + // + enum class fdopen_mode: std::uint16_t + { + in = 0x01, // Open for reading. + out = 0x02, // Open for writing. + append = 0x04, // Seek to the end of file before each write. + truncate = 0x08, // Discard the file contents on open. + create = 0x10, // Create a file if not exists. + exclusive = 0x20, // Fail if the file exists and the create flag is set. + binary = 0x40, // Set binary translation mode. + + none = 0 // Usefull when build the mode incrementally. + }; + + fdopen_mode operator& (fdopen_mode, fdopen_mode); + fdopen_mode operator| (fdopen_mode, fdopen_mode); + fdopen_mode operator&= (fdopen_mode&, fdopen_mode); + fdopen_mode operator|= (fdopen_mode&, fdopen_mode); + + // Open a file returning the file descriptor on success and throwing + // std::system_error otherwise. + // + // The mode argument should have at least one of the in or out flags set. + // The append and truncate flags are meaningless in the absense of the out + // flag and are ignored without it. The exclusive flag is meaningless in the + // absense of the create flag and is ignored without it. Note also that if + // the exclusive flag is specified then a dangling symbolic link is treated + // as an existing file. + // + // The permissions argument is taken into account only if the file is + // created. Note also that permissions can be adjusted while being set in a + // way specific for the OS. On POSIX systems they are modified with the + // process' umask, so effective permissions are permissions & ~umask. On + // Windows permissions other than ru and wu are unlikelly to have effect. + // + int + fdopen (const path&, + fdopen_mode, + permissions = permissions::ru | permissions::wu | + permissions::rg | permissions::wg | + permissions::ro | permissions::wo); + // Set the translation mode for the file descriptor. Return the previous // mode on success, throw std::system_error otherwise. // @@ -161,4 +208,6 @@ namespace butl fdnull () noexcept; } +#include + #endif // BUTL_FDSTREAM diff --git a/butl/fdstream.cxx b/butl/fdstream.cxx index 13becfa..e4d11ba 100644 --- a/butl/fdstream.cxx +++ b/butl/fdstream.cxx @@ -5,13 +5,15 @@ #include #ifndef _WIN32 -# include // open(), O_RDWR -# include // close(), read(), write() +# include // open(), O_* +# include // close(), read(), write() +# include // S_I* #else -# include // _close(), _read(), _write(), _setmode(), _sopen() -# include // _SH_DENYNO -# include // _fileno(), stdin, stdout, stderr -# include // _O_BINARY, _O_TEXT +# include // _close(), _read(), _write(), _setmode(), _sopen() +# include // _SH_DENYNO +# include // _fileno(), stdin, stdout, stderr +# include // _O_* +# include // S_I* #endif #include @@ -147,6 +149,122 @@ namespace butl // Utility functions // + int + fdopen (const path& f, fdopen_mode m, permissions p) + { + mode_t pf (S_IREAD | S_IWRITE | S_IEXEC); + +#ifdef S_IRWXG + pf |= S_IRWXG; +#endif + +#ifdef S_IRWXO + pf |= S_IRWXO; +#endif + + pf &= static_cast (p); + + // Return true if the open mode contains a specific flag. + // + auto mode = [m](fdopen_mode flag) -> bool {return (m & flag) == flag;}; + + int of (0); + bool in (mode (fdopen_mode::in)); + bool out (mode (fdopen_mode::out)); + +#ifndef _WIN32 + + if (in && out) + of |= O_RDWR; + else if (in) + of |= O_RDONLY; + else if (out) + of |= O_WRONLY; + + if (out) + { + if (mode (fdopen_mode::append)) + of |= O_APPEND; + + if (mode (fdopen_mode::truncate)) + of |= O_TRUNC; + } + + if (mode (fdopen_mode::create)) + { + of |= O_CREAT; + + if (mode (fdopen_mode::exclusive)) + of |= O_EXCL; + } + +#ifdef O_LARGEFILE + of |= O_LARGEFILE; +#endif + + int fd (open (f.string ().c_str (), of, pf)); + +#else + + if (in && out) + of |= _O_RDWR; + else if (in) + of |= _O_RDONLY; + else if (out) + of |= _O_WRONLY; + + if (out) + { + if (mode (fdopen_mode::append)) + of |= _O_APPEND; + + if (mode (fdopen_mode::truncate)) + of |= _O_TRUNC; + } + + if (mode (fdopen_mode::create)) + { + of |= _O_CREAT; + + if (mode (fdopen_mode::exclusive)) + of |= _O_EXCL; + } + + of |= mode (fdopen_mode::binary) ? _O_BINARY : _O_TEXT; + + // According to Microsoft _sopen() should not change the permissions of an + // existing file. Meanwhile it does if we pass them (reproduced on Windows + // XP, 7, and 8). And we must pass them if we have _O_CREATE. So we need + // to take care of preserving the permissions ourselves. Note that Wine's + // implementation of _sopen() works properly. + // + bool pass_perm (of & _O_CREAT); + + if (pass_perm && file_exists (f)) + { + // If the _O_CREAT flag is set then we need to clear it so that we can + // omit the permissions. But if the _O_EXCL flag is set as well we can't + // do that as fdopen() wouldn't fail as expected. + // + if (of & _O_EXCL) + throw system_error (EEXIST, system_category ()); + + of &= ~_O_CREAT; + pass_perm = false; + } + + int fd (pass_perm + ? _sopen (f.string ().c_str (), of, _SH_DENYNO, pf) + : _sopen (f.string ().c_str (), of, _SH_DENYNO)); + +#endif + + if (fd == -1) + throw system_error (errno, system_category ()); + + return fd; + } + #ifndef _WIN32 bool diff --git a/butl/fdstream.ixx b/butl/fdstream.ixx new file mode 100644 index 0000000..a3ea83c --- /dev/null +++ b/butl/fdstream.ixx @@ -0,0 +1,25 @@ +// file : butl/fdstream.ixx -*- C++ -*- +// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +namespace butl +{ + inline fdopen_mode operator& (fdopen_mode x, fdopen_mode y) {return x &= y;} + inline fdopen_mode operator| (fdopen_mode x, fdopen_mode y) {return x |= y;} + + inline fdopen_mode + operator&= (fdopen_mode& x, fdopen_mode y) + { + return x = static_cast ( + static_cast (x) & + static_cast (y)); + } + + inline fdopen_mode + operator|= (fdopen_mode& x, fdopen_mode y) + { + return x = static_cast ( + static_cast (x) | + static_cast (y)); + } +} diff --git a/butl/filesystem b/butl/filesystem index 780779f..7b23fc5 100644 --- a/butl/filesystem +++ b/butl/filesystem @@ -162,6 +162,47 @@ namespace butl mkhardlink (target, link, true); } + // File copy flags. + // + enum class cpflags: std::uint16_t + { + overwrite_content = 0x1, + overwrite_permissions = 0x2, + + none = 0 + }; + + cpflags operator& (cpflags, cpflags); + cpflags operator| (cpflags, cpflags); + cpflags operator&= (cpflags&, cpflags); + cpflags operator|= (cpflags&, cpflags); + + // Copy a regular file, including its permissions. Throw std::system_error + // on failure. Fail if the destination file exists and the overwrite_content + // flag is not set. Leave permissions of an existing destination file intact + // unless the overwrite_permissions flag is set. Delete incomplete copies + // before throwing. + // + // Note that in case of overwriting, the existing destination file gets + // truncated (not deleted) prior to being overwritten. As a side-effect, + // hard link to the destination file will still reference the same file + // system node after the copy. + // + // Also note that if the overwrite_content flag is not set and the + // destination is a dangling symbolic link, then this function will still + // fail. + // + void + cpfile (const path& from, const path& to, cpflags = cpflags::none); + + // Copy a regular file to an existing directory. + // + inline void + cpfile (const path& from, const dir_path& to, cpflags fl = cpflags::none) + { + cpfile (from, to / from.leaf (), fl); + } + // Return timestamp_nonexistent if the entry at the specified path // does not exist or is not a path. All other errors are reported // by throwing std::system_error. Note that this function resolves @@ -196,9 +237,18 @@ namespace butl permissions operator&= (permissions&, permissions); permissions operator|= (permissions&, permissions); + // Get path permissions. Throw std::system_error on failure. Note that this + // function resolves symlinks. + // permissions path_permissions (const path&); + // Set path permissions. Throw std::system_error on failure. Note that this + // function resolves symlinks. + // + void + path_permissions (const path&, permissions); + // Directory entry iteration. // enum class entry_type diff --git a/butl/filesystem.cxx b/butl/filesystem.cxx index 0ff0e28..eab62dc 100644 --- a/butl/filesystem.cxx +++ b/butl/filesystem.cxx @@ -8,16 +8,14 @@ # include // struct dirent, *dir() # include // symlink(), link(), stat(), rmdir(), unlink() # include // stat -# include // stat(), lstat(), S_IS*, mkdir() +# include // stat(), lstat(), S_I*, mkdir(), chmod() #else # include -# include // _find*(), _unlink() +# include // _find*(), _unlink(), _chmod() # include // _mkdir(), _rmdir() # include // _stat -# include // _stat(), S_IF* - -# include +# include // _stat(), S_I* #endif #include // errno, E* @@ -25,6 +23,8 @@ #include // unique_ptr #include +#include + using namespace std; #ifdef _MSC_VER // Unlikely to be fixed in newer versions. @@ -223,6 +223,68 @@ namespace butl } #endif + void + cpfile (const path& from, const path& to, cpflags fl) + { + permissions perm (path_permissions (from)); + + // We do not enable exceptions to be thrown when badbit/failbit are set for + // the ifs stream nor check the bits manually down the road as there is no + // input functions being called for the stream. Input functions are called + // directly for the input stream buffer, which is our fdbuf that throws. + // + ifdstream ifs (fdopen (from, fdopen_mode::in | fdopen_mode::binary)); + + fdopen_mode om (fdopen_mode::out | + fdopen_mode::truncate | + fdopen_mode::create | + fdopen_mode::binary); + + if ((fl & cpflags::overwrite_content) != cpflags::overwrite_content) + om |= fdopen_mode::exclusive; + + // Create prior to the output file stream creation so that the file is + // removed after it is closed. + // + auto_rmfile rm; + + ofdstream ofs (fdopen (to, om, perm)); + rm = auto_rmfile (to); + + // Setting badbit for the ofs stream is the only way to make sure the + // original std::system_error, that is thrown on the output operation + // failure, is retrown by the output stream's operator<<() and flush() + // calls (the latter is called from close()). Note that both of the + // functions behave as UnformattedOutputFunction. + // + ofs.exceptions (ofdstream::badbit); + + // If the output operation ends up with the badbit set for a reason other + // than std::system_error being thrown (by fdbuf), then ofdstream::failure + // will be thrown instead. We need to convert it to std::system_error to + // comply with the cpfile() interface specification. + // + try + { + // Throws std::system_error on fdbuf read/write failures. + // + ofs << ifs.rdbuf (); + + ifs.close (); + ofs.close (); // Throws std::system_error on flush failure. + } + catch (const ofdstream::failure& e) + { + throw system_error (EIO, system_category (), e.what ()); + } + + if ((fl & cpflags::overwrite_permissions) == + cpflags::overwrite_permissions) + path_permissions (to, perm); + + rm.cancel (); + } + // Figuring out whether we have the nanoseconds in struct stat. Some // platforms (e.g., FreeBSD), may provide some "compatibility" #define's, // so use the second argument to not end up with the same signatures. @@ -255,8 +317,13 @@ namespace butl timestamp file_mtime (const path& p) { +#ifndef _WIN32 struct stat s; if (stat (p.string ().c_str (), &s) != 0) +#else + struct _stat s; + if (_stat (p.string ().c_str (), &s) != 0) +#endif { if (errno == ENOENT || errno == ENOTDIR) return timestamp_nonexistent; @@ -274,8 +341,13 @@ namespace butl permissions path_permissions (const path& p) { +#ifndef _WIN32 struct stat s; if (stat (p.string ().c_str (), &s) != 0) +#else + struct _stat s; + if (_stat (p.string ().c_str (), &s) != 0) +#endif throw system_error (errno, system_category ()); // VC++ has no S_IRWXU defined. MINGW GCC <= 4.9 has no S_IRWXG, S_IRWXO @@ -299,6 +371,29 @@ namespace butl return static_cast (s.st_mode & f); } + void + path_permissions (const path& p, permissions f) + { + mode_t m (S_IREAD | S_IWRITE | S_IEXEC); + +#ifdef S_IRWXG + m |= S_IRWXG; +#endif + +#ifdef S_IRWXO + m |= S_IRWXO; +#endif + + m &= static_cast (f); + +#ifndef _WIN32 + if (chmod (p.string ().c_str (), m) == -1) +#else + if (_chmod (p.string ().c_str (), m) == -1) +#endif + throw system_error (errno, system_category ()); + } + // dir_{entry,iterator} // #ifndef _WIN32 diff --git a/butl/filesystem.ixx b/butl/filesystem.ixx index 037ab8b..8e81b59 100644 --- a/butl/filesystem.ixx +++ b/butl/filesystem.ixx @@ -46,6 +46,24 @@ namespace butl inline auto_rm:: ~auto_rm () {if (!path_.empty ()) try_rmdir_r (path_, true);} + // cpflags + // + inline cpflags operator& (cpflags x, cpflags y) {return x &= y;} + inline cpflags operator| (cpflags x, cpflags y) {return x |= y;} + inline cpflags operator&= (cpflags& x, cpflags y) + { + return x = static_cast ( + static_cast (x) & + static_cast (y)); + } + + inline cpflags operator|= (cpflags& x, cpflags y) + { + return x = static_cast ( + static_cast (x) | + static_cast (y)); + } + // permissions // inline permissions operator& (permissions x, permissions y) {return x &= y;} -- cgit v1.1