Lucrarea 7
Apeluri sistem avansate pentru lucrul cu fisiere

 

1. Scopul lucrării

Pe lângă apelurile sistem de bază folosite în realizarea operatiilor de I/E descrise în lucrarea anterioara exista câteva apeluri mai speciale. Lucrarea are ca scop prezentarea apelurilor sistem care modifică atributele unui fisier.

2. Consideratii teoretice

2.1. Apelul sistem STAT si FSTAT

Pentru a obtine informatii detaliate despre un fisier nedeschis, respectiv deschis se folosesc apelurile sistem stat si fstat.

#include <sys/types.h>

#include <sys/stat.h>

int stat( const char *path, struct stat *buf);

int fstat( int df, struct stat *buf);

Returnează 0 în caz de succes, -1 în caz de eroare.

În SVR4 exista si apelul lstat care furnizează informatii despre legaturi simbolice.

Dacă se precizează un nume de fisier, prin argumentul path, sau un descriptor de fisier asociat unui fisier deschis, prin argumentul df, apelul stat respectiv fstat completează o structură de informatii despre acel fisier. Structura referita de buf e definita în fisierul antet sys/stat.h si contine câmpurile:

struct stat {

mode_t st_mode; /* tip fisier & drepturi */
ino_t st_ino; /* i-node */
dev_t st_dev; /* număr de dispozitiv (SF) */
nlink_t st_nlink; /* numărul de legaturi */
uid_t st_uid; /* ID proprietar */
gid_t st_gid; /* ID grup */
off_t st_size; /* dimensiune fisiere ordinare */
time_t st_atime; /* timpul ultimului acces */
time_t st_mtime; /* timpul ultimei modificări */
time_t st_ctime; /* timpul schimbării stării */
dev_t st_rdev; /* număr dispozitiv fisiere speciale*/
long st_blksize; /* dimensiunea optima a blocului de I/E */
long st_blocks; /* număr de blocuri alocate de 512 octeti*/
};

Ultimele două câmpuri exista în versiunile SVR4 si 4.3+BSD si nu sunt definite de standardul POSIX.

Comanda Unix care foloseste cel mai des acest apel este ls.

Declaratiile de tipuri pentru membrii structurii se găsesc în fisierul sys/types.h.

Tipul de fisier codificat în câmpul st_mode poate fi determinat folosind macrourile:
 
Macro 
Semnificatie
S_ISREG() Fisier obisnuit (REGular file). 
S_ISDIR() Fisier director. 
S_ISCHR() Dispozitiv special în mod caracter. 
S_ISBLK() Dispozitiv special în mod bloc. 
S_ISFIFO() Fisier pipe sau fifo. 
S_ISLNK() Legătura simbolica. 

Versiuni mai vechi de Unix nu oferă aceste macro definiri S_ISxxx. Pentru a le defini trebuie realizat un SI pe biti (&) intre câmpul st_mode si masca S_IFMT si comparat rezultatul cu una dintre valorile S_IFxxx. De exemplu, macro-ul S_ISDIR e definit astfel:

#define S_ISDIR( mode) ((mode) & S_IFMT) == S_IFDIR)

Bitii suid si sgid au fost prezentati intr-o sectiune anterioara. Rolul bitului suid era de a pozitiona IDU efectiv al procesului (care executa programul) la proprietarul fisierului (câmpul st_uid). în acest mod daca proprietarul unui fisier este altcineva ( de exemplu superuser) si bitul suid este pozitionat, pe parcursul executiei programului ca proces, cel care-l executa va detine privilegiile altui utilizator ( de exemplu, privilegii de superuser). Analog actionează bitul sgid pentru grup.

În cazurile obisnuite când bitul suid nu e pozitionat la executia unui program IDU si GIDU efectiv ai procesului sunt egali cu IDU respectiv GIDU real.

Apelul stat returnează valoarea celor doi biti în câmpul st_mode. Valoarea lor poate fi testata printr-o operatie SI pe biti intre st_mode si constantele simbolice S_ISUID respectiv S_ISGID.

În mod analog se testează si bitul sticky dar cu constanta simbolica S_ISVTX.

