venerdì 1 ottobre 2010

Il Virtual File System in Linux: tipici problemi implementativi.

Maria Susana Diaz | 11:24 |
Nei sistemi Unix, il ``file'' è l'oggetto più utilizzato: un pathname unico identifica ogni file all'interno di un sistema. Ogni file si comporta come ogni altro file nel modo in cui viene utilizzato e modificato: le stesse chiamate di sistema e gli stessi comandi funzionano con qualsiasi file.

Questo succede indipendentemente dal supporto fisico che contiene l'informazione e dal modo in cui l'informazione è organizzata sul supporto.

L'astrazione dal dispositivo in cui l'informazione è immagazzinata viene realizzata richiedendo il trasferimento dati ai vari device driver; l'astrazione dal modo in cui l'informazione è organizzata viene ottenuta in Linux tramite il VFS.



Come fa Unix.
Linux vede il suo filesystem nello stesso modo di Unix: adotta il concetto di super-blocco, inode, directory e file come vengono usati in Unix. L'albero dei file che viene visto in un determinato momento dipende da come le differenti parti vengono assemblate; ogni ``parte'' in questo caso è rappresentata da una partizione di disco rigido o un altro dispositivo che viene ``montato'' nel sistema. Mentre credo che non ci siano problemi sull'operazione di mount di un albero, credo sia il caso di dare alcune spiegazioni sui concetti di super-blocco, inode, directory e file.
  • Il ``super-blocco'' deve il suo nome alle sue origini storiche, quando la meta-informazione riguardo al disco (o alla partizione) era immagazzinata nel primo settore del disco stesso. Al giorno d'oggi il super-blocco non corrisponde più ad un blocco di dati del disco, ma è ancora la struttura dati che contiene le informazioni riguardo al filesystem di cui fa parte. La struttura dati in Linux si chiama struct super_block, e contiene diverse informazioni di gestione, come i flag passati al comando mount, l'istante nel quale il disco è stato montato e dimensione del dispositivo. Il kernel 2.0 usa un vettore statico di 64 di tali strutture per essere in grado di montare fino a 64 dispositivi.
  • Un ``inode'' (index-node) è associato ad ogni file. L'inode contiene tutte le informazioni riguardanti un file tranne che il suo nome ed i suoi dati: il proprietario, il gruppo, i permessi e la dimensione dei dati contenuti nel file, come pure il numero di ``link'' relativi a tale file e altre informazioni. L'idea di separare le informazioni dal nome del file e dai suoi dati è quello che permette l'implementazione degli ``hard link'' -- come pure permette di usare le notazione `punto' e `punto-punto' per le directory senza il bisogno di trattare tali nomi come speciali. Un inode all'interno del kernel viene descritto da una struct inode.
  • La ``directory'' è un file che associa i nomi dei file agli inode. Il kernel non usa nessuna struttura dati particolare per rappresentare una directory, che viene trattata come un file normale nella maggior parte delle situazioni. Una directory viene letta e modificata tramite funzioni specifiche a ciascun filesystem, indipendentemente da come l'informazione è immagazzinata sul disco.
  • Il ``file'' è qualcosa associato ad un inode. Di solito i file sono aree dati, ma possono anche essere directory, dispositivi, FIFO o socket. Un `` file aperto'' è rappresentato nel kernel da una struttura struct file: tale struttura contiene un puntatore all'inode che identifica il file. Le strutture file vengono create dalle chiamate di sistema come open(), pipe() e socket(), esse vengono condivise tra processo padre e figlio attraverso la chiamata fork().

Orientazione agli oggetti.

Mentre la lista precedente descrive l'organizzazione teorica dell'informazione, un sistema operativo deve essere in grado di gestire differenti modi di organizzare l'informazione sul disco. Anche se in teoria è possibile cercare una disposizione ottimale delle informazioni ed usare questa struttura per tutti i dischi, la maggior parte degli utenti di calcolatori hanno bisogno di avere accesso ai loro dischi senza bisogno di riformattarli, e talvolta devono poter montare volumi via NFS attraverso la rete, ed a volte addirittura usare quegli strani CD e floppy i cui nomi di file non possono eccedere 8+3 caratteri.
Il problema di poter gestire differenti formati di dati in maniera trasparente è stato affrontato trasformando i super-blocchi, gli inode e i file in ``oggetti'': ogni oggetto dichiara un insieme di operazioni che possono essere usate su di lui. Il kernel eviterà di avere grossi costrutti switch per poter avere accesso a differenti modi di strutturare l'informazione sul disco, e nuovi tipi di filesystem potranno essere aggiunti o rimossi a run-time.
Tutta l'idea del VFS, perciò, è implementata tramite insiemi di operazioni che agiscono su tali oggetti. Ogni oggetto include una struttura dati che elenca le operazioni per agire su di lui, e la maggior parte di tali operazioni (funzioni C) ricevono come argomento un puntatore ``self'' come primo argomento, permettendo perciò la modifica dell'oggetto stesso.
In pratica, un super-blocco contiene un campo struct super_operations *s_op, un inode contiene struct inode_operations *i_op ed un file contiene struct file_operations *f_op.
Tutta la gestione dei dati e la bufferizzazione che viene effettuata dal kernel Linux è indipendente dal formato effettivo dei dati immagazzinati: ogni comunicazione con il supporto di immagazzinamento avviene attraverso una delle strutture operations. Il ``tipo di filesystem'', poi, è il modulo software che si occupa di tradurre le operazioni sull'effettivo meccanismo di immagazzinamento dei dati -- sia esso un dispositivo a blocchi, una connessione di rete (NFS) o virtualmente qualunque altro mezzo per salvare e recuperare dati. Questi moduli software che implementano i tipi di filesystem posso far parte del kernel che viene lanciato o essere compilati come moduli caricabili dinamicamente tramite insmod o kerneld.
L'implementazione attuale di Linux permette di utilizzare i moduli per tutti i tipi di filesystem utilizzati tranne il filesystem root (almeno il filesystem root deve essere montato prima di essere in grado di caricare un file nel kernel). In effetti, il meccanismo initrd permette di caricare un modulo prima di montare il filesytem root, montando temporaneamente come root un ram-disk. Questa ultima tecnica e' solitamente solo utilizzata nei dischetti di installazione.
In questo articolo utilizzo l'espressione ``modulo'' per riferirmi sia ad un modulo caricabile dinamicamente sia ad un decodificatore di filesystem che faccia parte del kernel.
In sintesi, la gestione dei file avviene come descritto qui sotto, e come rappresentato in figura:

La figura è anche disponibile in postscript come lj-vfs.ps.

  • struct file_system_type è una struttura che dichiara solo il nome del filesystem ed una funzione read_super(). Quando mount viene eseguito, la funzione riceve informazioni riguardo il dispositivo che viene montato e deve riempire una struttura super_block. La funzione deve anche caricare l'inode della directory root del filesystem all'interno di sb->s_mounted, dove sb è il super-blocco che viene riempito. Il campo aggiuntivo requires_dev viene usato da ciascun tipo di filesystem per dichiarare se tale tipo ha bisogno di un dispositivo a blocchi oppure no: per esempio, NFS e /proc non usano un dispositivo a blocchi, mentre ext2 e iso9660 si. Dopo che il super-blocco viene riempito, la struttura file_system_type non viene più usata; solo il super-blocco conterrà un puntatore a tale struttura per poter essere in grado di ritornare informazioni all'utente (/proc/mounts è un esempio di tale informazione di ritorno). La struttura è definita come segue:
     struct file_system_type {     struct super_block *(*read_super) (struct super_block *, void *, int);     const char *name;     int requires_dev;     struct file_system_type * next; /* there's a linked list of types */ };  
  • La strutture super_operations viene usata dal kernel per leggere e scrivere gli inode, salvare le informazioni del super-blocco sul disco e raccogliere statistiche (per poter rispondere alle chiamate di sistema statfs() e fstatfs()). Quando un filesystem viene infine smontato, l'operazione put_super() viene chiamata -- nel lessico del kernel, ``get'' vuol dire `alloca e riempi', ``read'' vuol dire riempi e ``put'' vuol dire `rilascia'. Le super_operations dichiarate da ciascun tipo di filesystem sono le seguenti:
     struct super_operations {     void (*read_inode) (struct inode *);  /* fill the structure */     int (*notify_change) (struct inode *, struct iattr *);     void (*write_inode) (struct inode *);     void (*put_inode) (struct inode *);     void (*put_super) (struct super_block *);     void (*write_super) (struct super_block *);     void (*statfs) (struct super_block *, struct statfs *, int);     int (*remount_fs) (struct super_block *, int *, char *); };  
  • Dopo la creazione di una copia in memoria di un inode, il kernel agirà su di lui tramite le sue proprie operazioni. struct inode_operations è il secondo insieme di operazioni che viene dichiarato da ciascun tipo di filesystem, e sono elencate qui sotto: come si vede tali operazioni si occupano principalmente della gestione dell'albero delle directory. Le operazioni di gestione delle directory fanno parte delle operazioni relative agli inode perchè l'implementazione di una apposita dir_operations sarebbe risultato in esecuzioni condizionali aggiuntive per ciascun accesso al filesystem. Invece si è scelto di far fare il controllo degli errori all'interno di ciascuna operazione, e le operazioni che hanno solo senso per le directory si rifiuteranno di operare su altri tipi di file. Il primo campo delle operazioni sugli inode definisce le operazioni per agire sui file regolari: se invece l'inode si riferisce ad una FIFO, un socket oppure un dispositivo, allora verranno usate le operazioni specifiche per questi file. Le operazioni sugli inode sono elencate sotto: la versione 2.0.1 del kernel ha cambiato la definizione di rename() rispetto alla versione 2.0.0.
     struct inode_operations {     struct file_operations * default_file_ops;     int (*create) (struct inode *,const char *,int,int,struct inode **);     int (*lookup) (struct inode *,const char *,int,struct inode **);     int (*link) (struct inode *,struct inode *,const char *,int);     int (*unlink) (struct inode *,const char *,int);     int (*symlink) (struct inode *,const char *,int,const char *);     int (*mkdir) (struct inode *,const char *,int,int);     int (*rmdir) (struct inode *,const char *,int);     int (*mknod) (struct inode *,const char *,int,int,int);     int (*rename) (struct inode *,const char *,int, struct inode *,                const char *,int, int); /* this from 2.0.1 onwards */     int (*readlink) (struct inode *,char *,int);     int (*follow_link) (struct inode *,struct inode *,int,int,struct inode **);     int (*readpage) (struct inode *, struct page *);     int (*writepage) (struct inode *, struct page *);     int (*bmap) (struct inode *,int);     void (*truncate) (struct inode *);     int (*permission) (struct inode *, int);     int (*smap) (struct inode *,int); };  
  • Infine, le file_operations specificano come agire sui dati all'interno di un file regolare: le operazioni implementano i dettagli di basso livello delle chiamate di sistema read(), write(), lseek() e le altre funzioni che agiscono sui dati. Siccome la stessa struttura file_operations viene usata per accedere ai dispositivi, essa contiene anche alcuni campi che hanno solo senso per i dispositive a carattere e a blocchi. E` interessante notare che la versione 2.1 del kernel ha cambiato i prototipi di read(), write() ed lseek() in modo da permettere un'estensione maggiore degli offset nei file. Le operazioni come appaiono in Linux-2.0 sono mostrate qui sotto.
     struct file_operations {     int (*lseek) (struct inode *, struct file *, off_t, int);     int (*read) (struct inode *, struct file *, char *, int);     int (*write) (struct inode *, struct file *, const char *, int);     int (*readdir) (struct inode *, struct file *, void *, filldir_t);     int (*select) (struct inode *, struct file *, int, select_table *);     int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);     int (*mmap) (struct inode *, struct file *, struct vm_area_struct *);     int (*open) (struct inode *, struct file *);     void (*release) (struct inode *, struct file *);     int (*fsync) (struct inode *, struct file *);     int (*fasync) (struct inode *, struct file *, int);     int (*check_media_change) (kdev_t dev);     int (*revalidate) (kdev_t dev); };  
