Nei titoli e nei testi troverete qualche rimando cinematografico (eh 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 mando avanti anche una versione in Spagnolo (e, normalmente, i post sono disponibili un po' in ritardo rispetto alla versione in Italiano...). Potete trovarlo su El arte de la programación en C. Buona lettura.

sabato 11 marzo 2017

The FileCopy
come scrivere una funzione di File Copy in C

Ok, questo post non centra niente con The Thing, il capolavoro immortale di John Carpenter (a parte la piccola assonanza del nome). Anzi, è solo una scusa per celebrarlo, dato che l'ho rivisto (per la millesima volta) da poco. Comunque, dato che ci siamo, parleremo anche un po' di C...

capolavoro immortale
Con questo post vedremo come scrivere una funzione per fare una copia di un file, dato che i sistemi POSIX (come Linux) non prevedono una funzione specifica di libreria per farlo. Ci sono, evidentemente, mille maniere per scriverla, e questa volta ne ho pensate due. Vai con la prima!
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/sendfile.h>

// prototipi locali
static int cpFile(const char* src, const char* dest);

// funzione main()
int main(int argc, char *argv[])
{
    // test argumenti
    if (argc != 3) {
        // errore: conteggio argomenti errato
        printf("%s: wrong arguments counts\n", argv[0]);
        printf("usage: %s srcfile destfile [e.g.: %s try.c try.save]\n", argv[0], argv[0]);
        return EXIT_FAILURE;
    }

    // esegue copy
    if (cpFile(argv[2], argv[1]) == -1) {
        // mostra errore ed esce
        fprintf(stderr, "%s: error: %s\n", argv[0], strerror(errno));
        exit(EXIT_FAILURE);
    }

    // esce
    return EXIT_SUCCESS;
}

// funzione cpFile()
static int cpFile(
    const char* dest,               // file destinazione
    const char* src)                // file sorgente
{
    // apre il file sorgente
    int fd_in;
    if ((fd_in = open(src, O_RDONLY)) == -1) {
        // return con errore
        return -1;
    }

    // apre il file destinazione
    int fd_out;
    if ((fd_out = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 00644)) == -1) {
        // chiude il file e return con errore
        close(fd_in);
        return -1;
    }

    // r/w loop per la copia usando unbuffered I/O
    size_t n_read;
    char buffer[BUFSIZ];
    while ((n_read = read(fd_in, buffer, sizeof(buffer))) > 0) {
        // write buffer
        if (write(fd_out, buffer, n_read) == -1) {
            // chiude i file e return con errore
            close(fd_in);
            close(fd_out);
            return -1;
        }
    }

    // chiude i file
    close(fd_in);
    close(fd_out);

    // esce con l'ultimo risultato di read() (0 o -1)
    return n_read;
}
Ok, come vedete è ampiamente commentato e quindi è auto-esplicativo, per cui non mi dilungherò sulle singole istruzioni e/o gruppi di istruzioni (leggete i commenti! sono li per quello!), ma aggiungerò, solo, qualche dettaglio strutturale. Il main(), in questo caso, serve solo per testare la funzione di copia e il programma generato si comporta (a livello basico) come la funzione POSIX cp(1), che è proprio quella che vogliamo emulare usando la nostra nuova funzione di libreria.

La funzione che esegue il lavoro l'ho chiamata cpFile() ed è abbastanza semplice, come si vede. Usa l'I/O non bufferizzato (quindi, per esempio, read(2) invece di fread(3)) e, pur essendo compattissima ed efficiente, tratta anche in maniera esaustiva gli errori ed è scritta per essere una funzione di libreria, quindi non scrive nulla su stderr e stdout ma si limita a eseguire il lavoro e a restituire un codice di ritorno (0 o -1) che può essere trattato dal chiamante (in questo caso il main()) per visualizzare eventuali errori usando strerror(3) ed errno. Tutto il lavoro viene eseguito in un loop che legge un buffer dal file sorgente e lo scrive nel file destinazione, fino alla fine del file. Il resto del codice è apertura/chiusura dei file e trattamento degli errori. Visto che usiamo l'unbuffered I/O ho dimensionato il buffer di lettura/scrittura usando la define BUFSIZ del sistema che dovrebbe garantire la dimensione ottimale per le operazioni di I/O.

Avevo detto che avrei proposto due versioni: fermo restando il main() (che va bene per entrambi i casi) la versione alternativa è questa:
// funzione cpFile()
static int cpFile(
    const char* dest,               // file destinazione
    const char* src)                // file sorgente
{
    // apre il file sorgente
    int fd_in;
    if ((fd_in = open(src, O_RDONLY)) == -1) {
        // return con errore
        return -1;
    }

    // apre il file destinazione
    int fd_out;
    if ((fd_out = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 00644)) == -1) {
        // chiude il file e return con errore
        close(fd_in);
        return -1;
    }

    // copia in kernel-space usando la funzione sendfile()
    off_t bytesCopied = 0;
    struct stat fileinfo = {0};
    fstat(fd_in, &fileinfo);
    int result = sendfile(fd_out, fd_in, &bytesCopied, fileinfo.st_size);

    // chiude i file
    close(fd_in);
    close(fd_out);

    // esce con il risultato di sendfile()
    return result;
}
Come vedete è quasi sovrapponibile alla precedente ma è diversa proprio nella parte che esegue il lavoro di copia: al posto del loop viene usata la funzione sendfile(2), che ci permette di eseguire una copia diretta e super-efficiente a livello kernel-space (mentre la prima versione lavorava in user-space).

