Merge pull request 'Merge features/fs' (#6) from features/fs into master

Reviewed-on: PallasDev/DrangPlatform#6
This commit is contained in:
MechSlayer 2025-09-09 20:43:29 +02:00
commit 27190311c0
16 changed files with 2078 additions and 7 deletions

4
.gitignore vendored
View file

@ -11,4 +11,6 @@ build-*/
CMakeLists.txt.user
# CMake
CMakeUserPresets.json
CMakeUserPresets.json
Playground/

View file

@ -33,3 +33,7 @@ set(MI_BUILD_OBJECT OFF CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(mimalloc)
add_subdirectory(Source/DrangPlatform)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/Playground)
add_subdirectory(Playground)
endif()

View file

@ -147,6 +147,25 @@ DRANG_PLATFORM_API void drang_free(void *ptr);
*/
#define DRANG_ALLOC_T_N(_Type_, _Num_) ((_Type_ *) drang_alloc_aligned(sizeof(_Type_) * (_Num_), alignof(_Type_)))
/**
* @brief Allocates zero-initialized memory for a single object of the specified type with proper alignment.
*
* @param _Type_ The type to allocate memory for.
* @return Pointer to allocated zero-initialized memory cast to the appropriate type.
* @remarks Automatically calculates total size and uses proper alignment for the type.
*/
#define DRANG_CALLOC_T(_Type_) ((_Type_ *) drang_calloc_aligned(1, sizeof(_Type_), alignof(_Type_)))
/**
* @brief Allocates zero-initialized memory for an array of objects of the specified type with proper alignment.
*
* @param _Type_ The type to allocate memory for.
* @param _Num_ Number of objects to allocate.
* @return Pointer to allocated zero-initialized memory cast to the appropriate type.
* @remarks Automatically calculates total size and uses proper alignment for the type.
*/
#define DRANG_CALLOC_T_N(_Type_, _Num_) ((_Type_ *) drang_calloc_aligned((_Num_), sizeof(_Type_), alignof(_Type_)))
/**
* @brief Reallocates memory for an array of objects of the specified type with proper alignment.
*
@ -266,6 +285,24 @@ DRANG_PLATFORM_API void drang_free(void *ptr);
*/
#define DRANG_TRY_ALLOC_T_N(_Type_, _Num_) DRANG_ALLOC_TRY_IMPL(DRANG_ALLOC_T_N(_Type_, (_Num_)))
/**
* @brief Allocates zero-initialized memory for a typed object with automatic error handling.
*
* @param _Type_ The type to allocate memory for.
* @return Pointer to allocated zero-initialized memory cast to the appropriate type.
* @remarks Combines type-safe allocation with automatic error handling. Preferred for single object allocation.
*/
#define DRANG_TRY_CALLOC_T(_Type_) DRANG_ALLOC_TRY_IMPL(DRANG_CALLOC_T(_Type_))
/**
* @brief Allocates zero-initialized memory for a typed array with automatic error handling.
*
* @param _Type_ The type to allocate memory for.
* @param _Num_ Number of objects to allocate.
* @return Pointer to allocated zero-initialized memory cast to the appropriate type.
*/
#define DRANG_TRY_CALLOC_T_N(_Type_, _Num_) DRANG_ALLOC_TRY_IMPL(DRANG_CALLOC_T_N(_Type_, (_Num_)))
/**
* @brief Reallocates memory for a typed array with automatic error handling.
*

View file

@ -0,0 +1,139 @@
#pragma once
#include "platform.h"
#include <stdint.h>
#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)
# ifdef __has_include
# if __has_include(<endian.h>)
# include <endian.h> // gnu libc normally provides, linux
# elif __has_include(<machine/endian.h>)
# include <machine/endian.h> //open bsd, macos
# elif __has_include(<sys/param.h>)
# include <sys/param.h> // mingw, some bsd (not open/macos)
# elif __has_include(<sys/isadefs.h>)
# include <sys/isadefs.h> // solaris
# endif
# endif
#endif
#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)
# if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) \
|| (defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN) \
|| (defined(_BYTE_ORDER) && _BYTE_ORDER == _BIG_ENDIAN) || (defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN) \
|| (defined(__sun) && defined(__SVR4) && defined(_BIG_ENDIAN)) || defined(__ARMEB__) || defined(__THUMBEB__) \
|| defined(__AARCH64EB__) || defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__) || defined(_M_PPC)
# define __BIG_ENDIAN__
# elif (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || /* gcc */ \
(defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN) /* linux header */ \
|| (defined(_BYTE_ORDER) && _BYTE_ORDER == _LITTLE_ENDIAN) \
|| (defined(BYTE_ORDER) && BYTE_ORDER == LITTLE_ENDIAN) /* mingw header */ \
|| (defined(__sun) && defined(__SVR4) && defined(_LITTLE_ENDIAN)) || /* solaris */ \
defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || defined(__MIPSEL) \
|| defined(__MIPSEL__) || defined(_M_IX86) || defined(_M_X64) || defined(_M_IA64) \
|| /* msvc for intel processors */ \
defined(_M_ARM) /* msvc code on arm executes in little endian mode */
# define __LITTLE_ENDIAN__
# endif
#endif
#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__)
# error "Unknown endianness"
#endif
#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) || defined(__clang__)
# define DRANG_BSWAP8(_X_) (_X_)
# define DRANG_BSWAP16(_X_) (__builtin_bswap16(_X_))
# define DRANG_BSWAP32(_X_) (__builtin_bswap32(_X_))
# define DRANG_BSWAP64(_X_) (__builtin_bswap64(_X_))
#elif defined(_MSC_VER)
# include <stdlib.h>
# define DRANG_BSWAP8(_X_) (_X_)
# define DRANG_BSWAP16(_X_) (_byteswap_ushort(_X_))
# define DRANG_BSWAP32(_X_) (_byteswap_ulong(_X_))
# define DRANG_BSWAP64(_X_) (_byteswap_uint64(_X_))
#else
static inline uint16 drang_bswap16_impl(const uint16_t x)
{
return (((x >> 8) & 0xFFu) | ((x & 0xFFu) << 8));
}
static inline uint32_t drang_bswap32_impl(const uint32_t x)
{
return (((x & 0xFF000000u) >> 24) | ((x & 0x00FF0000u) >> 8) | ((x & 0x0000FF00u) << 8) | ((x & 0x000000FFu) << 24));
}
static inline uint64_t drang_bswap64_impl(const uint64_t x)
{
return (((x & 0xFF00000000000000ull) >> 56) | ((x & 0x00FF000000000000ull) >> 40)
| ((x & 0x0000FF0000000000ull) >> 24) | ((x & 0x000000FF00000000ull) >> 8)
| ((x & 0x00000000FF000000ull) << 8) | ((x & 0x0000000000FF0000ull) << 24)
| ((x & 0x000000000000FF00ull) << 40) | ((x & 0x00000000000000FFull) << 56));
}
# define DRANG_BSWAP16(_X_) (drang_bswap16_impl(_X_))
# define DRANG_BSWAP32(_X_) (drang_bswap32_impl(_X_))
# define DRANG_BSWAP64(_X_) (drang_bswap64_impl(_X_))
#endif
#define DRANG_BSWAPF(_X_) DRANG_BIT_CAST(DRANG_BSWAP32(DRANG_BIT_CAST(_X_, float, uint32_t)), uint32_t, float)
#define DRANG_BSWAPD(_X_) DRANG_BIT_CAST(DRANG_BSWAP64(DRANG_BIT_CAST(_X_, double, uint64_t)), uint64_t, double)
#if defined(__LITTLE_ENDIAN__)
# define DRANG_NE_TO_LE8(_X_) (_X_)
# define DRANG_NE_TO_LE16(_X_) (_X_)
# define DRANG_NE_TO_LE32(_X_) (_X_)
# define DRANG_NE_TO_LE64(_X_) (_X_)
# define DRANG_NE_TO_LEF(_X_) (_X_)
# define DRANG_NE_TO_LED(_X_) (_X_)
# define DRANG_LE_TO_NE8(_X_) (_X_)
# define DRANG_LE_TO_NE16(_X_) (_X_)
# define DRANG_LE_TO_NE32(_X_) (_X_)
# define DRANG_LE_TO_NE64(_X_) (_X_)
# define DRANG_LE_TO_NEF(_X_) (_X_)
# define DRANG_LE_TO_NED(_X_) (_X_)
# define DRANG_NE_TO_BE8(_X_) DRANG_BSWAP8(_X_)
# define DRANG_NE_TO_BE16(_X_) DRANG_BSWAP16(_X_)
# define DRANG_NE_TO_BE32(_X_) DRANG_BSWAP32(_X_)
# define DRANG_NE_TO_BE64(_X_) DRANG_BSWAP64(_X_)
# define DRANG_NE_TO_BEF(_X_) DRANG_BSWAPF(_X_)
# define DRANG_NE_TO_BED(_X_) DRANG_BSWAPD(_X_
# define DRANG_BE_TO_NE8(_X_) DRANG_BSWAP8(_X_)
# define DRANG_BE_TO_NE16(_X_) DRANG_BSWAP16(_X_)
# define DRANG_BE_TO_NE32(_X_) DRANG_BSWAP32(_X_)
# define DRANG_BE_TO_NE64(_X_) DRANG_BSWAP64(_X_)
# define DRANG_BE_TO_NEF(_X_) DRANG_BSWAPF(_X_)
# define DRANG_BE_TO_NED(_X_) DRANG_BSWAPD(_X_
#elif defined(__BIG_ENDIAN__)
# define DRANG_NE_TO_LE8(_X_) DRANG_BSWAP8(_X_)
# define DRANG_NE_TO_LE16(_X_) DRANG_BSWAP16(_X_)
# define DRANG_NE_TO_LE32(_X_) DRANG_BSWAP32(_X_)
# define DRANG_NE_TO_LE64(_X_) DRANG_BSWAP64(_X_)
# define DRANG_NE_TO_LEF(_X_) DRANG_BSWAPF(_X_)
# define DRANG_NE_TO_LED(_X_) DRANG_BSWAPD(_X_)
# define DRANG_LE_TO_NE8(_X_) DRANG_BSWAP8(_X_)
# define DRANG_LE_TO_NE16(_X_) DRANG_BSWAP16(_X_)
# define DRANG_LE_TO_NE32(_X_) DRANG_BSWAP32(_X_)
# define DRANG_LE_TO_NE64(_X_) DRANG_BSWAP64(_X_)
# define DRANG_LE_TO_NEF(_X_) DRANG_BSWAPF(_X_)
# define DRANG_LE_TO_NED(_X_) DRANG_BSWAPD(_X_
# define DRANG_NE_TO_BE8(_X_) (_X_)
# define DRANG_NE_TO_BE16(_X_) (_X_)
# define DRANG_NE_TO_BE32(_X_) (_X_)
# define DRANG_NE_TO_BE64(_X_) (_X_)
# define DRANG_NE_TO_BEF(_X_) (_X_)
# define DRANG_NE_TO_BED(_X_) (_X_)
# define DRANG_BE_TO_NE8(_X_) (_X_)
# define DRANG_BE_TO_NE16(_X_) (_X_)
# define DRANG_BE_TO_NE32(_X_) (_X_)
# define DRANG_BE_TO_NE64(_X_) (_X_)
# define DRANG_BE_TO_NEF(_X_) (_X_)
# define DRANG_BE_TO_NED(_X_) (_X_)
#else
# error "Unknown endianness"
#endif

View file

@ -21,6 +21,9 @@ DRANG_PLATFORM_API const char *drang_error_str(int err);
#define DRANG_EPERM (-8) // Operation not permitted
#define DRANG_ETIMEDOUT (-9) // Connection timed out
#define DRANG_EBUSY (-10) // Device or resource busy
#define DRANG_EOVERFLOW (-11) // Value too large to be stored in data type
#define DRANG_ENOSPC (-12) // No space left on device
#define DRANG_EROFS (-13) // Read-only file system
#define DRANG_EPLATFORM (-1000) // An unknown platform-specific error occurred
/**

View file

@ -0,0 +1,543 @@
#pragma once
#include "platform.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
DRANG_BEGIN_DECLS
/**
* @brief File open mode flags for controlling file access behavior.
*
* These flags can be combined using bitwise OR operations to specify
* the desired file access mode and creation behavior.
*
* @remarks Flags can be combined, for example:
* drang_fs_mode_read | drang_fs_mode_write for read-write access.
* Some combinations may not be valid on all platforms.
*/
typedef enum {
drang_fs_mode_read = 0x01, /**< Open file for reading */
drang_fs_mode_write = 0x02, /**< Open file for writing */
drang_fs_mode_append = 0x04, /**< Open file for appending (writes go to end) */
drang_fs_mode_create = 0x08, /**< Create file if it doesn't exist */
drang_fs_mode_truncate = 0x10, /**< Truncate file to zero length if it exists */
drang_fs_mode_exclusive = 0x20 /**< Fail if file already exists (used with create) */
} drang_fs_mode_t;
/**
* @brief Origin points for seek operations.
*
* Specifies the reference point for file positioning operations.
*/
typedef enum {
drang_seek_origin_begin = 0, /**< Seek from beginning of file */
drang_seek_origin_current = 1, /**< Seek from current file position */
drang_seek_origin_end = 2 /**< Seek from end of file */
} drang_seek_origin_t;
/**
* @brief File system entry type classification.
*
* Used to identify the type of file system objects when querying
* file information or reading directory entries.
*/
typedef enum {
drang_fs_type_unknown = 0, /**< Unknown or unsupported file type */
drang_fs_type_file = 1, /**< Regular file */
drang_fs_type_directory = 2, /**< Directory */
drang_fs_type_symlink = 3 /**< Symbolic link */
} drang_fs_type_t;
/**
* @brief File system permissions type.
*
* Represents file permissions using Unix-style permission bits.
* Use the DRANG_FS_PERM_* constants to construct permission values.
*/
typedef uint32_t drang_fs_permissions_t;
/**
* @brief File system timestamp type.
*
* Represents 64-bits of time in seconds since the Unix epoch (1970-01-01 00:00:00 UTC).
*/
typedef uint64_t drang_fs_time_t;
/**
* @name File Permission Constants
* @brief Unix-style file permission bit flags.
*
* These constants can be combined using bitwise OR to create
* permission specifications for files and directories.
* @{
*/
#define DRANG_FS_PERM_USER_READ 0x0400 /**< User read permission */
#define DRANG_FS_PERM_USER_WRITE 0x0200 /**< User write permission */
#define DRANG_FS_PERM_USER_EXEC 0x0100 /**< User execute permission */
#define DRANG_FS_PERM_GROUP_READ 0x0040 /**< Group read permission */
#define DRANG_FS_PERM_GROUP_WRITE 0x0020 /**< Group write permission */
#define DRANG_FS_PERM_GROUP_EXEC 0x0010 /**< Group execute permission */
#define DRANG_FS_PERM_OTHER_READ 0x0004 /**< Other read permission */
#define DRANG_FS_PERM_OTHER_WRITE 0x0002 /**< Other write permission */
#define DRANG_FS_PERM_OTHER_EXEC 0x0001 /**< Other execute permission */
/** @} */
/**
* @brief Opaque structure representing an open file handle.
*
* This structure contains platform-specific file handle information
* and should only be manipulated through the provided API functions.
*/
typedef struct drang_fs_file drang_fs_file_t;
/**
* @brief Opaque structure representing an open directory handle.
*
* This structure contains platform-specific directory handle information
* and should only be manipulated through the provided API functions.
*/
typedef struct drang_fs_directory drang_fs_directory_t;
/**
* @brief File system metadata structure.
*
* Contains comprehensive information about a file system entry
* including type, size, permissions, and timestamps.
*/
struct drang_fs_stat
{
drang_fs_type_t type; /**< Type of file system entry */
uint64_t size; /**< Size in bytes (0 for directories) */
drang_fs_permissions_t permissions; /**< File permissions */
drang_fs_time_t created_time; /**< File creation time */
drang_fs_time_t modified_time; /**< Last modification time */
drang_fs_time_t accessed_time; /**< Last access time */
};
/**
* @brief Maximum size for relative path names in directory entries.
*/
#define DRANG_FS_MAX_REL_PATH_SIZE (512)
/**
* @brief Directory entry structure.
*
* Represents a single entry within a directory, containing the
* entry name and type information.
*/
typedef struct fs_dir_entry
{
char name[DRANG_FS_MAX_REL_PATH_SIZE]; /**< Entry name (null-terminated) */
drang_fs_type_t type; /**< Entry type */
} drang_fs_dir_entry_t;
/**
* @brief Gets the platform-specific path separator character.
*
* Returns the character used to separate path components on the current platform.
* This is typically '/' on Unix-like systems (Linux, macOS) and '\' on Windows.
*
* @return Platform-specific path separator character.
* @retval '/' On Unix-like systems (Linux, macOS, BSD, etc.).
* @retval '\' On Windows systems.
* @remarks This function is useful for constructing portable file paths.
* The returned character should be used when manually building paths
* or when parsing path strings in a platform-aware manner.
* For portable path construction, consider using this function instead
* of hardcoding separator characters.
*/
DRANG_PLATFORM_API char drang_fs_path_separator(void);
/**
* @brief Opens a file with specified access mode.
*
* Opens or creates a file at the specified path with the given access mode.
* The file handle must be closed with drang_fs_close() when no longer needed.
*
* @param[in] path Path to the file to open.
* @param[in] mode File access mode flags (combination of drang_fs_mode_t values).
* @param[out] out_file Pointer to store the opened file handle.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - file opened successfully.
* @retval DRANG_EINVAL Invalid parameter (path or out_file is NULL).
* @retval DRANG_ENOENT File not found and create flag not set.
* @retval DRANG_EPERM Permission denied.
* @retval DRANG_ENOMEM Insufficient memory to allocate file handle.
* @retval DRANG_EIO I/O error occurred during file opening.
* @retval DRANG_EBUSY File is locked or in use by another process.
* @remarks The returned file handle must be freed with drang_fs_close().
* File path should use platform-appropriate path separators.
*/
DRANG_PLATFORM_API int drang_fs_open(const char *path, drang_fs_mode_t mode, drang_fs_file_t **out_file);
/**
* @brief Closes an open file handle.
*
* Closes the file handle and releases associated resources. Any pending
* writes are flushed before closing.
*
* @param[in] file File handle to close, or NULL.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - file closed successfully.
* @retval DRANG_EINVAL Invalid file handle.
* @retval DRANG_EIO I/O error occurred during file closing.
* @remarks Safe to call with NULL pointer. File handle becomes invalid after closing.
* Pending writes are automatically flushed before closing.
*/
DRANG_PLATFORM_API int drang_fs_close(drang_fs_file_t *file);
/**
* @brief Reads data from an open file.
*
* Reads up to count*size bytes from the file into the provided buffer.
* The actual number of bytes read is returned through bytes_read.
*
* @param[in] file Open file handle.
* @param[out] buffer Buffer to receive read data.
* @param[in] count Number of elements to read.
* @param[in] size Size of each element in bytes.
* @param[out] bytes_read Pointer to store actual bytes read (may be NULL).
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - data read successfully.
* @retval DRANG_EINVAL Invalid parameter (file or buffer is NULL).
* @retval DRANG_EIO I/O error occurred during reading.
* @retval DRANG_EPERM File not opened for reading.
* @remarks Reading beyond end of file is not an error - bytes_read will indicate
* actual bytes read. File position advances to bytes read.
*/
DRANG_PLATFORM_API int drang_fs_read(drang_fs_file_t *file, void *buffer, size_t count, size_t size, size_t *bytes_read);
/**
* @brief Writes data to an open file.
*
* Writes up to count*size bytes from the buffer to the file.
* The actual number of bytes written is returned through bytes_written.
*
* @param[in] file Open file handle.
* @param[in] buffer Buffer containing data to write.
* @param[in] count Number of elements to write.
* @param[in] size Size of each element in bytes.
* @param[out] bytes_written Pointer to store actual bytes written (may be NULL).
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - data written successfully.
* @retval DRANG_EINVAL Invalid parameter (file or buffer is NULL).
* @retval DRANG_EIO I/O error occurred during writing.
* @retval DRANG_EPERM File not opened for writing.
* @retval DRANG_ENOMEM Insufficient disk space.
* @remarks Data may be buffered - use drang_fs_flush() to ensure data is written.
* File position advances by bytes written.
*/
DRANG_PLATFORM_API int drang_fs_write(
drang_fs_file_t *file, const void *buffer, size_t count, size_t size, size_t *bytes_written);
/**
* @brief Sets the file position indicator.
*
* Changes the current file position to the specified offset relative
* to the given origin point.
*
* @param[in] file Open file handle.
* @param[in] offset Byte offset from the origin.
* @param[in] origin Reference point for the offset.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - file position set successfully.
* @retval DRANG_EINVAL Invalid parameter (file is NULL or invalid origin).
* @retval DRANG_EIO I/O error occurred during seek operation.
* @remarks Seeking beyond end of file is allowed and may extend the file on write.
* Use drang_fs_tell() to get the current position after seeking.
*/
DRANG_PLATFORM_API int drang_fs_seek(drang_fs_file_t *file, int64_t offset, drang_seek_origin_t origin);
/**
* @brief Gets the current file position.
*
* Returns the current byte offset of the file position indicator
* from the beginning of the file.
*
* @param[in] file Open file handle.
* @param[out] position Pointer to store the current file position.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - position retrieved successfully.
* @retval DRANG_EINVAL Invalid parameter (file or position is NULL).
* @retval DRANG_EIO I/O error occurred during position query.
* @remarks Position is always relative to the beginning of the file.
*/
DRANG_PLATFORM_API int drang_fs_tell(drang_fs_file_t *file, uint64_t *position);
/**
* @brief Flushes pending writes to storage.
*
* Forces any buffered write operations to be written to the underlying
* storage device. Does not guarantee data persistence across system crashes.
*
* @param[in] file Open file handle.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - data flushed successfully.
* @retval DRANG_EINVAL Invalid file handle.
* @retval DRANG_EIO I/O error occurred during flush operation.
* @remarks Does not guarantee data persistence - use platform-specific sync
* operations for crash-safe persistence.
*/
DRANG_PLATFORM_API int drang_fs_flush(drang_fs_file_t *file);
/**
* @brief Truncates or extends a file to specified size.
*
* Changes the size of the file to the specified length. If the file
* is extended, the new area is filled with zeros.
*
* @param[in] file Open file handle.
* @param[in] size New file size in bytes.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - file size changed successfully.
* @retval DRANG_EINVAL Invalid file handle.
* @retval DRANG_EIO I/O error occurred during truncation.
* @retval DRANG_EPERM File not opened for writing.
* @retval DRANG_ENOMEM Insufficient disk space for extension.
* @remarks If extending the file, new bytes are zero-filled.
* File position may be adjusted if beyond new file size.
*/
DRANG_PLATFORM_API int drang_fs_truncate(drang_fs_file_t *file, uint64_t size);
/**
* @brief Creates a new empty file.
*
* Creates a new file at the specified path with the given permissions.
* Fails if the file already exists.
*
* @param[in] path Path where the file should be created.
* @param[in] permissions Initial file permissions.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - file created successfully.
* @retval DRANG_EINVAL Invalid parameter (path is NULL).
* @retval DRANG_ENOENT Parent directory does not exist.
* @retval DRANG_EPERM Permission denied.
* @retval DRANG_EBUSY File already exists.
* @retval DRANG_ENOMEM Insufficient disk space.
* @retval DRANG_EIO I/O error occurred during creation.
* @remarks Use drang_fs_open() with create mode for more control over file creation.
* Created file is initially empty.
*/
DRANG_PLATFORM_API int drang_fs_create(const char *path, drang_fs_permissions_t permissions);
/**
* @brief Removes a file from the file system.
*
* Deletes the specified file from the file system. The file must not
* be currently open by any process.
*
* @param[in] path Path to the file to remove.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - file removed successfully.
* @retval DRANG_EINVAL Invalid parameter (path is NULL).
* @retval DRANG_ENOENT File does not exist.
* @retval DRANG_EPERM Permission denied or file is in use.
* @retval DRANG_EIO I/O error occurred during removal.
* @remarks File must not be open by any process. Use drang_fs_rmdir() for directories.
* This operation is typically not reversible.
*/
DRANG_PLATFORM_API int drang_fs_remove(const char *path);
/**
* @brief Moves or renames a file.
*
* Moves a file from the old path to the new path. This can be used
* for renaming or moving files within or across directories.
*
* @param[in] old_path Current path of the file.
* @param[in] new_path Destination path for the file.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - file moved successfully.
* @retval DRANG_EINVAL Invalid parameter (old_path or new_path is NULL).
* @retval DRANG_ENOENT Source file does not exist.
* @retval DRANG_EPERM Permission denied.
* @retval DRANG_EBUSY File is in use or destination exists.
* @retval DRANG_EIO I/O error occurred during move operation.
* @remarks Moving across different file systems may require copy+delete.
* Destination file is overwritten if it exists and permissions allow.
*/
DRANG_PLATFORM_API int drang_fs_move(const char *old_path, const char *new_path);
/**
* @brief Checks if a file or directory exists.
*
* Tests whether a file system entry exists at the specified path.
* This function does not distinguish between files and directories.
*
* @param[in] path Path to check for existence.
* @return true if the path exists, false otherwise.
* @remarks This function does not set error codes. Use drang_fs_stat()
* for more detailed information about file system entries.
* Result may be outdated immediately after return due to concurrent access.
*/
DRANG_PLATFORM_API bool drang_fs_exists(const char *path);
/**
* @brief Retrieves file system metadata.
*
* Gets comprehensive information about a file system entry including
* type, size, permissions, and timestamps.
*
* @param[in] path Path to the file system entry.
* @param[out] out_stat Pointer to structure to receive file information.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - file information retrieved successfully.
* @retval DRANG_EINVAL Invalid parameter (path or out_stat is NULL).
* @retval DRANG_ENOENT File does not exist.
* @retval DRANG_EPERM Permission denied.
* @retval DRANG_EIO I/O error occurred during stat operation.
* @remarks Provides more detailed information than drang_fs_exists().
* Timestamp format and precision may be platform-dependent.
*/
DRANG_PLATFORM_API int drang_fs_stat(const char *path, struct drang_fs_stat *out_stat);
/**
* @brief Sets file permissions.
*
* Changes the access permissions of the specified file or directory.
* The exact behavior may vary between platforms.
*
* @param[in] path Path to the file or directory.
* @param[in] permissions New permission flags.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - permissions set successfully.
* @retval DRANG_EINVAL Invalid parameter (path is NULL).
* @retval DRANG_ENOENT File does not exist.
* @retval DRANG_EPERM Permission denied.
* @retval DRANG_NOTSUP Operation not supported on this platform.
* @retval DRANG_EIO I/O error occurred during permission change.
* @remarks Permission semantics may vary between platforms (Unix vs Windows).
* Some permission combinations may not be supported on all platforms.
*/
DRANG_PLATFORM_API int drang_fs_set_permissions(const char *path, drang_fs_permissions_t permissions);
/**
* @brief Creates a new directory.
*
* Creates a directory at the specified path with the given permissions.
* Parent directories must already exist.
*
* @param[in] path Path where the directory should be created.
* @param[in] permissions Initial directory permissions.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - directory created successfully.
* @retval DRANG_EINVAL Invalid parameter (path is NULL).
* @retval DRANG_ENOENT Parent directory does not exist.
* @retval DRANG_EPERM Permission denied.
* @retval DRANG_EBUSY Directory already exists.
* @retval DRANG_ENOMEM Insufficient disk space.
* @retval DRANG_EIO I/O error occurred during creation.
* @remarks Parent directories must exist. Use recursive creation for nested paths.
* Directory permissions may be modified by system umask.
*/
DRANG_PLATFORM_API int drang_fs_mkdir(const char *path, drang_fs_permissions_t permissions);
/**
* @brief Removes an empty directory.
*
* Deletes the specified directory from the file system. The directory
* must be empty (contain no files or subdirectories).
*
* @param[in] path Path to the directory to remove.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - directory removed successfully.
* @retval DRANG_EINVAL Invalid parameter (path is NULL).
* @retval DRANG_ENOENT Directory does not exist.
* @retval DRANG_EPERM Permission denied.
* @retval DRANG_EBUSY Directory is not empty or in use.
* @retval DRANG_EIO I/O error occurred during removal.
* @remarks Directory must be empty before removal. Use drang_fs_remove() for files.
*/
DRANG_PLATFORM_API int drang_fs_rmdir(const char *path);
/**
* @brief Opens a directory for reading.
*
* Opens the specified directory for reading its entries. The directory
* handle must be closed with drang_fs_closedir() when no longer needed.
*
* @param[in] path Path to the directory to open.
* @param[out] out_directory Pointer to store the opened directory handle.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - directory opened successfully.
* @retval DRANG_EINVAL Invalid parameter (path or out_directory is NULL).
* @retval DRANG_ENOENT Directory does not exist.
* @retval DRANG_EPERM Permission denied.
* @retval DRANG_ENOMEM Insufficient memory to allocate directory handle.
* @retval DRANG_EIO I/O error occurred during directory opening.
* @remarks The returned directory handle must be freed with drang_fs_closedir().
* Use drang_fs_readdir() to iterate through directory entries.
*/
DRANG_PLATFORM_API int drang_fs_opendir(const char *path, drang_fs_directory_t **out_directory);
/**
* @brief Reads the next directory entry.
*
* Reads the next entry from an open directory. Returns information
* about the entry including name and type.
*
* @param[in] directory Open directory handle.
* @param[out] out_entry Pointer to structure to receive entry information.
* @return 0 on success, negative error code on failure or end of directory.
* @retval DRANG_EOK Success - entry read successfully.
* @retval DRANG_EINVAL Invalid parameter (directory or out_entry is NULL).
* @retval DRANG_ENOENT No more entries (end of directory reached).
* @retval DRANG_EIO I/O error occurred during reading.
* @remarks Returns DRANG_ENOENT when no more entries are available.
* Entry names "." and ".." will not be included.
*/
DRANG_PLATFORM_API int drang_fs_readdir(drang_fs_directory_t *directory, drang_fs_dir_entry_t *out_entry);
/**
* @brief Closes an open directory handle.
*
* Closes the directory handle and releases associated resources.
*
* @param[in] directory Directory handle to close, or NULL.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - directory closed successfully.
* @retval DRANG_EINVAL Invalid directory handle.
* @retval DRANG_EIO I/O error occurred during directory closing.
* @remarks Safe to call with NULL pointer. Directory handle becomes invalid after closing.
*/
DRANG_PLATFORM_API int drang_fs_closedir(drang_fs_directory_t *directory);
/**
* @brief Gets the current working directory.
*
* Retrieves the absolute path of the current working directory.
* The path is null-terminated and stored in the provided buffer.
*
* @param[out] buffer Buffer to receive the current working directory path.
* @param[in] size Size of the buffer in bytes.
* @param[out] out_size Pointer to store the actual path length (may be NULL).
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - current directory retrieved successfully.
* @retval DRANG_EINVAL Invalid parameter (buffer is NULL or size is 0).
* @retval DRANG_ENOMEM Buffer too small to hold the path.
* @retval DRANG_EIO I/O error occurred during path retrieval.
* @remarks If out_size is provided, it contains the required buffer size on DRANG_ENOMEM.
* The returned path is always absolute and null-terminated.
*/
DRANG_PLATFORM_API int drang_fs_get_cwd(char *buffer, size_t size, size_t *out_size);
/**
* @brief Sets the current working directory.
*
* Changes the current working directory to the specified path.
* Affects the process-wide current directory.
*
* @param[in] path Path to the new working directory.
* @return 0 on success, negative error code on failure.
* @retval DRANG_EOK Success - working directory changed successfully.
* @retval DRANG_EINVAL Invalid parameter (path is NULL).
* @retval DRANG_ENOENT Directory does not exist.
* @retval DRANG_EPERM Permission denied.
* @retval DRANG_EIO I/O error occurred during directory change.
* @remarks Changes the process-wide current working directory.
* Relative paths in subsequent operations will be relative to this directory.
*/
DRANG_PLATFORM_API int drang_fs_set_cwd(const char *path);
DRANG_END_DECLS

View file

@ -36,11 +36,14 @@
# define DRANG_ALLOC_ALIGN_ATTR(idx) __attribute__((alloc_align(idx)))
# define DRANG_MALLOC_ATTR __attribute__((malloc))
# define DRANG_BIT_CAST(_Expr_, _From_, _To_) __builtin_bit_cast(_To_, _Expr_)
# define DRANG_MUL_OVERFLOW(_A_, _B_, _Result_) (__builtin_mul_overflow(_A_, _B_, _Result_) != 0)
# define DRANG_ADD_OVERFLOW(_A_, _B_, _Result_) (__builtin_add_overflow(_A_, _B_, _Result_) != 0)
# define DRANG_SUB_OVERFLOW(_A_, _B_, _Result_) (__builtin_sub_overflow(_A_, _B_, _Result_) != 0)
#else
# define DRANG_PRINTF_ATTR(fmt_idx, arg_idx)
# define DRANG_VPRINTF_ATTR(fmt_idx)
# define DRANG_ALLOC_SIZE_COUNT_ATTR(idx, count)
# define DRANG_ALLOC_SIZE_ATTR(idx)
# define DRANG_ALLOC_ALIGN_ATTR(idx)
# define DRANG_MALLOC_ATTR
# error "Unsupported compiler, please implement attribute macros"
#endif
#define DRANG_UNUSED(x) ((void) (x))

View file

@ -24,6 +24,8 @@ int drang_error_to_errno(int error)
return ETIMEDOUT;
case DRANG_EBUSY:
return EBUSY;
case DRANG_EOVERFLOW:
return EOVERFLOW;
case DRANG_EPLATFORM:
default:
return EIO; // Generic I/O error for unknown platform errors
@ -50,6 +52,8 @@ int drang_errno_to_error(const int errnum)
return DRANG_ETIMEDOUT;
case EBUSY:
return DRANG_EBUSY;
case EOVERFLOW:
return DRANG_EOVERFLOW;
default:
return DRANG_EPLATFORM;
}