În cuvântul st_mode sunt precizate si drepturile de acces asupra fisierului (cei noua biti). Constantele care definesc valorile lor au fost prezentate în lucrarea anterioara.

2.1.1. Proprietarul unui fisier sau director nou

IDU al unui fisier nou este pozitionat la valoarea UID efectiv al procesului. Standardul POSIX permite ca GIDU noului fisier sa devină GIDU efectiv al procesului sau GIDU directorului în care fisierul a fost creat.

2.1.2. Dimensiunea unui fisier

Câmpul st_size al structurii stat contine dimensiunea (in octeti) a fisierului. Acest câmp contine informatie utila pentru fisiere obisnuite, directoare si legaturi simbolice.

SVR4 defineste dimensiunea unui pipe ca fiind numărul de octeti care pot fi cititi din pipe.

Pentru un fisier obisnuit o dimensiune 0 semnifica un fisier gol, prima citire indicând EOF.

Pentru un director, dimensiunea este un multiplu de 16.

Pentru o legătura simbolica, dimensiunea semnifica numărul de octeti din numele fisierului. De exemplu pentru linia:

lrwxrwxrwx 1 root 7 Apr 25 19:07 bin -> usr/bin

dimensiunea 7 este lungimea sirului "usr/bin" (legătura simbolică nu contine marcatorul de sfârsit de sir C).

2.1.3. Contorul de legaturi

Doua intrări director pot sa refere acelasi i-node. Fiecare i-node are un contor de legaturi care contine numărul de intrări director care refera i-node-ul. Doar când acest contor este 0 fisierul poate fi sters. Din acest motiv un apel sistem unlink asupra unui fisier nu înseamnă neapărat stergerea blocurilor asociate fisierului. În structura stat contorul de legaturi este continut de membrul st_nlink.

Pentru un director frunza contorul de legaturi este întotdeauna 2. Valoarea aceasta rezulta din suma legaturilor spre acest director: intrarea director care contine acest director si intrarea . din director. Orice director creat în directorul curent cauzează incrementarea contorului de legaturi a directorului curent.

2.1.4. Legătura simbolica

Legătura simbolica a fost introdusa odată cu 4.2 BSD si SVR4. Ea este un pointer indirect la un fisier, spre deosebire de legătura hard care refera direct i-node-ul fisierului. Legătura simbolica a fost introdusa pentru a elimina dezavantajele legăturii hard: a) legătura hard impune acelasi sistem de fisier si b) numai superuse-ul putea crea o legătura hard la un director. Legătura simbolica este utila la mutarea unui fisier sau a unui subarbore întreg intr-un alt subarbore în structura ierarhica arborescenta.

Nu toate apelurile sistem care contin ca argument un nume de fisier accepta numele de fisier precizat printr-o legătura simbolica. în acest sens se recomanda consultarea bibliografiei.

Legătura simbolica încurca parcurgerea recursia a unei structuri arborescente întrucât aceasta poate introduce bucle infinite.

2.1.5. Fisiere speciale

Fiecare sistem de fisiere este identificat prin numărul de dispozitiv major si minor. Accesul la aceste doua numere este realizat, în majoritatea implementărilor, prin macrourile major si minor. Dacă acestea exista modul de reprezentare interna a acestor doua numere în obiectul de tip dev_t nu contează.

Versiuni mai vechi de Unix memorează numărul de dispozitiv pe 16 biti (8+8). SVR4 foloseste 32 de biti (14+18)

Valoarea st_dev pentru un nume de fisier este numărul de dispozitiv al sistemului de fisiere care contine numele fisierului si i-node-ul corespunzător.

Câmpul st_rdev contine o valoare numai pentru fisierele speciale care deservesc dispozitive periferice în mod caracter sau bloc. Aceasta valoare reprezintă numărul de dispozitiv pentru dispozitivul curent.

2.2. Apelul sistem ACCESS

La accesul unui fisier prin apelul sistem open, nucleul realizează accesul în functie de IDU si GIDU efectiv. Exista situatii când un proces testează drepturile de acces bazându-se pe IDU si GDU real. O situatie în care acest lucru e util este când un proces se executa cu alte drepturi folosind bitul suid sau bitul sgid. Cu toate ca procesul poate avea drept de superuser în timpul executiei, uneori este nevoie de a verifica daca proprietarul real poate accesa fisierul. Apelul access permite testarea IDU sau GIDU real al unui proces. Sintaxa apelului sistem este:

