[Avanti]  [Indietro]  [Su]  

5.1.6 Accesso alle directory

Benché le directory alla fine non siano altro che dei file che contengono delle liste di nomi ed inode, per il ruolo che rivestono nella struttura del sistema, non possono essere trattate come dei normali file di dati. Ad esempio, onde evitare inconsistenze all'interno del filesystem, solo il kernel può scrivere il contenuto di una directory, e non può essere un processo a inserirvi direttamente delle voci con le usuali funzioni di scrittura.

Ma se la scrittura e l'aggiornamento dei dati delle directory è compito del kernel, sono molte le situazioni in cui i processi necessitano di poterne leggere il contenuto. Benché questo possa essere fatto direttamente (vedremo in sez. 6.2.1 che è possibile aprire una directory come se fosse un file, anche se solo in sola lettura) in generale il formato con cui esse sono scritte può dipendere dal tipo di filesystem, tanto che, come riportato in tab. 4.2, il VFS del kernel prevede una apposita funzione per la lettura delle directory.

Tutto questo si riflette nello standard POSIX9 che ha introdotto una apposita interfaccia per la lettura delle directory, basata sui cosiddetti directory stream (chiamati così per l'analogia con i file stream dell'interfaccia standard di cap. 7). La prima funzione di questa interfaccia è opendir, il cui prototipo è:

La funzione restituisce un puntatore al directory stream in caso di successo e NULL per un errore, nel qual caso errno assumerà i valori EACCES, EMFILE, ENFILE, ENOENT, ENOMEM e ENOTDIR.

La funzione apre un directory stream per la directory dirname, ritornando il puntatore ad un oggetto di tipo DIR (che è il tipo opaco usato dalle librerie per gestire i directory stream) da usare per tutte le operazioni successive, la funzione inoltre posiziona lo stream sulla prima voce contenuta nella directory.

Dato che le directory sono comunque dei file, in alcuni casi può servire conoscere il file descriptor associato ad un directory stream, a questo scopo si può usare la funzione dirfd, il cui prototipo è:

La funzione restituisce il file descriptor (un valore positivo) in caso di successo e -1 in caso di errore.

La funzione10 restituisce il file descriptor associato al directory stream dir, essa è disponibile solo definendo _BSD_SOURCE o _SVID_SOURCE. Di solito si utilizza questa funzione in abbinamento alla funzione fchdir per cambiare la directory di lavoro (vedi sez. 5.1.7) a quella relativa allo stream che si sta esaminando.

La lettura di una voce della directory viene effettuata attraverso la funzione readdir; il suo prototipo è:

La funzione restituisce il puntatore alla struttura contenente i dati in caso di successo e NULL altrimenti, in caso di descrittore non valido errno assumerà il valore EBADF, il valore NULL viene restituito anche quando si raggiunge la fine dello stream.

La funzione legge la voce corrente nella directory, posizionandosi sulla voce successiva. I dati vengono memorizzati in una struttura dirent (la cui definizione11 è riportata in fig. 5.2). La funzione restituisce il puntatore alla struttura; si tenga presente però che quest'ultima è allocata staticamente, per cui viene sovrascritta tutte le volte che si ripete la lettura di una voce sullo stesso stream.

Di questa funzione esiste anche una versione rientrante, readdir_r, che non usa una struttura allocata staticamente, e può essere utilizzata anche con i thread; il suo prototipo è:

La funzione restituisce 0 in caso di successo e -1 in caso di errore, gli errori sono gli stessi di readdir.