Tipici problemi implementativi.
I meccanismi descritti qui sopra per accedere ai dati dei filesystem sono staccati dalla disposizione fisica dei dati sul disco e sono progettati per gestire tutte le semantiche Unix che riguardano i filesystem.



Purtroppo, però, non tutti i tipi di filesystem supportano tutte le funzioni descritte. In particolare non tutti i tipi hanno il concetto di inode, nonostante il kernel identifichi ogni file tramite un numero di inode unsigned long. Se la disposizione dei dati non ha il concetto di inode, il codice che implementa readdir() e read_inode() deve inventare un numero di inode per ciascun file immagazzinato sul disco.
Una tecnica tipica per scegliere il numero di inode è l'utilizzo dell'offset del blocco di controllo del file all'interno dell'area dati del filesystem, assumendo che i file siano identificati da qualcosa che può essere chiamato blocco di controllo. Il filesystem iso9660, per esempio, usa questa tecnica per creare un numero di inode associato ad ogni file.



Il filesystem /proc, d'altro canto, non si appoggia su alcun supporto fisico per estrarre i suoi dati, ed usa perciò numeri predefiniti per i file standard (come /proc/interrupts), ed assegna numeri dinamici per gli altri file. Il numero di inode associato ad ogni file è immagazzinato nella struttura dati associata ad ogni file allocato dinamicamente.



