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/errno_status.c | 5 + src/filesystem.c | 122 ++++++++++++ src/posix/filesystem_posix.c | 444 +++++++++++++++++++++++++++++++++++++++++++ src/posix/system_posix.c | 26 +++ src/status.c | 6 +- src/system.c | 41 ++++ src/system.h | 30 +++ src/win32/filesystem_win32.c | 325 +++++++++++++++++++++++++++++++ src/win32/system_win32.c | 20 ++ src/zix_config.h | 137 +++++++++++++ 10 files changed, 1155 insertions(+), 1 deletion(-) create mode 100644 src/filesystem.c create mode 100644 src/posix/filesystem_posix.c create mode 100644 src/posix/system_posix.c create mode 100644 src/system.c create mode 100644 src/system.h create mode 100644 src/win32/filesystem_win32.c create mode 100644 src/win32/system_win32.c (limited to 'src') diff --git a/src/errno_status.c b/src/errno_status.c index 142d456..4922b56 100644 --- a/src/errno_status.c +++ b/src/errno_status.c @@ -28,11 +28,16 @@ zix_errno_status(const int e) {EAGAIN, ZIX_STATUS_UNAVAILABLE}, {EEXIST, ZIX_STATUS_EXISTS}, {EINVAL, ZIX_STATUS_BAD_ARG}, + {EMLINK, ZIX_STATUS_MAX_LINKS}, {ENOENT, ZIX_STATUS_NOT_FOUND}, {ENOMEM, ZIX_STATUS_NO_MEM}, + {ENOSPC, ZIX_STATUS_NO_SPACE}, {ENOSYS, ZIX_STATUS_NOT_SUPPORTED}, {EPERM, ZIX_STATUS_BAD_PERMS}, {ETIMEDOUT, ZIX_STATUS_TIMEOUT}, +#ifdef ENOTSUP + {ENOTSUP, ZIX_STATUS_NOT_SUPPORTED}, +#endif {0, ZIX_STATUS_ERROR}, // Fallback mapping }; diff --git a/src/filesystem.c b/src/filesystem.c new file mode 100644 index 0000000..c3e8f10 --- /dev/null +++ b/src/filesystem.c @@ -0,0 +1,122 @@ +// Copyright 2007-2022 David Robillard +// SPDX-License-Identifier: ISC + +#include "zix/filesystem.h" + +#include "index_range.h" +#include "path_iter.h" +#include "system.h" + +#include "zix/allocator.h" +#include "zix/status.h" + +#ifdef _WIN32 +# include +# include +#else +# include +#endif + +#include +#include + +#include +#include +#include +#include +#include + +ZixStatus +zix_create_directories(ZixAllocator* const allocator, + const char* const dir_path) +{ + if (!dir_path[0]) { + return ZIX_STATUS_BAD_ARG; + } + + // Allocate a working copy of the path to chop along the way + const size_t path_len = strlen(dir_path); + char* const path = (char*)zix_malloc(allocator, path_len + 1U); + memcpy(path, dir_path, path_len + 1U); + + // Start at the root directory (past any name) + ZixPathIter p = zix_path_begin(path); + while (p.state < ZIX_PATH_FILE_NAME) { + p = zix_path_next(path, p); + } + + // Create each directory down the path + ZixStatus st = ZIX_STATUS_SUCCESS; + while (p.state != ZIX_PATH_END) { + const char old_end = path[p.range.end]; + + path[p.range.end] = '\0'; + if (zix_file_type(path) != ZIX_FILE_TYPE_DIRECTORY) { + if ((st = zix_create_directory(path))) { + break; + } + } + + path[p.range.end] = old_end; + p = zix_path_next(path, p); + } + + zix_free(allocator, path); + return st; +} + +ZixFileOffset +zix_file_size(const char* const path) +{ + struct stat sb; + return stat(path, &sb) ? (off_t)0 : sb.st_size; +} + +bool +zix_file_equals(ZixAllocator* const allocator, + const char* const path_a, + const char* const path_b) +{ + if (!strcmp(path_a, path_b)) { + return true; // Paths match + } + + errno = 0; + + // Open files and get file information + const int fd_a = zix_system_open_fd(path_a, O_RDONLY, 0); + const int fd_b = zix_system_open_fd(path_b, O_RDONLY, 0); + struct stat stat_a; + struct stat stat_b; + if (fd_a < 0 || fd_b < 0 || fstat(fd_a, &stat_a) || fstat(fd_b, &stat_b)) { + zix_system_close_fds(fd_b, fd_a); + return false; + } + + bool match = false; + if (stat_a.st_dev == stat_b.st_dev && stat_a.st_ino && stat_b.st_ino && + stat_a.st_ino == stat_b.st_ino) { + match = true; // Fast path: paths refer to the same file + } else if (stat_a.st_size == stat_b.st_size) { + // Slow path: files have equal size, compare contents + const uint32_t size = zix_system_page_size(); + void* const page_a = zix_aligned_alloc(allocator, size, size); + void* const page_b = zix_aligned_alloc(allocator, size, size); + + if (page_a && page_b) { + match = true; + for (ZixSystemCountReturn n = 0; (n = read(fd_a, page_a, size)) > 0;) { + if (read(fd_b, page_b, size) != n || + !!memcmp(page_a, page_b, (size_t)n)) { + match = false; + break; + } + } + } + + zix_aligned_free(allocator, page_b); + zix_aligned_free(allocator, page_a); + } + + return !zix_system_close_fds(fd_b, fd_a) && match; +} 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; +} diff --git a/src/posix/system_posix.c b/src/posix/system_posix.c new file mode 100644 index 0000000..4c37315 --- /dev/null +++ b/src/posix/system_posix.c @@ -0,0 +1,26 @@ +// Copyright 2007-2022 David Robillard +// SPDX-License-Identifier: ISC + +#include "../system.h" +#include "../zix_config.h" + +#include + +#include + +#if defined(PAGE_SIZE) +# define ZIX_DEFAULT_PAGE_SIZE PAGE_SIZE +#else +# define ZIX_DEFAULT_PAGE_SIZE 4096U +#endif + +uint32_t +zix_system_page_size(void) +{ +#if USE_SYSCONF + const long r = sysconf(_SC_PAGE_SIZE); + return r > 0L ? (uint32_t)r : ZIX_DEFAULT_PAGE_SIZE; +#else + return (uint32_t)ZIX_DEFAULT_PAGE_SIZE; +#endif +} diff --git a/src/status.c b/src/status.c index 4b06b97..e3d8b99 100644 --- a/src/status.c +++ b/src/status.c @@ -1,4 +1,4 @@ -// Copyright 2014-2020 David Robillard +// Copyright 2014-2022 David Robillard // SPDX-License-Identifier: ISC #include "zix/status.h" @@ -31,6 +31,10 @@ zix_strerror(const ZixStatus status) return "Not supported"; case ZIX_STATUS_UNAVAILABLE: return "Resource unavailable"; + case ZIX_STATUS_NO_SPACE: + return "Out of storage space"; + case ZIX_STATUS_MAX_LINKS: + return "Too many links"; } return "Unknown error"; } diff --git a/src/system.c b/src/system.c new file mode 100644 index 0000000..eab568b --- /dev/null +++ b/src/system.c @@ -0,0 +1,41 @@ +// Copyright 2007-2022 David Robillard +// SPDX-License-Identifier: ISC + +#include "system.h" + +#include "errno_status.h" + +#include "zix/status.h" + +#ifdef _WIN32 +# include +#else +# include +#endif + +#include +#include + +int +zix_system_open_fd(const char* const path, const int flags, const mode_t mode) +{ +#ifdef O_CLOEXEC + return open(path, flags | O_CLOEXEC, mode); // NOLINT(hicpp-signed-bitwise) +#else + return open(path, flags, mode); +#endif +} + +ZixStatus +zix_system_close_fds(const int fd1, const int fd2) +{ + // Careful: we need to always close both files, but catch errno at any point + + const ZixStatus st0 = zix_errno_status(errno); + const int r1 = fd1 >= 0 ? close(fd1) : 0; + const ZixStatus st1 = r1 ? ZIX_STATUS_SUCCESS : zix_errno_status(errno); + const int r2 = fd2 >= 0 ? close(fd2) : 0; + const ZixStatus st2 = r2 ? ZIX_STATUS_SUCCESS : zix_errno_status(errno); + + return st0 ? st0 : st1 ? st1 : st2; +} diff --git a/src/system.h b/src/system.h new file mode 100644 index 0000000..ca55161 --- /dev/null +++ b/src/system.h @@ -0,0 +1,30 @@ +// Copyright 2022 David Robillard +// SPDX-License-Identifier: ISC + +#ifndef ZIX_SYSTEM_H +#define ZIX_SYSTEM_H + +#include "zix/status.h" + +#include +#include + +#ifdef _WIN32 +typedef int ZixSystemCountReturn; +# ifndef __GNUC__ +typedef int mode_t; +# endif +#else +typedef ssize_t ZixSystemCountReturn; +#endif + +uint32_t +zix_system_page_size(void); + +int +zix_system_open_fd(const char* path, int flags, mode_t mode); + +ZixStatus +zix_system_close_fds(int fd1, int fd2); + +#endif // ZIX_SYSTEM_H diff --git a/src/win32/filesystem_win32.c b/src/win32/filesystem_win32.c new file mode 100644 index 0000000..3e9b7a6 --- /dev/null +++ b/src/win32/filesystem_win32.c @@ -0,0 +1,325 @@ +// Copyright 2007-2022 David Robillard +// SPDX-License-Identifier: ISC + +#define _WIN32_WINNT 0x0600 // Vista + +#include "zix/bump_allocator.h" +#include "zix/filesystem.h" + +#include "../errno_status.h" +#include "../zix_config.h" + +#include "zix/allocator.h" +#include "zix/path.h" +#include "zix/status.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static inline ZixStatus +zix_winerror_status(const DWORD e) +{ + switch (e) { + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: + return ZIX_STATUS_NO_MEM; + case ERROR_SEM_TIMEOUT: + return ZIX_STATUS_TIMEOUT; + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + return ZIX_STATUS_NOT_FOUND; + case ERROR_BUFFER_OVERFLOW: + return ZIX_STATUS_OVERFLOW; + case ERROR_DISK_FULL: + return ZIX_STATUS_NO_SPACE; + case ERROR_ALREADY_EXISTS: + case ERROR_FILE_EXISTS: + return ZIX_STATUS_EXISTS; + case ERROR_PRIVILEGE_NOT_HELD: + return ZIX_STATUS_BAD_PERMS; + case ERROR_LOCK_VIOLATION: + return ZIX_STATUS_UNAVAILABLE; + } + + return ZIX_STATUS_ERROR; +} + +static inline ZixStatus +zix_windows_status(const bool success) +{ + return success ? ZIX_STATUS_SUCCESS : zix_winerror_status(GetLastError()); +} + +ZixStatus +zix_copy_file(ZixAllocator* const allocator, + const char* const src, + const char* const dst, + const ZixCopyOptions options) +{ + (void)allocator; + + return zix_windows_status( + CopyFile(src, dst, !(options & ZIX_COPY_OPTION_OVERWRITE_EXISTING))); +} + +char* +zix_create_temporary_directory(ZixAllocator* const allocator, + const char* const path_pattern) +{ + static const char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + static const int n_chars = sizeof(chars) - 1; + + // Ensure that the pattern ends with "XXXXXX" + const size_t length = strlen(path_pattern); + if (length < 7 || strcmp(path_pattern + length - 6, "XXXXXX")) { + errno = EINVAL; + return NULL; + } + + // Allocate a result to manipulate as we search for paths + char* const result = (char*)zix_calloc(allocator, length + 1U, 1U); + if (!result) { + return NULL; + } + + // Repeatedly try creating a directory with random suffixes + memcpy(result, path_pattern, length + 1U); + char* const suffix = result + length - 6U; + for (unsigned attempt = 0U; attempt < 128U; ++attempt) { + for (unsigned i = 0U; i < 6U; ++i) { + suffix[i] = chars[rand() % n_chars]; + } + + if (!_mkdir(result)) { + return result; + } + } + + zix_free(allocator, result); + return NULL; +} + +ZixStatus +zix_remove(const char* const path) +{ + return ((zix_file_type(path) == ZIX_FILE_TYPE_DIRECTORY) + ? zix_windows_status(RemoveDirectory(path)) + : remove(path) ? zix_errno_status(errno) + : ZIX_STATUS_SUCCESS); +} + +void +zix_dir_for_each(const char* const path, + void* const data, + void (*const f)(const char* path, + const char* name, + void* data)) +{ + const size_t path_len = strlen(path); + char pat[MAX_PATH + 2U]; + memcpy(pat, path, path_len + 1U); + pat[path_len] = '\\'; + pat[path_len + 1U] = '*'; + pat[path_len + 2U] = '\0'; + + WIN32_FIND_DATA fd; + HANDLE fh = FindFirstFile(pat, &fd); + if (fh != INVALID_HANDLE_VALUE) { + do { + if (!!strcmp(fd.cFileName, ".") && !!strcmp(fd.cFileName, "..")) { + f(path, fd.cFileName, data); + } + } while (FindNextFile(fh, &fd)); + } + FindClose(fh); +} + +ZixStatus +zix_file_lock(FILE* const file, const ZixFileLockMode mode) +{ + HANDLE handle = (HANDLE)_get_osfhandle(fileno(file)); + OVERLAPPED overlapped = {0}; + + const DWORD flags = + (LOCKFILE_EXCLUSIVE_LOCK | + (mode == ZIX_FILE_LOCK_TRY ? LOCKFILE_FAIL_IMMEDIATELY : 0)); + + return zix_windows_status( + LockFileEx(handle, flags, 0, UINT32_MAX, UINT32_MAX, &overlapped)); +} + +ZixStatus +zix_file_unlock(FILE* const file, const ZixFileLockMode mode) +{ + (void)mode; + + HANDLE handle = (HANDLE)_get_osfhandle(fileno(file)); + OVERLAPPED overlapped = {0}; + + return zix_windows_status( + UnlockFileEx(handle, 0, UINT32_MAX, UINT32_MAX, &overlapped)); +} + +char* +zix_canonical_path(ZixAllocator* const allocator, const char* const path) +{ + char full[MAX_PATH] = {0}; + if (!path || !GetFullPathName(path, MAX_PATH, full, NULL)) { + return NULL; + } + + const HANDLE h = + CreateFile(full, + FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + + const DWORD flags = FILE_NAME_NORMALIZED | VOLUME_NAME_DOS; + const DWORD final_size = GetFinalPathNameByHandle(h, NULL, 0U, flags); + if (!final_size) { + CloseHandle(h); + return NULL; + } + + char* const final = (char*)zix_calloc(allocator, final_size + 1U, 1U); + if (final && !GetFinalPathNameByHandle(h, final, final_size + 1U, flags)) { + zix_free(allocator, final); + CloseHandle(h); + return NULL; + } + + CloseHandle(h); + return final; +} + +static ZixFileType +attrs_file_type(const DWORD attrs) +{ + if (attrs == INVALID_FILE_ATTRIBUTES) { + return ZIX_FILE_TYPE_NONE; + } + + if (attrs & FILE_ATTRIBUTE_DIRECTORY) { + return ZIX_FILE_TYPE_DIRECTORY; + } + + if (attrs & FILE_ATTRIBUTE_REPARSE_POINT) { + return ZIX_FILE_TYPE_SYMLINK; + } + + if (attrs & (FILE_ATTRIBUTE_DEVICE)) { + return ZIX_FILE_TYPE_UNKNOWN; + } + + return ZIX_FILE_TYPE_REGULAR; +} + +ZixFileType +zix_file_type(const char* const path) +{ + const ZixFileType type = attrs_file_type(GetFileAttributes(path)); + if (type != ZIX_FILE_TYPE_SYMLINK) { + return type; + } + + // Resolve symlink to find the canonical type + char buf[MAX_PATH]; + ZixBumpAllocator allocator = zix_bump_allocator(sizeof(buf), buf); + char* const canonical = zix_canonical_path(&allocator.base, path); + return zix_file_type(canonical); +} + +ZixFileType +zix_symlink_type(const char* const path) +{ + return attrs_file_type(GetFileAttributes(path)); +} + +ZixStatus +zix_create_directory(const char* const dir_path) +{ + return (!dir_path[0]) ? ZIX_STATUS_BAD_ARG + : _mkdir(dir_path) ? zix_errno_status(errno) + : ZIX_STATUS_SUCCESS; +} + +ZixStatus +zix_create_directory_like(const char* const dir_path, + const char* const existing_path) +{ + return (zix_file_type(existing_path) != ZIX_FILE_TYPE_DIRECTORY) + ? ZIX_STATUS_NOT_FOUND + : zix_create_directory(dir_path); +} + +ZixStatus +zix_create_symlink(const char* const target_path, const char* const link_path) +{ +#if USE_CREATESYMBOLICLINK + static const DWORD flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + + return zix_windows_status(CreateSymbolicLink(link_path, target_path, flags)); +#else + return ZIX_STATUS_NOT_SUPPORTED; +#endif +} + +ZixStatus +zix_create_directory_symlink(const char* const target_path, + const char* const link_path) +{ +#if USE_CREATESYMBOLICLINK + static const DWORD flags = + SYMBOLIC_LINK_FLAG_DIRECTORY | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + + return zix_windows_status(CreateSymbolicLink(link_path, target_path, flags)); +#else + return ZIX_STATUS_NOT_SUPPORTED; +#endif +} + +ZixStatus +zix_create_hard_link(const char* const target_path, const char* const link_path) +{ + return zix_windows_status(CreateHardLink(link_path, target_path, NULL)); +} + +char* +zix_temp_directory_path(ZixAllocator* const allocator) +{ + const DWORD size = GetTempPath(0U, NULL); + char* const buf = (char*)zix_calloc(allocator, size, 1); + if (buf && (GetTempPath(size, buf) != size - 1U)) { + zix_free(allocator, buf); + return NULL; + } + + return buf; +} + +char* +zix_current_path(ZixAllocator* const allocator) +{ + const DWORD size = GetCurrentDirectory(0U, NULL); + char* const buf = (char*)zix_calloc(allocator, size, 1); + if (buf && (GetCurrentDirectory(size, buf) != size - 1U)) { + zix_free(allocator, buf); + return NULL; + } + + return buf; +} diff --git a/src/win32/system_win32.c b/src/win32/system_win32.c new file mode 100644 index 0000000..a2e68bf --- /dev/null +++ b/src/win32/system_win32.c @@ -0,0 +1,20 @@ +// Copyright 2007-2022 David Robillard +// SPDX-License-Identifier: ISC + +#include "../system.h" + +#include + +#include +#include + +uint32_t +zix_system_page_size(void) +{ + SYSTEM_INFO info; + GetSystemInfo(&info); + + return (info.dwPageSize > 0 && info.dwPageSize < UINT32_MAX) + ? (uint32_t)info.dwPageSize + : 512U; +} diff --git a/src/zix_config.h b/src/zix_config.h index b1096a5..7b87826 100644 --- a/src/zix_config.h +++ b/src/zix_config.h @@ -48,6 +48,53 @@ # endif # endif +// MacOS: clonefile() +# ifndef HAVE_CLONEFILE +# if defined(__APPLE__) && __has_include() +# define HAVE_CLONEFILE 1 +# else +# define HAVE_CLONEFILE 0 +# endif +# endif + +// FreeBSD 13, Linux 4.5, and glibc 2.27: copy_file_range() +# ifndef HAVE_COPY_FILE_RANGE +# if (defined(__FreeBSD__) && __FreeBSD__ >= 13) || defined(__linux__) || \ + (defined(__GLIBC__) && \ + (__GLIBC__ > 2 || __GLIBC__ == 2 && __GLIBC_MINOR__ >= 27)) +# define HAVE_COPY_FILE_RANGE 1 +# else +# define HAVE_COPY_FILE_RANGE 0 +# endif +# endif + +// Windows: CreateSymbolicLink() +# ifndef HAVE_CREATESYMBOLICLINK +# if defined(_MSC_VER) && _MSC_VER >= 1910 +# define HAVE_CREATESYMBOLICLINK 1 +# else +# define HAVE_CREATESYMBOLICLINK 0 +# endif +# endif + +// POSIX.1-2001, Windows: fileno() +# ifndef HAVE_FILENO +# if defined(_WIN32) || defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L +# define HAVE_FILENO 1 +# else +# define HAVE_FILENO 0 +# endif +# endif + +// Classic UNIX: flock() +# ifndef HAVE_FLOCK +# if defined(__APPLE__) || defined(__unix__) +# define HAVE_FLOCK 1 +# else +# define HAVE_FLOCK 0 +# endif +# endif + // POSIX.1-2001: mlock() # ifndef HAVE_MLOCK # if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L @@ -57,6 +104,24 @@ # endif # endif +// POSIX.1-2001: pathconf() +# ifndef HAVE_PATHCONF +# if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L +# define HAVE_PATHCONF 1 +# else +# define HAVE_PATHCONF 0 +# endif +# endif + +// POSIX.1-2001: posix_fadvise() +# ifndef HAVE_POSIX_FADVISE +# if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L +# define HAVE_POSIX_FADVISE 1 +# else +# define HAVE_POSIX_FADVISE 0 +# endif +# endif + // POSIX.1-2001: posix_memalign() # ifndef HAVE_POSIX_MEMALIGN # if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L @@ -66,6 +131,15 @@ # endif # endif +// POSIX.1-2001: realpath() +# ifndef HAVE_REALPATH +# if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L +# define HAVE_REALPATH 1 +# else +# define HAVE_REALPATH 0 +# endif +# endif + // POSIX.1-2001: sem_timedwait() # ifndef HAVE_SEM_TIMEDWAIT # if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L @@ -75,6 +149,15 @@ # endif # endif +// POSIX.1-2001: sysconf() +# ifndef HAVE_SYSCONF +# if defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112L +# define HAVE_SYSCONF 1 +# else +# define HAVE_SYSCONF 0 +# endif +# endif + #endif // !defined(ZIX_NO_DEFAULT_CONFIG) /* @@ -91,22 +174,76 @@ # define USE_CLOCK_GETTIME 0 #endif +#if HAVE_CLONEFILE +# define USE_CLONEFILE 1 +#else +# define USE_CLONEFILE 0 +#endif + +#if HAVE_COPY_FILE_RANGE +# define USE_COPY_FILE_RANGE 1 +#else +# define USE_COPY_FILE_RANGE 0 +#endif + +#if HAVE_CREATESYMBOLICLINK +# define USE_CREATESYMBOLICLINK 1 +#else +# define USE_CREATESYMBOLICLINK 0 +#endif + +#if HAVE_FILENO +# define USE_FILENO 1 +#else +# define USE_FILENO 0 +#endif + +#if HAVE_FLOCK +# define USE_FLOCK 1 +#else +# define USE_FLOCK 0 +#endif + #if HAVE_MLOCK # define USE_MLOCK 1 #else # define USE_MLOCK 0 #endif +#if HAVE_PATHCONF +# define USE_PATHCONF 1 +#else +# define USE_PATHCONF 0 +#endif + +#if HAVE_POSIX_FADVISE +# define USE_POSIX_FADVISE 1 +#else +# define USE_POSIX_FADVISE 0 +#endif + #if HAVE_POSIX_MEMALIGN # define USE_POSIX_MEMALIGN 1 #else # define USE_POSIX_MEMALIGN 0 #endif +#if HAVE_REALPATH +# define USE_REALPATH 1 +#else +# define USE_REALPATH 0 +#endif + #if HAVE_SEM_TIMEDWAIT # define USE_SEM_TIMEDWAIT 1 #else # define USE_SEM_TIMEDWAIT 0 #endif +#if HAVE_SYSCONF +# define USE_SYSCONF 1 +#else +# define USE_SYSCONF 0 +#endif + #endif // ZIX_CONFIG_H -- cgit v1.2.1