From d8b960be46007f9c09356e526d3c2dcff4b186a5 Mon Sep 17 00:00:00 2001 From: David Robillard Date: Sun, 23 Oct 2022 13:41:27 -0400 Subject: Add filesystem API --- src/posix/filesystem_posix.c | 444 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 444 insertions(+) create mode 100644 src/posix/filesystem_posix.c (limited to 'src/posix/filesystem_posix.c') diff --git a/src/posix/filesystem_posix.c b/src/posix/filesystem_posix.c new file mode 100644 index 0000000..0360403 --- /dev/null +++ b/src/posix/filesystem_posix.c @@ -0,0 +1,444 @@ +// Copyright 2007-2022 David Robillard +// SPDX-License-Identifier: ISC + +#include "zix/filesystem.h" + +#include "../errno_status.h" +#include "../system.h" +#include "../zix_config.h" + +#include "zix/allocator.h" +#include "zix/status.h" + +#if USE_FLOCK && USE_FILENO +# include +#endif + +#if USE_CLONEFILE +# include +# include +#endif + +#if USE_REALPATH +# include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifndef MAX +# define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +static inline ZixStatus +zix_posix_status(const int rc) +{ + return rc ? zix_errno_status(errno) : ZIX_STATUS_SUCCESS; +} + +static uint32_t +zix_get_block_size(const struct stat s1, const struct stat s2) +{ + const blksize_t b1 = s1.st_blksize; + const blksize_t b2 = s2.st_blksize; + + return (b1 > 0 && b2 > 0) ? (uint32_t)MAX(b1, b2) : 4096U; +} + +static ZixStatus +finish_copy(const int dst_fd, const int src_fd, const ZixStatus status) +{ + const ZixStatus st0 = zix_posix_status(dst_fd >= 0 ? fdatasync(dst_fd) : 0); + const ZixStatus st1 = zix_system_close_fds(dst_fd, src_fd); + + return status ? status : st0 ? st0 : st1; +} + +static char* +copy_path(ZixAllocator* const allocator, + const char* const path, + const size_t length) +{ + char* result = NULL; + + if (path) { + if ((result = (char*)zix_calloc(allocator, length + 1U, 1U))) { + memcpy(result, path, length + 1U); + } + } + + return result; +} + +#if !defined(PATH_MAX) && USE_PATHCONF + +static size_t +max_path_size(void) +{ + const long path_max = pathconf(path, _PC_PATH_MAX); + return (path_max > 0) ? (size_t)path_max : zix_system_page_size(); +} + +#elif !defined(PATH_MAX) + +static size_t +max_path_size(void) +{ + return zix_system_page_size(); +} + +#endif + +#if USE_CLONEFILE + +static ZixStatus +zix_clonefile(const char* const src, + const char* const dst, + const ZixCopyOptions options) +{ + errno = 0; + + ZixStatus st = ZIX_STATUS_SUCCESS; + const ZixFileType dst_type = zix_file_type(dst); + const bool overwrite = (options == ZIX_COPY_OPTION_OVERWRITE_EXISTING); + if (overwrite && dst_type == ZIX_FILE_TYPE_REGULAR) { + st = zix_remove(dst); + } else if (dst_type != ZIX_FILE_TYPE_NONE) { + st = ZIX_STATUS_NOT_SUPPORTED; + } + + return st ? st : zix_posix_status(clonefile(src, dst, 0)); +} + +#endif + +#if USE_COPY_FILE_RANGE + +static ZixStatus +zix_copy_file_range(const int src_fd, const int dst_fd, const size_t size) +{ + errno = 0; + + size_t remaining = size; + ssize_t r = 0; + while (remaining > 0 && + (r = copy_file_range(src_fd, NULL, dst_fd, NULL, remaining, 0U)) > 0) { + remaining -= (size_t)r; + } + + return (r >= 0) ? ZIX_STATUS_SUCCESS + : zix_errno_status( + (errno == EXDEV || errno == EINVAL) ? ENOSYS : errno); +} + +#endif + +static ZixStatus +copy_blocks(const int src_fd, + const int dst_fd, + void* const block, + const size_t block_size) +{ + ssize_t n_read = 0; + while ((n_read = read(src_fd, block, block_size)) > 0) { + if (write(dst_fd, block, (size_t)n_read) != n_read) { + return zix_errno_status(errno); + } + } + + return ZIX_STATUS_SUCCESS; +} + +ZixStatus +zix_copy_file(ZixAllocator* const allocator, + const char* const src, + const char* const dst, + const ZixCopyOptions options) +{ + ZixStatus st = ZIX_STATUS_SUCCESS; + (void)st; + +#if USE_CLONEFILE + // Try to copy via the kernel on MacOS to take advantage of CoW + st = zix_clonefile(src, dst, options); + if (st != ZIX_STATUS_NOT_SUPPORTED) { + return st; + } +#endif + + // Open source file and get its status + const int src_fd = zix_system_open_fd(src, O_RDONLY, 0); + struct stat src_stat; + if (src_fd < 0 || fstat(src_fd, &src_stat)) { + return finish_copy(-1, src_fd, zix_errno_status(errno)); + } + + // Fail if the source is not a regular file (since we need a size) + if (!S_ISREG(src_stat.st_mode)) { + return finish_copy(-1, src_fd, ZIX_STATUS_BAD_ARG); + } + + // Open a new destination file + const bool overwrite = (options == ZIX_COPY_OPTION_OVERWRITE_EXISTING); + const int dst_flags = O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : O_EXCL); + const int dst_fd = zix_system_open_fd(dst, dst_flags, 0644); + struct stat dst_stat; + if (dst_fd < 0 || fstat(dst_fd, &dst_stat)) { + return finish_copy(dst_fd, src_fd, zix_errno_status(errno)); + } + +#if USE_COPY_FILE_RANGE + // Try to copy via the kernel on Linux/BSD to take advantage of CoW + st = zix_copy_file_range(src_fd, dst_fd, (size_t)src_stat.st_size); + if (st != ZIX_STATUS_NOT_SUPPORTED) { + return finish_copy(dst_fd, src_fd, st); + } +#endif + + // Set sequential hints so the kernel can optimize the page cache +#if USE_POSIX_FADVISE + posix_fadvise(src_fd, 0, src_stat.st_size, POSIX_FADV_SEQUENTIAL); + posix_fadvise(dst_fd, 0, src_stat.st_size, POSIX_FADV_SEQUENTIAL); +#endif + + errno = 0; + + // Allocate a block for copying + const size_t align = zix_system_page_size(); + const uint32_t block_size = zix_get_block_size(src_stat, dst_stat); + void* const block = zix_aligned_alloc(allocator, align, block_size); + + // Fall back to using a small stack buffer if allocation is unavailable + char stack_buf[512]; + void* const buffer = block ? block : stack_buf; + const size_t buffer_size = block ? block_size : sizeof(stack_buf); + + // Copy file content one buffer at a time + st = copy_blocks(src_fd, dst_fd, buffer, buffer_size); + + zix_aligned_free(NULL, block); + return finish_copy(dst_fd, src_fd, st); +} + +ZixStatus +zix_create_directory(const char* const dir_path) +{ + return (!dir_path[0]) ? ZIX_STATUS_BAD_ARG + : zix_posix_status(mkdir(dir_path, 0777)); +} + +ZixStatus +zix_create_directory_like(const char* const dir_path, + const char* const existing_path) +{ + struct stat sb; + return !dir_path[0] ? ZIX_STATUS_BAD_ARG + : stat(existing_path, &sb) + ? zix_errno_status(errno) + : zix_posix_status(mkdir(dir_path, sb.st_mode)); +} + +ZixStatus +zix_create_hard_link(const char* const target_path, const char* const link_path) +{ + return zix_posix_status(link(target_path, link_path)); +} + +ZixStatus +zix_create_symlink(const char* const target_path, const char* const link_path) +{ + return zix_posix_status(symlink(target_path, link_path)); +} + +ZixStatus +zix_create_directory_symlink(const char* const target_path, + const char* const link_path) +{ + return zix_create_symlink(target_path, link_path); +} + +char* +zix_create_temporary_directory(ZixAllocator* const allocator, + const char* const path_pattern) +{ + const size_t length = strlen(path_pattern); + char* const result = (char*)zix_calloc(allocator, length + 1U, 1U); + if (result) { + memcpy(result, path_pattern, length + 1U); + if (!mkdtemp(result)) { + zix_free(allocator, result); + return NULL; + } + } + + return result; +} + +ZixStatus +zix_remove(const char* const path) +{ + return zix_posix_status(remove(path)); +} + +void +zix_dir_for_each(const char* const path, + void* const data, + void (*const f)(const char* path, + const char* name, + void* data)) +{ + DIR* dir = opendir(path); + if (dir) { + // NOLINTNEXTLINE(concurrency-mt-unsafe) + for (struct dirent* entry = NULL; (entry = readdir(dir));) { + if (!!strcmp(entry->d_name, ".") && !!strcmp(entry->d_name, "..")) { + f(path, entry->d_name, data); + } + } + closedir(dir); + } +} + +char* +zix_canonical_path(ZixAllocator* const allocator, const char* const path) +{ + if (!path) { + return NULL; + } + +#if USE_REALPATH && defined(PATH_MAX) + // Some POSIX systems have a static PATH_MAX so we can resolve on the stack + char buffer[PATH_MAX] = {0}; + char* const canonical = realpath(path, buffer); + if (canonical) { + return copy_path(allocator, buffer, strlen(buffer)); + } + +#elif USE_REALPATH && USE_PATHCONF + // Others don't so we have to query PATH_MAX at runtime to allocate the result + const size_t size = max_path_size(path); + char* const buffer = (char*)zix_calloc(allocator, size, 1); + char* const canonical = realpath(path, buffer); + if (canonical) { + return canonical; + } + + zix_free(allocator, buffer); +#endif + + return NULL; +} + +ZixStatus +zix_file_lock(FILE* const file, const ZixFileLockMode mode) +{ +#if !defined(__EMSCRIPTEN__) && USE_FLOCK && USE_FILENO + return zix_posix_status( + flock(fileno(file), + (mode == ZIX_FILE_LOCK_BLOCK) ? LOCK_EX : (LOCK_EX | LOCK_NB))); + +#else + (void)file; + (void)mode; + return ZIX_STATUS_NOT_SUPPORTED; +#endif +} + +ZixStatus +zix_file_unlock(FILE* const file, const ZixFileLockMode mode) +{ +#if !defined(__EMSCRIPTEN__) && USE_FLOCK && USE_FILENO + return zix_posix_status( + flock(fileno(file), + (mode == ZIX_FILE_LOCK_BLOCK) ? LOCK_UN : (LOCK_UN | LOCK_NB))); + +#else + (void)file; + (void)mode; + return ZIX_STATUS_NOT_SUPPORTED; +#endif +} + +static ZixFileType +stat_file_type(const struct stat sb) +{ + typedef struct { + unsigned mask; + ZixFileType type; + } Mapping; + + static const Mapping map[] = { + {S_IFREG, ZIX_FILE_TYPE_REGULAR}, + {S_IFDIR, ZIX_FILE_TYPE_DIRECTORY}, + {S_IFLNK, ZIX_FILE_TYPE_SYMLINK}, + {S_IFBLK, ZIX_FILE_TYPE_BLOCK}, + {S_IFCHR, ZIX_FILE_TYPE_CHARACTER}, + {S_IFIFO, ZIX_FILE_TYPE_FIFO}, + {S_IFSOCK, ZIX_FILE_TYPE_SOCKET}, + {0U, ZIX_FILE_TYPE_UNKNOWN}, + }; + + const unsigned mask = (unsigned)sb.st_mode & (unsigned)S_IFMT; + unsigned m = 0U; + while (map[m].mask && map[m].mask != mask) { + ++m; + } + + return map[m].type; +} + +ZixFileType +zix_file_type(const char* const path) +{ + struct stat sb; + return stat(path, &sb) ? ZIX_FILE_TYPE_NONE : stat_file_type(sb); +} + +ZixFileType +zix_symlink_type(const char* const path) +{ + struct stat sb; + return lstat(path, &sb) ? ZIX_FILE_TYPE_NONE : stat_file_type(sb); +} + +char* +zix_temp_directory_path(ZixAllocator* const allocator) +{ + const char* tmpdir = getenv("TMPDIR"); // NOLINT(concurrency-mt-unsafe) + + tmpdir = tmpdir ? tmpdir : "/tmp"; + + return copy_path(allocator, tmpdir, strlen(tmpdir)); +} + +char* +zix_current_path(ZixAllocator* const allocator) +{ +#if defined(PATH_MAX) + // Some POSIX systems have a static PATH_MAX so we can store it on the stack + char buffer[PATH_MAX] = {0}; + return copy_path(allocator, getcwd(buffer, PATH_MAX), strlen(buffer)); + +#elif USE_PATHCONF + // Others don't so we have to query PATH_MAX at runtime to allocate the result + const size_t size = max_path_size(); + char* const buffer = (char*)zix_calloc(allocator, size, 1); + char* const current = getcwd(buffer, size); + if (current) { + return current; + } + + zix_free(allocator, buffer); +#endif + + return NULL; +} -- cgit v1.2.1