Dr.Blair: Non so di chi fidarmi.
R.J.MacReady: Ti capisco benissimo, Blair. La fiducia è una cosa molto rara oggigiorno. Sai che cosa ti dico? Affidati solo al Signore.
(...una premessa: questo post (parte 1 di 3) è un remake di un mio vecchio post. Ma, anche se tratta lo stesso argomento, amplia e perfeziona un po' il discorso è mi è sembrato il caso di riproporlo. Leggete e mi direte...)
Ok, questo post non centra niente con The Thing, il Capolavoro Immortale del Maestro 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... |
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <fcntl.h>#include <errno.h>// prototipi localiint cpFile(const char* src, const char* dest);// funzione main()int main(int argc, char *argv[]){// test argumentiif (argc != 3) {// errore: numero errato di argomentifprintf(stderr, "%s: numero errato di argomenti\n", argv[0]);fprintf(stderr, "uso: %s src dest [e.g.: %s prova.c prova.bak]\n", argv[0], argv[0]);return EXIT_FAILURE;}// eseguo copyoff_t n_copied;if ((n_copied = cpFile(argv[2], argv[1])) == -1) {// mostro errore ed escofprintf(stderr, "%s: error: %s\n", argv[0], strerror(errno));exit(EXIT_FAILURE);}// escoprintf("%s: copiati %ld byte\n", argv[0], n_copied);return EXIT_SUCCESS;}// cpFile() - copia il file <src> nel file <dest>int cpFile(const char *dest, const char *src){// apro il file sorgenteint fd_in;if ((fd_in = open(src, O_RDONLY)) == -1) {// return con errorereturn -1;}// apro il file destinazioneint fd_out;if ((fd_out = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 00644)) == -1) {// salvo errno della open(2) precedenteint save_errno = errno;// chiudo il fileclose(fd_in);// set errno (eventualmente modificato dalla close(2)) e ritorno erroreerrno = save_errno;return -1;}// r/w loop per la copia usando unbuffered I/Ooff_t n_copied = 0;ssize_t n_read;char buffer[BUFSIZ];while ((n_read = read(fd_in, buffer, sizeof(buffer))) > 0) {// write bufferif (write(fd_out, buffer, n_read) == -1) {// salvo errno della write() precedenteint save_errno = errno;// chiudo i fileclose(fd_in);close(fd_out);// set errno (eventualmente modificato dalle close(2)) e ritorno erroreerrno = save_errno;return -1;}// incremento il contatore di bytes copiatin_copied += n_read;}// chiudo i fileclose(fd_in);close(fd_out);// ritorno il numero di bytes copiatireturn n_copied;}
Ok, come vedete è, volutamente, super-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 il comando UNIX/Linux cp(1), che è proprio quello che vogliamo emulare usando la nostra nuova funzione di libreria.
La funzione che esegue il lavoro l'ho chiamata cpFile() ed è abbastanza semplice, come avrete notato. 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 che sarà il numero dei byte copiati o -1 in caso di errore. Il risultato può essere trattato dal chiamante (in questo caso il main()) per visualizzare eventuali errori usando strerror(3) ed errno(3). 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.
Posso aggiungere che la nostra funziona cpFile() usa, evidentemente, una interfaccia GNU-specific (come già detto sopra: ritorna -1 in caso di errore), e il valore di errno viene opportunamente salvato per permettere al chiamante di verificare il problema reale che ha creato l'errore nella funzione.
Volendo si potrebbe modificare l'interfaccia per usarne una di tipo XSI-compliant (ossia: POSIX): in questo caso si dovrebbe ritornare un codice di successo (che normalmente è 0) o di errore (un numero positivo: l'errno) per permettere al chiamante le stesse attività appena viste sopra. Faccio notare, infine, che anche se l'interfaccia XSI-compliant canonica dovrebbe ritornare l'errno gestito dalla libc, è possibile, per funzioni particolari, usare degli errori user-defined che potremo, ad esempio, definire in un header apposito del progetto, ma in quel caso non potremo, ovviamente, usare la strerror(3) del sistema, ma dovremo scriverne una ad-hoc per i nostri nuovi errori.
Avevo detto che avrei proposto due versioni: fermo restando il main() (che va bene per entrambi i casi) la versione alternativa è questa:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <fcntl.h>#include <errno.h>#include <sys/stat.h>#include <sys/sendfile.h>// prototipi localiint cpFile(const char* src, const char* dest);// funzione main()int main(int argc, char *argv[]){// test argumentiif (argc != 3) {// errore: numero errato di argomentifprintf(stderr, "%s: numero errato di argomenti\n", argv[0]);fprintf(stderr, "uso: %s src dest [e.g.: %s prova.c prova.bak]\n", argv[0], argv[0]);return EXIT_FAILURE;}// eseguo copyoff_t n_copied;if ((n_copied = cpFile(argv[2], argv[1])) == -1) {// mostro errore ed escofprintf(stderr, "%s: error: %s\n", argv[0], strerror(errno));exit(EXIT_FAILURE);}// escoprintf("%s: copiati %ld byte\n", argv[0], n_copied);return EXIT_SUCCESS;}// cpFile() - copia il file <src> nel file <dest>int cpFile(const char *dest, const char *src){// apro il file sorgenteint fd_in;if ((fd_in = open(src, O_RDONLY)) == -1) {// return con errorereturn -1;}// apro il file destinazioneint fd_out;if ((fd_out = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 00644)) == -1) {// salvo errno della open(2) precedenteint save_errno = errno;// chiudo il fileclose(fd_in);// set errno (eventualmente modificato dalla close(2)) e ritorno erroreerrno = save_errno;return -1;}// ottengo il size del file per la funzione sendfile(2)off_t n_copied = 0;struct stat fileinfo = {0};if (fstat(fd_in, &fileinfo) == -1) {// salvo errno della fstat(2) precedenteint save_errno = errno;// chiudo i fileclose(fd_in);close(fd_out);// set errno (eventualmente modificato dalle close(2)) e ritorno erroreerrno = save_errno;return -1;}// copia in kernel-space usando la funzione sendfile(2)if (sendfile(fd_out, fd_in, &n_copied, fileinfo.st_size) == -1) {// salvo errno della sendfile(2) precedenteint save_errno = errno;// chiudo i fileclose(fd_in);close(fd_out);// set errno (eventualmente modificato dalle close(2)) e ritorno erroreerrno = save_errno;return -1;}// chiudo i fileclose(fd_in);close(fd_out);// ritorno il numero di bytes copiatireturn n_copied;}
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 funziona solo dalla versione 10.5 in avanti). E già che ci siamo specifichiamo meglio: anche la prima versione (quella con read(2) e write(2)) 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, per oggi può bastare. Potete già intuire che l'argomento del prossimo articolo 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!

Nessun commento:
Posta un commento