diff --git a/.gitignore b/.gitignore index 0796bd3..932a2e4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,6 @@ build-*/ CMakeLists.txt.user # CMake -CMakeUserPresets.json \ No newline at end of file +CMakeUserPresets.json + +Playground/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 1760c94..a411444 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() diff --git a/Source/DrangPlatform/Include/drang/alloc.h b/Source/DrangPlatform/Include/drang/alloc.h index 2390210..4ae175b 100644 --- a/Source/DrangPlatform/Include/drang/alloc.h +++ b/Source/DrangPlatform/Include/drang/alloc.h @@ -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. * diff --git a/Source/DrangPlatform/Include/drang/endianness.h b/Source/DrangPlatform/Include/drang/endianness.h new file mode 100644 index 0000000..572a93d --- /dev/null +++ b/Source/DrangPlatform/Include/drang/endianness.h @@ -0,0 +1,139 @@ +#pragma once +#include "platform.h" +#include +#if !defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) +# ifdef __has_include +# if __has_include() +# include // gnu libc normally provides, linux +# elif __has_include() +# include //open bsd, macos +# elif __has_include() +# include // mingw, some bsd (not open/macos) +# elif __has_include() +# include // 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 +# 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 diff --git a/Source/DrangPlatform/Include/drang/error.h b/Source/DrangPlatform/Include/drang/error.h index 6100d59..754cc27 100644 --- a/Source/DrangPlatform/Include/drang/error.h +++ b/Source/DrangPlatform/Include/drang/error.h @@ -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 /** diff --git a/Source/DrangPlatform/Include/drang/fs.h b/Source/DrangPlatform/Include/drang/fs.h new file mode 100644 index 0000000..43753f3 --- /dev/null +++ b/Source/DrangPlatform/Include/drang/fs.h @@ -0,0 +1,543 @@ +#pragma once +#include "platform.h" +#include +#include +#include + +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 diff --git a/Source/DrangPlatform/Include/drang/platform.h b/Source/DrangPlatform/Include/drang/platform.h index fcaca42..239199b 100644 --- a/Source/DrangPlatform/Include/drang/platform.h +++ b/Source/DrangPlatform/Include/drang/platform.h @@ -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)) diff --git a/Source/DrangPlatform/Source/errno_convert.c b/Source/DrangPlatform/Source/errno_convert.c index 0ec599f..c993411 100644 --- a/Source/DrangPlatform/Source/errno_convert.c +++ b/Source/DrangPlatform/Source/errno_convert.c @@ -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; } diff --git a/Source/DrangPlatform/Source/linux/fs/dir.c b/Source/DrangPlatform/Source/linux/fs/dir.c new file mode 100644 index 0000000..664d18f --- /dev/null +++ b/Source/DrangPlatform/Source/linux/fs/dir.c @@ -0,0 +1,158 @@ +#include "drang/alloc.h" +#include "errno_convert.h" +#include "internal.h" +#include +#include +#include +#include +#include +#include + +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() +} diff --git a/Source/DrangPlatform/Source/linux/fs/file.c b/Source/DrangPlatform/Source/linux/fs/file.c new file mode 100644 index 0000000..17211cb --- /dev/null +++ b/Source/DrangPlatform/Source/linux/fs/file.c @@ -0,0 +1,499 @@ + +#include "drang/alloc.h" +#include "internal.h" +#include +#include +#include +#include +#include +#include + +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() +} diff --git a/Source/DrangPlatform/Source/linux/fs/internal.h b/Source/DrangPlatform/Source/linux/fs/internal.h new file mode 100644 index 0000000..e0ac8f2 --- /dev/null +++ b/Source/DrangPlatform/Source/linux/fs/internal.h @@ -0,0 +1,18 @@ +#pragma once +#include "linux/sync/internal.h" +#include +#include +#include +#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; +}; diff --git a/Source/DrangPlatform/Source/win32/common.c b/Source/DrangPlatform/Source/win32/common.c new file mode 100644 index 0000000..ba1e89a --- /dev/null +++ b/Source/DrangPlatform/Source/win32/common.c @@ -0,0 +1,35 @@ +#include "common.h" +#include +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 + } +} diff --git a/Source/DrangPlatform/Source/win32/common.h b/Source/DrangPlatform/Source/win32/common.h new file mode 100644 index 0000000..f3370c8 --- /dev/null +++ b/Source/DrangPlatform/Source/win32/common.h @@ -0,0 +1,10 @@ +#pragma once +#define WIN32_LEAN_AND_MEAN +#include +#include + +DRANG_BEGIN_DECLS + +DRANG_PLATFORM_API int win32_error_to_drang(DWORD error); + +DRANG_END_DECLS diff --git a/Source/DrangPlatform/Source/win32/fs/dir.c b/Source/DrangPlatform/Source/win32/fs/dir.c new file mode 100644 index 0000000..1f4f069 --- /dev/null +++ b/Source/DrangPlatform/Source/win32/fs/dir.c @@ -0,0 +1,154 @@ +#include "internal.h" +#include "win32/common.h" +#include +#include +#include +#include + +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() +} diff --git a/Source/DrangPlatform/Source/win32/fs/file.c b/Source/DrangPlatform/Source/win32/fs/file.c new file mode 100644 index 0000000..7c50442 --- /dev/null +++ b/Source/DrangPlatform/Source/win32/fs/file.c @@ -0,0 +1,442 @@ +#include "drang/sync.h" +#include "internal.h" +#include "win32/common.h" +#include +#include + +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() +} diff --git a/Source/DrangPlatform/Source/win32/fs/internal.h b/Source/DrangPlatform/Source/win32/fs/internal.h new file mode 100644 index 0000000..fde1e26 --- /dev/null +++ b/Source/DrangPlatform/Source/win32/fs/internal.h @@ -0,0 +1,20 @@ +#pragma once +#include +#define WIN32_LEAN_AND_MEAN +#include + +#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; +};