aboutsummaryrefslogtreecommitdiff
path: root/butl
diff options
context:
space:
mode:
Diffstat (limited to 'butl')
-rw-r--r--butl/fdstream51
-rw-r--r--butl/fdstream.cxx130
-rw-r--r--butl/fdstream.ixx25
-rw-r--r--butl/filesystem50
-rw-r--r--butl/filesystem.cxx105
-rw-r--r--butl/filesystem.ixx18
6 files changed, 367 insertions, 12 deletions
diff --git a/butl/fdstream b/butl/fdstream
index 21df3f8..23cc58c 100644
--- a/butl/fdstream
+++ b/butl/fdstream
@@ -7,6 +7,10 @@
#include <istream>
#include <ostream>
+#include <cstdint> // uint16_t
+
+#include <butl/path>
+#include <butl/filesystem> // 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 <butl/fdstream.ixx>
+
#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 <butl/fdstream>
#ifndef _WIN32
-# include <fcntl.h> // open(), O_RDWR
-# include <unistd.h> // close(), read(), write()
+# include <fcntl.h> // open(), O_*
+# include <unistd.h> // close(), read(), write()
+# include <sys/stat.h> // S_I*
#else
-# include <io.h> // _close(), _read(), _write(), _setmode(), _sopen()
-# include <share.h> // _SH_DENYNO
-# include <stdio.h> // _fileno(), stdin, stdout, stderr
-# include <fcntl.h> // _O_BINARY, _O_TEXT
+# include <io.h> // _close(), _read(), _write(), _setmode(), _sopen()
+# include <share.h> // _SH_DENYNO
+# include <stdio.h> // _fileno(), stdin, stdout, stderr
+# include <fcntl.h> // _O_*
+# include <sys/stat.h> // S_I*
#endif
#include <system_error>
@@ -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<mode_t> (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<fdopen_mode> (
+ static_cast<std::uint16_t> (x) &
+ static_cast<std::uint16_t> (y));
+ }
+
+ inline fdopen_mode
+ operator|= (fdopen_mode& x, fdopen_mode y)
+ {
+ return x = static_cast<fdopen_mode> (
+ static_cast<std::uint16_t> (x) |
+ static_cast<std::uint16_t> (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 <dirent.h> // struct dirent, *dir()
# include <unistd.h> // symlink(), link(), stat(), rmdir(), unlink()
# include <sys/types.h> // stat
-# include <sys/stat.h> // stat(), lstat(), S_IS*, mkdir()
+# include <sys/stat.h> // stat(), lstat(), S_I*, mkdir(), chmod()
#else
# include <butl/win32-utility>
-# include <io.h> // _find*(), _unlink()
+# include <io.h> // _find*(), _unlink(), _chmod()
# include <direct.h> // _mkdir(), _rmdir()
# include <sys/types.h> // _stat
-# include <sys/stat.h> // _stat(), S_IF*
-
-# include <cassert>
+# include <sys/stat.h> // _stat(), S_I*
#endif
#include <errno.h> // errno, E*
@@ -25,6 +23,8 @@
#include <memory> // unique_ptr
#include <system_error>
+#include <butl/fdstream>
+
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<permissions> (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<mode_t> (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<dir_path>::
~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<cpflags> (
+ static_cast<std::uint16_t> (x) &
+ static_cast<std::uint16_t> (y));
+ }
+
+ inline cpflags operator|= (cpflags& x, cpflags y)
+ {
+ return x = static_cast<cpflags> (
+ static_cast<std::uint16_t> (x) |
+ static_cast<std::uint16_t> (y));
+ }
+
// permissions
//
inline permissions operator& (permissions x, permissions y) {return x &= y;}