From d8b960be46007f9c09356e526d3c2dcff4b186a5 Mon Sep 17 00:00:00 2001
From: David Robillard <d@drobilla.net>
Date: Sun, 23 Oct 2022 13:41:27 -0400
Subject: Add filesystem API

---
 include/zix/filesystem.h | 371 +++++++++++++++++++++++++++++++++++++++++++++++
 include/zix/path.h       |   3 +-
 include/zix/status.h     |   2 +
 include/zix/zix.h        |   1 +
 4 files changed, 376 insertions(+), 1 deletion(-)
 create mode 100644 include/zix/filesystem.h

(limited to 'include')

diff --git a/include/zix/filesystem.h b/include/zix/filesystem.h
new file mode 100644
index 0000000..7b01c0e
--- /dev/null
+++ b/include/zix/filesystem.h
@@ -0,0 +1,371 @@
+// Copyright 2007-2022 David Robillard <d@drobilla.net>
+// SPDX-License-Identifier: ISC
+
+#ifndef ZIX_FILESYSTEM_H
+#define ZIX_FILESYSTEM_H
+
+#include "zix/allocator.h"
+#include "zix/attributes.h"
+#include "zix/status.h"
+
+#if !(defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64)
+#  include <stddef.h>
+#endif
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+
+ZIX_BEGIN_DECLS
+
+/**
+   @defgroup zix_filesystem Filesystem
+   @ingroup zix_file_system
+   @{
+*/
+
+/**
+   @defgroup zix_fs_creation Creation and Removal
+   @{
+*/
+
+/// Options to control filesystem copy operations
+typedef enum {
+  ZIX_COPY_OPTION_NONE               = 0U,       ///< Report any error
+  ZIX_COPY_OPTION_OVERWRITE_EXISTING = 1U << 0U, ///< Replace existing file
+} ZixCopyOption;
+
+/// Bitwise OR of ZixCopyOptions values
+typedef uint32_t ZixCopyOptions;
+
+/**
+   Copy the file at path `src` to path `dst`.
+
+   If supported by the system, a lightweight copy will be made to take
+   advantage of copy-on-write support in the filesystem.  Otherwise, a simple
+   deep copy will be made.
+
+   @param allocator Allocator used for a memory block for copying if necessary.
+   @param src Path to source file to copy.
+   @param dst Path to destination file to create.
+   @param options Options to control the kind of copy and error conditions.
+   @return #ZIX_STATUS_SUCCESS if `dst` was successfully created, or an error.
+*/
+ZIX_API
+ZixStatus
+zix_copy_file(ZixAllocator* ZIX_NULLABLE allocator,
+              const char* ZIX_NONNULL    src,
+              const char* ZIX_NONNULL    dst,
+              ZixCopyOptions             options);
+
+/**
+   Create the directory `dir_path` with all available permissions.
+
+   @return #ZIX_STATUS_SUCCESS if `dir_path` was successfully created, or an
+   error.
+*/
+ZIX_API
+ZixStatus
+zix_create_directory(const char* ZIX_NONNULL dir_path);
+
+/**
+   Create the directory `dir_path` with the permissions of another.
+
+   This is like zix_create_directory(), but will copy the permissions from
+   another directory.
+
+   @return #ZIX_STATUS_SUCCESS if `dir_path` was successfully created, or an
+   error.
+*/
+ZIX_API
+ZixStatus
+zix_create_directory_like(const char* ZIX_NONNULL dir_path,
+                          const char* ZIX_NONNULL existing_path);
+
+/**
+   Create the directory `dir_path` and any parent directories if necessary.
+
+   @param allocator Allocator used for a temporary path buffer if necessary.
+
+   @param dir_path The path to the deepest directory to create.
+
+   @return #ZIX_STATUS_SUCCESS if all directories in `dir_path` were
+   successfully created (or already existed), or an error.
+*/
+ZIX_API
+ZixStatus
+zix_create_directories(ZixAllocator* ZIX_NULLABLE allocator,
+                       const char* ZIX_NONNULL    dir_path);
+
+/**
+   Create a hard link at path `link` that points to path `target`.
+
+   @return #ZIX_STATUS_SUCCESS, or an error.
+*/
+ZIX_API
+ZixStatus
+zix_create_hard_link(const char* ZIX_NONNULL target_path,
+                     const char* ZIX_NONNULL link_path);
+
+/**
+   Create a symbolic link at path `link` that points to path `target`.
+
+   Note that portable code should use zix_create_directory_symlink() if the
+   target is a directory, since this function won't work for that on some
+   systems (like Windows).
+
+   @return #ZIX_STATUS_SUCCESS, or an error.
+*/
+ZIX_API
+ZixStatus
+zix_create_symlink(const char* ZIX_NONNULL target_path,
+                   const char* ZIX_NONNULL link_path);
+
+/**
+   Create a symbolic link at path `link` that points to the directory `target`.
+
+   This is a separate function from zix_create_symlink() because some systems
+   (like Windows) require directory symlinks to be created specially.
+
+   @return #ZIX_STATUS_SUCCESS, or an error.
+*/
+ZIX_API
+ZixStatus
+zix_create_directory_symlink(const char* ZIX_NONNULL target_path,
+                             const char* ZIX_NONNULL link_path);
+
+/**
+   Create a unique temporary directory at a given path pattern.
+
+   The last six characters of `pattern` must be "XXXXXX" and will be replaced
+   with unique characters in the result.
+
+   @param allocator Allocator used for the returned path.
+
+   @param path_pattern A path pattern ending in "XXXXXX".
+
+   @return The path of the created directory, or null.
+*/
+ZIX_API
+char* ZIX_ALLOCATED
+zix_create_temporary_directory(ZixAllocator* ZIX_NULLABLE allocator,
+                               const char* ZIX_NONNULL    path_pattern);
+
+/// Remove the file or empty directory at `path`
+ZIX_API
+ZixStatus
+zix_remove(const char* ZIX_NONNULL path);
+
+/**
+   @}
+   @defgroup zix_fs_access Access
+   @{
+*/
+
+/**
+   Visit every file in the directory at `path`.
+
+   @param path A path to a directory.
+
+   @param data Opaque user data that is passed to `f`.
+
+   @param f A function called on every entry in the directory.  The `path`
+   parameter is always the directory path passed to this function, the `name`
+   parameter is the name of the directory entry (not its full path).
+*/
+ZIX_API
+void
+zix_dir_for_each(const char* ZIX_NONNULL path,
+                 void* ZIX_NULLABLE      data,
+                 void (*ZIX_NONNULL f)(const char* ZIX_NONNULL path,
+                                       const char* ZIX_NONNULL name,
+                                       void* ZIX_NONNULL       data));
+
+/**
+   Return whether the given paths point to files with identical contents.
+
+   @param allocator Allocator used for a memory block for comparison if
+   necessary.
+
+   @param a_path Path to the first file to compare
+
+   @param b_path Path to the second file to compare
+
+   @return True if the two files have byte-for-byte identical contents.
+*/
+ZIX_API
+bool
+zix_file_equals(ZixAllocator* ZIX_NULLABLE allocator,
+                const char* ZIX_NONNULL    a_path,
+                const char* ZIX_NONNULL    b_path);
+
+/**
+   @}
+   @defgroup zix_fs_resolution Resolution
+   @{
+*/
+
+/**
+   Return `path` as a canonical absolute path to a "real" file.
+
+   This expands all symbolic links, relative references, and removes extra
+   directory separators.
+
+   Since this function may return null anyway, it accepts a null parameter to
+   allow easier chaining of path functions when only the final result is
+   required, for example:
+
+   @code{.c}
+   char* path      = zix_path_join(alloc, "/some/dir", "filename.txt");
+   char* canonical = zix_canonical_path(path);
+   if (canonical) {
+     // Do something with the canonical path...
+   } else {
+     // No canonical path for some reason, we don't care which...
+   }
+   @endcode
+
+   @return A new canonical version of `path`, or null if it doesn't exist.
+*/
+ZIX_API
+char* ZIX_ALLOCATED
+zix_canonical_path(ZixAllocator* ZIX_NULLABLE allocator,
+                   const char* ZIX_NULLABLE   path);
+
+/**
+   @}
+   @defgroup zix_fs_locking Locking
+   @{
+*/
+
+/**
+   A mode for locking files.
+
+   The same mode should be used for the lock and the corresponding unlock.
+*/
+typedef enum {
+  ZIX_FILE_LOCK_BLOCK, ///< Block until the operation succeeds
+  ZIX_FILE_LOCK_TRY,   ///< Fail if the operation would block
+} ZixFileLockMode;
+
+/**
+   Set an advisory exclusive lock on `file`.
+
+   @param file Handle for open file to lock.
+   @param mode Lock mode.
+   @return #ZIX_STATUS_SUCCESS if the file was locked, or an error.
+*/
+ZIX_API
+ZixStatus
+zix_file_lock(FILE* ZIX_NONNULL file, ZixFileLockMode mode);
+
+/**
+   Remove an advisory exclusive lock on `file`.
+
+   @param file Handle for open file to lock.
+   @param mode Lock mode.
+   @return #ZIX_STATUS_SUCCESS if the file was unlocked, or an error.
+*/
+ZIX_API
+ZixStatus
+zix_file_unlock(FILE* ZIX_NONNULL file, ZixFileLockMode mode);
+
+/**
+   @}
+   @defgroup zix_fs_queries Queries
+   @{
+*/
+
+/**
+   An offset into a file or a file size in bytes.
+
+   This is signed, and may be 64 bits even on 32-bit systems.
+*/
+#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64
+typedef int64_t ZixFileOffset;
+#else
+typedef ptrdiff_t ZixFileOffset;
+#endif
+
+/**
+   A type of file.
+
+   Note that not all types may be supported, and the system may support
+   additional types not enumerated here.
+*/
+typedef enum {
+  ZIX_FILE_TYPE_NONE,      ///< Non-existent file
+  ZIX_FILE_TYPE_REGULAR,   ///< Regular file
+  ZIX_FILE_TYPE_DIRECTORY, ///< Directory
+  ZIX_FILE_TYPE_SYMLINK,   ///< Symbolic link
+  ZIX_FILE_TYPE_BLOCK,     ///< Special block file
+  ZIX_FILE_TYPE_CHARACTER, ///< Special character file
+  ZIX_FILE_TYPE_FIFO,      ///< FIFO
+  ZIX_FILE_TYPE_SOCKET,    ///< Socket
+  ZIX_FILE_TYPE_UNKNOWN,   ///< Existing file with unknown type
+} ZixFileType;
+
+/**
+   Return the type of a file or directory, resolving symlinks.
+*/
+ZIX_API
+ZixFileType
+zix_file_type(const char* ZIX_NONNULL path);
+
+/**
+   Return the type of a file or directory or symlink.
+
+   On Windows, a directory symlink (actually a "reparse point") always appears
+   as a directory.
+*/
+ZIX_API
+ZixFileType
+zix_symlink_type(const char* ZIX_NONNULL path);
+
+/**
+   Return the size of a file.
+
+   Note that the returned value is signed and must be checked for errors.
+   Non-negative values can be thought of as the "end" offset just past the last
+   byte.
+
+   @return A non-negative size in bytes, or -1 on error.
+*/
+ZIX_API
+ZixFileOffset
+zix_file_size(const char* ZIX_NONNULL path);
+
+/**
+   @}
+   @defgroup zix_fs_environment Environment
+   @{
+*/
+
+/**
+   Return the current working directory.
+
+   @param allocator Allocator used for the returned path.
+*/
+ZIX_API
+char* ZIX_ALLOCATED
+zix_current_path(ZixAllocator* ZIX_NULLABLE allocator);
+
+/**
+   Return the path to a directory suitable for making temporary files.
+
+   @param allocator Allocator used for the returned path.
+
+   @return A new path to a temporary directory, or null on error.
+*/
+ZIX_API
+char* ZIX_ALLOCATED
+zix_temp_directory_path(ZixAllocator* ZIX_NULLABLE allocator);
+
+/**
+   @}
+   @}
+*/
+
+ZIX_END_DECLS
+
+#endif /* ZIX_FILESYSTEM_H */
diff --git a/include/zix/path.h b/include/zix/path.h
index 5d3bd60..7a52d31 100644
--- a/include/zix/path.h
+++ b/include/zix/path.h
@@ -67,7 +67,8 @@ zix_path_preferred(ZixAllocator* ZIX_NULLABLE allocator,
    replaced with a single "\" on Windows, and a single "/" everwhere else).
 
    Note that this function doesn't access the filesystem, so won't do anything
-   like case normalization or symbolic link dereferencing.
+   like case normalization or symbolic link dereferencing.  For that, use
+   zix_canonical_path().
 */
 ZIX_API
 char* ZIX_ALLOCATED
diff --git a/include/zix/status.h b/include/zix/status.h
index 0e95f43..dfd6412 100644
--- a/include/zix/status.h
+++ b/include/zix/status.h
@@ -28,6 +28,8 @@ typedef enum {
   ZIX_STATUS_OVERFLOW,      ///< Overflow
   ZIX_STATUS_NOT_SUPPORTED, ///< Not supported
   ZIX_STATUS_UNAVAILABLE,   ///< Resource unavailable
+  ZIX_STATUS_NO_SPACE,      ///< Out of storage space
+  ZIX_STATUS_MAX_LINKS,     ///< Too many links
 } ZixStatus;
 
 /// Return a string describing a status code
diff --git a/include/zix/zix.h b/include/zix/zix.h
index a91b6f8..b144cee 100644
--- a/include/zix/zix.h
+++ b/include/zix/zix.h
@@ -58,6 +58,7 @@
    @{
 */
 
+#include "zix/filesystem.h"
 #include "zix/path.h"
 
 /**
-- 
cgit v1.2.1