View file

@ -0,0 +1,158 @@
#include "drang/alloc.h"
#include "errno_convert.h"
#include "internal.h"
#include <drang/error.h>
#include <drang/fs.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
int drang_fs_mkdir(const char *path, drang_fs_permissions_t permissions)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
int mode = 0;
if (permissions & DRANG_FS_PERM_USER_READ)
mode |= S_IRUSR;
if (permissions & DRANG_FS_PERM_USER_WRITE)
mode |= S_IWUSR;
if (permissions & DRANG_FS_PERM_USER_EXEC)
mode |= S_IXUSR;
if (permissions & DRANG_FS_PERM_GROUP_READ)
mode |= S_IRGRP;
if (permissions & DRANG_FS_PERM_GROUP_WRITE)
mode |= S_IWGRP;
if (permissions & DRANG_FS_PERM_GROUP_EXEC)
mode |= S_IXGRP;
if (permissions & DRANG_FS_PERM_OTHER_READ)
mode |= S_IROTH;
if (permissions & DRANG_FS_PERM_OTHER_WRITE)
mode |= S_IWOTH;
if (permissions & DRANG_FS_PERM_OTHER_EXEC)
mode |= S_IXOTH;
if (mkdir(path, mode) != 0) {
DRANG_FAIL(drang_errno_to_error(errno));
}
DRANG_END_TRY_IGNORE()
}
int drang_fs_rmdir(const char *path)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
if (rmdir(path) != 0) {
DRANG_FAIL(drang_errno_to_error(errno));
}
DRANG_END_TRY_IGNORE()
}
int drang_fs_opendir(const char *path, drang_fs_directory_t **out_directory)
{
struct drang_fs_directory *fs_dir = NULL;
DIR *dir = NULL;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(out_directory, DRANG_EINVAL);
dir = opendir(path);
if (!dir) {
DRANG_FAIL(drang_errno_to_error(errno));
}
fs_dir = DRANG_TRY_ALLOC_T(struct drang_fs_directory);
fs_dir->dir = dir;
DRANG_RETURN_IN(out_directory, fs_dir);
DRANG_CATCH(_)
{
if (dir) {
closedir(dir);
}
drang_free(fs_dir);
}
DRANG_END_TRY()
}
int drang_fs_readdir(drang_fs_directory_t *directory, drang_fs_dir_entry_t *out_entry)
{
struct dirent *entry = NULL;
DRANG_BEGIN_TRY()
entry = readdir(directory->dir);
DRANG_CHECK(entry != NULL, DRANG_ENOENT);
while (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
entry = readdir(directory->dir);
DRANG_CHECK(entry != NULL, DRANG_ENOENT);
}
strncpy(out_entry->name, entry->d_name, DRANG_FS_MAX_REL_PATH_SIZE);
out_entry->name[DRANG_FS_MAX_REL_PATH_SIZE - 1] = '\0';
switch (entry->d_type) {
case DT_REG:
out_entry->type = drang_fs_type_file;
break;
case DT_DIR:
out_entry->type = drang_fs_type_directory;
break;
case DT_LNK:
out_entry->type = drang_fs_type_symlink;
break;
default:
out_entry->type = drang_fs_type_unknown;
break;
}
DRANG_END_TRY_IGNORE()
}
int drang_fs_closedir(drang_fs_directory_t *directory)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(directory, DRANG_EINVAL);
if (closedir(directory->dir) != 0) {
DRANG_FAIL(drang_errno_to_error(errno));
}
drang_free(directory);
DRANG_END_TRY_IGNORE()
}
int drang_fs_get_cwd(char *buffer, size_t size, size_t *out_size)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(buffer, DRANG_EINVAL);
if (getcwd(buffer, size) != NULL) {
DRANG_RETURN_IN(out_size, strlen(buffer));
}
if (errno == ERANGE) {
if (out_size) {
long required_size = pathconf(".", _PC_PATH_MAX);
*out_size = required_size;
}
DRANG_FAIL(DRANG_ENOMEM);
}
DRANG_FAIL(drang_errno_to_error(errno));
DRANG_END_TRY_IGNORE()
}
int drang_fs_set_cwd(const char *path)
{
DRANG_BEGIN_TRY()
if (chdir(path) != 0) {
DRANG_FAIL(drang_errno_to_error(errno));
}
DRANG_END_TRY_IGNORE()
}