Un altro tipico problema che si incontra nell'implementazione di un tipo di filesystem è la gestione delle limitazioni nelle capacità di immagazzinamento dell'informazione. Per esempio, come reagire quando un utente prova a rinominare un file con un nome più lungo del massimo consentito in quel particolare filesystem, o quando si prova a modificare il tempo di accesso di un file all'interno di un filesystem che non ha il concetto di tempo di accesso.



In questi casi il codice ritornerà il valore -ENOPERM, che significa ``Operation non permitted''. La maggior parte delle funzioni del VFS, come tutte le chiamate di sistema ed un certo numero di altre funzioni del kernel, ritornano zero o un numero positivo in caso di successo, e un numero negativo in caso di errore. I codici di errore ritornati dalle funzioni del kernel sono il negato di uno dei valori definiti in asm/errno.h.



File dinamici in /proc

Vorrei mostrare adesso un po' di codice per giocare con il VFS, ma è abbastanza difficile inventare un filesystem abbastanza piccolo da stare in questo articolo. La scrittura di un nuovo filesystem è sicuramente un compito interessante, ma una implementazione completa include 39 funzioni di tipo ``operazione''. In pratica, c'è veramente bisogno di costruire un altro tipo di filesystem giusto per il gusto di farlo?
Fortunatamanete, il filesystem /proc come definito all'interno del kernel permette ai moduli di giocare con le strutture interne del VFS senza il bisogno di registrare un tipo di filesystem completamente nuovo. Ogni file all'interno di /proc può dichiarare le sue inode_operations e file_operations, ed è perciò in grado di sfruttare tutte le caratteristiche del VFS. L'interfaccia per la creazione di file /proc è abbastanza facile da poter essere presentata qui, senza andare troppo nel dettaglio. I file /proc dinamici vengono chiamati così perchè il loro numero di inode viene allocato dinamicamente al momento della creazione del file, invece di essere estratto da una tabella di inode o essere generato da un numero di blocco.
In questa parte dell'articolo costruiremo un modulo chiamato burp, che sta per ``Bella ed Utile Risorsa per Provare''. Non mostrerò qui nel testo tutto il codice del modulo in quanto la struttura interna di ciascun file che verrà creato non è direttamente collegata con il tema di questo articolo. L'intero modulo, burp.c, può comunque essere compilato e provato da chiunque abbia accesso come root su di una macchina Linux.



