blob: cc6a1fcbd31688aa16162e36221217eb40e0f78b [file]
//===--- Implementation of the Linux specialization of File ---------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "file.h"
#include "file_flags.h"
#include "hdr/stdio_macros.h"
#include "hdr/sys_stat_macros.h" // For S_IS*, S_IF*, and S_IR* flags.
#include "hdr/types/off_t.h"
#include "src/__support/CPP/new.h"
#include "src/__support/File/file.h"
#include "src/__support/File/file_mode.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/close.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/dup2.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/fcntl.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/lseek.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/open.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/read.h"
#include "src/__support/OSUtil/linux/syscall_wrappers/write.h"
#include "src/__support/alloc-checker.h"
#include "src/__support/libc_errno.h" // For error macros
#include "src/__support/macros/config.h"
namespace LIBC_NAMESPACE_DECL {
FileIOResult linux_file_write(File *f, const void *data, size_t size) {
auto *lf = reinterpret_cast<LinuxFile *>(f);
auto ret = linux_syscalls::write(lf->get_fd(), data, size);
if (!ret) {
return {0, ret.error()};
}
return static_cast<size_t>(ret.value());
}
FileIOResult linux_file_read(File *f, void *buf, size_t size) {
auto *lf = reinterpret_cast<LinuxFile *>(f);
auto ret = linux_syscalls::read(lf->get_fd(), buf, size);
if (!ret) {
return {0, ret.error()};
}
return static_cast<size_t>(ret.value());
}
ErrorOr<off_t> linux_file_seek(File *f, off_t offset, int whence) {
auto *lf = reinterpret_cast<LinuxFile *>(f);
return linux_syscalls::lseek(lf->get_fd(), offset, whence);
}
int linux_file_close(File *f) {
File::remove_file(f);
auto *lf = reinterpret_cast<LinuxFile *>(f);
int retval = 0;
if (lf->get_fd() >= 0) {
// Linux closes the file descriptor early in the syscall, so we assume it's
// always closed after the call. That means we should also delete the
// LinuxFile on error.
auto result = linux_syscalls::close(lf->get_fd());
if (!result)
retval = result.error();
} else {
retval = EBADF;
}
delete lf;
return retval;
}
static int map_c_mode_flags_to_linux_open_flags(const FileMode &file_mode) {
int open_flags = 0;
// handle access patterns i.e whether the file should be in
// only read, write modes or both.
if (file_mode.is_update())
open_flags = LinuxFileFlags::READ_AND_WRITE;
else if (file_mode.is_append() || file_mode.is_write())
open_flags = LinuxFileFlags::WRITE_ONLY;
else
open_flags = LinuxFileFlags::READ_ONLY;
// handle the behaviour of the file when accessed i.e should the file
// be appended to or truncate when created.
if (file_mode.is_append())
open_flags |= LinuxFileFlags::CREATE_AND_APPEND;
else if (file_mode.is_write())
open_flags |= LinuxFileFlags::CREATE_OR_TRUNCATE;
return open_flags;
}
ErrorOr<File *> openfile(const char *path, const char *mode) {
const FileMode file_mode(mode);
if (!file_mode.is_valid()) {
return Error(EINVAL);
}
int open_flags = map_c_mode_flags_to_linux_open_flags(file_mode);
ErrorOr<int> fd =
linux_syscalls::open(path, open_flags, LinuxFileFlags::OPEN_MODE);
if (!fd)
return Error(fd.error());
uint8_t *buffer;
{
AllocChecker ac;
buffer = new (ac) uint8_t[File::DEFAULT_BUFFER_SIZE];
if (!ac)
return Error(ENOMEM);
}
AllocChecker ac;
auto *file = new (ac) LinuxFile(fd.value(), buffer, File::DEFAULT_BUFFER_SIZE,
_IOFBF, true, file_mode);
if (!ac)
return Error(ENOMEM);
File::add_file(file);
return file;
}
ErrorOr<LinuxFile *> create_file_from_fd(int fd, const char *mode) {
const FileMode file_mode(mode);
if (!file_mode.is_valid()) {
return Error(EINVAL);
}
auto result = linux_syscalls::fcntl(fd, F_GETFL);
if (!result.has_value()) {
return Error(EBADF);
}
int fd_flags = result.value();
if ((LinuxFileFlags::is_file_descriptor_opened_in_read_only(fd_flags) &&
file_mode.write_allowed()) ||
(LinuxFileFlags::is_file_descriptor_opened_in_write_only(fd_flags) &&
file_mode.read_allowed())) {
return Error(EINVAL);
}
bool do_seek = false;
if (file_mode.is_append() &&
!LinuxFileFlags::file_has_append_flag(fd_flags)) {
do_seek = true;
if (!linux_syscalls::fcntl(fd, F_SETFL,
reinterpret_cast<void *>(fd_flags | O_APPEND))
.has_value()) {
return Error(EBADF);
}
}
uint8_t *buffer;
{
AllocChecker ac;
buffer = new (ac) uint8_t[File::DEFAULT_BUFFER_SIZE];
if (!ac) {
return Error(ENOMEM);
}
}
AllocChecker ac;
auto *file = new (ac)
LinuxFile(fd, buffer, File::DEFAULT_BUFFER_SIZE, _IOFBF, true, file_mode);
if (!ac) {
return Error(ENOMEM);
}
File::add_file(file);
if (do_seek) {
result = file->seek(0, SEEK_END);
if (!result.has_value()) {
File::remove_file(file);
delete file;
return Error(result.error());
}
}
return file;
}
int LinuxFile::reopen_unlocked(const char *path, const char *mode) {
flush_unlocked();
const FileMode file_mode(mode);
if (path != nullptr) {
int old_fd = get_fd();
if (!file_mode.is_valid()) {
if (old_fd >= 0) {
linux_syscalls::close(old_fd);
set_fd(-1);
}
reset_stream_state_unlocked(file_mode);
return EINVAL;
}
int open_flags = map_c_mode_flags_to_linux_open_flags(file_mode);
ErrorOr<int> new_fd =
linux_syscalls::open(path, open_flags, LinuxFileFlags::OPEN_MODE);
// If the new file fails to open, POSIX says we still have to close the old
// file.
if (!new_fd) {
if (old_fd >= 0) {
// POSIX: "Failure to close the file descriptor successfully shall be
// ignored"
linux_syscalls::close(old_fd);
set_fd(-1);
}
reset_stream_state_unlocked(file_mode);
return new_fd.error();
}
// Else the new file successfully opened, so we move it into the fd the old
// file was using if the old fd exists.
if (old_fd >= 0) {
auto dup_result = linux_syscalls::dup2(new_fd.value(), old_fd);
if (!dup_result) {
linux_syscalls::close(new_fd.value());
reset_stream_state_unlocked(file_mode);
return dup_result.error();
}
auto close_result = linux_syscalls::close(new_fd.value());
if (!close_result) {
reset_stream_state_unlocked(file_mode);
return close_result.error();
}
} else {
set_fd(new_fd.value());
}
reset_stream_state_unlocked(file_mode);
return 0;
}
if (!file_mode.is_valid())
return EINVAL;
if (fd < 0)
return EBADF;
auto result = linux_syscalls::fcntl(fd, F_GETFL);
if (!result.has_value())
return EBADF;
int fd_flags = result.value();
if ((LinuxFileFlags::is_file_descriptor_opened_in_read_only(fd_flags) &&
file_mode.write_allowed()) ||
(LinuxFileFlags::is_file_descriptor_opened_in_write_only(fd_flags) &&
file_mode.read_allowed())) {
return EBADF;
}
bool do_seek = false;
bool has_append_flag = LinuxFileFlags::file_has_append_flag(fd_flags);
if (file_mode.is_append() && !has_append_flag) {
if (!linux_syscalls::fcntl(fd, F_SETFL,
reinterpret_cast<void *>(fd_flags | O_APPEND))
.has_value()) {
return EBADF;
}
do_seek = true;
} else if (!file_mode.is_append() && has_append_flag) {
if (!linux_syscalls::fcntl(fd, F_SETFL,
reinterpret_cast<void *>(fd_flags & ~O_APPEND))
.has_value()) {
return EBADF;
}
}
reset_stream_state_unlocked(file_mode);
if (do_seek) {
auto seek_result = linux_file_seek(this, 0, SEEK_END);
if (!seek_result.has_value())
return seek_result.error();
}
return 0;
}
int get_fileno(File *f) {
auto *lf = reinterpret_cast<LinuxFile *>(f);
return lf->get_fd();
}
int reopenfile(File *f, const char *path, const char *mode) {
File::FileLock lock(f);
return reopenfile_unlocked(f, path, mode);
}
int reopenfile_unlocked(File *f, const char *path, const char *mode) {
auto *lf = reinterpret_cast<LinuxFile *>(f);
return lf->reopen_unlocked(path, mode);
}
} // namespace LIBC_NAMESPACE_DECL