Senza entrare nei dettagli profondi che tutto questo comporta (kernel-space e user-space dei sistemi della famiglia UNIX), mi limito a precisare che questa seconda versione è migliore della prima ma è meno portabile, visto che la sendfile(2) ha comportamenti diversi in base al sistema (per esempio su Linux si può usare solo dal Kernel 2.6.33 in avanti, mentre su macOS, viceversa, funziona solo fino alla versione 10.8). E già che ci siamo specifichiamo meglio: anche la prima versione non è completamente portabile, visto che su alcuni sistemi (tipo quello che comincia con W e che preferisco non nominare neanche) la system call read(2) non c'è.

Ok, allora potete già intuire che l'argomento del prossimo post sarà una versione con buffered I/O della funzione cpFile(), ossia una versione intrinsecamente portabile, già che userà l'I/O standard del C (quello contenuto in stdio.h, per intenderci).

Non trattenete il respiro nell'attesa, mi raccomando...

Ciao e al prossimo post!

sabato 18 febbraio 2017

Prendi il makefile e scappa
come scrivere un makefile universale

Questo è un post veloce. E non è neanche propriamente un post sul C. Il consiglio è di prendere l'informazione, scappare e conservarla gelosamente per il futuro, perché potrebbe tornare molto utile. E non fatevi prendere, se no potreste fare la fine di Virgil Starkwell.

