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; }