diff --git a/CMakeLists.txt b/CMakeLists.txt index 5398c4d..a91b41d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}") project(${PROJ} VERSION ${VERSION} LANGUAGES C) # default flags -set(CMAKE_C_FLAGS "${CFLAGS} -O2 -pedantic-errors") +set(CMAKE_C_FLAGS "$ENV{CFLAGS} -O2 -pedantic-errors") set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS}") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} -Wextra -Wall -Werror -W") set(CMAKE_COLOR_MAKEFILE ON) @@ -35,15 +35,15 @@ if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") endif() if(CMAKE_BUILD_TYPE STREQUAL "Debug") - add_definitions(-DEBUG) - set(CMAKE_VERBOSE_MAKEFILE true) - if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - message("install to ${CMAKE_CURRENT_SOURCE_DIR}/install ") - set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_SOURCE_DIR}/install) - endif() - set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS_DEBUG}) + add_definitions(-DEBUG) + set(CMAKE_VERBOSE_MAKEFILE true) + if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + message("install to ${CMAKE_CURRENT_SOURCE_DIR}/install ") + set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_SOURCE_DIR}/install) + endif() + set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS_DEBUG}) else() - set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS_RELEASE}) + set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS_RELEASE}) endif() message("Build type: ${CMAKE_BUILD_TYPE}") @@ -97,7 +97,7 @@ link_directories(${${PROJ}_LIBRARY_DIRS}) add_definitions(-DLOCALEDIR=\"${LOCALEDIR}\" -DPACKAGE_VERSION=\"${VERSION}\" -DGETTEXT_PACKAGE=\"${PROJ}\" -DMINOR_VERSION=\"${MINOR_VERSION}\" -DMID_VERSION=\"${MID_VERSION}\" - -DMAJOR_VERSION=\"${MAJOR_VESION}\") + -DMAJOR_VERSION=\"${MAJOR_VERSION}\") # -l target_link_libraries(${PROJ} ${${PROJ}_LIBRARIES} -lm) diff --git a/Changelog b/Changelog index ec9dea2..ccab0bb 100644 --- a/Changelog +++ b/Changelog @@ -1,3 +1,8 @@ +Thu May 7 14:39:35 MSK 2026 +VERSION 0.3.5 +- added Readme.md +- some minor bugs fixed + Wed Mar 4 14:18:15 MSK 2026 VERSION 0.3.4: - fixed wrong UNIX-sockets names diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..0333a8a --- /dev/null +++ b/Readme.md @@ -0,0 +1,750 @@ +# `libusefull_macros`  A collection of useful C snippets for Linux + +**Version:** 0.3.5 +**Author:** Edward V. Emelianov () +**License:** GPLv3+ +**Repository:** [github.com/eddyem/snippets_library](https://github.com/eddyem/snippets_library) + +--- + +## Table of Contents + +1. [Overview](#overview) +2. [Installation](#installation) + - [Building from source](#building-from-source) + - [CMake options](#cmake-options) + - [Linking](#linking) +3. [Quick Start](#quick-start) +4. [Module Reference](#module-reference) + - [Initialization & Locale](#initialization--locale) + - [Colored Terminal Output](#colored-terminal-output) + - [Error & Warning Macros](#error--warning-macros) + - [Memory Management](#memory-management) + - [String Utilities](#string-utilities) + - [Number Conversion](#number-conversion) + - [Console / Terminal I/O](#console--terminal-io) + - [Logging](#logging) + - [Command-line Argument Parsing](#command-line-argument-parsing) + - [Configuration Files](#configuration-files) + - [Daemon Support](#daemon-support) + - [FIFO / LIFO Linked List](#fifo--lifo-linked-list) + - [Ring Buffer](#ring-buffer) + - [TCP / UNIX Socket Server & Client](#tcp--unix-socket-server--client) + - [Serial Port (TTY)](#serial-port-tty) + - [Sub-options Parsing](#sub-options-parsing) + - [Miscellaneous Utilities](#miscellaneous-utilities) +5. [Data Structures](#data-structures) +6. [Examples](#examples) +7. [Internationalization (i18n)](#internationalization-i18n) +8. [Thread Safety](#thread-safety) + +--- + +## Overview + +`libusefull_macros` is a C shared library that bundles many frequently needed utility routines for +Linux application development. It covers: + +- Colored, locale-aware terminal output +- Safe memory allocation and memory-mapped file I/O +- GNU `getopt_long`-style command-line argument parsing with typesafe callbacks +- INIlike configuration file reading/writing +- Daemonization with PIDfile management +- Threadsafe ring buffer for producerconsumer patterns +- FIFO/LIFO linked list +- TCP and UNIX socket server/client framework with builtin HTTP parsing and keyvalue handler dispatch +- Serial port (TTY) management with nonstandard baud rates +- Filebased logging with multiple severity levels +- `gettext` integration for internationalization + +All public identifiers are prefixed with `sl_` (for "snippets library") to avoid naming collisions. + +--- + +## Installation + +### Building from source + +```bash +git clone https://github.com/eddyem/snippets_library.git +cd snippets_library +mkdir build && cd build +cmake .. +make -j$(nproc) +make install +``` + +The build produces a shared library `libusefull_macros.so` and a pkg-config file. + +### CMake options + +| Variable | Default | Description | +|----------|---------|-------------| +| `DEBUG=1` | off | Build with `-Wextra -Wall -Werror -W` and enable debug output | +| `EXAMPLES=1` | off | Build example programs in the `examples/` subdirectory | +| `NOGETTEXT` | not set | Disable gettext integration | +| `PROCESSOR_COUNT` | auto | Number of threads for parallel operations (default detects from `/proc/cpuinfo`) | + +Example: + +```bash +cmake .. -DDEBUG=1 -DEXAMPLES=1 +``` + +### Linking + +A pkg-config file is installed: + +```bash +pkg-config --cflags --libs usefull_macros +``` + +Or manually: + +```bash +gcc -o myapp myapp.c -I/usr/local/include -L/usr/local/lib -lusefull_macros -lm -lpthread +``` + +--- + +## Quick Start + +```c +#include + +int main(int argc, char **argv) { + sl_init(); // locale, gettext, colored output + green("Hello, world!\n"); // green text on tty + red("An error occurred\n"); // red text on tty + return 0; +} +``` + +Compile: + +```bash +gcc -o hello hello.c $(pkg-config --cflags --libs usefull_macros) +``` + +--- + +## Module Reference + +### Initialization & Locale + +```c +void sl_init(void); +``` + +Must be called once at the beginning of `main()`. It: + +- Detects whether `stdout`/`stderr` are terminals and sets up colored output functions accordingly. +- Calls `setlocale(LC_ALL, "")` and `setlocale(LC_NUMERIC, "C")` (decimal point is always a dot). +- If compiled with `GETTEXT` defined, binds the message domain. + +**Important:** `sl_init()` must be called before any other library function that generates output. + +--- + +### Colored Terminal Output + +When output is a terminal (not redirected to a file or pipe), text can be printed in color: + +```c +extern int (*red)(const char *fmt, ...); +extern int (*green)(const char *fmt, ...); +``` + +These function pointers are set by `sl_init()`. Use them like `printf`: + +```c +red("Error code: %d\n", err); +green("Operation successful\n"); +``` + +When output is not a tty, `red` wraps the message between lines of asterisks, and `green` falls +back to plain `printf`. + +--- + +### Error & Warning Macros + +```c +#define ERR(...) // print errno + message, then exit(9) +#define ERRX(...) // print message (no errno), then exit(9) +#define WARN(...) // print errno + message, continue +#define WARNX(...) // print message (no errno), continue +``` + +These use the `_WARN` function pointer (respects colored output). They automatically include the +current `errno` value when using `ERR`/`WARN`. + +The default signal handler for `ERR`/`ERRX` is `signals(9)`, which simply calls `exit(9)`. You can +override the `signals` function since it is declared `__attribute__((weak))`: + +```c +void signals(int sig) { + // custom cleanup + exit(sig); +} +``` + +**Debug macros** (active only when `-DEBUG` is defined): + +```c +FNAME() // print current function name, file, line +DBG(...) // printf-like debug message +``` + +--- + +### Memory Management + +```c +void *sl_alloc(size_t N, size_t S); +``` + +Safe `calloc` wrapper. Exits with error message if allocation fails. + +Convenience macros: + +```c +ALLOC(type, var, size) // declare + allocate: type *var = calloc(size, sizeof(type)) +MALLOC(type, size) // allocate without declaration +FREE(ptr) // free and set to NULL +``` + +**Memorymapped files:** + +```c +typedef struct { char *data; size_t len; } sl_mmapbuf_t; +sl_mmapbuf_t *sl_mmap(char *filename); +void sl_munmap(sl_mmapbuf_t *b); +``` + +Maps a file readonly into memory; `sl_munmap` unmaps and frees the structure. + +**System memory query:** + +```c +uint64_t sl_mem_avail(void); // available physical memory in bytes +``` + +--- + +### String Utilities + +```c +char *sl_omitspaces(const char *str); // skip leading whitespace +char *sl_omitspacesr(const char *str); // pointer to (last non-space char + 1) +int sl_remove_quotes(char *string); // remove outer matching quotes (' or ") +int sl_get_keyval(const char *pair, char key[32], char value[128]); // parse "key = value" +``` + +`sl_remove_quotes` strips matched pairs of single or double quotes from both ends. Returns the +number of pairs removed (0 if none). + +`sl_get_keyval` parses a line into key and value: +- Returns `0` if the line is empty or a comment (starts with `#`). +- Returns `1` if only a key is present. +- Returns `2` if both key and value are found. +- Ignores inline comments, strips surrounding whitespace and quotes. + +--- + +### Number Conversion + +```c +int sl_str2i(int *num, const char *str); +int sl_str2ll(long long *num, const char *str); +int sl_str2d(double *num, const char *str); +``` + +Safe `strtol`/`strtod` wrappers. Return `TRUE` (1) on success, `FALSE` (0) on failure. The output +pointer may be `NULL` to only check validity. + +--- + +### Console / Terminal I/O + +For noncanonical, noecho terminal input: + +```c +void sl_setup_con(void); // switch terminal to raw mode +void sl_restore_con(void); // restore original terminal settings +int sl_read_con(void); // nonblocking read (0 if no key) +int sl_getchar(void); // blocking read of one character +``` + +Typical usage: + +```c +sl_setup_con(); +int ch = sl_getchar(); +sl_restore_con(); +``` + +**Important:** These functions are **not threadsafe**  they use a global `struct termios2`. + +--- + +### Logging + +```c +typedef enum { + LOGLEVEL_NONE, // no logging + LOGLEVEL_ERR, // only errors + LOGLEVEL_WARN, // warnings + errors + LOGLEVEL_MSG, // all except debug + LOGLEVEL_DBG, // all messages + LOGLEVEL_ANY // everything +} sl_loglevel_e; + +sl_log_t *sl_createlog(const char *logpath, sl_loglevel_e level, int prefix); +void sl_deletelog(sl_log_t **log); +int sl_putlogt(int timest, sl_log_t *log, sl_loglevel_e lvl, const char *fmt, ...); +``` + +A "global" log is managed through the pointer `sl_globlog`: + +```c +extern sl_log_t *sl_globlog; +``` + +Convenience macros (write to `sl_globlog`): + +| Macro | Meaning | +|-------|---------| +| `OPENLOG(path, level, prefix)` | Open global log | +| `LOGERR(...)` | Error with timestamp | +| `LOGERRADD(...)` | Error without timestamp | +| `LOGWARN(...)` / `LOGWARNADD(...)` | Warning | +| `LOGMSG(...)` / `LOGMSGADD(...)` | Message | +| `LOGDBG(...)` / `LOGDBGADD(...)` | Debug | + +Timestamps use format `YYYY/MM/DD-HH:MM:SS`. Each log call locks the file with `flock` for +concurrent access. + +--- + +### Command-line Argument Parsing + +Built on top of `getopt_long`. Supports: + +- Short and long options +- Required, optional, and no arguments +- Multiple occurrences of the same option (multiparameters) +- Six data types: `int`, `long long`, `double`, `float`, `char*`, and function callback +- Automatic help generation + +**Option descriptor:** + +```c +typedef struct { + const char *name; // long option (NULL for short-only) + sl_hasarg_e has_arg; // NO_ARGS, NEED_ARG, OPT_ARG, or MULT_PAR + int *flag; // NULL  return val; else set *flag = val + int val; // short option character or flag value + sl_argtype_e type; // arg_int, arg_longlong, arg_double, arg_float, + // arg_string, arg_function + void *argptr; // pointer to variable or callback function + const char *help; // help text (mandatory; end_option marks end) +} sl_option_t; +``` + +**Helper macro:** + +```c +#define APTR(x) ((void*)x) +``` + +**Functions:** + +```c +void sl_parseargs(int *argc, char ***argv, sl_option_t *options); +void sl_parseargs_hf(int *argc, char ***argv, sl_option_t *options, + void (*helpfun)(int, sl_option_t*)); +void sl_showhelp(int oindex, sl_option_t *options); +void sl_helpstring(char *s); // customize help header +``` + +After calling `sl_parseargs`, `argc` and `argv` are updated to point to remaining nonoption +arguments. + +**Example:** + +```c +int verbose = 0; +char *output = NULL; +sl_option_t opts[] = { + {"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&verbose), "increase verbosity"}, + {"output", NEED_ARG, NULL, 'o', arg_string, APTR(&output), "output file"}, + end_option +}; + +int main(int argc, char **argv) { + sl_init(); + sl_parseargs(&argc, &argv, opts); + // argc, argv now contain nonoption arguments +} +``` + +**Multiparameters** (`MULT_PAR`): Options that may appear multiple times. The library allocates a +`NULL`terminated array of pointers, each pointing to a newly allocated value. For `arg_string`, +each element is a pointer to a `strdup`'d string; for numeric types, each is a pointer to a +heapallocated number. + +**Function callback** (`arg_function`): The callback must have signature `int (*fn)(void *arg)` and +receives a `strdup`'d argument string. + +**Custom help function:** Pass a function pointer to `sl_parseargs_hf` to handle errors differently +than the default `sl_showhelp` (which calls `exit(-1)`). + +--- + +### Configuration Files + +Reads keyvalue pairs from a file and treats them as commandline options. + +```c +int sl_conf_readopts(const char *filename, sl_option_t *options); +char *sl_print_opts(sl_option_t *opt, int showall); +void sl_conf_showhelp(int idx, sl_option_t *options); +``` + +`sl_conf_readopts` reads a file with lines like: + +``` +# comment +key1 = value1 +key2 +key3 = "quoted value" +``` + +Each noncomment line is converted to `--key=value` (or `--key` if no value) and passed to +`sl_parseargs`. Returns the number of recognized options. + +`sl_print_opts` generates a string representation of current option values (useful for debugging or +saving state). The returned string must be freed with `free()`. + +--- + +### Daemon Support + +```c +int sl_daemonize(void); +void sl_check4running(char *selfname, char *pidfilename); +char *sl_getPSname(pid_t pid); +void sl_iffound_deflt(pid_t pid); // WEAK  overridable +``` + +`sl_daemonize()`: +- `chdir("/")` +- `umask(0)` +- Closes stdin/stdout/stderr, reopens to `/dev/null` +- Ignores `SIGHUP` +- Returns 0 on success, -1 on failure + +`sl_check4running()`: +- Checks a PID file and `/proc` for a running process with the same name. +- If found, calls `sl_iffound_deflt` (by default prints a message and exits). +- Otherwise writes its own PID to the PID file. + +Override `sl_iffound_deflt` in your application (it is `__attribute__((weak))`): + +```c +void sl_iffound_deflt(pid_t pid) { + fprintf(stderr, "Already running (pid %d)\n", pid); + exit(1); +} +``` + +--- + +### FIFO / LIFO Linked List + +A simple singlylinked list with both head and tail pointers. + +```c +typedef struct sl_buff_node { + void *data; + struct sl_buff_node *next, *last; +} sl_list_t; + +sl_list_t *sl_list_push(sl_list_t **lst, void *v); // LIFO (push to head) +sl_list_t *sl_list_push_tail(sl_list_t **lst, void *v); // FIFO (push to tail) +void *sl_list_pop(sl_list_t **lst); // pop from head +``` + +`sl_list_pop` returns the data pointer and frees the node. The caller is responsible for freeing +the data if needed. + +--- + +### Ring Buffer + +A threadsafe, fixedsize ring buffer for byte streams, protected by `pthread_mutex_t`. + +```c +typedef struct { + uint8_t *data; + size_t length, head, tail; + pthread_mutex_t busy; +} sl_ringbuffer_t; + +sl_ringbuffer_t *sl_RB_new(size_t size); +void sl_RB_delete(sl_ringbuffer_t **b); +size_t sl_RB_read(sl_ringbuffer_t *b, uint8_t *s, size_t len); +ssize_t sl_RB_readto(sl_ringbuffer_t *b, uint8_t byte, uint8_t *s, size_t len); +ssize_t sl_RB_readline(sl_ringbuffer_t *b, char *s, size_t len); +int sl_RB_putbyte(sl_ringbuffer_t *b, uint8_t byte); +size_t sl_RB_write(sl_ringbuffer_t *b, const uint8_t *str, size_t len); +size_t sl_RB_writestr(sl_ringbuffer_t *b, char *s); +size_t sl_RB_datalen(sl_ringbuffer_t *b); +size_t sl_RB_freesize(sl_ringbuffer_t *b); +void sl_RB_clearbuf(sl_ringbuffer_t *b); +ssize_t sl_RB_hasbyte(sl_ringbuffer_t *b, uint8_t byte); +``` + +Key behaviors: + +- `sl_RB_readline` reads up to and including a newline (`\n`), replaces `\n` with `\0`. +- `sl_RB_readto` reads until (and including) a specified byte. +- `sl_RB_writestr` ensures the string ends with `\n` before writing. +- All read/write operations are atomic with respect to the mutex. + +--- + +### TCP / UNIX Socket Server & Client + +A highlevel socket framework supporting TCP and UNIX domain sockets, with builtin HTTP method +detection. + +**Socket types:** + +```c +typedef enum { SOCKT_UNIX, SOCKT_NETLOCAL, SOCKT_NET } sl_socktype_e; +``` + +**Creating and destroying:** + +```c +sl_sock_t *sl_sock_run_server(sl_socktype_e type, const char *path, + int bufsiz, sl_sock_hitem_t *handlers); +sl_sock_t *sl_sock_run_client(sl_socktype_e type, const char *path, int bufsiz); +void sl_sock_delete(sl_sock_t **sock); +``` + +- `path` for UNIX sockets: file path; prefix with `\0` or `@` for abstract namespace. +- `path` for INET sockets: `"host:port"` (client) or `":port"` (server). +- `handlers`: `NULL`terminated array of keyvalue handlers (see below). +- `bufsiz`: internal ring buffer size (minimum 256). + +**Sending data:** + +```c +ssize_t sl_sock_sendbinmessage(sl_sock_t *socket, const uint8_t *msg, size_t l); +ssize_t sl_sock_sendstrmessage(sl_sock_t *socket, const char *msg); +ssize_t sl_sock_sendbyte(sl_sock_t *socket, uint8_t byte); +int sl_sock_sendall(sl_sock_t *sock, uint8_t *data, size_t len); // server only +``` + +**Reading (client):** + +```c +ssize_t sl_sock_readline(sl_sock_t *sock, char *str, size_t len); +``` + +**Handler dispatch (server):** + +```c +typedef sl_sock_hresult_e (*sl_sock_msghandler)(struct sl_sock *s, + struct sl_sock_hitem *item, const char *val); + +typedef struct sl_sock_hitem { + sl_sock_msghandler handler; + const char *key; + const char *help; + void *data; // user data (e.g., &variable) +} sl_sock_hitem_t; +``` + +Handler results: + +```c +typedef enum { + RESULT_OK, RESULT_FAIL, RESULT_BADVAL, + RESULT_BADKEY, RESULT_SILENCE +} sl_sock_hresult_e; +``` + +Builtin handlers for common types: + +```c +sl_sock_hresult_e sl_sock_inthandler(...); // int64_t +sl_sock_hresult_e sl_sock_dblhandler(...); // double +sl_sock_hresult_e sl_sock_strhandler(...); // string +``` + +**Optional key numbering** (`key[0]`, `key(1)`, `key{2}`, `key3`): + +```c +typedef struct { double magick; int n; } sl_sock_keyno_t; +#define SL_SOCK_KEYNO_DEFAULT { .magick = -INFINITY, .n = -1 } +void sl_sock_keyno_init(sl_sock_keyno_t *k); +int sl_sock_keyno_check(sl_sock_keyno_t *k); +``` + +**Server hooks:** + +```c +void sl_sock_changemaxclients(sl_sock_t *sock, int val); +void sl_sock_maxclhandler(sl_sock_t *sock, void (*h)(int)); +void sl_sock_connhandler(sl_sock_t *sock, int (*h)(struct sl_sock*)); +void sl_sock_dischandler(sl_sock_t *sock, void (*h)(struct sl_sock*)); +void sl_sock_defmsghandler(sl_sock_t *sock, sl_sock_hresult_e (*h)(struct sl_sock*, const char*)); +``` + +The server thread automatically handles `POLLIN` events, parses messages using `sl_get_keyval`, and +dispatches them to matching handlers. HTTP `GET`/`POST` requests are partially parsed: `GET` +parameters are URLdecoded and dispatched; `POST` data is accumulated and then parsed. + +--- + +### Serial Port (TTY) + +```c +typedef struct { + char *portname; + int speed; + char *format; // e.g., "8N1" + int comfd; + char *buf; + size_t bufsz, buflen; + int exclusive; +} sl_tty_t; + +int sl_tty_fdescr(const char *comdev, const char *format, int speed, int exclusive); +sl_tty_t *sl_tty_new(char *comdev, int speed, size_t bufsz); +int sl_tty_setformat(sl_tty_t *d, const char *format); +sl_tty_t *sl_tty_open(sl_tty_t *d, int exclusive); +int sl_tty_read(sl_tty_t *d); +int sl_tty_write(int comfd, const char *buff, size_t length); +void sl_tty_close(sl_tty_t **descr); +int sl_tty_tmout(double usec); +``` + +Format string: three characters  data bits (58), parity (N/E/O/0/1), stop bits (1/2). Example: +`"8N1"`. + +Uses `struct termios2` via `ioctl(TCGETS2/TCSETS2)` to support arbitrary baud rates (not limited to +the standard `Bxxx` constants). + +`sl_tty_read` uses `select()` with a configurable timeout (default 5 ms, change with +`sl_tty_tmout`). Returns the number of bytes read; data is placed in `d->buf` with length +`d->buflen`. + +`sl_tty_fdescr` allows to use library functions for opening serial device with given path, format string, +non-standard speed, marking it as exclusive (not share with other processes) or not. It doesn't allocates +memory and just returns opened tty file descriptor or `-1` in case of error. + + +--- + +### Sub-options Parsing + +Parses strings like `key1=val1:key2=val2,key3`: + +```c +typedef struct { + const char *name; + sl_hasarg_e has_arg; + sl_argtype_e type; + void *argptr; +} sl_suboption_t; + +int sl_get_suboption(char *str, sl_suboption_t *opt); +``` + +The input string is tokenized on `:` and `,`; each token is matched against option names +(caseinsensitive). + +--- + +### Miscellaneous Utilities + +```c +const char *sl_libversion(void); // returns PACKAGE_VERSION string +double sl_dtime(void); // UNIX time as double (seconds) +long sl_random_seed(void); // seed from /dev/random or time +int sl_canread(int fd); // nonblocking select() for read +int sl_canwrite(int fd); // nonblocking select() for write +``` + +--- + +## Data Structures + +| Structure | Purpose | +|-----------|---------| +| `sl_option_t` | Commandline option descriptor | +| `sl_suboption_t` | Suboption descriptor | +| `sl_tty_t` | Serial port state | +| `sl_log_t` | Log file descriptor | +| `sl_mmapbuf_t` | Memorymapped file | +| `sl_list_t` | Linked list node | +| `sl_ringbuffer_t` | Threadsafe ring buffer | +| `sl_sock_t` | Socket state (client or server) | +| `sl_sock_hitem_t` | Socket handler item | +| `sl_sock_int_t` | Timestamped `int64_t` | +| `sl_sock_double_t` | Timestamped `double` | +| `sl_sock_string_t` | Timestamped string | +| `sl_sock_keyno_t` | Optional key number | + +--- + +## Examples + +The repository includes several example programs in the `examples/` directory: + +| Example | Demonstrates | +|---------|-------------| +| `helloworld` | Minimal usage: `sl_init`, colored output, `sl_setup_con`/`sl_getchar`/`sl_restore_con` | +| `options` + `cmdlnopts` | Full commandline parsing with all types, logging, serial port, signals | +| `conffile` | Configuration file reading, `sl_print_opts`, multiparameters | +| `fifo` | LIFO and FIFO list operations | +| `ringbuffer` | Ring buffer creation, line reading, overflow handling | +| `clientserver` | Socket server/client with custom handlers, bit flags, logging | +| `daemon` | Daemonization, PID file, child process monitoring | + +Build examples with: + +```bash +cmake .. -DEXAMPLES=1 +make +``` + +--- + +## Internationalization (i18n) + +If compiled with `GETTEXT` defined, the `_()` macro wraps `gettext()`. Translation files are +expected in the `locale/` directory. The library generates `.po` and `.mo` files during the build +(in Debug mode). To disable, define `NOGETTEXT`. + +```c +#define _(String) gettext(String) // when GETTEXT is defined +#define _(String) (String) // otherwise +``` + +--- + +## Thread Safety + +- **Ring buffer:** all operations are protected by a `pthread_mutex_t`. +- **Logging:** file writes are guarded with `flock(LOCK_EX)`. +- **Sockets:** server thread uses `poll()`; client read thread is separate; send operations lock the socket mutex. +- **Console I/O:** `sl_setup_con`/`sl_read_con`/`sl_getchar`/`sl_restore_con` are **not** threadsafe (global terminal state). + +--- + diff --git a/examples/cmdlnopts.c b/examples/cmdlnopts.c index 0d519cd..0de807f 100644 --- a/examples/cmdlnopts.c +++ b/examples/cmdlnopts.c @@ -87,7 +87,7 @@ glob_pars *parse_args(int argc, char **argv){ void *ptr; ptr = memcpy(&G, &Gdefault, sizeof(G)); assert(ptr); size_t hlen = 1024; - char helpstring[1024], *hptr = helpstring; + static char helpstring[1024], *hptr = helpstring; snprintf(hptr, hlen, "Usage: %%s [args]\n\n\tWhere args are:\n"); // format of help: "Usage: progname [args]\n" sl_helpstring(helpstring); diff --git a/parseargs.c b/parseargs.c index e6d7b10..02ee35b 100644 --- a/parseargs.c +++ b/parseargs.c @@ -182,6 +182,7 @@ void *get_aptr(void *paptr, sl_argtype_e type){ break;*/ } aptr = realloc(aptr, (i + 1) * sizeof(void*)); + if(!aptr) ERR("realloc()"); *((void***)paptr) = aptr; aptr[i] = NULL; if(sz){ diff --git a/ringbuffer.c b/ringbuffer.c index 3b93d6c..4e95aee 100644 --- a/ringbuffer.c +++ b/ringbuffer.c @@ -258,7 +258,7 @@ void sl_RB_clearbuf(sl_ringbuffer_t *b){ /** * @brief sl_RB_writestr - write FULL string `s` to buffer (without trailing zero!) * @param b - rb - * @param s - string + * @param s - string !!! can be modified if have no '\n' on end !!! * @return amount of bytes written (strlen of s) or 0 */ size_t sl_RB_writestr(sl_ringbuffer_t *b, char *s){ diff --git a/socket.c b/socket.c index d41384b..300d5b4 100644 --- a/socket.c +++ b/socket.c @@ -89,8 +89,8 @@ void sl_sock_delete(sl_sock_t **sock){ sl_sock_t *ptr = *sock; ptr->connected = 0; if(ptr->rthread){ - DBG("Cancel thread"); - pthread_cancel(ptr->rthread); + DBG("Join thread"); + pthread_join(ptr->rthread, NULL); } DBG("close fd=%d", ptr->fd); if(ptr->fd > -1) close(ptr->fd); @@ -136,12 +136,13 @@ static void *clientrbthread(void *d){ do{ ssize_t written = sl_RB_write(s->buffer, (uint8_t*)buf + got, n-got); //DBG("Put %zd to buffer, got=%zd, n=%zd", written, got, n); - if(got > n) return NULL; + if(got > n) goto errex; if(written > 0) got += written; }while(got != n); //DBG("All messsages done"); } errex: + FREE(buf); s->rthread = 0; s->connected = FALSE; return NULL; @@ -259,6 +260,7 @@ void url_decode(char *str) { static sl_sock_hresult_e msgparser(sl_sock_t *client, char *str); +// TODO: handle Content-Length correctly // parser of web-encoded data by POST/GET: static sl_sock_hresult_e parse_post_data(sl_sock_t *c, char *str){ if (!c || !str) return RESULT_BADKEY; @@ -462,6 +464,8 @@ static void *serverthread(void _U_ *d){ c->lineno = 0; c->gotemptyline = 0; sl_RB_clearbuf(c->buffer); + DBG("unlock fd=%d", c->fd); + pthread_mutex_unlock(&c->mutex); // now move all data of last client to disconnected if(nfd > 2 && N != nfd - 1){ // don't move the only or the last DBG("lock fd=%d", clients[nfd-1]->fd); @@ -472,8 +476,6 @@ static void *serverthread(void _U_ *d){ pthread_mutex_unlock(&clients[N]->mutex); // now N is nfd-1 poll_set[N] = poll_set[nfd - 1]; } - DBG("unlock fd=%d", c->fd); - pthread_mutex_unlock(&c->mutex); --nfd; } #pragma GCC diagnostic pop @@ -861,7 +863,7 @@ ssize_t sl_sock_sendbinmessage(sl_sock_t *socket, const uint8_t *msg, size_t l){ return l; } DBG("send to fd=%d message with len=%zd (%s)", socket->fd, l, msg); - while(socket && socket->connected && !sl_canwrite(socket->fd)); + while(socket && socket->connected && 1 != sl_canwrite(socket->fd)); if(!socket || !socket->connected) return -1; DBG("lock"); pthread_mutex_lock(&socket->mutex); diff --git a/term.h b/term.h deleted file mode 100644 index 153236e..0000000 --- a/term.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * term.h - * - * Copyright 2016 Edward V. Emelianov - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. - */ -#pragma once -#ifndef __TERM_H__ -#define __TERM_H__ - -#include // tcsetattr, baudrates - -void term_quit(int ex_stat); -int conv_spd(int speed); -void ttys_open(char **ports, int **speeds, int globspeed); - -void set_comlogname(char* nm); -void set_charmode(); - -#endif // __TERM_H__