mirror of
https://github.com/RGBCube/serenity
synced 2025-07-27 06:57:45 +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:
parent
d934e77522
commit
919492945e
2 changed files with 238 additions and 1 deletions
|
@ -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 {};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,9 +26,11 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/OSError.h>
|
||||
#include <AK/Result.h>
|
||||
#include <AK/String.h>
|
||||
#include <LibCore/IODevice.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
|
@ -46,9 +48,41 @@ public:
|
|||
static bool is_directory(const String& filename);
|
||||
|
||||
static bool exists(const String& filename);
|
||||
static bool ensure_parent_directories(const String& path);
|
||||
|
||||
enum class RecursionMode {
|
||||
Allowed,
|
||||
Disallowed
|
||||
};
|
||||
|
||||
enum class LinkMode {
|
||||
Allowed,
|
||||
Disallowed
|
||||
};
|
||||
|
||||
enum class AddDuplicateFileMarker {
|
||||
Yes,
|
||||
No,
|
||||
};
|
||||
|
||||
struct CopyError {
|
||||
OSError error_code;
|
||||
bool tried_recursing;
|
||||
};
|
||||
|
||||
static Result<void, CopyError> copy_file(const String& dst_path, const struct stat& src_stat, File& source);
|
||||
static Result<void, CopyError> copy_directory(const String& dst_path, const String& src_path, const struct stat& src_stat, LinkMode = LinkMode::Disallowed);
|
||||
static Result<void, CopyError> copy_file_or_directory(const String& dst_path, const String& src_path, RecursionMode = RecursionMode::Allowed, LinkMode = LinkMode::Disallowed, AddDuplicateFileMarker = AddDuplicateFileMarker::Yes);
|
||||
|
||||
static String real_path_for(const String& filename);
|
||||
static String read_link(const StringView& link_path);
|
||||
static bool ensure_parent_directories(const String& path);
|
||||
static Result<void, OSError> link_file(const String& dst_path, const String& src_path);
|
||||
|
||||
struct RemoveError {
|
||||
String file;
|
||||
OSError error_code;
|
||||
};
|
||||
static Result<void, RemoveError> remove(const String& path, RecursionMode, bool force);
|
||||
|
||||
virtual bool open(IODevice::OpenMode) override;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue