commit 13a44527586b01bd51efea16890dd8b3a2818f1f Author: Mikhail Romanko Date: Sun Jul 26 09:37:43 2026 +0300 Initial commit 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; +}