Siamo abituati a pensare che una directory contenga dei file: in verità, si tratta di un’illusione. Le directory non contengono file. I dati dei file non sono memorizzati nella directory.

Una directory è solo un file. È un file speciale con regole particolari (non puoi semplicemente digitare cp /dev/null directory per cancellarla). Ha dei bit appositi per garantire che un utente normale non possa far danni. Questo perché se un file system viene corrotto, si può anche dire addio ai propri dati. Su sistemi UNIX più vecchi era possibile in realtà “leggere” i contenuti di una directory usando cat ., ma su questo tornerò tra un attimo…

Un file Unix è memorizzato su due parti diverse del disco – i data blocks e gli inodes. (Non mi addentrerò nei superblocks ed altre informazioni esoteriche.) I data blocks mantengono il “contenuto” del file. Le informazioni circa il file, invece, sono conservate altrove, per l’appunto nell’inode.

Entrambi gli inodes e i data blocks sono posti in un filesystem, che è poi il modo in cui la partizione del disco è organizzata. Vediamo un’attimo di capire cosa sono gli inodes.

“ls -i” mostra l’inode di un file

I normali utenti Unix/Linux/MacOS non sono nemmeno consapevoli dell’esistenza degli inode. Tuttavia, esiste un modo molto semplice di scoprirli: usando il comando “ls -i”.
Diamo uno sguardo al file system della root.

% cd /
% ls -i
2637825 bin     983041 etc	       1572865 lib	   2981889 media  2531329 root	 106497 selinux    81921 usr
 196609 boot         2 home	       1761281 lib64	   2129921 mnt	     6416 run	2457601 srv	  425985 var

L’opzione “-i” inserisce il numero dell’inode prima del nome del file. I numeri appaiono come numeri piuttosto grandi, eccetto per “home”.
Cerchiamo ora di ottenere più informazioni e mostrare alcuni file in più aggiungendo le opzioni “-a” e “-l”.

% ls -lai | tail -7
total 132
      2 drwxr-xr-x  24 root root   4096 Feb 26 13:31 .
      2 drwxr-xr-x  24 root root   4096 Feb 26 13:31 ..
2637825 drwxr-xr-x   2 root root   4096 Jan 14 19:02 bin
 196609 drwxr-xr-x   3 root root   4096 Feb 24 10:41 boot
      3 drwxr-xr-x  16 root root   4460 Mar  5 09:35 dev
 983041 drwxr-xr-x 206 root root  12288 Mar  5 07:45 etc
      2 drwxr-xr-x  14 root root   4096 Dec 29 09:24 home

È interessante: tre dei file hanno il valore dell’inode pari a 2. Ma come si può notare, ha perfettamente senso.

Come i sistemi Unix possono supportare diversi tipi di file system, così, nel filesystem “classico”, l’inode numero 2 è sempre la root del file system. Se si vuole cercare un file, è possibile partire dall’inode #2 e scendere nella struttura della directory. Normalmente “..” punta alla directory padre ma giacché “/” è il punto più alto dell’albero, il padre di “/” è “/”.

La directory “dev” ha inode 3. Ho il sospetto che quando il filesystem è stato creato, la directory “/dev” sia stato il primo file ad essere creato.

Ma, potreste chiedervi, perché “home” ha inode 2?
La ragione è semplice. Si tratta di una partizione differente, di cui “/home” è la root.
Gli inodes sono sempre unici ma unici per ciascuna partizione. Per identificare in maniera univoca, si ha necessità di conoscere l’inode e il dispositivo (la partizione del disco).

Cosa c’è in un inode?

Mentre i data block mantengono il contenuto del file, l’inode contiene le seguenti porzioni di informazioni:

  • Permessi
  • Owner ID
  • Group ID
  • Dimensione del file
  • Numero di hard links al file
  • Data dell’ultimo accesso
  • Data dell’ultima modifica
  • Data dell’ultima modifica dell’inode

Come dicevo, un file system si divide in due parti: gli inode e i data block. Una volta creati, il numero di blocchi di ciascun tipo è fisso. Non è possible aumentare il numero di inodes di una partizione o il numero di disk block. (Per approfondire può tornare utile dare una letta a man mkfs.ext2).

Vi sembra che manchi qualcosa? Dov’è il NOME del file. O il path? NON è nell’inode. NON è dei data block. È nella directory. Esatto. Un “file” in verità è in tre (o più) posizioni su disco.

Infatti, la directory è solo una tabella che contiene i filename nella directory ed il corrispondente inode. Sempre nell’ottica della tabella, le prime due entry sono sempre “.” e “..”
La prima punta all’inode della directory corrente e la seconda punta all’inode della directory padre. Per Definizione. Invero, come è stato stabilito dagli Dei di Unix.

Questa magia degli inode è quella che permette di creare un “hard link” – avendo due o più nomi per lo stesso file. Si pensi ad una directory come ad una tabella che contiene il nome e l’inode di ciascun file della directory. Questo è il punto focale: il nome di un file è usato solo nella directory.
È possibile avere un’altra directory “contenente” lo stesso file, pur avendo un nome diverso.

Quando si crea un hard link, si sta solo creando un nuovo nome nella tabella, a fianco all’inode, senza spostare il file. Quando si sposta un file (o si rinomina), non vengono copiati i dati, sarebbe troppo lento. Semplicemente si crea una entry (nome, inode) nella nuova directory e si elimina la vecchia entry nella tabella all’interno della vecchia directory.
In altre parole, spostare un file dell’ordine dei gigabyte impiega pochissimo tempo. Allo stesso modo, è possibile spostare o rinominare le directory in maniera molto facile. Ecco perché “mv /usr /old_usr” è così rapido, anche qualora “/usr” contenga, ad esempio, 57981 file.

Usando l’opzione “ls -i” si possono osservare queste cose sull’inode. Mostra il numero dell’inode. find(1) can use it as well. (?). Con l’opzione “d”, inoltre, si consultano le informazioni riguardo la directory invece che i contenuti della stessa.
You can see this “inode” stuff if you use the “ls -i” option. It lists the inode number. find(1) can use it as well. Let’s also use the “-d” option to list information about the directory, rather than the contents of the directory.

Prima creaimo una nuova directory usando i comandi seguenti:

cd /tmp
mkdir test
cd test

Digitando

ls -id ..
cd ..
ls -id .

Si otterrà un risultato simile a questo

/tmp/test$ ls -id ..
327681 ..
/tmp/test$ cd ..
/tmp$ ls -id .
327681 .

Come si può vedere, questi due “file” puntano allo stesso inode – il cui numero è 327681.
Cerchiamo di ottenere ulteriori informazioni da questa directory.

$ ls -lad /tmp/test
drwxrwxr-x 2 fponzi fponzi 4096 Mar  5 10:42 /tmp/test

Il secondo campo ha valore 2, poiché indica che esistono due hard link a questo file. Ciò ha senso perché le directories, come è evidente qui, hanno sempre almeno due nomi.

/tmp/test$ cd /tmp
/tmp$ ls -iad test  # look at the file /tmp/test
435297 test
/tmp$ cd test
/tmp/test$ ls -iad . # look at the file "." in the /tmp/test directory
435297 .

È il momento di far combaciare i pezzi del nostro puzzle!
Sono sul sistema e digito due comandi. Ecco di seguito i comandi e i risultati:

% cd
% ls -ld . ..
drwxr-xr-x   66 fponzi  users       12288 Mar  7 18:43 .
drwxr-xr-x    6 root     root         4096 Feb 19  2012 ..

A questo punto dovreste aver capito quante directory ho nella mia home directory e quanti altri utenti abbiano home directory sul mio sistema.

Vi lascio ragionare un secondo.

Ricordate, il file “..” punta sempre alla directory padre. E ricordate che quando creo una directory, il kernel crea i file “.” e “..” all’interno di essa.

Di conseguenza, ogni volta che creo una directory “all’interno” della mia directory corrente, la nuova directory ha una entry “..” che è la sua directory padre.

Ciò significa che siccome ho 66 “copie” o hard link alla mia home directory, devo avere 64 sotto-directory al suo interno, perché partendo sempre da 2 link, è sufficiente sottrarre 2 dal totale.

Quante altre directory sono /home directory? La risposta è 3, quindi ci sono altri 3 utenti che hanno una home directory. Non è molto accurato dato che gli utenti devono avere una entry nel file /etc/passwd. In ogni caso, grazie al valore 6 della directory “..” sappiamo che ci sono 4 directory al di sopra della mia home directory, perché dobbiamo sempre sottrarre due dalla directory stessa ed in più sottraiamo anche la mia directory, il che ci porta a contarne 3.

Usare gli inodes

Gli inodes possono essere molto utili per indagare meglio il file system. Come descrivevo prima, può dare informazioni sulle varie directory sul computer e su quando dovrebbero essere. Potrebbe anche aiutare ad individuare una directory nascosta. Cominciamo dai casi più semplici.

Trovare i file tramite gli inodes

Se conosci l’inode, puoi trovare il file usando il comando find.

$ find . -inum 435304 -print

Eliminare file con nomi strani

A volte i file sono creati con caratteri particolari nel nome del file. Il filesystem di Unix permette che venga usato qualunque carattere all’interno di un filename fatta eccezione per null (ASCII 000) o un “/”. Qualunque altro carattere è consentito.

Gli hacker possono creare file con caratteri che rendono difficile localizzare la directory o il file. Possono creare la directory “..” con uno spazio alla fine o creare un file che abbia un backspace nel nome usando

touch `printf "aa\bb"`

Ecco cosa succede usando il comando “ls”:

$ ls
aa?b
$ ls | grep 'a'
ab

Si può notare che “ls” manda il risultato al terminale inserendo un “?” nel filename per indicare un carattere che non può essere stampato.

Ci si può disfare di questo file usando “rm -i *”: così facendo mostrerà il prompt prima di eliminare ciascuno dei file. Volendo si può usare anche “find” per rimuovere il file, una volta venuti a conoscenza del valore dell’inode.

$ ls -i
435304 aa?b
$ find . -inum 435304 -delete

Controllare la percentuale di inode utilizzati in una partizione

Penso che abbiamo tutti familiarità con il problema dello spazio disco che arriva a saturarsi. Il comando df mostra ogni partizione e la percentuale di disk blocks usati di ciascuna di esse.

È anche possibile che ci siano parecchi data block ma che il numero di inodes sia esaurito. Se questo accade, non possono essere più creati nuovi file. Il comando df -i mostra la percentuale (ed il totale) di inodes su ciascuna partizione.

La chiamata di sistema stat(2)

Concludiamo mostrando la struct con l’effettivo contenuto dell’inode, secondo quanto riporta la pagina del manuale di stat(2)

           struct stat {
               dev_t     st_dev;     /* ID of device containing file */
               ino_t     st_ino;     /* inode number */
               mode_t    st_mode;    /* protection */
               nlink_t   st_nlink;   /* number of hard links */
               uid_t     st_uid;     /* user ID of owner */
               gid_t     st_gid;     /* group ID of owner */
               dev_t     st_rdev;    /* device ID (if special file) */
               off_t     st_size;    /* total size, in bytes */
               blksize_t st_blksize; /* blocksize for file system I/O */
               blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
               time_t    st_atime;   /* time of last access */
               time_t    st_mtime;   /* time of last modification */
               time_t    st_ctime;   /* time of last status change */
           };

Si ringrazia Maria Giulia Cecchini per l’aiuto nella stesura di questo articolo.