aboutsummaryrefslogtreecommitdiff
path: root/build2
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2016-11-25 18:40:03 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2016-12-05 17:21:48 +0300
commit4bad3a3aa3ffc111197f1d5d4b50baa1c4390444 (patch)
tree10eb60d71cf2006ea1718e38cd0f6c974238669d /build2
parent2e4ba5a271cb5967feaf4686337e8f63d45acb1f (diff)
Add rm builtin
Diffstat (limited to 'build2')
-rw-r--r--build2/test/script/builtin.cxx141
1 files changed, 141 insertions, 0 deletions
diff --git a/build2/test/script/builtin.cxx b/build2/test/script/builtin.cxx
index 2dcfde6..b12ae7e 100644
--- a/build2/test/script/builtin.cxx
+++ b/build2/test/script/builtin.cxx
@@ -370,6 +370,146 @@ namespace build2
return 1;
}
+ // rm [-r] [-f] <path>...
+ //
+ // Remove a file or directory. A path must not be the test scope working
+ // directory or its parent directory. It also must not be outside the
+ // script working directory unless -f option is specified. Note that
+ // directories are not removed by default.
+ //
+ // -r
+ // Remove directories recursively. Must be specified to remove even an
+ // empty directory.
+ //
+ // -f
+ // Do not fail if path doesn't exist or no paths specified. Removing
+ // paths outside the script working directory is not an error.
+ //
+ // 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));
+
+ try
+ {
+ in.close ();
+ out.close ();
+
+ auto i (args.begin ());
+
+ // Process options.
+ //
+ bool dir (false);
+ bool force (false);
+ for (; i != args.end (); ++i)
+ {
+ if (*i == "-r")
+ dir = true;
+ else if (*i == "-f")
+ force = true;
+ else
+ {
+ if (*i == "--")
+ ++i;
+
+ break;
+ }
+ }
+
+ // Remove entries.
+ //
+ if (i == args.end () && !force)
+ {
+ cerr << "rm: missing file" << endl;
+ throw failed ();
+ }
+
+ for (; i != args.end (); ++i)
+ {
+ path p (parse_path (*i, sp.wd_path));
+
+ if (!p.sub (sp.root->wd_path) && !force)
+ {
+ cerr << "rm: '" << p << "' is outside script working directory"
+ << endl;
+ throw failed ();
+ }
+
+ try
+ {
+ dir_path d (path_cast<dir_path> (p));
+
+ if (dir_exists (d))
+ {
+ if (!dir)
+ {
+ cerr << "rm: '" << p << "' is a directory" << endl;
+ throw failed ();
+ }
+
+ if (sp.wd_path.sub (d))
+ {
+ cerr << "rm: '" << p << "' contains scope working directory"
+ << endl;
+ throw failed ();
+ }
+
+ // 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 && !force)
+ throw system_error (ENOENT, system_category ());
+ }
+ catch (const system_error& e)
+ {
+ cerr << "rm: unable to remove '" << p << "': " << e.what ()
+ << endl;
+ throw failed ();
+ }
+ }
+
+ r = 0;
+ }
+ catch (const invalid_path& e)
+ {
+ cerr << "rm: invalid path '" << e.path << "'" << endl;
+ }
+ // Can be thrown while closing in, out or writing to cerr (that's why
+ // need to check its state before writing).
+ //
+ catch (const io_error& e)
+ {
+ if (cerr.good ())
+ cerr << "rm: " << e.what () << endl;
+ }
+ catch (const failed&)
+ {
+ // Diagnostics has already been issued.
+ }
+
+ cerr.close ();
+ return r;
+ }
+ catch (const std::exception&)
+ {
+ return 1;
+ }
+
// touch <file>...
//
// Change file access and modification times to the current time. Create
@@ -548,6 +688,7 @@ namespace build2
{"echo", &async_impl<&echo>},
{"false", &false_},
{"mkdir", &sync_impl<&mkdir>},
+ {"rm", &sync_impl<&rm>},
{"touch", &sync_impl<&touch>},
{"true", &true_}
};