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ì 18 settembre 2019

L'invasione dei Multithread
considerazioni sulla Programmazione Multithread in C

(...una premessa: questo è il seguito naturale (e promesso) dell'articolo Errno Miei (correre a rileggerlo, prego). Potrebbe apparire un po' polemico (ebbene si, lo è), ma prendetelo con le pinze: io non sono un buonista e preferisco dire "pane al pane e vino al vino", e questo potrebbe urtare qualcuno (che, in tal caso, direi che se lo merita), però potrebbe alimentare la fame di conoscenza che è il fiore all'occhiello di tutti gli appassionati di informatica, e sono sicuro che quasi tutti voi disponete di questa caratteristica. Ah, e ci tengo a precisare: non sono il messia, sicuramente dico e faccio tante scemate, ma a volte ci azzecco, e quindi: i permalosi e/o privi di senso dell'umorismo sono esentati dalla lettura dell'articolo, e chi continua non dica che non l'avevo avvertito...)
Dr. Bassett: Allora, che ne pensa, dottor Hill?
Dr. Hill: Secondo me si tratta di un incubo. 
Dr. Bassett: Altro non può essere. Semi che provengono da altri mondi e che generano esseri umani! Mh, roba da pazzi!
Nel mitico L'invasione degli ultracorpi si narra di una invasione di extraterrestri che si sostituiscono agli umani, e i sostituti sono molto somiglianti ma smascherabili analizzandone il comportamento. Ecco, a volte ho l'impressione che, nel nostro mondo (informatico) reale, stiamo assistendo a un'altra invasione, quella dei (falsi) "Programmatori specialisti in Multithread" che non sono quei programmatori specializzati nel fare 10 lavori alla volta (in quello, ahimè, siamo tutti specializzati, chi più chi meno...), ma sono quei programmatori che sanno scrivere applicazioni multithread.
...scappiamo, mica che ci prendono per Programmatori Multithread...
Oramai gli annunci di ricerca sono quasi tutti del tipo "Cercasi A.P. Junior (anche primo impiego) con 10 anni di esperienza in Applicazioni Multithread", e anche l'annuncio dell'arrivo in ufficio di un nuovo collega è sempre del tipo "domani arriva tizio: è uno specialista in Multithreading... no, non fa 10 lavori per volta". Poi ti capita (true story capitata a mio cuggino) che metti le mani sul codice scritto da uno di questi specialisti e ti senti obbligato a chiedere:
mio cuggino: Scusa, solo per curiosità, com'è che hai scritto questa enorme applicazione multithread senza mai usare una funzione della famiglia "nomefunzione_r"?
specialista multithread: "nomefunzione_r"? Uhm... in questo momento mi sfugge il significato... A cosa servono?
mio cuggino: ...e poi ho visto che hai usato un po' di variabili globali senza proteggerle...
specialista multithread:  Ah, le variabili globali... dici che è meglio non usarle?
mio cuggino: E vabbè, continuiamo così, facciamoci del male...
Il problema è che, per essere un vero programmatore multithread non basta aver scritto una (sola) volta del "codice che faceva uso di ben due thread", e non basta nemmeno (per la proprietà transitiva) "avere stretto la mano a uno che ha parlato una volta con un vero specialista in multithreading": bisogna avere le basi e averci sbattuto la testa (veramente) per qualche annetto (...hey, ci sono passato anch'io quando ero ggiovane: ricordatevi che nessuno nasce imparato...). Adesso: visto che le basi sono tante (ho detto "race condition"? oppure "deadlock"? oppure bla, bla, bla) e non è che con un articolo scritto da un volenteroso e umile (ehm...) programmatore si possa affrontarle tutte, ma, tanto per cominciare, si può iniziare chiarendo due concetti misteriosi e fondamentali: sto parlando di "Thread safety" e "Reentrancy".

Ebbene, la teoria dice che:
Thread safety: una funzione è thread-safe se può essere eseguita da più thread in modo sicuro (ovvero ottenendo sempre il risultato che ci si aspetta), anche se le chiamate si verificano contemporaneamente su più thread e si manipolano dati condivisi. 
Reentrancy: una funzione è rientrante se può essere interrotta in qualsiasi momento durante la sua esecuzione e quindi richiamata in modo sicuro ("rientrata") prima che le sue precedenti invocazioni completino l'esecuzione.
(...le definizioni qui sopra le potete trovare in rete in versioni più o meno simili: questo è un argomento un po' controverso, quindi per alcuni queste definizioni potrebbero risultare limitate, oppure eccessive, oppure non completamente esatte. Comunque credo che rendano bene l'idea...)

Come si nota i due concetti descritti sono simili e parzialmente sovrapponibili, ma si può dire che la "Reentrancy" sia, come dire? A un livello superiore, perché è una specie di "Thread safety" applicabile anche in applicazioni single-thread, e difatti è un concetto più antico, applicato da molto tempo (perfino in sistemi come l'orribile MS-DOS), visto che quando si lavora con gli Interrupt (o coi Segnali) le funzioni di servizio invocate devono essere rientranti... ma non voglio mettere troppa carne al fuoco: parleremo di Interrupt, Segnali e routine di servizio in un altro post, promesso.

Riepiloghiamo: una funzione thread-safe può essere chiamata tranquillamente su più thread, mentre che una funzione rientrante può fare le stesse cose e, in più, ti permette di interrompere e richiamare in sicurezza più istanze della stessa funzione all'interno dello stesso thread. Quindi, se usiamo sempre funzioni rientranti siamo al sicuro? Beh, in pratica si, ma in teoria no, perché in realtà abbiamo quattro possibilità (...eh si: la magica potenza del 2...):
  1. Funzioni thread-safe e rientranti
  2. Funzioni thread-safe e non rientranti
  3. Funzioni non thread-safe e rientranti
  4. Funzioni non thread-safe e non rientranti
il caso 1 (thread-safe e rientranti) è, fortunatamente, il più probabile e frequente: come già anticipato nel post Errno Miei esiste una famiglia di funzioni "nomefunzione_r" della libc (tipo la strerror_r()) dove la desinenza "_r" indica "rientrante", e sono, per nostra fortuna, scritte come si deve, quindi sono anche thread-safe. Uno specialista in multithreading sa che nelle applicazioni che scrive deve sempre preferire l'uso di queste funzioni. Un'altra cosa che deve sapere lo specialista in multithreading è che anche le funzioni che scrive lui nell'applicazione devono essere del tipo 1 (thread-safe e rientranti) o, come minimo, del tipo 2 (solo thread-safe). E, infine, uno specialista non scriverà mai una funzione del tipo 3 o del tipo 4, e se è costretto a a usare una funzione di libreria non thread-safe la dovrà usare con le dovute accortezze (più avanti vedremo qualche esempio).

Le linee guida che deve seguire uno specialista quando scrive una funzione che userà in una applicazione multithread sono varie, ma per semplificare possiamo descrivere le due fondamentali:
  1. Non usare mai variabili globali o statiche (che sono delle globali travestite).
  2. Non chiamare mai (internamente alla funzione) altre funzioni non thread-safe (logico, no?).
E ora possiamo vedere in pratica quanto detto finora: scriveremo una semplice funzione per trasformare in minuscolo una stringa, e lo faremo MALE (non thread-safe e non rientrante), BENINO (thread-safe e non rientrante) e BENE (thread-safe e rientrante). Sono, evidentemente, esempi stra-semplificati, senza controllo degli errori, ecc. (ma sono solo degli esempi, cosa pretendete?). Vai col codice!

// MALE: versione non thread-safe e non rientrante
char *strLower1(const char *src)
{
    // questo buffer statico rende strLower() non rientrante
    static char buf[256];

    // loop per scrivere la versione lower sulla destinazione
    int i = 0;
    while (*src)
        buf[i++] = tolower(*src++);  // Ok, tolower() è thread-safe

    buf[i] = 0;     // aggiungo il terminatore

    // ritorno il buffer destinazione
    return buf;
}

// BENINO: versione thread-safe e non rientrante (con Thread Local Storage)
char *strLower2(const char *src)
{
    // questo buffer statico rende strLower() non rientrante
    static __thread char buf[256];  // con __thread il buffer è locale ad ogni thread

    // loop per scrivere la versione lower sulla destinazione
    int i = 0;
    while (*src)
        buf[i++] = tolower(*src++);  // Ok, tolower() è thread-safe

    buf[i] = 0;     // aggiungo il terminatore

    // ritorno il buffer destinazione
    return buf;
}

// BENINO: versione thread-safe e non rientrante (con mutex)
pthread_mutex_t buf_lock;   // con un mutex proteggiamo gli accessi al buffer statico

char *strLower3(const char *src)
{
    // questo buffer statico rende strLower() non rientrante
    static char buf[256];

    // loop per scrivere la versione lower sulla destinazione
    pthread_mutex_lock(&buf_lock);      // lock del buffer
    int i = 0;
    while (*src)
        buf[i++] = tolower(*src++);      // Ok, tolower() è thread-safe

    buf[i] = 0;     // aggiungo il terminatore
    pthread_mutex_unlock(&buf_lock);    // unlock del buffer

    // ritorno il buffer destinazione
    return buf;
}

// BENE: versione thread-safe e rientrante
char *strLower4(char *dest, const char *src)
{
    // salvo localmente dest
    char *my_dest = dest;

    // loop per scrivere la versione lower sulla destinazione
    while (*src)
        *my_dest++ = tolower(*src++);    // Ok, tolower() è thread-safe

    *my_dest = 0;   // aggiungo il terminatore

    // ritorno il dest salvato
    return dest;
}
Credo che gli abbondanti commenti nel codice abbiano già chiarito come e perché le varie versioni mostrate hanno comportamenti differenti a livello di sicurezza. Come si nota l'unica versione che rispetta le due linee guida fondamentali riportate sopra è la strLower4(), che ha l'unico inconveniente di avere una interfaccia (e quindi un uso) diverso, visto che bisogna passargli il buffer destinazione: questo che il chiamante si deve occupare di allocare e passare la destinazione è il classico "trucco" usato nelle versioni "_r" della libc (l'avevamo già visto a proposito della strerror_r(), ricordate?).

E, come al solito, il MALE sta nelle variabili globali (e anche di questo ne abbiamo già parlato): se proprio non possiamo farne a meno dobbiamo proteggerle, come negli esempi, usando Thead Local Storage o, ad esempio, un mutex. Faccio notare che strLower2() e strLower3() sono simili (thread-safe e non rientranti) ma strLower3() è peggio: usando (per sbaglio!) strLower2() in una routine di servizio sotto Interrupt (o Segnale) avremmo solo degli errori, mentre con strLower3() potremmo avere anche un deadlock (il mutex viene bloccato alla prima interruzione e una seconda interruzione lo lascia bloccato indefinitamente), quindi attenzione!

Qualcuno avrà notato che non ho fornito un esempio del caso 3 (non thread-safe e rientranti): il fatto è che solo con un codice molto contorto si può arrivare a ottenere questo (cioè: bisogna proprio farlo apposta!), quindi direi che è meglio non fornire un esempio che fornirne uso assurdo.

La funzione strLower3() ci può essere utile come base di descrizione su come  fare a inserire funzioni di libreria non thread-safe che, per qualche strano motivo, non possiamo evitare di di usare: possiamo proteggerle con un mutex, più o meno così:
// versione thread-safe e non rientrante (con una ipotetica tolower() non thread-safe)
pthread_mutex_t fun_lock;   // con un mutex proteggiamo l'uso di tolower()

char *strLower5(char *dest, const char *src)
{
    // salvo localmente dest
    char *my_dest = dest;

    // loop per scrivere la versione lower sulla destinazione
    pthread_mutex_lock(&fun_lock);      // lock della funzione
    while (*src)
        *my_dest++ = tolower(*src++);    // e se tolower non fosse thread-safe?

    *my_dest = 0;   // aggiungo il terminatore
    pthread_mutex_unlock(&fun_lock);    // unlock della funzione

    // ritorno il dest salvato
    return dest;
}
Ovviamente questo sopra è un esempio sbagliato, visto che tolower() è sicuramente thread-safe, ma serve ugualmente a rendere l'idea. E direi che per oggi può bastare. Come anticipato, in un futuro articolo (non necessariamente il prossimo) completeremo il discorso con alcuni esempi di uso di funzioni rientranti sotto Interrupt (o Segnali). Non trattenete il respiro nell'attesa, mi raccomando...

Ciao, e al prossimo post!

giovedì 8 agosto 2019

Lo chiamavano Jeeg OpenSSL
come scrivere TCP Server e Client con OpenSSL in C - pt.3

Enzo/Jeeg: T'è annata male, Zingaro! Si vòi diventà famoso te conviene torna' a fa' l'imitazione der Grande Fratello! 
Fabio/Zingaro: Era Buona Domenica, cojone!
E ci risiamo un'altra volta: anche per questa terza puntata di Lo chiamavano Jeeg Robot (oops: Lo chiamavano Jeeg OpenSSL) abbiamo due notizie, una buona e una cattiva. La buona è che questo post completa (finalmente) il discorso della libreria MySSL. La cattiva notizia è che ho, finalmente, scoperto che diventare ricchi e famosi col Software è veramente molto complicato... Un consiglio: iscrivetevi, invece, al casting del Grande Fratello, quello è il futuro! (no, è mejo Buona Domenica, cojone!).
...e usa sta' MySSL cojone!...
(...si lo so, lo so: nell'ultimo articolo avevo promesso che avremmo proseguito e approfondito il discorso errno/strerror disquisendo di reentrant/thread-safe: ma siamo ad agosto, e l'articolo promesso potrebbe risultare troppo pesante per chi ha già la testa (e anche il corpo, spero) in vacanza... Vi/mi faccio un favore: ne riparleremo a Settembre, così oltre alla depressione post-vacanze vi beccherete anche un articolo pesantone in grado di schiantarvi del tutto...

Allora: perché una terza puntata di Lo chiamavano Jeeg OpenSSL? Se ben ricordate (e se non vi ricordate questa è una occasione imperdibile per rileggere la parte 1 e la parte 2 della serie) abbiamo lasciato la saga con la proposta di una semplice libreria per scrivere in maniera user-friendly applicazioni che usano OpenSSL (che è in effetti un po' ostica da usare così com'è). Era una piccola libreria di smart-wrapper per alcune delle funzioni OpenSSL e l'avevo scritta ad-hoc per il blog, usando una struttura ultra-semplificata: era composta solo da due file, myssl.c e myssl.h, e doveva essere compilata e linkata direttamente con la applicazione che la usava (ad esempio un SSL Server).

Per questa terza parte del post ho deciso di rendere la libreria un po' più professionale, come struttura e come distribuzione, quindi l'ho rivoluzionata (abbastanza) e l'ho trasformata in un repository GitHub. La potete trovare qui (...in realtà già ai tempi della seconda parte del post il mio ottimo collega Andrea Simone Costa mi aveva suggerito di farlo, ma avevo rimandato la cosa a tempi più propizi, tipo ora...). Vi riporto subito il file README.md del repository GitHub così non ci perdiamo in chiacchiere (per l'occasione tradotto in Italiano, ma su GitHub lo trovate in Inglese): 
MySSL 
Un'interfaccia user-friendly per la libreria OpenSSL

Panoramica
----------
La libreria MySSL è una semplice interfaccia per la libreria OpenSSL che 
consente una scrittura user-friendly (in C o C++) di SSL Server e SSL Client.

Note
----
- Licenza GNU MySSL (omissis)
- Licenza OpenSSL (omissis)
- La libreria "OpenSSL" di riferimento è la ver.1.0.2g
- Attualmente la libreria MySSL supporta solo sistemi Linux

Installazione
-------------
Questa è attualmente una versione beta e utilizza un Makefile molto semplice: 
quindi per generare e testare la libreria MySSL è necessario eseguire alcuni 
passaggi manuali:

1. Posizionarsi nella directory src e generare la libreria con 
   "make clean && make". Questo genera una libreria condivisa (libmyssl.so) e 
   la copia nella directory tests/lib. Il header-file myssl.h viene copiato 
   nella directory tests/include.
2. Posizionarsi nella directory tests e generare i due programmi di test
   (sslserver e sslclient) con "make clean && make".
3. Leggere il paragrafo "Testing" di questo file per vedere come eseguire un 
   semplice test della libreria MySSL utilizzando due terminali.
4. Notare che in questa versione beta la libreria non viene installata 
   automaticamente, quindi per eseguire il test è necessario installare 
   libmyssl.so nel sistema oppure è possibile eseguire un'installazione 
   temporanea in entrambi i due terminali in questo modo:

   export LD_LIBRARY_PATH = "~/path-di-myssl-package/tests/lib"

La prossima versione della libreria MySSL utilizzerà Autotools per generare e 
installare automaticamente e per rendere il pacchetto portatile su molti sistemi
Unix-like.

Testing
-------
Due test sono forniti nelle directory tests/server e tests/client e si può 
modificare liberamente il codice sorgente in base alle proprie esigenze (è Free
Software :).

I due programmi di test sono un SSL Server e un SSL Client che utilizzano la 
libreria MySSL e, oltre a testare la libreria, possono servire da esempio su 
come scrivere codice usando la libreria MySSL.

Le due directory di test forniscono esempi di base (e funzionanti) dei 
certificati SSL utilizzati dalla libreria MySSL per funzionare.

Per un rapido test della libreria MySSL, è possibile eseguire i seguenti 
programmi in due shell aperte nelle directory tests/server e tests/client:

1. ./sslserver 8888
2. ./sslclient 127.0.0.1 8888

TODO list
---------
- Generazione e installazione della libreria con Autotools
- Documentazione
- Supporto Windows
Ecco, penso che il README.md sia più che sufficiente a descrivere la nuova MySSL (l'ho scritto apposta!), quindi approfitto dell'occasione per descrivere, invece, i passi che ho eseguito per trasformare una libreria con struttura semplice in una più complessa per GitHub: questa guida è generica ed è, ovviamente, molto soggettiva, ma secondo me potrebbe essere un buon spunto per tutti quelli che si accingono a fare una operazione del genere. Vediamo i passi che ho eseguito:
  1. Ho diviso il "sorgentone" originale myssl.c in più sorgenti, uno per ogni funzione della libreria (sslread.c, sslwrite.c, ecc.).
  2. Ho diviso il "headerone" originale myssl.h in due header, myssl.h (pubblico) e myssl-private.h (privato): i sorgenti della libreria MySSL includono entrambi i file, mentre gli applicativi che usano MySSL includeranno solo l'header pubblico.
  3. Ho aggiunto opportuni header in testa a tutti i file con varie informazioni descrittive (seguendo un po' lo stile dei manuali Unix/Linux e lo standard POSIX).
  4. Ho aggiunto opportuni header in testa a tutte le funzioni della libreria (seguendo un po' lo stile dei manuali Unix/Linux e lo standard POSIX).
  5. Ho tradotto tutto (header e commenti nei sorgenti) in inglese: sono tutt'altro che un esterofilo però, visto che GitHub è un sito di uso globale, bisogna dare una opportunità di lettura diretta a tutto il pubblico potenziale.
  6. Ho creato una struttura di directory semplice e funzionale: src (sorgenti della libreria) e tests (applicazioni di test): per entrambe ho scritto un Makefile. Il Makefile in src genera la libreria, mentre quello in tests genera le due applicazioni di test (un SSL Server e un SSL Client).
  7. Ho scritto i "canonici" file GitHub (README.md, LICENSE e .gitignore) e li ho copiati nella root-directory della libreria.
  8. E, finalmente, ho copiato tutto nel repository GitHub. Missione compiuta!
That's all folks! E buone vacanze!

Ciao e al prossimo post!

lunedì 8 luglio 2019

Errno miei
come funziona errno e come usarlo in C

(...una premessa: questo post è un remake di un mio vecchio post (anzi, di due). L'ho riadattato e "modernizzato" per pubblicarlo su quell'altro bel blog collettivo dove scrivo. Visto che le modifiche sono molte e (forse) interessanti, lo ripubblico anche qui. Questo potrebbe ripetersi in futuro...)
Mascetti: Tarapìa tapiòco! Prematurata la supercazzola, o scherziamo?
Vigile: Prego?
Mascetti: No, mi permetta. No, io... scusi, noi siamo in quattro. Come se fosse antani anche per lei soltanto in due, oppure in quattro anche scribàcchi confaldina? Come antifurto, per esempio.
Vigile: Ma che antifurto, mi faccia il piacere! Questi signori qui stavano sonando loro. 'Un s'intrometta!
Mascetti: No, aspetti, mi porga l'indice; ecco lo alzi così... guardi, guardi, guardi. Lo vede il dito? Lo vede che stuzzica? Che prematura anche? Ma allora io le potrei dire, anche con il rispetto per l'autorità, che anche soltanto le due cose come vicesindaco, capisce?
Vigile: Vicesindaco? Basta 'osì, mi seguano al commissariato, prego!
Perozzi: No, no, no, attenzione! Noo! Pàstene soppaltate secondo l'articolo 12, abbia pazienza, sennò posterdati, per due, anche un pochino antani in prefettura...
Mascetti: ...senza contare che la supercazzola prematurata ha perso i contatti col tarapìa tapiòco.
A volte penso che per spiegare come funziona e come usare errno sarebbe sufficiente usare una Supercazzola come quella dello specialista Mascetti nel capolavoro Amici Miei. Tanto nessuno ti ascolta, errno funziona e tanto basta, quindi perché approfondire? Beh, non sono d'accordo, ho visto (ahimè) molti usi impropri di errno, quindi un po' di sana teoria di base può far bene (anzi, fa sempre bene). Ovviamente gli eventuali "Masters of Errno" qui presenti potranno saltare a piè pari la lettura dell'articolo (che potrebbe risultare noioso, lo ammetto...).
...errno come se fosse antani...
Allora: cos'è errno? Molte funzioni di libreria e system calls in caso di errore aggiornano il valore di una variabile globale (errno, giust'appunto) che quindi contiene, in ogni momento, il valore dell'ultimo errore di esecuzione. Anticamente errno era definito così nel header (della libc) errno.h:
    extern int errno;
e si riferiva a una semplice variabile globale della libc, per l'appunto un int chiamato errno. Poi sono arrivati i thread, con lo standard POSIX 1003.1c (detto anche POSIX.1c, ma fa lo stesso) e quindi errno ha dovuto adeguarsi al modo thread-safe. Come si può ben leggere nel manuale di errno (dopo POSIX.1c) ora la definizione è:
    errno is defined by the ISO C standard to be a modifiable lvalue of
    type int, and must not be explicitly declared; errno may be a macro.
    errno is thread-local; setting it in one thread does not affect its
    value in any other thread.
Quindi la nuova definizione di errno è ora in bits/errno.h (che viene incluso dal errno.h classico). Semplificando un po' (ho omesso alcuni dettagli per facilitare la lettura) il nuovo giro del fumo è:
nel header-file errno.h
#include <bits/errno.h>
/* Declare the `errno' variable, unless it's defined as a macro by
   bits/errno.h.  This is the case in GNU, where it is a per-thread
   variable.  This redeclaration using the macro still works, but it
   will be a function declaration without a prototype and may trigger
   a -Wstrict-prototypes warning.  */
#ifndef errno
extern int errno;
#endif

nel header-file bits/errno.h
/* Function to get address of global `errno' variable. */
extern int *__errno_location (void);

/* When using threads, errno is a per-thread value. */
#define errno (*__errno_location ())
Quindi, in parole povere, ora errno non è più un int globale ma è "il contenuto di un indirizzo ritornato da una funzione globale". Ovviamente la variabile int a cui punta la funzione in oggetto è il nuovo errno locale di un thread (ossia: ogni thread ha il suo errno). Un esempio (mooolto semplificato) di come si potrebbe implementare la __errno_location() è il seguente:
// errno locale di un thread: è una variabile di tipo thread-local storage (TLS)
__thread int th_errno;

int *__errno_location(void)
{
    // ritorna l'indirizzo della variabile th_errno
    return &th_errno;
}
E, alla fine della fiera, nonostante i cambi descritti, sarà ancora possibile fare operazioni di questo tipo:
int my_errno = errno; // Ok, equivale a: int my_errno = (* __errno_location());
errno = EADDRINUSE;   // Ok, equivale a: (* __errno_location()) = EADDRINUSE;
perché, ovviamente, tutto è stato pensato per essere retro-compatibile, e quindi errno, nonostante ora sia una macro, deve ancora comportarsi come quando era un semplice int.

E, a questo punto, non possiamo farci mancare un piccolo estratto dello standard POSIX.1c, che puntualizza tutto quello detto fin'ora:
Redefinition of errno
In POSIX.1, errno is defined as an external global variable. But this definition
is unacceptable in a multithreaded environment, because its use can result in 
nondeterministic results. The problem is that two or more threads can encounter 
errors, all causing the same errno to be set. Under these circumstances, a thread 
might end up checking errno after it has already been updated by another thread. 

To circumvent the resulting nondeterminism, POSIX.1c redefines errno as a service 
that can access the per-thread error number as follows (ISO/IEC 9945:1-1996, n2.4): 

Some functions may provide the error number in a variable accessed through the 
symbol errno. The symbol errno is defined by including the header <errno.h>, as 
specified by the C Standard ... For each thread of a process, the value of errno 
shall not be affected by function calls or assignments to errno by other threads... 
E ora facciamo un passo avanti: come possiamo sfruttare l'esistenza di errno? A parte usarlo direttamente come numero e agire opportunamente nel codice in base all'errore indicato, è prassi abituale cercare la descrizione  dell'errore (come stringa di testo) con un meccanismo che ci viene gentilmente offerta dalla nostra amata libc: infatti, parallelamente a errno, viene mantenuta un'altra variabile globale, _sys_errlist, che contiene le stringhe corrispondenti a ogni error number, per cui (dettaglio importantissimo di cui molti si dimenticano) prima che qualche altra parte del programma in esecuzione alteri il valore di errno, si può localizzare in _sys_errlist la stringa di errore che vogliamo trattare. Questa ultima operazione si può fare usando la funzione strerror(), in una delle sue (tante) varianti.

Ebbene si: la strerror() ha molte personalità, quindi quale scelgo? la strerror() o la strerror_r()? E se uso quest'ultima quale scelgo, la versione XSI-compliant o la versione GNU-specific? (per non parlare, poi, delle altre varianti, la strerror_l(), la strerror_s(), ecc., ma queste sono varianti secondarie).

Cominciamo con la prima domanda: strerror() o strerror_r()? Anticamente esisteva solo la prima, ma poi, come detto sopra, sono apparsi i thread e anche qui sono cominciati i problemi, perché nel codice multithread ci sono alcune parti critiche dove bisognerebbe usare solo funzioni thread-safe. La strerror() non è dichiarata thread-safe nello standard, e per capire il perché basta analizzare una implementazione semplificata (però molto simile alle implementazioni reali che possiamo trovare nelle varie libc disponibili). Vai col codice!
#include <stdio.h> // stdio.h include sys_errlist.h che dichiara le variabili
                   // globali _sys_errlist (array errori) e _sys_nerr (num.errori)
static char buf[256]; // buffer globale statico per la stringa da ritornare

char *strerror(int errnum)
{
    // test se errnum è un valore valido
    if (errnum < 0 || errnum >= _sys_nerr || _sys_errlist[errnum] == NULL) {
        // errore sconosciuto: copio in buf un messaggio di errore generico
        snprintf(buf, sizeof(buf), "Unknown error %d", errnum);
    }
    else {
        // errore conosciuto: copio in buf il messaggio corrispondente
        snprintf(buf, sizeof(buf), "%s", _sys_errlist[errnum]);
    }

    // ritorno buf che ora contiene il messaggio di errore
    return buf;
}
risulta evidente dal codice (ben commentato, come sempre, così non devo spiegarlo riga per riga) che la strerror() non ritorna direttamente _sys_errlist[errnum] (e se fosse così sarebbe intrinsecamente thread-safe) ma compone un messaggio di errore (per trattare anche gli error number non validi) usando un buffer globale statico buf: quindi, se due thread di una applicazione usano (quasi) contemporaneamente la strerror() il contenuto di buf non sarà attendibile (prevale il thread che ha scritto per ultimo). 

(...apro una parentesi: non è vietato scrivere una strerror() che sia thread-safe, e in alcuni sistemi lo è: ma visto che secondo lo standard non lo è, non possiamo essere sicuri che sul sistema che stiamo usando (o sul sistema su cui, un giorno, girerà la applicazione che stiamo scrivendo) non ci sia una implementazione come quella appena descritta, quindi...)

Allora, per il Software multithread è nata la strerror_r() che è thread-safe. Come funziona? vai col codice!
#include <stdio.h> // stdio.h include sys_errlist.h che dichiara le variabili
                   // globali _sys_errlist (array errori) e _sys_nerr (num.errori)

char *strerror_r(int errnum, char *buf, size_t buflen);
{
    // test se errnum è un valore valido
    if (errnum < 0 || errnum >= _sys_nerr || _sys_errlist[errnum] == NULL) {
        // errore sconosciuto: copio in buf un messaggio di errore generico
        snprintf(buf, buflen, "Unknown error %d", errnum);
    }
    else {
        // errore conosciuto: copio in buf il messaggio corrispondente
        snprintf(buf, buflen, "%s", _sys_errlist[errnum]);
    }

    // ritorno buf che ora contiene il messaggio di errore
    return buf;
}
anche in questo caso si tratta di codice semplificato, ma molto vicino alla realtà: il trucco è semplice, invece di usare un buffer globale statico (che è la fonte dei problemi della strerror()) il chiamante della funzione si deve preoccupare di allocare e passare un buffer (e la sua lunghezza) alla strerror_r(). In questo modo il buffer che usa la strerror_r() è locale al thread che la chiama, e non può essere sovrascritto da un altro thread concorrente. Abbiamo sacrificato un po' di semplicità d'uso ma abbiamo ottenuto l'agognato comportamento thread-safe!

Ed ora aggiungiamo un po' di complicazione: la versione di strerror_r() appena mostrata è la GNU-specific. Ma, sfortunatamente, esiste anche la XSI-compliant, che è la seguente:
int strerror_r(int errnum, char *buf, size_t buflen);
Come si nota questa seconda versione non ritorna il buffer con la error-string, ma ritorna, invece, un codice di errore, e la stringa trovata bisogna ripescarla direttamente nel buffer che abbiamo passato. Per quanto riguarda il codice di ritorno è 0 in caso di successo e, in base alla versione di libc in uso, potrebbe ritornare -1 in caso di errore (settando errno al valore specifico di errore) oppure un valore positivo corrispondente a errno (bah, questo doppio comportamento non è proprio il massimo della semplicità d'uso...). Per usare questa versione o la GNU-specific bisogna giocare opportunamente con i flag _GNU_SOURCE, _POSIX_C_SOURCE e _XOPEN_SOURCE del preprocessore (come descritto nel manuale della strerror()).

Il motivo per cui esiste la versione XSI-compliant è descritto nello stesso  estratto dello standard POSIX.1c citato precedentemente, al posto dei puntini di sospensione finali c'è questa parte:
...In addition, all POSIX.1c functions avoid using errno and, instead, return the 
error number directly as the function return value, with a return value of zero 
indicating that no error was detected. This strategy is, in fact, being followed 
on a POSIX-wide basis for all new functions.
E ora siamo pronti per la seconda domanda: quale usiamo, la GNU-specific o la XSI-compliant? Beh, io direi che quando scriviamo del codice per trattare dei codici di errore probabilmente non ci interessa trattare anche gli errori generati in questa fase (e nella fase successiva, ecc., ecc., un loop infinito di ricerca degli errori!); ci interessa, invece, scrivere codice lineare e semplice... per toglierci il dubbio possiamo analizzare due piccoli esempi d'uso:
GNU-specific
if ((my_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    // errore socket()
    char errbuf[MAX_ERROR_LEN];    // buffer per strerror_r()
    printf("socket() error (%s)\n", strerror_r(errno, errbuf, sizeof(errbuf)));
    return EXIT_FAILURE;
}
XSI-compliant
if ((my_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    // errore socket()
    char errbuf[MAX_ERROR_LEN];    // buffer per strerror_r()
    int my_error = strerror_r(errno, errbuf, sizeof(errbuf)));
    if (! my_error)
        printf("socket() error (%s)\n", errbuf);
    else {
        // tratto l'errore (magari usando di nuovo strerror_r()?)
        ...
    }

    return EXIT_FAILURE;
}
Non so voi cosa ne pensate, ma io uso sempre la versione GNU-specific! A voi la scelta...

In conclusione: abbiamo analizzato, nel dettaglio, come funziona errno. Poi abbiamo visto come usarlo. E, ancora, abbiamo esaminato le varianti principali della strerror(), notando che, molto spesso, conviene usare la strerror_r() che è la versione thread-safe... Cosa ci manca da approfondire? Ah si, ecco: perchè la strerror_r() si chiama così? E perchè c'è una intera famiglia di funzioni che finiscono in "_r"? Uhm... la "_r" sta per reentrant che, al contrario delle credenze comuni, non è esattamente la stessa cosa di thread-safe... Si, forse dobbiamo approfondire ulteriormente... (SPOILER ALERT: sarà nel prossimo articolo!).

Ciao, e al prossimo post!