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:
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 {};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue