capolavoro immortale |
#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!
Nessun commento:
Posta un commento