Skip to content

eddyem/snippets_library

Repository files navigation

libusefull_macros - A collection of useful C snippets for Linux

Version: 0.3.5
Author: Edward V. Emelianov (edward.emelianoff@gmail.com)
License: GPLv3+
Repository: github.com/eddyem/snippets_library


Table of Contents

  1. Overview
  2. Installation
  3. Quick Start
  4. Module Reference
  5. Data Structures
  6. Examples
  7. Internationalization (i18n)
  8. 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 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
  • gettext integration for internationalization

All public identifiers are prefixed with sl_ (for "snippets library") to avoid naming collisions.


Installation

Building from source

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:

cmake .. -DDEBUG=1 -DEXAMPLES=1

Linking

A pkg-config file is installed:

pkg-config --cflags --libs usefull_macros

Or manually:

gcc -o myapp myapp.c -I/usr/local/include -L/usr/local/lib -lusefull_macros -lm -lpthread

Quick Start

#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)

Module Reference

Initialization & Locale

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:

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.


Error & Warning Macros

#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)):

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 message

Memory Management

void *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 NULL

Memory-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 bytes

String Utilities

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

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 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 character

Typical usage:

sl_setup_con();
int ch = sl_getchar();
sl_restore_con();

Important: These functions are not thread-safe - they use a global struct termios2.


Logging

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.


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 (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 header

After 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)).


Configuration Files

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().


Daemon Support

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))):

void sl_iffound_deflt(pid_t pid) {
    fprintf(stderr, "Already running (pid %d)\n", pid);
    exit(1);
}

FIFO / LIFO Linked List

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 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 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_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 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);
  • 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 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 only

Reading (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;

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(...);  // string

Optional key numbering (key[0], key(1), key{2}, key3):

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.


Serial Port (TTY)

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.


Sub-options Parsing

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).


Miscellaneous Utilities

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 write

Data Structures

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

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 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
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.

#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 thread-safe (global terminal state).

About

The common library for my most usefull snippets

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors