1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 17:17:44 +00:00

LibCore: Add file management helpers to reduce code duplication

FileManager, cp, mv, rm had some duplicated code, implementing basic
file management operations. This patch creates adds functions that try
to provide an interface suited for all these programs, but this patch
does not make them be used throughout the Userland.

They are added to Core::File following the example of functions such as
read_link/real_path_for.
This commit is contained in:
Mițca Dumitru 2021-02-21 02:51:55 +02:00 committed by Andreas Kling
parent d934e77522
commit 919492945e
2 changed files with 238 additions and 1 deletions

View file

@ -27,7 +27,9 @@
#ifdef __serenity__
# include <serenity.h>
#endif
#include <AK/LexicalPath.h>
#include <AK/ScopeGuard.h>
#include <LibCore/DirIterator.h>
#include <LibCore/File.h>
#include <errno.h>
#include <fcntl.h>
@ -37,6 +39,11 @@
#include <sys/stat.h>
#include <unistd.h>
// On Linux distros that use glibc `basename` is defined as a macro that expands to `__xpg_basename`, so we undefine it
#if defined(__linux__) && defined(basename)
# undef basename
#endif
namespace Core {
Result<NonnullRefPtr<File>, String> File::open(const String& filename, IODevice::OpenMode mode, mode_t permissions)
@ -259,4 +266,200 @@ NonnullRefPtr<File> File::standard_error()
}
return *stderr_file;
}
static String get_duplicate_name(const String& path, int duplicate_count)
{
if (duplicate_count == 0) {
return path;
}
LexicalPath lexical_path(path);
StringBuilder duplicated_name;
duplicated_name.append('/');
for (size_t i = 0; i < lexical_path.parts().size() - 1; ++i) {
duplicated_name.appendff("{}/", lexical_path.parts()[i]);
}
auto prev_duplicate_tag = String::formatted("({})", duplicate_count);
auto title = lexical_path.title();
if (title.ends_with(prev_duplicate_tag)) {
// remove the previous duplicate tag "(n)" so we can add a new tag.
title = title.substring(0, title.length() - prev_duplicate_tag.length());
}
duplicated_name.appendff("{} ({})", lexical_path.title(), duplicate_count);
if (!lexical_path.extension().is_empty()) {
duplicated_name.appendff(".{}", lexical_path.extension());
}
return duplicated_name.build();
}
Result<void, File::CopyError> File::copy_file_or_directory(const String& dst_path, const String& src_path, RecursionMode recursion_mode, LinkMode link_mode, AddDuplicateFileMarker add_duplicate_file_marker)
{
if (add_duplicate_file_marker == AddDuplicateFileMarker::Yes) {
int duplicate_count = 0;
while (access(get_duplicate_name(dst_path, duplicate_count).characters(), F_OK) == 0) {
++duplicate_count;
}
if (duplicate_count != 0) {
return copy_file_or_directory(get_duplicate_name(dst_path, duplicate_count), src_path);
}
}
auto source_or_error = File::open(src_path, IODevice::ReadOnly);
if (source_or_error.is_error())
return CopyError { OSError(errno), false };
auto& source = *source_or_error.value();
struct stat src_stat;
if (fstat(source.fd(), &src_stat) < 0)
return CopyError { OSError(errno), false };
if (source.is_directory()) {
if (recursion_mode == RecursionMode::Disallowed)
return CopyError { OSError(errno), true };
return copy_directory(dst_path, src_path, src_stat);
}
if (link_mode == LinkMode::Allowed) {
if (link(src_path.characters(), dst_path.characters()) < 0)
return CopyError { OSError(errno), false };
return {};
}
return copy_file(dst_path, src_stat, source);
}
Result<void, File::CopyError> File::copy_file(const String& dst_path, const struct stat& src_stat, File& source)
{
int dst_fd = creat(dst_path.characters(), 0666);
if (dst_fd < 0) {
if (errno != EISDIR)
return CopyError { OSError(errno), false };
auto dst_dir_path = String::formatted("{}/{}", dst_path, LexicalPath(source.filename()).basename());
dst_fd = creat(dst_dir_path.characters(), 0666);
if (dst_fd < 0)
return CopyError { OSError(errno), false };
}
ScopeGuard close_fd_guard([dst_fd]() { ::close(dst_fd); });
if (src_stat.st_size > 0) {
if (ftruncate(dst_fd, src_stat.st_size) < 0)
return CopyError { OSError(errno), false };
}
for (;;) {
char buffer[32768];
ssize_t nread = ::read(source.fd(), buffer, sizeof(buffer));
if (nread < 0) {
return CopyError { OSError(errno), false };
}
if (nread == 0)
break;
ssize_t remaining_to_write = nread;
char* bufptr = buffer;
while (remaining_to_write) {
ssize_t nwritten = ::write(dst_fd, bufptr, remaining_to_write);
if (nwritten < 0)
return CopyError { OSError(errno), false };
ASSERT(nwritten > 0);
remaining_to_write -= nwritten;
bufptr += nwritten;
}
}
// NOTE: We don't copy the set-uid and set-gid bits.
auto my_umask = umask(0);
umask(my_umask);
if (fchmod(dst_fd, (src_stat.st_mode & ~my_umask) & ~06000) < 0)
return CopyError { OSError(errno), false };
return {};
}
Result<void, File::CopyError> File::copy_directory(const String& dst_path, const String& src_path, const struct stat& src_stat, LinkMode link)
{
if (mkdir(dst_path.characters(), 0755) < 0)
return CopyError { OSError(errno), false };
String src_rp = File::real_path_for(src_path);
src_rp = String::formatted("{}/", src_rp);
String dst_rp = File::real_path_for(dst_path);
dst_rp = String::formatted("{}/", dst_rp);
if (!dst_rp.is_empty() && dst_rp.starts_with(src_rp))
return CopyError { OSError(errno), false };
DirIterator di(src_path, DirIterator::SkipDots);
if (di.has_error())
return CopyError { OSError(errno), false };
while (di.has_next()) {
String filename = di.next_path();
auto result = copy_file_or_directory(
String::formatted("{}/{}", dst_path, filename),
String::formatted("{}/{}", src_path, filename),
RecursionMode::Allowed, link);
if (result.is_error())
return result.error();
}
auto my_umask = umask(0);
umask(my_umask);
if (chmod(dst_path.characters(), src_stat.st_mode & ~my_umask) < 0)
return CopyError { OSError(errno), false };
return {};
}
Result<void, OSError> File::link_file(const String& dst_path, const String& src_path)
{
int duplicate_count = 0;
while (access(get_duplicate_name(dst_path, duplicate_count).characters(), F_OK) == 0) {
++duplicate_count;
}
if (duplicate_count != 0) {
return link_file(src_path, get_duplicate_name(dst_path, duplicate_count));
}
int rc = symlink(src_path.characters(), dst_path.characters());
if (rc < 0) {
return OSError(errno);
}
return {};
}
Result<void, File::RemoveError> File::remove(const String& path, RecursionMode mode, bool force)
{
struct stat path_stat;
if (lstat(path.characters(), &path_stat) < 0) {
if (!force)
return RemoveError { path, OSError(errno) };
return {};
}
if (S_ISDIR(path_stat.st_mode) && mode == RecursionMode::Allowed) {
auto di = DirIterator(path, DirIterator::SkipParentAndBaseDir);
if (di.has_error())
return RemoveError { path, OSError(di.error()) };
while (di.has_next()) {
auto result = remove(di.next_full_path(), RecursionMode::Allowed, true);
if (result.is_error())
return result.error();
}
if (rmdir(path.characters()) < 0 && !force)
return RemoveError { path, OSError(errno) };
} else {
if (unlink(path.characters()) < 0 && !force)
return RemoveError { path, OSError(errno) };
}
return {};
}
}