Version: 0.3.5
Author: Edward V. Emelianov (edward.emelianoff@gmail.com)
License: GPLv3+
Repository: github.com/eddyem/snippets_library
- Overview
- Installation
- Quick Start
- Module Reference
- Initialization & Locale
- Colored Terminal Output
- Error & Warning Macros
- Memory Management
- String Utilities
- Number Conversion
- Console / Terminal I/O
- Logging
- Command-line Argument Parsing
- Configuration Files
- Daemon Support
- FIFO / LIFO Linked List
- Ring Buffer
- TCP / UNIX Socket Server & Client
- Serial Port (TTY)
- Sub-options Parsing
- Miscellaneous Utilities
- Data Structures
- Examples
- Internationalization (i18n)
- Thread Safety
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 type-safe callbacks - INI-like configuration file reading/writing
- Daemonization with PID-file management
- Thread-safe ring buffer for producer-consumer patterns
- FIFO/LIFO linked list
- TCP and UNIX socket server/client framework with built-in HTTP parsing and key-value handler dispatch
- Serial port (TTY) management with non-standard baud rates
- File-based logging with multiple severity levels
gettextintegration for internationalization
All public identifiers are prefixed with sl_ (for "snippets library") to avoid naming collisions.
git clone https://github.com/eddyem/snippets_library.git
cd snippets_library
mkdir build && cd build
cmake ..
make -j$(nproc)
make installThe build produces a shared library libusefull_macros.so and a pkg-config file.
| 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:
cmake .. -DDEBUG=1 -DEXAMPLES=1A pkg-config file is installed:
pkg-config --cflags --libs usefull_macrosOr manually:
gcc -o myapp myapp.c -I/usr/local/include -L/usr/local/lib -lusefull_macros -lm -lpthread#include <usefull_macros.h>
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:
gcc -o hello hello.c $(pkg-config --cflags --libs usefull_macros)void sl_init(void);Must be called once at the beginning of main(). It:
- Detects whether
stdout/stderrare terminals and sets up colored output functions accordingly. - Calls
setlocale(LC_ALL, "")andsetlocale(LC_NUMERIC, "C")(decimal point is always a dot). - If compiled with
GETTEXTdefined, binds the message domain.
Important: sl_init() must be called before any other library function that generates output.
When output is a terminal (not redirected to a file or pipe), text can be printed in color:
extern int (*red)(const char *fmt, ...);
extern int (*green)(const char *fmt, ...);These function pointers are set by sl_init(). Use them like printf:
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.
#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), continueThese 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)):
void signals(int sig) {
// custom cleanup
exit(sig);
}Debug macros (active only when -DEBUG is defined):
FNAME() // print current function name, file, line
DBG(...) // printf-like debug messagevoid *sl_alloc(size_t N, size_t S);Safe calloc wrapper. Exits with error message if allocation fails.
Convenience macros:
ALLOC(type, var, size) // declare + allocate: type *var = calloc(size, sizeof(type))
MALLOC(type, size) // allocate without declaration
FREE(ptr) // free and set to NULLMemory-mapped files:
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 read-only into memory; sl_munmap unmaps and frees the structure.
System memory query:
uint64_t sl_mem_avail(void); // available physical memory in byteschar *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
0if the line is empty or a comment (starts with#). - Returns
1if only a key is present. - Returns
2if both key and value are found. - Ignores inline comments, strips surrounding whitespace and quotes.
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.
For non-canonical, no-echo terminal input:
void sl_setup_con(void); // switch terminal to raw mode
void sl_restore_con(void); // restore original terminal settings
int sl_read_con(void); // non-blocking read (0 if no key)
int sl_getchar(void); // blocking read of one characterTypical usage:
sl_setup_con();
int ch = sl_getchar();
sl_restore_con();Important: These functions are not thread-safe - they use a global struct termios2.
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:
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.
Built on top of getopt_long. Supports:
- Short and long options
- Required, optional, and no arguments
- Multiple occurrences of the same option (multi-parameters)
- Six data types:
int,long long,double,float,char*, and function callback - Automatic help generation
Option descriptor:
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:
#define APTR(x) ((void*)x)Functions:
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 headerAfter calling sl_parseargs, argc and argv are updated to point to remaining non-option
arguments.
Example:
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 non-option arguments
}Multi-parameters (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
heap-allocated 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)).
Reads key-value pairs from a file and treats them as command-line options.
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 non-comment 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().
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 - overridablesl_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
/procfor 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))):
void sl_iffound_deflt(pid_t pid) {
fprintf(stderr, "Already running (pid %d)\n", pid);
exit(1);
}A simple singly-linked list with both head and tail pointers.
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 headsl_list_pop returns the data pointer and frees the node. The caller is responsible for freeing
the data if needed.
A thread-safe, fixed-size ring buffer for byte streams, protected by pthread_mutex_t.
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_readlinereads up to and including a newline (\n), replaces\nwith\0.sl_RB_readtoreads until (and including) a specified byte.sl_RB_writestrensures the string ends with\nbefore writing.- All read/write operations are atomic with respect to the mutex.
A high-level socket framework supporting TCP and UNIX domain sockets, with built-in HTTP method detection.
Socket types:
typedef enum { SOCKT_UNIX, SOCKT_NETLOCAL, SOCKT_NET } sl_socktype_e;Creating and destroying:
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);pathfor UNIX sockets: file path; prefix with\0or@for abstract namespace.pathfor INET sockets:"host:port"(client) or":port"(server).handlers:NULL-terminated array of key-value handlers (see below).bufsiz: internal ring buffer size (minimum 256).
Sending data:
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 onlyReading (client):
ssize_t sl_sock_readline(sl_sock_t *sock, char *str, size_t len);Handler dispatch (server):
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;Field help allows you to show help message for key when user sends "help" message, format:
"key: help". All items shown are strictly in user's order.
Field and key could be NULL, in this case "help" for this item will just send string in help.
The handler in this case should be inited with sl_comment.
Field data is for internal user's usage or for key numbering (see later).
End of list is handler == NULL.
Handler results:
typedef enum {
RESULT_OK, RESULT_FAIL, RESULT_BADVAL,
RESULT_BADKEY, RESULT_SILENCE
} sl_sock_hresult_e;Built-in handlers for common types:
sl_sock_hresult_e sl_sock_inthandler(...); // int64_t
sl_sock_hresult_e sl_sock_dblhandler(...); // double
sl_sock_hresult_e sl_sock_strhandler(...); // stringOptional key numbering (key[0], key(1), key{2}, key3):
In this case you should init field data in sl_sock_hitem_t with pointer to sl_sock_keyno_t.
Then the appropriate handler called with sl_sock_keyno_t->n inited with this number.
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:
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 URL-decoded and dispatched; POST data is accumulated and then parsed.
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 (5-8), 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.
Parses strings like key1=val1:key2=val2,key3:
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
(case-insensitive).
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); // non-blocking select() for read
int sl_canwrite(int fd); // non-blocking select() for writeWith qsort() you can use these functions:
sl_cmpchar- sort array of chars;sl_cmpint- sort array of integers;sl_cmpdouble- sort array of doubles;sl_cmpstring- sort array ofchar*;sl_cmpstringp- sort array ofchar**.
| Structure | Purpose |
|---|---|
sl_option_t |
Command-line option descriptor |
sl_suboption_t |
Sub-option descriptor |
sl_tty_t |
Serial port state |
sl_log_t |
Log file descriptor |
sl_mmapbuf_t |
Memory-mapped file |
sl_list_t |
Linked list node |
sl_ringbuffer_t |
Thread-safe 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 |
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 command-line parsing with all types, logging, serial port, signals |
conffile |
Configuration file reading, sl_print_opts, multi-parameters |
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:
cmake .. -DEXAMPLES=1
makeIf 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.
#define _(String) gettext(String) // when GETTEXT is defined
#define _(String) (String) // otherwise- 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_conare not thread-safe (global terminal state).