add some dependencies and test code

This commit is contained in:
2025-03-17 22:54:34 +02:00
parent 21bf4d8715
commit b0be5aec60
23 changed files with 1993 additions and 43 deletions

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "3rd/sc"]
path = 3rd/sc
url = https://github.com/tezc/sc.git
[submodule "3rd/str"]
path = 3rd/str
url = https://github.com/maxim2266/str.git

1
3rd/sc Submodule

Submodule 3rd/sc added at 81fd6b1698

1
3rd/str Submodule

Submodule 3rd/str added at 05fab479f9

View File

@@ -1,16 +1,48 @@
cmake_minimum_required(VERSION 3.21) cmake_minimum_required(VERSION 3.21)
project(Hiload) project(Hiload)
# I just like to have this with my tooling # I just like to have this with my tooling. Also might be required for proper hotreloading.
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD 11)
# Conditionally define _GNU_SOURCE for Linux systems
if(UNIX AND NOT APPLE)
# This checks for Linux (UNIX but not macOS)
add_compile_definitions(_GNU_SOURCE)
message(STATUS "Defining _GNU_SOURCE for Unix build")
endif()
# Handle 3rd party dependencies
# #############################
set(COPY_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/scripts/copy_dependencies.sh)
execute_process(
COMMAND bash ${COPY_SCRIPT}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE SCRIPT_RESULT
OUTPUT_VARIABLE SCRIPT_OUTPUT
ERROR_VARIABLE SCRIPT_ERROR
)
# Check if the script executed successfully
if(NOT SCRIPT_RESULT EQUAL 0)
message(WARNING "Script execution failed: ${SCRIPT_ERROR}")
else()
message(STATUS "Dependencies copied:\n${SCRIPT_OUTPUT}")
endif()
# hiload library # hiload library
# ############## # ##############
add_library(hiload SHARED add_library(hiload SHARED
src/hiload.c src/hiload.c
src/symbols.c src/symbols.c
# dependencies
src/str.c
src/logger/sc_log.c
) )
set_property(TARGET hiload PROPERTY POSITION_INDEPENDENT_CODE ON) set_property(TARGET hiload PROPERTY POSITION_INDEPENDENT_CODE ON)
@@ -53,3 +85,8 @@ install(TARGETS auditor-x86_64
LIBRARY DESTINATION lib LIBRARY DESTINATION lib
RUNTIME DESTINATION bin RUNTIME DESTINATION bin
) )
# tests and test projects
# #######################
add_subdirectory(test)

View File

@@ -0,0 +1,43 @@
# clang-toolchain.cmake - Complete toolchain file for Clang
# Specify the compiler
set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_COMPILER clang++)
# Specify the target system
# set(CMAKE_SYSTEM_NAME Linux) # Change to appropriate system (Linux, Darwin, Windows, etc.)
# Specify LLVM binutils
set(CMAKE_AR llvm-ar CACHE FILEPATH "Archive command")
set(CMAKE_NM llvm-nm CACHE FILEPATH "Name mangling command")
set(CMAKE_RANLIB llvm-ranlib CACHE FILEPATH "Archive indexer command")
set(CMAKE_OBJDUMP llvm-objdump CACHE FILEPATH "Object dump command")
set(CMAKE_OBJCOPY llvm-objcopy CACHE FILEPATH "Object copy command")
set(CMAKE_STRIP llvm-strip CACHE FILEPATH "Strip command")
set(CMAKE_LINKER lld CACHE FILEPATH "Linker command")
# Use lld as the linker
# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fuse-ld=lld")
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fuse-ld=lld")
# Set linker flags
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fuse-ld=lld")
# set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fuse-ld=lld")
# set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -fuse-ld=lld")
# Use Clang's libc++ instead of GCC's libstdc++ (optional)
# Uncomment these lines if you want to use libc++
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++ -lc++abi")
# set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libc++ -lc++abi")
# Set compiler optimization flags (optional)
# set(CMAKE_C_FLAGS_RELEASE "-O3 -DNDEBUG")
# set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
# set(CMAKE_C_FLAGS_DEBUG "-g -O0")
# set(CMAKE_CXX_FLAGS_DEBUG "-g -O0")
# Enable address sanitizer (optional)
# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address")

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
cp --verbose "3rd/str/str.h" "src/"
cp --verbose "3rd/str/str.c" "src/"
cp --verbose "3rd/sc/array/sc_array.h" "src/array/"
cp --verbose "3rd/sc/logger/sc_log.c" "src/logger/"
cp --verbose "3rd/sc/logger/sc_log.h" "src/logger/"
exit 0

6
src/array.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef ARRAY_H_
#define ARRAY_H_
#include "array/sc_array.h"
#endif // ARRAY_H_

224
src/array/sc_array.h Normal file
View File

