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.

giovedì 5 novembre 2026

The D-Bus
come usare D-Bus in C (e Go) - pt.1

regista (H.Ellis): Tu.
Hrundi V.Bakshi (P.Sellers): Io?
regista: Sì, tu. Scendi dal mio set ed esci dalla mia foto. Spegni, spegni! Sei finito, hai finito! Farò in modo che tu non faccia mai più un altro film!
Hrundi V.Bakshi: Include la televisione, signore?

Nel bellissimo film The Party (Hollywood Party in Italia) il gran Peter Sellers si intrufola (da invitato per sbaglio) in un party del jet set  hollywoodiano, e scombussola un po' l'ambiente... Ecco, il tema di questo articolo si riferisce a un sistema di IPC (inter-process communication) che si è intrufolato un po' tardi (nel 2003) nel mondo IPC, quando già ne esistevano mille tipi ben consolidati, eppure si è fatto strada e oggi è usatissimo. Preparatevi, in questo articolo (e nei prossimi) parleremo di D-Bus!

...ma da dove è arrivato 'sto D-Bus?...

Visto che l'argomento è vasto questo sarà il primo articolo di una serie, e per introdurre l'argomento mi sembra il caso di iniziare con:

Un po' di storia...

D-Bus (Desktop Bus) è un sistema di comunicazione tra processi (IPC) utilizzato principalmente in ambienti Linux e Unix-like (e quindi POSIX). È stato sviluppato da Red Hat e dalla community freedesktop.org per offrire alle applicazioni un modo semplice e standardizzato per scambiarsi messaggi e servizi senza bisogno di supportarsi direttamente tra loro. E, come dice il nome stesso, è un sistema nato per facilitare la comunicazione tra le applicazioni dello stesso desktop (ambiente grafico) e tra l'ambiente desktop e il sistema operativo. Interessante, no?

D-Bus si basa su un daemon (un demone Unix, un classico) che gestisce il traffico dei messaggi e permette:

  • La comunicazione tra applicazioni dello stesso ambiente desktop (Session Bus).
  • La comunicazione tra il sistema operativo e l'ambiente desktop o tra processi di sistema (System Bus).

L'architettura di D-Bus è strutturata su tre livelli: la libreria di base libdbus, il daemon di gestione dei messaggi e le librerie wrapper specifiche per diversi ambienti applicativi, facilitando l'integrazione e la gestione del ciclo di vita dei processi in modo affidabile. E aggiungo un ultimo dettaglio: anche se D-Bus è basato nativamente, come visto sopra, su un daemon, è comunque anche un IPC classico, e quindi supporta la connessione diretta (peer-to-peer) che non usa il daemon: ma questa opzione si usa solo per casi particolari.

Ah, prima che mi dimentico: D-Bus è anche multi-piattaforma, quindi è disponibile (ma poco usato) anche in quel sistema operativo che comincia con W e che, di solito, preferisco non nominare nemmeno (ah ah ah).

E come si scrive il codice C per usare D-Bus?

Ecco, or incomincian le dolenti note: la libreria base storica, quella nata insieme al protocollo è la libdbus: è una libreria low-level completissima, ben fatta, super-testata e ben funzionante ma... praticamente inutilizzabile (ah ah ah) perché criptica e complicatissima da usare così com'è. Infatti, nella documentazione ufficiale di freedesktop.org se ne sconsiglia l'uso!

È opportuno sottolineare che l´implementazione di basso livello non è pensata principalmente
per essere utilizzata dagli autori delle applicazioni. Si tratta piuttosto di una base per gli
autori di binding e di un riferimento per le reimplementazioni. Se possibile, si raccomanda di
utilizzare uno dei binding o delle implementazioni di livello superiore. Un elenco di questi è
disponibile nella pagina dedicata ai binding.” [cit. freedesktop.org - What is D-bus?]

e lo stesso consiglio si trova in altre fonti, tra qui questa:

“NOTA: si sconsiglia di utilizzare questa API a meno che non sia assolutamente necessario.
Gli sviluppatori di applicazioni dovrebbero utilizzare uno dei binding, se possibile. Se si
scrive in in C, si consigliano i binding GLib.” [cit. Matthew Johnson - Using the DBUS C API]

