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:
- 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.
- 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!... |
...All functions not in the above lists are considered to be unsafe with respectto signals. That is to say, the behaviour of such functions when called from asignal handler is undefined. In general though, signal handlers should do littlemore 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-safestatic int connectSyslog(){// preparo la struct sockaddr_un per il collegamento via Unix Domain Socketstruct 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 Streamint log_sock;if ((log_sock = socket(AF_UNIX, SOCK_STREAM, 0)) != -1) {// connect to the syslog socketif (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 Datagramif ((log_sock = socket(AF_UNIX, SOCK_DGRAM, 0)) != -1) {// connect to the syslog socketif (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 effettuatareturn -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-safestatic char *buildLogStr(char *dest, int signum){// prendo il numero PID e lo converto in una stringapid_t pid = getpid(); // getpid(2) è async-signal-safechar 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-safelast = 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 segnoint sign;if ((sign = n) < 0) // registro il segnon = -n; // lo rendo positivo// genero i digit in ordine inversoint 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 stringaif (sign < 0)*(s + i++) = '-';*(s + i) = '\0';// inverto il risultatoint j;for (i = 0, j = strlen(s) - 1; i < j; i++, j--) { // strlen(3) è async-signal-safeint 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 localistatic 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/implementazionein uso. (vedere "C11 draft standard n1570: 5.1.2.3") */volatile sig_atomic_t resta_attivo = 1;// main - funzione mainint main(int argc, char *argv[]){// prepara il signal handlersigAction();// un loop pseudo-infinito per testare la applicazionewhile (resta_attivo)sleep(1);// il loop pseudo-infinito è stato interrotto da un SIGTERMprintf("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!