Files
Lucas Chollet b6aad29331 chmod: Don't treat unnamed symlinks as errors
chmod ignore symlinks if they are not explicitly listed in the input
files. With this commit we don't consider non-listed symlinks as an
error anymore.

This behavior is not specified by POSIX, but if we trust a libcxx
comment, only AIX is returning an error in this case. So this aligns us
with other UNIXs.

This functionality is used by libcxx's test suite harness. Changing this
behavior makes us pass ~40 more tests in the input.output/filesystems
folder.
2026-08-23 12:36:39 +02:00

80 lines
2.7 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <[email protected]>
* Copyright (c) 2021, Kenneth Myhra <[email protected]>
* Copyright (c) 2021, Xavier Defrang <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Vector.h>
#include <LibCore/ArgsParser.h>
#include <LibCore/DirIterator.h>
#include <LibCore/FilePermissionsMask.h>
#include <LibCore/System.h>
#include <LibMain/Main.h>
ErrorOr<int> serenity_main(Main::Arguments arguments)
{
TRY(Core::System::pledge("stdio rpath fattr"));
StringView mode;
Vector<StringView> paths;
bool recursive = false;
Core::ArgsParser args_parser;
args_parser.add_option(recursive, "Change file modes recursively", "recursive", 'R');
args_parser.add_positional_argument(mode, "File mode in octal or symbolic notation", "mode");
args_parser.add_positional_argument(paths, "Paths to file", "paths");
args_parser.parse(arguments);
auto mask = TRY(Core::FilePermissionsMask::parse(mode));
Function<bool(StringView)> update_path_permissions = [&](StringView const& path) {
auto stat_or_error = Core::System::lstat(path);
if (stat_or_error.is_error()) {
warnln("Could not stat '{}': {}", path, stat_or_error.release_error());
return false;
}
auto stat = stat_or_error.release_value();
if (S_ISLNK(stat.st_mode)) {
// Symlinks don't get processed unless they are explicitly listed on the command line.
if (!paths.contains_slow(path))
return true;
// The chmod syscall changes the file that a link points to, so we will have to get
// the correct mode to base our modifications on.
stat_or_error = Core::System::stat(path);
if (stat_or_error.is_error()) {
warnln("Could not stat '{}': {}", path, stat_or_error.release_error());
return false;
}
stat = stat_or_error.release_value();
}
auto success = true;
auto maybe_error = Core::System::chmod(path, mask.apply(stat.st_mode));
if (maybe_error.is_error()) {
warnln("Failed to change permissions of '{}': {}", path, maybe_error.release_error());
success = false;
}
if (recursive && S_ISDIR(stat.st_mode)) {
Core::DirIterator it(path, Core::DirIterator::Flags::SkipParentAndBaseDir);
while (it.has_next())
success &= update_path_permissions(it.next_full_path());
}
return success;
};
auto success = true;
for (auto const& path : paths) {
success &= update_path_permissions(path);
}
return success ? 0 : 1;
}