@@ -0,0 +1,224 @@
/*
* BSD-3-Clause
*
* Copyright 2021 Ozan Tezcan
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef SC_ARRAY_H
#define SC_ARRAY_H
#include <assert.h>
#include <string.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#define SC_ARRAY_VERSION "2.0.0"
#ifdef SC_HAVE_CONFIG_H
#include "config.h"
#else
#define sc_array_realloc realloc
#define sc_array_free free
#endif
#ifndef SC_ARRAY_MAX
#define SC_ARRAY_MAX SIZE_MAX
#endif
#define sc_array_def(T, name) \
struct sc_array_##name { \
bool oom; \
size_t cap; \
size_t size; \
/* NOLINTNEXTLINE */ \
T *elems; \
}
/**
* Init array
* @param a array
*/
#define sc_array_init(a) \
do { \
memset((a), 0, sizeof(*(a))); \
} while (0)
/**
* Destroy array
* @param a array
*/
#define sc_array_term(a) \
do { \
sc_array_free((a)->elems); \
sc_array_init(a); \
} while (0)
/**
* Add elem to array, call sc_array_oom(v) to see if 'add' failed because of out
* of memory.
*
* @param a array
* @param k elem
*/
#define sc_array_add(a, k) \
do { \
const size_t _max = SC_ARRAY_MAX / sizeof(*(a)->elems); \
size_t _cap; \
void *_p; \
\
if ((a)->cap == (a)->size) { \
if ((a)->cap > _max / 2) { \
(a)->oom = true; \
break; \
} \
_cap = (a)->cap == 0 ? 8 : (a)->cap * 2; \
_p = sc_array_realloc((a)->elems, \
_cap * sizeof(*((a)->elems))); \
if (_p == NULL) { \
(a)->oom = true; \
break; \
} \
(a)->cap = _cap; \
(a)->elems = _p; \
} \
(a)->oom = false; \
(a)->elems[(a)->size++] = k; \
} while (0)
/**
* Deletes items from the array without deallocating underlying memory
* @param a array
*/
#define sc_array_clear(a) \
do { \
(a)->size = 0; \
(a)->oom = false; \
} while (0)
/**
* @param a array
* @return true if last add operation failed, false otherwise.
*/
#define sc_array_oom(a) ((a)->oom)
/**
* Get element at index i, if 'i' is out of range, result is undefined.
*
* @param a array
* @param i index
* @return element at index 'i'
*/
#define sc_array_at(a, i) ((a)->elems[i])
/**
* @param a array
* @return element count
*/
#define sc_array_size(a) ((a)->size)
/**
* @param a array
* @param i element index, If 'i' is out of the range, result is undefined.
*/
#define sc_array_del(a, i) \
do { \
size_t idx = (i); \
assert(idx < (a)->size); \
\
const size_t _cnt = (a)->size - (idx) - 1; \
if (_cnt > 0) { \
memmove(&((a)->elems[idx]), &((a)->elems[idx + 1]), \
_cnt * sizeof(*((a)->elems))); \
} \
(a)->size--; \
} while (0)
/**
* Deletes the element at index i, replaces last element with the deleted
* element unless deleted element is the last element. This is faster than
* moving elements but elements will no longer be in the 'add order'
*
* arr[a,b,c,d,e,f] -> sc_array_del_unordered(arr, 2) - > arr[a,b,f,d,e]
*
* @param a array
* @param i index. If 'i' is out of the range, result is undefined.
*/
#define sc_array_del_unordered(a, i) \
do { \
size_t idx = (i); \
assert(idx < (a)->size); \
(a)->elems[idx] = (a)->elems[(--(a)->size)]; \
} while (0)
/**
* Deletes the last element. If current size is zero, result is undefined.
* @param a array
*/
#define sc_array_del_last(a) \
do { \
assert((a)->size != 0); \
(a)->size--; \
} while (0)
/**
* Sorts the array using qsort()
* @param a array
* @param cmp comparator, check qsort() documentation for details
*/
#define sc_array_sort(a, cmp) \
(qsort((a)->elems, (a)->size, sizeof(*(a)->elems), cmp))
/**
* Returns last element. If array is empty, result is undefined.
* @param a array
*/
#define sc_array_last(a) (a)->elems[(a)->size - 1]
/**
* @param a array
* @param elem elem
*/
#define sc_array_foreach(a, elem) \
for (size_t _k = 1, _i = 0; _k && _i != (a)->size; _k = !_k, _i++) \
for ((elem) = (a)->elems[_i]; _k; _k = !_k)
// (type, name)
sc_array_def(int, int);
sc_array_def(unsigned int, uint);
sc_array_def(long, long);
sc_array_def(long long, ll);
sc_array_def(unsigned long, ulong);
sc_array_def(unsigned long long, ull);
sc_array_def(uint32_t, 32);
sc_array_def(uint64_t, 64);
sc_array_def(double, double);
sc_array_def(const char *, str);
sc_array_def(void *, ptr);
#endif

View File

