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 |
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 |
Le costanti della tabella di sopra sono raggruppabili in tre categorie:
-
Flag di accesso al file:
Questi sono i flag
O_RDONLY,O_WRONLY, eO_RDWRche 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_GETFLeF_SETFLche 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/fdinfopossono 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_APPENDLe scritture sono sempre aggiunte alla fine del file. Discuteremo il comportamento di questo flag in dettaglio nelle prossime lezioni.
-
O_ASYNCGenera 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 flagO_CLOEXECpermette a un programma di evitare operazioni aggiuntivefcntl()F_SETFDeF_SETFDper impostare il flag close-on-exec. È anche necessario nei programmi multithreaded per evitare race-conditions. -
O_CREATSe 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 chiamataopen(); altrimenti, i permessi del nuovo file saranno impostati a qualche valore casuale dallo stack. -
O_DIRECTPermette all'I/O del file di bypassare la cache del buffer. Studieremo questa funzionalità nelle prossime lezioni.
-
O_DIRECTORYRestituisce un errore (
errnouguale aENOTDIR) sepathnamenon è 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_EXCLQuesto flag è usato insieme a
O_CREATper indicare che se il file esiste già, non dovrebbe essere aperto; invece,open()dovrebbe fallire, conerrnoimpostato aEEXIST. 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 siaO_CREATcheO_EXCLsono specificati inflags,open()fallisce (con l'erroreEEXIST) sepathnameè 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_LARGEFILEApre 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_LARGEFILEsi 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,openfallisce con l'erroreEPERM. Questo flag è un'estensione non standard di Linux. Per esporne la definizione dal file di intestazionefcntl.h, dobbiamo definire la macro_GNU_SOURCEprima 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_NOCTTYSe 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_NOFOLLOWNormalmente,
opendereferenzia il percorso di un file se quest'ultimo è un link simbolico. Tuttavia, se viene specificato il flagO_NOFOLLOW, la funzioneopenfallisce con l'erroreELOOPnel caso in cui si tratti di un link simbolico. Questo flag è utile, specialmente per i programmi privilegiati, per assicurarsi cheopennon dereferenzi link simbolici. -
O_NONBLOCKApre il file in modalità non bloccante. Studieremo questo concetto più avanti.
-
O_SYNCApre il file per l'I/O sincrono.
-
O_TRUNCSe 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_RDONLYeO_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.