diff options
author | Karen Arutyunov <karen@codesynthesis.com> | 2020-03-07 14:07:28 +0300 |
---|---|---|
committer | Karen Arutyunov <karen@codesynthesis.com> | 2020-03-09 14:18:20 +0300 |
commit | dcccba655fe848564e961b3f285ce3a82d3ac73a (patch) | |
tree | 598ced3b406d80c23798672930e1a17cfe112b75 /tests/path-entry | |
parent | 63b2988e4f2630cc688ff43b7e5f0d4f977896cd (diff) |
Add more support for symlinks on Windows
See mksymlink() for details of the symlinks support on Windows.
Diffstat (limited to 'tests/path-entry')
-rw-r--r-- | tests/path-entry/driver.cxx | 212 | ||||
-rw-r--r-- | tests/path-entry/testscript | 175 |
2 files changed, 353 insertions, 34 deletions
diff --git a/tests/path-entry/driver.cxx b/tests/path-entry/driver.cxx index d48bf49..51ac04d 100644 --- a/tests/path-entry/driver.cxx +++ b/tests/path-entry/driver.cxx @@ -4,7 +4,9 @@ #include <cassert> #ifndef __cpp_lib_modules_ts +#include <string> #include <iostream> +#include <stdexcept> // invalid_argument #include <system_error> #endif @@ -15,48 +17,210 @@ import std.core; import std.io; #endif +import butl.path; import butl.utility; // operator<<(ostream, exception) +import butl.optional; +import butl.timestamp; import butl.filesystem; #else +#include <libbutl/path.mxx> #include <libbutl/utility.mxx> +#include <libbutl/optional.mxx> +#include <libbutl/timestamp.mxx> #include <libbutl/filesystem.mxx> #endif using namespace std; using namespace butl; -// Usage: argv[0] <path> +// Usage: argv[0] [-l] [-t] [-p <permissions>] [-m <time>] [-a <time>] <path> // -// If path entry exists then print it's type and size (meaningful for the -// regular file only) to STDOUT, and exit with the zero code. Otherwise exit -// with the one code. Don't follow symlink. On failure print the error -// description to STDERR and exit with the two code. +// If path entry exists then optionally modify its meta-information and print +// its type, size (meaningful for the regular file only), permissions, +// modification and access times to STDOUT, one value per line, and exit with +// the zero code. Otherwise exit with the one code. Don't follow symlink by +// default. On failure print the error description to STDERR and exit with +// the two code. +// +// -l +// Follow symlinks. +// +// -t +// Assume the path is a file and touch it. Implies -l. +// +// -p <permissions> +// Set path permissions specified in the chmod utility octal form. Implies +// -l. +// +// -m <time> +// Set path modification time specified in the "%Y-%m-%d %H:%M:%S%[.N]" +// format. Implies -l. +// +// -a <time> +// As -m but set the access time. // int main (int argc, const char* argv[]) -try { - assert (argc == 2); + string stage; + + try + { + using butl::optional; + + bool follow_symlinks (false); + optional<permissions> perms; + optional<timestamp> mtime; + optional<timestamp> atime; + bool touch (false); + + auto time = [] (const char* v) + { + return from_string (v, "%Y-%m-%d %H:%M:%S%[.N]", true /* local */); + }; + + int i (1); + for (; i != argc; ++i) + { + string v (argv[i]); + + if (v == "-l") + follow_symlinks = true; + else if (v == "-t") + { + touch = true; + follow_symlinks = true; + } + else if (v == "-p") + { + assert (++i != argc); + v = argv[i]; + + size_t n; + perms = static_cast<permissions> (stoull (v, &n, 8)); + assert (n == v.size ()); + + follow_symlinks = true; + } + else if (v == "-m") + { + assert (++i != argc); + mtime = time (argv[i]); + + follow_symlinks = true; + } + else if (v == "-a") + { + assert (++i != argc); + atime = time (argv[i]); + + follow_symlinks = true; + } + else + break; + } + + assert (i == argc - 1); + + path p (argv[i]); + + if (touch) + { + stage = "touch"; + touch_file (p); + } + + stage = "stat entry"; + pair<bool, entry_stat> es (path_entry (p, follow_symlinks)); - auto es (path_entry (argv[1])); + if (!es.first) + return 1; - if (!es.first) - return 1; + // The entry is a directory with a symlink followed. + // + bool tdir; - switch (es.second.type) + if (follow_symlinks || es.second.type != entry_type::symlink) + tdir = (es.second.type == entry_type::directory); + else + { + stage = "stat target"; + pair<bool, entry_stat> ts (path_entry (p, true /* follow_symlinks */)); + + if (!ts.first) + return 1; + + tdir = (ts.second.type == entry_type::directory); + } + + if (perms) + { + stage = "set permissions"; + path_permissions (p, *perms); + } + + if (mtime) + { + if (tdir) + { + stage = "set directory mtime"; + dir_mtime (path_cast<dir_path> (p), *mtime); + } + else + { + stage = "set file mtime"; + file_mtime (p, *mtime); + } + } + + if (atime) + { + if (tdir) + { + stage = "set directory atime"; + dir_atime (path_cast<dir_path> (p), *atime); + } + else + { + stage = "set file atime"; + file_atime (p, *atime); + } + } + + cout << "type: "; + + switch (es.second.type) + { + case entry_type::unknown: cout << "unknown"; break; + case entry_type::regular: cout << "regular"; break; + case entry_type::directory: cout << "directory"; break; + case entry_type::symlink: cout << "symlink"; break; + case entry_type::other: cout << "other"; break; + } + + stage = "get permissions"; + + cout << endl + << "size: " << es.second.size << endl + << "permissions: " << oct + << static_cast<size_t> (path_permissions (p)) << endl; + + stage = tdir ? "get directory times" : "get file times"; + + entry_time et (tdir ? dir_time (path_cast<dir_path> (p)) : file_time (p)); + cout << "mtime: " << et.modification << endl + << "atime: " << et.access << endl; + + return 0; + } + catch (const invalid_argument& e) { - case entry_type::unknown: cout << "unknown"; break; - case entry_type::regular: cout << "regular"; break; - case entry_type::directory: cout << "directory"; break; - case entry_type::symlink: cout << "symlink"; break; - case entry_type::other: cout << "other"; break; + cerr << e << endl; + return 2; + } + catch (const system_error& e) + { + cerr << stage << " failed: " << e << endl; + return 2; } - - cout << endl << es.second.size << endl; - return 0; -} -catch (const system_error& e) -{ - cerr << e << endl; - return 2; } diff --git a/tests/path-entry/testscript b/tests/path-entry/testscript index 456f96f..76316bf 100644 --- a/tests/path-entry/testscript +++ b/tests/path-entry/testscript @@ -10,21 +10,176 @@ : printed on Windows. This why we exclude it, to get consistent behavior on : both POSIX and Windows. : - cat <:'abc' >=f; - $* f >>EOO - regular - 3 - EOO + { + cat <:'abc' >=f; + $* f >>~/EOO/ + type: regular + size: 3 + /.+ + EOO + } : dir : : Note that the size value is meaningless for directory entries. : - mkdir -p d; - $* d >>~/EOO/ - directory - /. - EOO + { + mkdir -p d; + $* d >>~/EOO/ + type: directory + /.+ + EOO + } + + : followed-symlink + : + { + cat <:'abc' >=f; + ln -s f l; + $* -l l >>~/EOO/ + type: regular + size: 3 + /.+ + EOO + } + + : symlink + : + : If we are not cross-testing let's test if symlinks are properly followed. + : On Windows that involves mklink command usability test. If we fail to + : create a trial link (say because we are not in the Developer Mode and are + : running non-elevated console), then the test group will be silently + : skipped. + : + if ($test.target == $build.host) + { + +if ($cxx.target.class != 'windows') + lns = ln -s t l &l + else + echo 'yes' >=t + if cmd /C 'mklink l t' >- 2>- &?l && cat l >'yes' + lns = cmd /C 'mklink l t' &l >- + end + + jnc = cmd /C 'mklink /J l t' &l >- + end + + : symlink + : + if! $empty($lns) + { + : file + : + { + : get-info + : + : Test that the target type, size, permissions and file times are + : obtained via a symlink. + : + { + cat <:'abc' >=t; + $lns; + $* -p 400 -m '2020-03-05 00:00:00' -a '2020-03-05 00:00:01' t | set ti; + $* -l l >"$ti" + } + + : set-info + : + : Test that permissions and file times are set via a symlink. + : + { + cat <:'abc' >=t; + $lns; + $* -p 400 -m '2020-03-05 00:00:00' -a '2020-03-05 00:00:01' l | set ti; + sed -n -e 's/permissions: (.+)/\1/p' <"$ti" >~/'4.{2}'/; + sed -n -e 's/mtime: (.+)/\1/p' <"$ti" >'2020-03-05 00:00:00'; + sed -n -e 's/atime: (.+)/\1/p' <"$ti" >'2020-03-05 00:00:01' + } + + : touch + : + : Test that a symlink touch touches the target. + : + { + cat <:'abc' >=t; + $lns; + $* t | set ti; + sleep 2; + $* -t l | set li; + if ("$ti" == "$li") + exit "link touch doesn't change target" + end + } + } + + : dir + : + : Note that the following tests may fail on Windows (see symlinks known + : issues in libbutl/filesystem.mxx). + : + if ($cxx.target.class != 'windows') + { + : get-info + : + : Test that the target type, size, permissions and file times are + : obtained via a symlink. + : + { + mkdir t; + $lns; + $* -p 400 -m '2020-03-05 00:00:00' -a '2020-03-05 00:00:01' t | set ti; + $* -l l >"$ti"; + $* -p 600 t >- # @@ TMP; until build2 is staged. + } + + : set-info + : + : Test that permissions and file times are set via a symlink. + : + { + mkdir t; + $lns; + $* -p 400 -m '2020-03-05 00:00:00' -a '2020-03-05 00:00:01' l | set ti; + sed -n -e 's/permissions: (.+)/\1/p' <"$ti" >~/'4.{2}'/; + sed -n -e 's/mtime: (.+)/\1/p' <"$ti" >'2020-03-05 00:00:00'; + sed -n -e 's/atime: (.+)/\1/p' <"$ti" >'2020-03-05 00:00:01'; + $* -p 600 t >- # @@ TMP; until build2 is staged. + } + } + } + + : junction + : + if! $empty($jnc) + { + : get-info + : + : Test that the target type, size, permissions and file times are + : obtained via a junction. + : + { + mkdir t; + $jnc; + $* -p 400 -m '2020-03-05 00:00:00' -a '2020-03-05 00:00:01' t | set ti; + $* -l l >"$ti"; + $* -p 600 t >- # @@ TMP; until build2 is staged. + } + + : set-info + : + : Test that permissions and file times are set via a junction. + : + { + mkdir t; + $jnc; + $* -p 400 -m '2020-03-05 00:00:00' -a '2020-03-05 00:00:01' l | set ti; + sed -n -e 's/permissions: (.+)/\1/p' <"$ti" >~/'4.{2}'/; + sed -n -e 's/mtime: (.+)/\1/p' <"$ti" >'2020-03-05 00:00:00'; + sed -n -e 's/atime: (.+)/\1/p' <"$ti" >'2020-03-05 00:00:01'; + $* -p 600 t >- # @@ TMP; until build2 is staged. + } + } + } } : non-existent |