Initial commit
CI / build-and-analyze (push) Failing after 43s

This commit is contained in:
2026-09-22 19:00:03 +03:00
commit bb5e4eeaa9
13 changed files with 822 additions and 0 deletions
+89
View File
@@ -0,0 +1,89 @@
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 gcc-mingw-w64-x86-64 cmake
- 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: Build with MinGW
run: |
make -f Makefile.posix clean
make -f Makefile.mingw CC=x86_64-w64-mingw32-gcc AR=x86_64-w64-mingw32-ar CFLAGS="-std=c99 -Wall -Wextra -Wpedantic -Werror -O2"
- name: Build with CMake
run: |
cmake -S . -B build
cmake --build build
- 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
View 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
+27
View File
@@ -0,0 +1,27 @@
cmake_minimum_required(VERSION 3.10)
project(CgeConf LANGUAGES C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CONF_SOURCES
Conf.c
Query.c
)
set(CONF_HEADERS
CgeConf.h
CgeConfQuery.h
)
add_library(CgeConf STATIC ${CONF_SOURCES} ${CONF_HEADERS})
target_include_directories(CgeConf PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
install(TARGETS CgeConf
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
install(FILES ${CONF_HEADERS} DESTINATION include)
+33
View File
@@ -0,0 +1,33 @@
#ifndef CGE_CONF_H
#define CGE_CONF_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CgeConfIO {
int (*read)(void *ctx, void *buffer, size_t size, size_t *actual);
void *ctx;
} CgeConfIO;
enum CgeConfEvent {
CGE_CONF_SECTION,
CGE_CONF_KEYVALUE,
};
typedef void (*CgeConfCb)(int type, const char *key, const char *value,
void *user);
int CgeConfFromString(const char *str, CgeConfCb callback, void *user);
int CgeConfFromMemory(const char *buffer, size_t size, CgeConfCb callback,
void *user);
int CgeConfFromFile(const char *path, CgeConfCb callback, void *user);
int CgeConfFromCb(CgeConfIO *io, CgeConfCb callback, void *user);
#ifdef __cplusplus
}
#endif
#endif /* CGE_CONF_H */
+30
View File
@@ -0,0 +1,30 @@
#ifndef CGE_CONF_QUERY_H
#define CGE_CONF_QUERY_H
#include "CgeConf.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct CgeConfQuery CgeConfQuery;
typedef void (*CgeConfQueryIterCb)(const char *section, const char *key,
const char *value, void *user);
CgeConfQuery *CgeConfQueryFromCb(CgeConfIO *io);
CgeConfQuery *CgeConfQueryFromFile(const char *file);
CgeConfQuery *CgeConfQueryFromMemory(const char *buffer, size_t size);
CgeConfQuery *CgeConfQueryFromString(const char *str);
void CgeConfQueryFree(CgeConfQuery *query);
const char *CgeConfQueryGet(CgeConfQuery *query, const char *section,
const char *key, const char *defaultValue);
void CgeConfQueryIter(CgeConfQuery *query, CgeConfQueryIterCb iter,
void *user);
#ifdef __cplusplus
}
#endif
#endif /* CGE_CONF_QUERY_H */
+188
View File
@@ -0,0 +1,188 @@
#include "CgeConf.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define MAX_LINE 4096
typedef struct StrView {
char *data;
size_t size;
} StrView;
typedef struct MemCtx {
const char *buffer;
size_t size;
size_t pos;
} MemCtx;
static int memRead(void *ctx, void *buffer, size_t size, size_t *actual) {
MemCtx *mem = (MemCtx *)ctx;
size_t remaining = mem->size - mem->pos;
if (size > remaining)
size = remaining;
memcpy(buffer, mem->buffer + mem->pos, size);
mem->pos += size;
*actual = size;
return 1;
}
static int fileRead(void *ctx, void *buffer, size_t size, size_t *actual) {
*actual = fread(buffer, 1, size, (FILE *)ctx);
return 1;
}
static void trimLeft(StrView *view) {
for (; view->size && isspace(*view->data); ++view->data, --view->size);
}
static void trimRight(StrView *view) {
char *last = view->data + view->size;
for (; last > view->data && isspace(*(last - 1)); last--, view->size--);
}
static void trim(StrView *view) {
trimLeft(view);
trimRight(view);
}
static size_t firstIndex(StrView *view, char symbol) {
char *current, *end;
current = view->data;
end = view->data + view->size;
for (; current < end; ++current) {
if (*current == symbol)
return current - view->data;
}
return -1;
}
static int split(StrView *view, StrView *prefix, char delim) {
size_t position;
if ((position = firstIndex(view, delim)) == -1)
return 0;
prefix->data = view->data;
prefix->size = position;
view->data += position;
view->size -= position;
return 1;
}
static void parseLine(StrView *view, CgeConfCb callback, void *user) {
StrView key;
trim(view);
if (!view->size || *view->data == '#' || *view->data == ';')
return;
if (*view->data == '[' && *(view->data + view->size - 1) == ']') {
view->size -= 2;
view->data += 1;
trim(view);
view->data[view->size] = 0;
callback(CGE_CONF_SECTION, NULL, view->data, user);
} else if (split(view, &key, '=')) {
trimRight(&key);
view->data++, view->size--;
trimLeft(view);
if (view->size > 1 && *view->data == '"' &&
*(view->data + view->size - 1) == '"') {
view->size -= 2;
view->data += 1;
}
key.data[key.size] = 0;
view->data[view->size] = 0;
callback(CGE_CONF_KEYVALUE, key.data, view->data, user);
}
}
static int readSymbol(CgeConfIO *io) {
unsigned char symbol;
size_t actual;
if (!io->read(io->ctx, &symbol, 1, &actual) || !actual)
return -1;
return (int)symbol;
}
static size_t readLine(CgeConfIO *io, char *buffer, size_t size) {
int symbol;
size_t counter = 0;
while ((symbol = readSymbol(io)) != -1) {
if (counter && symbol == '\n')
break;
else if (symbol == '\n')
continue;
if (symbol == '\r')
continue;
if (counter >= size - 1)
return -1;
buffer[counter++] = symbol;
}
buffer[counter] = 0;
return counter;
}
int CgeConfFromString(const char *str, CgeConfCb callback, void *user) {
return CgeConfFromMemory(str, strlen(str), callback, user);
}
int CgeConfFromMemory(const char *buffer, size_t size, CgeConfCb callback,
void *user) {
CgeConfIO io;
MemCtx mem;
mem.buffer = buffer;
mem.size = size;
mem.pos = 0;
io.read = memRead;
io.ctx = &mem;
return CgeConfFromCb(&io, callback, user);
}
int CgeConfFromFile(const char *path, CgeConfCb callback, void *user) {
CgeConfIO io;
FILE *file;
int ret;
file = fopen(path, "rb");
if (!file)
return 0;
io.read = fileRead;
io.ctx = file;
ret = CgeConfFromCb(&io, callback, user);
fclose(file);
return ret;
}
int CgeConfFromCb(CgeConfIO *io, CgeConfCb callback, void *user) {
char buffer[MAX_LINE];
StrView view;
while ((view.size = readLine(io, buffer, MAX_LINE))) {
if (view.size == -1)
return 0;
view.data = buffer;
parseLine(&view, callback, user);
}
return 1;
}
+12
View 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
View 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 -not -path "*/win32/*"); 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
View File
@@ -0,0 +1,23 @@
# MinGW Makefile for CgeConf
CC = gcc
AR = ar
CFLAGS = -std=c99 -O2 -Wall -Wextra
ARFLAGS = rcs
TARGET = libCgeConf.a
SOURCES = Conf.c Query.c
OBJECTS = $(SOURCES:.c=.o)
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJECTS)
$(AR) $(ARFLAGS) $@ $^
%.o: %.c CgeConf.h CgeConfQuery.h
$(CC) $(CFLAGS) -c $< -o $@
clean:
del $(OBJECTS) $(TARGET) 2>nul || exit 0
+35
View File
@@ -0,0 +1,35 @@
# POSIX Makefile for CgeConf
CC = gcc
AR = ar
CFLAGS = -std=c99 -O2 -Wall -Wextra -fPIC
ARFLAGS = rcs
TARGET = libCgeConf.a
SOURCES = Conf.c Query.c
OBJECTS = $(SOURCES:.c=.o)
.PHONY: all clean install
all: $(TARGET)
$(TARGET): $(OBJECTS)
$(AR) $(ARFLAGS) $@ $^
%.o: %.c CgeConf.h CgeConfQuery.h
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJECTS) $(TARGET)
install: $(TARGET)
cp $(TARGET) /usr/local/lib/
cp CgeConf.h /usr/local/include/
cp CgeConfQuery.h /usr/local/include/
ldconfig || echo "Run ldconfig manually if needed"
uninstall:
rm -f /usr/local/lib/libCgeConf.a
rm -f /usr/local/include/CgeConf.h
rm -f /usr/local/include/CgeConfQuery.h
ldconfig || true
+23
View File
@@ -0,0 +1,23 @@
# Makefile.win32 for MSVC (NMake) — CgeConf
# Usage: Open "x86 Native Tools Command Prompt", then:
# nmake -f Makefile.win32
CC = cl
LIB = lib
CFLAGS = /c /nologo /W3 /O2
LIBFLAGS = /nologo
TARGET = CgeConf.lib
SOURCES = Conf.c Query.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
+258
View File
@@ -0,0 +1,258 @@
#include "CgeConfQuery.h"
#include <stdlib.h>
#include <string.h>
#define INIT_EXP 4
#define INIT_CAPACITY ((size_t)1 << INIT_EXP)
#define MAX_SECTION 4096
typedef struct {
char *section;
char *key;
char *value;
} Entry;
struct CgeConfQuery {
Entry *entryData;
size_t size;
size_t capacity;
Entry **slotData;
char currentSection[MAX_SECTION];
int exp;
};
static char *stringDup(const char *str) {
size_t length;
char *dest;
length = strlen(str);
if ((dest = malloc(length + 1)))
memcpy(dest, str, length + 1);
return dest;
}
static size_t hashKey(const char *section, const char *key) {
size_t h = 5381;
const char *p;
int c;
for (p = section; (c = *p); ++p)
h = ((h << 5) + h) + (unsigned char)c;
h = ((h << 5) + h);
for (p = key; (c = *p); ++p)
h = ((h << 5) + h) + (unsigned char)c;
return h;
}
static size_t lookup(size_t hash, int exp, size_t index) {
size_t mask = (1 << exp) - 1;
size_t step = (hash >> (sizeof(hash) * 8 - exp)) | 1;
return (index + step) & mask;
}
static int reserveSpace(CgeConfQuery *query, size_t need) {
CgeConfQuery tmp;
size_t i, hash, index;
if (need <= (query->capacity - (query->capacity >> 2)))
return 1;
tmp.exp = query->exp ? query->exp + 1 : INIT_EXP;
tmp.capacity = query->capacity ? query->capacity * 2 : INIT_CAPACITY;
tmp.size = 0;
tmp.entryData = malloc(tmp.capacity * sizeof(*tmp.entryData));
tmp.slotData = malloc(tmp.capacity * sizeof(*tmp.slotData));
if (!tmp.entryData || !tmp.slotData) {
free(tmp.entryData);
free(tmp.slotData);
return 0;
}
memset(tmp.slotData, 0, sizeof(*tmp.slotData) * tmp.capacity);
for (i = 0; i < query->size; ++i) {
Entry *entry = &query->entryData[i];
hash = hashKey(entry->section, entry->key);
index = lookup(hash, tmp.exp, 0);
while (tmp.slotData[index] != NULL)
index = lookup(hash, tmp.exp, index);
tmp.entryData[tmp.size] = *entry;
tmp.slotData[index] = tmp.entryData + tmp.size++;
}
free(query->entryData);
free(query->slotData);
query->entryData = tmp.entryData;
query->slotData = tmp.slotData;
query->capacity = tmp.capacity;
query->exp = tmp.exp;
return 1;
}
static int insert(CgeConfQuery *query, const char *section, const char *key,
const char *value) {
size_t hash, index;
char *newValue;
Entry *entry;
if (!reserveSpace(query, query->size + 1))
return 0;
hash = hashKey(section, key);
index = lookup(hash, query->exp, 0);
while ((entry = query->slotData[index])) {
if (strcmp(entry->section, section) == 0 &&
strcmp(entry->key, key) == 0) {
newValue = stringDup(value);
if (!newValue)
return 0;
free(entry->value);
entry->value = newValue;
return 1;
}
index = lookup(hash, query->exp, index);
}
entry = &query->entryData[query->size];
entry->section = stringDup(section);
entry->key = stringDup(key);
entry->value = stringDup(value);
if (!entry->section || !entry->key || !entry->value) {
free(entry->section);
free(entry->key);
free(entry->value);
return 0;
}
query->size++;
query->slotData[index] = entry;
return 1;
}
static void populateCb(int type, const char *key, const char *value,
void *user) {
CgeConfQuery *query = (CgeConfQuery *)user;
size_t len;
if (type == CGE_CONF_SECTION) {
len = strlen(value);
if (len >= sizeof(query->currentSection))
len = sizeof(query->currentSection) - 1;
memcpy(query->currentSection, value, len);
query->currentSection[len] = 0;
} else if (type == CGE_CONF_KEYVALUE) {
insert(query, query->currentSection, key, value);
}
}
static CgeConfQuery *queryNew(void) {
CgeConfQuery *query;
query = (CgeConfQuery *)malloc(sizeof(CgeConfQuery));
if (!query)
return NULL;
memset(query, 0, sizeof(*query));
return query;
}
CgeConfQuery *CgeConfQueryFromCb(CgeConfIO *io) {
CgeConfQuery *query;
query = queryNew();
if (!query)
return NULL;
if (!CgeConfFromCb(io, populateCb, query)) {
CgeConfQueryFree(query);
return NULL;
}
return query;
}
CgeConfQuery *CgeConfQueryFromFile(const char *file) {
CgeConfQuery *query;
query = queryNew();
if (!query)
return NULL;
if (!CgeConfFromFile(file, populateCb, query)) {
CgeConfQueryFree(query);
return NULL;
}
return query;
}
CgeConfQuery *CgeConfQueryFromMemory(const char *buffer, size_t size) {
CgeConfQuery *query;
query = queryNew();
if (!query)
return NULL;
if (!CgeConfFromMemory(buffer, size, populateCb, query)) {
CgeConfQueryFree(query);
return NULL;
}
return query;
}
CgeConfQuery *CgeConfQueryFromString(const char *str) {
return CgeConfQueryFromMemory(str, strlen(str));
}
const char *CgeConfQueryGet(CgeConfQuery *query, const char *section,
const char *key, const char *defaultValue) {
size_t hash, index, i;
Entry *entry;
if (!query || !key)
return defaultValue;
if (!section)
section = "";
hash = hashKey(section, key);
index = lookup(hash, query->exp, 0);
for (i = 0; i < query->capacity; ++i) {
entry = query->slotData[index];
if (!entry)
break;
if (strcmp(entry->section, section) == 0 &&
strcmp(entry->key, key) == 0)
return entry->value;
index = lookup(hash, query->exp, index);
}
return defaultValue;
}
void CgeConfQueryFree(CgeConfQuery *query) {
size_t i;
if (!query)
return;
for (i = 0; i < query->size; ++i) {
free(query->entryData[i].section);
free(query->entryData[i].key);
free(query->entryData[i].value);
}
free(query->entryData);
free(query->slotData);
free(query);
}
void CgeConfQueryIter(CgeConfQuery *query, CgeConfQueryIterCb iter,
void *user) {
Entry *entry, *end;
entry = query->entryData;
end = query->entryData + query->size;
for (; entry != end; ++entry)
iter(entry->section, entry->key, entry->value, user);
}
+7
View File
@@ -0,0 +1,7 @@
# CgeArgs - CLI argument parsing
TODO
## License
0BSD - a permissive license with no attribution required.