add some dependencies and test code
This commit is contained in:
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal 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
1
3rd/sc
Submodule
Submodule 3rd/sc added at 81fd6b1698
1
3rd/str
Submodule
1
3rd/str
Submodule
Submodule 3rd/str added at 05fab479f9
@@ -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)
|
||||
|
||||
43
cmake/clang-toolchain.cmake
Normal file
43
cmake/clang-toolchain.cmake
Normal 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")
|
||||
9
scripts/copy_dependencies.sh
Normal file
9
scripts/copy_dependencies.sh
Normal 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
6
src/array.h
Normal 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
224
src/array/sc_array.h
Normal 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
|
||||
@@ -1,5 +1,3 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <link.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 *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);
|
||||
|
||||
@@ -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
11
src/compilation.h
Normal 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_
|
||||
@@ -1,6 +1,3 @@
|
||||
// Required for dlinfo and other GNU specific linker code
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "hiload/hiload.h"
|
||||
#include "hiload/symbols.h"
|
||||
|
||||
|
||||
6
src/logger.h
Normal file
6
src/logger.h
Normal 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
451
src/logger/sc_log.c
Normal 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
146
src/logger/sc_log.h
Normal 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
740
src/str.c
Normal 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
293
src/str.h
Normal 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
|
||||
@@ -1,4 +1,3 @@
|
||||
#define _GNU_SOURCE
|
||||
#include "hiload/symbols.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
@@ -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);
|
||||
|
||||
1
test/CMakeLists.txt
Normal file
1
test/CMakeLists.txt
Normal file
@@ -0,0 +1 @@
|
||||
add_subdirectory(manual)
|
||||
@@ -1,6 +1,6 @@
|
||||
project(Minimal)
|
||||
|
||||
set( CMAKE_EXPORT_COMPILE_COMMANDS ON )
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
add_executable(minimal
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
|
||||
Reference in New Issue
Block a user