faccia da "ma ho solo rubato un makefile..."
Allora, supponiamo che dobbiamo fare un progetto (che chiameremo, per esempio, pluto) e, per vari motivi, non vogliamo (siamo della vecchia scuola) o non possiamo (non ce n'è uno adatto) usare un IDE. Organizziamo i nostri file in una maniera canonica, in tre directory: pluto, lib e include. Ovviamente scriveremo il codice in C e piazzeremo i file in maniera logica (evidentemente il file con il main() andrà nella directory pluto). I file sono tanti e  e ogni volta che ricompiliamo non vogliamo riscrivere il comando a mano e vogliamo ricompilare solo quello che serve (solo i sorgenti modificati) soddisfacendo automaticamente le dipendenze dagli header (ricompilare solo i sorgenti che dipendono da un header modificato)... Ma ci serve un makefile! Ok, tutti voi sapete già cosa è un makefile, ma... sapete scriverne uno veramente semplice e, al tempo stesso, super funzionale e, soprattutto, generico e universale? Se la risposta è NO questo è il vostro post (e se la risposta è SI allora Ciao e al prossimo post!).

Bando alle ciance: se state leggendo questa riga avete risposto NO alla domanda precedente, e quindi vai con l'esempio!
# variabili
CC = gcc
SRCS = $(wildcard *.c)
SRCS_LIB = $(wildcard ../lib/*.c)
OBJS = $(SRCS:.c=.o)
OBJS_LIB = $(SRCS_LIB:.c=.o)
DEPS = $(SRCS:.c=.d)
DEPS_LIB = $(SRCS_LIB:.c=.d)

# creazione del target file eseguibile
pluto: $(OBJS) $(OBJS_LIB)
    $(CC) $^ -o $@ -lcurl

# creazione degli object files
%.o: %.c
    $(CC) -MMD -MP -I../include -c $< -o $@ -g -Wall -std=c11 -D SIMULATION

# direttive phony
.PHONY: clean

# pulizia progetto ($(RM) è di default "rm -f")
clean:
    $(RM) $(OBJS) $(OBJS_LIB) $(DEPS) $(DEPS_LIB)

# creazione dipendenze
    -include $(DEPS) $(DEPS_LIB)
Come vedete il makefile presentato è veramente semplice. Però è anche veramente completo: fa tutto quello che serve, compresa la generazione dei file di dipendenza dagli header, e possiamo usarlo per qualsiasi progetto, indipendentemente dal numero di file (le directory lib e include potrebbero essere vuote oppure contenere centinaia di file). Possiamo aggiungere e togliere sorgenti e header e ricompilare senza modificare una sola linea del makefile, perché lui si adatta automaticamente a quello che trova nelle tre directory del progetto: cosa vogliamo di più?

Qualche piccolo dettaglio sui blocchi (commentati) che compongono il makefile:

# variabili
Qua si mettono le variabili che vengono usate nel resto del makefile. In particolare la variabile CC indica il compilatore da usare: nel nostro caso è gcc, ma potrebbe essere, per esempio, g++ (per il C++). Ovviamente in questo caso i sorgenti sarebbero dei .cpp o .cc, quindi bisogna ricordarsi di modificare anche le altre variabili che fanno riferimento ai .c.

# creazione del target file eseguibile
Qua si mette il comando per linkare i file oggetto creati e produrre il file eseguibile finale. Se usiamo qualche libreria esterna il riferimento si aggiunge qui (nell'esempio si linka la libcurl usando -lcurl).

# creazione degli object files
Qua si mette il comando per compilare ogni sorgente e creare il file oggetto corrispondente, attivando tutte le opzioni del compilatore che ci servono. Se usiamo qualche #ifdef particolare (come quelle viste la) la attivazione si mette qui (nell'esempio si attiva una define SIMULATION usata nei sorgenti).

# direttive phony
Qua si mettono tutte le direttive phony (è un po' lungo da spiegare: guardate il link, che è chiarissimo).

# pulizia progetto ($(RM) è di default "rm -f")
Qua si mette il comando di cancellazione degli oggetti per, eventualmente, forzare una successiva ricompilazione completa.

# creazione dipendenze
Qua si mette il comando per generare i file di dipendenza che ci permettono di ricompilare solo quello che serve quando modifichiamo un header file.

Il makefile presentato è un esempio reale, pronto all'uso. Ovviamente le direttive -lcurl e -D SIMULATION sono state aggiunte come esempio per indicare come estendere le funzionalità del makefile: se non ci servono possiamo toglierle senza problemi (e aggiungeremo quelle che ci servono usando lo stessa sintassi).

Che ne dite? L'obbiettivo non era di spiegare cosa è un makefile e come si scrive (uff, c'è in rete una documentazione enorme sull'argomento). E neppure era di spiegare i segreti della sintassi (che permette anche soluzioni complesse). L'obbiettivo era di fornire un makefile basico e completo allo stesso tempo, un makefile universale per (quasi) qualsiasi progetto. Io direi che l'obbiettivo è compiuto... poi, se dobbiamo fare progetti complessi e portabili, con auto-installatori, ecc. magari ci troveremo più comodi usando un IDE di buona qualità oppure usando a mano tools come Autotools o CMake... ma vi assicuro che il metodo rapido e vecchia-scuola che ho descritto è usabile sempre e senza limitazioni. Sono soddisfazioni...

Ciao e al prossimo post!

sabato 14 gennaio 2017

Per un pugno di ifdef
come usare il preprocessore in C

Dopo i bagordi di fine anno è meglio riprendere con un post leggero. Parleremo, quindi del preprocessore... be, in realtà se parlassimo del preprocessore in maniera approfondita non sarebbe un post molto leggero, quindi ci limiteremo ad un caso semplice, cioè ad un uso interessante della direttiva #ifdef. E vi consiglio di seguire i consigli di Joe (lo straniero) perché è uno che si arrabbia facilmente...
...e chi non usa la ifdef dovrà vedersela con me...
Allora: riprendiamo un vecchio pezzo di codice mostrato qui (subito a rileggerlo!), quello del Socket Server. Dando per scontato che abbiate ben chiaro come funziona, lo modificheremo seguendo un possibile caso reale, che sarebbe il seguente: supponiamo di dover compilare il nostro codice per due diversi ambienti operativi, ad esempio per un Linux Desktop/Server recente, e per un Linux Embedded un po' datato (con compilatore, Kernel e glibc di qualche annetto fa). Abbiamo deciso per motivi vari che il nuovo codice deve creare un socket non bloccante nella fase di accept, quindi, ad esempio, si potrebbe sostituire la chiamata ad accept() con una ad accept4() che dispone di un argomento flags che si può impostare a SOCK_NONBLOCK che è proprio quello che ci serve. Sfortunatamente il nostro sistema embedded (datato, come detto) usa un kernel più vecchio del 2.6.28 e una glibc più vecchia della 2.10 (che sono le due condizioni minime per poter usare la accept4()). Che fare? Facciamo due versioni del codice? NO! Perché così ci toccherebbe (in futuro) fare anche doppia manutenzione, che è una situazione da evitare. Manterremo, invece, una sola versione con delle opportune #ifdef per gestire la compilazione nei due ambienti operativi.

Allora, il pezzo di codice (estratto da quel post lì) che dobbiamo modificare è questo:
...
// accetta connessioni da un client entrante
printf("%s: attesa connessioni entranti...\n", argv[0]);
socklen_t socksize = sizeof(struct sockaddr_in);
struct sockaddr_in client;          // (remote) client socket info
int client_sock;
if ((client_sock = accept(my_socket, (struct sockaddr *)&client, &socksize)) < 0) {
    // errore accept()
    printf("%s: accept failed (%s)\n", argv[0], strerror(errno));
    return EXIT_FAILURE;
}
...
E la nuova versione con compilazione condizionale via #ifdef sarà la seguente:
...
// accetta connessioni da un client entrante (in non blocking mode: my_socket è SOCK_NONBLOCK)
printf("%s: attesa connessioni entranti...\n", argv[0]);
socklen_t socksize = sizeof(struct sockaddr_in);
struct sockaddr_in client;          // (remote) client socket info
int client_sock;
#ifdef OLD_LINUX
if ((client_sock = accept(my_socket, (struct sockaddr *)&client, &socksize)) < 0) {
#else
if ((client_sock = accept4(my_socket, (struct sockaddr *)&client, &socksize, SOCK_NONBLOCK)) < 0) {
#endif
    // errore accept()
    printf("%s: accept failed (%s)\n", argv[0], strerror(errno));
    return EXIT_FAILURE;
}
#ifdef OLD_LINUX
else {
    // accept eseguita: set socket a non-blocking
    int flags;
    if ((flags = fcntl(client_sock, F_GETFL, 0)) >= 0) {
        if (fcntl(client_sock, F_SETFL, flags | O_NONBLOCK) < 0) {
            // errore accept()
            printf("%s: fcntl failed (%s)\n", argv[0], strerror(errno));
            return EXIT_FAILURE;
        }
    }
}
#endif
...
Ok, come vedete è ampiamente commentato e quindi è auto-esplicativo, per cui non mi dilungherò sulle singole istruzioni e/o gruppi di istruzioni (leggete i commenti! sono li per quello!), ma aggiungerò, solo, qualche dettaglio strutturale.

Nelle parti incluse negli #ifdef OLD_LINUX c'è la versione di codice che NON usa la accept4(), ed è, ovviamente, un po' più complicata dell'altra, visto che ci tocca usare fcntl() per rendere non-bloccante il socket creato, mentre la accept4() lo crea direttamente passandogli, come detto, il flag SOCK_NONBLOCK. Comunque, come potete notare, il codice risultante è abbastanza chiaro e leggibile e, tutto sommato, non ha un brutto aspetto (lo stile prima di tutto!).

Qualcuno potrebbe dire: la versione sotto #ifdef funziona anche con un Linux recente, quindi perché non lasciare solo quella e togliere le #ifdef? NO! NO! e ancora NO! Non dobbiamo scrivere codice old-style per essere retro-compatibili: bisogna sempre cercare di scrivere in maniera moderna, e in caso di necessità (come nell'esempio) il vecchiume lo mettiamo in #ifdef. E quando sarà il momento (quando, per esempio, non ci servirà più un doppio ambiente operativo) faremo pulizia e lasceremo solo un bel codice moderno.

Ovviamente la compilazione condizionale la realizzeremo inserendo (o non inserendo) una istruzione -D OLD_LINUX nella linea del nostro makefile che genera i file oggetto, oppure direttamente nella linea di comando se non usiamo un makefile. Bene, finalmente abbiamo scritto un unico codice per due ambienti operativi diversi: missione compiuta!

Solo un ultima precisazione sul nostro socket server modificato (e che non centra niente con gli ifdef): se il non-blocking socket ci serve per eseguire delle recv() non bloccanti nella fase successiva a quella di accept, è molto più semplice modificare opportunamente il loop di ricezione e passare il flag MSG_DONTWAIT alla recv() nell'argomento flags (il quarto). Così la recv() non blocca in entrambi gli ambienti operativi, e tutto senza usare le #ifdef. Ma questa è un altra storia...

Ciao e al prossimo post!

giovedì 8 dicembre 2016

L'ultimo degli Apache III - Il ritorno
come scrivere un modulo Apache in C - pt.3

Allora, facciamo il punto: negli ultimi tre post abbiamo parlato di moduli lighttpd (qui, qui e ancora qui). Nel primo dei tre avevo ricordato che l'argomento non era nuovo: anticamente avevo scritto due post (qui e qui) in cui si parlava di moduli per Apache, che è un parente stretto di lighttpd (famiglia Web Servers). Ecco, per chiudere in bellezza vi propongo un terzo episodio de "L'ultimo degli Apache" (o dei Mohicani?), con una (spero) interessante ampliazione di quello che si era scritto.
...veramente sono un Mohicano, non un Apache...
Bene, ne "L'ultimo degli Apache II - La vendetta" (che, avete appena riletto, immagino...) avevamo scritto un bel modulo elementare per Apache. Faceva poco (scriveva solo una presentazione nel browser) però era un buon inizio per entrare nel mondo dei moduli per Web Servers. Ecco, adesso riprenderemo quel modulo e gli aggiungeremo una funzione che estrae i dati POST inclusi in una eventuale petizione HTTP di tipo POST che arriva al modulo. Perché ho scelto di aggiungere proprio questa funzionalità? Beh, ovviamente perchè è abbastanza normale che un modulo tratti vari tipi di petizioni (GET, POST, ecc.), e poi, in particolare, ricordo che la prima volta che aggiunsi questa funzionalità in un modulo Apache che avevo scritto, notai che non erano disponibili molte indicazioni su questo argomento (e questo nonostante la enorme mole di documentazione Apache disponibile rispetto a quella di lighttpd).

Bene, senza stare a ripetere tutto il codice e la maniera di generarlo (c'è già tutto in "L'ultimo degli Apache II - La vendetta") riscriveremo solo la funzione myapmodHandler() e aggiungeremo una nuova funzione getPost(). Vai col codice!
// handler del modulo
static int myapmodHandler(
    request_rec *reqrec)            // request data
{
    // test handler
    if (! reqrec->handler || strcmp(reqrec->handler, "myapmod"))
        return DECLINED;

    // set del appropriato content type
    ap_set_content_type(reqrec, "text/html");

    // test metodo http
    if (reqrec->method_number == M_GET) {
        // petizione GET: scrive solo "Hello, World!"
        ap_rprintf(reqrec, "GET request: Hello, world!");
    }
    else if (reqrec->method_number == M_POST) {
        // petizione POST: legge POST data
        char *data;
        if (readPost(reqrec, &data) != -1) {
            // scrive "Hello, World!" e mostra POST data
            ap_rprintf(reqrec, "POST request: Hello, world! postdata: %s", data);
        }
        else {
            // POST data non disponibile: scrive solo "Hello, World!"
            ap_rprintf(reqrec, "POST request: Hello, world!");
        }
    }
    else
        return HTTP_METHOD_NOT_ALLOWED;

    // esce con OK
    return OK;
}

// funzione per leggere POST data
static int readPost(
    request_rec *reqrec,            // request data
    char        **data)             // buffer di destinazione per POST data
{
    // setup del client per permettera che Apache legga il request body
    if (ap_setup_client_block(reqrec, REQUEST_CHUNKED_ERROR) == OK) {
        // determina se il client ha spedito dati
        if (ap_should_client_block(reqrec)) {
            // legge i dati di POST
            char argsbuffer[HUGE_STRING_LEN];
            int rsize, len_read, rpos=0;
            long length = reqrec->remaining;
            *data = (char*)apr_pcalloc(reqrec->pool, length +1);

            // loop di inserzione dati nel buffer di destinazione
            while ((len_read = ap_get_client_block(reqrec, argsbuffer, sizeof(argsbuffer))) > 0) {
                if ((rpos + len_read) > length)
                    rsize = length - rpos;
                else
                    rsize = len_read;

                // copia un blocco di dati
                memcpy((char *)*data + rpos, argsbuffer, rsize);
                rpos += rsize;
            }

            // POST data letto: return OK
            return 0;
        }
    }

    // esce con NOK
    return -1;
}
Ok, come vedete è ampiamente commentato e quindi è auto-esplicativo, per cui non mi dilungherò sulle singole istruzioni e/o gruppi di istruzioni (leggete i commenti! sono li per quello!), ma aggiungerò, solo, qualche dettaglio strutturale.

L'originale funzione myapmodHandler() è diventata una sorta di funzione helloworld (beh, in pratica lo era anche prima), che distingue i tipi di petizione e, nel caso POST, chiama la funzione readPost() e scrive come risposta "Hello, world!" più i dati. Nel caso di petizioni GET o di petizioni POST senza dati si limita a scrivere "Hello, world!", e per altri tipi di petizione esce con errore.

La funzione readPost() è semplice ma non immediata: a suo tempo la derivai dall'unico esempio interessante e ben fatto che trovai, e che stava nel (ottimo) libro "Writing Apache Modules with Perl and C", che vi raccomando. Direi che nell'ultima versione di Apache, la 2.4, sono stati introdotti (e descritti qui) altri metodi di estrazione dei dati POST (vi auguro una buona lettura!), comunque io me l'ero cavata così, e il modulo funzionava (e funziona tutt'ora) benissimo.

Ah, il test, dimenticavo! Testare con petizioni POST non è semplice come farlo con le GET, dove è sufficiente (come detto nel post precedente) scrivere la URI del modulo nella barra degli indirizzi di Firefox o Chrome e aspettare la risposta. Per testare il nostro nuovo modulo è meglio usare un bel plugin del browser (tipo Poster) per semplificare il lavoro.

Va bene, per il momento credo che possiamo fermare per un tempo l'argomento moduli Apache/lighttpd. Giuro che nel prossimo post parlerò d'altro, non vorrei che pensaste che il C si usa solo per i Web Servers...

Ciao e al prossimo post!

sabato 5 novembre 2016

Il grande lighttpd
come scrivere un modulo lighttpd in C - pt.3

    Drugo: E se poi quello se la prende?   
    Bunny: A lui non importa niente di niente, è un nichilista.
    Drugo: Ah, dev’essere faticoso da morire.

Ebbene si, siamo faticosamente arrivati al capitolo finale, oggi si scrive il codice del nostro mini-modulo lighttpd. Uff, che fatica per il Drugo...
...che fatica...
Ma prima vediamo un attimo come è strutturato un modulo. Eccovi un piccolo schema esemplificativo (estratto dal nostro mod_helloworld.c creato nel post precedente: rileggere subito il post precedente!):
// init the plugin data
INIT_FUNC(mod_helloworld_init) {
    ...
}

// destroy the plugin data
FREE_FUNC(mod_helloworld_free) {
    ...
}

// handle plugin config and check values
SETDEFAULTS_FUNC(mod_helloworld_set_defaults) {
    ...
}

// handle uri
URIHANDLER_FUNC(mod_helloworld_uri_handler) {
    ...
}

// this function is called at dlopen() time and inits the callbacks
int mod_helloworld_plugin_init(plugin *p) {
    p->version          = LIGHTTPD_VERSION_ID;
    p->name             = buffer_init_string("helloworld");
    p->init             = mod_helloworld_init;
    p->handle_uri_clean = mod_helloworld_uri_handler;
    p->set_defaults     = mod_helloworld_set_defaults;
    p->cleanup          = mod_helloworld_free;
    p->data             = NULL;
    return 0;
}
Come si nota si usano delle macro (gentilmente fornite dall'ambiente di sviluppo lighttpd) per creare delle callback con cui inizializzare una struttura plugin che poi lighttpd userà per gestire il nostro modulo ogni qualvolta arrivi una petizione HTTP che ne richieda l'uso. Per chi vuole approfondire l'argomento callback è già stato ampiamente descritto qui e qui (immagino abbiate già letto quei post... o no?).

Allora, che parti del nostro modulo dobbiamo modificare per fare in modo che, usandolo, ci scriva "Hello, world!" nel browser? Visto che il problema è abbastanza semplice anche il codice da scrivere non sarà molto voluminoso e complesso: ci basterà modificare solo la callback mod_helloworld_uri_handler nella seguente maniera:
URIHANDLER_FUNC(mod_helloworld_uri_handler)
{
    plugin_data *p = p_d;
    UNUSED(srv);

    // test modo (return se errore)
    if (con->mode != DIRECT)
        return HANDLER_GO_ON;

    // test uri path (return se errore)
    if (con->uri.path->used == 0)
        return HANDLER_GO_ON;

    mod_helloworld_patch_connection(srv, con, p);

    // test handler (return se errore)
    if (con->uri.path->ptr && strstr(con->uri.path->ptr, "helloworld")) {
        // scrive buffer
        buffer *b = chunkqueue_get_append_buffer(con->write_queue);
        BUFFER_APPEND_STRING_CONST(b, "<big>Hello, world!</big>");

        // send header
        response_header_overwrite(srv, con, CONST_STR_LEN("Content-Type"), CONST_STR_LEN("text/html"));
        con->http_status = 200;
        con->file_finished = 1;

        // handling finito
        return HANDLER_FINISHED;
    }
    else
        return HANDLER_GO_ON;
}
Ok, come vedete è ampiamente commentato e quindi è auto-esplicativo, per cui non mi dilungherò sulle singole istruzioni e/o gruppi di istruzioni (leggete i commenti! sono li per quello!), ma aggiungerò, solo, qualche dettaglio strutturale.

Alcune parti sono rimaste identiche alla callback originale (ma ho aggiunto dei commenti), e sono tipiche della struttura del codice di lighttpd: per non complicare troppo l'argomento non mi dilungherò su queste parti. Comunque fidatevi perché lighttpd è scritto e funziona come si deve.

La parte veramente nuova che ho scritto è quella che inizia col commento "test handler (return se errore)", dove si testa se la URI passata al browser contiene il nome del modulo e, in quel caso, esegue quello che vogliamo: copia in un buffer la scritta "Hello, world!" e la invia come risposta alla petizione. Quindi se noi scriviamo nella barra degli indirizzi di Firefox, Chrome, Opera (o qualsiasi altro browser meno IE, per favore...)
http://127.0.0.1/helloworld
avremo come risposta nel browser:
Hello, world!
Ho scritto 127.0.0.1 (e si poteva scrivere anche localhost) perché, ovviamente, il primo test lo faremo in locale. Se poi avete voglia di aprire la vostra macchina al mondo esterno e usarla come un vero Web Server (in una rete locale o nel WWW) potrete ripetere il test usando il vostro indirizzo IP pubblico e otterrete lo stesso risultato. Il modulo funziona!

Il codice appena mostrato l'ho scritto e provato con lighttpd 1.4.33. Se usate una versione più recente (dalla 1.4.36 in avanti) scoprirete che qualcosa è cambiato a livello di gestione dei buffer, quindi ad esempio le linee:   
if (con->uri.path->used == 0)
...
buffer *buf = chunkqueue_get_append_buffer(con->write_queue);
devono diventare:   
if (buffer_is_empty(con->uri.path))
...
buffer *buf = buffer_init();
ma questi sono dettagli. In futuro, come promesso, vi proporrò qualche modulo più sofisticato, con modifiche anche alle altre callback del modulo, ma questo per il momento può bastare, non gettiamo troppa carne al fuoco, se no ci stanchiamo come il Drugo...

Ciao e al prossimo post!

domenica 9 ottobre 2016

Il grande lighttpd
come scrivere un modulo lighttpd in C - pt.2

Drugo: Sai, questo... questo è un caso molto, molto complicato, Maude. Un sacco di input e di output. Sai, fortunatamente io rispetto un regime di droghe piuttosto rigido per mantenere la mente, diciamo, flessibile.
Allora: la buona notizia è che per scrivere un modulo lighttpd non è necessario seguire lo stesso regime del Drugo... quella cattiva è che ci vuole un certo impegno, ma sarà molto più semplice dopo aver letto questo post (e il precedente, ovviamente... non l'avete ancora letto? Ma questa è la seconda parte!).
faccia da "Ora so come si scrive un modulo lighttpd!"
Allora, dove eravamo rimasti? Abbiamo installato lighttpd sul nostro sistema e abbiamo creato un ambiente di sviluppo scaricando e decomprimendo il tar di installazione trovato sul sito ufficiale di lighttpd (ricordarsi di creare l'ambiente usando la stessa versione di quella già installata sul sistema). La compilazione e installazione della versione scaricata ha dato (spero) buon esito, quindi siamo pronti per scrivere il nostro modulo.

Per prima cosa scegliamo un nome e un azione... sarà un classico: lo chiameremo mod_helloworld e il modulo scriverà nel browser la frase "Hello, world!" (molto, ma molto, originale!).

Apriamo un terminale Linux, entriamo nella root-directory dell'ambiente di sviluppo ed eseguiamo i seguenti comandi (che ho numerato per descriverli uno a uno):
 1. sudo gedit /etc/lighttpd/lighttpd.conf
 2. gedit src/Makefile.am     
 3. cp src/mod_skeleton.c src/mod_helloworld.c 
 4. gedit src/mod_helloworld.c    
 5. ./autogen.sh
 6. ./configure
 7. make
 8. sudo make install
 9. sudo cp /usr/local/lib/mod_helloworld.so /usr/lib/lighttpd
10. sudo /etc/init.d/lighttpd restart
Allora: con 1. editiamo (con gedit, pluma, vim, geany... o quello che preferite) il file di configurazione di lighttpd per aggiungere alla lista dei moduli installati il nostro nuovo modulo: cercate la lista "server.modules" e inserite, dopo l'ultimo modulo listato, il nuovo. Fatto ciò l'aspetto della lista sarà del tipo:
server.modules = (
    "mod_access",
    "mod_alias",
    "mod_compress",
    "mod_redirect",
    "mod_helloworld",
)
Con 2. editiamo il file Makefile.am del package. Rispetto al package originale stiamo aggiungendo un modulo, quindi bisogna cercare l'ultimo modulo standard presente (normalmente è mod_accesslog). Troveremo quattro linee così:
lib_LTLIBRARIES += mod_accesslog.la
mod_accesslog_la_SOURCES = mod_accesslog.c
mod_accesslog_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
mod_accesslog_la_LIBADD = $(common_libadd) 
Copiamo queste linee e le replichiamo immediatamente sotto, sostituendo nelle NUOVE quattro linee mod_accesslog con mod_helloworld. Salviamo e usciamo: ora il nostro Makefile.am è pronto per gestire anche il nuovo modulo. Le linee aggiunte saranno queste:
lib_LTLIBRARIES += mod_helloworld.la
mod_helloworld_la_SOURCES = mod_helloworld.c
mod_helloworld_la_LDFLAGS = -module -export-dynamic -avoid-version -no-undefined
mod_helloworld_la_LIBADD = $(common_libadd)
Con 3. copiamo il file mod_skeleton.c in un nuovo file che chiameremo mod_helloworld.c: mod_skeleton.c è un template file contenente lo scheletro base di ogni modulo: questo file ce lo mettono gentilmente a disposizione gli ottimi sviluppatori di lighttpd per facilitarci la scrittura di nuovi moduli.

Con 4. editiamo mod_helloworld.c e, con un comando di sostituzione globale, cambiamo tutte le ricorrenze della parola skeleton in helloworld. Per il momento salviamo così il file e usciamo dall'editor.

Con la sequenza 5.6.7.8. (vista già nel post precedente, ricordate?) compiliamo e installiamo il nuovo modulo. È possibile che nella fase 6. (quella di configurazione) il processo vi dica che non trova alcune librerie: normalmente l'errore si risolve usando:
sudo apt-get install libpcre3-dev libbz2-dev
e ripetendo, poi, il punto 6.

Con 9. copiamo il nuovo modulo generato (che ha la forma di una libreria dinamica, una .so) nella directory di sistema che contiene i moduli lighttpd.

Con 10., finalmente, riavviamo lighttpd e, se non ci appare nessun errore il nostro Web Server è già pronto per usare il nuovo modulo (che però, per il momento, è semivuoto: come detto sopra è solo lo scheletro di un modulo).

Cosa ci manca a questo punto? Beh, ovviamente, dobbiamo aggiungere un po' di codice nel nostro modulo per fargli fare qualcosa (nel nostro caso mostrarci un bel "Hello, world!"). Ma questo lo vedremo nella prossima puntata, e, come sempre, non trattenete il respiro nell'attesa...

Ciao e al prossimo post!