E allora che si fa?

Rimanendo sul C si potrebbe/dovrebbe usare uno dei wrapper disponibili, ad esempio GDBus, ma questo è molto legato all'ambiente grafico GNOME/GTK, quindi volendo scrivere del Software un po' più generico (è il nostro caso) non è adatto. Oppure si potrebbero usare i wrapper  in C++ (ehm...): ad esempio QtDBus, ma per questo valgono le stesse considerazioni fatte per GDBus (e in questo caso si dipenderebbe da Qt). Oppure usare dbus-c++, che è, effettivamente, un buon wrapper  diretto di libdbus: facilita molto la scrittura del codice, ma è un po' obsoleto e credo che non sia più un progetto attivo. E poi, la domanda iniziale era: "E come si scrive il codice C per usare D-Bus?" Quindi dimentichiamoci del C++ (bene! Anzi, benissimo!).

Ripetiamo: "E allora che si fa?" La risposta sarà nel prossimo articolo! Lì vedremo l'evoluzione Software che ha avuto il nostro D-Bus e le nuove librerie disponibili (in C!).

Comunque non posso terminare un articolo senza codice, no? Quindi ho scritto due applicazioni in C (un Server e un Client) funzionanti (parlano tra loro) e che usano libdbus. Vai col codice! Prima il Server:

// server.c - un server D-Bus con libdbus (usa dbus-daemon)
#include <stdio.h>
#include <stdlib.h>
#include <dbus/dbus.h>

// nomi condivisai tra server e client
#define SRV_BUS_NAME "com.esempio.server" // nome del bus del server
#define SRV_OBJ_PATH "/com/esempio/object" // percorso dell'oggetto
#define IFACE_NAME "com.esempio.interface" // nome dell'interfaccia
#define METHOD_NAME "SendAndRecv" // nome del metodo

// funzione main()
int main()
{
// init gestione errori
DBusError err;
dbus_error_init(&err);

// mi connetto al bus di sessione
DBusConnection *conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err)) {
// errore di connessione: esco
fprintf(stderr, "errore di connessione: %s\n", err.message);
dbus_error_free(&err);
return EXIT_FAILURE;
}

// richiedo un nome noto sul bus
int ret = dbus_bus_request_name(conn, SRV_BUS_NAME,
DBUS_NAME_FLAG_REPLACE_EXISTING, &err);
if (dbus_error_is_set(&err)) {
// errore sul nome: esco
fprintf(stderr, "errore nel nome del bus: %s\n", err.message);
dbus_error_free(&err);
return EXIT_FAILURE;
}

// check dei privilegi
if (ret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) {
// errore di privilegi: esco
fprintf(stderr, "non sei il proprietario primario del nome del bus\n");
return EXIT_FAILURE;
}

// loop di comunicazione col client
for (;;) {
// Leggo i dati dal socket e li inserisco nella coda dei messaggi
dbus_connection_read_write_dispatch(conn, -1); // -1 per blocking mode
DBusMessage *msg = dbus_connection_pop_message(conn);
if (msg == NULL) {
// continuo fino a quando non c'è un messaggio
continue;
}

// controllo se il messaggio è una chiamata al metodo corretto
if (dbus_message_is_method_call(msg, IFACE_NAME, METHOD_NAME)) {
// estraggo gli argomenti (di tipo stringa)
char *param = NULL;
if (dbus_message_get_args(msg, &err, DBUS_TYPE_STRING,
&param, DBUS_TYPE_INVALID)) {
// tutto Ok: mostro il messaggio
printf("messaggio ricevuto dal client: %s", param);

// creo il messaggio di risposta (method return)
char *reply_str = malloc(256);
snprintf(reply_str, 256, "mi hai scritto: %s", param);
DBusMessage *reply = dbus_message_new_method_return(msg);

// accodo l'argomento alla risposta
DBusMessageIter args;
dbus_message_iter_init_append(reply, &args);
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &reply_str)) {
// errore di accodamento
fprintf(stderr, "errore di memoria nell'accodare la risposta\n");
}

// invio la risposta e faccio il flush della coda
if (!dbus_connection_send(conn, reply, NULL)) {
// errore di invio
fprintf(stderr, "errore nell'invio della risposta\n");
}

dbus_connection_flush(conn);

// libero la memoria del messaggio di risposta
dbus_message_unref(reply);
free(reply_str);
}
else {
// errore sui parametri
fprintf(stderr, "errore nella lettura dei parametri: %s\n", err.message);
dbus_error_free(&err);
}
}