#include <unistd.h>

int access( const char *path, int mod);

Returnează 0 daca cererea e valida, -1 în caz contrar.

Argumentul mod este o combinatie SI pe biti intre:

R_OK Test peste dreptul de citire.

W_OK Test peste dreptul de scriere.

X_OK Test peste dreptul de executie.

F_OK Test de existenta fisier.

Dacă IDU real al procesului si IDU al proprietarului caii path se potrivesc, atunci mod este comparat cu bitii de drepturi de acces ai proprietarului. Dacă IDU real are fiecare drept specificat de mod, apelul sistem access returnează 0, în caz contrar -1.

Dacă cei doi IDU nu se potrivesc, dar se potrivesc GIDU sunt testati bitii corespunzători drepturilor de acces ai grupului. Dacă nici acestia nu se potrivesc se testează bitii cu drepturi de acces pentru altii.

Dacă mod este F_OK, acces testează daca exista calea path. Fiecare director de pe cale trebuie sa aibă dreptul de acces (x).

2.3. Apelul sistem UMASK

Pentru a îmbunătătii securitatea în operatiile de creare a fisierelor, sistemul de operare Unix oferă o masca implicita cu drepturi de acces. Masca este formata dintr-un număr pe noua biti si serveste la stergerea acelor drepturi, în apelurile sistem ulterioare creat, open si mknod, pentru care masca a fost pozitionata. Altfel spus, bitii pozitionati pe 1 din masca invalidează bitii corespunzători din argumentul care precizează drepturile de acces la apelurile de creare a fisierelor.

Masca nu afectează apelul sistem chmod, astfel încât procesele au o posibilitatea de a-si fixa drepturile de acces indiferent de masca. Sintaxa apelului este:

#include <sys/types.h>

#include <sys/stat.h>

mode_t umask( mode_t mask);

Returnează valoarea măstii anterioare.

Efectul apelului este explicat de exemplul:

main() /* test umask */

{

int fd;

umask(022);

if (( fd=creat("temp", 0666))==-1)

err_sys("creat");
system("ls -l temp");

}

Rezultatul afisat: -rw-r--r-- 1 adi k 1 Apr 12 13:07 temp

Se poate observa ca cu toate ca dreptul de acces fixat asupra fisierului temp prin creat era de citire si scriere pentru toti, bitii de scriere pentru grup si altii au fost stersi.

Toti utilizatorii primesc la intrarea în sistem o masca implicita, care este 022 (in octal) pe majoritatea sistemelor. Interpretorul de comenzi are o comanda interna umask ( ca si cd), care permite modificarea unei măsti. Aceasta se precizează de regula în fisierul de comenzi de intrare în sistem (.profile) daca masca implicita din sistem nu este potrivita.

Deoarece fiecare proces are o masca si orice combinatie de noua biti este corecta, umask întoarce vechea masca.

În locul numărului octal care precizează drepturile de acces se pot folosi constantele simbolice definite în sys/stat.h si prezentate la apelul sistem stat.

2.4. Apelul sistem CHMOD

Pentru a modifica drepturile de acces asupra unui fisier existent se foloseste apelul:

#include <sys/types.h>

#include <sys/stat.h>

int chmod( const char *path, mode_t mod);

Returnează 0 în caz de reusită, -1 în caz contrar.

Apelul chmod modifica drepturile de acces ale fisierului specificat prin calea path în conformitate cu drepturile specificate de argumentul mod.

Pentru a modifica drepturile de acces, IDU efectiv al procesului trebuie sa fie egal cu cel al proprietarului fisierului, sau procesul trebuie sa aibă drepturi de superuser.

Argumentul mod este specificat prin una dintre constantele simbolice definite în sys/stat.h. Efectul cumulat al lor se poate obtine folosind operatorul SAU pe bit:
 