La funzione restituisce in result (come value result argument) l'indirizzo dove sono stati salvati i dati, che di norma corrisponde a quello della struttura precedentemente allocata e specificata dall'argomento entry (anche se non è assicurato che la funzione usi lo spazio fornito dall'utente).

I vari campi di dirent contengono le informazioni relative alle voci presenti nella directory; sia BSD che SVr412 prevedono che siano sempre presenti il campo d_name, che contiene il nome del file nella forma di una stringa terminata da uno zero,13 ed il campo d_ino, che contiene il numero di inode cui il file è associato (di solito corrisponde al campo st_ino di stat).


1: struct dirent { 
2:     ino_t d_ino;                    /* inode number */ 
3:     off_t d_off;                    /* offset to the next dirent */ 
4:     unsigned short int d_reclen;    /* length of this record */ 
5:     unsigned char d_type;           /* type of file */ 
6:     char d_name[256];               /* We must not include limits.h! */ 
7: }; 
Figura 5.2: La struttura dirent per la lettura delle informazioni dei file.

La presenza di ulteriori campi opzionali è segnalata dalla definizione di altrettante macro nella forma _DIRENT_HAVE_D_XXX dove XXX è il nome del relativo campo; nel nostro caso sono definite le macro _DIRENT_HAVE_D_TYPE, _DIRENT_HAVE_D_OFF e _DIRENT_HAVE_D_RECLEN.


|Valore----|Significato----------|
|DT_UNKNOWN-|tipo-sconosciuto.-----|
|DT_REG     |file normale.        |
|DT_DIR     |directory.           |
|DT_FIFO    |fifo.               |
|DT_SOCK    |socket.             |
|DT_CHR     |dispositivo a caratteri.|
-DT_BLK------dispositivo-a blocchi.-

Tabella 5.2: Costanti che indicano i vari tipi di file nel campo d_type della struttura dirent.

Per quanto riguarda il significato dei campi opzionali, il campo d_type indica il tipo di file (fifo, directory, link simbolico, ecc.); i suoi possibili valori14 sono riportati in tab. 5.2; per la conversione da e verso l'analogo valore mantenuto dentro il campo st_mode di stat sono definite anche due macro di conversione IFTODT e DTTOIF:

Il campo d_off contiene invece la posizione della voce successiva della directory, mentre il campo d_reclen la lunghezza totale della voce letta. Con questi due campi diventa possibile, determinando la posizione delle varie voci, spostarsi all'interno dello stream usando la funzione seekdir,15 il cui prototipo è:

La funzione non ritorna nulla e non segnala errori, è però necessario che il valore dell'argomento offset sia valido per lo stream dir; esso pertanto deve essere stato ottenuto o dal valore di d_off di dirent o dal valore restituito dalla funzione telldir, che legge la posizione corrente; il prototipo di quest'ultima è:

La funzione restituisce la posizione corrente nello stream (un numero positivo) in caso di successo, e -1 altrimenti, nel qual caso errno assume solo il valore di EBADF, corrispondente ad un valore errato per dir.

La sola funzione di posizionamento nello stream prevista dallo standard POSIX è rewinddir, che riporta la posizione a quella iniziale; il suo prototipo è:

Una volta completate le operazioni si può chiudere il directory stream con la funzione closedir, il cui prototipo è:

La funzione restituisce 0 in caso di successo e -1 altrimenti, nel qual caso errno assume il valore EBADF.

A parte queste funzioni di base in BSD 4.3 è stata introdotta un'altra funzione che permette di eseguire una scansione completa (con tanto di ricerca ed ordinamento) del contenuto di una directory; la funzione è scandir16 ed il suo prototipo è:

La funzione restituisce in caso di successo il numero di voci trovate, e -1 altrimenti.

Al solito, per la presenza fra gli argomenti di due puntatori a funzione, il prototipo non è molto comprensibile; queste funzioni però sono quelle che controllano rispettivamente la selezione di una voce (select) e l'ordinamento di tutte le voci selezionate (compar).

La funzione legge tutte le voci della directory indicata dall'argomento dir, passando ciascuna di esse come argomento alla funzione di select; se questa ritorna un valore diverso da zero la voce viene inserita in una struttura allocata dinamicamente con malloc, qualora si specifichi un valore NULL per select vengono selezionate tutte le voci. Tutte le voci selezionate vengono poi inserite un una lista (anch'essa allocata con malloc, che viene riordinata tramite qsort usando la funzione compar come criterio di ordinamento; alla fine l'indirizzo della lista ordinata è restituito nell'argomento namelist.

Per l'ordinamento sono disponibili anche due funzioni predefinite, alphasort e versionsort, i cui prototipi sono:

Le funzioni restituiscono un valore minore, uguale o maggiore di zero qualora il primo argomento sia rispettivamente minore, uguale o maggiore del secondo.

La funzione alphasort deriva da BSD ed è presente in Linux fin dalle libc417 e deve essere specificata come argomento compare per ottenere un ordinamento alfabetico (secondo il valore del campo d_name delle varie voci). Le glibc prevedono come estensione18 anche versionsort, che ordina i nomi tenendo conto del numero di versione (cioè qualcosa per cui file10 viene comunque dopo file4.)

Un semplice esempio dell'uso di queste funzioni è riportato in fig. 5.3, dove si è riportata la sezione principale di un programma che, usando la routine di scansione illustrata in fig. 5.4, stampa i nomi dei file contenuti in una directory e la relativa dimensione (in sostanza una versione semplificata del comando ls).


01: #include <sys/types.h> 
02: #include <sys/stat.h> 
03: #include <dirent.h>        /* directory */ 
04: #include <stdlib.h>        /* C standard library */ 
05: #include <unistd.h> 
06:  
07: /* computation function for DirScan */ 
08: int do_ls(struct dirent * direntry); 
09: /* main body */ 
10: int main(int argc, char *argv[])  
11: { 
12:     ... 
13:     if ((argc - optind) != 1) {          /* There must be remaing parameters */ 
14:         printf("Wrong number of arguments %d\n", argc - optind); 
15:         usage(); 
16:     } 
17:     DirScan(argv[1], do_ls); 
18:     exit(0); 
19: } 
20: /* 
21:  * Routine to print file name and size inside DirScan 
22:  */ 
23: int do_ls(struct dirent * direntry)  
24: { 
25:     struct stat data; 
26:  
27:     stat(direntry->d_name, &data);                          /* get stat data */ 
28:     printf("File: %s \t size: %d\n", direntry->d_name, data.st_size); 
29:     return 0; 
30: } 
Figura 5.3: Esempio di codice per eseguire la lista dei file contenuti in una directory.

Il programma è estremamente semplice; in fig. 5.3 si è omessa la parte di gestione delle opzioni (che prevede solo l'uso di una funzione per la stampa della sintassi, anch'essa omessa) ma il codice completo potrà essere trovato coi sorgenti allegati nel file myls.c.

In sostanza tutto quello che fa il programma, dopo aver controllato (10–13) di avere almeno un parametro (che indicherà la directory da esaminare) è chiamare (14) la funzione DirScan per eseguire la scansione, usando la funzione do_ls (20–26) per fare tutto il lavoro.

Quest'ultima si limita (23) a chiamare stat sul file indicato dalla directory entry passata come argomento (il cui nome è appunto direntry->d_name), memorizzando in una opportuna struttura data i dati ad esso relativi, per poi provvedere (24) a stampare il nome del file e la dimensione riportata in data.

Dato che la funzione verrà chiamata all'interno di DirScan per ogni voce presente questo è sufficiente a stampare la lista completa dei file e delle relative dimensioni. Si noti infine come si restituisca sempre 0 come valore di ritorno per indicare una esecuzione senza errori.


01: #include <sys/types.h> 
02: #include <sys/stat.h> 
03: #include <dirent.h>        /* directory */ 
04: #include <stdlib.h>        /* C standard library */ 
05: #include <unistd.h> 
06:  
07: /* 
08:  * Function DirScan:  
09:  *  
10:  * Input:  the directory name and a computation function 
11:  * Return: 0 if OK, -1 on errors 
12:  */ 
13: int DirScan(char * dirname, int(*compute)(struct dirent *))  
14: { 
15:     DIR * dir; 
16:     struct dirent *direntry; 
17:  
18:     if ( (dir = opendir(dirname)) == NULL) { /* open directory */ 
19:         printf("Opening %s\n", dirname);     /* on error print messages */ 
20:         perror("Cannot open directory");     /* and then return */ 
21:         return -1; 
22:     } 
23:     fd = dirfd(dir);                        /* get file descriptor */ 
24:     fchdir(fd);                             /* change directory */ 
25:     /* loop on directory entries */ 
26:     while ( (direntry = readdir(dir)) != NULL) { /* read entry */ 
27:         if (compute(direntry)) {            /* execute function on it */ 
28:             return -1;                      /* on error return */ 
29:         } 
30:     } 
31:     closedir(dir); 
32:     return 0; 
33: } 
Figura 5.4: Codice della routine di scansione di una directory contenuta nel file DirScan.c.

Tutto il grosso del lavoro è svolto dalla funzione DirScan, riportata in fig. 5.4. La funzione è volutamente generica e permette di eseguire una funzione, passata come secondo argomento, su tutte le voci di una directory. La funzione inizia con l'aprire (19–23) uno stream sulla directory passata come primo argomento, stampando un messaggio in caso di errore.

Il passo successivo (24–25) è cambiare directory di lavoro (vedi sez. 5.1.7), usando in sequenza le funzione dirfd e fchdir (in realtà si sarebbe potuto usare direttamente chdir su dirname), in modo che durante il successivo ciclo (27–31) sulle singole voci dello stream ci si trovi all'interno della directory.19

Avendo usato lo stratagemma di fare eseguire tutte le manipolazioni necessarie alla funzione passata come secondo argomento, il ciclo di scansione della directory è molto semplice; si legge una voce alla volta (27) all'interno di una istruzione di while e fintanto che si riceve una voce valida (cioè un puntatore diverso da NULL) si esegue (27) la funzione di elaborazione compare (che nel nostro caso sarà do_ls), ritornando con un codice di errore (28) qualora questa presenti una anomalia (identificata da un codice di ritorno negativo).

Una volta terminato il ciclo la funzione si conclude con la chiusura (32) dello stream20 e la restituzione (33) del codice di operazioni concluse con successo.


[Avanti]  [Indietro]  [Su]  
© 2000-2003 Simone Piccardi
Pubblicazione web curata da Mirko Maischberger