Aprire un file in Linux: open

System Call open

Per aprire un file in Linux, si utilizza la chiamata di sistema open().

La sua firma è la seguente:

Chiamata di sistema open()

#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags, ... /* mode_t mode */);

In caso di successo, open() restituisce un file descriptor (un intero non negativo) che rappresenta il file aperto. In caso di errore, restituisce -1 e imposta errno per indicare il tipo di errore.

Il file da aprire è identificato dall'argomento pathname. Se pathname è un link simbolico, viene dereferenziato.

In caso di successo, open() restituisce un file descriptor che viene utilizzato per riferirsi al file in chiamate di sistema successive. Se si verifica un errore, open() restituisce -1 e errno viene impostato di conseguenza.

Il flag flags è una maschera di bit che specifica la modalità di accesso per il file, utilizzando uno dei costanti mostrate nella tabella che segue:

Modo di Accesso Descrizione
O_RDONLY Apre il file in sola lettura
O_WRONLY Apre il file in sola scrittura
O_RDWR Apre il file per lettura e scrittura
Tabella 1: Modalità di accesso a file.

Le prime versioni di UNIX utilizzavano i numeri 0, 1 e 2 al posto dei nomi mostrati nella tabella. La maggior parte delle implementazioni moderne di UNIX definisce queste costanti con quei valori. Quindi, possiamo notare che O_RDWR non è equivalente a O_RDONLY | O_WRONLY; quest'ultima combinazione rappresenta un errore logico.

Quando open() viene utilizzato per aprire un file esistente, il flag flags può essere specificato come una combinazione di bit OR tra i valori mostrati nella tabella. Se il file non esiste, l'operazione fallisce a meno che non venga specificato il flag O_CREAT, che indica al sistema di creare un nuovo file se non esiste.

Nel caso in cui il flag O_CREAT sia specificato, è necessario fornire anche un argomento mode, che specifica le autorizzazioni del file da creare. Questo argomento è di tipo mode_t e viene utilizzato per impostare i permessi del file appena creato. Se il flag O_CREAT non è specificato, l'argomento mode deve essere omesso.

Per il momento tralasciamo i dettagli sull'argomento mode, che verrà trattato in seguito.

