From 18c7623a687ea3f264a0b66aeccb73fc1fb6f0c4 Mon Sep 17 00:00:00 2001 From: MechSlayer <0jcrespo1996@gmail.com> Date: Fri, 5 Sep 2025 19:45:10 +0200 Subject: [PATCH 01/13] feature: add file system API with open, read, write, and directory operations --- Source/DrangPlatform/Include/drang/fs.h | 661 ++++++++++++++++++++++++ 1 file changed, 661 insertions(+) create mode 100644 Source/DrangPlatform/Include/drang/fs.h diff --git a/Source/DrangPlatform/Include/drang/fs.h b/Source/DrangPlatform/Include/drang/fs.h new file mode 100644 index 0000000..b212903 --- /dev/null +++ b/Source/DrangPlatform/Include/drang/fs.h @@ -0,0 +1,661 @@ +#pragma once +#include "platform.h" +#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 file timestamps as 64-bit values. The specific format + * and epoch may be platform-dependent. + */ +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 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 by 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); + +/** + * @brief Joins two path components. + * + * Combines two path components into a single path using the appropriate + * platform-specific path separator. + * + * @param[out] result Buffer to store the joined path. + * @param[in] result_size Size of the result buffer in bytes. + * @param[in] path1 First path component. + * @param[in] path2 Second path component. + * @param[out] out_size Pointer to store the actual result length (may be NULL). + * @return 0 on success, negative error code on failure. + * @retval DRANG_EOK Success - paths joined successfully. + * @retval DRANG_EINVAL Invalid parameter (result, path1, or path2 is NULL). + * @retval DRANG_ENOMEM Result buffer too small. + * @remarks Uses platform-appropriate path separators. If out_size is provided, + * it contains the required buffer size on DRANG_ENOMEM. + * The result is always null-terminated. + */ +DRANG_PLATFORM_API int drang_fs_path_join( + char *result, size_t result_size, const char *path1, const char *path2, size_t *out_size); + +/** + * @brief Joins two path components with explicit lengths. + * + * Combines two path components into a single path using the appropriate + * platform-specific path separator. Path components are specified with + * explicit lengths rather than being null-terminated. + * + * @param[out] result Buffer to store the joined path. + * @param[in] result_size Size of the result buffer in bytes. + * @param[in] path1 First path component. + * @param[in] path1_length Length of the first path component. + * @param[in] path2 Second path component. + * @param[in] path2_length Length of the second path component. + * @param[out] out_size Pointer to store the actual result length (may be NULL). + * @return 0 on success, negative error code on failure. + * @retval DRANG_EOK Success - paths joined successfully. + * @retval DRANG_EINVAL Invalid parameter (result, path1, or path2 is NULL). + * @retval DRANG_ENOMEM Result buffer too small. + * @remarks Useful when working with non-null-terminated path strings. + * The result is always null-terminated. + */ +DRANG_PLATFORM_API int drang_fs_path_join_length(char *result, + size_t result_size, + const char *path1, + size_t path1_length, + const char *path2, + size_t path2_length, + size_t *out_size); + +/** + * @brief Normalizes a file path. + * + * Resolves relative path components (. and ..) and removes redundant + * separators to produce a canonical path representation. + * + * @param[out] result Buffer to store the normalized path. + * @param[in] result_size Size of the result buffer in bytes. + * @param[in] path Path to normalize. + * @param[out] out_size Pointer to store the actual result length (may be NULL). + * @return 0 on success, negative error code on failure. + * @retval DRANG_EOK Success - path normalized successfully. + * @retval DRANG_EINVAL Invalid parameter (result or path is NULL). + * @retval DRANG_ENOMEM Result buffer too small. + * @remarks Resolves . and .. components and removes duplicate separators. + * Does not resolve symbolic links. The result is always null-terminated. + */ +DRANG_PLATFORM_API int drang_fs_path_normalize(char *result, size_t result_size, const char *path, size_t *out_size); + +/** + * @brief Normalizes a file path with explicit length. + * + * Resolves relative path components (. and ..) and removes redundant + * separators to produce a canonical path representation. The input path + * is specified with an explicit length rather than being null-terminated. + * + * @param[out] result Buffer to store the normalized path. + * @param[in] result_size Size of the result buffer in bytes. + * @param[in] path Path to normalize. + * @param[in] path_length Length of the input path. + * @param[out] out_size Pointer to store the actual result length (may be NULL). + * @return 0 on success, negative error code on failure. + * @retval DRANG_EOK Success - path normalized successfully. + * @retval DRANG_EINVAL Invalid parameter (result or path is NULL). + * @retval DRANG_ENOMEM Result buffer too small. + * @remarks Useful when working with non-null-terminated path strings. + * The result is always null-terminated. + */ +DRANG_PLATFORM_API int drang_fs_path_normalize_length( + char *result, size_t result_size, const char *path, size_t path_length, size_t *out_size); + +/** + * @brief Converts a path to absolute form. + * + * Converts a relative or absolute path to a fully qualified absolute path. + * Relative paths are resolved against the current working directory. + * + * @param[out] result Buffer to store the absolute path. + * @param[in] result_size Size of the result buffer in bytes. + * @param[in] path Path to convert to absolute form. + * @param[out] out_size Pointer to store the actual result length (may be NULL). + * @return 0 on success, negative error code on failure. + * @retval DRANG_EOK Success - absolute path created successfully. + * @retval DRANG_EINVAL Invalid parameter (result or path is NULL). + * @retval DRANG_ENOMEM Result buffer too small. + * @retval DRANG_EIO I/O error occurred while resolving current directory. + * @remarks Relative paths are resolved against the current working directory. + * The result is always null-terminated and fully qualified. + */ +DRANG_PLATFORM_API int drang_fs_path_absolute(char *result, size_t result_size, const char *path, size_t *out_size); + +/** + * @brief Converts a path to absolute form with explicit length. + * + * Converts a relative or absolute path to a fully qualified absolute path. + * The input path is specified with an explicit length rather than being + * null-terminated. Relative paths are resolved against the current working directory. + * + * @param[out] result Buffer to store the absolute path. + * @param[in] result_size Size of the result buffer in bytes. + * @param[in] path Path to convert to absolute form. + * @param[in] path_length Length of the input path. + * @param[out] out_size Pointer to store the actual result length (may be NULL). + * @return 0 on success, negative error code on failure. + * @retval DRANG_EOK Success - absolute path created successfully. + * @retval DRANG_EINVAL Invalid parameter (result or path is NULL). + * @retval DRANG_ENOMEM Result buffer too small. + * @retval DRANG_EIO I/O error occurred while resolving current directory. + * @remarks Useful when working with non-null-terminated path strings. + * The result is always null-terminated and fully qualified. + */ +DRANG_PLATFORM_API int drang_fs_path_absolute_length( + char *result, size_t result_size, const char *path, size_t path_length, size_t *out_size); + +DRANG_END_DECLS From 4f3ae943dba6bafedad3e70efdd040b81e73faa2 Mon Sep 17 00:00:00 2001 From: MechSlayer <0jcrespo1996@gmail.com> Date: Sat, 6 Sep 2025 01:25:14 +0200 Subject: [PATCH 02/13] feature: add DRANG_BIT_CAST macro for type-safe bit casting --- Source/DrangPlatform/Include/drang/platform.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Source/DrangPlatform/Include/drang/platform.h b/Source/DrangPlatform/Include/drang/platform.h index fcaca42..d162d0b 100644 --- a/Source/DrangPlatform/Include/drang/platform.h +++ b/Source/DrangPlatform/Include/drang/platform.h @@ -36,11 +36,9 @@ # 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_) + #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 From f3d307c03ef85e64f630b1188e00ca87ae6c60e6 Mon Sep 17 00:00:00 2001 From: MechSlayer <0jcrespo1996@gmail.com> Date: Tue, 9 Sep 2025 03:57:18 +0200 Subject: [PATCH 03/13] feature: add zero-initialized memory allocation macros and error handling for overflow --- .gitignore | 4 +- CMakeLists.txt | 4 ++ Source/DrangPlatform/Include/drang/alloc.h | 37 +++++++++++++++++++ Source/DrangPlatform/Include/drang/error.h | 5 +++ Source/DrangPlatform/Include/drang/platform.h | 6 +++ Source/DrangPlatform/Source/errno_convert.c | 4 ++ 6 files changed, 59 insertions(+), 1 deletion(-) 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/error.h b/Source/DrangPlatform/Include/drang/error.h index 6100d59..8641e9a 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 /** @@ -170,4 +173,6 @@ DRANG_PLATFORM_API const char *drang_error_str(int err); */ #define DRANG_FAIL_IF_NULL_OOM(ptr) DRANG_FAIL_IF_NULL(ptr, DRANG_ENOMEM) + + DRANG_END_DECLS diff --git a/Source/DrangPlatform/Include/drang/platform.h b/Source/DrangPlatform/Include/drang/platform.h index d162d0b..b4bca46 100644 --- a/Source/DrangPlatform/Include/drang/platform.h +++ b/Source/DrangPlatform/Include/drang/platform.h @@ -39,6 +39,12 @@ #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 #error "Unsupported compiler, please implement attribute macros" #endif + +#define DRANG_UNUSED(x) ((void)(x)) \ No newline at end of file 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; } From 0db765dc98a7f2c3580218e6e537febff0e866ea Mon Sep 17 00:00:00 2001 From: MechSlayer <0jcrespo1996@gmail.com> Date: Tue, 9 Sep 2025 03:57:43 +0200 Subject: [PATCH 04/13] feature: add endianness handling and byte swap macros for cross-platform compatibility --- .../DrangPlatform/Include/drang/endianness.h | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 Source/DrangPlatform/Include/drang/endianness.h 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 From 710f231a4799abe70388c760b6bc57d8f81a018a Mon Sep 17 00:00:00 2001 From: MechSlayer <0jcrespo1996@gmail.com> Date: Tue, 9 Sep 2025 03:57:53 +0200 Subject: [PATCH 05/13] feature: update fs.h to include stddef.h and clarify timestamp type documentation --- Source/DrangPlatform/Include/drang/fs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/DrangPlatform/Include/drang/fs.h b/Source/DrangPlatform/Include/drang/fs.h index b212903..89d2b2b 100644 --- a/Source/DrangPlatform/Include/drang/fs.h +++ b/Source/DrangPlatform/Include/drang/fs.h @@ -2,6 +2,7 @@ #include "platform.h" #include #include +#include DRANG_BEGIN_DECLS @@ -59,8 +60,7 @@ typedef uint32_t drang_fs_permissions_t; /** * @brief File system timestamp type. * - * Represents file timestamps as 64-bit values. The specific format - * and epoch may be platform-dependent. + * 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; From 7cd3fe29051fcc59c3fd268c2a0c6c1fa39f4d4a Mon Sep 17 00:00:00 2001 From: MechSlayer <0jcrespo1996@gmail.com> Date: Tue, 9 Sep 2025 03:58:17 +0200 Subject: [PATCH 06/13] feature: implement linux file handling functions including open, close, read, write, and permissions management --- Source/DrangPlatform/Source/linux/fs/file.c | 484 ++++++++++++++++++ .../DrangPlatform/Source/linux/fs/internal.h | 13 + 2 files changed, 497 insertions(+) create mode 100644 Source/DrangPlatform/Source/linux/fs/file.c create mode 100644 Source/DrangPlatform/Source/linux/fs/internal.h diff --git a/Source/DrangPlatform/Source/linux/fs/file.c b/Source/DrangPlatform/Source/linux/fs/file.c new file mode 100644 index 0000000..8f81f52 --- /dev/null +++ b/Source/DrangPlatform/Source/linux/fs/file.c @@ -0,0 +1,484 @@ + +#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; +} + +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() +} \ No newline at end of file diff --git a/Source/DrangPlatform/Source/linux/fs/internal.h b/Source/DrangPlatform/Source/linux/fs/internal.h new file mode 100644 index 0000000..7cf40bd --- /dev/null +++ b/Source/DrangPlatform/Source/linux/fs/internal.h @@ -0,0 +1,13 @@ +#pragma once +#include +#include +#include "linux/sync/internal.h" + +#define DRANG_LINUX_FS_BUFFER_SIZE (4096) + +struct drang_fs_file +{ + int fd; + drang_fs_mode_t mode; + struct drang_rwlock lock; +}; From 053f4f9043f422f3ac70d6b24f38dc99f83f2127 Mon Sep 17 00:00:00 2001 From: MechSlayer <0jcrespo1996@gmail.com> Date: Tue, 9 Sep 2025 03:58:22 +0200 Subject: [PATCH 07/13] feature: implement Win32 file handling functions including open, close, read, write, and permissions management --- Source/DrangPlatform/Source/win32/common.c | 35 ++ Source/DrangPlatform/Source/win32/common.h | 11 + Source/DrangPlatform/Source/win32/fs/file.c | 438 ++++++++++++++++++ .../DrangPlatform/Source/win32/fs/internal.h | 16 + 4 files changed, 500 insertions(+) create mode 100644 Source/DrangPlatform/Source/win32/common.c create mode 100644 Source/DrangPlatform/Source/win32/common.h create mode 100644 Source/DrangPlatform/Source/win32/fs/file.c create mode 100644 Source/DrangPlatform/Source/win32/fs/internal.h 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..30768f6 --- /dev/null +++ b/Source/DrangPlatform/Source/win32/common.h @@ -0,0 +1,11 @@ +#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/file.c b/Source/DrangPlatform/Source/win32/fs/file.c new file mode 100644 index 0000000..c0ecdc0 --- /dev/null +++ b/Source/DrangPlatform/Source/win32/fs/file.c @@ -0,0 +1,438 @@ +#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; +} + +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() +} \ No newline at end of file diff --git a/Source/DrangPlatform/Source/win32/fs/internal.h b/Source/DrangPlatform/Source/win32/fs/internal.h new file mode 100644 index 0000000..0551fe5 --- /dev/null +++ b/Source/DrangPlatform/Source/win32/fs/internal.h @@ -0,0 +1,16 @@ +#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; + +}; \ No newline at end of file From 2194e4c805031a0550afd86eef226ee1020f09f3 Mon Sep 17 00:00:00 2001 From: MechSlayer <0jcrespo1996@gmail.com> Date: Tue, 9 Sep 2025 04:01:11 +0200 Subject: [PATCH 08/13] chore: run clang-format --- Source/DrangPlatform/Include/drang/error.h | 2 - Source/DrangPlatform/Include/drang/fs.h | 2 +- Source/DrangPlatform/Include/drang/platform.h | 13 ++- Source/DrangPlatform/Source/linux/fs/file.c | 96 ++++++++++--------- .../DrangPlatform/Source/linux/fs/internal.h | 2 +- Source/DrangPlatform/Source/win32/common.h | 1 - Source/DrangPlatform/Source/win32/fs/file.c | 27 +++--- .../DrangPlatform/Source/win32/fs/internal.h | 5 +- 8 files changed, 75 insertions(+), 73 deletions(-) diff --git a/Source/DrangPlatform/Include/drang/error.h b/Source/DrangPlatform/Include/drang/error.h index 8641e9a..754cc27 100644 --- a/Source/DrangPlatform/Include/drang/error.h +++ b/Source/DrangPlatform/Include/drang/error.h @@ -173,6 +173,4 @@ DRANG_PLATFORM_API const char *drang_error_str(int err); */ #define DRANG_FAIL_IF_NULL_OOM(ptr) DRANG_FAIL_IF_NULL(ptr, DRANG_ENOMEM) - - DRANG_END_DECLS diff --git a/Source/DrangPlatform/Include/drang/fs.h b/Source/DrangPlatform/Include/drang/fs.h index 89d2b2b..84b30ef 100644 --- a/Source/DrangPlatform/Include/drang/fs.h +++ b/Source/DrangPlatform/Include/drang/fs.h @@ -1,8 +1,8 @@ #pragma once #include "platform.h" #include -#include #include +#include DRANG_BEGIN_DECLS diff --git a/Source/DrangPlatform/Include/drang/platform.h b/Source/DrangPlatform/Include/drang/platform.h index b4bca46..239199b 100644 --- a/Source/DrangPlatform/Include/drang/platform.h +++ b/Source/DrangPlatform/Include/drang/platform.h @@ -36,15 +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_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) +# 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 -#error "Unsupported compiler, please implement attribute macros" +# error "Unsupported compiler, please implement attribute macros" #endif -#define DRANG_UNUSED(x) ((void)(x)) \ No newline at end of file +#define DRANG_UNUSED(x) ((void) (x)) diff --git a/Source/DrangPlatform/Source/linux/fs/file.c b/Source/DrangPlatform/Source/linux/fs/file.c index 8f81f52..745bf90 100644 --- a/Source/DrangPlatform/Source/linux/fs/file.c +++ b/Source/DrangPlatform/Source/linux/fs/file.c @@ -40,27 +40,35 @@ static int mode_to_open_flags(const drang_fs_mode_t mode) 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; + 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; } int drang_fs_open(const char *path, drang_fs_mode_t mode, drang_fs_file_t **out_file) { - struct drang_fs_file* file = NULL; + 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); @@ -77,7 +85,6 @@ int drang_fs_open(const char *path, drang_fs_mode_t mode, drang_fs_file_t **out_ file->mode = mode; DRANG_RETURN_IN(out_file, file); - DRANG_CATCH(_) { if (file != NULL) { @@ -92,7 +99,8 @@ int drang_fs_open(const char *path, drang_fs_mode_t mode, drang_fs_file_t **out_ int drang_fs_close(drang_fs_file_t *file) { bool locked = false; - if (file == NULL) return DRANG_EOK; + if (file == NULL) + return DRANG_EOK; DRANG_BEGIN_TRY() DRANG_TRY(drang_fs_flush(file)); @@ -137,7 +145,6 @@ int drang_fs_read(drang_fs_file_t *file, void *buffer, size_t count, size_t size 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; @@ -147,7 +154,7 @@ int drang_fs_read(drang_fs_file_t *file, void *buffer, size_t count, size_t size } if (bytes_read != NULL) { - *bytes_read = (size_t)res; + *bytes_read = (size_t) res; } drang_rwlock_rdunlock(&file->lock); @@ -161,8 +168,7 @@ int drang_fs_read(drang_fs_file_t *file, void *buffer, size_t count, size_t size 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) +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() @@ -173,7 +179,6 @@ int drang_fs_write( 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)); @@ -184,7 +189,7 @@ int drang_fs_write( DRANG_FAIL(drang_errno_to_error(errno)); } if (bytes_written != NULL) { - *bytes_written = (size_t)res; + *bytes_written = (size_t) res; } drang_rwlock_wrunlock(&file->lock); @@ -221,12 +226,11 @@ int drang_fs_seek(drang_fs_file_t *file, int64_t offset, drang_seek_origin_t ori 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) { + 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(_) @@ -249,10 +253,10 @@ int drang_fs_tell(drang_fs_file_t *file, uint64_t *position) DRANG_TRY(drang_rwlock_rdlock(&file->lock)); locked = true; const off_t res = lseek(file->fd, 0, SEEK_CUR); - if (res == (off_t)-1) { + if (res == (off_t) -1) { DRANG_FAIL(drang_errno_to_error(errno)); } - *position = (uint64_t)res; + *position = (uint64_t) res; drang_rwlock_rdunlock(&file->lock); @@ -302,7 +306,7 @@ int drang_fs_truncate(drang_fs_file_t *file, uint64_t size) DRANG_TRY(drang_rwlock_wrlock(&file->lock)); locked = true; - const int res = ftruncate(file->fd, (off_t)size); + const int res = ftruncate(file->fd, (off_t) size); if (res == -1) { DRANG_FAIL(drang_errno_to_error(errno)); } @@ -358,7 +362,6 @@ int drang_fs_move(const char *old_path, const char *new_path) DRANG_RETURN(); } - if (errno == EXDEV) { // Cross-device link, need to copy+delete struct stat src_stat = {0}; @@ -376,7 +379,7 @@ int drang_fs_move(const char *old_path, const char *new_path) 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); + 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)); } @@ -450,22 +453,30 @@ int drang_fs_stat(const char *path, struct drang_fs_stat *out_stat) out_stat->type = drang_fs_type_unknown; } - - out_stat->size = (uint64_t)st.st_size; + 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; + 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; + 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() } @@ -479,6 +490,5 @@ int drang_fs_set_permissions(const char *path, drang_fs_permissions_t permission DRANG_FAIL(drang_errno_to_error(errno)); } - DRANG_END_TRY_IGNORE() -} \ No newline at end of file +} diff --git a/Source/DrangPlatform/Source/linux/fs/internal.h b/Source/DrangPlatform/Source/linux/fs/internal.h index 7cf40bd..dad7663 100644 --- a/Source/DrangPlatform/Source/linux/fs/internal.h +++ b/Source/DrangPlatform/Source/linux/fs/internal.h @@ -1,7 +1,7 @@ #pragma once +#include "linux/sync/internal.h" #include #include -#include "linux/sync/internal.h" #define DRANG_LINUX_FS_BUFFER_SIZE (4096) diff --git a/Source/DrangPlatform/Source/win32/common.h b/Source/DrangPlatform/Source/win32/common.h index 30768f6..f3370c8 100644 --- a/Source/DrangPlatform/Source/win32/common.h +++ b/Source/DrangPlatform/Source/win32/common.h @@ -3,7 +3,6 @@ #include #include - DRANG_BEGIN_DECLS DRANG_PLATFORM_API int win32_error_to_drang(DWORD error); diff --git a/Source/DrangPlatform/Source/win32/fs/file.c b/Source/DrangPlatform/Source/win32/fs/file.c index c0ecdc0..4ad5be1 100644 --- a/Source/DrangPlatform/Source/win32/fs/file.c +++ b/Source/DrangPlatform/Source/win32/fs/file.c @@ -377,10 +377,10 @@ int drang_fs_stat(const char *path, struct drang_fs_stat *out_stat) } 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; + 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; + out_stat->size = ((uint64_t) file_data.nFileSizeHigh << 32) | file_data.nFileSizeLow; } // Convert Windows permissions to Unix-style permissions @@ -392,11 +392,10 @@ int drang_fs_stat(const char *path, struct drang_fs_stat *out_stat) } // 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))) { + 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; } @@ -406,18 +405,18 @@ int drang_fs_stat(const char *path, struct drang_fs_stat *out_stat) 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; + 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; + 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; + 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() @@ -435,4 +434,4 @@ int drang_fs_set_permissions(const char *path, drang_fs_permissions_t permission DRANG_UNUSED(permissions); DRANG_END_TRY_IGNORE() -} \ No newline at end of file +} diff --git a/Source/DrangPlatform/Source/win32/fs/internal.h b/Source/DrangPlatform/Source/win32/fs/internal.h index 0551fe5..a8da21b 100644 --- a/Source/DrangPlatform/Source/win32/fs/internal.h +++ b/Source/DrangPlatform/Source/win32/fs/internal.h @@ -5,12 +5,9 @@ #include "../sync/internal.h" - - struct drang_fs_file { HANDLE handle; drang_fs_mode_t mode; struct drang_rwlock lock; - -}; \ No newline at end of file +}; From d56466526b0dfff7c12d57adb176b7f4a5813eae Mon Sep 17 00:00:00 2001 From: MechSlayer <0jcrespo1996@gmail.com> Date: Tue, 9 Sep 2025 19:51:52 +0200 Subject: [PATCH 09/13] feature: remove path manipulation functions from fs.h --- Source/DrangPlatform/Include/drang/fs.h | 135 ------------------------ 1 file changed, 135 deletions(-) diff --git a/Source/DrangPlatform/Include/drang/fs.h b/Source/DrangPlatform/Include/drang/fs.h index 84b30ef..c4a8cbc 100644 --- a/Source/DrangPlatform/Include/drang/fs.h +++ b/Source/DrangPlatform/Include/drang/fs.h @@ -523,139 +523,4 @@ DRANG_PLATFORM_API int drang_fs_get_cwd(char *buffer, size_t size, size_t *out_s */ DRANG_PLATFORM_API int drang_fs_set_cwd(const char *path); -/** - * @brief Joins two path components. - * - * Combines two path components into a single path using the appropriate - * platform-specific path separator. - * - * @param[out] result Buffer to store the joined path. - * @param[in] result_size Size of the result buffer in bytes. - * @param[in] path1 First path component. - * @param[in] path2 Second path component. - * @param[out] out_size Pointer to store the actual result length (may be NULL). - * @return 0 on success, negative error code on failure. - * @retval DRANG_EOK Success - paths joined successfully. - * @retval DRANG_EINVAL Invalid parameter (result, path1, or path2 is NULL). - * @retval DRANG_ENOMEM Result buffer too small. - * @remarks Uses platform-appropriate path separators. If out_size is provided, - * it contains the required buffer size on DRANG_ENOMEM. - * The result is always null-terminated. - */ -DRANG_PLATFORM_API int drang_fs_path_join( - char *result, size_t result_size, const char *path1, const char *path2, size_t *out_size); - -/** - * @brief Joins two path components with explicit lengths. - * - * Combines two path components into a single path using the appropriate - * platform-specific path separator. Path components are specified with - * explicit lengths rather than being null-terminated. - * - * @param[out] result Buffer to store the joined path. - * @param[in] result_size Size of the result buffer in bytes. - * @param[in] path1 First path component. - * @param[in] path1_length Length of the first path component. - * @param[in] path2 Second path component. - * @param[in] path2_length Length of the second path component. - * @param[out] out_size Pointer to store the actual result length (may be NULL). - * @return 0 on success, negative error code on failure. - * @retval DRANG_EOK Success - paths joined successfully. - * @retval DRANG_EINVAL Invalid parameter (result, path1, or path2 is NULL). - * @retval DRANG_ENOMEM Result buffer too small. - * @remarks Useful when working with non-null-terminated path strings. - * The result is always null-terminated. - */ -DRANG_PLATFORM_API int drang_fs_path_join_length(char *result, - size_t result_size, - const char *path1, - size_t path1_length, - const char *path2, - size_t path2_length, - size_t *out_size); - -/** - * @brief Normalizes a file path. - * - * Resolves relative path components (. and ..) and removes redundant - * separators to produce a canonical path representation. - * - * @param[out] result Buffer to store the normalized path. - * @param[in] result_size Size of the result buffer in bytes. - * @param[in] path Path to normalize. - * @param[out] out_size Pointer to store the actual result length (may be NULL). - * @return 0 on success, negative error code on failure. - * @retval DRANG_EOK Success - path normalized successfully. - * @retval DRANG_EINVAL Invalid parameter (result or path is NULL). - * @retval DRANG_ENOMEM Result buffer too small. - * @remarks Resolves . and .. components and removes duplicate separators. - * Does not resolve symbolic links. The result is always null-terminated. - */ -DRANG_PLATFORM_API int drang_fs_path_normalize(char *result, size_t result_size, const char *path, size_t *out_size); - -/** - * @brief Normalizes a file path with explicit length. - * - * Resolves relative path components (. and ..) and removes redundant - * separators to produce a canonical path representation. The input path - * is specified with an explicit length rather than being null-terminated. - * - * @param[out] result Buffer to store the normalized path. - * @param[in] result_size Size of the result buffer in bytes. - * @param[in] path Path to normalize. - * @param[in] path_length Length of the input path. - * @param[out] out_size Pointer to store the actual result length (may be NULL). - * @return 0 on success, negative error code on failure. - * @retval DRANG_EOK Success - path normalized successfully. - * @retval DRANG_EINVAL Invalid parameter (result or path is NULL). - * @retval DRANG_ENOMEM Result buffer too small. - * @remarks Useful when working with non-null-terminated path strings. - * The result is always null-terminated. - */ -DRANG_PLATFORM_API int drang_fs_path_normalize_length( - char *result, size_t result_size, const char *path, size_t path_length, size_t *out_size); - -/** - * @brief Converts a path to absolute form. - * - * Converts a relative or absolute path to a fully qualified absolute path. - * Relative paths are resolved against the current working directory. - * - * @param[out] result Buffer to store the absolute path. - * @param[in] result_size Size of the result buffer in bytes. - * @param[in] path Path to convert to absolute form. - * @param[out] out_size Pointer to store the actual result length (may be NULL). - * @return 0 on success, negative error code on failure. - * @retval DRANG_EOK Success - absolute path created successfully. - * @retval DRANG_EINVAL Invalid parameter (result or path is NULL). - * @retval DRANG_ENOMEM Result buffer too small. - * @retval DRANG_EIO I/O error occurred while resolving current directory. - * @remarks Relative paths are resolved against the current working directory. - * The result is always null-terminated and fully qualified. - */ -DRANG_PLATFORM_API int drang_fs_path_absolute(char *result, size_t result_size, const char *path, size_t *out_size); - -/** - * @brief Converts a path to absolute form with explicit length. - * - * Converts a relative or absolute path to a fully qualified absolute path. - * The input path is specified with an explicit length rather than being - * null-terminated. Relative paths are resolved against the current working directory. - * - * @param[out] result Buffer to store the absolute path. - * @param[in] result_size Size of the result buffer in bytes. - * @param[in] path Path to convert to absolute form. - * @param[in] path_length Length of the input path. - * @param[out] out_size Pointer to store the actual result length (may be NULL). - * @return 0 on success, negative error code on failure. - * @retval DRANG_EOK Success - absolute path created successfully. - * @retval DRANG_EINVAL Invalid parameter (result or path is NULL). - * @retval DRANG_ENOMEM Result buffer too small. - * @retval DRANG_EIO I/O error occurred while resolving current directory. - * @remarks Useful when working with non-null-terminated path strings. - * The result is always null-terminated and fully qualified. - */ -DRANG_PLATFORM_API int drang_fs_path_absolute_length( - char *result, size_t result_size, const char *path, size_t path_length, size_t *out_size); - DRANG_END_DECLS From 05f593311195232d7846c8176c5a9a09cfd8b200 Mon Sep 17 00:00:00 2001 From: MechSlayer <0jcrespo1996@gmail.com> Date: Tue, 9 Sep 2025 19:52:16 +0200 Subject: [PATCH 10/13] feature: implement linux directory handling functions --- Source/DrangPlatform/Source/linux/fs/dir.c | 160 ++++++++++++++++++ .../DrangPlatform/Source/linux/fs/internal.h | 8 +- 2 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 Source/DrangPlatform/Source/linux/fs/dir.c diff --git a/Source/DrangPlatform/Source/linux/fs/dir.c b/Source/DrangPlatform/Source/linux/fs/dir.c new file mode 100644 index 0000000..e039d0a --- /dev/null +++ b/Source/DrangPlatform/Source/linux/fs/dir.c @@ -0,0 +1,160 @@ +#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() +} \ No newline at end of file diff --git a/Source/DrangPlatform/Source/linux/fs/internal.h b/Source/DrangPlatform/Source/linux/fs/internal.h index dad7663..d43fd4f 100644 --- a/Source/DrangPlatform/Source/linux/fs/internal.h +++ b/Source/DrangPlatform/Source/linux/fs/internal.h @@ -2,7 +2,7 @@ #include "linux/sync/internal.h" #include #include - +#include #define DRANG_LINUX_FS_BUFFER_SIZE (4096) struct drang_fs_file @@ -11,3 +11,9 @@ struct drang_fs_file drang_fs_mode_t mode; struct drang_rwlock lock; }; + + +struct drang_fs_directory +{ + DIR* dir; +}; \ No newline at end of file From c43748d1d43ce35d46814f199ac3470028a9b916 Mon Sep 17 00:00:00 2001 From: MechSlayer <0jcrespo1996@gmail.com> Date: Tue, 9 Sep 2025 20:02:45 +0200 Subject: [PATCH 11/13] feature: add platform-specific path separator functions for Unix and Windows --- Source/DrangPlatform/Include/drang/fs.h | 21 ++++++++++++++++++++- Source/DrangPlatform/Source/linux/fs/file.c | 5 +++++ Source/DrangPlatform/Source/win32/fs/file.c | 5 +++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Source/DrangPlatform/Include/drang/fs.h b/Source/DrangPlatform/Include/drang/fs.h index c4a8cbc..8d5d7a3 100644 --- a/Source/DrangPlatform/Include/drang/fs.h +++ b/Source/DrangPlatform/Include/drang/fs.h @@ -132,6 +132,25 @@ typedef struct fs_dir_entry 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. * @@ -187,7 +206,7 @@ DRANG_PLATFORM_API int drang_fs_close(drang_fs_file_t *file); * @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 by bytes read. + * 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); diff --git a/Source/DrangPlatform/Source/linux/fs/file.c b/Source/DrangPlatform/Source/linux/fs/file.c index 745bf90..17211cb 100644 --- a/Source/DrangPlatform/Source/linux/fs/file.c +++ b/Source/DrangPlatform/Source/linux/fs/file.c @@ -61,6 +61,11 @@ static int drang_permissions_to_linux(const drang_fs_permissions_t permissions) 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; diff --git a/Source/DrangPlatform/Source/win32/fs/file.c b/Source/DrangPlatform/Source/win32/fs/file.c index 4ad5be1..7c50442 100644 --- a/Source/DrangPlatform/Source/win32/fs/file.c +++ b/Source/DrangPlatform/Source/win32/fs/file.c @@ -32,6 +32,11 @@ static void mode_to_access(drang_fs_mode_t mode, DWORD *out_access, DWORD *out_c *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; From f766cdb21ef085ca0e175f8c070a83703c5b4613 Mon Sep 17 00:00:00 2001 From: MechSlayer <0jcrespo1996@gmail.com> Date: Tue, 9 Sep 2025 20:16:27 +0200 Subject: [PATCH 12/13] feature: implement Win32 directory handling functions including mkdir, rmdir, opendir, readdir, closedir, getcwd, and setcwd --- Source/DrangPlatform/Source/win32/fs/dir.c | 154 ++++++++++++++++++ .../DrangPlatform/Source/win32/fs/internal.h | 7 + 2 files changed, 161 insertions(+) create mode 100644 Source/DrangPlatform/Source/win32/fs/dir.c 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/internal.h b/Source/DrangPlatform/Source/win32/fs/internal.h index a8da21b..37a6b0a 100644 --- a/Source/DrangPlatform/Source/win32/fs/internal.h +++ b/Source/DrangPlatform/Source/win32/fs/internal.h @@ -11,3 +11,10 @@ struct drang_fs_file drang_fs_mode_t mode; struct drang_rwlock lock; }; + +struct drang_fs_directory +{ + HANDLE handle; + WIN32_FIND_DATAA find_data; + bool first_entry; +}; \ No newline at end of file From 9810cadd373976c78dc47b9a17bef5e3b39d7dd8 Mon Sep 17 00:00:00 2001 From: MechSlayer <0jcrespo1996@gmail.com> Date: Tue, 9 Sep 2025 20:17:08 +0200 Subject: [PATCH 13/13] chore: run clang-format --- Source/DrangPlatform/Include/drang/fs.h | 2 -- Source/DrangPlatform/Source/linux/fs/dir.c | 4 +--- Source/DrangPlatform/Source/linux/fs/internal.h | 7 +++---- Source/DrangPlatform/Source/win32/fs/internal.h | 2 +- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Source/DrangPlatform/Include/drang/fs.h b/Source/DrangPlatform/Include/drang/fs.h index 8d5d7a3..43753f3 100644 --- a/Source/DrangPlatform/Include/drang/fs.h +++ b/Source/DrangPlatform/Include/drang/fs.h @@ -132,8 +132,6 @@ typedef struct fs_dir_entry drang_fs_type_t type; /**< Entry type */ } drang_fs_dir_entry_t; - - /** * @brief Gets the platform-specific path separator character. * diff --git a/Source/DrangPlatform/Source/linux/fs/dir.c b/Source/DrangPlatform/Source/linux/fs/dir.c index e039d0a..664d18f 100644 --- a/Source/DrangPlatform/Source/linux/fs/dir.c +++ b/Source/DrangPlatform/Source/linux/fs/dir.c @@ -143,8 +143,6 @@ int drang_fs_get_cwd(char *buffer, size_t size, size_t *out_size) DRANG_FAIL(drang_errno_to_error(errno)); - - DRANG_END_TRY_IGNORE() } @@ -157,4 +155,4 @@ int drang_fs_set_cwd(const char *path) } DRANG_END_TRY_IGNORE() -} \ No newline at end of file +} diff --git a/Source/DrangPlatform/Source/linux/fs/internal.h b/Source/DrangPlatform/Source/linux/fs/internal.h index d43fd4f..e0ac8f2 100644 --- a/Source/DrangPlatform/Source/linux/fs/internal.h +++ b/Source/DrangPlatform/Source/linux/fs/internal.h @@ -1,8 +1,8 @@ #pragma once #include "linux/sync/internal.h" +#include #include #include -#include #define DRANG_LINUX_FS_BUFFER_SIZE (4096) struct drang_fs_file @@ -12,8 +12,7 @@ struct drang_fs_file struct drang_rwlock lock; }; - struct drang_fs_directory { - DIR* dir; -}; \ No newline at end of file + DIR *dir; +}; diff --git a/Source/DrangPlatform/Source/win32/fs/internal.h b/Source/DrangPlatform/Source/win32/fs/internal.h index 37a6b0a..fde1e26 100644 --- a/Source/DrangPlatform/Source/win32/fs/internal.h +++ b/Source/DrangPlatform/Source/win32/fs/internal.h @@ -17,4 +17,4 @@ struct drang_fs_directory HANDLE handle; WIN32_FIND_DATAA find_data; bool first_entry; -}; \ No newline at end of file +};