diff --git a/src/core/op.cpp b/src/core/op.cpp index acf8965..4a3eadd 100644 --- a/src/core/op.cpp +++ b/src/core/op.cpp @@ -8,6 +8,8 @@ namespace mob::op { +// most of the functions from the header will check the paths, return early +// for dry run, and then forward to these to do the actual work void do_touch(const context& cx, const fs::path& p); void do_create_directories(const context& cx, const fs::path& p); void do_delete_directory(const context& cx, const fs::path& p); @@ -16,24 +18,25 @@ void do_copy_file_to_dir(const context& cx, const fs::path& f, const fs::path& d void do_copy_file_to_file(const context& cx, const fs::path& f, const fs::path& d); void do_remove_readonly(const context& cx, const fs::path& p); void do_rename(const context& cx, const fs::path& src, const fs::path& dest); -void check(const context& cx, const fs::path& p); + +// checks whether the path is valid, bails out if not +// +void check(const context& cx, const fs::path& p, flags f); -void touch(const context& cx, const fs::path& p) +void touch(const context& cx, const fs::path& p, flags f) { cx.trace(context::fs, "touching {}", p); - check(cx, p); + check(cx, p, f); if (!conf::dry()) - do_touch(cx ,p); + do_touch(cx, p); } void create_directories(const context& cx, const fs::path& p, flags f) { cx.trace(context::fs, "creating dir {}", p); - - if (!is_set(f, unsafe)) - check(cx, p); + check(cx, p, f); if (!conf::dry()) do_create_directories(cx, p); @@ -42,7 +45,7 @@ void create_directories(const context& cx, const fs::path& p, flags f) void delete_directory(const context& cx, const fs::path& p, flags f) { cx.trace(context::fs, "deleting dir {}", p); - check(cx, p); + check(cx, p, f); if (!fs::exists(p)) { @@ -67,7 +70,7 @@ void delete_directory(const context& cx, const fs::path& p, flags f) void delete_file(const context& cx, const fs::path& p, flags f) { cx.trace(context::fs, "deleting file {}", p); - check(cx, p); + check(cx, p, f); if (!fs::exists(p)) { @@ -123,17 +126,14 @@ void delete_file_glob(const context& cx, const fs::path& glob, flags f) } } -void remove_readonly(const context& cx, const fs::path& first) +void remove_readonly(const context& cx, const fs::path& dir, flags f) { - cx.trace(context::fs, "removing read-only from {}", first); - check(cx, first); + cx.trace(context::fs, "removing read-only from {}", dir); + check(cx, dir, f); if (!conf::dry()) { - if (fs::is_regular_file(first)) - do_remove_readonly(cx, first); - - for (auto&& p : fs::recursive_directory_iterator(first)) + for (auto&& p : fs::recursive_directory_iterator(dir)) { if (fs::is_regular_file(p)) do_remove_readonly(cx, p); @@ -215,10 +215,10 @@ bool is_source_better( return false; } -void rename(const context& cx, const fs::path& src, const fs::path& dest) +void rename(const context& cx, const fs::path& src, const fs::path& dest, flags f) { - check(cx, src); - check(cx, dest); + check(cx, src, f); + check(cx, dest, f); if (fs::exists(dest)) { @@ -227,14 +227,16 @@ void rename(const context& cx, const fs::path& src, const fs::path& dest) } cx.trace(context::fs, "renaming {} to {}", src, dest); - do_rename(cx, src, dest); + + if (!conf::dry()) + do_rename(cx, src, dest); } void move_to_directory( - const context& cx, const fs::path& src, const fs::path& dest_dir) + const context& cx, const fs::path& src, const fs::path& dest_dir, flags f) { - check(cx, src); - check(cx, dest_dir); + check(cx, src, f); + check(cx, dest_dir, f); const auto target = dest_dir / src.filename(); @@ -246,17 +248,16 @@ void move_to_directory( } cx.trace(context::fs, "moving {} to {}", src, target); - do_rename(cx, src, target); + + if (!conf::dry()) + do_rename(cx, src, target); } void copy_file_to_dir_if_better( const context& cx, const fs::path& file, const fs::path& dir, flags f) { - if ((f & unsafe) == 0) - { - check(cx, file); - check(cx, dir); - } + check(cx, file, f); + check(cx, dir, f); if (file.u8string().find(u8"*") != std::string::npos) cx.bail_out(context::fs, "{} contains a glob", file); @@ -297,11 +298,8 @@ void copy_file_to_dir_if_better( void copy_file_to_file_if_better( const context& cx, const fs::path& src, const fs::path& dest, flags f) { - if ((f & unsafe) == 0) - { - check(cx, src); - check(cx, dest); - } + check(cx, src, f); + check(cx, dest, f); if (src.u8string().find(u8"*") != std::string::npos) cx.bail_out(context::fs, "{} contains a glob", src); @@ -345,6 +343,8 @@ void copy_glob_to_dir_if_better( const context& cx, const fs::path& src_glob, const fs::path& dest_dir, flags f) { + check(cx, dest_dir, f); + const auto file_parent = src_glob.parent_path(); const auto wildcard = src_glob.filename().native(); @@ -400,12 +400,15 @@ void copy_glob_to_dir_if_better( } } -void swap_files( +void replace_file( const context& cx, const fs::path& src, const fs::path& dest, - const fs::path& backup, flags) + const fs::path& backup, flags f) { cx.trace(context::fs, "swapping {} and {}", src, dest); + check(cx, src, f); + check(cx, dest, f); + if (conf::dry()) return; @@ -483,11 +486,14 @@ void write_text_file( const context& cx, encodings e, const fs::path& p, std::string_view utf8, flags f) { - check(cx, p); - const std::string bytes = utf8_to_bytes(e, utf8); cx.trace(context::fs, "writing {} bytes to {}", bytes.size(), p); + check(cx, p, f); + + if (conf::dry()) + return; + { std::ofstream out(p, std::ios::binary); out.write(bytes.data(), static_cast(bytes.size())); @@ -513,9 +519,10 @@ void write_text_file( void archive_from_glob( const context& cx, const fs::path& src_glob, const fs::path& dest_file, - const std::vector& ignore) + const std::vector& ignore, flags f) { cx.trace(context::fs, "archiving {} into {}", src_glob, dest_file); + check(cx, dest_file, f); if (conf::dry()) return; @@ -526,8 +533,10 @@ void archive_from_glob( void archive_from_files( const context& cx, const std::vector& files, const fs::path& files_root, - const fs::path& dest_file) + const fs::path& dest_file, flags f) { + check(cx, dest_file, f); + cx.trace(context::fs, "archiving {} files rooted in {} into {}", files.size(), files_root, dest_file); @@ -652,11 +661,14 @@ void do_rename(const context& cx, const fs::path& src, const fs::path& dest) } } -void check(const context& cx, const fs::path& p) +void check(const context& cx, const fs::path& p, flags f) { if (p.empty()) cx.bail_out(context::fs, "path is empty"); + if (is_set(f, unsafe)) + return; + auto is_inside = [](auto&& p, auto&& dir) { const std::string s = path_to_utf8(p); diff --git a/src/core/op.h b/src/core/op.h index b81fc16..a996696 100644 --- a/src/core/op.h +++ b/src/core/op.h @@ -7,72 +7,143 @@ namespace mob { class context; } namespace mob::op { +// filesystem operations, also handle --dry +// +// for functions that end with _if_better(): the source is considered better +// than the destination if: +// 1) the destination doesn't exist, or +// 2) the size is different, or +// 3) the date is newer + + +// various flags for the operations below, only some of them are used by some +// functions +// enum flags { noflags = 0x00, + + // the operation is optional, don't bail out if it fails optional = 0x01, + + // used by copy_glob_to_dir_if_better() to decide if files and/or + // directories are copied copy_files = 0x02, copy_dirs = 0x04, + + // operations will typically fail early if paths are empty or if they're not + // inside a list of approved locations, like the prefix, %TEMP%, etc. + // + // this is to prevent mob from going on a deletion spree in case of bugs unsafe = 0x08 }; MOB_ENUM_OPERATORS(flags); -void touch(const context& cx, const fs::path& p); +// creates the given file if it doesn't exist +// +void touch(const context& cx, const fs::path& p, flags f=noflags); -void create_directories( - const context& cx, const fs::path& p, flags f=noflags); +// creates all the directories in the given path +// +void create_directories(const context& cx, const fs::path& p, flags f=noflags); -void delete_directory( - const context& cx, const fs::path& p, flags f=noflags); +// deletes the given directory, recursive +// +// if deletion fails because of access denied, attemps to remove the readonly +// flag on all files and tries again; this happens with some archives like 7z +// +void delete_directory(const context& cx, const fs::path& p, flags f=noflags); -void delete_file( - const context& cx, const fs::path& p, flags f=noflags); +// deletes the given file +// +void delete_file(const context& cx, const fs::path& p, flags f=noflags); -void delete_file_glob( - const context& cx, const fs::path& glob, flags f=noflags); +// deletes all files matching the glob in the glob's parent directory +// +void delete_file_glob(const context& cx, const fs::path& glob, flags f=noflags); -void remove_readonly( - const context& cx, const fs::path& first); +// removes the readonly flag for all files in `dir`, recursive +// +void remove_readonly(const context& cx, const fs::path& dir, flags f=noflags); +// renames `src` to `dest`, files or directories; fails if it already exists +// void rename( - const context& cx, const fs::path& src, const fs::path& dest); + const context& cx, const fs::path& src, const fs::path& dest, + flags f=noflags); +// moves a file or directory `src` into dir `dest_dir`, using the same name +// (renames src to dest_dir/src.filename()); fails if it already exists +// void move_to_directory( - const context& cx, const fs::path& src, const fs::path& dest_dir); + const context& cx, const fs::path& src, const fs::path& dest_dir, + flags f=noflags); +// copies a single file `file` into `dest_dir`; if the file already exists, only +// copies it if it's considered better (see comment on top); doesn't support +// globs or directories +// void copy_file_to_dir_if_better( const context& cx, const fs::path& file, const fs::path& dest_dir, flags f=noflags); +// same as copy_file_to_dir_if_better(), but the `dest_file` contains the +// target filename instead of being constructed from dest_dir/src.filename() +// void copy_file_to_file_if_better( const context& cx, const fs::path& src_file, const fs::path& dest_file, flags f=noflags); +// basically calls copy_file_to_dir_if_better() for every file matching the +// glob; recursive +// void copy_glob_to_dir_if_better( const context& cx, const fs::path& src_glob, const fs::path& dest_dir, flags f); -void swap_files( +// renames `dest` to `src`, deleting `src` if it exists; if `backup` is given, +// `src` is first renamed to it +// +// this attempts an atomic rename with ReplaceFile(), falls back to non-atomic +// renames if it fails +// +void replace_file( const context& cx, const fs::path& src, const fs::path& dest, const fs::path& backup={}, flags f=noflags); +// reads the given file, converts it to utf8 from the given encoding, returns +// the utf8 string; if `e` is `dont_know`, returns the bytes as-is +// std::string read_text_file( const context& cx, encodings e, const fs::path& p, flags f=noflags); +// creates file `p`, writes the given utf8 string into it, converting the string +// to the given encoding; if `e` is dont_know, the bytes are written as-is +// void write_text_file( const context& cx, encodings e, const fs::path& p, std::string_view utf8, flags f=noflags); +// creates an archive `dest_file` and puts all the files matching `src_glob` +// into it, ignoring any file in `ignore` by name +// +// uses tools::archiver +// void archive_from_glob( const context& cx, const fs::path& src_glob, const fs::path& dest_file, - const std::vector& ignore); + const std::vector& ignore, + flags f=noflags); +// creates an archive `dest_file` and puts all the files from `files` in it, +// resolving relative paths against `files_root` +// void archive_from_files( const context& cx, const std::vector& files, const fs::path& files_root, - const fs::path& dest_file); + const fs::path& dest_file, + flags f=noflags); } // namespace diff --git a/src/tasks/sip.cpp b/src/tasks/sip.cpp index 3503f30..7fc0ccf 100644 --- a/src/tasks/sip.cpp +++ b/src/tasks/sip.cpp @@ -158,7 +158,7 @@ void sip::generate() { const std::string utf8 = op::read_text_file(cx(), encodings::utf8, src); op::write_text_file(cx(), encodings::acp, dest, utf8); - op::swap_files(cx(), src, dest, backup); + op::replace_file(cx(), src, dest, backup); } run_tool(process_runner(process()