La struttura principale usata nella costruzione dell'albero dei file in /proc è struct proc_dir_entry: una di tali strutture è associata a ciascun file all'interno di /proc e viene usata per tenere traccia dell'albero dei file. Le operazioni readdir() e lookup() di default relative al filesystem utilizzano un albero di struct proc_dir_entry per restituire informazioni al processo nello spazio utente.



Il modulo burp, equipaggiato con le strutture necessarie, crea tre file: /proc/root è il dispositivo a blocchi associato alla partizione di root del sistema; /proc/insmod è un'interfaccia per caricare/scaricare i moduli senza bisogno di diventare root; /proc/jiffies legge il valore corrente del contatore dei jiffies (cioè il numero di interruzioni del clock a partire dall'avvio del sistema). Questi tre file non hanno nessun valore reale e servono solo a mostrare come vengono usate le file_operations e le inode_operations. Come si nota, burp è in effetti un ``Banale Utilizzo delle Risorse di Proc''. Per evitare che la trattazione diventi troppo noiosa, non descriverò qui i dettagli del caricamento/scaricamento del modulo: tali dettagli sono già stati descritti nei precedenti articoli del Pluto Journal.



La creazione e la distruzione di un file in /proc viene effettuata chiamanto le seguenti funzioni:
 proc_register_dynamic(struct proc_dir_entry *where,                       struct proc_dir_entry *self); proc_unregister(struct proc_dir_entry *where, int inode);  