Mod 
Descriere 
S_ISUID suid la executie 
S_ISGID sgid la executie 
S_ISVTX text salvat ( bitul sticky
S_IRWXU  Drept de citire, scriere si executie pentru proprietar obtinut din: S_IRUSR | S_IWUSR | S_IXUSR. 
S_IRWXG  Drept de citire, scriere si executie pentru grup, obtinut din: S_IRGRP | S_IWGRP | S_IXGRP. 
S_IRWXO Drept de citire, scriere si executie pentru altii, obtinut din: S_IROTH | S_IWOTH | S_IXOTH. 

Drepturile de acces asupra unui fisier pot fi modificate direct sau relativ la drepturile de acces curente. în ultimul caz este necesar apelul sistem stat.

Apelul chmod sterge automat doi biti din drepturile de acces în situatiile:

a) Dacă se încearcă stergerea bitului sticky al unui fisier obisnuit fără a avea drept de superuser, bitul sticky devine 0. Acest lucru nu permite utilizatorilor sa umple zona de swap.

b) Este posibil ca GIDU-ul unui fisier creat sa fie al unui grup căruia procesul apelant sa nu apartină. Dacă GIDU fisierului nou creat nu este egal cu GIDU efectiv al procesului sau procesul nu are drept de superuser si bitul suid este sters. Acest lucru nu permite utilizatorului sa creeze un fisier cu bitul suid al cărui proprietar sa fie un grup la care utilizatorul nu apartine.

2.5. Apelul sistem CHOWN

Apelul este utilizat în scopul modificării proprietarului (IDU) si grupului (GIDU) căruia ii apartine un fisier.

#include <sys/types.h>

#include <unistd.h>

int chown( const char *path, uid_t owner, gid_t grp);

Returnează 0 în caz de succes -1 în caz de eroare.

Apelul schimba proprietarul si grupul fisierului precizat de argumentul path, la noul proprietar specificat de argumentul owner si la noul grup specificat de argumentul grp.

Se poate efectua aceasta schimbare daca procesul este superuser sau daca procesul nu este superuser în cazurile:

  • Procesului în cadrul căruia este realizat acest apel este si proprietarul fisierului în cauza ( IDU efectiv este egal cu IDU fisierului).
  • Dacă owner este egal cu IDU fisierului si grp este egal cu GIDU efectiv al procesului sau cu unul din ID suplimentari de grup.
Altfel spus nu se poate schimba proprietarul (IDU) fisierelor altor utilizatori, dar se poate schimba proprietarul grupului (GIDU) de fisiere proprii, dar numai pentru acele grupuri din care faceti parte.

Dacă acest apel este realizat dintr-un proces fără drept de superuser, la revenirea cu succes din apel bitii suid si sgid sunt invalidati (0).

2.6. Apelul sistem UTIME

În structura stat exista trei membri care se refera la timp, conform tabelului de mai jos.


 
Câmp
Descriere 
Operatie
st_atime  Ultimul acces la datele fisierului  citire 
st_mtime  Ultima modificare a datelor fisierului  scriere 
st_ctime  Schimbare stării i-node-ului  chmod, chown

Fig.1. Cele trei valori de tip asociate unui fisier.

Diferenta intre timpul de modificare si de schimbare a stării consta în faptul ca primul se refera la momentul în care continutul fisierului a fost modificat, iar al doilea la momentul în care informatia din i-node a fost modificata. Acest lucru se datorează faptului ca informatia din i-node este memorata separat de continutul fisierului. Apeluri sistem care modifica i-node-ul sunt cele care modifica drepturile de acces asupra unui fisier, cele care schimba IDU, numărul de legaturi, etc.

Sistemul nu retine ultimul acces la i-node. Acesta este motivul pentru care apelurile sistem access si stat nu schimba nici unul dintre acesti timpi.

Timpul de acces este folosit de administratorul sistemului pentru a sterge fisierele care nu au fost demult accesate. De exemplu, stergerea tuturor fisierelor a.out si core din sistem, care nu au fost accesate de o săptămâna.

Comanda Unix ls afisează sau sortează doar unul dintre timpi. La folosirea optiunii -l sau -t este afisat timpul de modificare a fisierului. Optiunea -u permite afisarea timpului de acces, iar optiunea -c permite afisarea timpului de schimbare a stării i-node-ului.

