aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2016-06-22 23:00:36 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2016-06-23 15:13:25 +0300
commite0b126d8c7f691856ec4d80bb57cb1ba5c71fd69 (patch)
tree20613b639d81a08dd1189ef4db37f41f3de54a68
parent748eab79085d7c8a3b3da90316a90a892db884ae (diff)
Add mkslink(), mkhlink()
-rw-r--r--.gitignore1
-rw-r--r--butl/buildfile41
-rw-r--r--butl/filesystem35
-rw-r--r--butl/filesystem.cxx61
-rw-r--r--butl/pager.cxx7
-rw-r--r--butl/path.cxx50
-rw-r--r--butl/process.cxx49
-rw-r--r--butl/win32-utility52
-rw-r--r--butl/win32-utility.cxx51
-rw-r--r--tests/buildfile2
-rw-r--r--tests/link/buildfile7
-rw-r--r--tests/link/driver.cxx141
12 files changed, 385 insertions, 112 deletions
diff --git a/.gitignore b/.gitignore
index f01859a..a0c7e80 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,4 +5,5 @@
*.so
*.a
*.exe
+*.dll
core
diff --git a/butl/buildfile b/butl/buildfile
index db33ef7..5c939af 100644
--- a/butl/buildfile
+++ b/butl/buildfile
@@ -2,26 +2,27 @@
# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
# license : MIT; see accompanying LICENSE file
-lib{butl}: \
-{hxx cxx}{ base64 } \
-{hxx cxx}{ char-scanner } \
-{hxx cxx}{ fdstream } \
-{hxx ixx cxx}{ filesystem } \
-{hxx }{ multi-index } \
-{hxx }{ optional } \
-{hxx cxx}{ pager } \
-{hxx ixx txx cxx}{ path } \
-{hxx }{ path-io } \
-{hxx }{ path-map } \
-{hxx txx }{ prefix-map } \
-{hxx ixx cxx}{ process } \
-{hxx cxx}{ sha256 } \
-{hxx txx }{ string-table } \
-{hxx cxx}{ timestamp } \
-{hxx cxx}{ triplet } \
-{hxx }{ utility } \
-{hxx }{ vector-view } \
-{hxx }{ version }
+lib{butl}: \
+{hxx cxx}{ base64 } \
+{hxx cxx}{ char-scanner } \
+{hxx cxx}{ fdstream } \
+{hxx ixx cxx}{ filesystem } \
+{hxx }{ multi-index } \
+{hxx }{ optional } \
+{hxx cxx}{ pager } \
+{hxx ixx txx cxx}{ path } \
+{hxx }{ path-io } \
+{hxx }{ path-map } \
+{hxx txx }{ prefix-map } \
+{hxx ixx cxx}{ process } \
+{hxx cxx}{ sha256 } \
+{hxx txx }{ string-table } \
+{hxx cxx}{ timestamp } \
+{hxx cxx}{ triplet } \
+{hxx }{ utility } \
+{hxx }{ vector-view } \
+{hxx }{ version } \
+{hxx cxx}{ win32-utility }
# This one is included into sha256.cxx so treat it as file to exclude
# from the compilation.
diff --git a/butl/filesystem b/butl/filesystem
index ab39f36..565f465 100644
--- a/butl/filesystem
+++ b/butl/filesystem
@@ -120,6 +120,41 @@ namespace butl
using auto_rmfile = auto_rm<path>;
using auto_rmdir = auto_rm<dir_path>; // Note: recursive (rm_r).
+ // Create a symbolic link to a file (default) or directory (third argument
+ // is true). Throw std::system_error on failures.
+ //
+ // Note that Windows symlinks are currently not supported.
+ //
+ void
+ mksymlink (const path& target, const path& link, bool dir = false);
+
+ // Create a symbolic link to a directory. Throw std::system_error on
+ // failures.
+ //
+ inline void
+ mksymlink (const dir_path& target, const dir_path& link)
+ {
+ mksymlink (target, link, true);
+ }
+
+ // Create a hard link to a file (default) or directory (third argument is
+ // true). Throw std::system_error on failures.
+ //
+ // Note that on Linix, FreeBSD and some other platforms the target can not
+ // be a directory. While Windows support directories (via junktions), this
+ // is currently not implemented.
+ //
+ void
+ mkhardlink (const path& target, const path& link, bool dir = false);
+
+ // Create a hard link to a directory. Throw std::system_error on failures.
+ //
+ inline void
+ mkhardlink (const dir_path& target, const dir_path& link)
+ {
+ mkhardlink (target, link, true);
+ }
+
// 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
diff --git a/butl/filesystem.cxx b/butl/filesystem.cxx
index a2d6434..c3d21cd 100644
--- a/butl/filesystem.cxx
+++ b/butl/filesystem.cxx
@@ -4,18 +4,23 @@
#include <butl/filesystem>
-#include <errno.h> // errno, E*
-#include <unistd.h> // stat, rmdir(), unlink()
-#include <sys/types.h> // stat
-#include <sys/stat.h> // stat(), lstat(), S_IS*, mkdir()
-
#ifndef _WIN32
# include <dirent.h> // struct dirent, *dir()
+# include <unistd.h> // symlink(), link()
#else
-# include <io.h> // _find*()
-# include <direct.h> // _mkdir()
+# include <butl/win32-utility>
+
+# include <io.h> // _find*()
+# include <direct.h> // _mkdir()
+
+# include <cassert>
#endif
+#include <errno.h> // errno, E*
+#include <unistd.h> // stat, rmdir(), unlink()
+#include <sys/types.h> // stat
+#include <sys/stat.h> // stat(), lstat(), S_IS*, mkdir()
+
#include <memory> // unique_ptr
#include <system_error>
@@ -152,6 +157,48 @@ namespace butl
return r;
}
+#ifndef _WIN32
+ void
+ mksymlink (const path& target, const path& link, bool)
+ {
+ if (symlink (target.string ().c_str (), link.string ().c_str ()) == -1)
+ throw system_error (errno, system_category ());
+ }
+
+ void
+ mkhardlink (const path& target, const path& link, bool)
+ {
+ if (::link (target.string ().c_str (), link.string ().c_str ()) == -1)
+ throw system_error (errno, system_category ());
+ }
+
+#else
+
+ void
+ mksymlink (const path&, const path&, bool)
+ {
+ throw system_error (ENOSYS, system_category (), "symlinks not supported");
+ }
+
+ void
+ mkhardlink (const path& target, const path& link, bool dir)
+ {
+ if (!dir)
+ {
+ if (!CreateHardLinkA (link.string ().c_str (),
+ target.string ().c_str (),
+ nullptr))
+ {
+ string e (win32::last_error_msg ());
+ throw system_error (EIO, system_category (), e);
+ }
+ }
+ else
+ throw system_error (
+ ENOSYS, system_category (), "directory hard links not supported");
+ }
+#endif
+
// 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.
diff --git a/butl/pager.cxx b/butl/pager.cxx
index 9999bb0..0f29bb4 100644
--- a/butl/pager.cxx
+++ b/butl/pager.cxx
@@ -11,11 +11,8 @@
# include <chrono>
# include <thread> // this_thread::sleep_for()
#else
-# ifndef WIN32_LEAN_AND_MEAN
-# define WIN32_LEAN_AND_MEAN
-# endif
-# include <windows.h> // GetConsoleScreenBufferInfo(), GetStdHandle(),
- // Sleep()
+# include <butl/win32-utility>
+
# include <io.h> // _close()
#endif
diff --git a/butl/path.cxx b/butl/path.cxx
index 92b0d6c..1693c86 100644
--- a/butl/path.cxx
+++ b/butl/path.cxx
@@ -5,15 +5,12 @@
#include <butl/path>
#ifdef _WIN32
+# include <butl/win32-utility>
+
# include <stdlib.h> // _MAX_PATH, _wgetenv()
# include <direct.h> // _[w]getcwd(), _[w]chdir()
-# ifndef WIN32_LEAN_AND_MEAN
-# define WIN32_LEAN_AND_MEAN
-# endif
-# include <windows.h> // GetTempPath*(), FormatMessageA(), LocalFree()
# include <shlobj.h> // SHGetFolderPath*(), CSIDL_PROFILE
# include <winerror.h> // SUCCEEDED()
-# include <memory> // unique_ptr
#else
# include <pwd.h> // struct passwd, getpwuid_r()
# include <errno.h> // EINVAL
@@ -23,6 +20,7 @@
# include <string.h> // strlen(), strcpy()
# include <sys/stat.h> // stat(), S_IS*
# include <sys/types.h> // stat
+
# include <vector>
#endif
@@ -40,6 +38,10 @@
using namespace std;
+#ifdef _WIN32
+using namespace butl::win32;
+#endif
+
namespace butl
{
char const* invalid_path_base::
@@ -84,39 +86,7 @@ namespace butl
#endif
}
-#ifdef _WIN32
- struct msg_deleter
- {
- void operator() (char* p) const {LocalFree (p);}
- };
-
- static string
- error_msg (DWORD e)
- {
- char* msg;
- if (!FormatMessageA (
- FORMAT_MESSAGE_ALLOCATE_BUFFER |
- FORMAT_MESSAGE_FROM_SYSTEM |
- FORMAT_MESSAGE_IGNORE_INSERTS |
- FORMAT_MESSAGE_MAX_WIDTH_MASK,
- 0,
- e,
- MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
- (char*)&msg,
- 0,
- 0))
- return "unknown error code " + to_string (e);
-
- unique_ptr<char, msg_deleter> m (msg);
- return msg;
- }
-
- inline static string
- last_error ()
- {
- return error_msg (GetLastError ());
- }
-#else
+#ifndef _WIN32
static const char*
temp_directory ()
{
@@ -179,7 +149,7 @@ namespace butl
if (r == 0)
{
- string e (last_error ());
+ string e (last_error_msg ());
throw system_error (ENOTDIR, system_category (), e);
}
@@ -301,7 +271,7 @@ namespace butl
if (r == 0)
{
- string e (last_error ());
+ string e (last_error_msg ());
throw system_error (ENOTDIR, system_category (), e);
}
#else
diff --git a/butl/process.cxx b/butl/process.cxx
index 588c0b6..aaab915 100644
--- a/butl/process.cxx
+++ b/butl/process.cxx
@@ -8,10 +8,8 @@
# include <unistd.h> // execvp, fork, dup2, pipe, chdir, *_FILENO, getpid
# include <sys/wait.h> // waitpid
#else
-# ifndef WIN32_LEAN_AND_MEAN
-# define WIN32_LEAN_AND_MEAN
-# endif
-# include <windows.h> // CreatePipe(), CreateProcess()
+# include <butl/win32-utility>
+
# include <io.h> // _open_osfhandle(), _get_osfhandle(), _close()
# include <fcntl.h> // _O_TEXT
# include <stdlib.h> // getenv()
@@ -21,6 +19,7 @@
# include <memory> // unique_ptr
# include <butl/path>
+# include <butl/win32-utility>
#endif
#include <cassert>
@@ -29,6 +28,10 @@
using namespace std;
+#ifdef _WIN32
+using namespace butl::win32;
+#endif
+
namespace butl
{
class auto_fd
@@ -226,38 +229,6 @@ namespace butl
#else // _WIN32
- struct msg_deleter
- {
- void operator() (char* p) const {LocalFree (p);}
- };
-
- static string
- error (DWORD e)
- {
- char* msg;
- if (!FormatMessageA (
- FORMAT_MESSAGE_ALLOCATE_BUFFER |
- FORMAT_MESSAGE_FROM_SYSTEM |
- FORMAT_MESSAGE_IGNORE_INSERTS |
- FORMAT_MESSAGE_MAX_WIDTH_MASK,
- 0,
- e,
- MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
- (char*)&msg,
- 0,
- 0))
- return "unknown error code " + to_string (e);
-
- unique_ptr<char, msg_deleter> m (msg);
- return msg;
- }
-
- static inline string
- last_error ()
- {
- return error (GetLastError ());
- }
-
static path
path_search (const path& f)
{
@@ -383,7 +354,7 @@ namespace butl
auto fail = [](const char* m = nullptr)
{
- throw process_error (m == nullptr ? last_error () : m);
+ throw process_error (m == nullptr ? last_error_msg () : m);
};
// Create a pipe and clear the inherit flag on the parent side.
@@ -605,7 +576,7 @@ namespace butl
handle = 0; // We have tried.
if (e != NO_ERROR)
- throw process_error (error (e));
+ throw process_error (error_msg (e));
status = s;
}
@@ -631,7 +602,7 @@ namespace butl
handle = 0; // We have tried.
if (e != NO_ERROR)
- throw process_error (error (e));
+ throw process_error (error_msg (e));
status = s;
}
diff --git a/butl/win32-utility b/butl/win32-utility
new file mode 100644
index 0000000..c74eb66
--- /dev/null
+++ b/butl/win32-utility
@@ -0,0 +1,52 @@
+// file : butl/win32-utility -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#ifndef BUTL_WIN32_UTILITY
+#define BUTL_WIN32_UTILITY
+
+// Use this header to include <windows.h> and a couple of Win32-specific
+// utilities.
+//
+
+#ifdef _WIN32
+
+// Try to include <windows.h> so that it doesn't mess other things up.
+//
+#ifndef WIN32_LEAN_AND_MEAN
+# define WIN32_LEAN_AND_MEAN
+# ifndef NOMINMAX // No min and max macros.
+# define NOMINMAX
+# include <windows.h>
+# undef NOMINMAX
+# else
+# include <windows.h>
+# endif
+# undef WIN32_LEAN_AND_MEAN
+#else
+# ifndef NOMINMAX
+# define NOMINMAX
+# include <windows.h>
+# undef NOMINMAX
+# else
+# include <windows.h>
+# endif
+#endif
+
+#include <string>
+
+namespace butl
+{
+ namespace win32
+ {
+ std::string
+ error_msg (DWORD code);
+
+ std::string
+ last_error_msg ();
+ }
+};
+
+#endif // _WIN32
+
+#endif // BUTL_WIN32_UTILITY
diff --git a/butl/win32-utility.cxx b/butl/win32-utility.cxx
new file mode 100644
index 0000000..cf44d4d
--- /dev/null
+++ b/butl/win32-utility.cxx
@@ -0,0 +1,51 @@
+// file : butl/win32-utility.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <butl/win32-utility>
+
+#ifdef _WIN32
+
+#include <memory> // unique_ptr
+
+using namespace std;
+
+namespace butl
+{
+ namespace win32
+ {
+ struct msg_deleter
+ {
+ void operator() (char* p) const {LocalFree (p);}
+ };
+
+ string
+ error_msg (DWORD code)
+ {
+ char* msg;
+ if (!FormatMessageA (
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_IGNORE_INSERTS |
+ FORMAT_MESSAGE_MAX_WIDTH_MASK,
+ 0,
+ code,
+ MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
+ (char*)&msg,
+ 0,
+ 0))
+ return "unknown error code " + to_string (code);
+
+ unique_ptr<char, msg_deleter> m (msg);
+ return msg;
+ }
+
+ string
+ last_error_msg ()
+ {
+ return error_msg (GetLastError ());
+ }
+ }
+}
+
+#endif // _WIN32
diff --git a/tests/buildfile b/tests/buildfile
index e6670ca..311a290 100644
--- a/tests/buildfile
+++ b/tests/buildfile
@@ -2,7 +2,7 @@
# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
# license : MIT; see accompanying LICENSE file
-d = base64/ dir-iterator/ pager/ path/ prefix-map/ process/ sha256/ \
+d = base64/ dir-iterator/ link/ pager/ path/ prefix-map/ process/ sha256/ \
timestamp/ triplet/
.: $d
diff --git a/tests/link/buildfile b/tests/link/buildfile
new file mode 100644
index 0000000..860f0fc
--- /dev/null
+++ b/tests/link/buildfile
@@ -0,0 +1,7 @@
+# file : tests/link/buildfile
+# copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+exe{driver}: cxx{driver} ../../butl/lib{butl}
+
+include ../../butl/
diff --git a/tests/link/driver.cxx b/tests/link/driver.cxx
new file mode 100644
index 0000000..7ec4ac2
--- /dev/null
+++ b/tests/link/driver.cxx
@@ -0,0 +1,141 @@
+// file : tests/link/driver.cxx -*- C++ -*-
+// copyright : Copyright (c) 2014-2016 Code Synthesis Ltd
+// license : MIT; see accompanying LICENSE file
+
+#include <set>
+#include <cassert>
+#include <fstream>
+#include <utility> // pair
+#include <system_error>
+
+#include <butl/path>
+#include <butl/filesystem>
+
+using namespace std;
+using namespace butl;
+
+static const char text[] = "ABCDEF";
+
+static bool
+link_file (const path& target, const path& link, bool hard, bool check_content)
+{
+ try
+ {
+ if (hard)
+ mkhardlink (target, link);
+ else
+ mksymlink (target, link);
+ }
+ catch (const system_error&)
+ {
+ return false;
+ }
+
+ if (!check_content)
+ return true;
+
+ string s;
+ ifstream ifs;
+ ifs.exceptions (fstream::badbit | fstream::failbit);
+ ifs.open (link.string ());
+ ifs >> s;
+ return s == text;
+}
+
+#ifndef _WIN32
+static bool
+link_dir (
+ const dir_path& target, const dir_path& link, bool hard, bool check_content)
+{
+ try
+ {
+ if (hard)
+ mkhardlink (target, link);
+ else
+ mksymlink (target, link);
+ }
+ catch (const system_error&)
+ {
+ return false;
+ }
+
+ if (!check_content)
+ return true;
+
+ dir_path tp (target.absolute () ? target : link.directory () / target);
+ set<pair<entry_type, path>> te;
+ for (const dir_entry& de: dir_iterator (tp))
+ te.emplace (de.ltype (), de.path ());
+
+ set<pair<entry_type, path>> le;
+ for (const dir_entry& de: dir_iterator (link))
+ le.emplace (de.ltype (), de.path ());
+
+ return te == le;
+}
+#endif
+
+int
+main ()
+{
+ dir_path td (dir_path::temp_directory () / dir_path ("butl-link"));
+
+ // Recreate the temporary directory (that possibly exists from the previous
+ // faulty run) for the test files. Delete the directory only if the test
+ // succeeds to simplify the failure research.
+ //
+ try_rmdir_r (td);
+ assert (try_mkdir (td) == mkdir_status::success);
+
+ // Prepare the target file.
+ //
+ path fn ("target");
+ path fp (td / fn);
+
+ {
+ ofstream ofs;
+ ofs.exceptions (fstream::badbit | fstream::failbit);
+ ofs.open (fp.string ());
+ ofs << text;
+ }
+
+ // Create the file hard link.
+ //
+ assert (link_file (fp, td / path ("hlink"), true, true));
+
+#ifndef _WIN32
+ // Create the file symlink using an absolute path.
+ //
+ assert (link_file (fp, td / path ("slink"), false, true));
+
+ // Create the file symlink using a relative path.
+ //
+ assert (link_file (fn, td / path ("rslink"), false, true));
+
+ // Create the file symlink using an unexistent file path.
+ //
+ assert (link_file (fp / path ("a"), td / path ("sa"), false, false));
+
+ // Prepare the target directory.
+ //
+ dir_path dn ("dir");
+ dir_path dp (td / dn);
+ assert (try_mkdir (dp) == mkdir_status::success);
+ assert (link_file (fp, dp / path ("hlink"), true, true));
+ assert (link_file (fp, dp / path ("slink"), false, true));
+
+ // Create the directory symlink using an absolute path.
+ //
+ assert (link_dir (dp, td / dir_path ("dslink"), false, true));
+
+ // Create the directory symlink using a relative path.
+ //
+ assert (link_dir (dn, td / dir_path ("rdslink"), false, true));
+
+ // Create the directory symlink using an unexistent directory path.
+ //
+ assert (link_dir (dp / dir_path ("a"), td / dir_path ("dsa"), false, false));
+#endif
+
+ rmdir_r (td);
+}