In entrambe le funzioni, where è la directory a cui il nuovo file appartiene: burp utilizza &proc_root come argomento per specificare la root-directory del filesystem. La struttura self, d'altra parte, è dichiarata all'interno di burp.c per ciascuno dei tre file. La definizione di proc_dir_entry è riportata qui sotto.
 struct proc_dir_entry {         unsigned short low_ino;  /* inode number for the file */         unsigned short namelen;  /* lenght of filename */         const char *name;        /* the filename itself */         mode_t mode;             /* mode (and type) of file */         nlink_t nlink;           /* number of links (1 for files) */         uid_t uid;               /* owner */         gid_t gid;               /* group */         unsigned long size;      /* size, can be 0 if not relevant */         struct inode_operations * ops; /* inode ops for this file */         int (*get_info)(char *, char **, off_t, int, int);  /* read data */         void (*fill_inode)(struct inode *);  /* fill missing inode info */         struct proc_dir_entry *next, *parent, *subdir; /* internal use */         void *data;              /* used in sysctl */ };  
La parte ``sincrona'' di burp si riduce perciò a tre linee all'interno di init_module() e tre all'interno di cleanup_module(). Tutto il resto viene gestito dall'interfaccia VFS ed è ``event-driven'' per quanto un processo che accede ad un file può essere considerato un evento (si, so che questo modo di pensare le cose è eterodosso, e sconsiglio di usare queste espressioni in ambiti professionali o accademici).