// Libero il messaggio elaborato
dbus_message_unref(msg);
}

// esco con Ok
return EXIT_SUCCESS;
}

E poi il Client:

// client.c - un client D-Bus con libdbus (usa dbus-daemon)
#include <stdio.h>
#include <stdlib.h>
#include <dbus/dbus.h>

// nomi condivisai tra server e client
#define SRV_BUS_NAME "com.esempio.server" // nome del bus del server
#define SRV_OBJ_PATH "/com/esempio/object" // percorso dell'oggetto
#define IFACE_NAME "com.esempio.interface" // nome dell'interfaccia
#define METHOD_NAME "SendAndRecv" // nome del metodo

// funzione main()
int main()
{
// init gestione errori
DBusError err;
dbus_error_init(&err);

// mi connetto al bus di sessione
DBusConnection *conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
if (dbus_error_is_set(&err)) {
// errore di connessione: esco
fprintf(stderr, "errore di connessione: %s\n", err.message);
dbus_error_free(&err);
return EXIT_FAILURE;
}

// loop di comunicazione col server
char *input_str = malloc(128);
for (;;) {
// compongo messaggio per il server
printf("scrivi un messaggio per il server: ");
fgets(input_str, 128, stdin);

// creo la chiamata al metodo (destinatario, percorso oggetto, interfaccia, metodo)
DBusMessage *msg;
if ((msg = dbus_message_new_method_call(
SRV_BUS_NAME, SRV_OBJ_PATH, IFACE_NAME, METHOD_NAME)) == NULL) {
// errore di creazione
fprintf(stderr, "errore nella creazione del messaggio\n");
return EXIT_FAILURE;
}

// aggiungo la stringa di input come argomento
DBusMessageIter args;
dbus_message_iter_init_append(msg, &args);
if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &input_str)) {
// errore: esco
fprintf(stderr, "errore nell'inserimento dell'argomento\n");
return EXIT_FAILURE;
}

// invio il messaggio e attendo la risposta (asincrona e bloccante)
DBusPendingCall *pending;
if (!dbus_connection_send_with_reply(conn, msg, &pending, -1)) {
// errore di invio: esco
fprintf(stderr, "errore nell'invio del messaggio\n");
return EXIT_FAILURE;
}

// check dela risposta pendente
if (pending == NULL) {
// errore: esco
fprintf(stderr, "la connessione è chiusa o inattiva\n");
return EXIT_FAILURE;
}

// flush della coda per forzare l'invio immediato sul socket
dbus_connection_flush(conn);

// libero il messaggio di richiesta originale
dbus_message_unref(msg);

// blocco il thread finché la risposta non è pronta
dbus_pending_call_block(pending);

// estraggo il messaggio di risposta
DBusMessage *reply = dbus_pending_call_steal_reply(pending);
if (reply == NULL) {
fprintf(stderr, "risposta nulla ricevuta\n");
return EXIT_FAILURE;
}

// libero la memoria della risposta pendente
dbus_pending_call_unref(pending);

// leggo il testo restituito dal server
char *reply_str;
if (dbus_message_get_args(reply, &err, DBUS_TYPE_STRING,
&reply_str, DBUS_TYPE_INVALID)) {
// tutto Ok: mostro la risposta
printf("risposta del server: %s", reply_str);
} else {
// errore di lettura
fprintf(stderr, "errore nella lettura della risposta: %s\n", err.message);
dbus_error_free(&err);
}

// libero la memoria del messaggio di risposta
dbus_message_unref(reply);
}

free(input_str);

// esco con Ok
return EXIT_SUCCESS;
}

