Come fa il mio computer a immagazzinare le cose sul disco?

Quando si legge un disco fisso su Unix, si vede un albero di nomi di file e directory. Normalmente non sarà necessario vedere oltre, ma può essere utile avere maggiori dettagli se capita un crash del disco e si ha la necessità di provare a salvare dei file. Sfortunatamente, non c'è un buon modo per descrivere l'organizzazione del disco dal livello dei file in giù, quindi dovrò partire dall'hardware e risalire.

Struttura di basso livello del disco e del file system

La superficie del disco, dove vengono immagazzinati i dati, è divisa in qualcosa simile a un bersaglio per il tiro a freccette: in piste circolari che sono poi divise in settori. Dal momento che le piste vicino al bordo esterno hanno area maggiore di quelle vicino al centro, le piste esterne hanno più settori rispetto a quelle interne. Ogni settore (o blocco del disco) ha la stessa dimensione, che sui moderni sistemi Unix è generalmente pari a 1 K binario (1024 byte da 8 bit). Ogni blocco del disco è individuato da un indirizzo univoco o numero di blocco del disco.

Unix divide il disco in partizioni del disco. Ogni partizione è formata da una serie continua di blocchi che vengono usati separatamente da quelli delle altre partizioni, come file system oppure come spazio di swap. In origine lo scopo delle partizioni aveva a che fare col ripristino dopo un crash quando i dischi erano più lenti e maggiormante inclini ad errori; le delimitazioni che le separano riducono la frazione del disco che probablmente diventerebbe inaccessibile o difettosa per un errore casuale sul disco. Oggigiorno, è più importante che le partizioni possano essere dichiarate di sola lettura (impedendo a un intruso di modificare file di sistema critici) o condivisi in una rete in diversi modi che qui non verranno trattati. La partizione con numero più basso viene spesso trattata in modo speciale, come una partizione di avvio dove si può mettere un kernel per essere avviato.

Ogni partizione può essere sia uno spazio di swap (usato per implementare la memoria virtuale) sia un file system usato per contenere i file. Le partizioni swap sono trattate proprio come una sequenza lineare di blocchi. I file system, invece, hanno bisogno di un modo per associare i nomi dei file alle sequenze di blocchi del disco. Dal momento che la dimensione dei file aumenta, diminuisce, si modifica nel tempo, i blocchi-dati di un file non saranno una sequenza lineare ma potranno essere disseminati su tutta la sua partizione (dipende da dove il sistema operativo riesce a trovare un blocco libero quando gliene serve uno). Questo effetto dispersivo è chiamato frammentazione.

Nomi dei file e directory

All'interno di ciascun file system la corrispondenza tra i nomi e i blocchi viene gestita attraverso una struttura chiamata i-node. C'è un gruppo di questi elementi vicino al fondo (i blocchi a numerazione più bassa) di ciascun file system (quelli più bassi in assoluto sono usati per scopi di manutenzione e di etichettatura, non saranno descritti qui). Ogni i-node individua un file. I blocchi-dati dei file (incluse le directory) si trovano sotto gli i-node (nei blocchi a numerazione più alta).

Ogni i-node contiene una lista dei numeri di blocco-disco relativi al file che individua. (Questa è una mezza verità, corretta solo per i file piccoli, ma il resto dei dettagli non è importante qui.) Notare che l'i-node non contiene il nome del file.

I nomi dei file si trovano nelle strutture delle directory. Una struttura della directory associa solo i nomi ai numeri i-node. Ecco perché, su Unix, un file può avere più nomi reali (o collegamenti fisici); sono soltanto voci di directory multiple che puntano allo stesso i-node.

Punti di Mount

