From 13a44527586b01bd51efea16890dd8b3a2818f1f Mon Sep 17 00:00:00 2001 From: Mikhail Romanko Date: Sun, 26 Jul 2026 09:37:43 +0300 Subject: [PATCH] Initial commit --- .github/workflows/ci.yaml | 79 ++++++++++++ .gitignore | 68 +++++++++++ CMakeLists.txt | 39 ++++++ CgeFile.h | 62 ++++++++++ File.c | 31 +++++ LICENSE | 12 ++ Makefile.lint | 29 +++++ Makefile.mingw | 23 ++++ Makefile.posix | 33 +++++ Makefile.win32 | 23 ++++ README.md | 73 +++++++++++ posix/File.c | 251 ++++++++++++++++++++++++++++++++++++++ win32/File.c | 242 ++++++++++++++++++++++++++++++++++++ 13 files changed, 965 insertions(+) create mode 100644 .github/workflows/ci.yaml create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 CgeFile.h create mode 100644 File.c create mode 100644 LICENSE create mode 100644 Makefile.lint create mode 100644 Makefile.mingw create mode 100644 Makefile.posix create mode 100644 Makefile.win32 create mode 100644 README.md create mode 100644 posix/File.c create mode 100644 win32/File.c diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..369e537 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,79 @@ +name: CI + +on: + push: + branches: [trunk] + pull_request: + branches: [trunk] + +jobs: + build-and-analyze: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install tools + run: | + sudo apt-get update -qq + sudo apt-get install -y build-essential clang clang-tools clang-tidy cppcheck + + - name: Build with GCC + run: | + make -f Makefile.posix clean + make -f Makefile.posix CC=gcc CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -O2" + + - name: Build with Clang + run: | + make -f Makefile.posix clean + make -f Makefile.posix CC=clang CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -O2" + + - name: Linting checks + run: | + echo "Running all linting checks..." + FAILED=0 + + # cppcheck + echo "=== Check 1/4: cppcheck ===" + if make -f Makefile.lint cppcheck; then + echo "cppcheck: PASSED" + else + echo "cppcheck: FAILED" + FAILED=1 + fi + + # clang-tidy + echo "=== Check 2/4: clang-tidy ===" + if make -f Makefile.lint clang-tidy; then + echo "clang-tidy: PASSED" + else + echo "clang-tidy: FAILED" + FAILED=1 + fi + + # scan-build + echo "=== Check 3/4: scan-build ===" + if make -f Makefile.lint scan-build; then + echo "scan-build: PASSED" + else + echo "scan-build: FAILED" + FAILED=1 + fi + + # security-check + echo "=== Check 4/4: security-check ===" + if make -f Makefile.lint security-check; then + echo "security-check: PASSED" + else + echo "security-check: FAILED" + FAILED=1 + fi + + # Final result + if [ $FAILED -ne 0 ]; then + echo "One or more linting checks failed." + exit 1 + else + echo "All linting checks passed." + fi \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f765426 --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# ---> C +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# ---> CMake +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps +CMakeUserPresets.json + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..a9d69a5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.10) +project(CgeFile LANGUAGES C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +set(SOURCES) + +set(SOURCES_WIN32 + src/win32/File.c +) + +set(SOURCES_POSIX + src/posix/File.c +) + +set(HEADERS + CgeFile.h +) + +if(WIN32) + message(STATUS "Platform - Win32") + list(APPEND SOURCES ${SOURCES_WIN32}) +elseif(UNIX) + message(STATUS "Platform: Unix (Linux/BSD/MacOS X)") + list(APPEND SOURCES ${SOURCES_POSIX}) +endif() + +add_library(CgeFile STATIC ${SOURCES} ${HEADERS}) + +target_include_directories(CgeFile PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +install(TARGETS CgeFile + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +install(FILES ${HEADERS} DESTINATION include) diff --git a/CgeFile.h b/CgeFile.h new file mode 100644 index 0000000..7309f0b --- /dev/null +++ b/CgeFile.h @@ -0,0 +1,62 @@ +#ifndef CGE_FILE_H +#define CGE_FILE_H + +#include +#include + +enum CgeFileMode { + CGE_FILE_READ = (1 << 0), + CGE_FILE_WRITE = (1 << 1), + CGE_FILE_READWRITE = (1 << 0) | (1 << 1), + CGE_FILE_APPEND = (1 << 4), + CGE_FILE_TRUNCATE = (1 << 5), + CGE_FILE_CREATE = (1 << 6), + CGE_FILE_EXIST = (1 << 7), +}; + +enum CgeFileSeek { + CGE_FILE_SET, + CGE_FILE_CUR, + CGE_FILE_END, +}; + +enum CgeFileFlags { + CGE_FILE_OK = (1 << 0), + CGE_FILE_ERROR = (1 << 1), + CGE_FILE_EOF = (1 << 2), +}; + +enum CgeFileCode { + CGE_FILE_EOK, /* Success */ + CGE_FILE_EUNKNOWN, /* Unknown or unexpected error */ + CGE_FILE_EOOM, /* Out of memory */ + CGE_FILE_ENOSPC, /* No space left on device */ + CGE_FILE_EINVAL, /* Invalid mode/flag combination */ + CGE_FILE_ENOENT, /* No such file or directory */ + CGE_FILE_EACCES, /* Permission denied */ + CGE_FILE_EROFS, /* Read-only file system */ + CGE_FILE_EEXIST, /* File already exists */ + CGE_FILE_EISDIR, /* Is a directory */ + CGE_FILE_EMFILE, /* Too many open files */ + CGE_FILE_ENAMETOOLONG /* File name too long */ +}; + +typedef struct CgeFile CgeFile; + +CgeFile *CgeFileNew(const char *path, int mode, int *result); +void CgeFileFree(CgeFile *file); +int CgeFileRead(CgeFile *file, char *buffer, size_t size, size_t *actual); +int CgeFileWrite(CgeFile *file, const char *buffer, size_t size, + size_t *actual); +int CgeFilePeek(CgeFile *file, char *buffer, size_t size, size_t *actual); +int CgeFileTell(CgeFile *file, int64_t *offset); +int CgeFileSeek(CgeFile *file, int64_t offset, int whence); +int CgeFileFlush(CgeFile *file); +int CgeFileSize(CgeFile *file, int64_t *size); +int CgeFileFlags(CgeFile *file, int *flags); +int CgeFileError(CgeFile *file); +int CgeFileEndOfFile(CgeFile *file); +int CgeFileClear(CgeFile *file); +int CgeFileErrorCode(CgeFile *file); + +#endif /* CGE_FILE_H */ diff --git a/File.c b/File.c new file mode 100644 index 0000000..081ceb8 --- /dev/null +++ b/File.c @@ -0,0 +1,31 @@ +#include "CgeFile.h" + +int CgeFileError(CgeFile *file) { + int flags; + + CgeFileFlags(file, &flags); + return flags & CGE_FILE_ERROR; +} + +int CgeFileEndOfFile(CgeFile *file) { + int flags; + + CgeFileFlags(file, &flags); + return flags & CGE_FILE_EOF; +} + +int CgeFilePeek(CgeFile *file, char *buffer, size_t size, size_t *actual) { + int64_t pos; + size_t n = 0; + + if (CgeFileError(file) || CgeFileEndOfFile(file)) + return 0; + + if (!CgeFileTell(file, &pos) || + !CgeFileRead(file, buffer, size, &n) || + !CgeFileSeek(file, pos, CGE_FILE_SET)) + return 0; + + if (actual) *actual = n; + return 1; +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3d44ffd --- /dev/null +++ b/LICENSE @@ -0,0 +1,12 @@ +Copyright (C) 2026 by blankhex me@blankhex.com + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile.lint b/Makefile.lint new file mode 100644 index 0000000..3897d63 --- /dev/null +++ b/Makefile.lint @@ -0,0 +1,29 @@ +# Makefile.lint - Individual linting targets + +.PHONY: all +all: cppcheck clang-tidy scan-build security-check + @echo "All linting scheduled" + +.PHONY: cppcheck +cppcheck: + @echo "Running cppcheck..." + @cppcheck --enable=warning,performance,portability --std=c99 --quiet -Iinclude -I. . + +.PHONY: clang-tidy +clang-tidy: + @echo "Running clang-tidy..." + @for file in $$(find . -name "*.c" -type f); do \ + clang-tidy "$$file" -- -I. -Iinclude -std=c99; \ + done + +.PHONY: scan-build +scan-build: + @echo "Running scan-build..." + @scan-build --status-bugs make -f Makefile.posix clean all CC=clang + +.PHONY: security-check +security-check: + @echo "Scanning for unsafe C functions..." + @grep -rnE '\b(strcpy|strcat|sprintf|gets|scanf|sscanf|realpath|mktemp|tempnam|tmpnam|getwd|getlogin)\b' \ + . --include="*.c" --include="*.h" | grep -v "^Binary file" && \ + exit 1 || exit 0 \ No newline at end of file diff --git a/Makefile.mingw b/Makefile.mingw new file mode 100644 index 0000000..053e426 --- /dev/null +++ b/Makefile.mingw @@ -0,0 +1,23 @@ +# MinGW Makefile for CgeFile + +CC = gcc +AR = ar +CFLAGS = -std=c99 -O2 -Wall -Wextra +ARFLAGS = rcs +TARGET = libCgeFile.a + +SOURCES = win32/File.c +OBJECTS = $(SOURCES:.c=.o) + +.PHONY: all clean + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(AR) $(ARFLAGS) $@ $^ + +%.o: %.c CgeFile.h + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + del $(OBJECTS) $(TARGET) 2>nul || exit 0 diff --git a/Makefile.posix b/Makefile.posix new file mode 100644 index 0000000..84527b2 --- /dev/null +++ b/Makefile.posix @@ -0,0 +1,33 @@ +# POSIX Makefile for CgeFile + +CC = gcc +AR = ar +CFLAGS = -std=c99 -O2 -Wall -Wextra -fPIC +ARFLAGS = rcs +TARGET = libCgeFile.a + +SOURCES = posix/File.c +OBJECTS = $(SOURCES:.c=.o) + +.PHONY: all clean install + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(AR) $(ARFLAGS) $@ $^ + +%.o: %.c CgeFile.h + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJECTS) $(TARGET) + +install: $(TARGET) + cp $(TARGET) /usr/local/lib/ + cp CgeFile.h /usr/local/include/ + ldconfig || echo "Run ldconfig manually if needed" + +uninstall: + rm -f /usr/local/lib/libCgeFile.a + rm -f /usr/local/include/CgeFile.h + ldconfig || true diff --git a/Makefile.win32 b/Makefile.win32 new file mode 100644 index 0000000..aa6c94e --- /dev/null +++ b/Makefile.win32 @@ -0,0 +1,23 @@ +# Makefile.win32 for MSVC (NMake) +# Usage: Open "x86 Native Tools Command Prompt", then: +# nmake -f Makefile.win32 + +CC = cl +LIB = lib +CFLAGS = /c /nologo /W3 /O2 +LIBFLAGS = /nologo +TARGET = CgeFile.lib + +SOURCES = win32/File.c +OBJECTS = $(SOURCES:.c=.obj) + +$(TARGET): $(OBJECTS) + $(LIB) $(LIBFLAGS) /OUT:$(TARGET) $(OBJECTS) + +{.}.c{}.obj: + $(CC) $(CFLAGS) /Fo$@ $< + +clean: + del $(OBJECTS) $(TARGET) 2>nul + +.PHONY: clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..0265acb --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ + # CgeFile - File Access Library for C + +A lightweight, dependency-free C library providing a consistent interface for +file operations across different operating systems. The library abstracts +platform-specific details and offers a unified API for reading, writing, and +manipulating files + +## Features + +- Cross-platform compatibility (Linux, macOS, Windows, POSIX systems) +- Multiple file modes (read, write, read/write, append, truncate, create, + existence checking) +- Flexible seeking (relative, absolute, end-based positioning) +- Comprehensive error handling with detailed error codes +- File metadata retrieval (size, current position, flags) +- Peek functionality (read data without advancing the file pointer) +- Buffering and flushing (control data persistence) +- EOF detection and error clearing + +## Usage Example + +```c +#include "CgeFile.h" +#include +#include + +int main(void) { + int result; + CgeFile *file = CgeFileNew("example.txt", CGE_FILE_READWRITE | CGE_FILE_CREATE, &result); + if (!file) { + fprintf(stderr, "Failed to open file: error code %d\n", result); + return 1; + } + + const char *data = "Hello, CgeFile!"; + size_t written; + if (!CgeFileWrite(file, data, strlen(data), &written)) { + fprintf(stderr, "Write failed\n"); + CgeFileFree(file); + return 1; + } + + char buffer[100]; + size_t actual; + if (CgeFileSeek(file, 0, CGE_FILE_SET) && + CgeFileRead(file, buffer, sizeof(buffer) - 1, &actual)) { + buffer[actual] = '\0'; + printf("Read: %s\n", buffer); + } + + CgeFileFree(file); + return 0; +} +``` + +## Build Systems + +- **CMake**: supports Linux, macOS, Windows (MSVC, MinGW) +- **Makefile.posix**: for GCC/Clang on POSIX systems +- **Makefile.mingw**: for MinGW on Windows +- **Makefile.win32**: for MSVC with NMake + +Builds a static library. No shared library or external dependencies + +## Portability + +- **Standard compliance**: written in C89 + stdint.h +- **No dynamic memory allocation**: all memory management is explicit +- **No external dependencies**: relies only on standard C libraries + +## License + +0BSD - a permissive license with no attribution required. diff --git a/posix/File.c b/posix/File.c new file mode 100644 index 0000000..6507d1b --- /dev/null +++ b/posix/File.c @@ -0,0 +1,251 @@ +#define _FILE_OFFSET_BITS 64 + +#include "../CgeFile.h" +#include +#include +#include +#include +#include +#include +#include + +static const int64_t off_t_max = + (sizeof(off_t) == 8) ? INT64_MAX : + (sizeof(off_t) == 4) ? INT32_MAX : 0; + +static const int64_t off_t_min = + (sizeof(off_t) == 8) ? INT64_MIN : + (sizeof(off_t) == 4) ? INT32_MIN : 0; + +struct CgeFile { + int flags; + int handle; + int lastError; +}; + +static int errnoToFileCode(int err) { + switch (err) { + case 0: + return CGE_FILE_EOK; + + case ENOMEM: + return CGE_FILE_EOOM; + + case ENOSPC: + return CGE_FILE_ENOSPC; + + case EINVAL: + return CGE_FILE_EINVAL; + + case ENOENT: + return CGE_FILE_ENOENT; + + case EACCES: + case EPERM: + return CGE_FILE_EACCES; + + case EROFS: + return CGE_FILE_EROFS; + + case EEXIST: + return CGE_FILE_EEXIST; + + case EISDIR: + return CGE_FILE_EISDIR; + + case EMFILE: + case ENFILE: + return CGE_FILE_EMFILE; + + case ENAMETOOLONG: + return CGE_FILE_ENAMETOOLONG; + + default: + return CGE_FILE_EUNKNOWN; + } +} + +static int fileOpenFlags(int mode) { + int flags = 0; + + if ((mode & CGE_FILE_READWRITE) == CGE_FILE_READWRITE) + flags |= O_RDWR; + else if (mode & CGE_FILE_WRITE) + flags |= O_WRONLY; + else if (mode & CGE_FILE_READ) + flags |= O_RDONLY; + else + return -1; + + if (!(mode & CGE_FILE_EXIST)) + { + flags |= O_CREAT; + if (mode & CGE_FILE_CREATE) + flags |= O_EXCL; + } + + if (mode & CGE_FILE_APPEND) + flags |= O_APPEND; + + if (mode & CGE_FILE_TRUNCATE) + flags |= O_TRUNC; + + return flags; +} + +static int fileInit(CgeFile *file, const char *path, int mode) { + static const mode_t openMode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + int flags; + + if (!path) + return CGE_FILE_EINVAL; + + flags = fileOpenFlags(mode); + if (flags == -1) + return CGE_FILE_EINVAL; + + file->flags = 0; + file->handle = open(path, flags, openMode); + if (file->handle == -1) + return errnoToFileCode(errno); + + return CGE_FILE_EOK; +} + +CgeFile *CgeFileNew(const char *path, int mode, int *result) { + CgeFile *file; + int code = 0; + + if ((file = malloc(sizeof(*file)))) { + if ((code = fileInit(file, path, mode))) { + free(file); + file = NULL; + } + } + if (result) *result = code; + return file; +} + +void CgeFileFree(CgeFile *file) { + close(file->handle); + free(file); +} + +int CgeFileRead(CgeFile *file, char *buffer, size_t size, size_t *actual) { + ssize_t readed; + + if (file->flags & CGE_FILE_ERROR) + return 0; + + readed = read(file->handle, buffer, size); + if (readed < 0) + goto error; + + if (readed > 0) file->flags &= ~CGE_FILE_EOF; + else file->flags |= CGE_FILE_EOF; + + if (actual) *actual = readed; + return 1; + +error: + file->flags |= CGE_FILE_ERROR; + file->lastError = errnoToFileCode(errno); + return 0; +} + +int CgeFileWrite(CgeFile *file, const char *buffer, size_t size, + size_t *actual) { + ssize_t written; + + if (file->flags & CGE_FILE_ERROR) + return 0; + + written = write(file->handle, buffer, size); + if (written < 0) + goto error; + + if (actual) *actual = written; + return 1; + +error: + file->flags |= CGE_FILE_ERROR; + file->lastError = errnoToFileCode(errno); + return 0; +} + +int CgeFileTell(CgeFile *file, int64_t *offset) { + if (file->flags & CGE_FILE_ERROR) + return 0; + + if ((*offset = lseek(file->handle, 0, SEEK_CUR)) == -1) + goto error; + + return 1; + +error: + file->flags |= CGE_FILE_ERROR; + file->lastError = errnoToFileCode(errno); + return 0; +} + +int CgeFileSeek(CgeFile *file, int64_t offset, int whence) { + if (file->flags & CGE_FILE_ERROR) + return 0; + + if (offset < off_t_min || offset > off_t_max) { + file->flags |= CGE_FILE_ERROR; + file->lastError = CGE_FILE_EINVAL; + return 0; + } + + if (lseek(file->handle, offset, whence) == -1) + goto error; + + return 1; + +error: + file->flags |= CGE_FILE_ERROR; + file->lastError = errnoToFileCode(errno); + return 0; +} + +int CgeFileFlush(CgeFile *file) { + if (fsync(file->handle)) + goto error; + + return 1; + +error: + file->flags |= CGE_FILE_ERROR; + file->lastError = errnoToFileCode(errno); + return 0; +} + +int CgeFileSize(CgeFile *file, int64_t *size) { + struct stat sb; + + if (fstat(file->handle, &sb)) + goto error; + + *size = sb.st_size; + return 1; + +error: + file->flags |= CGE_FILE_ERROR; + file->lastError = errnoToFileCode(errno); + return 0; +} + +int CgeFileFlags(CgeFile *file, int *flags) { + *flags = file->flags; + return 1; +} + +int CgeFileClear(CgeFile *file) { + file->flags &= ~CGE_FILE_ERROR; + return 1; +} + +int CgeFileErrorCode(CgeFile *file) { + return file->lastError; +} diff --git a/win32/File.c b/win32/File.c new file mode 100644 index 0000000..2bebdc7 --- /dev/null +++ b/win32/File.c @@ -0,0 +1,242 @@ +#include "../CgeFile.h" +#include + +struct CgeFile { + HANDLE handle; + int flags; + int mode; + int lastError; +}; + +static int errorCodeFromError(void) { + DWORD error; + + error = GetLastError(); + switch (error) { + case ERROR_NOT_ENOUGH_MEMORY: + return CGE_FILE_EOOM; + + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + return CGE_FILE_ENOENT; + + case ERROR_ACCESS_DENIED: + return CGE_FILE_EACCES; + + case ERROR_FILE_EXISTS: + case ERROR_ALREADY_EXISTS: + return CGE_FILE_EEXIST; + + case ERROR_DIRECTORY: + return CGE_FILE_EISDIR; + + case ERROR_WRITE_PROTECT: + return CGE_FILE_EROFS; + + case ERROR_TOO_MANY_OPEN_FILES: + return CGE_FILE_EMFILE; + + case ERROR_INVALID_NAME: + case ERROR_FILENAME_EXCED_RANGE: + return CGE_FILE_ENAMETOOLONG; + + case ERROR_DISK_FULL: + case ERROR_HANDLE_DISK_FULL: + return CGE_FILE_ENOSPC; + + default: + return CGE_FILE_EUNKNOWN; + } +} + +static int fileInit(CgeFile *file, const char *path, int mode) { + DWORD access = 0, how = 0; + + if (!path) + return CGE_FILE_EINVAL; + + if (mode & CGE_FILE_READ) + access |= GENERIC_READ; + if (mode & CGE_FILE_WRITE) + access |= GENERIC_WRITE; + + if (!access) + return CGE_FILE_EINVAL; + + if (mode & CGE_FILE_TRUNCATE) { + switch (mode & (CGE_FILE_CREATE | CGE_FILE_EXIST)) { + case 0: how = CREATE_ALWAYS; break; + case CGE_FILE_CREATE: how = CREATE_NEW; break; + case CGE_FILE_EXIST: how = TRUNCATE_EXISTING; break; + default: return CGE_FILE_EINVAL; + } + } else { + switch (mode & (CGE_FILE_CREATE | CGE_FILE_EXIST)) { + case 0: how = OPEN_ALWAYS; break; + case CGE_FILE_CREATE: how = CREATE_NEW; break; + case CGE_FILE_EXIST: how = OPEN_EXISTING; break; + default: return CGE_FILE_EINVAL; + } + } + + file->flags = 0; + file->mode = mode; + file->handle = CreateFileA(path, access, FILE_SHARE_READ, NULL, how, FILE_ATTRIBUTE_NORMAL, NULL); + + if (file->handle == INVALID_HANDLE_VALUE) + return errorCodeFromError(); + + if (mode & CGE_FILE_TRUNCATE) + SetEndOfFile(file->handle); + + return CGE_FILE_EOK; +} + +CgeFile *CgeFileNew(const char *path, int mode, int *result) { + CgeFile *file; + int code = 0; + + if ((file = malloc(sizeof(*file)))) { + if ((code = fileInit(file, path, mode))) { + free(file); + file = NULL; + } + } + if (result) *result = code; + return file; +} + +void CgeFileFree(CgeFile *file) { + CloseHandle(file->handle); + free(file); +} + +int CgeFileRead(CgeFile *file, char *buffer, size_t size, size_t *actual) { + DWORD readed; + + if (file->flags & CGE_FILE_ERROR) + return 0; + + if (!ReadFile(file->handle, buffer, (DWORD)size, &readed, NULL)) + goto error; + + if (!readed) file->flags |= CGE_FILE_EOF; + else file->flags &= ~CGE_FILE_EOF; + if (actual) *actual = readed; + + return 1; + +error: + file->flags |= CGE_FILE_ERROR; + file->lastError = errorCodeFromError(); + return 0; +} + +int CgeFileWrite(CgeFile *file, const char *buffer, size_t size, + size_t *actual) { + DWORD written; + LARGE_INTEGER position; + + if (file->flags & CGE_FILE_ERROR) + return 0; + + if (file->mode & CGE_FILE_APPEND) { + position.QuadPart = 0; + if (!SetFilePointerEx(file->handle, position, NULL, FILE_END)) + goto error; + } + + if (!WriteFile(file->handle, buffer, (DWORD)size, &written, NULL)) + goto error; + + if (actual) *actual = written; + + return 1; + +error: + file->flags |= CGE_FILE_ERROR; + file->lastError = errorCodeFromError(); + return 0; +} + +int CgeFileTell(CgeFile *file, int64_t *offset) { + LARGE_INTEGER dummy, position; + + if (file->flags & CGE_FILE_ERROR) + return 0; + + dummy.QuadPart = 0; + if (!SetFilePointerEx(file->handle, dummy, &position, FILE_CURRENT)) + goto error; + + *offset = position.QuadPart; + return 1; + +error: + file->flags |= CGE_FILE_ERROR; + file->lastError = errorCodeFromError(); + return 0; +} + +int CgeFileSeek(CgeFile *file, int64_t offset, int whence) { + LARGE_INTEGER position; + + if (file->flags & CGE_FILE_ERROR) + return 0; + + position.QuadPart = offset; + if (!SetFilePointerEx(file->handle, position, NULL, whence)) + goto error; + + file->flags &= ~CGE_FILE_EOF; + return 1; + +error: + file->flags |= CGE_FILE_ERROR; + file->lastError = errorCodeFromError(); + return 0; +} + +int CgeFileFlush(CgeFile *file) { + if (!FlushFileBuffers(file->handle)) + goto error; + + return 1; + +error: + file->flags |= CGE_FILE_ERROR; + file->lastError = errorCodeFromError(); + return 0; +} + +int CgeFileSize(CgeFile *file, int64_t *size) { + LARGE_INTEGER dummy; + + if (file->flags & CGE_FILE_ERROR) + return 0; + + if (!GetFileSizeEx(file->handle, &dummy)) + goto error; + + *size = dummy.QuadPart; + return 1; + +error: + file->flags |= CGE_FILE_ERROR; + file->lastError = errorCodeFromError(); + return 0; +} + +int CgeFileFlags(CgeFile *file, int *flags) { + *flags = file->flags; + return 1; +} + +int CgeFileClear(CgeFile *file) { + file->flags &= ~CGE_FILE_ERROR; + return 1; +} + +int CgeFileErrorCode(CgeFile *file) { + return file->lastError; +}