Che ve ne pare? Come al solito è tutto stra-commentato, quindi non c'è bisogno di aggiungere molti dettagli: la struttura del codice è, più o meno, simile a quella dei classici Socket Server e Client  come quelli che ho già mostrato qui e qui... ma il problema è che il risultato finale è molto più complicato! È codice abbastanza incomprensibile, ed è l'esempio più semplice che mi è venuto in mente! Direi che le citazioni che ho fatto sopra sono veritiere!

Ah, se non vi fidate e volete volete vedere se funziona veramente, vi passo le linee di installazione della libreria (su Linux) e delle compilazioni di Server  e Client:

sudo apt install libdbus-1-dev
gcc -Wall -I/usr/include/dbus-1.0 -I/usr/lib/x86_64-linux-gnu/dbus-1.0/include -o server server.c -ldbus-1
gcc -Wall -I/usr/include/dbus-1.0 -I/usr/lib/x86_64-linux-gnu/dbus-1.0/include -o client client.c -ldbus-1

dopodiché, se eseguite i due programmi in terminali separati vedrete la magia dello scambio di messaggi (interattivo) tra Client e Server. Provare per credere!

Ok, per oggi può bastare. Nel prossimo articolo vi parlerò, come anticipato sopra, del D-Bus con le librerie C! Restate sintonizzati!

Ciao, e al prossimo post!

P.S.

Ricordatevi di visitare il mio nuovissimo cineblog! Lo trovate qui: Polvere di Cinema. Buona lettura!

venerdì 9 ottobre 2026

The FileCopy
come scrivere una funzione di File Copy in C (e Go) - pt.3

R.J.MacReady: Qualcuno in questo campo non è quello che sembra. Al momento potrebbe trattarsi di uno o due di noi. Entro la primavera, potremmo essere tutti noi.
Childs: Allora, come facciamo a sapere chi è umano? Se fossi un’imitazione, un’imitazione perfetta, come faresti a capire se fossi davvero io?

(...una premessa: questo post (parte 3 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...)

Dove eravamo rimasti? Ah, si! Nell'ultimo articolo (l'avete letto vero?) avevo proposto la versione buffered della funzione cpFile(), dopo aver proposto (nel penultimo articolo) la versione unbuffered e quella "special" che funziona in kernel-space. E oggi, come promesso (e le promesse si mantengono, sempre) vi racconterò di come si può fare in Go la stessa cosa. Ma è doveroso, anche per questa terza puntata, fornire un altra immagine tratta da The Thing del Maestro John Carpenter, per ricordare che questo, come ben sapete, è un blog per programmatori cinefili...

...le copie dei file vengono meglio col fresco...

Ok, veniamo al dunque: ho scritto due versioni in Go della cpFile(), la prima si può accomunare ai tipi buffered/unbuffered già visti, mentre la seconda si può paragonare al tipo "special". Cominciamo con la versione "normal", che ho chiamato cpbase.go. Vai col codice!

// cpbase.go - un cp(1) con os.ReadFile() e os.WriteFile()
package main

import (
"os"
"fmt"
)

// funzione main
func main() {

// test argomenti
argc := len(os.Args)
if (argc != 3) {
// errore: numero errato di argomenti: esco
fmt.Printf("%s: numero errato di argomenti\n", os.Args[0])
fmt.Printf("uso: %s src dest [e.g.: %s prova.c prova.bak]\n", os.Args[0], os.Args[0])
os.Exit(1)
}

// eseguo la copia
n_copied, err := cpFile(os.Args[2], os.Args[1])
if err != nil {
// errore: copia fallita: esco
fmt.Printf("%s: error: %s\n", os.Args[0], err);
os.Exit(1)
}

// esco con Ok
fmt.Printf("%s: copiati %d byte\n", os.Args[0], n_copied);
os.Exit(0)
}

// cpFile() - copia il file <src> nel file <dest>
func cpFile(dest string, src string) (n_copied int, err error) {

// leggo tutto il contenuto dal file sorgente e lo metto in <data []byte>
// NOTA: non usare per file molto grandi: potrebbe non bastare la memoria
data, err := os.ReadFile(src)
if err != nil {
// errore: ritorno 0 e l'errore
return
}

// scrivo il contenuto sul file destinazione (specificando i permessi)
err = os.WriteFile(dest, data, 0644)
if err != nil {
// errore: ritorno 0 e l'errore
return
}

// set byte copiati
n_copied = len(data)

// ritorno il numero di byte copiati ed errore nullo
return
}