Nel caso più semplice, tutto il proprio file system Unix si trova su di una sola partizione del disco. Anche se questa situazione si ritrova in qualche piccolo sistema Unix personale, è inusuale. Più generalmente il file system è distribuito tra pi` partizioni-disco, possibilmente su diversi dischi fisici. Così, per esempio, il proprio sistema può avere una piccola partizione dove risiede il kernel, una un po' più grande dove si trovano i programmi di utilità del SO e una molto più grande dove ci sono le directory personali degli utenti.

La sola partizione alla quale si avrà accesso subito dopo l'avvio del sistema è la propria partizione di root, che è (quasi sempre) quella dalla quale si è si è avviato il sistema. Essa contiene la "root directory" (directory radice) del file system, il nodo superiore dal quale dipende tutto il resto.

Le altre partizioni del sistema devono essere collegate a questa directory "root" affinché tutto il proprio file system multipartizione sia accessibile. Circa a metà del processo di avvio, il sistema Unix renderà accessibili queste partizioni non root. Esso dovrà montare ognuna di esse su di una directory della partizione root.

Per esempio, se si ha una directory Unix chiamata /usr, si tratta probabilmente di un punto di mount per una partizione che contiene molti programmi installati col proprio sistema Unix ma che non sono necessari durante l'avvio iniziale.

Come un file viene visto

Ora possiamo guardare al file system dall'alto al basso. Ecco cosa succede quando si apre un file (quale, ad esempio, /home/esr/WWW/ldp/fundamentals.sgml):

Il kernel si avvia alla radice del file system Unix (dalla partizione root). Cerca una directory chiamata ‘home’. Di solito ‘home’ è un punto di mount per una partizione utente di grandi dimensioni che si trova da qualche altra parte, così andrà lì. Nella struttura della directory di livello più alto di quella partizione utente cerca poi una voce chiamata ‘esr’ e ne estrae un numero di i-node. Va a quell'i-node, vede che si tratta di una struttura di directory e cerca ‘WWW’. Estraendo quell'i-node, va alla corrispondente sottodirectory e cerca ‘ldp’. Questo lo porta a un altro i-node di directory ancora. Aprendolo, trova il numero i-node di ‘fundamentals.xml’. Questo i-node non è una directory, ma contiene invece l'elenco dei blocchi-disco associati al file.

Possesso dei file, permessi e sicurezza

Per impedire ai programmi di intervenire accidentalmente o malignamente su dati su cui non dovrebbero intervenire, Unix ha la funzionalità dei permessi. Questi vennero originariamente pensati per il "timesharing" (suddivisione di tempo), proteggendo gli uni dagli altri utenti diversi sulla stessa macchina, quando ancora Unix veniva usato su costosi minicomputer condivisi.

Per comprendere i permessi sui file, occorre rivedere la descrizione di utenti e gruppi nella sezione Che cosa accade quando si fa il log in?. Ciascun file ha un utente proprietario e un gruppo proprietario. Inizialmente sono quelli del creatore del file; possono essere poi modificati con i programmi chown(1) e chgrp(1).

I permessi fondamentali che possono essere associati a un file sono ‘read’ (permesso di leggere i dati contenuti), ‘write’ (permesso di modificarli) ed ‘execute’ (permesso di eseguirli come programma). Ogni file ha tre impostazioni di permessi; uno per l'utente proprietario, uno per tutti gli utenti nel gruppo proprietario e uno per tutti gli altri. I ‘privilegi’ che si ottengono al momento del log in sono solo la possibilità di leggere, modificare ed eseguire quei file i cui bit dei permessi corrispondono con il proprio ID utente o quello di un gruppo a cui si appartiene, o i file che sono stati resi accessibili a tutti.

Per vedere come questi possono interagire e come le visualizza Unix, osserviamo alcuni elenchi di file su un sistema Unix ipotetico. Ecco un esempio:

 snark:~$ ls -l notes
-rw-r--r--   1 esr      users         2993 Jun 17 11:00 notes

Si tratta di un file di dati ordinario. Il listato ci informa che il proprietario è l'utente ‘esr’, creato con il gruppo proprietario ‘users’. Probabilmente la macchina su cui si trova mette per definizione tutti gli utenti ordinari in questo gruppo; altri gruppi che si vedranno comunemente su macchine con timesharing sono ‘staff’, ‘admin’, o ‘wheel’ (per ovvie ragioni, i gruppi non sono molto importanti su workstation a singolo utente o PC). Il sistema Unix in uso potrebbe usare un gruppo predefinito differente, magari derivato dal proprio ID utente.

La stringa ‘-rw-r--r--’ rappresenta i bit di permessi per il file. Il primo trattino è la posizione del bit directory; se il file fosse stato una directory il bit sarebbe stato ‘d’, o ‘l’ se il file fosse stato un collegamento simbolico. Dopo di questo, le prime tre posizioni successive sono i permessi utente, le seconde tre i permessi del gruppo e le terze tre i permessi per gli altri (spesso chiamate autorizzazioni ‘world’). Su questo file l'utente proprietario ‘esr’ può leggere e modificare il file, gli altri appartenenti al gruppo ‘users’ possono leggerlo e così tutti gli altri utenti. Si tratta di un insieme di permessi piuttosto tipici per un file di dati ordinario.

Ora osserviamo un file con permessi molto diversi. Tale file è GCC, il compilatore C GNU.

snark:~$ ls -l /usr/bin/gcc
-rwxr-xr-x   3 root     bin         64796 Mar 21 16:41 /usr/bin/gcc

Questo file appartiene a un utente chiamato ‘root’ e a un gruppo chiamato ‘bin’; può essere modificato solo da root, ma letto ed eseguito da tutti. Si tratta di un proprietario e di un insieme di permessi tipici per un comando di sistema pre-installato. Il gruppo ‘bin’ esiste su alcuni sistemi Unix per raggruppare i comandi di sistema (il nome è una reliquia storica, abbreviazione di ‘binary’). Il sistema Unix in uso potrebbe usare invece un gruppo ‘root’ (non esattamente la stessa cosa dell'utente ‘root’!).

L'utente ‘root’ è il nome convenzionale per utente con ID uguale a 0, un account speciale privilegiato che può scavalcare tutti i privilegi. L'accesso come root è utile ma pericoloso; un errore di battitura quando si è collegati come root potrebbe rovinare file critici del sistema, cosa che non può avvenire con un account di utente ordinario.

Poiché l'account root è così potente, l'accesso a questo account dovrebbe essere sorvegliato attentamente. La propria password di root è la parte più critica nelle informazioni di sicurezza del proprio sistema, ed essa è ciò che ogni "cracker" e ogni intruso che verranno dopo cercheranno di ottenere.

Per quanto riguarda le password: non la si scriva su qualche parte; e non si scelgano password che possano essere facilmente indovinate, come il nome della propria ragazza/sposa o del proprio ragazzo/sposo. Questa è una cattiva pratica sorprendentemente comune che aiuta continuamente i "cracker". In generale, non si scelga alcuna parola nel dizionario; esistono dei programmi chiamati dictionary crackers che cercano le possibili password scorrendo liste di parole di uso comune. Una tecnica valida è quella di scegliere una combinazione consistente di una parola, un numero, e un'altra parola, come ad esempio: ‘shark6cider’ o ‘jump3joy’; che renderà il campo di ricerca troppo grande per un "dictionary cracker". Non si usino questi esempi, però; i "cracker" se lo potrebbero aspettare dopo la lettura di questo testo e mettere questi esempi nei loro dizionari.

Osserviamo ora un terzo caso:

snark:~$ ls -ld ~
drwxr-xr-x  89 esr      users          9216 Jun 27 11:29 /home2/esr
snark:~$ 

Questo file è una directory (notare la ‘d’ in prima posizione nella stringa dei permessi). Vediamo che può essere modificata solo da esr, ma letta ed eseguita da tutti gli altri.

Il permesso in lettura fornisce la possibilità di listare la directory, cioè di vedere i nomi dei file e delle directory che essa contiene. Il permesso in scrittura dà la possibilità di creare e cancellare file nella directory. Se si ricorda che una directory include una lista dei nomi di file e delle sottodirectory che contiene, questa regola ha una sua logica

Il permesso di esecuzione su di una directory significa che si può esaminare la directory per aprire i file e le directory sotto di essa. Di fatto, dà il permesso di accedere agli i-node della directory. Una directory con il permesso di esecuzione completamente mancante potrebbe essere inusabile

Occasionalmente si vedrà una directory che è eseguibile da tutti ma non leggibile da tutti; questo significa che un utente qualunque può accedere ai file e alle directory al suo interno, ma solamente se ne conosce il nome esatto (la directory non può essere listata).

È importante ricordare che i permessi di lettura, scrittura o esecuzione su una directory sono indipendenti dai permessi sui file e le directory al suo interno. In particolare, l'accesso in scrittura su una directory significa che si possono creare nuovi file o cancellare file esistenti al suo interno, ma non dà automaticamente l'accesso in scrittura ai file esistenti.

Infine, osserviamo i permessi dello stesso programma login.

snark:~$ ls -l /bin/login
-rwsr-xr-x   1 root     bin         20164 Apr 17 12:57 /bin/login

Possiede i permessi che ci aspetteremmo per un comando di sistema; eccetto per la ‘s’ dove dovrebbe esserci il bit del permesso di esecuzione del proprietario. Questa è la manifestazione visibile di un tipo speciale di permesso chiamato ‘set-user-id’ o setuid bit.

Il bit setuid è normalmente legato a programmi che necessitano di dare agli utenti ordinari i privilegi di root, ma in un modo controllato. Quando è impostato su un programma eseguibile, si acquistano i privilegi del proprietario di quel file di programma finché si esegue quel programma, sia che corrispondano a quelli dell'utente oppure no.

Come l'account root stesso, i programmi setuid sono utili ma pericolosi. Chiunque sia in grado di sovvertire o modificare un programma setuid che ha root come proprietario, può usarlo per accedere alla shell con privilegi di root. Per questa ragione, sulla maggior parte dei sistemi Unix, aprendo un file in scrittura il suo bit setuid viene disattivato automaticamente. Molti attacchi alla sicurezza su Unix tentano di scoprire bug nei programmi setuid, con lo scopo di sovvertirli. Gli amministratori di sistema attenti alla sicurezza sono quindi molto prudenti con questi programmi e riluttanti a installarne di nuovi.

Ci sono un paio di dettagli importanti che abbiamo sorvolato durante la precedente spiegazione dei permessi; in particolare, come vengono assegnati il gruppo proprietario e i permessi quando viene creato un file o una directory per la prima volta. Il gruppo è un problema poiché gli utenti possono essere membri di più gruppi, ma uno di essi (specificato nella voce dell'utente in /etc/passwd) è il gruppo predefinito dell'utente e normalmente sarà proprietario dei file creati dall'utente.

La storia con i bit iniziali dei permessi è un po' più complicata. Un programma che crea un file normalmente specifica i permessi coi quali dovrà iniziare. Ma questi verranno modificati da una variabile nell'ambiente dell'utente chiamata umask. Umask speciica quali bit dei permessi disattivare quando si crea un file; il valore più comune, quello predefinito sulla maggior parte dei sistemi, è -------w- o 002, che disattiva il bit di scrittura per tutti gli utenti. Vedere la documentazione per il comando umask nella pagina di manuale della shell per maggiori dettagli.

Anche il gruppo iniziale di directory è un po' complicato. Su alcuni sistemi Unix una nuova directory ottiene il gruppo predefinito dell'utente che l'ha creata (questo nella convenzione del System V); su altri sistemi ottiene il gruppo proprietario della directory genitrice in cui essa viene creata (questa è la convenzione di BSD). Su alcuni Unix moderni, incluso Linux, quest'ultimo comportamento può essere selezionato impostando il set-group-ID sulla directory (chmod g+s).

Come le cose possono andare male

Precedentemente è stato accennato che i file system possono essere delicati. Ora sappiamo che per raggiungere un file dobbiamo fare il gioco della campana attraverso quella che può essere una catena arbitrariamente lunga di directory e riferimenti i-node. Supponiamo ora che sul disco fisso si formi un punto danneggiato.

Se si è fortunati ciò verrà perso solo qualche file di dati. Se invece si è sfortunati, si potrebbe danneggiare una struttura di directory o un numero i-node e un intero sottoalbero del sistema potrebbe rimanere sospeso nel limbo. Oppure, peggio ancora, si potrebbe originare una struttura danneggiata che punta in più modi allo stesso blocco disco o i-node. Un danneggiamento di questo tipo si può propagare a partire da una normale operazione sui file, facendo perdere tutti i dati collegati al punto danneggiato di origine.

Fortunatamente, questo tipo di eventualità è divenuto abbastanza infrequente perché l'hardware dei dischi è più affidabile. Tuttavia, questo significa che il sistema Unix cercherà di controllare periodicamente l'integrità del file system per assicurarsi che non ci sia nulla fuori posto. I sistemi Unix moderni compiono un rapido controllo dell'integrità di ciascuna partizione nella fase di avvio, appena prima di montarle. Dopo un certo numero di riavvii fanno un controllo molto più approfondito che impiega qualche minuto in più.

Se tutto questo può far sembrare che Unix sia terribilmente complesso e incline a malfunzionamenti, può essere rassicurante sapere che questi controlli nella fase d'avvio tipicamente intercettano e correggono i problemi normali prima che diventino veramente disastrosi. Altri sistemi operativi non hanno questi strumenti, cosa che velocizza un po' l'avvio ma può mettere molto di più nei pasticci quando si cerca di fare un salvataggio manuale (e sempre assumendo che si abbia una copia delle Norton Utilities o simili, tanto per cominciare...).

Una delle tendenze attuali nella progettazione dei sistemi Unix sono i file system con journaling. Questi gestiscono il traffico al disco in modo da garantirgli di rimanere in uno stato coerente che può ripristinato quando il sistema ritorna a funzionare. Questo renderà molto più veloce la verifica dell'integrità all'avvio.