Timpul de acces si timpul de modificare a unui fisier de orice tip pot fi schimbat prin apelul:

#include <sys/types.h>

#include <utime.h>

int utime( const char *path, const struct utimbuf *times);

Returnează 0 în caz de succes, -1 în caz contrar.

Structura folosita de acest apel este:

struct utimbuf {

time_t actime; /* timp de acces */
time_t modtime; /* timp modificare */
}

Nota: Denumirea acestor câmpuri în versiuni Unix mai vechi era: atime si mtime.

Tipul time_t este de fapt tipul long si timpul este în forma standard calendar-timp si contorizează secundele scurse începând cu data de 1 ianuarie 1970, care este considerata data referintă.

Rezultatul apelului depinde de valoarea argumentului times:

  • Dacă acesta este NULL, timpii de acces si modificare sunt actualizati la timpul curent. Pentru a realiza acest lucru IDU procesului trebuie sa fie egal cu ID proprietarului fisierului sau procesul trebuie sa aibă drept de scriere în fisier.
  • Dacă acesta este diferit de zero timpii de acces si modificare sunt actualizati la valorile din structura referita de times. în acest caz IDU efectiv al procesului trebuie sa fie egal cu ID proprietarului fisierului, sau procesul sa fie superuser.
Câmpul st_ctime este actualizat automat de acest apel.

3. Aplicatii

3.1. Sa se scrie un program C care sa afiseze pentru fiecare nume de fisier primit ca argument în linia de comanda tipul sau.

# ft - File Type - Laborator SO Unix - (C) 1995 K.

#include <sys/types.h>

#include <sys/stat.h>

#include "hdr.h"

int main( int argc, char *argv[])

{

int i;

struct stat buf;

char *p;

for ( i=1; i<argc; i++) {

printf("%s: ", argv[i]);
if ( lstat( argv[i], &buf) < 0) {
err_ret("lstat error");
continue;
}

if ( S_ISREG( buf.st_mode)) p="regular";

else if ( S_ISDIR( buf.st_mode)) p="director";
else if ( S_ISCHR( buf.st_mode)) p="special pe caracter";
else if ( S_ISBLK( buf.st_mode)) p="special pe bloc";
else if ( S_ISFIFO( buf.st_mode)) p="fifo";
#ifdef S_ISLNK
else if ( S_ISLNK( buf.st_mode)) p="legatura simbolica";
#endif

#ifdef S_ISSOCK

else if ( S_ISSOCK( buf.st_mode)) p="socket";
#endif
else p=" *** tip necunoscut ***";
printf("%s\n", p);
}

exit(0);

}

Executia acestui program pe un calculator cu sistem Linux afisează:

$ft /etc /dev/tty /dev/sda1 /root/linux /dev/printer

err.c: regular

/etc: director

/dev/tty: special pe caracter

/dev/sda1: special pe bloc

/root/linux: legătura simbolica

/dev/printer: socket

3.2. Sa se scrie un program care afisează toate intrările dintr-un director, inclusiv din subarbori daca acestia exista.

# pd.c - Print Directory - Laborator SO Unix - (C) 1995 K.

#include <sys/types.h>

#include <sys/stat.h>

#include <sys/dir.h>

char *malloc();

main( int argc, char *argv[])

{

if ( argc != 2)

err_quit("Utilizare: %s <cale>\n", argv[0]);
printf("%s\n", argv[1]);

rec( argv[1]);

}

rec( char *dirpath)