@@ -1,5 +1,3 @@
#define _GNU_SOURCE
#include <link.h> #include <link.h>
#include <stdio.h> #include <stdio.h>
@@ -52,7 +50,7 @@ void la_preinit(uintptr_t *cookie) { printf("la_preinit(): %p\n", cookie); }
uintptr_t la_symbind32(Elf32_Sym *sym, unsigned int ndx, uintptr_t *refcook, uintptr_t la_symbind32(Elf32_Sym *sym, unsigned int ndx, uintptr_t *refcook,
uintptr_t *defcook, unsigned int *flags, uintptr_t *defcook, unsigned int *flags,
const char *symname) { const char *symname) {
printf("la_symbind64(): symname = %s; sym->st_value = %p\n", symname, printf("la_symbind64(): symname = %s; sym->st_value = %u\n", symname,
sym->st_value); sym->st_value);
printf(" ndx = %u; flags = %#x", ndx, *flags); printf(" ndx = %u; flags = %#x", ndx, *flags);
printf("; refcook = %p; defcook = %p\n", refcook, defcook); printf("; refcook = %p; defcook = %p\n", refcook, defcook);

View File

@@ -1,26 +0,0 @@
#ifndef AUDITOR_H_
#define AUDITOR_H_
#define _GNU_SOURCE
#include <link.h>
#include <stdint.h>
unsigned int la_version(unsigned int version);
char *la_objsearch(const char *name, uintptr_t *cookie, unsigned int flag);
void la_activity(uintptr_t *cookie, unsigned int flag);
unsigned int la_objopen(struct link_map *map, Lmid_t lmid, uintptr_t *cookie);
unsigned int la_objclose(uintptr_t *cookie);
void la_preinit(uintptr_t *cookie);
uintptr_t la_symbind32(Elf32_Sym *sym, unsigned int ndx, uintptr_t *refcook,
uintptr_t *defcook, unsigned int *flags,
const char *symname);
uintptr_t la_symbind64(Elf64_Sym *sym, unsigned int ndx, uintptr_t *refcook,
uintptr_t *defcook, unsigned int *flags,
const char *symname);
/* Elf32_Addr la_i86_gnu_pltenter(Elf32_Sym *sym, unsigned int ndx, */
/* uintptr_t *refcook, uintptr_t *defcook, */
/* La_i86_regs *regs, unsigned int *flags, */
/* const char *symname, long *framesizep); */
#endif // AUDITOR_H_

11
src/compilation.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef COMPILATION_H_
#define COMPILATION_H_
typedef struct {
const char *directory;
const char *file;
const char *command;
} CompileCommand;
#endif // COMPILATION_H_

View File

@@ -1,6 +1,3 @@
// Required for dlinfo and other GNU specific linker code
#define _GNU_SOURCE
#include "hiload/hiload.h" #include "hiload/hiload.h"
#include "hiload/symbols.h" #include "hiload/symbols.h"

6
src/logger.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef LOGGER_H_
#define LOGGER_H_
#include "logger/sc_log.h"
#endif // LOGGER_H_

451
src/logger/sc_log.c Normal file
View File

@@ -0,0 +1,451 @@
/*
* BSD-3-Clause
*
* Copyright 2021 Ozan Tezcan
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 700
#endif
#include "sc_log.h"
#include <ctype.h>
#include <errno.h>
#include <time.h>
// clang-format off
#ifndef thread_local
#if __STDC_VERSION__ >= 201112 && !defined __STDC_NO_THREADS__
#define thread_local _Thread_local
#elif defined _WIN32 && (defined _MSC_VER || defined __ICL || \
defined __DMC__ || defined __BORLANDC__)
#define thread_local __declspec(thread)
#elif defined __GNUC__ || defined __SUNPRO_C || defined __xlC__
#define thread_local __thread
#else
#error "Cannot define thread_local"
#endif
#endif
#if __STDC_VERSION__ >= 201112 && !defined __STDC_NO_ATOMIC__
#define SC_ATOMIC
#include <stdatomic.h>
#define sc_atomic _Atomic
#define sc_atomic_store(var, val) \
(atomic_store_explicit(var, val, memory_order_relaxed))
#define sc_atomic_load(var) \
(atomic_load_explicit(var, memory_order_relaxed))
#else
#define sc_atomic
#define sc_atomic_store(var, val) ((*(var)) = (val))
#define sc_atomic_load(var) (*(var))
#endif
// clang-format on
thread_local char sc_name[32] = "Thread";
#if defined(_WIN32) || defined(_WIN64)
#ifdef _MSC_VER
#pragma warning(disable : 4996)
#endif
#define strcasecmp _stricmp
#define localtime_r(a, b) (localtime_s(b, a) == 0 ? b : NULL)
#include <windows.h>
struct sc_log_mutex {
CRITICAL_SECTION mtx;
};
int sc_log_mutex_init(struct sc_log_mutex *mtx)
{
InitializeCriticalSection(&mtx->mtx);
return 0;
}
int sc_log_mutex_term(struct sc_log_mutex *mtx)
{
DeleteCriticalSection(&mtx->mtx);
return 0;
}
void sc_log_mutex_lock(struct sc_log_mutex *mtx)
{
EnterCriticalSection(&mtx->mtx);
}
void sc_log_mutex_unlock(struct sc_log_mutex *mtx)
{
LeaveCriticalSection(&mtx->mtx);
}
#else
#include <pthread.h>
struct sc_log_mutex {
pthread_mutex_t mtx;
};
int sc_log_mutex_init(struct sc_log_mutex *mtx)
{
int rc;
pthread_mutexattr_t attr;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
mtx->mtx = mut;
rc = pthread_mutexattr_init(&attr);
if (rc != 0) {
return rc;
}
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
rc = pthread_mutex_init(&mtx->mtx, &attr);
pthread_mutexattr_destroy(&attr);
return rc;
}
void sc_log_mutex_term(struct sc_log_mutex *mtx)
{
pthread_mutex_destroy(&mtx->mtx);
}
void sc_log_mutex_lock(struct sc_log_mutex *mtx)
{
pthread_mutex_lock(&mtx->mtx);
}
void sc_log_mutex_unlock(struct sc_log_mutex *mtx)
{
pthread_mutex_unlock(&mtx->mtx);
}
#endif
struct sc_log {
FILE *fp;
char current_file[256];
char prev_file[256];
size_t file_size;
struct sc_log_mutex mtx;
sc_atomic enum sc_log_level level;
bool init;
bool to_stdout;
void *arg;
int (*cb)(void *, enum sc_log_level, const char *, va_list);
};
struct sc_log sc_log;
int sc_log_init(void)
{
int rc;
sc_log = (struct sc_log){0};
sc_atomic_store(&sc_log.level, SC_LOG_INFO);
sc_log.to_stdout = true;
rc = sc_log_mutex_init(&sc_log.mtx);
if (rc != 0) {
errno = rc;
}
sc_log.init = true;
return rc;
}
int sc_log_term(void)
{
int rc = 0;
if (!sc_log.init) {
return -1;
}
if (sc_log.fp) {
rc = fclose(sc_log.fp);
if (rc != 0) {
rc = -1;
}
}
sc_log_mutex_term(&sc_log.mtx);
sc_log = (struct sc_log){0};
return rc;
}
void sc_log_set_thread_name(const char *name)
{
strncpy(sc_name, name, sizeof(sc_name) - 1);
}
static int sc_strcasecmp(const char *a, const char *b)
{
int n;
for (;; a++, b++) {
if (*a == 0 && *b == 0) {
return 0;
}
n = tolower(*a) - tolower(*b);
if (n != 0) {
return n;
}
}
}
int sc_log_set_level(const char *str)
{
size_t count = sizeof(sc_log_levels) / sizeof(sc_log_levels[0]);
for (size_t i = 0; i < count; i++) {
if (sc_strcasecmp(str, sc_log_levels[i].str) == 0) {
#ifdef SC_ATOMIC
sc_atomic_store(&sc_log.level, sc_log_levels[i].id);
#else
sc_log_mutex_lock(&sc_log.mtx);
sc_log.level = sc_log_levels[i].id;
sc_log_mutex_unlock(&sc_log.mtx);
#endif
return 0;
}
}
return -1;
}
void sc_log_set_stdout(bool enable)
{
sc_log_mutex_lock(&sc_log.mtx);
sc_log.to_stdout = enable;
sc_log_mutex_unlock(&sc_log.mtx);
}
int sc_log_set_file(const char *prev, const char *current)
{
int rc = 0, saved_errno = 0;
long size;
FILE *fp = NULL;
sc_log_mutex_lock(&sc_log.mtx);
if (sc_log.fp != NULL) {
fclose(sc_log.fp);
sc_log.fp = NULL;
}
sc_log.prev_file[0] = '\0';
sc_log.current_file[0] = '\0';
if (prev == NULL || current == NULL) {
goto out;
}
if (strlen(prev) >= sizeof(sc_log.prev_file) - 1 ||
strlen(current) >= sizeof(sc_log.current_file) - 1) {
goto error;
}
memcpy(sc_log.prev_file, prev, strlen(prev) + 1);
memcpy(sc_log.current_file, current, strlen(current) + 1);
fp = fopen(sc_log.current_file, "a+");
if (fp == NULL) {
goto error;
}
if (fprintf(fp, "\n") < 0) {
goto error;
}
size = ftell(fp);
if (size < 0) {
goto error;
}
sc_log.file_size = (size_t) size;
sc_log.fp = fp;
goto out;
error:
rc = -1;
saved_errno = errno;
if (fp != NULL) {
fclose(fp);
}
out:
sc_log_mutex_unlock(&sc_log.mtx);
errno = saved_errno;
return rc;
}
void sc_log_set_callback(void *arg, int (*cb)(void *, enum sc_log_level,
const char *, va_list))
{
sc_log_mutex_lock(&sc_log.mtx);
sc_log.arg = arg;
sc_log.cb = cb;
sc_log_mutex_unlock(&sc_log.mtx);
}
static int sc_log_print_header(FILE *fp, enum sc_log_level level)
{
int rc;
time_t t;
struct tm result, *tm;
t = time(NULL);
tm = localtime_r(&t, &result);
if (tm == NULL) {
return -1;
}
rc = fprintf(fp, "[%d-%02d-%02d %02d:%02d:%02d][%-5s][%s] ",
tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec,
sc_log_levels[level].str, sc_name);
if (rc < 0) {
return -1;
}
return 0;
}
static int sc_log_stdout(enum sc_log_level level, const char *fmt, va_list va)
{
int rc;
FILE *dest = level == SC_LOG_ERROR ? stderr : stdout;
rc = sc_log_print_header(dest, level);
if (rc < 0) {
return -1;
}
rc = vfprintf(dest, fmt, va);
if (rc < 0) {
return -1;
}
return 0;
}
static int sc_log_file(enum sc_log_level level, const char *fmt, va_list va)
{
int rc, size;
rc = sc_log_print_header(sc_log.fp, level);
if (rc < 0) {
return -1;
}
size = vfprintf(sc_log.fp, fmt, va);
if (size < 0) {
return -1;
}
sc_log.file_size += size;
if (sc_log.file_size > (size_t) SC_LOG_FILE_SIZE) {
fclose(sc_log.fp);
(void) rename(sc_log.current_file, sc_log.prev_file);
sc_log.fp = fopen(sc_log.current_file, "w+");
if (sc_log.fp == NULL) {
return -1;
}
sc_log.file_size = 0;
}
return rc;
}
int sc_log_log(enum sc_log_level level, const char *fmt, ...)
{
int rc = 0;
va_list va;
// Use relaxed atomics to avoid locking cost, e.g., DEBUG logs when
// level=INFO will get away without any synchronization on most
// platforms.
#ifdef SC_ATOMIC
enum sc_log_level curr;
curr = sc_atomic_load(&sc_log.level);
if (level < curr) {
return 0;
}
#endif
sc_log_mutex_lock(&sc_log.mtx);
#ifndef SC_ATOMIC
if (level < sc_log.level) {
sc_log_mutex_unlock(&sc_log.mtx);
return 0;
}
#endif
if (sc_log.to_stdout) {
va_start(va, fmt);
rc |= sc_log_stdout(level, fmt, va);
va_end(va);
}
if (sc_log.fp != NULL) {
va_start(va, fmt);
rc |= sc_log_file(level, fmt, va);
va_end(va);
}
if (sc_log.cb) {
va_start(va, fmt);
rc |= sc_log.cb(sc_log.arg, level, fmt, va);
va_end(va);
}
sc_log_mutex_unlock(&sc_log.mtx);
return rc;
}

146
src/logger/sc_log.h Normal file
View File

@@ -0,0 +1,146 @@
/*
* BSD-3-Clause
*
* Copyright 2021 Ozan Tezcan
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef SC_LOG_H
#define SC_LOG_H
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SC_LOG_VERSION "2.0.0"
enum sc_log_level
{
SC_LOG_DEBUG,
SC_LOG_INFO,
SC_LOG_WARN,
SC_LOG_ERROR,
SC_LOG_OFF,
};
// clang-format off
static const struct sc_log_level_pair
{
const enum sc_log_level id;
const char *str;
} sc_log_levels[] = {
{SC_LOG_DEBUG, "DEBUG"},
{SC_LOG_INFO, "INFO" },
{SC_LOG_WARN, "WARN" },
{SC_LOG_ERROR, "ERROR"},
{SC_LOG_OFF, "OFF" },
};
// clang-format on
// Internal function
int sc_log_log(enum sc_log_level level, const char *fmt, ...);
// Max file size to rotate, should not be more than 4 GB.
#define SC_LOG_FILE_SIZE (2 * 1024 * 1024)
// Define SC_LOG_PRINT_FILE_NAME to print file name and line no in the log line.
#ifdef SC_LOG_PRINT_FILE_NAME
#define sc_log_ap(fmt, ...) \
"(%s:%d) " fmt, strrchr("/" __FILE__, '/') + 1, __LINE__, __VA_ARGS__
#else
#define sc_log_ap(fmt, ...) fmt, __VA_ARGS__
#endif
/**
* sc_log_init() call once in your application before using log functions.
* sc_log_term() call once to clean up, you must not use logger after this call.
* These functions are not thread-safe, should be called from a single thread.
*
* @return '0' on success, negative value on error, errno will be set.
*/
int sc_log_init(void);
int sc_log_term(void);
/**
* Set thread name.
*
* Call once from each thread if you want to set thread name.
* @param name Thread name
*/
void sc_log_set_thread_name(const char *name);
/**
* Set log level.
*
* @param level One of "DEBUG", "INFO", "WARN", "ERROR", "OFF"
* @return '0' on success, negative value on invalid level string
*/
int sc_log_set_level(const char *level);
/**
* Enable stdout logging.
*
* @param enable 'true' to enable, 'false' to disable logging to stdout.
*/
void sc_log_set_stdout(bool enable);
/**
* Enable file logging.
*
* Log files will be rotated to prevent generating very big files. Once current
* log file reaches 'SC_LOG_FILE_SIZE' (see definition above), it will be
* renamed as `prev` and the latest logs will always be in the 'current' file.
*
* e.g., sc_log_set_file("/tmp/log.0.txt", "/tmp/log-latest.txt");
*
* To disable logging into file: sc_log_set_file(NULL, NULL);
*
* @param prev file path for previous log file, 'NULL' to disable
* @param current file path for latest log file, 'NULL' to disable
* @return 0 on success, -1 on error, errno will be set.
*/
int sc_log_set_file(const char *prev, const char *current);
/**
* Enabled logging to callback.
*
* @param arg user arg to callback.
* @param cb log callback.
*/
void sc_log_set_callback(void *arg,
int (*cb)(void *arg, enum sc_log_level level,
const char *fmt, va_list va));
// e.g., sc_log_error("Errno: %d, reason: %s", errno, strerror(errno));
#define sc_log_debug(...) (sc_log_log(SC_LOG_DEBUG, sc_log_ap(__VA_ARGS__, "")))
#define sc_log_info(...) (sc_log_log(SC_LOG_INFO, sc_log_ap(__VA_ARGS__, "")))
#define sc_log_warn(...) (sc_log_log(SC_LOG_WARN, sc_log_ap(__VA_ARGS__, "")))
#define sc_log_error(...) (sc_log_log(SC_LOG_ERROR, sc_log_ap(__VA_ARGS__, "")))
#endif

740
src/str.c Normal file
View File

@@ -0,0 +1,740 @@
/*
BSD 3-Clause License
Copyright (c) 2020,2021,2022,2023,2024 Maxim Konakov and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#define _DEFAULT_SOURCE // for strncasecmp()
#define _XOPEN_SOURCE 500 // for IOV_MAX
#include "str.h"
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// append to destination and return the end pointer
static inline
void* mem_append(void* dest, const void* src, const size_t n)
{
return memcpy(dest, src, n) + n;
}
// string deallocation
void str_free(const str s)
{
if(str_is_owner(s))
free((void*)s.ptr);
}
// version of str_free() for str_auto macro
void str_free_auto(const str* const ps)
{
if(ps)
str_free(*ps);
}
// memory allocation helpers
#define ALLOC(n) \
({ \
void* const ___p = malloc(n); \
if(!___p) return ENOMEM; \
___p; \
})
// errno checker
#define RETURN_ON_ERROR(expr) \
while((expr) < 0) do { const int __err = errno; if(__err != EINTR) return __err; } while(0)
// swap
void str_swap(str* const s1, str* const s2)
{
const str tmp = *s1;
*s1 = *s2;
*s2 = tmp;
}
// empty string
const char* const str_empty_string = "";
// string comparison ---------------------------------------------------------------------
// compare two strings lexicographically
int str_cmp(const str s1, const str s2)
{
const size_t n1 = str_len(s1), n2 = str_len(s2);
// either string may be missing a null terminator, hence "memcmp"
const int res = memcmp(str_ptr(s1), str_ptr(s2), (n1 < n2) ? n1 : n2);
if(res != 0 || n1 == n2)
return res;
return (n1 < n2) ? -1 : 1;
}
// case-insensitive comparison
int str_cmp_ci(const str s1, const str s2)
{
const size_t n1 = str_len(s1), n2 = str_len(s2);
// either string may be missing a null terminator, hence "strNcasecmp"
const int res = strncasecmp(str_ptr(s1), str_ptr(s2), (n1 < n2) ? n1 : n2);
if(res != 0 || n1 == n2)
return res;
return (n1 < n2) ? -1 : 1;
}
// test for prefix
bool str_has_prefix(const str s, const str prefix)
{
const size_t n = str_len(prefix);
return (n == 0)
|| (str_len(s) >= n && memcmp(str_ptr(s), str_ptr(prefix), n) == 0);
}
// test for suffix
bool str_has_suffix(const str s, const str suffix)
{
const size_t n = str_len(suffix);
return (n == 0)
|| (str_len(s) >= n && memcmp(str_end(s) - n, str_ptr(suffix), n) == 0);
}
// string constructors -----------------------------------------------------------------
// create a reference to the given range of chars
str str_ref_chars(const char* const s, const size_t n)
{
return (s && n > 0) ? ((str){ s, str_ref_info(n) }) : str_null;
}
str str_ref_from_ptr(const char* const s)
{
return s ? str_ref_chars(s, strlen(s)) : str_null;
}
// take ownership of the given range of chars
str str_acquire_chars(const char* const s, const size_t n)
{
if(!s)
return str_null;
if(n == 0)
{
free((void*)s);
return str_null;
}
return (str){ s, str_owner_info(n) };
}
// take ownership of the given C string
str str_acquire(const char* const s)
{
return s ? str_acquire_chars(s, strlen(s)) : str_null;
}
// allocate a copy of the given string
int str_dup_impl(str* const dest, const str s)
{
const size_t n = str_len(s);
if(n == 0)
str_clear(dest);
else
{
char* const p = memcpy(ALLOC(n + 1), str_ptr(s), n);
p[n] = 0;
str_assign(dest, str_acquire_chars(p, n));
}
return 0;
}
#ifndef STR_MAX_FILE_SIZE
#define STR_MAX_FILE_SIZE (64 * 1024 * 1024 - 1)
#endif
static
int get_file_size(const int fd, off_t* const size)
{
// stat the file
struct stat info;
RETURN_ON_ERROR(fstat(fd, &info));
*size = info.st_size;
// only regular files are allowed
switch(info.st_mode & S_IFMT)
{
case S_IFREG:
return (info.st_size > STR_MAX_FILE_SIZE) ? EFBIG : 0;
case S_IFDIR:
return EISDIR;
default:
return EOPNOTSUPP;
}
}
static
int read_from_fd(const int fd, void* p, off_t* const psize)
{
const void* const end = p + *psize;
ssize_t n;
do
{
RETURN_ON_ERROR(n = read(fd, p, end - p));
p += n;
} while(n > 0 && p < end);
*psize -= end - p;
return 0;
}
static
int str_from_fd(const int fd, const off_t size, str* const dest)
{
if(size == 0)
{
str_clear(dest);
return 0;
}
char* buff = ALLOC(size + 1);
off_t n = size;
const int err = read_from_fd(fd, buff, &n);
if(err != 0)
{
free(buff);
return err;
}
if(n == 0)
{
free(buff);
str_clear(dest);
return 0;
}
if(n < size)
{
char* const p = realloc(buff, n + 1);
if(!p)
{
free(buff);
return ENOMEM;
}
buff = p;
}
buff[n] = 0;
str_assign(dest, str_acquire_chars(buff, n));
return 0;
}
int str_from_file(str* const dest, const char* const file_name)
{
int fd;
RETURN_ON_ERROR(fd = open(file_name, O_CLOEXEC | O_RDONLY));
off_t size = 0;
int err = get_file_size(fd, &size);
if(err == 0)
err = str_from_fd(fd, size, dest);
close(fd);
return err;
}
// string composition -----------------------------------------------------------------------
// append string
static inline
char* append_str(char* p, const str s)
{
return mem_append(p, str_ptr(s), str_len(s));
}
static
size_t total_length(const str* src, size_t count)
{
size_t sum = 0;
for(; count > 0; --count)
sum += str_len(*src++);
return sum;
}
// concatenate strings
int str_cat_range_impl(str* const dest, const str* src, size_t count)
{
if(!src)
{
str_clear(dest);
return 0;
}
// calculate total length
const size_t num = total_length(src, count);
if(num == 0)
{
str_clear(dest);
return 0;
}
// allocate
char* const buff = ALLOC(num + 1);
// copy bytes
char* p = buff;
for(; count > 0; --count)
p = append_str(p, *src++);
// null-terminate and assign
*p = 0;
str_assign(dest, str_acquire_chars(buff, num));
return 0;
}
// writing to file descriptor
int str_cpy_to_fd(const int fd, const str s)
{
size_t n = str_len(s);
const void* p = str_ptr(s);
while(n > 0)
{
ssize_t m;
RETURN_ON_ERROR(m = write(fd, p, n));
n -= m;
p += m;
}
return 0;
}
// writing to byte stream
int str_cpy_to_stream(FILE* const stream, const str s)
{
const size_t n = str_len(s);
return (n > 0 && fwrite(str_ptr(s), 1, n, stream) < n) ? EIO : 0;
}
// write iovec
static
int write_iovec(const int fd, struct iovec* pv, unsigned nv)
{
while(nv > 0)
{
ssize_t n;
RETURN_ON_ERROR(n = writev(fd, pv, nv));
// discard items already written
for(; nv > 0; ++pv, --nv)
{
if(n < (ssize_t)pv->iov_len)
{
pv->iov_base += n;
pv->iov_len -= n;
break;
}
n -= (ssize_t)pv->iov_len;
}
}
return 0;
}
// concatenate to file descriptor
static
struct iovec* vec_append(struct iovec* const pv, const str s)
{
*pv = (struct iovec){ (void*)str_ptr(s), str_len(s) };
return pv + 1;
}
static
struct iovec* vec_append_nonempty(struct iovec* const pv, const str s)
{
return str_is_empty(s) ? pv : vec_append(pv, s);
}
int str_cat_range_to_fd(const int fd, const str* src, size_t count)
{
if(!src)
return 0;
struct iovec v[IOV_MAX];
while(count > 0)
{
struct iovec* p = vec_append_nonempty(v, *src++);
while(--count > 0 && p < v + IOV_MAX)
p = vec_append_nonempty(p, *src++);
const size_t n = p - v;
if(n == 0)
break;
const int ret = write_iovec(fd, v, n);
if(ret != 0)
return ret;
}
return 0;
}
int str_cat_range_to_stream(FILE* const stream, const str* src, size_t count)
{
if(!src)
return 0;
int err = 0;
for(; count > 0 && err == 0; --count)
err = str_cpy(stream, *src++);
return err;
}
// join strings
int str_join_range_impl(str* const dest, const str sep, const str* src, size_t count)
{
// test for simple cases
if(str_is_empty(sep))
return str_cat_range(dest, src, count);
if(!src || count == 0)
{
str_clear(dest);
return 0;
}
if(count == 1)
return str_cpy(dest, *src);
// calculate total length
const size_t num = total_length(src, count) + str_len(sep) * (count - 1);
// allocate
char* const buff = ALLOC(num + 1);
// copy bytes
char* p = append_str(buff, *src++);
while(--count > 0)
p = append_str(append_str(p, sep), *src++);
// null-terminate and assign
*p = 0;
str_assign(dest, str_acquire_chars(buff, num));
return 0;
}
int str_join_range_to_fd(const int fd, const str sep, const str* src, size_t count)
{
if(str_is_empty(sep))
return str_cat_range(fd, src, count);
if(!src || count == 0)
return 0;
if(count == 1)
return str_cpy(fd, *src);
struct iovec v[IOV_MAX];
struct iovec* p = vec_append_nonempty(v, *src++);
for(--count; count > 0; p = v)
{
p = vec_append_nonempty(vec_append(p, sep), *src++);
while(--count > 0 && p < v + IOV_MAX - 1)
p = vec_append_nonempty(vec_append(p, sep), *src++);
const size_t n = p - v;
if(n == 0)
break;
const int ret = write_iovec(fd, v, n);
if(ret != 0)
return ret;
}
return 0;
}
int str_join_range_to_stream(FILE* const stream, const str sep, const str* src, size_t count)
{
if(str_is_empty(sep))
return str_cat_range(stream, src, count);
if(!src || count == 0)
return 0;
int err = str_cpy(stream, *src++);
while(--count > 0 && err == 0)
err = str_cat(stream, sep, *src++);
return err;
}
// searching and sorting --------------------------------------------------------------------
// string partitioning
bool str_partition(const str src, const str patt, str* const prefix, str* const suffix)
{
const size_t patt_len = str_len(patt);
if(patt_len > 0 && !str_is_empty(src))
{
const char* s = memmem(str_ptr(src), str_len(src), str_ptr(patt), patt_len);
if(s)
{
if(prefix)
str_assign(prefix, str_ref_chars(str_ptr(src), s - str_ptr(src)));
if(suffix)
{
s += patt_len;
str_assign(suffix, str_ref_chars(s, str_end(src) - s));
}
return true;
}
}
if(prefix)
str_assign(prefix, str_ref(src));
if(suffix)
str_clear(suffix);
return false;
}
// comparison functions
int str_order_asc(const void* const s1, const void* const s2)
{
return str_cmp(*(const str*)s1, *(const str*)s2);
}
int str_order_desc(const void* const s1, const void* const s2)
{
return -str_cmp(*(const str*)s1, *(const str*)s2);
}
int str_order_asc_ci(const void* const s1, const void* const s2)
{
return str_cmp_ci(*(const str*)s1, *(const str*)s2);
}
int str_order_desc_ci(const void* const s1, const void* const s2)
{
return -str_cmp_ci(*(const str*)s1, *(const str*)s2);
}
// sorting
void str_sort_range(const str_cmp_func cmp, str* const array, const size_t count)
{
if(array && count > 1)
qsort(array, count, sizeof(array[0]), cmp);
}
// searching
const str* str_search_range(const str key, const str* const array, const size_t count)
{
if(!array || count == 0)
return NULL;
if(count == 1)
return str_eq(key, array[0]) ? array : NULL;
return bsearch(&key, array, count, sizeof(str), str_order_asc);
}
// partitioning
size_t str_partition_range(bool (*pred)(const str), str* const array, const size_t count)
{
if(!array)
return 0;
const str* const end = array + count;
str* p = array;
while(p < end && pred(*p))
++p;
for(str* s = p + 1; s < end; ++s)
if(pred(*s))
str_swap(p++, s);
return p - array;
}
// unique partitioning
size_t str_unique_range(str* const array, const size_t count)
{
if(!array || count == 0)
return 0;
if(count == 1)
return 1;
str_sort_range(str_order_asc, array, count);
const str* const end = array + count;
str* p = array;
for(str* s = array + 1; s < end; ++s)
if(!str_eq(*p, *s) && (++p < s))
str_swap(p, s);
return p + 1 - array;
}
// string iterator function
#ifdef __STDC_UTF_32__
char32_t str_cp_iterator_next(str_cp_iterator* const it)
{
if(it->curr >= it->end)
return CPI_END_OF_STRING;
char32_t c;
const size_t n = mbrtoc32(&c, it->curr, it->end - it->curr, &it->state);
switch(n) // see https://en.cppreference.com/w/c/string/multibyte/mbrtoc32
{
case 0: // null character (U+0000) is allowed
++it->curr;
return 0;
case (size_t)-1: // encoding error
case (size_t)-3: // surrogate pair detected
return CPI_ERR_INVALID_ENCODING;
case (size_t)-2: // incomplete sequence
return CPI_ERR_INCOMPLETE_SEQ;
default: // ok
it->curr += n;
return c;
}
}
#endif // ifdef __STDC_UTF_32__
// tokeniser
static inline
bool is_delim(const str_tok_state* const state, const char c)
{
return state->bits[(unsigned char)c >> 3] & (1 << (c & 0x7));
}
static inline
void set_bit(str_tok_state* const state, const char c)
{
state->bits[(unsigned char)c >> 3] |= (1 << (c & 0x7));
}
void str_tok_delim(str_tok_state* const state, const str delim_set)
{
memset(state->bits, 0, sizeof(state->bits));
const char* const end = str_end(delim_set);
for(const char* s = str_ptr(delim_set); s < end; ++s)
set_bit(state, *s);
}
void str_tok_init(str_tok_state* const state, const str src, const str delim_set)
{
state->src = str_ptr(src);
state->end = str_end(src);
str_tok_delim(state, delim_set);
}
bool str_tok(str* const dest, str_tok_state* const state)
{
// token start
const char* begin = state->src;
while(begin < state->end && is_delim(state, *begin))
++begin;
if(begin == state->end)
{
str_clear(dest);
return false;
}
// token end
const char* end = begin + 1;
while(end < state->end && !is_delim(state, *end))
++end;
state->src = end;
str_assign(dest, str_ref_chars(begin, end - begin));
return true;
}

293
src/str.h Normal file
View File

@@ -0,0 +1,293 @@
/*
BSD 3-Clause License
Copyright (c) 2020,2021,2022,2023,2024 Maxim Konakov and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#include <stdio.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
// string type ----------------------------------------------------------------------------
typedef struct
{
const char* ptr;
size_t info;
} str;
// NULL string
#define str_null ((str){ 0, 0 })
// helper macros
#define str_ref_info(n) ((n) << 1)
#define str_owner_info(n) (str_ref_info(n) | 1)
// string properties ----------------------------------------------------------------------
// length of the string
static inline
size_t str_len(const str s) { return s.info >> 1; }
// pointer to the string
static inline
const char* str_ptr(const str s)
{
extern const char* const str_empty_string;
return s.ptr ? s.ptr : str_empty_string;
}
// end of the string
static inline
const char* str_end(const str s) { return str_ptr(s) + str_len(s); }
// test if the string is empty
static inline
bool str_is_empty(const str s) { return str_len(s) == 0; }
// test if the string is allocated on the heap
static inline
bool str_is_owner(const str s) { return (s.info & 1) != 0; }
// test if the string is a reference
static inline
bool str_is_ref(const str s) { return !str_is_owner(s); }
// string memory control -------------------------------------------------------------------
// free memory allocated for the string
void str_free(const str s);
// automatic cleanup
void str_free_auto(const str* const ps);
#define str_auto str __attribute__((cleanup(str_free_auto)))
// string movements -----------------------------------------------------------------------
// free target string, then assign the new value to it
static inline
void str_assign(str* const ps, const str s) { str_free(*ps); *ps = s; }
// move the string, resetting the source to str_null
static inline
str str_move(str* const ps) { const str t = *ps; *ps = str_null; return t; }
// pass ownership of the string
static inline
str str_pass(str* const ps) { const str t = *ps; ps->info &= ~(size_t)1; return t; }
// swap two string objects
void str_swap(str* const s1, str* const s2);
// string helpers --------------------------------------------------------------------------
// reset the string to str_null
static inline
void str_clear(str* const ps) { str_assign(ps, str_null); }
// compare two strings lexicographically
int str_cmp(const str s1, const str s2);
// test if two strings match
static inline
bool str_eq(const str s1, const str s2) { return str_cmp(s1, s2) == 0; }
// case-insensitive comparison
int str_cmp_ci(const str s1, const str s2);
// case-insensitive match
static inline
bool str_eq_ci(const str s1, const str s2) { return str_cmp_ci(s1, s2) == 0; }
// test for prefix
bool str_has_prefix(const str s, const str prefix);
// test for suffix
bool str_has_suffix(const str s, const str suffix);
// string composition ------------------------------------------------------------------
// implementation helpers
int str_dup_impl(str* const dest, const str s);
int str_cpy_to_fd(const int fd, const str s);
int str_cpy_to_stream(FILE* const stream, const str s);
// copy string
#define str_cpy(dest, src) \
_Generic((dest), \
str*: str_dup_impl, \
int: str_cpy_to_fd, \
FILE*: str_cpy_to_stream \
)((dest), (src))
// implementation helpers
int str_cat_range_impl(str* const dest, const str* src, size_t count);
int str_cat_range_to_fd(const int fd, const str* src, size_t count);
int str_cat_range_to_stream(FILE* const stream, const str* src, size_t count);
// concatenate range of strings
#define str_cat_range(dest, src, count) \
_Generic((dest), \
str*: str_cat_range_impl, \
int: str_cat_range_to_fd, \
FILE*: str_cat_range_to_stream \
)((dest), (src), (count))
// concatenate string arguments
#define str_cat(dest, ...) \
({ \
const str args[] = { __VA_ARGS__ }; \
str_cat_range((dest), args, sizeof(args)/sizeof(args[0])); \
})
// implementation helpers
int str_join_range_impl(str* const dest, const str sep, const str* src, size_t count);
int str_join_range_to_fd(const int fd, const str sep, const str* src, size_t count);
int str_join_range_to_stream(FILE* const stream, const str sep, const str* src, size_t count);
// join strings around the separator
#define str_join_range(dest, sep, src, count) \
_Generic((dest), \
str*: str_join_range_impl, \
int: str_join_range_to_fd, \
FILE*: str_join_range_to_stream \
)((dest), (sep), (src), (count))
// join string arguments around the separator
#define str_join(dest, sep, ...) \
({ \
const str args[] = { __VA_ARGS__ }; \
str_join_range((dest), (sep), args, sizeof(args)/sizeof(args[0])); \
})
// constructors ----------------------------------------------------------------------------
// string reference from a string literal
#define str_lit(s) ((str){ "" s, str_ref_info(sizeof(s) - 1) })
static inline
str str_ref_impl(const str s) { return (str){ s.ptr, s.info & ~(size_t)1 }; }
str str_ref_from_ptr(const char* const s);
// string reference from anything
#define str_ref(s) \
_Generic((s), \
str: str_ref_impl, \
char*: str_ref_from_ptr, \
const char*: str_ref_from_ptr \
)(s)
// create a reference to the given range of chars
str str_ref_chars(const char* const s, const size_t n);
// take ownership of the given range of chars
str str_acquire_chars(const char* const s, const size_t n);
// take ownership of the given string
str str_acquire(const char* const s);
// string from file
int str_from_file(str* const dest, const char* const file_name);
// searching and sorting --------------------------------------------------------------------
// string partitioning (substring search)
bool str_partition(const str src, const str patt, str* const prefix, str* const suffix);
// comparison functions
typedef int (*str_cmp_func)(const void*, const void*);
int str_order_asc(const void* const s1, const void* const s2);
int str_order_desc(const void* const s1, const void* const s2);
int str_order_asc_ci(const void* const s1, const void* const s2);
int str_order_desc_ci(const void* const s1, const void* const s2);
// sort array of strings
void str_sort_range(const str_cmp_func cmp, str* const array, const size_t count);
// searching
const str* str_search_range(const str key, const str* const array, const size_t count);
// partitioning
size_t str_partition_range(bool (*pred)(const str), str* const array, const size_t count);
// unique partitioning
size_t str_unique_range(str* const array, const size_t count);
// UTF-32 codepoint iterator ----------------------------------------------------------------
#ifdef __STDC_UTF_32__
#include <uchar.h>
// iterator
#define for_each_codepoint(var, src) \
for_each_cp((var), (src), CAT1(inner_it_, __COUNTER__))
// iterator error codes
#define CPI_END_OF_STRING ((char32_t)-1)
#define CPI_ERR_INCOMPLETE_SEQ ((char32_t)-2)
#define CPI_ERR_INVALID_ENCODING ((char32_t)-3)
// implementation
#define for_each_cp(var, src, it) \
for(str_cp_iterator it = str_make_cp_iterator(src); (var = str_cp_iterator_next(&it)) <= 0x10FFFFu;)
#define CAT1(x, y) CAT2(x, y)
#define CAT2(x, y) x ## y
typedef struct
{
const char* curr;
const char* const end;
mbstate_t state;
} str_cp_iterator;
static inline
str_cp_iterator str_make_cp_iterator(const str s)
{
return (str_cp_iterator){ .curr = str_ptr(s), .end = str_end(s) };
}
char32_t str_cp_iterator_next(str_cp_iterator* const it);
#endif // ifdef __STDC_UTF_32__
// tokeniser --------------------------------------------------------------------------------
typedef struct
{
unsigned char bits[32]; // 256 / 8
const char *src, *end;
} str_tok_state;
void str_tok_init(str_tok_state* const state, const str src, const str delim_set);
bool str_tok(str* const dest, str_tok_state* const state);
void str_tok_delim(str_tok_state* const state, const str delim_set);
#ifdef __cplusplus
}
#endif

View File

@@ -1,4 +1,3 @@
#define _GNU_SOURCE
#include "hiload/symbols.h" #include "hiload/symbols.h"
#include <dlfcn.h> #include <dlfcn.h>
@@ -33,9 +32,9 @@ CreateResult he_create_symbol_info(SymbolInfos *symbols,
printf("Dynamic Header:\n"); printf("Dynamic Header:\n");
printf("p_type: %u\n", phdr->p_type); printf("p_type: %u\n", phdr->p_type);
printf("p_flags: %u\n", phdr->p_flags); printf("p_flags: %u\n", phdr->p_flags);
printf("p_offset: %#06x\n", phdr->p_offset); printf("p_offset: %#06lx\n", phdr->p_offset);
printf("p_vaddr: %#06x\n", phdr->p_vaddr); printf("p_vaddr: %#06lx\n", phdr->p_vaddr);
printf("p_paddr: %#06x\n", phdr->p_paddr); printf("p_paddr: %#06lx\n", phdr->p_paddr);
printf("p_filesz: %zu\n", phdr->p_filesz); printf("p_filesz: %zu\n", phdr->p_filesz);
printf("p_memsz: %zu\n", phdr->p_memsz); printf("p_memsz: %zu\n", phdr->p_memsz);
printf("p_align: %zu\n", phdr->p_align); printf("p_align: %zu\n", phdr->p_align);

1
test/CMakeLists.txt Normal file
View File

@@ -0,0 +1 @@
add_subdirectory(manual)

View File

@@ -8,9 +8,12 @@ int main(int argc, char *argv[]) {
int modified = -1; int modified = -1;
while (modified != 0) { while (modified != 0) {
modified = minimal_lib::getModified(5);
printf("getModified(5): %d\n", modified);
modified = minimal_lib::getNewValue(5);
printf("getModified(5): %d\n", modified);
std::this_thread::sleep_for(std::chrono::seconds(1));
printf("otherValue: %d\n", minimal_lib::otherValue);
std::this_thread::sleep_for(std::chrono::seconds(1)); std::this_thread::sleep_for(std::chrono::seconds(1));
} }

View File

@@ -2,6 +2,10 @@
namespace minimal_lib { namespace minimal_lib {
int getModified(int x) { return x + 1; } int getNewValue(int x) {
static int value = 0;
value = value + x;
return value;
}
} // namespace minimal_lib } // namespace minimal_lib

View File

@@ -3,9 +3,9 @@
namespace minimal_lib { namespace minimal_lib {
static int value = 5; static int otherValue = 5;
int getModified(int x); int getNewValue(int x);
} }
#endif // MINIMAL_LIB_H_ #endif // MINIMAL_LIB_H_