Adesso vediamo qualche esempio di utilizzo della chiamata di sistema open(). Questi esempi mostrano altri flag che possono essere utilizzati con open() e che descriveremo a breve.

  • Aprire un file in sola lettura:

    /* Apertura di un file in Sola Lettura */
    int fd = open("esempio.txt", O_RDONLY);
    
    if (fd == -1) {
        perror("Errore nell'apertura del file");
        exit(EXIT_FAILURE);
    }
    
  • Aprire un file in lettura e scrittura e lo crea se non esiste:

    /*
     * Apre un file in lettura e scrittura;
     * lo crea se non esiste, altrimenti lo tronca a zero
     * byte di lunghezza.
     * Imposta i permessi di lettura e scrittura per il proprietario,
     * e nessun permesso per gli altri.
     */
    fd = open("esempio.txt", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
    
    if (fd == -1) {
        perror("Errore nell'apertura del file");
        exit(EXIT_FAILURE);
    }
    
  • Aprire un file in sola scrittura:

    /* Apre un file in Sola Scrittura
     * Se il file esiste, lo tronca a zero byte di lunghezza.
     * Se il file non esiste, lo crea.
     * Se esiste, lo apre in modalità append.
     */
    fd = open("esempio.txt", O_WRONLY | O_CREAT | O_TRUNC | O_APPEND,
    S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("Errore nell'apertura del file");
        exit(EXIT_FAILURE);
    }
    

Il file descriptor restituito da open

Lo standard POSIX specifica che il file descriptor restituito da open() deve essere il più basso possibile tra quelli disponibili per il processo. Questo significa che se un file descriptor è già stato chiuso, open() lo riutilizzerà per il nuovo file aperto.

Ad esempio, il seguente codice chiude il file descriptor 0 (che è solitamente associato all'input standard) e poi apre un file, garantendo che il file venga aperto con il file descriptor 0:

/* Chiude il file descriptor 0 (stdin)
 * e apre un file con lo stesso file descriptor */
if (close(STDIN_FILENO) == -1) {
    perror("close");
    exit(EXIT_FAILURE);
}

fd = open(pathname, O_RDONLY);

if (fd == -1) {
    perror("open");
    exit(EXIT_FAILURE);
}

/* A questo punto, fd è 0 e rappresenta il file aperto */

Dal momento che il file descriptor 0 è stato chiuso, open() lo riutilizza per il nuovo file aperto. Questo significa che ora il file aperto è associato al file descriptor 0, e qualsiasi operazione di lettura da stdin utilizzerà questo file invece dell'input standard originale.

Più avanti, vedremo modi per controllare esplicitamente quale file descriptor viene utilizzato per aprire un file, ad esempio utilizzando le funzioni dup2() o fcntl(). Queste funzioni offrono maggiore flessibilità e controllo rispetto a open(), consentendo di specificare esattamente quale file descriptor si desidera utilizzare per un file aperto.

I flag di apertura dei file

Negli esempi precedenti, abbiamo visto che il secondo argomento della chiamata di sistema open() è un intero che specifica i flag di apertura del file. Questi flag sono definiti come costanti in C e possono essere combinati tra loro utilizzando l'operatore bitwise OR (|).

Prima ne abbiamo visto alcuni, come O_RDONLY, O_WRONLY, e O_RDWR, che specificano le modalità di accesso al file. Tuttavia, ci sono molti altri flag che possono essere utilizzati per controllare il comportamento dell'apertura del file e delle operazioni successive.

La tabella che segue mostra l'insieme completo di tali flag:

Flag Descrizione
O_RDONLY Apre il file in sola lettura
O_WRONLY Apre il file in sola scrittura
O_RDWR Apre il file in lettura e scrittura
O_CLOEXEC Imposta il flag close-on-exec
O_CREAT Crea il file se non esiste
O_DIRECT Bypassa il buffer cache per le operazioni I/O
O_DIRECTORY Fallisce se il percorso non è una directory
O_EXCL Con O_CREAT: crea il file in modo esclusivo
O_LARGEFILE Utilizzato su sistemi a 32 bit per aprire file di grandi dimensioni (superiori a 4GiB)
O_NOATIME Non aggiorna il timestamp di accesso del file
O_NOCTTY Non rende il file il terminale di controllo del processo
O_NOFOLLOW Non segue i link simbolici
O_TRUNC Tronca il file a zero byte se esiste (in altre parole lo resetta)
O_APPEND Scrive sempre alla fine del file
O_ASYNC Abilita la generazione di segnali quando l'I/O è possibile
Tabella 2: Flag possibili per la chiamata di sistema open().

Le costanti della tabella di sopra sono raggruppabili in tre categorie:

  • Flag di accesso al file:

    Questi sono i flag O_RDONLY, O_WRONLY, e O_RDWR che abbiamo già visto. Indicano se il file deve essere aperto in sola lettura, in sola scrittura o in lettura e scrittura.

  • Flag di creazione del file:

    Questi sono flag che controllano vari aspetti del comportamento della chiamata open(), come la creazione di un nuovo file se non esiste (O_CREAT), la troncatura del file a zero byte (O_TRUNC), e l'apertura in modalità append (O_APPEND). Questi flag non possono essere recuperati o modificati dopo l'apertura del file.

  • Flag di stato del file aperto:

    Questi sono i flag rimanenti nella tabella di sopra. Possono essere recuperati e modificati usando le operazioni fcntl() F_GETFL e F_SETFL che vedremo nelle prossime lezioni. Questi flag sono talvolta semplicemente chiamati flag di stato del file.

    Dal kernel 2.6.22, i file specifici di Linux nella directory /proc/PID/fdinfo possono essere letti per ottenere informazioni sui descrittori di file di qualsiasi processo nel sistema. C'è un file in questa directory per ciascuno dei descrittori di file aperti del processo, con un nome che corrisponde al numero del descrittore. Il campo pos in questo file mostra l'offset corrente del file. Il campo flags è un numero ottale che mostra i flag della modalità di accesso al file e i flag di stato del file aperto. (Per decodificare questo numero, dobbiamo guardare i valori numerici di questi flag nei file header della libreria C)

Dettagli sui flag di apertura dei file

I dettagli per le costanti dei flag sono i seguenti:

  • O_APPEND

    Le scritture sono sempre aggiunte alla fine del file. Discuteremo il comportamento di questo flag in dettaglio nelle prossime lezioni.

  • O_ASYNC

    Genera un segnale quando l'I/O diventa possibile sul descrittore di file restituito da open(). Questa funzionalità, chiamata I/O guidato da segnale, è disponibile solo per certi tipi di file, come terminali, FIFO, e socket. Studieremo l'I/O guidato da segnale in una lezione successiva.

  • O_CLOEXEC (dal kernel 2.6.23)

    Abilita il flag close-on-exec (FD_CLOEXEC) per il nuovo descrittore di file. Usare il flag O_CLOEXEC permette a un programma di evitare operazioni aggiuntive fcntl() F_SETFD e F_SETFD per impostare il flag close-on-exec. È anche necessario nei programmi multithreaded per evitare race-conditions.

  • O_CREAT

    Se il file non esiste già, viene creato come un nuovo file vuoto. Questo flag è efficace anche se il file viene aperto solo per la lettura. Se specifichiamo O_CREAT, allora dobbiamo fornire un argomento mode nella chiamata open(); altrimenti, i permessi del nuovo file saranno impostati a qualche valore casuale dallo stack.

  • O_DIRECT

    Permette all'I/O del file di bypassare la cache del buffer. Studieremo questa funzionalità nelle prossime lezioni.

  • O_DIRECTORY

    Restituisce un errore (errno uguale a ENOTDIR) se pathname non è una directory.

  • O_DSYNC (dal kernel 2.6.33)

    Le scritture sul file completano solo quando i dati sono stati scritti sul supporto di memorizzazione. Le operazioni di lettura non sono influenzate da questo flag. Questo flag è simile a O_SYNC, ma non richiede che i metadati del file siano sincronizzati con il supporto di memorizzazione.

  • O_EXCL

    Questo flag è usato insieme a O_CREAT per indicare che se il file esiste già, non dovrebbe essere aperto; invece, open() dovrebbe fallire, con errno impostato a EEXIST. In altre parole, questo flag permette al chiamante di assicurarsi che sia il processo a creare il file. Il controllo per l'esistenza e la creazione del file sono eseguiti atomicamente. Quando sia O_CREAT che O_EXCL sono specificati in flags, open() fallisce (con l'errore EEXIST) se pathname è un link simbolico. SUSv3 richiede questo comportamento affinché un'applicazione privilegiata possa creare un file in una posizione conosciuta senza che ci sia la possibilità che un link simbolico causi la creazione del file in una posizione diversa (ad es., una directory di sistema), il che avrebbe implicazioni di sicurezza.

  • O_LARGEFILE

    Apre il file con supporto per file grandi. Questo flag è usato su sistemi a 32 bit per lavorare con file di grandi dimensioni (superiori a 4GiB). Sebbene non sia specificato nello standard SUSv3, il flag O_LARGEFILE si trova in diverse altre implementazioni UNIX. Su implementazioni Linux a 64 bit come Alpha e IA-64, questo flag non ha effetto.

  • O_NOATIME (dal kernel 2.6.8)

    Non aggiorna il tempo di ultimo accesso del file (ossia il campo st_atime) quando legge da questo file. Per adoperare questo flag, l'ID dello user effettivo del processo chiamante deve corrispondere a quello dell'owner del file, oppure il processo deve avere i necessari privilegi (CAP_FOWNER). In caso contrario, open fallisce con l'errore EPERM. Questo flag è un'estensione non standard di Linux. Per esporne la definizione dal file di intestazione fcntl.h, dobbiamo definire la macro _GNU_SOURCE prima dell'inclusione. Questo flag è pensato per i programmi di indicizzazione e backup. Il suo utilizzo può ridurre drasticamente la quantità di attività su disco, perché ricerche ripetute sul disco non richiedono di leggere il contenuto di un file e di aggiornare l'ultimo tempo di accesso nelle informazioni del file stesso.

  • O_NOCTTY

    Se il file aperto è un dispositivo terminale, previene al dispositivo di diventare il terminale di controllo. Se, invece, il file aperto non è un terminale, il flag non ha effetto. Studieremo i terminali di controllo più avanti.

  • O_NOFOLLOW

    Normalmente, open dereferenzia il percorso di un file se quest'ultimo è un link simbolico. Tuttavia, se viene specificato il flag O_NOFOLLOW, la funzione open fallisce con l'errore ELOOP nel caso in cui si tratti di un link simbolico. Questo flag è utile, specialmente per i programmi privilegiati, per assicurarsi che open non dereferenzi link simbolici.

  • O_NONBLOCK

    Apre il file in modalità non bloccante. Studieremo questo concetto più avanti.

  • O_SYNC

    Apre il file per l'I/O sincrono.

  • O_TRUNC

    Se il file esiste già ed è un file regolare, viene troncato a lunghezza zero, distruggendo tutti i dati presenti. Su Linux, il troncamento avviene sia che il file sia aperto in lettura sia in scrittura (in entrambi i casi è necessario avere il permesso di scrittura sul file). SUSv3 lascia non specificata la combinazione O_RDONLY e O_TRUNC, ma la maggior parte delle altre implementazioni UNIX si comporta come Linux.

In Sintesi

I flag di open() controllano modalità di accesso, creazione e stato del file, e possono essere combinati con l'operatore OR. Alcuni flag, come O_CLOEXEC, migliorano la sicurezza nei programmi multithread, mentre altri, come O_ASYNC, richiedono l'uso di fcntl() per essere attivati. La tabella riassume tutti i flag disponibili e la loro standardizzazione.