Vai alla Home Page About me Courseware Federica Living Library Federica Federica Podstudio Virtual Campus 3D La Corte in Rete
 
Il Corso Le lezioni del Corso La Cattedra
 
Materiali di approfondimento Risorse Web Il Podcast di questa lezione

Clemente Galdi » 14.System call per la gestione di file e directory


Scrittura su disco

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);

  • sync richiede il “commit” del buffer ma NON aspetta la scrittura.
    • Il commit corrisponde al solo invio delle informazioni al device ma non alla memorizzazione delle stesse.
  • fsync e fdatasync ritornano solo quando la scrittura è stata effettuata
    • Se il disco ha una cache interna, anche in questo caso le informazioni potrebbero non essere scritte su disco
    • È possibile utilizzare il flag O_SYNC per forzare la write ad attendere la scrittura su disco.

Ottenere informazioni su un file

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:

  • Le system call stat ed lstat prendono in input il pathname del file come primo parametro
  • fstatprende in input il file descriptor del file.
    • Chiaramente, questa system call è utilizzabile solo quando il file è già stato aperto attraverso la system call open.

Tutte le system memorizzano la struttura stat contenente le informazioni sul file in una struttura il cui puntatore deve essere passato come parametro.

La struttura stat

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 */
};

La struttura stat (segue)

I sistemi Unix definiscono sette tipi di file:

  • Regular file: Contiene “dati” di qualche tipo. L’interpretazione è lasciata all’applicazione.
    • Attenzione: Anche gli eseguibili sono “regular file”
  • Directory file:
    • Contiene nomi e “puntatori” a file.
    • Come tutti I file, i processi possono leggere in questi file se le protezioni lo permettono
    • È necessario utilizzare system call specifiche per la lettura e scrittura su directory.
  • Block Special File:
    • Forniscono I/O bufferizzato a device (e.g., dischi)
    • La dimensione del buffer e’ “fissa” (per device)
  • Character Special File:
    • Forniscono I/O NON bufferizzato a device.
    • La dimensione del “messaggio” può essere variabile.
  • FIFO: (o named pipe) Utilizzati per comunicazione tra processi.
  • Socket: Utilizzati per comunicazione tra processi su rete o “su una stessa macchina”…
  • Symbolic Link: Un puntatore ad un file.

Identificazione del tipo 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:

  • S_ISREG(m): Vero se file regolre;
  • S_ISDIR(m): Vero se directory;
  • S_ISCHR(m): Vero se character device;
  • S_ISBLK(m): Vero se block device;
  • S_ISFIFO(m): Vero se fifo;
  • S_ISLNK(m): Vero se link simbolico;
  • S_ISSOCK(m): Vero se Socke.

Identificazione del tipo di file: Mostra codice

Esempi di esecuzione e relativi output: Mostra codice

Identificazione del tipo di file (segue)

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:

  • P: può essumere I valori R, W o X, rispettivmente per Lettura, Scrittura ed Esecuzione
  • WHO: può assumere I valori USR, GRP, OTH, rispettvamente per proprietario, gruppo ed altri.

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

L’accesso ai file

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:

  • Real user e Real group id: Il “vero” proprietario/gruppo del processo.
  • Effective user, Effective group id:
    • Il proprietario e gruppo “temporanei” del processo. Questi id vengono utilizzati per il controllo d’accesso ai file.
    • Normalmente Real user group id sono identici ad Effective user/group id.
  • Supplementary group Ids: Possibili gruppi per il processo
  • set-user ID e set-group ID: Possibili “id alternativi”

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.

L’accesso ai file (segue)

Il sistema operativo concede o nega l’accesso ad un file in base al valore dell’effective user/group id, utilizzando il seguente algoritmo:

  • Se l’effective user è 0 (superuser): concedi l’accesso indipendentemente dalle protezioni.
  • Se l’effective user ID del processo è uguale all’owner ID del file.
    • Controlla i permessi per l’utente ed, in caso, nega l’accesso
  • Se l’effective group ID del processo è uguale al group ID del file
    • Controlla i permessi del gruppo ed, in caso, nega l’accesso
  • Altrimenti…Controlla i permessi per gli “altri”.

L’accesso ai file (segue)

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:

  • R_OK, W_OK, X_OK: Rispettivamente, accesso in Lettura, scrittura od esecuzione
  • F_OK: Esistenza del file.

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.

Accesso ai File

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.

Modifica dei permessi di accesso ai 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:

  • Set-uid, set-gid e sticky bit: S_ISUID, S_ISGID, S_ISVTX
  • Permessi per il proprietario: S_IRWXU, S_IRUSR, S_IWUSR, S_IXUSR.
  • Permessi per il gruppo: S_IRWXG, S_IRGRP, S_IWGRP, S_IXGRP
  • Permessi per gli altri: S_IRWXO, S_IROTH, S_IWOTH, S_IXOTH

Un esempio di utilizzo di access: Mostra codice

Modifica del proprietario e del gruppo

È 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:

  • (a) l’effective user id del processo è pari all’owner id del file e
  • (b) il parametro group e’ uguale all’effective GID del processo o ad uno dei gruppi “alternativi”

I campi “dimensione”

