diff options
author | Tim Schumacher <timschumi@gmx.de> | 2023-02-08 21:08:01 +0100 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2023-02-13 00:50:07 +0000 |
commit | d43a7eae545cd699f301471bd0f82399174339c1 (patch) | |
tree | c0a55f768f845041c3aebed3f126d462155a799f /Userland/Libraries/LibCore/DeprecatedFile.cpp | |
parent | 14951b92ca6160664ccb68c5e1b2d40133763e5f (diff) | |
download | serenity-d43a7eae545cd699f301471bd0f82399174339c1.zip |
LibCore: Rename `File` to `DeprecatedFile`
As usual, this removes many unused includes and moves used includes
further down the chain.
Diffstat (limited to 'Userland/Libraries/LibCore/DeprecatedFile.cpp')
-rw-r--r-- | Userland/Libraries/LibCore/DeprecatedFile.cpp | 632 |
1 files changed, 632 insertions, 0 deletions
diff --git a/Userland/Libraries/LibCore/DeprecatedFile.cpp b/Userland/Libraries/LibCore/DeprecatedFile.cpp new file mode 100644 index 0000000000..7bf17fa971 --- /dev/null +++ b/Userland/Libraries/LibCore/DeprecatedFile.cpp @@ -0,0 +1,632 @@ +/* + * Copyright (c) 2018-2022, Andreas Kling <kling@serenityos.org> + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/LexicalPath.h> +#include <AK/Platform.h> +#include <AK/ScopeGuard.h> +#include <LibCore/DeprecatedFile.h> +#include <LibCore/DirIterator.h> +#include <LibCore/System.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> +#include <utime.h> + +#ifdef AK_OS_SERENITY +# include <serenity.h> +#endif + +// On Linux distros that use glibc `basename` is defined as a macro that expands to `__xpg_basename`, so we undefine it +#if defined(AK_OS_LINUX) && defined(basename) +# undef basename +#endif + +namespace Core { + +ErrorOr<NonnullRefPtr<DeprecatedFile>> DeprecatedFile::open(DeprecatedString filename, OpenMode mode, mode_t permissions) +{ + auto file = DeprecatedFile::construct(move(filename)); + if (!file->open_impl(mode, permissions)) + return Error::from_errno(file->error()); + return file; +} + +DeprecatedFile::DeprecatedFile(DeprecatedString filename, Object* parent) + : IODevice(parent) + , m_filename(move(filename)) +{ +} + +DeprecatedFile::~DeprecatedFile() +{ + if (m_should_close_file_descriptor == ShouldCloseFileDescriptor::Yes && mode() != OpenMode::NotOpen) + close(); +} + +bool DeprecatedFile::open(int fd, OpenMode mode, ShouldCloseFileDescriptor should_close) +{ + set_fd(fd); + set_mode(mode); + m_should_close_file_descriptor = should_close; + return true; +} + +bool DeprecatedFile::open(OpenMode mode) +{ + return open_impl(mode, 0666); +} + +bool DeprecatedFile::open_impl(OpenMode mode, mode_t permissions) +{ + VERIFY(!m_filename.is_null()); + int flags = 0; + if (has_flag(mode, OpenMode::ReadOnly) && has_flag(mode, OpenMode::WriteOnly)) { + flags |= O_RDWR | O_CREAT; + } else if (has_flag(mode, OpenMode::ReadOnly)) { + flags |= O_RDONLY; + } else if (has_flag(mode, OpenMode::WriteOnly)) { + flags |= O_WRONLY | O_CREAT; + bool should_truncate = !(has_flag(mode, OpenMode::Append) || has_flag(mode, OpenMode::MustBeNew)); + if (should_truncate) + flags |= O_TRUNC; + } + if (has_flag(mode, OpenMode::Append)) + flags |= O_APPEND; + if (has_flag(mode, OpenMode::Truncate)) + flags |= O_TRUNC; + if (has_flag(mode, OpenMode::MustBeNew)) + flags |= O_EXCL; + if (!has_flag(mode, OpenMode::KeepOnExec)) + flags |= O_CLOEXEC; + int fd = ::open(m_filename.characters(), flags, permissions); + if (fd < 0) { + set_error(errno); + return false; + } + + set_fd(fd); + set_mode(mode); + return true; +} + +int DeprecatedFile::leak_fd() +{ + m_should_close_file_descriptor = ShouldCloseFileDescriptor::No; + return fd(); +} + +bool DeprecatedFile::is_device() const +{ + return is_device(fd()); +} + +bool DeprecatedFile::is_device(DeprecatedString const& filename) +{ + struct stat st; + if (stat(filename.characters(), &st) < 0) + return false; + return S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode); +} + +bool DeprecatedFile::is_device(int fd) +{ + struct stat st; + if (fstat(fd, &st) < 0) + return false; + return S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode); +} + +bool DeprecatedFile::is_block_device() const +{ + struct stat stat; + if (fstat(fd(), &stat) < 0) + return false; + return S_ISBLK(stat.st_mode); +} + +bool DeprecatedFile::is_block_device(DeprecatedString const& filename) +{ + struct stat st; + if (stat(filename.characters(), &st) < 0) + return false; + return S_ISBLK(st.st_mode); +} + +bool DeprecatedFile::is_char_device() const +{ + struct stat stat; + if (fstat(fd(), &stat) < 0) + return false; + return S_ISCHR(stat.st_mode); +} + +bool DeprecatedFile::is_char_device(DeprecatedString const& filename) +{ + struct stat st; + if (stat(filename.characters(), &st) < 0) + return false; + return S_ISCHR(st.st_mode); +} + +bool DeprecatedFile::is_directory() const +{ + return is_directory(fd()); +} + +bool DeprecatedFile::is_directory(DeprecatedString const& filename) +{ + struct stat st; + if (stat(filename.characters(), &st) < 0) + return false; + return S_ISDIR(st.st_mode); +} + +bool DeprecatedFile::is_directory(int fd) +{ + struct stat st; + if (fstat(fd, &st) < 0) + return false; + return S_ISDIR(st.st_mode); +} + +bool DeprecatedFile::is_link() const +{ + struct stat stat; + if (fstat(fd(), &stat) < 0) + return false; + return S_ISLNK(stat.st_mode); +} + +bool DeprecatedFile::is_link(DeprecatedString const& filename) +{ + struct stat st; + if (lstat(filename.characters(), &st) < 0) + return false; + return S_ISLNK(st.st_mode); +} + +bool DeprecatedFile::looks_like_shared_library() const +{ + return DeprecatedFile::looks_like_shared_library(m_filename); +} + +bool DeprecatedFile::looks_like_shared_library(DeprecatedString const& filename) +{ + return filename.ends_with(".so"sv) || filename.contains(".so."sv); +} + +bool DeprecatedFile::can_delete_or_move(StringView path) +{ + VERIFY(!path.is_empty()); + auto directory = LexicalPath::dirname(path); + auto directory_has_write_access = !Core::System::access(directory, W_OK).is_error(); + if (!directory_has_write_access) + return false; + + auto stat_or_empty = [](StringView path) { + auto stat_or_error = Core::System::stat(path); + if (stat_or_error.is_error()) { + struct stat stat { }; + return stat; + } + return stat_or_error.release_value(); + }; + + auto directory_stat = stat_or_empty(directory); + bool is_directory_sticky = directory_stat.st_mode & S_ISVTX; + if (!is_directory_sticky) + return true; + + // Directory is sticky, only the file owner, directory owner, and root can modify (rename, remove) it. + auto user_id = geteuid(); + return user_id == 0 || directory_stat.st_uid == user_id || stat_or_empty(path).st_uid == user_id; +} + +bool DeprecatedFile::exists(StringView filename) +{ + return !Core::System::stat(filename).is_error(); +} + +ErrorOr<size_t> DeprecatedFile::size(DeprecatedString const& filename) +{ + struct stat st; + if (stat(filename.characters(), &st) < 0) + return Error::from_errno(errno); + return st.st_size; +} + +DeprecatedString DeprecatedFile::real_path_for(DeprecatedString const& filename) +{ + if (filename.is_null()) + return {}; + auto* path = realpath(filename.characters(), nullptr); + DeprecatedString real_path(path); + free(path); + return real_path; +} + +DeprecatedString DeprecatedFile::current_working_directory() +{ + char* cwd = getcwd(nullptr, 0); + if (!cwd) { + perror("getcwd"); + return {}; + } + + auto cwd_as_string = DeprecatedString(cwd); + free(cwd); + + return cwd_as_string; +} + +DeprecatedString DeprecatedFile::absolute_path(DeprecatedString const& path) +{ + if (DeprecatedFile::exists(path)) + return DeprecatedFile::real_path_for(path); + + if (path.starts_with("/"sv)) + return LexicalPath::canonicalized_path(path); + + auto working_directory = DeprecatedFile::current_working_directory(); + auto full_path = LexicalPath::join(working_directory, path); + + return LexicalPath::canonicalized_path(full_path.string()); +} + +#ifdef AK_OS_SERENITY + +ErrorOr<DeprecatedString> DeprecatedFile::read_link(DeprecatedString const& link_path) +{ + // First, try using a 64-byte buffer, that ought to be enough for anybody. + char small_buffer[64]; + + int rc = serenity_readlink(link_path.characters(), link_path.length(), small_buffer, sizeof(small_buffer)); + if (rc < 0) + return Error::from_errno(errno); + + size_t size = rc; + // If the call was successful, the syscall (unlike the LibC wrapper) + // returns the full size of the link. Let's see if our small buffer + // was enough to read the whole link. + if (size <= sizeof(small_buffer)) + return DeprecatedString { small_buffer, size }; + // Nope, but at least now we know the right size. + char* large_buffer_ptr; + auto large_buffer = StringImpl::create_uninitialized(size, large_buffer_ptr); + + rc = serenity_readlink(link_path.characters(), link_path.length(), large_buffer_ptr, size); + if (rc < 0) + return Error::from_errno(errno); + + size_t new_size = rc; + if (new_size == size) + return { *large_buffer }; + + // If we're here, the symlink has changed while we were looking at it. + // If it became shorter, our buffer is valid, we just have to trim it a bit. + if (new_size < size) + return DeprecatedString { large_buffer_ptr, new_size }; + // Otherwise, here's not much we can do, unless we want to loop endlessly + // in this case. Let's leave it up to the caller whether to loop. + errno = EAGAIN; + return Error::from_errno(errno); +} + +#else + +// This is a sad version for other systems. It has to always make a copy of the +// link path, and to always make two syscalls to get the right size first. +ErrorOr<DeprecatedString> DeprecatedFile::read_link(DeprecatedString const& link_path) +{ + struct stat statbuf = {}; + int rc = lstat(link_path.characters(), &statbuf); + if (rc < 0) + return Error::from_errno(errno); + char* buffer_ptr; + auto buffer = StringImpl::create_uninitialized(statbuf.st_size, buffer_ptr); + if (readlink(link_path.characters(), buffer_ptr, statbuf.st_size) < 0) + return Error::from_errno(errno); + // (See above.) + if (rc == statbuf.st_size) + return { *buffer }; + return DeprecatedString { buffer_ptr, (size_t)rc }; +} + +#endif + +static RefPtr<DeprecatedFile> stdin_file; +static RefPtr<DeprecatedFile> stdout_file; +static RefPtr<DeprecatedFile> stderr_file; + +NonnullRefPtr<DeprecatedFile> DeprecatedFile::standard_input() +{ + if (!stdin_file) { + stdin_file = DeprecatedFile::construct(); + stdin_file->open(STDIN_FILENO, OpenMode::ReadOnly, ShouldCloseFileDescriptor::No); + } + return *stdin_file; +} + +NonnullRefPtr<DeprecatedFile> DeprecatedFile::standard_output() +{ + if (!stdout_file) { + stdout_file = DeprecatedFile::construct(); + stdout_file->open(STDOUT_FILENO, OpenMode::WriteOnly, ShouldCloseFileDescriptor::No); + } + return *stdout_file; +} + +NonnullRefPtr<DeprecatedFile> DeprecatedFile::standard_error() +{ + if (!stderr_file) { + stderr_file = DeprecatedFile::construct(); + stderr_file->open(STDERR_FILENO, OpenMode::WriteOnly, ShouldCloseFileDescriptor::No); + } + return *stderr_file; +} + +static DeprecatedString get_duplicate_name(DeprecatedString const& path, int duplicate_count) +{ + if (duplicate_count == 0) { + return path; + } + LexicalPath lexical_path(path); + StringBuilder duplicated_name; + duplicated_name.append('/'); + auto& parts = lexical_path.parts_view(); + for (size_t i = 0; i < parts.size() - 1; ++i) { + duplicated_name.appendff("{}/", parts[i]); + } + auto prev_duplicate_tag = DeprecatedString::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_view(0, title.length() - prev_duplicate_tag.length()); + } + duplicated_name.appendff("{} ({})", title, duplicate_count); + if (!lexical_path.extension().is_empty()) { + duplicated_name.appendff(".{}", lexical_path.extension()); + } + return duplicated_name.to_deprecated_string(); +} + +ErrorOr<void, DeprecatedFile::CopyError> DeprecatedFile::copy_file_or_directory(DeprecatedString const& dst_path, DeprecatedString const& src_path, RecursionMode recursion_mode, LinkMode link_mode, AddDuplicateFileMarker add_duplicate_file_marker, PreserveMode preserve_mode) +{ + 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, RecursionMode::Allowed, LinkMode::Disallowed, AddDuplicateFileMarker::Yes, preserve_mode); + } + } + + auto source_or_error = DeprecatedFile::open(src_path, OpenMode::ReadOnly); + if (source_or_error.is_error()) + return CopyError { errno, false }; + + auto& source = *source_or_error.value(); + + struct stat src_stat; + if (fstat(source.fd(), &src_stat) < 0) + return CopyError { errno, false }; + + if (source.is_directory()) { + if (recursion_mode == RecursionMode::Disallowed) + return CopyError { 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 { errno, false }; + + return {}; + } + + return copy_file(dst_path, src_stat, source, preserve_mode); +} + +ErrorOr<void, DeprecatedFile::CopyError> DeprecatedFile::copy_file(DeprecatedString const& dst_path, struct stat const& src_stat, DeprecatedFile& source, PreserveMode preserve_mode) +{ + int dst_fd = creat(dst_path.characters(), 0666); + if (dst_fd < 0) { + if (errno != EISDIR) + return CopyError { errno, false }; + + auto dst_dir_path = DeprecatedString::formatted("{}/{}", dst_path, LexicalPath::basename(source.filename())); + dst_fd = creat(dst_dir_path.characters(), 0666); + if (dst_fd < 0) + return CopyError { 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 { errno, false }; + } + + for (;;) { + char buffer[32768]; + ssize_t nread = ::read(source.fd(), buffer, sizeof(buffer)); + if (nread < 0) { + return CopyError { 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 { errno, false }; + + VERIFY(nwritten > 0); + remaining_to_write -= nwritten; + bufptr += nwritten; + } + } + + auto my_umask = umask(0); + umask(my_umask); + // NOTE: We don't copy the set-uid and set-gid bits unless requested. + if (!has_flag(preserve_mode, PreserveMode::Permissions)) + my_umask |= 06000; + + if (fchmod(dst_fd, src_stat.st_mode & ~my_umask) < 0) + return CopyError { errno, false }; + + if (has_flag(preserve_mode, PreserveMode::Ownership)) { + if (fchown(dst_fd, src_stat.st_uid, src_stat.st_gid) < 0) + return CopyError { errno, false }; + } + + if (has_flag(preserve_mode, PreserveMode::Timestamps)) { + struct timespec times[2] = { +#ifdef AK_OS_MACOS + src_stat.st_atimespec, + src_stat.st_mtimespec, +#else + src_stat.st_atim, + src_stat.st_mtim, +#endif + }; + if (utimensat(AT_FDCWD, dst_path.characters(), times, 0) < 0) + return CopyError { errno, false }; + } + + return {}; +} + +ErrorOr<void, DeprecatedFile::CopyError> DeprecatedFile::copy_directory(DeprecatedString const& dst_path, DeprecatedString const& src_path, struct stat const& src_stat, LinkMode link, PreserveMode preserve_mode) +{ + if (mkdir(dst_path.characters(), 0755) < 0) + return CopyError { errno, false }; + + DeprecatedString src_rp = DeprecatedFile::real_path_for(src_path); + src_rp = DeprecatedString::formatted("{}/", src_rp); + DeprecatedString dst_rp = DeprecatedFile::real_path_for(dst_path); + dst_rp = DeprecatedString::formatted("{}/", dst_rp); + + if (!dst_rp.is_empty() && dst_rp.starts_with(src_rp)) + return CopyError { errno, false }; + + DirIterator di(src_path, DirIterator::SkipParentAndBaseDir); + if (di.has_error()) + return CopyError { errno, false }; + + while (di.has_next()) { + DeprecatedString filename = di.next_path(); + auto result = copy_file_or_directory( + DeprecatedString::formatted("{}/{}", dst_path, filename), + DeprecatedString::formatted("{}/{}", src_path, filename), + RecursionMode::Allowed, link, AddDuplicateFileMarker::Yes, preserve_mode); + if (result.is_error()) + return result.release_error(); + } + + auto my_umask = umask(0); + umask(my_umask); + + if (chmod(dst_path.characters(), src_stat.st_mode & ~my_umask) < 0) + return CopyError { errno, false }; + + if (has_flag(preserve_mode, PreserveMode::Ownership)) { + if (chown(dst_path.characters(), src_stat.st_uid, src_stat.st_gid) < 0) + return CopyError { errno, false }; + } + + if (has_flag(preserve_mode, PreserveMode::Timestamps)) { + struct timespec times[2] = { +#ifdef AK_OS_MACOS + src_stat.st_atimespec, + src_stat.st_mtimespec, +#else + src_stat.st_atim, + src_stat.st_mtim, +#endif + }; + if (utimensat(AT_FDCWD, dst_path.characters(), times, 0) < 0) + return CopyError { errno, false }; + } + + return {}; +} + +ErrorOr<void> DeprecatedFile::link_file(DeprecatedString const& dst_path, DeprecatedString const& 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(get_duplicate_name(dst_path, duplicate_count), src_path); + } + if (symlink(src_path.characters(), dst_path.characters()) < 0) + return Error::from_errno(errno); + return {}; +} + +ErrorOr<void> DeprecatedFile::remove(StringView path, RecursionMode mode) +{ + auto path_stat = TRY(Core::System::lstat(path)); + + if (S_ISDIR(path_stat.st_mode) && mode == RecursionMode::Allowed) { + auto di = DirIterator(path, DirIterator::SkipParentAndBaseDir); + if (di.has_error()) + return Error::from_errno(di.error()); + + while (di.has_next()) { + TRY(remove(di.next_full_path(), RecursionMode::Allowed)); + } + + TRY(Core::System::rmdir(path)); + } else { + TRY(Core::System::unlink(path)); + } + + return {}; +} + +Optional<DeprecatedString> DeprecatedFile::resolve_executable_from_environment(StringView filename) +{ + if (filename.is_empty()) + return {}; + + // Paths that aren't just a file name generally count as already resolved. + if (filename.contains('/')) { + if (access(DeprecatedString { filename }.characters(), X_OK) != 0) + return {}; + + return filename; + } + + auto const* path_str = getenv("PATH"); + StringView path; + if (path_str) + path = { path_str, strlen(path_str) }; + if (path.is_empty()) + path = DEFAULT_PATH_SV; + + auto directories = path.split_view(':'); + + for (auto directory : directories) { + auto file = DeprecatedString::formatted("{}/{}", directory, filename); + + if (access(file.characters(), X_OK) == 0) + return file; + } + + return {}; +}; + +} |