From e0b126d8c7f691856ec4d80bb57cb1ba5c71fd69 Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Wed, 22 Jun 2016 23:00:36 +0300 Subject: Add mkslink(), mkhlink() --- .gitignore | 1 + butl/buildfile | 41 +++++++------- butl/filesystem | 35 ++++++++++++ butl/filesystem.cxx | 61 ++++++++++++++++++--- butl/pager.cxx | 7 +-- butl/path.cxx | 50 ++++-------------- butl/process.cxx | 49 ++++------------- butl/win32-utility | 52 ++++++++++++++++++ butl/win32-utility.cxx | 51 ++++++++++++++++++ tests/buildfile | 2 +- tests/link/buildfile | 7 +++ tests/link/driver.cxx | 141 +++++++++++++++++++++++++++++++++++++++++++++++++ 12 files changed, 385 insertions(+), 112 deletions(-) create mode 100644 butl/win32-utility create mode 100644 butl/win32-utility.cxx create mode 100644 tests/link/buildfile create mode 100644 tests/link/driver.cxx 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; using auto_rmdir = auto_rm; // 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 -#include // errno, E* -#include // stat, rmdir(), unlink() -#include // stat -#include // stat(), lstat(), S_IS*, mkdir() - #ifndef _WIN32 # include // struct dirent, *dir() +# include // symlink(), link() #else -# include // _find*() -# include // _mkdir() +# include + +# include // _find*() +# include // _mkdir() + +# include #endif +#include // errno, E* +#include // stat, rmdir(), unlink() +#include // stat +#include // stat(), lstat(), S_IS*, mkdir() + #include // unique_ptr #include @@ -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 # include // this_thread::sleep_for() #else -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include // GetConsoleScreenBufferInfo(), GetStdHandle(), - // Sleep() +# include + # include // _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 #ifdef _WIN32 +# include + # include // _MAX_PATH, _wgetenv() # include // _[w]getcwd(), _[w]chdir() -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include // GetTempPath*(), FormatMessageA(), LocalFree() # include // SHGetFolderPath*(), CSIDL_PROFILE # include // SUCCEEDED() -# include // unique_ptr #else # include // struct passwd, getpwuid_r() # include // EINVAL @@ -23,6 +20,7 @@ # include // strlen(), strcpy() # include // stat(), S_IS* # include // stat + # include #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 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 // execvp, fork, dup2, pipe, chdir, *_FILENO, getpid # include // waitpid #else -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif -# include // CreatePipe(), CreateProcess() +# include + # include // _open_osfhandle(), _get_osfhandle(), _close() # include // _O_TEXT # include // getenv() @@ -21,6 +19,7 @@ # include // unique_ptr # include +# include #endif #include @@ -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 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 and a couple of Win32-specific +// utilities. +// + +#ifdef _WIN32 + +// Try to include 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 +# undef NOMINMAX +# else +# include +# endif +# undef WIN32_LEAN_AND_MEAN +#else +# ifndef NOMINMAX +# define NOMINMAX +# include +# undef NOMINMAX +# else +# include +# endif +#endif + +#include + +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 + +#ifdef _WIN32 + +#include // 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 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 +#include +#include +#include // pair +#include + +#include +#include + +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> te; + for (const dir_entry& de: dir_iterator (tp)) + te.emplace (de.ltype (), de.path ()); + + set> 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); +} -- cgit v1.1