Le tre linee in init_module() assomiglieranno dunque a: "proc_register_dynamic(&proc_root, &burp_proc_root);", mentre quelle in cleanup_module() saranno come "proc_unregister(&proc_root, burp_proc_root.low_ino);".
Il campo low_ino è qui il numero di inode per il file che viene rimosso da /proc, ed è stato dinamicamente assegnato a load-time.
Ma come risponderanno questi file all'azione dell'utente? Vediamo ognuno di essi indipendentemente.
  • /proc/root è un dispositivo a blocchi. Il suo `modo' deve perciò avere acceso il bit S_IBLK, le sue inode_operations dovranno essere quelle dei dispositivi a blocchi e il suo numero di dispositivo dovrà essere quello del filesystem root attuale. Siccome il numero di dispositivo associato all'inode non è parte di proc_dir_entry, il campo fill_inode deve essere usato. Il numero di dispositivo del filesystem root verrà estratto dalla tabella delle partizioni attualmente montate.
  • /proc/insmod è un file scrivibile: necessità perciò delle sue file_operations in modo da dichiarare la sua funzione di scrittura. Questo file dichiara perciò le sue inode_operations che puntano alle sue file_operations. Ogni volta che la sua funzione write() viene invocata, il file chiede a kerneld di caricare o scaricare il modulo il cui nome è stato scritto. Il file è scrivibile da chiunque, ma questo non è un gran problema in quanto caricare un modulo non significa accedere all'hardware che questo controlla, e cosa può essere caricato è ancora controllato da /etc/modules.conf, di proprietà di root.
  • /proc/jiffies è molto più facile: il file viene solo letto. La versione 2.0 e le più recenti del kernel offrono una interfaccia semplificata per i file in sola lettura: il puntatore a funzione get_info all'interno di proc_dir_entry, viene usato per richiedere il riempimento di una pagina di dati ogni volta che il file viene letto. Perciò, /proc/jiffies non ha bisogno di dichiarare le sue proprie file_operations o inode_operatins, ma usa semplicemente get_info(). Questa funzione chiama poi sprintf() per convertire il numero intero jiffies in una stringa.
La sessione mostrata qui sotto fa vedere come tali file appaiono e come due di esse funzionano. Il codice incluso successivamente mostra le tre strutture usate per dichiarare i file in /proc. Le strutture non sono state definite completamente in quanto il compilatore C riempie con degli zeri le strutture parzialmente definite senza per questo generare dei messaggi di warning (questa è una caratteristica intenzionale del compilatore).
 morgana% ls -l /proc/root /proc/insmod /proc/jiffies --w--w--w-   1 root     root            0 Feb  4 23:02 /proc/insmod -r--r--r--   1 root     root           11 Feb  4 23:02 /proc/jiffies brw-------   1 root     root       3,   1 Feb  4 23:02 /proc/root morgana% cat /proc/jiffies 0002679216 morgana% cat /proc/modules burp               1            0 morgana% echo isofs > /proc/insmod morgana% cat /proc/modules isofs              5            0 (autoclean) burp               1            0 morgana% echo -isofs > /proc/insmod morgana% cat /proc/jiffies 0002682697 morgana%  

 struct proc_dir_entry burp_proc_root = {     0,                  /* low_ino: the inode -- dynamic */     4, "root",          /* len of name and name */     S_IFBLK | 0600,     /* mode: block device, r/w by owner */     1, 0, 0,            /* nlinks, owner (root), group (root) */     0, &blkdev_inode_operations,  /* size (unused), inode ops */     NULL,               /* get_info: unused */     burp_root_fill_ino, /* fill_inode: tell your major/minor */     /* nothing more */ };  struct proc_dir_entry burp_proc_insmod = {     0,                  /* low_ino: the inode -- dynamic */     6, "insmod",        /* len of name and name */     S_IFREG | S_IWUGO,  /* mode: REGular, Write UserGroupOther */     1, 0, 0,            /* nlinks, owner (root), group (root) */     0, &burp_insmod_iops, /* size - unused; inode ops */ };  struct proc_dir_entry burp_proc_jiffies = {     0,                  /* low_ino: the inode -- dynamic */     7, "jiffies",       /* len of name and name */     S_IFREG | S_IRUGO,  /* mode: regular, read by anyone */     1, 0, 0,            /* nlinks, owner (root), group (root) */     11, NULL,           /* size is 11; inode ops unused */     burp_read_jiffies,  /* use "get_info" instead */ };  
Il modulo è stato compilato e provato su di un PC, un Alpha ed una Sparc, tutte con un kernel 2.0.x
L'implementazione attuale di /proc ha altre interessanti caratteristiche da offrire, la più interessante delle quali è l'implementazione di sysctl(). L'idea è così interessante che non trova spazio qui, e sarà l'argomento di un articolo in un altro numero del Pluto Journal.

Esempi interessanti

La mia presentazione è finita, ma ci sono vari posti in cui trovare del codice interessante riguardante il VFS. Implementazioni particolarmente interessanti sono le seguenti:
  • Ovviamente, il filesystem /proc: è abbastanza semplice da capire in quanto non è critico per le prestazioni e nemmeno troppo pieno di cose, tranne che per l'implementazione di sysctl.
  • Il filesystem ``umsdos'': anche questo è parte del kernel ufficiale, e si appoggia sul filesystem msdos. Questo modulo implementa soltanto alcune delle operazioni del VFS per aggiungere nuove possibilità ad un filesystem vecchio ed inefficiente.
  • Il modulo ``userfs'': disponibile sia da tsx-11 che da sunsite sotto ALPHA/userfs. La versione 0.9.3 funziona con Linux-2.0. Il modulo definisce un nuovo filesystem che usa programmi esterni per recuperare i dati. Applicazioni interessanti sono il filesystem FTP ed un filesystem in sola lettura per montare i file tar compressi. Nonostante l'utilizzo di programmi per ottenere dati da un filesystem sia molto pericoloso e possa portare ad un blocco del sistema, l'idea è abbastanza interessante.
  • ``supermount'': il filesystem è disponibile su sunsite e mirror. Questo tipo di filesystem è in grado di montare dispositivi rimuovibili come i dischetti e i CD e gestisce la rimozione dei dischi senza costringere l'utente a smontare e rimontare la periferica. Il modulo lavora controllando un altro tipo di filesystem facendo in modo di tenere il dispositivo smontato quando non viene utilizzato. L'operazione è trasparente all'utente.
  • ``ext2'': il filesystem extended-2 è lo standard per Linux da qualche anno a questa parte. È codice molto difficile ma che val la pena di leggere per chi è interessato a vedere come un filesystem reale è implementato. Questo modulo contiene anche degli agganci per interessanti trovate riguardanti la sicurezza, come i flag di file immutabile e append-only. Se un file è immutabile o append-only può essere cancellato solo quando il calcolatore è in single-user, ottenendo perciò una certa sicurezza dagli attacchi via rete.

Ricerca personalizzata

Se ti è piaciuto l'articolo, iscriviti al feed per tenerti sempre aggiornato sui nuovi contenuti del blog:



Trovato questo articolo interessante? Condividilo sulla tua rete di contatti in Twitter, sulla tua bacheca su Facebook, in Linkedin, Instagram o Pinterest. Diffondere contenuti che trovi rilevanti aiuta questo blog a crescere. Grazie!

LINKEDIN