From b0be5aec60ffabdb33065db5af1b0c52e7d5e338 Mon Sep 17 00:00:00 2001 From: Kasper Date: Mon, 17 Mar 2025 22:54:34 +0200 Subject: [PATCH] add some dependencies and test code --- .gitmodules | 6 + 3rd/sc | 1 + 3rd/str | 1 + CMakeLists.txt | 39 +- cmake/clang-toolchain.cmake | 43 ++ scripts/copy_dependencies.sh | 9 + src/array.h | 6 + src/array/sc_array.h | 224 +++++++++++ src/auditor/auditor-x86_64.c | 4 +- src/auditor/auditor.h | 26 -- src/compilation.h | 11 + src/hiload.c | 3 - src/logger.h | 6 + src/logger/sc_log.c | 451 +++++++++++++++++++++ src/logger/sc_log.h | 146 +++++++ src/str.c | 740 +++++++++++++++++++++++++++++++++++ src/str.h | 293 ++++++++++++++ src/symbols.c | 7 +- test/CMakeLists.txt | 1 + test/manual/CMakeLists.txt | 2 +- test/manual/minimal.cpp | 7 +- test/manual/minimal_lib.cpp | 6 +- test/manual/minimal_lib.h | 4 +- 23 files changed, 1993 insertions(+), 43 deletions(-) create mode 100644 .gitmodules create mode 160000 3rd/sc create mode 160000 3rd/str create mode 100644 cmake/clang-toolchain.cmake create mode 100644 scripts/copy_dependencies.sh create mode 100644 src/array.h create mode 100644 src/array/sc_array.h delete mode 100644 src/auditor/auditor.h create mode 100644 src/compilation.h create mode 100644 src/logger.h create mode 100644 src/logger/sc_log.c create mode 100644 src/logger/sc_log.h create mode 100644 src/str.c create mode 100644 src/str.h create mode 100644 test/CMakeLists.txt diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..06583b4 --- /dev/null +++ b/.gitmodules @@ -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 diff --git a/3rd/sc b/3rd/sc new file mode 160000 index 0000000..81fd6b1 --- /dev/null +++ b/3rd/sc @@ -0,0 +1 @@ +Subproject commit 81fd6b169801c80edfdcd2f71a8c4975c97fa3a3 diff --git a/3rd/str b/3rd/str new file mode 160000 index 0000000..05fab47 --- /dev/null +++ b/3rd/str @@ -0,0 +1 @@ +Subproject commit 05fab479f9e8e687ff7177bbd4208c4f32405436 diff --git a/CMakeLists.txt b/CMakeLists.txt index e7d1a1b..82eedc6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,48 @@ cmake_minimum_required(VERSION 3.21) 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_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 # ############## add_library(hiload SHARED src/hiload.c src/symbols.c + +# dependencies + src/str.c + src/logger/sc_log.c ) set_property(TARGET hiload PROPERTY POSITION_INDEPENDENT_CODE ON) @@ -53,3 +85,8 @@ install(TARGETS auditor-x86_64 LIBRARY DESTINATION lib RUNTIME DESTINATION bin ) + +# tests and test projects +# ####################### + +add_subdirectory(test) diff --git a/cmake/clang-toolchain.cmake b/cmake/clang-toolchain.cmake new file mode 100644 index 0000000..10af68b --- /dev/null +++ b/cmake/clang-toolchain.cmake @@ -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") diff --git a/scripts/copy_dependencies.sh b/scripts/copy_dependencies.sh new file mode 100644 index 0000000..fc54610 --- /dev/null +++ b/scripts/copy_dependencies.sh @@ -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 diff --git a/src/array.h b/src/array.h new file mode 100644 index 0000000..3982f93 --- /dev/null +++ b/src/array.h @@ -0,0 +1,6 @@ +#ifndef ARRAY_H_ +#define ARRAY_H_ + +#include "array/sc_array.h" + +#endif // ARRAY_H_ diff --git a/src/array/sc_array.h b/src/array/sc_array.h new file mode 100644 index 0000000..cefca91 --- /dev/null +++ b/src/array/sc_array.h @@ -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 +#include +#include +#include +#include +#include + +#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 diff --git a/src/auditor/auditor-x86_64.c b/src/auditor/auditor-x86_64.c index d191884..80f5017 100644 --- a/src/auditor/auditor-x86_64.c +++ b/src/auditor/auditor-x86_64.c @@ -1,5 +1,3 @@ -#define _GNU_SOURCE - #include #include @@ -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 *defcook, unsigned int *flags, 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); printf(" ndx = %u; flags = %#x", ndx, *flags); printf("; refcook = %p; defcook = %p\n", refcook, defcook); diff --git a/src/auditor/auditor.h b/src/auditor/auditor.h deleted file mode 100644 index c3349a3..0000000 --- a/src/auditor/auditor.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef AUDITOR_H_ -#define AUDITOR_H_ - -#define _GNU_SOURCE -#include -#include - -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_ diff --git a/src/compilation.h b/src/compilation.h new file mode 100644 index 0000000..bfda3de --- /dev/null +++ b/src/compilation.h @@ -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_ diff --git a/src/hiload.c b/src/hiload.c index c34cd19..1e59664 100644 --- a/src/hiload.c +++ b/src/hiload.c @@ -1,6 +1,3 @@ -// Required for dlinfo and other GNU specific linker code -#define _GNU_SOURCE - #include "hiload/hiload.h" #include "hiload/symbols.h" diff --git a/src/logger.h b/src/logger.h new file mode 100644 index 0000000..a4735ac --- /dev/null +++ b/src/logger.h @@ -0,0 +1,6 @@ +#ifndef LOGGER_H_ +#define LOGGER_H_ + +#include "logger/sc_log.h" + +#endif // LOGGER_H_ diff --git a/src/logger/sc_log.c b/src/logger/sc_log.c new file mode 100644 index 0000000..bd5da24 --- /dev/null +++ b/src/logger/sc_log.c @@ -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 +#include +#include + +// 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 + + #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 + +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 + +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; +} diff --git a/src/logger/sc_log.h b/src/logger/sc_log.h new file mode 100644 index 0000000..ec59cb0 --- /dev/null +++ b/src/logger/sc_log.h @@ -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 +#include +#include +#include +#include + +#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 diff --git a/src/str.c b/src/str.c new file mode 100644 index 0000000..9595d08 --- /dev/null +++ b/src/str.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +// 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; +} diff --git a/src/str.h b/src/str.h new file mode 100644 index 0000000..052d21c --- /dev/null +++ b/src/str.h @@ -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 +#include + +#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 + +// 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 diff --git a/src/symbols.c b/src/symbols.c index 7005168..bc90eda 100644 --- a/src/symbols.c +++ b/src/symbols.c @@ -1,4 +1,3 @@ -#define _GNU_SOURCE #include "hiload/symbols.h" #include @@ -33,9 +32,9 @@ CreateResult he_create_symbol_info(SymbolInfos *symbols, printf("Dynamic Header:\n"); printf("p_type: %u\n", phdr->p_type); printf("p_flags: %u\n", phdr->p_flags); - printf("p_offset: %#06x\n", phdr->p_offset); - printf("p_vaddr: %#06x\n", phdr->p_vaddr); - printf("p_paddr: %#06x\n", phdr->p_paddr); + printf("p_offset: %#06lx\n", phdr->p_offset); + printf("p_vaddr: %#06lx\n", phdr->p_vaddr); + printf("p_paddr: %#06lx\n", phdr->p_paddr); printf("p_filesz: %zu\n", phdr->p_filesz); printf("p_memsz: %zu\n", phdr->p_memsz); printf("p_align: %zu\n", phdr->p_align); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..cda8dcd --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(manual) diff --git a/test/manual/CMakeLists.txt b/test/manual/CMakeLists.txt index d79ffee..6e545d7 100644 --- a/test/manual/CMakeLists.txt +++ b/test/manual/CMakeLists.txt @@ -1,6 +1,6 @@ project(Minimal) -set( CMAKE_EXPORT_COMPILE_COMMANDS ON ) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_executable(minimal minimal.cpp ) diff --git a/test/manual/minimal.cpp b/test/manual/minimal.cpp index 83c5a23..f818f2d 100644 --- a/test/manual/minimal.cpp +++ b/test/manual/minimal.cpp @@ -8,9 +8,12 @@ int main(int argc, char *argv[]) { int modified = -1; 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)); } diff --git a/test/manual/minimal_lib.cpp b/test/manual/minimal_lib.cpp index d771678..c900d1b 100644 --- a/test/manual/minimal_lib.cpp +++ b/test/manual/minimal_lib.cpp @@ -2,6 +2,10 @@ 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 diff --git a/test/manual/minimal_lib.h b/test/manual/minimal_lib.h index c1adf94..cddcc54 100644 --- a/test/manual/minimal_lib.h +++ b/test/manual/minimal_lib.h @@ -3,9 +3,9 @@ namespace minimal_lib { -static int value = 5; +static int otherValue = 5; -int getModified(int x); +int getNewValue(int x); } #endif // MINIMAL_LIB_H_