View file

@ -0,0 +1,499 @@
#include "drang/alloc.h"
#include "internal.h"
#include <drang/error.h>
#include <errno.h>
#include <errno_convert.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
static int mode_to_open_flags(const drang_fs_mode_t mode)
{
int oflags = 0;
if (mode & drang_fs_mode_read && mode & drang_fs_mode_write) {
oflags |= O_RDWR;
} else if (mode & drang_fs_mode_read) {
oflags |= O_RDONLY;
} else if (mode & drang_fs_mode_write) {
oflags |= O_WRONLY;
}
if (mode & drang_fs_mode_append) {
oflags |= O_APPEND;
}
if (mode & drang_fs_mode_create) {
oflags |= O_CREAT;
}
if (mode & drang_fs_mode_truncate) {
oflags |= O_TRUNC;
}
if (mode & drang_fs_mode_exclusive) {
oflags |= O_EXCL;
}
if (oflags == 0)
return 0;
oflags |= O_LARGEFILE; // Ensure large file support
return oflags;
}
static int drang_permissions_to_linux(const drang_fs_permissions_t permissions)
{
int result = 0;
if (permissions & DRANG_FS_PERM_USER_READ)
result |= S_IRUSR;
if (permissions & DRANG_FS_PERM_USER_WRITE)
result |= S_IWUSR;
if (permissions & DRANG_FS_PERM_USER_EXEC)
result |= S_IXUSR;
if (permissions & DRANG_FS_PERM_GROUP_READ)
result |= S_IRGRP;
if (permissions & DRANG_FS_PERM_GROUP_WRITE)
result |= S_IWGRP;
if (permissions & DRANG_FS_PERM_GROUP_EXEC)
result |= S_IXGRP;
if (permissions & DRANG_FS_PERM_OTHER_READ)
result |= S_IROTH;
if (permissions & DRANG_FS_PERM_OTHER_WRITE)
result |= S_IWOTH;
if (permissions & DRANG_FS_PERM_OTHER_EXEC)
result |= S_IXOTH;
return result;
}
char drang_fs_separator(void)
{
return '/';
}
int drang_fs_open(const char *path, drang_fs_mode_t mode, drang_fs_file_t **out_file)
{
struct drang_fs_file *file = NULL;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(out_file, DRANG_EINVAL);
const int oflags = mode_to_open_flags(mode);
DRANG_CHECK(oflags != 0, DRANG_EINVAL);
file = DRANG_TRY_CALLOC_T(struct drang_fs_file);
DRANG_TRY(drang_rwlock_init(&file->lock));
const int fd = (oflags & O_CREAT) ? open(path, oflags, 0666) : open(path, oflags);
if (fd == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
file->fd = fd;
file->mode = mode;
DRANG_RETURN_IN(out_file, file);
DRANG_CATCH(_)
{
if (file != NULL) {
drang_rwlock_fini(&file->lock);
drang_free(file);
}
}
DRANG_END_TRY()
}
int drang_fs_close(drang_fs_file_t *file)
{
bool locked = false;
if (file == NULL)
return DRANG_EOK;
DRANG_BEGIN_TRY()
DRANG_TRY(drang_fs_flush(file));
DRANG_CHECK(file->fd >= 0, DRANG_EINVAL);
DRANG_TRY(drang_rwlock_wrlock(&file->lock));
locked = true;
const int result = close(file->fd);
if (result == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
drang_rwlock_wrunlock(&file->lock);
drang_rwlock_fini(&file->lock);
drang_free(file);
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_wrunlock(&file->lock);
}
}
DRANG_END_TRY()
}
// TODO: Implement buffering
int drang_fs_read(drang_fs_file_t *file, void *buffer, size_t count, size_t size, size_t *bytes_read)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(buffer, DRANG_EINVAL);
DRANG_CHECK(file->fd >= 0, DRANG_EINVAL);
DRANG_CHECK(file->mode & drang_fs_mode_read, DRANG_EPERM);
DRANG_CHECK(count > 0 && size > 0, DRANG_EINVAL);
size_t total_bytes = 0;
DRANG_CHECK(!DRANG_MUL_OVERFLOW(count, size, &total_bytes), DRANG_EOVERFLOW);
DRANG_TRY(drang_rwlock_rdlock(&file->lock));
locked = true;
const ssize_t res = read(file->fd, buffer, total_bytes);
if (res == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
if (bytes_read != NULL) {
*bytes_read = (size_t) res;
}
drang_rwlock_rdunlock(&file->lock);
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_rdunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_write(drang_fs_file_t *file, const void *buffer, size_t count, size_t size, size_t *bytes_written)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(buffer, DRANG_EINVAL);
DRANG_CHECK(file->fd >= 0, DRANG_EINVAL);
DRANG_CHECK(file->mode & (drang_fs_mode_write | drang_fs_mode_append), DRANG_EPERM);
DRANG_CHECK(count > 0 && size > 0, DRANG_EINVAL);
size_t total_bytes = 0;
DRANG_CHECK(!DRANG_MUL_OVERFLOW(count, size, &total_bytes), DRANG_EOVERFLOW);
DRANG_TRY(drang_rwlock_wrlock(&file->lock));
locked = true;
const ssize_t res = write(file->fd, buffer, total_bytes);
if (res == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
if (bytes_written != NULL) {
*bytes_written = (size_t) res;
}
drang_rwlock_wrunlock(&file->lock);
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_wrunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_seek(drang_fs_file_t *file, int64_t offset, drang_seek_origin_t origin)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_CHECK(file->fd >= 0, DRANG_EINVAL);
int whence = 0;
switch (origin) {
case drang_seek_origin_begin:
whence = SEEK_SET;
break;
case drang_seek_origin_current:
whence = SEEK_CUR;
break;
case drang_seek_origin_end:
whence = SEEK_END;
break;
default:
DRANG_FAIL(DRANG_EINVAL);
}
DRANG_TRY(drang_rwlock_wrlock(&file->lock));
locked = true;
const off_t res = lseek(file->fd, (off_t) offset, whence);
if (res == (off_t) -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
drang_rwlock_wrunlock(&file->lock);
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_wrunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_tell(drang_fs_file_t *file, uint64_t *position)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(position, DRANG_EINVAL);
DRANG_CHECK(file->fd >= 0, DRANG_EINVAL);
DRANG_TRY(drang_rwlock_rdlock(&file->lock));
locked = true;
const off_t res = lseek(file->fd, 0, SEEK_CUR);
if (res == (off_t) -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
*position = (uint64_t) res;
drang_rwlock_rdunlock(&file->lock);
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_rdunlock(&file->lock);
}
}
DRANG_END_TRY()
}
// TODO: Implement buffering
int drang_fs_flush(drang_fs_file_t *file)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_CHECK(file->fd >= 0, DRANG_EINVAL);
DRANG_TRY(drang_rwlock_wrlock(&file->lock));
locked = true;
const int res = fsync(file->fd);
if (res == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
drang_rwlock_wrunlock(&file->lock);
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_wrunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_truncate(drang_fs_file_t *file, uint64_t size)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_CHECK(file->fd >= 0, DRANG_EINVAL);
DRANG_CHECK(file->mode & drang_fs_mode_write, DRANG_EPERM);
DRANG_TRY(drang_rwlock_wrlock(&file->lock));
locked = true;
const int res = ftruncate(file->fd, (off_t) size);
if (res == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
drang_rwlock_wrunlock(&file->lock);
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_wrunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_create(const char *path, drang_fs_permissions_t permissions)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
const int linux_perms = drang_permissions_to_linux(permissions);
const int fd = open(path, O_CREAT | O_EXCL | O_WRONLY | O_LARGEFILE, linux_perms);
if (fd == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
const int res = close(fd);
if (res == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
DRANG_END_TRY_IGNORE()
}
int drang_fs_remove(const char *path)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
const int res = unlink(path);
if (res == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
DRANG_END_TRY_IGNORE()
}
int drang_fs_move(const char *old_path, const char *new_path)
{
int src_fd = -1;
int dst_fd = -1;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(old_path, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(new_path, DRANG_EINVAL);
if (rename(old_path, new_path) == 0) {
DRANG_RETURN();
}
if (errno == EXDEV) { // Cross-device link, need to copy+delete
struct stat src_stat = {0};
if (stat(old_path, &src_stat) == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
src_fd = open(old_path, O_RDONLY | O_LARGEFILE);
if (src_fd == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
dst_fd = open(new_path, O_CREAT | O_EXCL | O_WRONLY | O_LARGEFILE, src_stat.st_mode);
if (dst_fd == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
char buffer[DRANG_LINUX_FS_BUFFER_SIZE];
ssize_t bytes_read = 0;
while ((bytes_read = read(src_fd, buffer, sizeof(buffer))) > 0) {
const ssize_t write_res = write(dst_fd, buffer, (size_t) bytes_read);
if (write_res == -1 || write_res != bytes_read) {
DRANG_FAIL(drang_errno_to_error(errno));
}
}
if (bytes_read == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
if (close(src_fd) == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
src_fd = -1;
if (close(dst_fd) == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
dst_fd = -1;
if (unlink(old_path) == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
DRANG_RETURN();
}
DRANG_FAIL(drang_errno_to_error(errno));
DRANG_CATCH(_)
{
if (src_fd != -1) {
close(src_fd);
}
if (dst_fd != -1) {
close(dst_fd);
unlink(new_path);
}
}
DRANG_END_TRY()
}
bool drang_fs_exists(const char *path)
{
if (path == NULL) {
return false;
}
struct stat buffer;
return (stat(path, &buffer) == 0);
}
int drang_fs_stat(const char *path, struct drang_fs_stat *out_stat)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(out_stat, DRANG_EINVAL);
struct stat st;
if (stat(path, &st) == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
if (S_ISREG(st.st_mode)) {
out_stat->type = drang_fs_type_file;
} else if (S_ISDIR(st.st_mode)) {
out_stat->type = drang_fs_type_directory;
} else if (S_ISLNK(st.st_mode)) {
out_stat->type = drang_fs_type_symlink;
} else {
out_stat->type = drang_fs_type_unknown;
}
out_stat->size = (uint64_t) st.st_size;
out_stat->permissions = 0;
if (st.st_mode & S_IRUSR)
out_stat->permissions |= DRANG_FS_PERM_USER_READ;
if (st.st_mode & S_IWUSR)
out_stat->permissions |= DRANG_FS_PERM_USER_WRITE;
if (st.st_mode & S_IXUSR)
out_stat->permissions |= DRANG_FS_PERM_USER_EXEC;
if (st.st_mode & S_IRGRP)
out_stat->permissions |= DRANG_FS_PERM_GROUP_READ;
if (st.st_mode & S_IWGRP)
out_stat->permissions |= DRANG_FS_PERM_GROUP_WRITE;
if (st.st_mode & S_IXGRP)
out_stat->permissions |= DRANG_FS_PERM_GROUP_EXEC;
if (st.st_mode & S_IROTH)
out_stat->permissions |= DRANG_FS_PERM_OTHER_READ;
if (st.st_mode & S_IWOTH)
out_stat->permissions |= DRANG_FS_PERM_OTHER_WRITE;
if (st.st_mode & S_IXOTH)
out_stat->permissions |= DRANG_FS_PERM_OTHER_EXEC;
out_stat->created_time = (drang_fs_time_t) st.st_ctim.tv_sec * 1000000000 + st.st_ctim.tv_nsec;
out_stat->modified_time = (drang_fs_time_t) st.st_mtim.tv_sec * 1000000000 + st.st_mtim.tv_nsec;
out_stat->accessed_time = (drang_fs_time_t) st.st_atim.tv_sec * 1000000000 + st.st_atim.tv_nsec;
DRANG_END_TRY_IGNORE()
}
int drang_fs_set_permissions(const char *path, drang_fs_permissions_t permissions)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
const int linux_perms = drang_permissions_to_linux(permissions);
if (chmod(path, linux_perms) == -1) {
DRANG_FAIL(drang_errno_to_error(errno));
}
DRANG_END_TRY_IGNORE()
}

View file

@ -0,0 +1,18 @@
#pragma once
#include "linux/sync/internal.h"
#include <dirent.h>
#include <drang/fs.h>
#include <unistd.h>
#define DRANG_LINUX_FS_BUFFER_SIZE (4096)
struct drang_fs_file
{
int fd;
drang_fs_mode_t mode;
struct drang_rwlock lock;
};
struct drang_fs_directory
{
DIR *dir;
};

View file

@ -0,0 +1,35 @@
#include "common.h"
#include <drang/error.h>
int win32_error_to_drang(const DWORD error)
{
switch (error) {
case ERROR_SUCCESS:
return DRANG_EOK;
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
case ERROR_INVALID_DRIVE:
return DRANG_ENOENT;
case ERROR_ACCESS_DENIED:
case ERROR_SHARING_VIOLATION:
case ERROR_LOCK_VIOLATION:
return DRANG_EPERM;
case ERROR_ALREADY_EXISTS:
case ERROR_FILE_EXISTS:
return DRANG_EBUSY;
case ERROR_NOT_ENOUGH_MEMORY:
case ERROR_OUTOFMEMORY:
return DRANG_ENOMEM;
case ERROR_DISK_FULL:
return DRANG_ENOSPC;
case ERROR_INVALID_PARAMETER:
return DRANG_EINVAL;
case ERROR_WRITE_PROTECT:
return DRANG_EROFS;
case ERROR_IO_DEVICE:
case ERROR_IO_PENDING:
return DRANG_EIO;
case ERROR_CRC:
default:
return DRANG_EPLATFORM; // Unknown or unhandled error
}
}

View file

@ -0,0 +1,10 @@
#pragma once
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <drang/platform.h>
DRANG_BEGIN_DECLS
DRANG_PLATFORM_API int win32_error_to_drang(DWORD error);
DRANG_END_DECLS

View file

@ -0,0 +1,154 @@
#include "internal.h"
#include "win32/common.h"
#include <drang/alloc.h>
#include <drang/error.h>
#include <drang/fs.h>
#include <stdio.h>
int drang_fs_mkdir(const char *path, drang_fs_permissions_t permissions)
{
DRANG_BEGIN_TRY()
DRANG_UNUSED(permissions); // Permissions are not used on Windows
if (!CreateDirectoryA(path, NULL)) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
DRANG_END_TRY_IGNORE()
}
int drang_fs_rmdir(const char *path)
{
DRANG_BEGIN_TRY()
if (!RemoveDirectoryA(path)) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
DRANG_END_TRY_IGNORE()
}
int drang_fs_opendir(const char *path, drang_fs_directory_t **out_directory)
{
struct drang_fs_directory *fs_dir = NULL;
char search_path[MAX_PATH];
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(out_directory, DRANG_EINVAL);
fs_dir = DRANG_TRY_CALLOC_T(struct drang_fs_directory);
snprintf(search_path, MAX_PATH, "%s\\*", path);
fs_dir->handle = FindFirstFileA(search_path, &fs_dir->find_data);
if (fs_dir->handle == INVALID_HANDLE_VALUE) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
fs_dir->first_entry = true;
DRANG_RETURN_IN(out_directory, fs_dir);
DRANG_CATCH(_)
{
if (fs_dir) {
if (fs_dir->handle != INVALID_HANDLE_VALUE) {
FindClose(fs_dir->handle);
}
drang_free(fs_dir);
}
}
DRANG_END_TRY()
}
static int skip_invalid_entries(struct drang_fs_directory *dir)
{
DRANG_BEGIN_TRY()
while (strcmp(dir->find_data.cFileName, ".") == 0 || strcmp(dir->find_data.cFileName, "..") == 0) {
if (!FindNextFileA(dir->handle, &dir->find_data)) {
const DWORD err = GetLastError();
if (err == ERROR_NO_MORE_FILES) {
DRANG_FAIL(DRANG_ENOENT);
}
DRANG_FAIL(win32_error_to_drang(err));
}
}
DRANG_END_TRY_IGNORE()
}
int drang_fs_readdir(drang_fs_directory_t *directory, drang_fs_dir_entry_t *out_entry)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(directory, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(out_entry, DRANG_EINVAL);
if (directory->first_entry) {
directory->first_entry = false;
DRANG_TRY(skip_invalid_entries(directory));
} else {
if (!FindNextFileA(directory->handle, &directory->find_data)) {
const DWORD err = GetLastError();
if (err == ERROR_NO_MORE_FILES) {
DRANG_FAIL(DRANG_ENOENT);
}
DRANG_FAIL(win32_error_to_drang(err));
}
DRANG_TRY(skip_invalid_entries(directory));
}
strncpy(out_entry->name, directory->find_data.cFileName, DRANG_FS_MAX_REL_PATH_SIZE);
out_entry->name[DRANG_FS_MAX_REL_PATH_SIZE - 1] = '\0';
if (directory->find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
out_entry->type = drang_fs_type_directory;
} else if (directory->find_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
out_entry->type = drang_fs_type_symlink;
} else {
out_entry->type = drang_fs_type_file;
}
DRANG_END_TRY_IGNORE()
}
int drang_fs_closedir(drang_fs_directory_t *directory)
{
DRANG_BEGIN_TRY()
if (directory) {
if (directory->handle != INVALID_HANDLE_VALUE) {
if (!FindClose(directory->handle)) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
}
drang_free(directory);
}
DRANG_END_TRY_IGNORE()
}
int drang_fs_get_cwd(char *buffer, size_t size, size_t *out_size)
{
DWORD length = 0;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(buffer, DRANG_EINVAL);
length = GetCurrentDirectoryA((DWORD) size, buffer);
if (length == 0) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
if (out_size) {
*out_size = length;
}
DRANG_CHECK(length < size, DRANG_ENOMEM);
DRANG_END_TRY_IGNORE()
}
int drang_fs_set_cwd(const char *path)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
if (!SetCurrentDirectoryA(path)) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
DRANG_END_TRY_IGNORE()
}

View file

@ -0,0 +1,442 @@
#include "drang/sync.h"
#include "internal.h"
#include "win32/common.h"
#include <drang/alloc.h>
#include <drang/error.h>
static void mode_to_access(drang_fs_mode_t mode, DWORD *out_access, DWORD *out_creation)
{
DWORD access = 0;
DWORD creation = 0;
if (mode & drang_fs_mode_read) {
access |= GENERIC_READ;
}
if (mode & drang_fs_mode_write) {
access |= GENERIC_WRITE;
}
if (mode & drang_fs_mode_append) {
access |= FILE_APPEND_DATA;
}
if (mode & drang_fs_mode_create) {
if (mode & drang_fs_mode_exclusive) {
creation = CREATE_NEW;
} else {
creation = OPEN_ALWAYS;
}
} else if (mode & drang_fs_mode_truncate) {
creation = TRUNCATE_EXISTING;
} else {
creation = OPEN_EXISTING;
}
*out_access = access;
*out_creation = creation;
}
char drang_fs_path_separator(void)
{
return '\\';
}
int drang_fs_open(const char *path, drang_fs_mode_t mode, drang_fs_file_t **out_file)
{
struct drang_fs_file *file = NULL;
HANDLE handle = INVALID_HANDLE_VALUE;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(out_file, DRANG_EINVAL);
DWORD access = 0;
DWORD creation = 0;
mode_to_access(mode, &access, &creation);
handle = CreateFileA(path,
access,
0, //TODO: Implement sharing
NULL,
creation,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (handle == INVALID_HANDLE_VALUE) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
file = DRANG_TRY_CALLOC_T(struct drang_fs_file);
file->handle = handle;
file->mode = mode;
DRANG_TRY(drang_rwlock_init(&file->lock));
*out_file = file;
DRANG_CATCH(_)
{
if (handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
}
if (file) {
drang_rwlock_fini(&file->lock);
drang_free(file);
}
}
DRANG_END_TRY()
}
int drang_fs_close(drang_fs_file_t *file)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_TRY(drang_rwlock_wrlock(&file->lock));
locked = true;
if (file->handle != INVALID_HANDLE_VALUE) {
DRANG_CHECK(CloseHandle(file->handle), win32_error_to_drang(GetLastError()));
file->handle = INVALID_HANDLE_VALUE;
}
drang_rwlock_fini(&file->lock);
locked = false;
drang_free(file);
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_wrunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_read(drang_fs_file_t *file, void *buffer, size_t count, size_t size, size_t *bytes_read)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(buffer, DRANG_EINVAL);
if (count == 0 || size == 0) {
if (bytes_read)
*bytes_read = 0;
DRANG_RETURN();
}
DRANG_CHECK(file->mode & drang_fs_mode_read, DRANG_EPERM);
DRANG_TRY(drang_rwlock_rdlock(&file->lock));
locked = true;
DWORD total_bytes = 0;
DRANG_CHECK(!DRANG_MUL_OVERFLOW(count, size, &total_bytes), DRANG_EOVERFLOW);
DWORD read_bytes = 0;
if (!ReadFile(file->handle, buffer, total_bytes, &read_bytes, NULL)) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
if (bytes_read) {
*bytes_read = (size_t) read_bytes;
}
drang_rwlock_rdunlock(&file->lock);
locked = false;
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_rdunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_write(drang_fs_file_t *file, const void *buffer, size_t count, size_t size, size_t *bytes_written)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(buffer, DRANG_EINVAL);
if (count == 0 || size == 0) {
if (bytes_written)
*bytes_written = 0;
DRANG_RETURN();
}
DRANG_CHECK(file->mode & (drang_fs_mode_write | drang_fs_mode_append), DRANG_EPERM);
DRANG_TRY(drang_rwlock_wrlock(&file->lock));
locked = true;
DWORD total_bytes = 0;
DRANG_CHECK(!DRANG_MUL_OVERFLOW(count, size, &total_bytes), DRANG_EOVERFLOW);
if (file->mode & drang_fs_mode_append) {
const LARGE_INTEGER zero = {0};
if (!SetFilePointerEx(file->handle, zero, NULL, FILE_END)) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
}
DWORD written_bytes = 0;
if (!WriteFile(file->handle, buffer, total_bytes, &written_bytes, NULL)) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
if (bytes_written) {
*bytes_written = (size_t) written_bytes;
}
drang_rwlock_wrunlock(&file->lock);
locked = false;
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_wrunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_seek(drang_fs_file_t *file, int64_t offset, drang_seek_origin_t origin)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_TRY(drang_rwlock_wrlock(&file->lock));
locked = true;
DWORD move_method = 0;
switch (origin) {
case drang_seek_origin_begin:
move_method = FILE_BEGIN;
break;
case drang_seek_origin_current:
move_method = FILE_CURRENT;
break;
case drang_seek_origin_end:
move_method = FILE_END;
break;
}
LARGE_INTEGER li;
li.QuadPart = offset;
if (!SetFilePointerEx(file->handle, li, NULL, move_method)) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
drang_rwlock_wrunlock(&file->lock);
locked = false;
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_wrunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_tell(drang_fs_file_t *file, uint64_t *position)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(position, DRANG_EINVAL);
DRANG_TRY(drang_rwlock_rdlock(&file->lock));
locked = true;
LARGE_INTEGER zero = {0};
LARGE_INTEGER pos = {0};
if (!SetFilePointerEx(file->handle, zero, &pos, FILE_CURRENT)) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
*position = (uint64_t) pos.QuadPart;
drang_rwlock_rdunlock(&file->lock);
locked = false;
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_rdunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_flush(drang_fs_file_t *file)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_CHECK(file->mode & (drang_fs_mode_write | drang_fs_mode_append), DRANG_EPERM);
DRANG_TRY(drang_rwlock_wrlock(&file->lock));
locked = true;
if (!FlushFileBuffers(file->handle)) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
drang_rwlock_wrunlock(&file->lock);
locked = false;
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_wrunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_truncate(drang_fs_file_t *file, uint64_t size)
{
bool locked = false;
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(file, DRANG_EINVAL);
DRANG_CHECK(file->mode & drang_fs_mode_write, DRANG_EPERM);
DRANG_TRY(drang_rwlock_wrlock(&file->lock));
locked = true;
LARGE_INTEGER li;
li.QuadPart = (LONGLONG) size;
if (!SetFilePointerEx(file->handle, li, NULL, FILE_BEGIN)) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
if (!SetEndOfFile(file->handle)) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
drang_rwlock_wrunlock(&file->lock);
locked = false;
DRANG_CATCH(_)
{
if (locked) {
drang_rwlock_wrunlock(&file->lock);
}
}
DRANG_END_TRY()
}
int drang_fs_create(const char *path, drang_fs_permissions_t permissions)
{
HANDLE handle = INVALID_HANDLE_VALUE;
DRANG_UNUSED(permissions);
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
// Create the file with CREATE_NEW to fail if it already exists
handle = CreateFileA(path,
GENERIC_WRITE, // Need write access to create
0, // No sharing during creation
NULL, // Default security attributes
CREATE_NEW, // Fail if file already exists
FILE_ATTRIBUTE_NORMAL, // Normal file attributes
NULL); // No template file
DRANG_CHECK(handle != INVALID_HANDLE_VALUE, win32_error_to_drang(GetLastError()));
// Close the file handle immediately since we're just creating an empty file
CloseHandle(handle);
// Note: Windows doesn't support Unix-style permissions directly.
// The permissions parameter is ignored on Windows as documented behavior
// may vary between platforms. For a full implementation, we could use
// SetFileSecurity() with ACLs, but that's complex and not typically needed.
DRANG_CATCH(_)
{
if (handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
}
}
DRANG_END_TRY()
}
int drang_fs_remove(const char *path)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
if (!DeleteFileA(path)) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
// No cleanup needed for this function
DRANG_END_TRY_IGNORE()
}
int drang_fs_move(const char *old_path, const char *new_path)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(old_path, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(new_path, DRANG_EINVAL);
if (!MoveFileA(old_path, new_path)) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
DRANG_END_TRY_IGNORE()
}
bool drang_fs_exists(const char *path)
{
if (path == NULL)
return false;
const DWORD attrs = GetFileAttributesA(path);
if (attrs == INVALID_FILE_ATTRIBUTES) {
false;
}
return true;
}
int drang_fs_stat(const char *path, struct drang_fs_stat *out_stat)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
DRANG_FAIL_IF_NULL(out_stat, DRANG_EINVAL);
WIN32_FILE_ATTRIBUTE_DATA file_data;
if (!GetFileAttributesExA(path, GetFileExInfoStandard, &file_data)) {
DRANG_FAIL(win32_error_to_drang(GetLastError()));
}
// Determine file type
if (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
out_stat->type = drang_fs_type_directory;
out_stat->size = 0; // Directories have no size
} else if (file_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
// This might be a symlink, but we need to check further
out_stat->type = drang_fs_type_symlink;
out_stat->size = ((uint64_t) file_data.nFileSizeHigh << 32) | file_data.nFileSizeLow;
} else {
out_stat->type = drang_fs_type_file;
out_stat->size = ((uint64_t) file_data.nFileSizeHigh << 32) | file_data.nFileSizeLow;
}
// Convert Windows permissions to Unix-style permissions
// Windows doesn't have the same permission model, so we'll approximate
out_stat->permissions = DRANG_FS_PERM_USER_READ | DRANG_FS_PERM_GROUP_READ | DRANG_FS_PERM_OTHER_READ;
if (!(file_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) {
out_stat->permissions |= DRANG_FS_PERM_USER_WRITE | DRANG_FS_PERM_GROUP_WRITE | DRANG_FS_PERM_OTHER_WRITE;
}
// For directories and executables, add execute permissions
if ((file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|| (strlen(path) > 4
&& (_stricmp(path + strlen(path) - 4, ".exe") == 0 || _stricmp(path + strlen(path) - 4, ".bat") == 0
|| _stricmp(path + strlen(path) - 4, ".cmd") == 0))) {
out_stat->permissions |= DRANG_FS_PERM_USER_EXEC | DRANG_FS_PERM_GROUP_EXEC | DRANG_FS_PERM_OTHER_EXEC;
}
// Convert FILETIME to Unix timestamp (seconds since epoch)
// Windows FILETIME is 100-nanosecond intervals since January 1, 1601
// Unix timestamp is seconds since January 1, 1970
const uint64_t WINDOWS_EPOCH_DIFF = 11644473600ULL; // Seconds between 1601 and 1970
// Convert creation time
uint64_t created_100ns = ((uint64_t) file_data.ftCreationTime.dwHighDateTime << 32)
| file_data.ftCreationTime.dwLowDateTime;
out_stat->created_time = (created_100ns / 10000000ULL) - WINDOWS_EPOCH_DIFF;
// Convert modification time
uint64_t modified_100ns = ((uint64_t) file_data.ftLastWriteTime.dwHighDateTime << 32)
| file_data.ftLastWriteTime.dwLowDateTime;
out_stat->modified_time = (modified_100ns / 10000000ULL) - WINDOWS_EPOCH_DIFF;
// Convert access time
uint64_t accessed_100ns = ((uint64_t) file_data.ftLastAccessTime.dwHighDateTime << 32)
| file_data.ftLastAccessTime.dwLowDateTime;
out_stat->accessed_time = (accessed_100ns / 10000000ULL) - WINDOWS_EPOCH_DIFF;
DRANG_END_TRY_IGNORE()
}
int drang_fs_set_permissions(const char *path, drang_fs_permissions_t permissions)
{
DRANG_BEGIN_TRY()
DRANG_FAIL_IF_NULL(path, DRANG_EINVAL);
// Windows doesn't support Unix-style permissions directly.
// The permissions parameter is ignored on Windows as documented behavior
// may vary between platforms. For a full implementation, we could use
// SetFileSecurity() with ACLs, but that's complex and not typically needed.
DRANG_UNUSED(permissions);
DRANG_END_TRY_IGNORE()
}

View file

@ -0,0 +1,20 @@
#pragma once
#include <drang/fs.h>
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include "../sync/internal.h"
struct drang_fs_file
{
HANDLE handle;
drang_fs_mode_t mode;
struct drang_rwlock lock;
};
struct drang_fs_directory
{
HANDLE handle;
WIN32_FIND_DATAA find_data;
bool first_entry;
};