aboutsummaryrefslogtreecommitdiff
path: root/libbuild2/test/script/builtin.cxx
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2019-09-10 23:23:43 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2019-09-27 18:04:30 +0300
commitdbed808c7d534069f76e63a1a68a85f30d2be81c (patch)
tree3161d9c9617f2fccf37bd278f0c9bf45fad2e20e /libbuild2/test/script/builtin.cxx
parent6e84c0f9c5e4d7d98d2a352eec6bc19de0d75d28 (diff)
Move testscript builtins to libbutl
Diffstat (limited to 'libbuild2/test/script/builtin.cxx')
-rw-r--r--libbuild2/test/script/builtin.cxx1924
1 files changed, 0 insertions, 1924 deletions
diff --git a/libbuild2/test/script/builtin.cxx b/libbuild2/test/script/builtin.cxx
deleted file mode 100644
index 06b0cec..0000000
--- a/libbuild2/test/script/builtin.cxx
+++ /dev/null
@@ -1,1924 +0,0 @@
-// file : libbuild2/test/script/builtin.cxx -*- C++ -*-
-// copyright : Copyright (c) 2014-2019 Code Synthesis Ltd
-// license : MIT; see accompanying LICENSE file
-
-#include <libbuild2/test/script/builtin.hxx>
-
-#include <chrono>
-#include <locale>
-#include <ostream>
-#include <sstream>
-#include <cstdlib> // strtoull()
-
-#include <libbutl/regex.mxx>
-#include <libbutl/path-io.mxx> // use default operator<< implementation
-#include <libbutl/fdstream.mxx> // fdopen_mode, fdstream_mode
-#include <libbutl/filesystem.mxx>
-
-#include <libbuild2/context.hxx> // sched
-
-#include <libbuild2/test/script/script.hxx>
-#include <libbuild2/test/script/builtin-options.hxx>
-
-// Strictly speaking a builtin which reads/writes from/to standard streams
-// must be asynchronous so that the caller can communicate with it through
-// pipes without being blocked on I/O operations. However, as an optimization,
-// we allow builtins that only print diagnostics to STDERR to be synchronous
-// assuming that their output will always fit the pipe buffer. Synchronous
-// builtins must not read from STDIN and write to STDOUT. Later we may relax
-// this rule to allow a "short" output for such builtins.
-//
-using namespace std;
-using namespace butl;
-
-namespace build2
-{
- namespace test
- {
- namespace script
- {
- using builtin_impl = uint8_t (scope&,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err);
-
- // Operation failed, diagnostics has already been issued.
- //
- struct failed {};
-
- // Accumulate an error message, print it atomically in dtor to the
- // provided stream and throw failed afterwards if requested. Prefixes
- // the message with the builtin name.
- //
- // Move constructible-only, not assignable (based to diag_record).
- //
- class error_record
- {
- public:
- template <typename T>
- friend const error_record&
- operator<< (const error_record& r, const T& x)
- {
- r.ss_ << x;
- return r;
- }
-
- error_record (ostream& o, bool fail, const char* name)
- : os_ (o), fail_ (fail), empty_ (false)
- {
- ss_ << name << ": ";
- }
-
- // Older versions of libstdc++ don't have the ostringstream move
- // support. Luckily, GCC doesn't seem to be actually needing move due
- // to copy/move elision.
- //
-#ifdef __GLIBCXX__
- error_record (error_record&&);
-#else
- error_record (error_record&& r)
- : os_ (r.os_),
- ss_ (move (r.ss_)),
- fail_ (r.fail_),
- empty_ (r.empty_)
- {
- r.empty_ = true;
- }
-#endif
-
- ~error_record () noexcept (false)
- {
- if (!empty_)
- {
- // The output stream can be in a bad state (for example as a
- // result of unsuccessful attempt to report a previous error), so
- // we check it.
- //
- if (os_.good ())
- {
- ss_.put ('\n');
- os_ << ss_.str ();
- os_.flush ();
- }
-
- if (fail_)
- throw failed ();
- }
- }
-
- private:
- ostream& os_;
- mutable ostringstream ss_;
-
- bool fail_;
- bool empty_;
- };
-
- // Parse and normalize a path. Also, unless it is already absolute, make
- // the path absolute using the specified directory. Throw invalid_path
- // if the path is empty, and on parsing and normalization failures.
- //
- static path
- parse_path (string s, const dir_path& d)
- {
- path p (move (s));
-
- if (p.empty ())
- throw invalid_path ("");
-
- if (p.relative ())
- p = d / move (p);
-
- p.normalize ();
- return p;
- }
-
- // Builtin commands functions.
- //
-
- // cat <file>...
- //
- // Note that POSIX doesn't specify if after I/O operation failure the
- // command should proceed with the rest of the arguments. The current
- // implementation exits immediatelly in such a case.
- //
- // @@ Shouldn't we check that we don't print a nonempty regular file to
- // itself, as that would merely exhaust the output device? POSIX
- // allows (but not requires) such a check and some implementations do
- // this. That would require to fstat() file descriptors and complicate
- // the code a bit. Was able to reproduce on a big file (should be
- // bigger than the stream buffer size) with the test
- // 'cat file >+file'.
- //
- // Note: must be executed asynchronously.
- //
- static uint8_t
- cat (scope& sp,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err) noexcept
- try
- {
- uint8_t r (1);
- ofdstream cerr (move (err));
-
- auto error = [&cerr] (bool fail = true)
- {
- return error_record (cerr, fail, "cat");
- };
-
- try
- {
- ifdstream cin (move (in), fdstream_mode::binary);
- ofdstream cout (move (out), fdstream_mode::binary);
-
- // Parse arguments.
- //
- cli::vector_scanner scan (args);
- cat_options ops (scan); // Makes sure no options passed.
-
- // Print files.
- //
- // Copy input stream to STDOUT.
- //
- auto copy = [&cout] (istream& is)
- {
- if (is.peek () != ifdstream::traits_type::eof ())
- cout << is.rdbuf ();
-
- is.clear (istream::eofbit); // Sets eofbit.
- };
-
- // Path of a file being printed to STDOUT. An empty path represents
- // STDIN. Used in diagnostics.
- //
- path p;
-
- try
- {
- // Print STDIN.
- //
- if (!scan.more ())
- copy (cin);
-
- // Print files.
- //
- while (scan.more ())
- {
- string f (scan.next ());
-
- if (f == "-")
- {
- if (!cin.eof ())
- {
- p.clear ();
- copy (cin);
- }
-
- continue;
- }
-
- p = parse_path (move (f), sp.wd_path);
-
- ifdstream is (p, fdopen_mode::binary);
- copy (is);
- is.close ();
- }
- }
- catch (const io_error& e)
- {
- error_record d (error ());
- d << "unable to print ";
-
- if (p.empty ())
- d << "stdin";
- else
- d << "'" << p << "'";
-
- d << ": " << e;
- }
-
- cin.close ();
- cout.close ();
- r = 0;
- }
- catch (const invalid_path& e)
- {
- error (false) << "invalid path '" << e.path << "'";
- }
- // Can be thrown while creating/closing cin, cout or writing to cerr.
- //
- catch (const io_error& e)
- {
- error (false) << e;
- }
- catch (const failed&)
- {
- // Diagnostics has already been issued.
- }
- catch (const cli::exception& e)
- {
- error (false) << e;
- }
-
- cerr.close ();
- return r;
- }
- catch (const std::exception&)
- {
- return 1;
- }
-
- // Make a copy of a file at the specified path, preserving permissions,
- // and registering a cleanup for a newly created file. The file paths
- // must be absolute. Fail if an exception is thrown by the underlying
- // copy operation.
- //
- static void
- cpfile (scope& sp,
- const path& from, const path& to,
- bool overwrite,
- bool attrs,
- bool cleanup,
- const function<error_record()>& fail)
- {
- try
- {
- bool exists (file_exists (to));
-
- cpflags f (
- overwrite
- ? cpflags::overwrite_permissions | cpflags::overwrite_content
- : cpflags::none);
-
- if (attrs)
- f |= cpflags::overwrite_permissions | cpflags::copy_timestamps;
-
- cpfile (from, to, f);
-
- if (!exists && cleanup)
- sp.clean ({cleanup_type::always, to}, true /* implicit */);
- }
- catch (const system_error& e)
- {
- fail () << "unable to copy file '" << from << "' to '" << to
- << "': " << e;
- }
- }
-
- // Make a copy of a directory at the specified path, registering a
- // cleanup for the created directory. The directory paths must be
- // absolute. Fail if the destination directory already exists or
- // an exception is thrown by the underlying copy operation.
- //
- static void
- cpdir (scope& sp,
- const dir_path& from, const dir_path& to,
- bool attrs,
- bool cleanup,
- const function<error_record()>& fail)
- {
- try
- {
- if (try_mkdir (to) == mkdir_status::already_exists)
- throw_generic_error (EEXIST);
-
- if (cleanup)
- sp.clean ({cleanup_type::always, to}, true /* implicit */);
-
- for (const auto& de: dir_iterator (from,
- false /* ignore_dangling */))
- {
- path f (from / de.path ());
- path t (to / de.path ());
-
- if (de.type () == entry_type::directory)
- cpdir (sp,
- path_cast<dir_path> (move (f)),
- path_cast<dir_path> (move (t)),
- attrs,
- cleanup,
- fail);
- else
- cpfile (sp, f, t, false /* overwrite */, attrs, cleanup, fail);
- }
-
- // Note that it is essential to copy timestamps and permissions after
- // the directory content is copied.
- //
- if (attrs)
- {
- path_permissions (to, path_permissions (from));
- dir_time (to, dir_time (from));
- }
- }
- catch (const system_error& e)
- {
- fail () << "unable to copy directory '" << from << "' to '" << to
- << "': " << e;
- }
- }
-
- // cp [-p|--preserve] [--no-cleanup] <src-file> <dst-file>
- // cp [-p|--preserve] [--no-cleanup] -R|-r|--recursive <src-dir> <dst-dir>
- // cp [-p|--preserve] [--no-cleanup] <src-file>... <dst-dir>/
- // cp [-p|--preserve] [--no-cleanup] -R|-r|--recursive <src-path>... <dst-dir>/
- //
- // Note: can be executed synchronously.
- //
- static uint8_t
- cp (scope& sp,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err) noexcept
- try
- {
- uint8_t r (1);
- ofdstream cerr (move (err));
-
- auto error = [&cerr] (bool fail = true)
- {
- return error_record (cerr, fail, "cp");
- };
-
- try
- {
- in.close ();
- out.close ();
-
- // Parse arguments.
- //
- cli::vector_scanner scan (args);
- cp_options ops (scan);
-
- // Copy files or directories.
- //
- if (!scan.more ())
- error () << "missing arguments";
-
- // Note that the arguments semantics depends on the last argument,
- // so we read out and cache them.
- //
- small_vector<string, 2> args;
- while (scan.more ())
- args.push_back (scan.next ());
-
- const dir_path& wd (sp.wd_path);
-
- auto i (args.begin ());
- auto j (args.rbegin ());
- path dst (parse_path (move (*j++), wd));
- auto e (j.base ());
-
- if (i == e)
- error () << "missing source path";
-
- auto fail = [&error] () {return error (true);};
-
- bool cleanup (!ops.no_cleanup ());
-
- // If destination is not a directory path (no trailing separator)
- // then make a copy of the filesystem entry at the specified path
- // (the only source path is allowed in such a case). Otherwise copy
- // the source filesystem entries into the destination directory.
- //
- if (!dst.to_directory ())
- {
- path src (parse_path (move (*i++), wd));
-
- // If there are multiple sources but no trailing separator for the
- // destination, then, most likelly, it is missing.
- //
- if (i != e)
- error () << "multiple source paths without trailing separator "
- << "for destination directory";
-
- if (!ops.recursive ())
- // Synopsis 1: make a file copy at the specified path.
- //
- cpfile (sp,
- src,
- dst,
- true /* overwrite */,
- ops.preserve (),
- cleanup,
- fail);
- else
- // Synopsis 2: make a directory copy at the specified path.
- //
- cpdir (sp,
- path_cast<dir_path> (src), path_cast<dir_path> (dst),
- ops.preserve (),
- cleanup,
- fail);
- }
- else
- {
- for (; i != e; ++i)
- {
- path src (parse_path (move (*i), wd));
-
- if (ops.recursive () && dir_exists (src))
- // Synopsis 4: copy a filesystem entry into the specified
- // directory. Note that we handle only source directories here.
- // Source files are handled below.
- //
- cpdir (sp,
- path_cast<dir_path> (src),
- path_cast<dir_path> (dst / src.leaf ()),
- ops.preserve (),
- cleanup,
- fail);
- else
- // Synopsis 3: copy a file into the specified directory. Also,
- // here we cover synopsis 4 for the source path being a file.
- //
- cpfile (sp,
- src,
- dst / src.leaf (),
- true /* overwrite */,
- ops.preserve (),
- cleanup,
- fail);
- }
- }
-
- r = 0;
- }
- catch (const invalid_path& e)
- {
- error (false) << "invalid path '" << e.path << "'";
- }
- // Can be thrown while closing in, out or writing to cerr.
- //
- catch (const io_error& e)
- {
- error (false) << e;
- }
- catch (const failed&)
- {
- // Diagnostics has already been issued.
- }
- catch (const cli::exception& e)
- {
- error (false) << e;
- }
-
- cerr.close ();
- return r;
- }
- catch (const std::exception&)
- {
- return 1;
- }
-
- // echo <string>...
- //
- // Note: must be executed asynchronously.
- //
- static uint8_t
- echo (scope&,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err) noexcept
- try
- {
- uint8_t r (1);
- ofdstream cerr (move (err));
-
- try
- {
- in.close ();
- ofdstream cout (move (out));
-
- for (auto b (args.begin ()), i (b), e (args.end ()); i != e; ++i)
- cout << (i != b ? " " : "") << *i;
-
- cout << '\n';
- cout.close ();
- r = 0;
- }
- catch (const std::exception& e)
- {
- cerr << "echo: " << e << endl;
- }
-
- cerr.close ();
- return r;
- }
- catch (const std::exception&)
- {
- return 1;
- }
-
- // false
- //
- // Failure to close the file descriptors is silently ignored.
- //
- // Note: can be executed synchronously.
- //
- static builtin
- false_ (scope&, uint8_t& r, const strings&, auto_fd, auto_fd, auto_fd)
- {
- return builtin (r = 1);
- }
-
- // true
- //
- // Failure to close the file descriptors is silently ignored.
- //
- // Note: can be executed synchronously.
- //
- static builtin
- true_ (scope&, uint8_t& r, const strings&, auto_fd, auto_fd, auto_fd)
- {
- return builtin (r = 0);
- }
-
- // Create a symlink to a file or directory at the specified path. The
- // paths must be absolute. Fall back to creating a hardlink, if symlink
- // creation is not supported for the link path. If hardlink creation is
- // not supported either, then fall back to copies. If requested, created
- // filesystem entries are registered for cleanup. Fail if the target
- // filesystem entry doesn't exist or an exception is thrown by the
- // underlying filesystem operation (specifically for an already existing
- // filesystem entry at the link path).
- //
- // Note that supporting optional removal of an existing filesystem entry
- // at the link path (the -f option) tends to get hairy. As soon as an
- // existing and the resulting filesystem entries could be of different
- // types, we would end up with canceling an old cleanup and registering
- // the new one. Also removing non-empty directories doesn't look very
- // natural, but would be required if we want the behavior on POSIX and
- // Windows to be consistent.
- //
- static void
- mksymlink (scope& sp,
- const path& target, const path& link,
- bool cleanup,
- const function<error_record()>& fail)
- {
- // Determine the target type, fail if the target doesn't exist.
- //
- bool dir (false);
-
- try
- {
- pair<bool, entry_stat> pe (path_entry (target));
-
- if (!pe.first)
- fail () << "unable to create symlink to '" << target << "': "
- << "no such file or directory";
-
- dir = pe.second.type == entry_type::directory;
- }
- catch (const system_error& e)
- {
- fail () << "unable to stat '" << target << "': " << e;
- }
-
- // First we try to create a symlink. If that fails (e.g., "Windows
- // happens"), then we resort to hard links. If that doesn't work out
- // either (e.g., not on the same filesystem), then we fall back to
- // copies. So things are going to get a bit nested.
- //
- // Note: similar to mkanylink() but with support for directories.
- //
- try
- {
- mksymlink (target, link, dir);
-
- if (cleanup)
- sp.clean ({cleanup_type::always, link}, true /* implicit */);
- }
- catch (const system_error& e)
- {
- // Note that we are not guaranteed (here and below) that the
- // system_error exception is of the generic category.
- //
- int c (e.code ().value ());
- if (!(e.code ().category () == generic_category () &&
- (c == ENOSYS || // Not implemented.
- c == EPERM))) // Not supported by the filesystem(s).
- fail () << "unable to create symlink '" << link << "' to '"
- << target << "': " << e;
-
- try
- {
- mkhardlink (target, link, dir);
-
- if (cleanup)
- sp.clean ({cleanup_type::always, link}, true /* implicit */);
- }
- catch (const system_error& e)
- {
- c = e.code ().value ();
- if (!(e.code ().category () == generic_category () &&
- (c == ENOSYS || // Not implemented.
- c == EPERM || // Not supported by the filesystem(s).
- c == EXDEV))) // On different filesystems.
- fail () << "unable to create hardlink '" << link << "' to '"
- << target << "': " << e;
-
- if (dir)
- cpdir (sp,
- path_cast<dir_path> (target), path_cast<dir_path> (link),
- false,
- cleanup,
- fail);
- else
- cpfile (sp,
- target,
- link,
- false /* overwrite */,
- true /* attrs */,
- cleanup,
- fail);
- }
- }
- }
-
- // ln [--no-cleanup] -s|--symbolic <target-path> <link-path>
- // ln [--no-cleanup] -s|--symbolic <target-path>... <link-dir>/
- //
- // Note: can be executed synchronously.
- //
- static uint8_t
- ln (scope& sp,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err) noexcept
- try
- {
- uint8_t r (1);
- ofdstream cerr (move (err));
-
- auto error = [&cerr] (bool fail = true)
- {
- return error_record (cerr, fail, "ln");
- };
-
- try
- {
- in.close ();
- out.close ();
-
- // Parse arguments.
- //
- cli::vector_scanner scan (args);
- ln_options ops (scan);
-
- if (!ops.symbolic ())
- error () << "missing -s|--symbolic option";
-
- // Create file or directory symlinks.
- //
- if (!scan.more ())
- error () << "missing arguments";
-
- // Note that the arguments semantics depends on the last argument,
- // so we read out and cache them.
- //
- small_vector<string, 2> args;
- while (scan.more ())
- args.push_back (scan.next ());
-
- const dir_path& wd (sp.wd_path);
-
- auto i (args.begin ());
- auto j (args.rbegin ());
- path link (parse_path (move (*j++), wd));
- auto e (j.base ());
-
- if (i == e)
- error () << "missing target path";
-
- auto fail = [&error] () {return error (true);};
-
- bool cleanup (!ops.no_cleanup ());
-
- // If link is not a directory path (no trailing separator), then
- // create a symlink to the target path at the specified link path
- // (the only target path is allowed in such a case). Otherwise create
- // links to the target paths inside the specified directory.
- //
- if (!link.to_directory ())
- {
- path target (parse_path (move (*i++), wd));
-
- // If there are multiple targets but no trailing separator for the
- // link, then, most likelly, it is missing.
- //
- if (i != e)
- error () << "multiple target paths with non-directory link path";
-
- // Synopsis 1: create a target path symlink at the specified path.
- //
- mksymlink (sp, target, link, cleanup, fail);
- }
- else
- {
- for (; i != e; ++i)
- {
- path target (parse_path (move (*i), wd));
-
- // Synopsis 2: create a target path symlink in the specified
- // directory.
- //
- mksymlink (sp, target, link / target.leaf (), cleanup, fail);
- }
- }
-
- r = 0;
- }
- catch (const invalid_path& e)
- {
- error (false) << "invalid path '" << e.path << "'";
- }
- // Can be thrown while closing in, out or writing to cerr.
- //
- catch (const io_error& e)
- {
- error (false) << e;
- }
- catch (const failed&)
- {
- // Diagnostics has already been issued.
- }
- catch (const cli::exception& e)
- {
- error (false) << e;
- }
-
- cerr.close ();
- return r;
- }
- catch (const std::exception&)
- {
- return 1;
- }
-
- // Create a directory if not exist and its parent directories if
- // necessary. Throw system_error on failure. Register created
- // directories for cleanup. The directory path must be absolute.
- //
- static void
- mkdir_p (scope& sp, const dir_path& p, bool cleanup)
- {
- if (!dir_exists (p))
- {
- if (!p.root ())
- mkdir_p (sp, p.directory (), cleanup);
-
- try_mkdir (p); // Returns success or throws.
-
- if (cleanup)
- sp.clean ({cleanup_type::always, p}, true /* implicit */);
- }
- }
-
- // mkdir [--no-cleanup] [-p|--parents] <dir>...
- //
- // Note that POSIX doesn't specify if after a directory creation failure
- // the command should proceed with the rest of the arguments. The current
- // implementation exits immediatelly in such a case.
- //
- // Note: can be executed synchronously.
- //
- static uint8_t
- mkdir (scope& sp,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err) noexcept
- try
- {
- uint8_t r (1);
- ofdstream cerr (move (err));
-
- auto error = [&cerr] (bool fail = true)
- {
- return error_record (cerr, fail, "mkdir");
- };
-
- try
- {
- in.close ();
- out.close ();
-
- // Parse arguments.
- //
- cli::vector_scanner scan (args);
- mkdir_options ops (scan);
-
- // Create directories.
- //
- if (!scan.more ())
- error () << "missing directory";
-
- bool cleanup (!ops.no_cleanup ());
-
- while (scan.more ())
- {
- dir_path p (path_cast<dir_path> (parse_path (scan.next (),
- sp.wd_path)));
-
- try
- {
- if (ops.parents ())
- mkdir_p (sp, p, cleanup);
- else if (try_mkdir (p) == mkdir_status::success)
- {
- if (cleanup)
- sp.clean ({cleanup_type::always, p}, true /* implicit */);
- }
- else // == mkdir_status::already_exists
- throw_generic_error (EEXIST);
- }
- catch (const system_error& e)
- {
- error () << "unable to create directory '" << p << "': " << e;
- }
- }
-
- r = 0;
- }
- catch (const invalid_path& e)
- {
- error (false) << "invalid path '" << e.path << "'";
- }
- // Can be thrown while closing in, out or writing to cerr.
- //
- catch (const io_error& e)
- {
- error (false) << e;
- }
- catch (const failed&)
- {
- // Diagnostics has already been issued.
- }
- catch (const cli::exception& e)
- {
- error (false) << e;
- }
-
- cerr.close ();
- return r;
- }
- catch (const std::exception&)
- {
- return 1;
- }
-
- // mv [--no-cleanup] [-f|--force] <src-path> <dst-path>
- // mv [--no-cleanup] [-f|--force] <src-path>... <dst-dir>/
- //
- // Note: can be executed synchronously.
- //
- static uint8_t
- mv (scope& sp,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err) noexcept
- try
- {
- uint8_t r (1);
- ofdstream cerr (move (err));
-
- auto error = [&cerr] (bool fail = true)
- {
- return error_record (cerr, fail, "mv");
- };
-
- try
- {
- in.close ();
- out.close ();
-
- // Parse arguments.
- //
- cli::vector_scanner scan (args);
- mv_options ops (scan);
-
- // Move filesystem entries.
- //
- if (!scan.more ())
- error () << "missing arguments";
-
- // Note that the arguments semantics depends on the last argument,
- // so we read out and cache them.
- //
- small_vector<string, 2> args;
- while (scan.more ())
- args.push_back (scan.next ());
-
- const dir_path& wd (sp.wd_path);
-
- auto i (args.begin ());
- auto j (args.rbegin ());
- path dst (parse_path (move (*j++), wd));
- auto e (j.base ());
-
- if (i == e)
- error () << "missing source path";
-
- auto mv = [ops, &wd, &sp, &error] (const path& from, const path& to)
- {
- const dir_path& rwd (sp.root.wd_path);
-
- if (!from.sub (rwd) && !ops.force ())
- error () << "'" << from << "' is out of working directory '"
- << rwd << "'";
-
- try
- {
- auto check_wd = [&wd, &error] (const path& p)
- {
- if (wd.sub (path_cast<dir_path> (p)))
- error () << "'" << p << "' contains test working directory '"
- << wd << "'";
- };
-
- check_wd (from);
- check_wd (to);
-
- bool exists (butl::entry_exists (to));
-
- // Fail if the source and destination paths are the same.
- //
- // Note that for mventry() function (that is based on the POSIX
- // rename() function) this is a noop.
- //
- if (exists && to == from)
- error () << "unable to move entity '" << from << "' to itself";
-
- // Rename/move the filesystem entry, replacing an existing one.
- //
- mventry (from,
- to,
- cpflags::overwrite_permissions |
- cpflags::overwrite_content);
-
- // Unless suppressed, adjust the cleanups that are sub-paths of
- // the source path.
- //
- if (!ops.no_cleanup ())
- {
- // "Move" the matching cleanup if the destination path doesn't
- // exist and is a sub-path of the working directory. Otherwise
- // just remove it.
- //
- // Note that it's not enough to just change the cleanup paths.
- // We also need to make sure that these cleanups happen before
- // the destination directory (or any of its parents) cleanup,
- // that is potentially registered. To achieve that we can just
- // relocate these cleanup entries to the end of the list,
- // preserving their mutual order. Remember that cleanups in
- // the list are executed in the reversed order.
- //
- bool mv_cleanups (!exists && to.sub (rwd));
- cleanups cs;
-
- // Remove the source path sub-path cleanups from the list,
- // adjusting/caching them if required (see above).
- //
- for (auto i (sp.cleanups.begin ()); i != sp.cleanups.end (); )
- {
- cleanup& c (*i);
- path& p (c.path);
-
- if (p.sub (from))
- {
- if (mv_cleanups)
- {
- // Note that we need to preserve the cleanup path
- // trailing separator which indicates the removal
- // method. Also note that leaf(), in particular, does
- // that.
- //
- p = p != from
- ? to / p.leaf (path_cast<dir_path> (from))
- : p.to_directory ()
- ? path_cast<dir_path> (to)
- : to;
-
- cs.push_back (move (c));
- }
-
- i = sp.cleanups.erase (i);
- }
- else
- ++i;
- }
-
- // Re-insert the adjusted cleanups at the end of the list.
- //
- sp.cleanups.insert (sp.cleanups.end (),
- make_move_iterator (cs.begin ()),
- make_move_iterator (cs.end ()));
- }
- }
- catch (const system_error& e)
- {
- error () << "unable to move entity '" << from << "' to '" << to
- << "': " << e;
- }
- };
-
- // If destination is not a directory path (no trailing separator)
- // then move the filesystem entry to the specified path (the only
- // source path is allowed in such a case). Otherwise move the source
- // filesystem entries into the destination directory.
- //
- if (!dst.to_directory ())
- {
- path src (parse_path (move (*i++), wd));
-
- // If there are multiple sources but no trailing separator for the
- // destination, then, most likelly, it is missing.
- //
- if (i != e)
- error () << "multiple source paths without trailing separator "
- << "for destination directory";
-
- // Synopsis 1: move an entity to the specified path.
- //
- mv (src, dst);
- }
- else
- {
- // Synopsis 2: move entities into the specified directory.
- //
- for (; i != e; ++i)
- {
- path src (parse_path (move (*i), wd));
- mv (src, dst / src.leaf ());
- }
- }
-
- r = 0;
- }
- catch (const invalid_path& e)
- {
- error (false) << "invalid path '" << e.path << "'";
- }
- // Can be thrown while closing in, out or writing to cerr.
- //
- catch (const io_error& e)
- {
- error (false) << e;
- }
- catch (const failed&)
- {
- // Diagnostics has already been issued.
- }
- catch (const cli::exception& e)
- {
- error (false) << e;
- }
-
- cerr.close ();
- return r;
- }
- catch (const std::exception&)
- {
- return 1;
- }
-
- // rm [-r|--recursive] [-f|--force] <path>...
- //
- // The implementation deviates from POSIX in a number of ways. It doesn't
- // interact with a user and fails immediatelly if unable to process an
- // argument. It doesn't check for dots containment in the path, and
- // doesn't consider files and directory permissions in any way just
- // trying to remove a filesystem entry. Always fails if empty path is
- // specified.
- //
- // Note: can be executed synchronously.
- //
- static uint8_t
- rm (scope& sp,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err) noexcept
- try
- {
- uint8_t r (1);
- ofdstream cerr (move (err));
-
- auto error = [&cerr] (bool fail = true)
- {
- return error_record (cerr, fail, "rm");
- };
-
- try
- {
- in.close ();
- out.close ();
-
- // Parse arguments.
- //
- cli::vector_scanner scan (args);
- rm_options ops (scan);
-
- // Remove entries.
- //
- if (!scan.more () && !ops.force ())
- error () << "missing file";
-
- const dir_path& wd (sp.wd_path);
- const dir_path& rwd (sp.root.wd_path);
-
- while (scan.more ())
- {
- path p (parse_path (scan.next (), wd));
-
- if (!p.sub (rwd) && !ops.force ())
- error () << "'" << p << "' is out of working directory '" << rwd
- << "'";
-
- try
- {
- dir_path d (path_cast<dir_path> (p));
-
- if (dir_exists (d))
- {
- if (!ops.recursive ())
- error () << "'" << p << "' is a directory";
-
- if (wd.sub (d))
- error () << "'" << p << "' contains test working directory '"
- << wd << "'";
-
- // The call can result in rmdir_status::not_exist. That's not
- // very likelly but there is also nothing bad about it.
- //
- try_rmdir_r (d);
- }
- else if (try_rmfile (p) == rmfile_status::not_exist &&
- !ops.force ())
- throw_generic_error (ENOENT);
- }
- catch (const system_error& e)
- {
- error () << "unable to remove '" << p << "': " << e;
- }
- }
-
- r = 0;
- }
- catch (const invalid_path& e)
- {
- error (false) << "invalid path '" << e.path << "'";
- }
- // Can be thrown while closing in, out or writing to cerr.
- //
- catch (const io_error& e)
- {
- error (false) << e;
- }
- catch (const failed&)
- {
- // Diagnostics has already been issued.
- }
- catch (const cli::exception& e)
- {
- error (false) << e;
- }
-
- cerr.close ();
- return r;
- }
- catch (const std::exception&)
- {
- return 1;
- }
-
- // rmdir [-f|--force] <path>...
- //
- // Note: can be executed synchronously.
- //
- static uint8_t
- rmdir (scope& sp,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err) noexcept
- try
- {
- uint8_t r (1);
- ofdstream cerr (move (err));
-
- auto error = [&cerr] (bool fail = true)
- {
- return error_record (cerr, fail, "rmdir");
- };
-
- try
- {
- in.close ();
- out.close ();
-
- // Parse arguments.
- //
- cli::vector_scanner scan (args);
- rmdir_options ops (scan);
-
- // Remove directories.
- //
- if (!scan.more () && !ops.force ())
- error () << "missing directory";
-
- const dir_path& wd (sp.wd_path);
- const dir_path& rwd (sp.root.wd_path);
-
- while (scan.more ())
- {
- dir_path p (path_cast<dir_path> (parse_path (scan.next (), wd)));
-
- if (wd.sub (p))
- error () << "'" << p << "' contains test working directory '"
- << wd << "'";
-
- if (!p.sub (rwd) && !ops.force ())
- error () << "'" << p << "' is out of working directory '"
- << rwd << "'";
-
- try
- {
- rmdir_status s (try_rmdir (p));
-
- if (s == rmdir_status::not_empty)
- throw_generic_error (ENOTEMPTY);
- else if (s == rmdir_status::not_exist && !ops.force ())
- throw_generic_error (ENOENT);
- }
- catch (const system_error& e)
- {
- error () << "unable to remove '" << p << "': " << e;
- }
- }
-
- r = 0;
- }
- catch (const invalid_path& e)
- {
- error (false) << "invalid path '" << e.path << "'";
- }
- // Can be thrown while closing in, out or writing to cerr.
- //
- catch (const io_error& e)
- {
- error (false) << e;
- }
- catch (const failed&)
- {
- // Diagnostics has already been issued.
- }
- catch (const cli::exception& e)
- {
- error (false) << e;
- }
-
- cerr.close ();
- return r;
- }
- catch (const std::exception&)
- {
- return 1;
- }
-
- // sed [-n|--quiet] [-i|--in-place] -e|--expression <script> [<file>]
- //
- // Note: must be executed asynchronously.
- //
- static uint8_t
- sed (scope& sp,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err) noexcept
- try
- {
- uint8_t r (1);
- ofdstream cerr (move (err));
-
- auto error = [&cerr] (bool fail = true)
- {
- return error_record (cerr, fail, "sed");
- };
-
- try
- {
- // Automatically remove a temporary file (used for in place editing)
- // on failure.
- //
- auto_rmfile rm;
-
- // Do not throw when failbit is set (getline() failed to extract any
- // character).
- //
- ifdstream cin (move (in), ifdstream::badbit);
- ofdstream cout (move (out));
-
- // Parse arguments.
- //
- cli::vector_scanner scan (args);
- sed_options ops (scan);
-
- if (ops.expression ().empty ())
- error () << "missing script";
-
- // Only a single script is supported.
- //
- if (ops.expression ().size () != 1)
- error () << "multiple scripts";
-
- struct
- {
- string regex;
- string replacement;
- bool icase = false;
- bool global = false;
- bool print = false;
- } subst;
-
- {
- const string& v (ops.expression ()[0]);
- if (v.empty ())
- error () << "empty script";
-
- if (v[0] != 's')
- error () << "only 's' command supported";
-
- // Parse the substitute command.
- //
- if (v.size () < 2)
- error () << "no delimiter for 's' command";
-
- char delim (v[1]);
- if (delim == '\\' || delim == '\n')
- error () << "invalid delimiter for 's' command";
-
- size_t p (v.find (delim, 2));
- if (p == string::npos)
- error () << "unterminated 's' command regex";
-
- subst.regex.assign (v, 2, p - 2);
-
- // Empty regex matches nothing, so not of much use.
- //
- if (subst.regex.empty ())
- error () << "empty regex in 's' command";
-
- size_t b (p + 1);
- p = v.find (delim, b);
- if (p == string::npos)
- error () << "unterminated 's' command replacement";
-
- subst.replacement.assign (v, b, p - b);
-
- // Parse the substitute command flags.
- //
- char c;
- for (++p; (c = v[p]) != '\0'; ++p)
- {
- switch (c)
- {
- case 'i': subst.icase = true; break;
- case 'g': subst.global = true; break;
- case 'p': subst.print = true; break;
- default:
- {
- error () << "invalid 's' command flag '" << c << "'";
- }
- }
- }
- }
-
- // Path of a file to edit. An empty path represents stdin.
- //
- path p;
- if (scan.more ())
- {
- string f (scan.next ());
-
- if (f != "-")
- p = parse_path (move (f), sp.wd_path);
- }
-
- if (scan.more ())
- error () << "unexpected argument '" << scan.next () << "'";
-
- // Edit file.
- //
- // If we edit file in place make sure that the file path is specified
- // and obtain a temporary file path. We will be writing to the
- // temporary file (rather than to stdout) and will move it to the
- // original file path afterwards.
- //
- path tp;
- if (ops.in_place ())
- {
- if (p.empty ())
- error () << "-i|--in-place option specified while reading from "
- << "stdin";
-
- try
- {
- tp = path::temp_path ("build2-sed");
-
- cout.close (); // Flush and close.
-
- cout.open (
- fdopen (tp,
- fdopen_mode::out | fdopen_mode::truncate |
- fdopen_mode::create,
- path_permissions (p)));
- }
- catch (const io_error& e)
- {
- error_record d (error ());
- d << "unable to open '" << tp << "': " << e;
- }
- catch (const system_error& e)
- {
- error_record d (error ());
- d << "unable to obtain temporary file: " << e;
- }
-
- rm = auto_rmfile (tp);
- }
-
- // Note that ECMAScript is implied if no grammar flag is specified.
- //
- regex re (subst.regex,
- subst.icase ? regex::icase : regex::ECMAScript);
-
- // Edit a file or STDIN.
- //
- try
- {
- // Open a file if specified.
- //
- if (!p.empty ())
- {
- cin.close (); // Flush and close.
- cin.open (p);
- }
-
- // Read until failbit is set (throw on badbit).
- //
- string s;
- while (getline (cin, s))
- {
- auto r (regex_replace_search (
- s,
- re,
- subst.replacement,
- subst.global
- ? regex_constants::format_default
- : regex_constants::format_first_only));
-
- // Add newline regardless whether the source line is newline-
- // terminated or not (in accordance with POSIX).
- //
- if (!ops.quiet () || (r.second && subst.print))
- cout << r.first << '\n';
- }
-
- cin.close ();
- cout.close ();
-
- if (ops.in_place ())
- {
- mvfile (
- tp, p,
- cpflags::overwrite_content | cpflags::overwrite_permissions);
-
- rm.cancel ();
- }
-
- r = 0;
- }
- catch (const io_error& e)
- {
- error_record d (error ());
- d << "unable to edit ";
-
- if (p.empty ())
- d << "stdin";
- else
- d << "'" << p << "'";
-
- d << ": " << e;
- }
- }
- catch (const regex_error& e)
- {
- // Print regex_error description if meaningful (no space).
- //
- error (false) << "invalid regex" << e;
- }
- catch (const invalid_path& e)
- {
- error (false) << "invalid path '" << e.path << "'";
- }
- // Can be thrown while creating cin, cout or writing to cerr.
- //
- catch (const io_error& e)
- {
- error (false) << e;
- }
- catch (const system_error& e)
- {
- error (false) << e;
- }
- catch (const failed&)
- {
- // Diagnostics has already been issued.
- }
- catch (const cli::exception& e)
- {
- error (false) << e;
- }
-
- cerr.close ();
- return r;
- }
- catch (const std::exception&)
- {
- return 1;
- }
-
- // sleep <seconds>
- //
- // Note: can be executed synchronously.
- //
- static uint8_t
- sleep (scope& s,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err) noexcept
- try
- {
- uint8_t r (1);
- ofdstream cerr (move (err));
-
- auto error = [&cerr] (bool fail = true)
- {
- return error_record (cerr, fail, "sleep");
- };
-
- try
- {
- in.close ();
- out.close ();
-
- // Parse arguments.
- //
- cli::vector_scanner scan (args);
- sleep_options ops (scan); // Makes sure no options passed.
-
- if (!scan.more ())
- error () << "missing time interval";
-
- uint64_t n;
-
- for (;;) // Breakout loop.
- {
- string a (scan.next ());
-
- // Note: strtoull() allows these.
- //
- if (!a.empty () && a[0] != '-' && a[0] != '+')
- {
- char* e (nullptr);
- n = strtoull (a.c_str (), &e, 10); // Can't throw.
-
- if (errno != ERANGE && e == a.c_str () + a.size ())
- break;
- }
-
- error () << "invalid time interval '" << a << "'";
- }
-
- if (scan.more ())
- error () << "unexpected argument '" << scan.next () << "'";
-
- // Sleep.
- //
- // If/when required we could probably support the precise sleep mode
- // (e.g., via an option).
- //
- s.root.test_target.ctx.sched.sleep (chrono::seconds (n));
-
- r = 0;
- }
- // Can be thrown while closing in, out or writing to cerr.
- //
- catch (const io_error& e)
- {
- error (false) << e;
- }
- catch (const failed&)
- {
- // Diagnostics has already been issued.
- }
- catch (const cli::exception& e)
- {
- error (false) << e;
- }
-
- cerr.close ();
- return r;
- }
- catch (const std::exception&)
- {
- return 1;
- }
-
- // test (-f|--file)|(-d|--directory) <path>
- //
- // Note: can be executed synchronously.
- //
- static uint8_t
- test (scope& sp,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err) noexcept
- try
- {
- uint8_t r (2);
- ofdstream cerr (move (err));
-
- auto error = [&cerr] (bool fail = true)
- {
- return error_record (cerr, fail, "test");
- };
-
- try
- {
- in.close ();
- out.close ();
-
- // Parse arguments.
- //
- cli::vector_scanner scan (args);
- test_options ops (scan); // Makes sure no options passed.
-
- if (!ops.file () && !ops.directory ())
- error () << "either -f|--file or -d|--directory must be specified";
-
- if (ops.file () && ops.directory ())
- error () << "both -f|--file and -d|--directory specified";
-
- if (!scan.more ())
- error () << "missing path";
-
- path p (parse_path (scan.next (), sp.wd_path));
-
- if (scan.more ())
- error () << "unexpected argument '" << scan.next () << "'";
-
- try
- {
- r = (ops.file () ? file_exists (p) : dir_exists (p)) ? 0 : 1;
- }
- catch (const system_error& e)
- {
- error () << "cannot test '" << p << "': " << e;
- }
- }
- catch (const invalid_path& e)
- {
- error (false) << "invalid path '" << e.path << "'";
- }
- // Can be thrown while closing in, out or writing to cerr.
- //
- catch (const io_error& e)
- {
- error (false) << e;
- }
- catch (const failed&)
- {
- // Diagnostics has already been issued.
- }
- catch (const cli::exception& e)
- {
- error (false) << e;
- }
-
- cerr.close ();
- return r;
- }
- catch (const std::exception&)
- {
- return 2;
- }
-
- // touch [--no-cleanup] [--after <ref-file>] <file>...
- //
- // Note that POSIX doesn't specify the behavior for touching an entry
- // other than file.
- //
- // Also note that POSIX doesn't specify if after a file touch failure the
- // command should proceed with the rest of the arguments. The current
- // implementation exits immediatelly in such a case.
- //
- // Note: can be executed synchronously.
- //
- static uint8_t
- touch (scope& sp,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err) noexcept
- try
- {
- uint8_t r (1);
- ofdstream cerr (move (err));
-
- auto error = [&cerr] (bool fail = true)
- {
- return error_record (cerr, fail, "touch");
- };
-
- try
- {
- in.close ();
- out.close ();
-
- // Parse arguments.
- //
- cli::vector_scanner scan (args);
- touch_options ops (scan);
-
- auto mtime = [&error] (const path& p) -> timestamp
- {
- try
- {
- timestamp t (file_mtime (p));
-
- if (t == timestamp_nonexistent)
- throw_generic_error (ENOENT);
-
- return t;
- }
- catch (const system_error& e)
- {
- error () << "cannot obtain file '" << p
- << "' modification time: " << e;
- }
- assert (false); // Can't be here.
- return timestamp ();
- };
-
- optional<timestamp> after;
- if (ops.after_specified ())
- after = mtime (parse_path (ops.after (), sp.wd_path));
-
- if (!scan.more ())
- error () << "missing file";
-
- // Create files.
- //
- while (scan.more ())
- {
- path p (parse_path (scan.next (), sp.wd_path));
-
- try
- {
- // Note that we don't register (implicit) cleanup for an
- // existing path.
- //
- if (touch_file (p) && !ops.no_cleanup ())
- sp.clean ({cleanup_type::always, p}, true /* implicit */);
-
- if (after)
- {
- while (mtime (p) <= *after)
- touch_file (p, false /* create */);
- }
- }
- catch (const system_error& e)
- {
- error () << "cannot create/update '" << p << "': " << e;
- }
- }
-
- r = 0;
- }
- catch (const invalid_path& e)
- {
- error (false) << "invalid path '" << e.path << "'";
- }
- // Can be thrown while closing in, out or writing to cerr.
- //
- catch (const io_error& e)
- {
- error (false) << e;
- }
- catch (const failed&)
- {
- // Diagnostics has already been issued.
- }
- catch (const cli::exception& e)
- {
- error (false) << e;
- }
-
- cerr.close ();
- return r;
- }
- catch (const std::exception&)
- {
- return 1;
- }
-
- // Run builtin implementation asynchronously.
- //
- static builtin
- async_impl (builtin_impl* fn,
- scope& sp,
- uint8_t& r,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err)
- {
- return builtin (
- r,
- thread ([fn, &sp, &r, &args,
- in = move (in),
- out = move (out),
- err = move (err)] () mutable noexcept
- {
- r = fn (sp, args, move (in), move (out), move (err));
- }));
- }
-
- template <builtin_impl fn>
- static builtin
- async_impl (scope& sp,
- uint8_t& r,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err)
- {
- return async_impl (fn, sp, r, args, move (in), move (out), move (err));
- }
-
- // Run builtin implementation synchronously.
- //
- template <builtin_impl fn>
- static builtin
- sync_impl (scope& sp,
- uint8_t& r,
- const strings& args,
- auto_fd in, auto_fd out, auto_fd err)
- {
- r = fn (sp, args, move (in), move (out), move (err));
- return builtin (r, thread ());
- }
-
- const builtin_map builtins
- {
- {"cat", &async_impl<&cat>},
- {"cp", &sync_impl<&cp>},
- {"echo", &async_impl<&echo>},
- {"false", &false_},
- {"ln", &sync_impl<&ln>},
- {"mkdir", &sync_impl<&mkdir>},
- {"mv", &sync_impl<&mv>},
- {"rm", &sync_impl<&rm>},
- {"rmdir", &sync_impl<&rmdir>},
- {"sed", &async_impl<&sed>},
- {"sleep", &sync_impl<&sleep>},
- {"test", &sync_impl<&test>},
- {"touch", &sync_impl<&touch>},
- {"true", &true_}
- };
- }
- }
-}