Per motivi di performance, quando viene richiesta la scrittura su disco, il kernel memorizza la richiesta in un buffer in memoria prima di inviarla al device.
Quando il buffer contiene un numero sufficiente di richieste, il kernel svuota il buffer.
Per rendere “permanente” una scrittura su disco è possibile utilizzare una delle seguenti system call:
int fsync(int fd);
int fdatasync(int fd);
void sync(void);
Come abbiamo accennato nelle lezioni precedenti, ad ogni file sono associate una serie di informazioni, quali, ad esempio, la dimensione, le protezioni, etc.
Un processo può ottenere queste informazioni utilizzando una delle seguenti system call:
int stat(const char *file_name, struct stat *buf);
int fstat(int filedes, struct stat *buf);
int lstat(const char *file_name, struct stat *buf);
Il valore ritornato da queste system call è 0 (zero) in caso di successo o -1 in caso di errore.
Per identificare il file per cui ottenere informazioni e’ necessario indicare alle system call il pathname del file od il file descriptor ad esso associato. In particolare:
Tutte le system memorizzano la struttura stat contenente le informazioni sul file in una struttura il cui puntatore deve essere passato come parametro.
Di seguito riportiamo i campi della struttura stat utilizzata dalle system call della famiglia stat.
struct stat {
mode_t st_mode; /* file type & mode (permissions) */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
ino_t st_ino; /* inode number */
dev_t st_dev; /* device number (file system) */
dev_t st_rdev; /* device type (if inode device) */
nlink_t st_nlink; /* number of links */
off_t st_size; /* total size, in bytes */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last change */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of blocks allocated */
};
I sistemi Unix definiscono sette tipi di file:
Il tipo di file associato ad un pathname od un file descriptor è codificato nel campo st_mode della struttura stat.
Le seguenti macro consentono di identificare il tipo di file in esame.
Le macro prendono in input il campo st_mode della struttura stat e ritornano vero se il file in esame corrisponde al tipo associato, falso altrimenti:
Identificazione del tipo di file: Mostra codice
Esempi di esecuzione e relativi output: Mostra codice
Il campo st_mode codifica anche le protezioni associate al file. In particolare, il campo st_mode è composto dall’OR delle costanti utilizzate dalla system call creat o read per la definizione delle protezioni sui file all’atto della loro creazione.
S_I{P}{WHO}
dove:
L’esempio: Mostra codice, riporta un esempio di utilizzo di queste costanti.
Il programma calcola l'AND logico tra il campo st_mode della struttura stat e la costante in esame. Se l'AND è non nullo, allora l'accesso corrispondente al flag in esame è possibile.
Esempi di esecuzione e relativi output: Mostra codice
Ad ogni file sono associati uno User ID (uid) ed un Group ID (gid) del proprietario. Queste informzioni sono memorizzate nei campi st_uid e st_gid della struttura stat.
Ogni processo possiede i seguenti ID:
La possibilità che ogni processo possa avere diversi uid e gid nasce dalla necessità di consentire agli utenti di eseguire, in particolari condizioni, operazioni che normalmente NON potrebbero effettuare. In particolare, è possibile indicare nel campo st_mode di un file eseguibile un flag, il set-uid o set-gid, che indica al sistema operativo di assegnare all’effective user id (o l’effective group id) del processo il valore dello uid del proprietario (risp., gruppo) del file. Si consideri il caso in cui un utente voglia modificare la propria password. Le password sono memorizzate in un file che, per ragioni di sicurezza, può essere modificato solo dall’amministratore di sistema, l’utente root. L’esistenza di un file eseguibile, (a) di proprietà dell’utente root e (b) in cui è settato il flag set-uid, consente a qualsiasi utente che esegue quel programma di acquisire, temporaneamente, l’identità dell’utente root.
Il sistema operativo concede o nega l’accesso ad un file in base al valore dell’effective user/group id, utilizzando il seguente algoritmo:
In alcuni casi, però, l’applicazione potrebbe avere la necessità di verificare le protezioni in base al real user/groud id.
Ad esempio, un utente che “temporaneamente” diventa root, non dovrebbe avere la possibilità di modificare I file di un altro utente.
Questa verifica delle protezioni deve essere effettuata esplicitamente all’interno del codice attraverso la seguete system call:
int access(const char *pathname, int mode);
il parametro mode può assumere i valori:
La system call ritorna zero se le protezioni del file indicato dal parametro pathname consentono l’accesso al processo corrente in base a real user e real group id.
Attenzione: a system call access indica al processo corrente la possibilità o meno che il real user/group ha di accedere al file. L’eventuale impossibilità di accedere al file deve essere gestita dal codice.
Il sistema operativo considera sempre e solo le protezioni imposte dal effective user e group id.
Esempio di utilizzo (sbagliato) di access: Mostra codice. La system call verifica se real user/group id hanno accesso al file, visualizzando il risultato dell'operazione.
L'errore sta nel NON considerare il risultato di access. Difatti, se anche il real user/group non avessero l'accesso al file, il sistema operativo non bloccherebbe l'accesso (in lettura in questo caso) se il file eseguibile è ad esempio, di proprietà di root ed il flag set-uid/gid è settato.
Esempio di esecuzione: Mostra codice. Il programma è eseguito dal "real user" lso.
La presenza del flag set-uid rende l'effective user id del processo pari a zero.
La system call access identifica, correttamente, che il real user (lso) NON ha accesso in lettura al file test.
Nonostante questo, la open viene effettuata con successo visto che l'effective user (root) ha accesso al file.
È possibile modificare I permessi di accesso ai file utilizzando le seguenti system call:
int chmod(const char *path, mode_t mode);
int fchmod(int fildes, mode_t mode);
La modifica alle protezioni è consentita solo quando l’effective user id del processo che invoca le system call coincide con lo user id del proprietario del file.
chmod prende in input il pathname del file
fchmod prende in input un file descriptor del file. È possibile utilizzare questa system call solo dopo aver aperto il file.
Il parametro “mode” può essere una delle seguenti costanti:
Un esempio di utilizzo di access: Mostra codice
È possibile modificare il proprietario ed il gruppo di un file utilizzando una delle seguenti system call:
int chown(const char *path, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);
Queste system call modificano i campi st_uid ed st_gid del file indicato dal pathname (chown e lchown) o dal file descriptor (fchown).
Nel caso in cui il pathname identifica un link simbolico, la lchown modifica il proprietario/gruppo del link piuttosto che del file puntato dal link.
Se il parametro “owner” o “group” e’ uguale a -1, il campo corrispondente non viene modificato.
Ciò consente di modificare uno solo dei due elementi senza conoscere preventivamente l’altro.
In molti sistemi, solo un processo del superuser può modificare il campo st_uid
In generale, un processo può modificare il gruppo se:
La struttura stat contiene diversi campi che indicano “dimensioni” diverse per il file cui sono associate. In particolare:
Le seguenti system call consentono di “troncare” un file.
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
La truncate prende come primo parametro il pathname del file.
La ftruncate prende come primo parametro il file descriptor del file.
È possibile utilizzare questa system call solo successivamente all’esecuzione di una open.
Entrambe funzioni “troncano” il file dopo “length” bytes.
Se la dimensione del file è maggiore del parametro length, i dati dal byte length+1 non sono più accessibili.
Se la dimensione del file è minore di length, il comportamento è dipendente dallo specifico sistema Unix
Nei sistemi Linux, la dimensione del file viene aumentata e la parte in eccesso contiene “zeri”.
Entrambe le system call ritornano zero se l’operazione ha avuto successo, -1 in caso di errore.
Come nella maggior parte dei sistemi operativi, i sistemi Unix consentono il partizionamento di un disco in più partizioni logiche. Ogni partizione, a sua volta, è divisa in gruppi. Ad ogni file:
Ogni gruppo include:
Ad ogni directory sono associati uno o più data block, detti directory block, contenenti, per ogni file, almeno le seguenti informazioni:
Si noti che un file “corrisponde” al suo inode. Difatti la modifica del nome del file corrisponde alla sola modifica della stringa contenuta nel directory block associata all’inode che identifica il file.
Ad ogni file all’interno di una directory corriponde un inode nell’array degli inode. L’inode, tra le altre informazioni, contiene I puntatori ai data block allocati al file.
Un hard link è una entry all’interno di una directory in cui l’inode number corrisponde ad all’inode di un file esistente. In pratica è possibile creare “un file” all’interno di una directory creando un puntatore ad un file già esistente.
Un soft link è una versione meno rigida di un hard link. Un soft link corrisponde ad un file che contiene, all’interno dell’unico data block ad esso allocato, il nome di un altro file. Un soft link può essere utilizzato per creare “shortcut” a file o directory, senza avere I limiti di indistinguibilità degli hard link.
La creazione di hard link ad un file consente, in pratica, di creare riferimenti alla stessa sequenza di data block in più punti del file system.
Un hard link è indistinguibile dal file originale. Per questa ragione, non si dovrebbero creare hard link a directory (per evitare loop infiniti delle utility di manutenzione) ed un hard link non può puntare ad un file in un’altra partizione del sistema.
Nell’esempio in figura, I file “Filename2″ e “Filename3″ sono uno hard link all’altro perché le due entry nelle directory block corrispondono allo stesso inode.
Il campo st_nlink della struttura stat contiene il numero di link (hard e soft) al file. Si noti che il link count di un file appena creato vale 1.
Un file può essere cancellato solo se questo campo ha valore 0.
Per le directory il link count è almeno 2
È possibile creare hard link utilizzando la system call link:
int link(const char *oldpath, const char *newpath);
Crea un (hard) link tra oldpath (file esistente) e newpath.
Molti sistemi consentono la creazione di hard link alle directory esclusivamente ai processi con UID 0.
Ritorna zero se l’operazione ha successo, -1 in caso di errore, assegnando opportunamente la variabile errno.
La rimozione di un hard link può essere effettuata utilizzando la seguente system call:
int unlink(const char *pathname);
unlink: Rimuove dalla directory il riferimento al file ed elimina il file se il campo st_nlink diventa 0.
Sono necessari permessi di scrittura e “ricerca” sulla directory o, se lo sticky bit è settato, essere proprietari della directory o del file.
Nota: Se anche il campo st_nlink è pari a zero, il kernel elimina il file solo se non vi sono altri processi che lo usano.
È possibile modificare il path name associato ad un file utilizzando la seguente system call:
int rename(const char *oldpath, const char *newpath);
Rinomina il file indicato da oldpath come newpath:
I link simbolici o soft link consentono di:
La directory entry per un link contiene:
È necessario controllare sempre se la funzione “segue” il link
e.g., “rename” di un symbolic link NON segue il link
È possibile creare e legere I soft link utilizzando le seguenti system call:
int symlink(const char *oldpath, const char *newpath);
int readlink(const char *path, char *buf, size_t bufsiz);
La “creation mask” di un processo modifica i permessi di accesso all’atto della creazione del file (e delle directory).
La creation mask è composta come “OR” dei flag associati ai permessi (S_I[RWX]USR/GRP/OTH)
I permessi di accesso al file vengono modificati come segue:
permessi reali=permessi richiesti AND (NOT(mask))
La creation mask consente di evitare la creazione di file con permessi che potrebbero compromettere la sicurezza dei dati in essi contenuti.
Ad esempio, la creation mask di default utilizzata in molti sistemi è 022 che corrisponde a S_IWGRP|S_IWOTH. Questa maschera evita la creazione di file con permessi di scrittura per i componenti del gruppo e gli “altri”, proteggendo, quindi, per default il contenuto del file da modifiche di utenti diversi dal proprietario.
La system call umask modifica la creation mask del processo.mode_t umask(mode_t mask);
Esempi di utilizzo di umask: Mostra codice
La creazione e la cancellazione di directory consistono nell’elaborazione dell’inode e dei directory block della directory. È quindi necessario utilizzare system call specifiche.
int mkdir(const char *pathname, mode_t mode);
int rmdir(const char *pathname);
Entrambe le system call ritornano il valore zero se l’operazione è stata eseguita con successo, -1 altrimenti.
Vista la struttura peculiare delle directory, il sistema operativo fornisce una serie di system call dedicate alla lettura delle directory entry. La modifica delle directory viene gestita implicitamente (e.g., creat) od esplicitamente attraverso system call specifiche (mkdir, rename, unlink, etc.) .
La system call opendir apre una directory (in lettura). In caso di successo, ritorna un puntatore alla directory. Altrimenti ritorna NULL in caso di errore.
DIR *opendir(const char *name);
La struttura dati che definisce una directory entry è la struct dirent. Lo standard POSIX definisce questa struttura come contenente solo il nome del file. In molte implementazioni, però, questa struttura contiene almeno I seguenti campi:
Si noti che questi due campi costituiscono le informazioni minime contenute in una directory entry. I campi effettivamente contenuti nella struct dirent dipendono dal sistema. Allo stesso modo, la dimensione massima di un nome file NAME_MAX è una costante che dipende dal filesystem, in genere 255. Osserviamo che la dimensione delle directory entry è variabile ed è funzione della dimensione del nome del file. La system call readdir legge la prossima directory entry dalla directory puntata da dp e ritorna un puntatore alla struttura dirent contenente le informazioni lette, oppure NULL in caso di terminazione della directory o di errore.
struct dirent *readdir(DIR *dp);
L’accesso sequenziale alle entry in una directory è possibile eseguendo consecutivamente la system cal readdir. La system call rewinddir consente di riportare l’indice di lettura alla prima entry della directory.
void rewinddir(DIR *dp);
Una volta terminate le operazioni di lettura, è possibile chiudere la directory utilizzando la system call closedir.
int closedir(DIR *dp);
Per l’accesso non sequenziale alle entry in una directory è necessario tener presente che la dimensione delle entry è variabile. La system call telldir ritorna la posizione corrente all’interno della directory.
long telldir(DIR *dp);
È possibile utilizzare il valore di ritorno della telldir per riposizionare l’indice di lettura della directory in una directory utilizzando la system call seekdir. Si noti che il secondo parametro della seekdir deve essere un valore ritornato da una precedente invocazione della telldir (sulla stessa directory e senza che siano state fatte modifiche alla directory). Le system call telldir e seekdir NON sono definite dallo standard POSIX.
void seekdir(DIR *dp, long loc);
Esempio di utilizzo delle system call appena viste: Mostra codice
Il programma esegue la scansione della directory "TEST", sottodirectory della directory corrente.
Viene aperta la directory utilizzando la opendir e, il programma di ferma in caso di errore.
Altrimenti, in un ciclo while, viene invocata la readdir fino a quando questa system call non ritorna NULL ("fine directory" o errore).
La struttura dirent conterrà, di volta in volta, il nome del prossimo file della directory. Si noti che il file indicato dalla dirent è, in realtà, contenuto nella nella directory TEST che è sottodirectory della directory corrente. Per eseguire la stat su questo file, è quindi necessario invocare la system call utilizzando come pathname TEST/nomefile.
L'invocazione della stat consente di ottenere informazioni sul file in esame tra cui la dimensione.
Esempio di output: Mostra codice
Esempi di utilizzo delle system call telldir e seekdir: Mostra codice, Mostra codice
La funzione "ricerca" prende in input un puntatore a directory ed una stringa ed esegue una scansione della directory, a partire dalla posizione corrente, cercando una entry che contiene come sottostringa il secondo parametro.
Prima della lettura (e del conseguente spostamento dell'indice), la funzione invoca la telldir per memorizzare quale sia la posizione iniziale della entry.
La readdir legge la entry e, in caso soddisfi i criteri di selezione, ritorna il valore ritornato dalla telldir.
La funzione main invoca la funzione ricerca ripetutamente fino a quando questa non ritorna -1.
L'effetto di questa strategia è la visualizzazione delle informazioni di tutti I file contenuti nella direcory "test" il cui nome contiene una sottostringa comune definita da "name".
Si noti che, prima di eseguire una eventuale seconda scansione della directory è necessario (a) chiedere e riaprire la directory OPPURE (b) eseguire la rewinddir della stessa, per reinizializzare l'indice di lettura.
1. Introduzione ai sistemi Unix
2. Principi di programmazione Shell
3. Esercitazioni su shell scripting - parte prima
5. Esercitazioni su shell scripting - parte seconda
6. Espressioni Regolari ed Introduzione ad AWK
7. Esercitazioni su espressioni regolari ed awk scripting
9. Esercitazioni su awk scripting - parte seconda
10. Programmazione in linguaggio C: Input/Output di basso livello
11. Esercitazioni su I/O di basso livello
12. Interazione con file di sistema e variabili d'ambiente
13. Esercitazioni sulla gestione dei file di sistema e le variabili...
14. System call per la gestione di file e directory
15. Esercitazioni su gestione file e directory
16. La programmazione multi-processo
17. Esercitazioni su programmazione multi-processo
18. I Segnali
20. I Socket
W.R. Stevens, S.A. Rago - Advanced Programming in the Unix Environment
Capitolo 3 (3.13), Capitolo 4 (4.1-4.17, 4.21).