From 21fdefd297db7d64fec41968dc7cfec2cbff183b Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Sat, 22 Jul 2023 10:24:44 +0100 Subject: [PATCH] FileOperation: Preserve mode bits and owner when copying files Previously, all files and folders copied with FileManager would be created with the default mode bits set and would have the same uid and gid as the currently logged in user. --- Userland/Services/FileOperation/main.cpp | 42 +++++++++++++++++------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/Userland/Services/FileOperation/main.cpp b/Userland/Services/FileOperation/main.cpp index a8d5f61d2d..1080b6331a 100644 --- a/Userland/Services/FileOperation/main.cpp +++ b/Userland/Services/FileOperation/main.cpp @@ -30,6 +30,9 @@ struct WorkItem { DeprecatedString source; DeprecatedString destination; off_t size; + mode_t mode { 0 }; + uid_t uid { 0 }; + gid_t gid { 0 }; }; static void report_warning(StringView message); @@ -38,7 +41,7 @@ static ErrorOr perform_copy(Vector const& sources, DeprecatedSt static ErrorOr perform_move(Vector const& sources, DeprecatedString const& destination); static ErrorOr perform_delete(Vector const& sources); static ErrorOr execute_work_items(Vector const& items); -static ErrorOr> open_destination_file(DeprecatedString const& destination); +static ErrorOr> open_destination_file(DeprecatedString const& destination, mode_t mode); static DeprecatedString deduplicate_destination_file_name(DeprecatedString const& destination); ErrorOr serenity_main(Main::Arguments arguments) @@ -80,13 +83,17 @@ static void report_error(StringView message) static ErrorOr collect_copy_work_items(DeprecatedString const& source, DeprecatedString const& destination, Vector& items) { - if (auto const st = TRY(Core::System::lstat(source)); !S_ISDIR(st.st_mode)) { + auto const st = TRY(Core::System::lstat(source)); + if (!S_ISDIR(st.st_mode)) { // It's a file. items.append(WorkItem { .type = WorkItem::Type::CopyFile, .source = source, .destination = LexicalPath::join(destination, LexicalPath::basename(source)).string(), .size = st.st_size, + .mode = st.st_mode, + .uid = st.st_uid, + .gid = st.st_gid, }); return 0; } @@ -97,6 +104,9 @@ static ErrorOr collect_copy_work_items(DeprecatedString const& source, Depr .source = {}, .destination = LexicalPath::join(destination, LexicalPath::basename(source)).string(), .size = 0, + .mode = st.st_mode, + .uid = st.st_uid, + .gid = st.st_gid, }); Core::DirIterator dt(source, Core::DirIterator::SkipParentAndBaseDir); @@ -124,13 +134,17 @@ ErrorOr perform_copy(Vector const& sources, DeprecatedString co static ErrorOr collect_move_work_items(DeprecatedString const& source, DeprecatedString const& destination, Vector& items) { - if (auto const st = TRY(Core::System::lstat(source)); !S_ISDIR(st.st_mode)) { + auto const st = TRY(Core::System::lstat(source)); + if (!S_ISDIR(st.st_mode)) { // It's a file. items.append(WorkItem { .type = WorkItem::Type::MoveFile, .source = source, .destination = LexicalPath::join(destination, LexicalPath::basename(source)).string(), .size = st.st_size, + .mode = st.st_mode, + .uid = st.st_uid, + .gid = st.st_gid, }); return 0; } @@ -141,6 +155,9 @@ static ErrorOr collect_move_work_items(DeprecatedString const& source, Depr .source = {}, .destination = LexicalPath::join(destination, LexicalPath::basename(source)).string(), .size = 0, + .mode = st.st_mode, + .uid = st.st_uid, + .gid = st.st_gid, }); Core::DirIterator dt(source, Core::DirIterator::SkipParentAndBaseDir); @@ -229,10 +246,11 @@ ErrorOr execute_work_items(Vector const& items) outln("PROGRESS {} {} {} {} {} {} {}", i, items.size(), executed_work_bytes, total_work_bytes, item_done, item.size, item.source); }; - auto copy_file = [&](DeprecatedString const& source, DeprecatedString const& destination) -> ErrorOr { + auto copy_file = [&](DeprecatedString const& source, DeprecatedString const& destination, mode_t mode, uid_t uid, gid_t gid) -> ErrorOr { auto source_file = TRY(Core::File::open(source, Core::File::OpenMode::Read)); // FIXME: When the file already exists, let the user choose the next action instead of renaming it by default. - auto destination_file = TRY(open_destination_file(destination)); + auto destination_file = TRY(open_destination_file(destination, mode)); + TRY(Core::System::fchown(destination_file->fd(), uid, gid)); auto buffer = TRY(ByteBuffer::create_zeroed(64 * KiB)); while (true) { @@ -262,8 +280,10 @@ ErrorOr execute_work_items(Vector const& items) case WorkItem::Type::CreateDirectory: { outln("MKDIR {}", item.destination); // FIXME: Support deduplication like open_destination_file() when the directory already exists. - if (mkdir(item.destination.characters(), 0755) < 0 && errno != EEXIST) + if (auto maybe_error = Core::System::mkdir(item.destination, item.mode); maybe_error.is_error() && maybe_error.error().code() != EEXIST) return Error::from_syscall("mkdir"sv, -errno); + + TRY(Core::System::chown(item.destination, item.uid, item.gid)); break; } @@ -273,7 +293,7 @@ ErrorOr execute_work_items(Vector const& items) } case WorkItem::Type::CopyFile: { - TRY(copy_file(item.source, item.destination)); + TRY(copy_file(item.source, item.destination, item.mode, item.uid, item.gid)); break; } @@ -300,7 +320,7 @@ ErrorOr execute_work_items(Vector const& items) } // EXDEV means we have to copy the file data and then remove the original - TRY(copy_file(item.source, item.destination)); + TRY(copy_file(item.source, item.destination, item.mode, item.uid, item.gid)); TRY(Core::System::unlink(item.source)); break; } @@ -327,11 +347,11 @@ ErrorOr execute_work_items(Vector const& items) return 0; } -ErrorOr> open_destination_file(DeprecatedString const& destination) +ErrorOr> open_destination_file(DeprecatedString const& destination, mode_t mode) { - auto destination_file_or_error = Core::File::open(destination, (Core::File::OpenMode)(Core::File::OpenMode::Write | Core::File::OpenMode::Truncate | Core::File::OpenMode::MustBeNew)); + auto destination_file_or_error = Core::File::open(destination, (Core::File::OpenMode::Write | Core::File::OpenMode::Truncate | Core::File::OpenMode::MustBeNew), mode); if (destination_file_or_error.is_error() && destination_file_or_error.error().code() == EEXIST) { - return open_destination_file(deduplicate_destination_file_name(destination)); + return open_destination_file(deduplicate_destination_file_name(destination), mode); } return destination_file_or_error; }