summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2022-10-23 13:41:27 -0400
committerDavid Robillard <d@drobilla.net>2022-10-23 14:57:45 -0400
commitd8b960be46007f9c09356e526d3c2dcff4b186a5 (patch)
treecd40db8d5634e74f8795922b7ab19fc8c6507648 /src
parentc886d489576cd0bc33d7d22d81981c794067946f (diff)
downloadzix-d8b960be46007f9c09356e526d3c2dcff4b186a5.tar.gz
zix-d8b960be46007f9c09356e526d3c2dcff4b186a5.tar.bz2
zix-d8b960be46007f9c09356e526d3c2dcff4b186a5.zip
Add filesystem API
Diffstat (limited to 'src')
-rw-r--r--src/errno_status.c5
-rw-r--r--src/filesystem.c122
-rw-r--r--src/posix/filesystem_posix.c444
-rw-r--r--src/posix/system_posix.c26
-rw-r--r--src/status.c6
-rw-r--r--src/system.c41
-rw-r--r--src/system.h30
-rw-r--r--src/win32/filesystem_win32.c325
-rw-r--r--src/win32/system_win32.c20
-rw-r--r--src/zix_config.h137
10 files changed, 1155 insertions, 1 deletions
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 <d@drobilla.net>
+// 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 <direct.h>
+# include <io.h>
+#else
+# include <unistd.h>
+#endif
+
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+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 <d@drobilla.net>
+// 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 <sys/file.h>
+#endif
+
+#if USE_CLONEFILE
+# include <sys/attr.h>
+# include <sys/clonefile.h>
+#endif
+
+#if USE_REALPATH
+# include <limits.h>
+#endif
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#include "../system.h"
+#include "../zix_config.h"
+
+#include <unistd.h>
+
+#include <stdint.h>
+
+#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 <d@drobilla.net>
+// Copyright 2014-2022 David Robillard <d@drobilla.net>
// 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 <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#include "system.h"
+
+#include "errno_status.h"
+
+#include "zix/status.h"
+
+#ifdef _WIN32
+# include <io.h>
+#else
+# include <unistd.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+
+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 <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#ifndef ZIX_SYSTEM_H
+#define ZIX_SYSTEM_H
+
+#include "zix/status.h"
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#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 <d@drobilla.net>
+// 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 <direct.h>
+#include <fcntl.h>
+#include <io.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <windows.h>
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+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 <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#include "../system.h"
+
+#include <windows.h>
+
+#include <limits.h>
+#include <stdint.h>
+
+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(<sys/clonefile.h>)
+# 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