La struttura stat contiene diversi campi che indicano “dimensioni” diverse per il file cui sono associate. In particolare:

  • Il campo st_sizedella struttura struct contiene la dimensione in byte del file. Ha senso solo per file regolari, directory e link per cui assume i seguenti significati:
    • File regolari: Numero di byte contenuti nel file;
    • Directory: A seconda dello specifico sistema Unix, è un multiplo di 16 o 512 ed indica (un multiplo del) numero di “blocchi” (da 16 o 512 byte) necessari per contenere l’elenco dei file nella directory con le allegate informazioni accessorie.
    • Link: Il numero di byte del pathname del file puntato dal link.
  • Il campo st_blksizecontiene la dimensione “ottimale” del blocco per operazioni di l/O
    • È utilizzato dal file system per l’ottimizzazione delle performance di I/O.
  • Il campo st_blocksindica il numero di blocchi allocati al file.
    • Ad ogni file sono sempre allocati un numero di blocchi tali da contenere almeno st_size byte.
    • Il sistema operativo, per ragioni di performance, può pre-allocare al file un certo numero di blocchi che saranno utilizzati quando necessario. Questa strategia consente di eliminare i tempi di attesa dovuti all’allocazione durante le operazioni di I/O.

File truncation

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.

Il file system di Unix

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:

  • corrisponde esattamente e solo un I-node; la maggior parte delle informazioni ritornate dalla stat sono contenute nell’I-node.
  • possono corrispondere zero o più data block. I data-block memorizzano il contenuto effettivo del file. All’aumento delle informazioni contenute nel file corrisponde un aumento dei data block allocati al file.

Ogni gruppo include:

  • Un Header, contenente l’informazione sulla partizione sul gruppo specifico.
  • Una bitmap degli I-node in cui, per ogni I-node nel gruppo, un bit indica se è libero o già utilizzato.
  • Una bitmap dei data-block, in cui, per ogni data block nel gruppo, un bit indica se è libero o già utilizzato.
  • Un array di I-node.
  • Un array di data-block.
Schema di una gruppo all’interno di una partizione.

Schema di una gruppo all'interno di una partizione.


Il file system di Unix

Ad ogni directory sono associati uno o più data block, detti directory block, contenenti, per ogni file, almeno le seguenti informazioni:

  • Il nome del file
  • Il numero di inode ad esso associato.

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.

Relazioni tra I-nodes, directory blocks e data blocks in una partizione Unix.

Relazioni tra I-nodes, directory blocks e data blocks in una partizione Unix.


Il file system di Unix (segue)

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

  • Un puntatore dalla directory “padre”;
  • Un puntatore dalla directory “.”
Relazioni tra I-nodes, directory blocks e data blocks in una partizione Unix.

Relazioni tra I-nodes, directory blocks e data blocks in una partizione Unix.


link/unlink

È 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.

Rename

È 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:

  • Se oldpath NON è una directory, newpath NON può essere una directory.
  • Se newpath esiste, viene cancellato.
  • Se oldpath è una directory
    • newpath deve essere una directory vuota
    • newpath non puo’ contenere oldpath come prefisso
      • Non è possibile rinominare /usr come /usr/bin
  • Se oldpath o newpath è un link, viene processato il link (non il file puntato dal link)

I link simbolici

I link simbolici o soft link consentono di:

  • Creare collegamenti tra entità su filesystem diversi
  • La creazione di link a directory da parte di utenti.

La directory entry per un link contiene:

  • Nel caso di hard link: Il numero di inode del file puntato.
  • Nel caso I soft link: Il nome del file puntato.

È 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);

  • symlink crea un link simbolico tra oldpath e newpath.Non è richiesto che oldpath esista.
  • readlink: legge il “nome del file” a cui il link punta.

La creation mask

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

Creazione e cancellazione di directory

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);

  • Crea una directory vuota con i permessi di accesso indicati (e modificati in base alla creation mask)
  • La directory contiene le sottodirectory “.” e “..”
  • Errore comune: Settare solo i permessi di lettura e scrittura.
    • È necessario settare almeno un permesso di “esecuzione” per le directory.

int rmdir(const char *pathname);

  • Cancella una directory solo se questa e vuota
    • I.e., la directory contiene solo le sottodirectory “.” e “..”
    • La directory viene effettivamente cancellata se nessun processo ha la directory aperta.
    • Dopo l’esecuzione della rmdir, non è possibile creare nuovi file nella directory.

Entrambe le system call ritornano il valore zero se l’operazione è stata eseguita con successo, -1 altrimenti.

Lettura delle directory

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:

  • ino_t d_ino: I-node number
  • char d_name[NAME_MAX+1]: nome file.

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);

Lettura delle directory (segue)

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);

Un esempio di scansione della directory

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

Telldir e seekdir: un esempio

Esempi di utilizzo delle system call telldir e seekdir: Mostra codiceMostra 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.

I materiali di supporto della lezione

W.R. Stevens, S.A. Rago - Advanced Programming in the Unix Environment

Capitolo 3 (3.13), Capitolo 4 (4.1-4.17, 4.21).

  • Contenuti protetti da Creative Commons
  • Feed RSS
  • Condividi su FriendFeed
  • Condividi su Facebook
  • Segnala su Twitter
  • Condividi su LinkedIn
Progetto "Campus Virtuale" dell'Università degli Studi di Napoli Federico II, realizzato con il cofinanziamento dell'Unione europea. Asse V - Società dell'informazione - Obiettivo Operativo 5.1 e-Government ed e-Inclusion

Fatal error: Call to undefined function federicaDebug() in /usr/local/apache/htdocs/html/footer.php on line 93