Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

mercoledì 9 luglio 2025

Signal Handler
come scrivere un signal handler in C - pt.3

Michele Apicella: [Sfogandosi con una delle sue piante che sta seccando e morendo] Hai troppo sole, poco sole, cos'è che vuoi? Più acqua, meno acqua? Perché non parli!? Rispondi! [rovescia il vaso].

Dove eravamo rimasti? Ah si! Avevo chiuso la seconda parte dell'articolo sui signal handler (e la prima parte la trovate qui) elencando due possibili problemi residui da risolvere in qualche maniera. Avevo scritto:

  1. Si può verificare sperimentalmente che, su alcuni sistemi Linux non completamente “canonici”  (come alcune implementazioni embedded oppure il terribile WSL), se viene fatto un restart del processo rsyslogd(8) il signal handler appena visto non riesce più a scrivere sul socket del syslog. Invece sui sistemi Linux standard (Mint, Ubuntu, ecc.) il problema non si presenta.
  2. In alcuni casi potrebbe essere necessario aggiungere dettagli “dinamici”  nelle stringhe destinate al syslog, ma come farlo senza usare, ad esempio, la snprintf(3)?

E quindi, come promesso, oggi ci dedicheremo a questa versione "plus" del nostro signal handler, sempre accompagnati dal nostro Michele Apicella (Nanni Moretti) che, nel bellissimo film Bianca, se la prendeva anche con le piante, un po' come succede a me (a noi) quando un programma non risponde bene ai segnali... (ah ah ah).

...ti avevo detto di uscire quando arrivava il segnale 15!...
E proseguiamo con il nostro nuovo signal handler che farà un po' più rispetto al già buon esempio dello scorso articolo. Ma, visto che questo "un po' più" mi preoccupa comincio con una avvertenza:
...All functions not in the above lists are considered to be unsafe with respect
to signals. That is to say, the behaviour of such functions when called from a
signal handler is undefined. In general though, signal handlers should do little
more than set a flag; most other actions are not safe...
[SIGACTION(2) FreeBSD Manual Pages]

Ecco, questa raccomandazione cade a fagiolo, e ci indica che un signal handler DEVE essere semplice, perché svolge un ruolo molto delicato e dobbiamo essere sicuri che faccia almeno il suo compito principale; ogni prestazione aggiuntiva potrebbe rovinarne il funzionamento. Quindi, io oggi vi darò alcune dritte (spero interessanti) ma sono da applicare "cum grano salis"...

(...ah, come dite? Ma quello sopra è tratto da un manuale di FreeBSD, non di Linux... Embè? FreeBSD è praticamente uno UNIX, quindi famiglia POSIX, quindi parente strettissimo di Linux (più padre che fratello, direi...), per cui (quasi) tutto ciò che è valido per FreeBSD lo è anche per Linux, non vi preoccupate...)

Ok, e come si possono affrontare i due punti descritti all'inizio dell'articolo? Cominciamo col punto 1:

Punto 1: la connessione

Risolvere il problema della perdita della connessione col socket collegato a /dev/log è abbastanza semplice: invece di effettuare questa connessione una volta per tutte nel main del programma (salvando il socket nella apposita variabile volatile sig_atomic_t) possiamo effettuare la connessione ogni volta che il socket handler intercetta un segnale, così saremo a prova di disconnessione. Certo, dovremo rivedere un po' la funzione connectSyslog() dell'ultimo articolo, pulendola di tutto ciò che non è async-signal-safe. Si può fare! Ma attenzione: questo appesantisce un po' il signal handler ! (vedi la raccomandazione fatta sopra...). E vediamola 'sta funzione! vai col codice!

// connectSyslog - collego il socket del syslog (/dev/log) in modo async-signal-safe
static int connectSyslog()
{
// preparo la struct sockaddr_un per il collegamento via Unix Domain Socket
struct sockaddr_un reader;
memset(&reader, 0, sizeof(reader));
reader.sun_family = AF_UNIX;
strcpy(reader.sun_path, "/dev/log");

// provo a creare il socket in modo IPC Stream
int log_sock;
if ((log_sock = socket(AF_UNIX, SOCK_STREAM, 0)) != -1) {
// connect to the syslog socket
if (connect(log_sock, (struct sockaddr *)&reader, sizeof(reader)) != -1) {
// successo della connect(2)
return log_sock;
} else {
// errore connect(2)
close(log_sock);
}
}

// errore IPC Stream: provo a creare il socket in modo IPC Datagram
if ((log_sock = socket(AF_UNIX, SOCK_DGRAM, 0)) != -1) {
// connect to the syslog socket
if (connect(log_sock, (struct sockaddr *)&reader, sizeof(reader)) != -1) {
// successo della connect(2)
return log_sock;
} else {
// errore connect(2)
close(log_sock);
}
}

// connessione non effettuata
return -1;
}

Beh, questa era semplice: come avrete visto è la connectSyslog() dell'ultimo articolo con qualche piccola modifica: niente scrittura degli eventuali errori e una snprintf(3) sostituita da una strcpy(3) che è async-signal-safe. Fatto! Possiamo passare al punto 2.

Punto 2: La stringa dinamica

