10 Commits

Author SHA1 Message Date
503325b68f fix again 2026-07-01 13:46:22 +03:00
566ca36af8 fixed little bug with long-only options 2026-07-01 11:57:07 +03:00
f1162e4df1 fix 2026-05-25 11:49:36 +03:00
850002d800 little fix 2026-05-20 10:01:47 +03:00
2525224423 fixed broken symbols 2026-05-18 12:10:01 +03:00
ca4565faec deleted 2026-05-18 11:55:13 +03:00
52970bfc04 add readme, fix some minor bugs 2026-05-07 14:44:38 +03:00
179e0631ff removed BUG 2026-04-10 15:49:31 +03:00
a2b7977866 add daemonizing function; modify test of copy running 2026-03-31 11:51:41 +03:00
Edward Emelianov
3b6d262583 deleted trash 2026-03-21 21:02:51 +03:00
17 changed files with 967 additions and 367 deletions

View File

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

View File

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

View File

@@ -1,2 +0,0 @@
# snippets_library
The common library for my most usefull snippets

750
Readme.md Normal file
View File

@@ -0,0 +1,750 @@
# `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](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 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
```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 <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:
```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
```
**Memory-mapped 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 read-only 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 non-canonical, no-echo 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); // non-blocking 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 thread-safe** - 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 (multi-parameters)
- 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 non-option
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 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.
```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 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
```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 singly-linked 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 thread-safe, fixed-size 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 high-level socket framework supporting TCP and UNIX domain sockets, with built-in 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 key-value 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;
```
Built-in 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 URL-decoded 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 (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`:
```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
(case-insensitive).
---
### 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); // 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:
```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** thread-safe (global terminal state).
---

View File

@@ -21,6 +21,7 @@
#include <stdio.h> // printf, fopen, ...
#include <unistd.h> // getpid
#include <signal.h>
#include <stdio.h> // perror
#include <sys/types.h> // opendir
#include <dirent.h> // opendir
@@ -73,42 +74,19 @@ void WEAK sl_iffound_deflt(pid_t pid){
/**
* check wether there is a same running process
* exit if there is a running process or error
* Checking have 3 steps:
* 1) lock executable file
* 2) check pidfile (if you run a copy?)
* 3) check /proc for executables with the same name (no/wrong pidfile)
* @param selfname - argv[0] or NULL for non-locking
* Checking have 2 steps:
* 1) check pidfile and its owner (if you run a copy?)
* 2) check /proc for executables with the same name (no/wrong pidfile)
* @param selfname - deprecated, maybe remove in next versions
* @param pidfilename - name of pidfile or NULL if none
*/
void sl_check4running(char *selfname, char *pidfilename){
void sl_check4running(char _U_ *selfname, char *pidfilename){
DIR *dir;
FILE *pidfile, *fself;
FILE *pidfile;
struct dirent *de;
struct stat s_buf;
pid_t pid = 0, self;
struct flock fl;
char *name, *myname;
if(selfname){ // block self
fself = fopen(selfname, "r"); // open self binary to lock
if(!fself){
WARN("fopen()");
goto selfpid;
}
memset(&fl, 0, sizeof(struct flock));
fl.l_type = F_WRLCK;
if(fcntl(fileno(fself), F_GETLK, &fl) == -1){ // check locking
WARN("fcntl()");
goto selfpid;
}
if(fl.l_type != F_UNLCK){ // file is locking - exit
sl_iffound_deflt(fl.l_pid);
}
fl.l_type = F_RDLCK;
if(fcntl(fileno(fself), F_SETLKW, &fl) == -1){
WARN("fcntl()");
}
}
selfpid:
self = getpid(); // get self PID
if(!(dir = opendir(PROC_BASE))){ // open /proc directory
ERR(PROC_BASE);
@@ -120,9 +98,11 @@ void sl_check4running(char *selfname, char *pidfilename){
if(pidfilename && stat(pidfilename, &s_buf) == 0){ // pidfile exists
pidfile = fopen(pidfilename, "r");
if(pidfile){
if(fscanf(pidfile, "%d", &pid) > 0){ // read PID of (possibly) running process
if((name = sl_getPSname(pid)) && strncmp(name, myname, 255) == 0)
if(fscanf(pidfile, "%d", &pid) == 1){ // read PID of (possibly) running process
if((name = sl_getPSname(pid)) && strncmp(name, myname, 255) == 0){
sl_iffound_deflt(pid);
exit(1); // run `exit` if user forgot to do it himself
}
}
fclose(pidfile);
}
@@ -131,8 +111,10 @@ void sl_check4running(char *selfname, char *pidfilename){
while((de = readdir(dir))){ // scan /proc
if(!(pid = (pid_t)atoi(de->d_name)) || pid == self) // pass non-PID files and self
continue;
if((name = sl_getPSname(pid)) && strncmp(name, myname, 255) == 0)
if((name = sl_getPSname(pid)) && strncmp(name, myname, 255) == 0){
sl_iffound_deflt(pid);
exit(1);
}
}
closedir(dir);
free(myname);
@@ -143,3 +125,26 @@ void sl_check4running(char *selfname, char *pidfilename){
fclose(pidfile);
}
}
/**
* @brief sl_daemonize - prepare for daemonize:
* - close stdin/out/err and reopen to /dev/null
* - croot /
* - umask(0)
* - ignore SIGHUP
* @return non-zero if failed
*/
int sl_daemonize(){
if(chdir("/")) return -1;
umask(0);
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
int ofd = open("/dev/null", O_RDWR);
if(ofd < 0) return -1;
if(dup(ofd) < 0) return -1; // STDIN
if(dup(ofd) < 0) return -1; // STDOUT
if(dup(ofd) < 0) return -1; // STDERR
if(SIG_ERR == signal(SIGHUP, SIG_IGN)) return -1;
return 0;
}

View File

@@ -14,3 +14,4 @@ add_executable(fifo fifo.c)
add_executable(conffile conffile.c)
add_executable(clientserver clientserver.c)
add_executable(ringbuffer ringbuffer.c)
add_executable(daemon daemon.c)

View File

@@ -52,9 +52,11 @@ static glob_pars const Gdefault = {
* name has_arg flag val type argptr help
* BE carefull! The `help` field is mandatory! Omitting it equivalent of 'end_option'
*/
// if val < 33 || val > 127 we mean this like long-only option;
// you can repeat the same `val` for several long-only options without any problem
static sl_option_t cmdlnopts[] = {
// short option in only-long options should be zeroed, or you can add flag to set it to given value
{"lo0", NEED_ARG, NULL, 0, arg_int, APTR(&G.lo0), _("only long arg 0 (int)")},
{"longonly",NEED_ARG, NULL, 0, arg_int, APTR(&G.lo0), _("only long arg 0 (int)")},
// for short-only options long option can be NULL
{NULL, NEED_ARG, NULL, '0', arg_string, APTR(&G.so1), _("only short arg 1 (string)")},
// if you change `arg_int` to `arg_none`, value will be incremented each `-h`
@@ -71,7 +73,7 @@ static sl_option_t cmdlnopts[] = {
{"Int", MULT_PAR, NULL, 'I', arg_int, APTR(&G.intarr), _("integer parameter")},
{"Dbl", MULT_PAR, NULL, 'D', arg_double, APTR(&G.dblarr), _("double parameter")},
{"Str", MULT_PAR, NULL, 'S', arg_string, APTR(&G.strarr), _("string parameter")},
{"lo1", NEED_ARG, NULL, 0, arg_int, APTR(&G.lo1), _("only long arg 1 (int)")},
{"long1", NEED_ARG, NULL, 0, arg_int, APTR(&G.lo1), _("only long arg 1 (int)")},
end_option
};
@@ -87,7 +89,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);

105
examples/daemon.c Normal file
View File

@@ -0,0 +1,105 @@
/*
* This file is part of the Snippets project.
* Copyright 2024 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
// example of simplest daemon: search for running process, daemonize if all OK and herd its child
#include <linux/prctl.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <usefull_macros.h>
static pid_t childpid = 0;
typedef struct{
int help;
int verbose;
int nodaemon;
char *logfile;
char *pidfile;
} parameters;
static parameters G = {0};
static sl_option_t cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
{"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verbose), "verbose level (each -v adds 1)"},
{"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&G.logfile), "log file name (FULL path!!!)"},
{"pidfile", NEED_ARG, NULL, 'p', arg_string, APTR(&G.pidfile), "PID-file name (FULL path!!!)"},
{"nodaemon", NO_ARGS, NULL, 0, arg_int, APTR(&G.nodaemon), "don't daemonize"},
end_option
};
void sl_iffound_deflt(pid_t pid){
WARNX("Another copy of this process found, pid=%d. Exit.", pid);
exit(1); // don't run `signals` to protect foreign PID-file from removal
}
void signals(int signo){
if(childpid){ // this is a main process!
LOGERR("Main process exits with status %d", signo);
// main process have nothing to cleanup, just remove PID-file
if(G.pidfile) unlink(G.pidfile);
}else{ // this is child
LOGERR("Killed with status %d, clearing", signo);
// here we can close everything and make cleanup
}
usleep(1000);
LOGERR("Exited");
exit(signo);
}
int main(int argc, char **argv){
sl_init();
sl_parseargs(&argc, &argv, cmdlnopts);
if(G.help) sl_showhelp(-1, cmdlnopts);
sl_loglevel_e lvl = G.verbose + LOGLEVEL_ERR;
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
sl_check4running(NULL, G.pidfile);
LOGMSG("Hello, I'm started");
if(G.logfile) OPENLOG(G.logfile, lvl, 1);
if(!G.nodaemon){
green("Daemonize..\n");
LOGMSG("Daemonize");
// and ignore SIGHUP
if(sl_daemonize()){
WARN("sl_daemonize() failed");
LOGERR("Can't daemonize");
}
}
signal(SIGTERM, signals); // kill (-15) - quit
signal(SIGINT, signals); // ctrl+C - quit
signal(SIGQUIT, signals); // ctrl+\ - quit
signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z
while(1){ // guard for dead processes
childpid = fork();
if(childpid){
LOGMSG("create child with PID %d\n", childpid);
DBG("Created child with PID %d\n", childpid);
wait(NULL);
WARNX("Child %d died\n", childpid);
LOGWARN("Child %d died\n", childpid);
sleep(1);
}else{
prctl(PR_SET_PDEATHSIG, SIGTERM); // send SIGTERM to child when parent dies
break; // go out to normal functional
}
}
LOGMSG("Here is the child process; sleep for 1 second and die");
sleep(1);
return 0;
}

View File

@@ -93,9 +93,9 @@ int main(int argc, char *argv[]){
printf("String[%d]: \"%s\"\n", i, *p++);
}
}
if(GP->lo0 != INT_MIN) printf("You set lo0 to %d\n", GP->lo0);
if(GP->lo1 != INT_MIN) printf("You set lo1 to %d\n", GP->lo1);
if(GP->lo2 != INT_MIN) printf("You set lo2 to %d\n", GP->lo2);
if(GP->lo0 != INT_MIN) printf("You set long 0 to %d\n", GP->lo0);
if(GP->lo1 != INT_MIN) printf("You set long 1 to %d\n", GP->lo1);
if(GP->lo2 != INT_MIN) printf("You set long 2 to %d\n", GP->lo2);
if(GP->so1){
sl_remove_quotes(GP->so1);
printf("String so1=%s\n", GP->so1);

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-04 17:48+0300\n"
"POT-Creation-Date: 2026-03-31 11:35+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -36,7 +36,7 @@ msgstr ""
msgid "Configuration file options (format: key=value):\n"
msgstr ""
#: /home/eddy/Docs/SAO/C_diff/snippets_library/daemon.c:69
#: /home/eddy/Docs/SAO/C_diff/snippets_library/daemon.c:70
#, c-format
msgid ""
"\n"
@@ -44,11 +44,11 @@ msgid ""
msgstr ""
#. error reading self name
#: /home/eddy/Docs/SAO/C_diff/snippets_library/daemon.c:117
#: /home/eddy/Docs/SAO/C_diff/snippets_library/daemon.c:95
msgid "Can't read self name"
msgstr ""
#: /home/eddy/Docs/SAO/C_diff/snippets_library/daemon.c:141
#: /home/eddy/Docs/SAO/C_diff/snippets_library/daemon.c:119
msgid "Can't open PID file"
msgstr ""

View File

@@ -7,7 +7,7 @@
msgid ""
msgstr "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-03-04 17:30+0300\n"
"POT-Creation-Date: 2026-03-31 11:31+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -35,7 +35,7 @@ msgstr "sl_conf_showhelp():
msgid "Configuration file options (format: key=value):\n"
msgstr "ïÐÃÉÉ ËÏÎÆÉÇÕÒÁÃÉÏÎÎÏÇÏ ÆÁÊÌÁ (ÆÏÒÍÁÔ: ËÌÀÞ=ÚÎÁÞÅÎÉÅ):\n"
#: /home/eddy/Docs/SAO/C_diff/snippets_library/daemon.c:69
#: /home/eddy/Docs/SAO/C_diff/snippets_library/daemon.c:70
#, c-format
msgid "\n"
"Found running process (pid=%d), exit.\n"
@@ -43,11 +43,11 @@ msgstr "\n"
"ïÂÎÁÒÕÖÅÎ ÏÄÎÏÉÍÅÎÎÙÊ ÐÒÏÃÅÓÓ (pid=%d), ×ÙÈÏÄ.\n"
#. error reading self name
#: /home/eddy/Docs/SAO/C_diff/snippets_library/daemon.c:117
#: /home/eddy/Docs/SAO/C_diff/snippets_library/daemon.c:95
msgid "Can't read self name"
msgstr "îÅ ÍÏÇÕ ÐÒÏÞÅÓÔØ ÉÍÑ ÐÒÏÃÅÓÓÁ"
#: /home/eddy/Docs/SAO/C_diff/snippets_library/daemon.c:141
#: /home/eddy/Docs/SAO/C_diff/snippets_library/daemon.c:119
msgid "Can't open PID file"
msgstr "îÅ ÍÏÇÕ ÏÔËÒÙÔØ PID ÆÁÊÌ"
@@ -236,5 +236,3 @@ msgstr "
#: /home/eddy/Docs/SAO/C_diff/snippets_library/usefull_macros.c:331
msgid "Can't setup console"
msgstr "îÅ ÍÏÇÕ ÎÁÓÔÒÏÉÔØ ËÏÎÓÏÌØ"
#, c-format

View File

@@ -30,6 +30,9 @@
#include <ctype.h> // isalpha
#include "usefull_macros.h"
// check `val` field: if it's <33 || >127 - it's just ordered number, not short option
#define CHKOPTVAL(Oval) ((Oval) > 32 && (Oval) < 128)
const char *helpstring = NULL; // will be inited later, can't init with gettext on this stage
/**
@@ -124,18 +127,33 @@ static int get_optind(const char *key, int opt, sl_option_t *options, void (*hel
int oind = 0, theopt = opt;
sl_option_t *opts = options;
assert(opts);
//printf("key: %s\n", key);
// `opt` should be ':' for "missed arguments", '?' for "not found" and short flag if found and checked
if(opt == '?'){ // not found
fprintf(stderr, _("No such parameter: `%s`\n"), key);
helpfun(-1, options);
return -1; // never reached until `helpfun` changed
}else if(opt == ':') theopt = optopt; // search to show helpstring "need parameter"
for(oind = 0; opts->help && opts->val != theopt; oind++, opts++){
DBG("cmp %c and %c", theopt, opts->val);
if(key[1] != '-'){ // search by unique short option
//printf("Short %c\n", theopt);
for(oind = 0; opts->help && opts->val != theopt; oind++, opts++){
DBG("cmp %c and %c", theopt, opts->val);
}
}else{ // search by long option
key += 2;
int l = strlen(key);
for(oind = 0; opts->help; oind++, opts++){
//printf("comp %s and %s\n", key, opts->name);
if(opts->name && 0 == strncmp(key, opts->name, l)) break;
}
}
if(!opts->help){
//printf("Not found\n");
return -1;
}
if(!opts->help) return -1;
if(opt == ':'){
fprintf(stderr, _("Parameter `%s` needs value\n"), key);
if(options[oind].name) fprintf(stderr, _("Parameter `--%s` needs value\n"), options[oind].name);
else fprintf(stderr, _("Parameter `-%c` needs value\n"), options[oind].val);
helpfun(oind, options);
return -1; // never reached until `helpfun` changed
}
@@ -182,6 +200,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){
@@ -248,7 +267,7 @@ void sl_parseargs_hf(int *argc, char ***argv, sl_option_t *options, void (*helpf
loptr->flag = opts->flag;
loptr->val = opts->val;
// fill short options if they are:
if(!opts->flag && opts->val){
if(!opts->flag && CHKOPTVAL(opts->val)){
shortlist[i] = (char) opts->val;
*soptr++ = opts->val;
if(loptr->has_arg) // add ':' if option has required argument
@@ -370,21 +389,22 @@ void sl_parseargs(int *argc, char ***argv, sl_option_t *options){
/**
* @brief argsort - compare function for qsort
* first - sort by short options; second - sort arguments without sort opts (by long options)
* first - sort by short options; second - sort arguments without short opts (by long options)
*/
static int argsort(const void *a1, const void *a2){
const sl_option_t *o1 = (sl_option_t*)a1, *o2 = (sl_option_t*)a2;
const char *l1 = o1->name, *l2 = o2->name;
int s1 = o1->val, s2 = o2->val;
int chk1 = CHKOPTVAL(s1), chk2 = CHKOPTVAL(s2);
int *f1 = o1->flag, *f2 = o2->flag;
// check if both options has short arg
if(f1 == NULL && f2 == NULL && s1 && s2){ // both have short arg
if(f1 == NULL && f2 == NULL && chk1 && chk2){ // both have short arg
return (s1 - s2);
}else if((f1 != NULL || !s1) && (f2 != NULL || !s2)){ // both don't have short arg - sort by long
}else if((f1 != NULL || !chk1) && (f2 != NULL || !chk2)){ // both don't have short arg - sort by long
assert(l1); assert(l2); // no way to omit long option if short is absent
return strcmp(l1, l2);
}else{ // only one have short arg -- return it
if(f2 || !s2) return -1; // a1 have short - it is 'lesser'
if(f2 || !chk2) return -1; // a1 have short - it is 'lesser'
else return 1;
}
}
@@ -393,7 +413,7 @@ static int argsort(const void *a1, const void *a2){
static void pr_helpstring(sl_option_t *opt, char *buf, int indent, size_t bufsz){
size_t p = sprintf(buf, " "); // a little indent
int havelongopt = opt->name && *opt->name;
if(!opt->flag && opt->val){ // .val is short argument
if(!opt->flag && CHKOPTVAL(opt->val)){ // .val is short argument
p += snprintf(buf+p, bufsz-p, "-%c", opt->val);
if(havelongopt) p += snprintf(buf+p, bufsz-p, ", "); // show comma only it there's shor arg
}

View File

@@ -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){

View File

@@ -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
@@ -716,7 +718,7 @@ int sl_sock_open(sl_socktype_e type, const char *path, int isserver, int ai_sock
break;
}
for(struct addrinfo *p = res; p; p = p->ai_next){
if((sock = socket(p->ai_family, p->ai_socktype|SOCK_NONBLOCK, p->ai_protocol)) < 0) continue;
if((sock = socket(p->ai_family, p->ai_socktype/*|SOCK_NONBLOCK*/, p->ai_protocol)) < 0) continue;
DBG("Try proto %d, type %d, socktype %d", p->ai_protocol, p->ai_socktype, p->ai_socktype);
if(isserver){
int reuseaddr = 1;
@@ -736,7 +738,7 @@ int sl_sock_open(sl_socktype_e type, const char *path, int isserver, int ai_sock
}
}else{
if(connect(sock, p->ai_addr, p->ai_addrlen) == -1){
WARN("connect()");
//WARN("connect()");
close(sock); sock = -1;
continue;
}
@@ -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);

254
term.c
View File

@@ -1,254 +0,0 @@
/*
* This file is part of the Snippets project.
* Copyright 2013 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include <unistd.h> // tcsetattr, close, read, write
#include <sys/ioctl.h> // ioctl
#include <stdio.h> // printf, getchar, fopen, perror
#include <stdlib.h> // exit, realloc
#include <sys/stat.h> // read
#include <fcntl.h> // read
#include <signal.h> // signal
#include <time.h> // time
#include <string.h> // memcpy
#include <stdint.h> // int types
#include <sys/time.h> // gettimeofday
#include <unistd.h> // usleep
#include "usefull_macros.h"
#define LOGBUFSZ (1024)
typedef struct {
int speed; // communication speed in bauds/s
tcflag_t bspeed; // baudrate from termios.h
} spdtbl;
static int tty_init(sl_tty_t *descr);
static spdtbl speeds[] = {
{50, B50},
{75, B75},
{110, B110},
{134, B134},
{150, B150},
{200, B200},
{300, B300},
{600, B600},
{1200, B1200},
{1800, B1800},
{2400, B2400},
{4800, B4800},
{9600, B9600},
{19200, B19200},
{38400, B38400},
{57600, B57600},
{115200, B115200},
{230400, B230400},
{460800, B460800},
{500000, B500000},
{576000, B576000},
{921600, B921600},
{1000000, B1000000},
{1152000, B1152000},
{1500000, B1500000},
{2000000, B2000000},
{2500000, B2500000},
{3000000, B3000000},
{3500000, B3500000},
{4000000, B4000000},
{0,0}
};
/**
* @brief sl_tty_convspd - test if `speed` is in .speed of `speeds` array
* @param speed - integer speed (bps)
* @return 0 if error, Bxxx if all OK
*/
tcflag_t sl_tty_convspd(int speed){
spdtbl *spd = speeds;
int curspeed = 0;
do{
curspeed = spd->speed;
if(curspeed == speed)
return spd->bspeed;
++spd;
}while(curspeed);
WARNX(_("Wrong speed value: %d!"), speed);
return 0;
}
/**
* @brief tty_init - open & setup terminal
* @param descr (io) - port descriptor
* @return 0 if all OK or error code
*/
static int tty_init(sl_tty_t *descr){
// |O_NONBLOCK ?
if ((descr->comfd = open(descr->portname, O_RDWR|O_NOCTTY)) < 0){
WARN(_("Can't use port %s"), descr->portname);
return globErr ? globErr : 1;
}
if(tcgetattr(descr->comfd, &descr->oldtty) < 0){ // Get settings
WARN(_("Can't get old TTY settings"));
return globErr ? globErr : 1;
}
descr->tty = descr->oldtty;
descr->tty.c_lflag = 0; // ~(ICANON | ECHO | ECHOE | ISIG)
descr->tty.c_iflag = 0;
descr->tty.c_oflag = 0;
descr->tty.c_cflag = descr->baudrate|CS8|CREAD|CLOCAL; // 9.6k, 8N1, RW, ignore line ctrl
descr->tty.c_cc[VMIN] = 0; // non-canonical mode
descr->tty.c_cc[VTIME] = 5;
if(tcsetattr(descr->comfd, TCSANOW, &descr->tty) < 0){
WARN(_("Can't apply new TTY settings"));
return globErr ? globErr : 1;
}
// make exclusive open
if(descr->exclusive){
if(ioctl(descr->comfd, TIOCEXCL)){
WARN(_("Can't do exclusive open"));
}}
return 0;
}
/**
* @brief restore_tty - restore opened TTY to previous state and close it
*/
void sl_tty_close(sl_tty_t **descr){
if(descr == NULL || *descr == NULL) return;
sl_tty_t *d = *descr;
if(d->comfd){
ioctl(d->comfd, TCSANOW, &d->oldtty); // return TTY to previous state
close(d->comfd);
}
FREE(d->portname);
FREE(d->buf);
FREE(*descr);
}
/**
* @brief sl_tty_new - create new TTY structure with partially filled fields
* @param comdev - TTY device filename
* @param speed - speed (number)
* @param bufsz - size of buffer for input data (or 0 if opened only to write)
* @return pointer to TTY structure if all OK
*/
sl_tty_t *sl_tty_new(char *comdev, int speed, size_t bufsz){
tcflag_t spd = sl_tty_convspd(speed);
if(!spd) return NULL;
sl_tty_t *descr = MALLOC(sl_tty_t, 1);
descr->portname = strdup(comdev);
descr->baudrate = spd;
descr->speed = speed;
if(!descr->portname){
WARNX(_("Port name is missing"));
}else{
if(bufsz){
descr->buf = MALLOC(char, bufsz+1);
descr->bufsz = bufsz;
return descr;
}else WARNX(_("Need non-zero buffer for TTY device"));
}
FREE(descr->portname);
FREE(descr);
return NULL;
}
/**
* @brief sl_tty_open - init & open tty device
* @param d - already filled structure (with new_tty or by hands)
* @param exclusive - == 1 to make exclusive open
* @return pointer to TTY structure if all OK
*/
sl_tty_t *sl_tty_open(sl_tty_t *d, int exclusive){
if(!d || !d->portname || !d->baudrate) return NULL;
if(exclusive) d->exclusive = TRUE;
else d->exclusive = FALSE;
if(tty_init(d)) return NULL;
return d;
}
static struct timeval tvdefault = {.tv_sec = 0, .tv_usec = 5000};
/**
* @brief sl_tty_tmout - set timeout for select() on reading
* @param usec - microseconds of timeout
* @return -1 if usec < 0, 0 if all OK
*/
int sl_tty_tmout(double usec){
if(usec < 0.) return -1;
tvdefault.tv_sec = 0;
if(usec > 999999){
tvdefault.tv_sec = (__time_t)(usec / 1e6);
usec -= tvdefault.tv_sec * 1e6;
}
tvdefault.tv_usec = (__suseconds_t) usec;
return 0;
}
/**
* @brief sl_tty_read - read data from TTY with 10ms timeout
* @param buff (o) - buffer for data read
* @param length - buffer len
* @return amount of bytes read or -1 if disconnected
*/
int sl_tty_read(sl_tty_t *d){
if(!d || d->comfd < 0) return 0;
size_t L = 0;
ssize_t l;
size_t length = d->bufsz;
char *ptr = d->buf;
fd_set rfds;
struct timeval tv;
int retval;
do{
l = 0;
FD_ZERO(&rfds);
FD_SET(d->comfd, &rfds);
//memcpy(&tv, &tvdefault, sizeof(struct timeval));
tv = tvdefault;
retval = select(d->comfd + 1, &rfds, NULL, NULL, &tv);
if(!retval) break;
if(retval < 0){
if(errno == EINTR) continue;
return -1;
}
if(FD_ISSET(d->comfd, &rfds)){
l = read(d->comfd, ptr, length);
if(l < 1) return -1; // disconnected
ptr += l; L += l;
length -= l;
}
}while(l && length);
d->buflen = L;
d->buf[L] = 0;
return (size_t)L;
}
/**
* @brief sl_tty_write - write data to serial port
* @param buff (i) - data to write
* @param length - its length
* @return 0 if all OK
*/
int sl_tty_write(int comfd, const char *buff, size_t length){
ssize_t L = write(comfd, buff, length);
if((size_t)L != length){
WARN("Write error");
return 1;
}
return 0;
}

34
term.h
View File

@@ -1,34 +0,0 @@
/*
* term.h
*
* Copyright 2016 Edward V. Emelianov <eddy@sao.ru, edward.emelianoff@gmail.com>
*
* 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 <termios.h> // 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__

View File

@@ -329,6 +329,8 @@ void WEAK sl_iffound_deflt(pid_t pid);
void sl_check4running(char *selfname, char *pidfilename);
// read name of process by its PID
char *sl_getPSname(pid_t pid);
// daemonize: reopen stdin/out/err as /dev/null, chdir to /, umask(0)
int sl_daemonize(); // return -1 if failed, 0 on OK
/******************************************************************************\
The original fifo_lifo.h