{

struct stat stbuf;

struct direct drbuf;

int i, fd;

char leaf[DIRSIZ+1], *newpath;

off_t drsize;

if ( stat( dirpath, &stbuf) < 0)

return( 1);
if (( stbuf.st_mode & S_IFMT) != S_IFDIR)
return( 1);
if (( fd = open(dirpath, 0)) < 0) {
printf("Eroare la citire %s\n", dirpath);
return(1);

}

drsize = stbuf.st_size;

for ( i = 0; i < (drsize / sizeof( struct direct )); i++) {

printf("%d", i);
if (( read( fd, &drbuf, sizeof( struct direct ))) !=
sizeof(struct direct )) {
printf("Eroare la citire %s\n", dirpath);
return(1);
}

if ( drbuf.d_ino) {

strncpy( leaf, drbuf.d_name, DIRSIZ);
newpath = malloc(strlen(dirpath) + strlen(leaf) + 1);
if ( strcmp( dirpath, "/") == 0)
sprintf(newpath, "%s%s", dirpath, leaf);
else
sprintf(newpath, "%s/%s", dirpath, leaf);
printf("%s\n", newpath);
if ( stat(newpath, &stbuf) < 0)
continue;
if ((stbuf.st_mode & S_IFMT) == S_IFDIR) {
if ( strcmp(leaf,".")==0 || strcmp(leaf,"..")==0)
continue;
close( fd);
rec( newpath);
fd = open( dirpath, 0);
lseek( fd, (long)((i+1)* sizeof( struct direct)),0);
}

free( newpath);

}

}

close(fd);

return(0);

}

3.3. Sa se scrie un program care trunchează lungimea fisierelor pe care le primeste ca argumente în linia de comanda, dar nu modifica timpul de acces si timpul de modificare.

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <utime.h>

#include "hdr.h"

int main( int argc, char *argv[])

{

int i;

struct stat bufst;

struct utimbuf buftimp;

for ( i=1; i<argc; i++) {

if ( stat( argv[i], &bufst) < 0) {
err_ret("%s: Eroare stat", argv[i]);
continue;
}

if ( open( argv[i], O_RDWR | O_TRUNC) < 0) {

err_ret("%s: Eroare open", argv[i]);
continue;
}

buftimp.actime=bufst.st_atime;

buftimp.modtime=bufst.st_mtime;

if ( utime( argv[i], &buftimp) < 0) {

err_ret("%s: Eroare utime", argv[i]);
continue;
}

}

exit(0);

}

4. Probleme propuse pentru rezolvare

4.1. Sa se scrie un program care copiază continutul unui fisier în alt fisier. în cazul în care cel de-al doilea fisier exista se va afisa un mesaj de validare. în functie de răspuns fisierul este sau nu suprascris.

4.2. Ce realizează programul ?

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include "hdr.h"

int main( void)

{

umask(0);

if ( creat("unu", S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) < 0) err_sys("creat unu");

umask( S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if ( creat("doi", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)< 0)
err_sys("creat doi");
system("ls -l unu doi");

exit(0);

}

4.3. Pentru fisierele unu si doi comanda ls -l a afisat:

-rw-r--r-- 1 k 0 nov 7 13:00 unu

-rw-rw-rw- 1 k 0 nov 7 13:00 doi

Cum se modifica informatiile afisate de comanda ls -l după executia programului:

#include <sys/types.h>

#include <sys/stat.h>

#include "hdr.h"

int main( void)

{

struct stat bufstat;

if ( stat("unu", &bufstat) < 0)

err_sys("Eroare stat unu");
if ( chmod("unu",(bufstat.st_mode & ~S_IXGRP) | S_ISGID)< 0)
err_sys("Eroare chmod unu");
if ( chmod("doi", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)< 0)
err_sys("Eroare chmod doi");
exit(0);

}

4.4. Cum se poate modifica doar unul dintre timpi folosind utime?

4.5. Sa se scrie un program rmd care sterge un director cu tot ce contine acesta.

4.6. Sa se scrie un program mov similar comenzii Unix mv. Programul se poate apela în formele:

move numeFisOld numeFisNew

move numeFis numeDir

move numeDirOld numeDirNew

4.7. Sa se implementeze comanda Unix ls.

4.8. Comanda finger afisează "new mail received..." si "unread since..." precizând prin ... timpul si data corespunzătoare. Cum poate programul sa determine timpul si data ?

4.9. Sa se scrie un program care sa permită traversarea unei arbore de fisiere cu scopul de obtine informatii statistice despre toate tipurile de fisiere din sistem. Programul primeste ca argument în linia de comanda un nod în arbore. Nota: atentie la legaturile simbolice.

4.10. Sa se scrie un program utilitar, care prin intermediul unui meniu, sa permită realizarea următoarelor actiuni: afisarea de informatii despre fisier, modificarea drepturilor de acces, modificarea proprietarului, modificarea timpului si executia unor comenzi Unix.