Come avrete sicuramente notato (e letto nei dettagliatissimi commenti) il main() è praticamente identico a quello dei programmi C visti precedentemente, mentre la funzione cpFile(), pur comportandosi esattamente nello stesso modo di quella in C, è alquanto diversa e, direi, molto semplificata: leggo il sorgente (prima operazione), scrivo sulla destinazione (seconda operazione) e ricavo il numero di byte copiati (terza operazione). Praticamente sono tre linee più i tradizionali test degli errori tipici del Go, tutto qua! Il Go conferma ancora una volta di essere un vero linguaggio di alto livello.

Ma c'è un "ma". Avrete sicuramente visto la nota:

// NOTA: non usare per file molto grandi: potrebbe non bastare la memoria

Ecco, questa versione è adatta solo per file relativamente piccoli, perché il file, prima di essere copiato nella destinazione, viene caricato completamento nello slice di byte data (di tipo []byte) che è allocato nello heap: e l'heap ha una dimensione limitata, ragionevolmente proporzionale alla memoria fisica disponibile, quindi se il file è molto grande (chessoio, 100GB) avremo un problema di memoria. Quindi questa funzione è imperfetta: non funziona con qualsiasi file e quando non funziona genera un grave errore di memoria. Se proprio volete usarla, come minimo dovrete aggiungere un controllo preliminare sulla dimensione del file sorgente per ritornare, eventualmente, un errore del tipo "errore: il file è troppo grande".

E allora passiamo alla versione "special" quella che risolve il problema appena mostrato: vai col codice!

// cpspecial.go - un cp(1) con io.Copy()
package main

import (
"io"
"os"
"fmt"
)

// funzione main
func main() {

// test argomenti
argc := len(os.Args)
if (argc != 3) {
// errore: numero errato di argomenti: esco
fmt.Printf("%s: numero errato di argomenti\n", os.Args[0])
fmt.Printf("uso: %s src dest [e.g.: %s prova.c prova.bak]\n", os.Args[0], os.Args[0])
os.Exit(1)
}

// eseguo la copia
n_copied, err := cpFile(os.Args[2], os.Args[1])
if err != nil {
// errore: copia fallita: esco
fmt.Printf("%s: error: %s\n", os.Args[0], err);
os.Exit(1)
}

// esco con Ok
fmt.Printf("%s: copiati %d byte\n", os.Args[0], n_copied);
os.Exit(0)
}

// cpFile() - copia il file <src> nel file <dest>
func cpFile(dest string, src string) (n_copied int64, err error) {

// apro il file sorgente
fsrc, err := os.Open(src)
if err != nil {
// errore: ritorno 0 e l'errore
return
}

defer fsrc.Close() // chiusura differita

// apro il file destinazione
fdest, err := os.Create(dest)
if err != nil {
// errore: ritorno 0 e l'errore
return
}

defer fdest.Close() // chiusura differita

// copio da sorgente a destinazione usando io.Copy()
// NOTA: è il metodo più efficiente perché usa uno stream invece della memoria
n_copied, err = io.Copy(fdest, fsrc)
if err != nil {
// errore: ritorno 0 e l'errore
return
}

// ritorno il numero di byte copiati ed errore nullo
return
}

Interessante, no? Il main() è, ovviamente, invariato, mentre la funzione cpFile() è radicalmente diversa, pur mantenendo la grande compattezza di quella appena vista. Anche in questo caso sono tre operazioni: apro il file sorgente, apro (o creo, se necessario) il file destinazione e copio usando la funzione io.Copy() che genera uno stream di dati da un file all'altro e si ferma quando raggiunge l'EOF: Una cpFile()  pulita, compatta, super efficiente... e senza limitazioni sulla dimensione dei file! Mi piace.

E anche questa terza (e ultima) parte dell'articolo si può considerare conclusa. Il Go si dimostra, ancora una volta, una perfetta controparte ad alto livello del C (e non solo). Ma ricordate: tutti e due hanno ampie ragioni di essere: il C è, da sempre, il Re de linguaggi a basso livello multi-purpose (per kernel, firmware, librerie e funzioni di sistema, ecc.), mentre il Go è un fantastico linguaggio ad alto livello più adatto ad altri usi (microservizi, API Web, infrastrutture Cloud, ecc.).

