This commit is contained in:
79
.github/workflows/ci.yaml
vendored
Normal file
79
.github/workflows/ci.yaml
vendored
Normal file
@@ -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
|
||||
68
.gitignore
vendored
Normal file
68
.gitignore
vendored
Normal file
@@ -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
|
||||
|
||||
39
CMakeLists.txt
Normal file
39
CMakeLists.txt
Normal file
@@ -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)
|
||||
62
CgeFile.h
Normal file
62
CgeFile.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#ifndef CGE_FILE_H
|
||||
#define CGE_FILE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
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 */
|
||||
31
File.c
Normal file
31
File.c
Normal file
@@ -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;
|
||||
}
|
||||
12
LICENSE
Normal file
12
LICENSE
Normal file
@@ -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.
|
||||
29
Makefile.lint
Normal file
29
Makefile.lint
Normal file
@@ -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
|
||||
23
Makefile.mingw
Normal file
23
Makefile.mingw
Normal file
@@ -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
|
||||
33
Makefile.posix
Normal file
33
Makefile.posix
Normal file
@@ -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
|
||||
23
Makefile.win32
Normal file
23
Makefile.win32
Normal file
@@ -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
|
||||
73
README.md
Normal file
73
README.md
Normal file
@@ -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 <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
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.
|
||||
251
posix/File.c
Normal file
251
posix/File.c
Normal file
@@ -0,0 +1,251 @@
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
|
||||
#include "../CgeFile.h"
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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;
|
||||
}
|
||||
242
win32/File.c
Normal file
242
win32/File.c
Normal file
@@ -0,0 +1,242 @@
|
||||
#include "../CgeFile.h"
|
||||
#include <windows.h>
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user