Certo, senza usare la snprintf(3) è un po' complicato creare in maniera molto flessibile delle stringhe di log  con dati dinamici, ma se è sufficiente costruire una stringa con dati fissi e aggiungere solo qualche numero, anche in questo caso Si può fare! Bisogna aggiungere una funzione di costruzione (che ho chiamato builLogStr()) e una per trasformare numeri in stringhe (che ho chiamato myItoa(), visto che è, effettivamente, una itoa()). Entrambe queste funzioni devono essere il più semplici possibili e non usare nulla che non sia async-signal-safe. E vediamo La buildLogStr()! Vai col codice!

// buildLogStr - costruisco la log string in modo async-signal-safe
static char *buildLogStr(char *dest, int signum)
{
// prendo il numero PID e lo converto in una stringa
pid_t pid = getpid(); // getpid(2) è async-signal-safe
char str_pid[16];
myItoa(pid, str_pid);

/* costruisco la stringa
(e.g.: "[1428] testsighandler: ricevuto un SIGSEGV. Esco con errore.") */
char *last = stpcpy(dest, "<11>[");
last = stpcpy(last, str_pid); // stpcpy(3) è async-signal-safe
last = stpcpy(last, "] testsighandler: ");

switch (signum) {
case SIGSEGV:
stpcpy(last, "ricevuto un SIGSEGV. Esco con errore.");
break;

case SIGINT:
stpcpy(last, "ricevuto un SIGINT. Esco con errore.");
break;

case SIGBUS:
stpcpy(last, "ricevuto un SIGBUS. Esco con errore.");
break;

case SIGABRT:
stpcpy(last, "ricevuto un SIGABRT. Esco con errore.");
break;

case SIGTERM:
stpcpy(last, "ricevuto un SIGTERM. Esco correttamente.");
break;

default:
break;
}

return dest;
}

Che ne dite? La buildLogStr(), che è, come al solito, stra-commentata, costruisce per passi una stringa usando solo funzioni async-signal-safe. Il punto di riferimento per la costruzione è il numero del segnale, che guida il risultato finale. L'obiettivo è creare una stringa di log del tipo indicato nei commenti, e cioè qualcosa come questo:

[numero PID] <nome applicazione>: <testo>

ossia, per esempio, questo:

[1428] testsighandler: ricevuto un SIGSEGV. Esco con errore.

E, come avrete sicuramente notato, giocando opportunamente con uno switch è un compito relativamente semplice.

E, a questo punto, l'unica cosa che manca è il numero PID  come stringa, ma questo ce lo fornisce la myItoa(): per questa funzione mi sono rifatto al bellissimo esempio contenuto nella Bibbia del C, il K&R "The C Programming Language (2nd ed.)". È una itoa() che usa esclusivamente istruzioni di basso livello più una sola funzione async-signal-safe, la strlen(3). E vediamo anche questa! vai col codice!

/* myItoa - una itoa() async-signal-safe
(ispirata dal K&R "The C Programming Language (2nd ed.)") */
static void myItoa(int n, char *s)
{
// check del segno
int sign;
if ((sign = n) < 0) // registro il segno
n = -n; // lo rendo positivo

// genero i digit in ordine inverso
int i = 0;
do {
*(s + i++) = n % 10 + '0'; // prendo il prossimo digit
} while ((n /= 10) > 0); // lo cancello

// re-check del segno e termino la stringa
if (sign < 0)
*(s + i++) = '-';

*(s + i) = '\0';

// inverto il risultato
int j;
for (i = 0, j = strlen(s) - 1; i < j; i++, j--) { // strlen(3) è async-signal-safe
int c = *(s + i);
*(s + i) = *(s + j);
*(s + j) = c;
}
}

Per completezza, e per concludere, vi mostro anche il nuovo main del nuovo testsighandler:

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/un.h>

// prototipi locali
static void sigHandler(int signum);
static void sigAction();
static int connectSyslog();
static char *buildLogStr(char *dest, int signum);
static void myItoa(int n, char *s);

/* un flag per mantenere attivo il loop pseudo-infinito.
volatile potrebbe essere necessario a seconda del sistema/implementazione
in uso. (vedere "C11 draft standard n1570: 5.1.2.3") */
volatile sig_atomic_t resta_attivo = 1;

// main - funzione main
int main(int argc, char *argv[])
{
// prepara il signal handler
sigAction();

// un loop pseudo-infinito per testare la applicazione
while (resta_attivo)
sleep(1);

// il loop pseudo-infinito è stato interrotto da un SIGTERM
printf("programma terminato correttamente\n");
return 0;
}

Insomma, il signal handler plus è fatto! Certo che abbiamo dovuto aggiungere alla versione originale ben tre funzioni: connectSyslog(), buildLogStr() e myItoa() il che appesantisce notevolmente il disegno iniziale e non rispetta molto la raccomandazione fatta sopra (quella del manuale FreeBSD). E, tanto per confondervi ulteriormente le idee, vi aggiungo: anche una funzione che usa internamente solo funzioni async-signal-safe non è garantito al 100% che sia veramente safe, perché se "fa troppe cose"  potrebbe non essere veramente interrompibile a piacere (vedi di nuovo la raccomandazione iniziale). Comunque, se è proprio indispensabile fare un signal handler  "sofisticato" il metodo che vi ho proposto direi che è un ottimo compromesso. Usatelo, se vi serve, ma occhio alle controindicazioni!

Credo che possiamo considerare concluso (almeno per un po' di tempo...) l'argomento signal handler: dopo ben tre articoli non ho molto altro da aggiungere. E ora cosa ci riserva il futuro? Boh... ci sto già pensando: non so esattamente cosa sarà ma vi assicuro che sarà interessante!

Ciao, e al prossimo post!