E nel prossimo articolo... boh, ci devo ancora pensare. Restate sintonizzati!

Ciao, e al prossimo post!

P.S.

Dopo anni di tentennamenti ho, finalmente, deciso di aprire un cineblog! Se vi interessa lo trovate qui: Polvere di Cinema. Buona lettura!

lunedì 14 settembre 2026

The FileCopy
come scrivere una funzione di File Copy in C (e Go) - pt.2

Childs: Beh... che facciamo?
R.J.MacReady: Perché... non aspettiamo qui ancora un po'... e vediamo che succede?

(...una premessa: questo post (parte 2 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, riprendiamo da dove ci eravamo lasciati nell'ultimo articolo (l'avete letto vero?) e, come promesso, questa volta l'argomento sarà una versione con buffered I/O  della funzione che esegue la copia di un file (una FileCopy, per gli amici). Ma è doveroso, prima, fornire un altra immagine tratta da The Thing del Maestro John Carpenter , se no potrebbe sembrare che questo sia solo un blog di programmazione, mentre, come ben sapete, è un blog per programmatori cinefili...

...con un cappello così si programma meglio...

Allora, ripetiamo: in questa nuova versione useremo I/O bufferizzato (e quindi, per esempio, fread(3) invece di read(2)) perché l'obbiettivo questa volta è la portabilità, e cosa c'è di più portabile (nel C) che usare il contenuto di stdio.h? Il codice che vedremo tra un attimo usa lo stesso main() della versione dello scorso articolo e gli unici cambi sono interni alla funzione cpFile(). In realtà, anche la cpFile() è quasi identica da un punto di vista logico, visto che le funzioni buffered hanno una sintassi d'uso e un funzionamento molto simile alle equivalenti versioni unbuffered. Ecco, posso aggiungere che se la versione buffered viene molto diversa (esteticamente e logicamente) dalla unbuffered c'è qualcosa che non va... ma su questo punto farò una breve digressione alla fine del post. Per il momento: vai col codice!

// cpbuffered.c - un cp(1) con buffered I/O
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

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

// funzione main()
int main(int argc, char *argv[])
{
// test argomenti
if (argc != 3) {
// errore: numero errato di argomenti: esco
fprintf(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 la copia
off_t n_copied;
if ((n_copied = cpFile(argv[2], argv[1])) == -1) {
// errore: copia fallita: esco
fprintf(stderr, "%s: error: %s\n", argv[0], strerror(errno));
return EXIT_FAILURE;
}

// esco con Ok
printf("%s: copiati %ld byte\n", argv[0], n_copied);
return EXIT_SUCCESS;
}

// cpFile() - copia il file <src> nel file <dest>
off_t cpFile(const char *dest, const char *src)
{
// apro il file sorgente
FILE *fp_in;
if ((fp_in = fopen(src, "rb")) == NULL) {
// ritorno errore
return -1;
}

// apro il file destinazione
FILE *fp_out;
if ((fp_out = fopen(dest, "wb")) == NULL) {
// errore: salvo errno della fopen(3) precedente e chiudo il file
int save_errno = errno;
fclose(fp_in);

// set errno salvato (evitando quello della fclose(3)) e ritorno errore
errno = save_errno;
return -1;
}

// r/w loop per la copia usando buffered I/O
off_t n_copied = 0;
size_t n_read;
char buffer[BUFSIZ];
while ((n_read = fread(buffer, 1, sizeof(buffer), fp_in)) > 0) {
// write buffer (con reset prudenziale di errno)
errno = 0;
if (fwrite(buffer, 1, n_read, fp_out) < n_read) {
// errore: salvo errno della fwrite(3) precedente e chiudo i file
// NOTA: se errno==0 con scrittura parziale: il disco è pieno (ENOSPC)
int save_errno = (errno != 0) ? errno : ENOSPC;
fclose(fp_in);
fclose(fp_out);

// set errno salvato (evitando quello della fclose(3)) e ritorno errore
errno = save_errno;
return -1;
}

// incremento il contatore di byte copiati
n_copied += n_read;
}

// r/w loop concluso: check finale errori di lettura con ferror(3)
if (ferror(fp_in)) {
// errore di lettura prima del EOF: salvo errno e chiudo i file
int save_errno = errno;
fclose(fp_in);
fclose(fp_out);

// set errno salvato (evitando quello della fclose(3)) e ritorno errore
errno = save_errno;
return -1;
}

// raggiunto EOF con successo: check fclose(3) in scrittura per i buffer pendenti
if (fclose(fp_out) != 0) {
// errore di chiusura fp_out: salvo errno e chiudo anche fp_in
int save_errno = errno;
fclose(fp_in);

// set errno salvato (evitando quello della fclose(3)) e ritorno errore
errno = save_errno;
return -1;
}

// chiudo fp_in e ritorno i byte copiati
fclose(fp_in);
return n_copied;
}

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(), come anticipato, è identico a quello visto precedentemente, mentre nella cpFile() si ripetono, esattamente, le spesse operazioni della versione unbuffered, usando però fopen(3) invece di open(2), fclose(3) invece di close(2), ecc. E dove troviamo qualche differenza significativa? Solo nella parte finale, quella dopo il loop while di lettura/scrittura: in questa versione vengono fatti alcuni test sui descrittori di file fp_in e fp_out per verificare che tutto il procedimento sia andato a buon fine: nella versione unbuffered questi test erano impliciti nelle operazioni di read/write (testando se il risultato era uguale a -1), mentre che in questo caso bisogna usare una funzione a parte, ferror(3), e questo perché:

On success, fread() and fwrite() return the number of items read or
written. This number equals the number of bytes transferred only when
size is 1. If an error occurs, or the end of the file is reached, the
return value is a short item count (or zero).
...
fread() does not distinguish between end-of-file and error, and callers
must use feof(3) and ferror(3) to determine which occurred.

Quello sopra è quello che riporta la man-page di fread(3) e fwrite(3), e credo che sia una giustificazione sufficiente sul perché ho scritto il codice così. Quindi, come anticipato, le versioni buffered e unbuffered devono essere quasi sovrapponibili (a parte i test finali), e mi sembra che sia esattamente il risultato raggiunto.

Ed ora la digressione promessa: mi è capitato di trovare in rete (anche in pregevoli blog/siti di programmazione) esempi di implementazione di lettura di file con buffered I/O che usano dei loop di questo tipo:

// loop di lettura/scrittura
while (!feof(fp_in)) {
// legge e scrive un buffer
...
}

ecco, come si può commentare questo? Con una sola parola:

NO

Se uno scrive il loop in questa maniera vuol dire che non ha letto la man-page di fread(3) e fwrite(3) o l'ha letta e non ne ha capito il contenuto. Non c'è bisogno di reinventare la ruota, ripeto: fread(3) e fwrite(3) funzionano quasi nello stesso modo di read(2) e write(2), quindi, se l'esempio della cpFile() unbuffered del post precedente era buono (e lo era!), allora l'esempio del post attuale deve risultare (quasi) uguale. Il loop con un while() che testa feof(3) è corretto sintatticamente ma non lo è logicamente, perché inizia testando qualcosa che non è ancora disponibile (uhm, un test predittivo?) e che, oltretutto, non serve testare. Bah, non voglio dilungarmi ulteriormente e vi rimando alla ottima analisi contenuta qui (nel sempre ottimo stackoverflow.com).

Ovviamente spero di non aver offeso nessuno (con la digressione precedente): ricordate, errare humanum est... e, sicuramente, anche in questo blog avrò scritto in passato qualche scemata (spero non grave come quella appena mostrata). Vi assicuro, però, che sto sempre attentissimo a non proporre soluzioni che non ho avuto tempo di scrivere e provare in maniera approfondita, se no invece di un blog di programmazione artistica questo sarebbe un blog di programmazione alla speriamo che funziona...

Ok, per questa seconda puntata quanto detto può bastare. Nella prossima, come promesso nel titolo stesso, vedremo come fare un FileCopy in Go. E sarà interessante, ve lo prometto!

Ciao, e al prossimo post!