Functii de sistem pentru lucrul cu fisiere in Linux
In sistemele de operare UNIX (deci si Linux) toate fisierele deschise sunt identificate prin descriptori de fisier. Un descriptor de fisier este un numar intreg nenegativ atribuit la deschiderea fisierului si este valabil pana la inchiderea acelui fisier.
fisiere standard deschise pentru orice proces:
- fisierul standard de iesire (STDOUT), care are descriptorul 0
- fisierul standard de intrare (STDIN), care are descriptorul 1
- fisierul standard de eroare (STDERR), care are descriptorul 2
Pentru fisierele standard este recomandata folosirea constantelor definite in <unistd.h>
PS: in Unix si Linux se considera a fi fisiere cam tot ce ne putem imagina, de la procesele care ruleaza la toate device-urile conectate la calculator (ex: cdrom, usb, imprimanta, etc... deci se poate face citire si scriere ca in niste fisiere normale, cu niste mici diferente
)
Functiile open, creat si close
Deschiderea unui fisier se face cu ajutorul uneia dintre functiile
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); int creat(const char *pathname, mode_t mode);
functiile returneaza -1 in caz de eroare si un numar pozitiv in caz de succes, anume descriptorul de fisier
pathname - calea spre fisierul deschis (sau creat)
flags - o insiruire de argumente care specifica modul de deschidere a fisierului, trebuie sa contina unul din flagurile O_RDONLY (doar citire), O_WRONLY (doar scriere), sau O_RDWR (actualizare adica si scriere si citire)
dar mai sunt si alte flaguri care pot fi folosite in combinatie cu acestea:
- O_APPEND - deschidere pentru adaugare la sfarsitul fisierului
- O_CREAT - crearea fisierului, daca el nu exista deja; daca e folosita cu aceasta optiune, functia open trebuie sa primeasca si parametrul mode.
- O_EXCL - creare "exclusiva" a fisierului: daca s-a folosit O_CREAT si fisierul exista deja, functia open va returna eroare
- O_TRUNC - daca fisierul exista, continutul lui este sters
mode - se foloseste doar daca este creat fisierul si specifica drepturile de acces pentru fisierul creat.
- S_IRUSR - drept de citire pentru proprietarul fisierului (user)
- S_IWUSR - drept de scriere pentru proprietarul fisierului (user)
- S_IXUSR - drept de executie pentru proprietarul fisierului (user)
- S_IRGRP - drept de citire pentru grupul proprietar al fisierului
- S_IWGRP - drept de scriere pentru grupul proprietar al fisierului
- S_IXGRP - drept de executie pentru grupul proprietar al fisierului
- S_IROTH - drept de citire pentru ceilalti utilizatori
- S_IWOTH - drept de scriere pentru ceilalti utilizatori
- S_IROTH - drept de executie pentru ceilalti utilizatori
Observatie: atat flags cat si mode pot fi obtinute prin combinarea cu ajutorul lui sau pe biti "|" a constantelor corespunzatoare.
Observatie: apelul lui creat cu argumentul
Pentru inchiderea unui fisier se apeleaza functia
int close (int filedes)
parametrul este descriptorul de fisier al fisierului care se vrem sa-l inchidem
Functiile read si write
Functia read citeste dintr-un fisier
ssize_t read(int fd, void *buff, size_t nbytes)
Functia citeste un numar de exact nbytes octeti de la pozitia curenta in fisierul al carui descriptor este fd si ii pune in zona de memorie indicata de pointerul buff.
Este posibil ca in fisier sa fie la un moment dat mai putin de nbytes octeti, caz in care functia read va pune in buffer doar atatia octeti cati poate citi. In orice caz, functia returneaza numarul de octeti cititi din fisier.
Daca s-a ajuns exact la sfarsitul fisierului, functia returneaza zero, iar in caz de eroare, -1
Functia write scrie intr-un fisier
ssize_t write(int fd, void *buff, size_t nbytes)
Paramterii sunt ca la functia read cu diferenta ca functia scrie in fisier primii nbytes octeti din bufferul indicat de buff.
Returneaza -1 in caz de eroare.
Functia lseek
Functia lseek ne permite sa ne pozitionam intr-un anume loc in fisier. Atat metodele de citire cat si cele de scriere incrementeaza indicatorul de pozitie in fisier dar cu ajutorul lui lseek putem sa revenim intr-un anumit loc sau sa mergem direct in fisier la o anumita pozitie.
off_t lseek(int fd, off_t offset, int pos)
Functia pozitioneaza indicatorul la deplasamentul offset in fisier, astfel:
- daca parametrul pos ia valoarea SEEK_SET, pozitionarea se face relativ la inceputul fisierului
- daca parametrul pos ia valoarea SEEK_CUR, pozitionarea se face relativ la pozitia curenta
- daca parametrul pos ia valoarea SEEK_END, pozitionarea se face relativ la sfarsitul fisierului
Parametrul offset poate lua si valori negative si reprezinta deplasamentul, calculat in octeti.
In caz de eroare, functia returneaza -1.
Ca deobicei un cod de exemplu 
In acest exemplu facem o mica "arhiva" cu fisiere.
Programul creaza un fisier mare din mai multe fisiere date ca parametru.
Apoi din acest fisier, "arhiva" se poate extrage un anumit fisier sau mai multe fisiere.
Programul nu arhiveaza in sensul de comprimare pentru reducerea spatiului si nici nu realizeaza o protectie cu parola. Va voi arata cum se face acest lucru intr-un tutorial viitor.
Apoi daca este lume interesata vom adauga si o interfata grafica si vom incerca si portarea pe windows a programului.
Pana atunci, aici este codul sursa:
/*
* Functii de sistem in linux pentru lucrul cu fisiere
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define SAFE_CALL(x) if((x) < 0) { printf("EROARE: eroare functie la linia %d !!!\n" , __LINE__); exit(-1); }
#define SAFE_PTR(x) if((x) == NULL) { printf("EROARE: pointer NULL la linia %d !!!\n" , __LINE__); exit(-1); }
#define SAFE_FREE(x) if(x != NULL) { free(x); x = NULL; }
const mode_t CREATE_MODE = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
/*
* structura retine informatii despre un fisier
* din arhiva
*/
typedef struct TREE {
int nameSize;
char *fileName; // numele fisierului
mode_t mode;
long offset; // offset-ul la care se afla fisierul
long size; // dimensiunea fisierului
} TREE;
char *getFileName(char *path);
char* _lastFileRead; // numele ultimului fisier citit
TREE* _lastFiles; // lista de fisiere din ultima arhiva citia
int _lastNrFiles; // nr de fisiere din ultima arhiva citita
/*
* functia pune in variabilele globale informatiile despre arhiva citita
* observatie: nu este citita toata arhiva, doar informatiile despre fisierele pe care le contine
*/
int readHeader(char *archive) {
int i, fd, nrFisiere, nameSize;
long offset, size;
mode_t mode;
char *fileName;
TREE *files;
// verifica daca nu e ultimul header citit
if (NULL != _lastFileRead && NULL != _lastFiles && _lastNrFiles > 0 && strcmp(archive, _lastFileRead) == 0) {
// arhiva este deja citita
return 1;
} else {
SAFE_FREE(_lastFiles);
SAFE_FREE(_lastFileRead);
_lastNrFiles = -1;
}
// deschidere fisier pentru citire
SAFE_CALL(fd = open(archive, O_RDONLY));
SAFE_CALL(read(fd, &nrFisiere, sizeof (int))); // citire numar fisiere
// alocare memorie pentru structura TREE care retine fisierele
SAFE_PTR(files = (TREE*) malloc(nrFisiere * sizeof (TREE)));
for (i = 0; i < nrFisiere; i++) {
// se citeste dimensiunea fisierului curent
SAFE_CALL(read(fd, &nameSize, sizeof (int)));
// se aloca memorie pentru numele fisierului care va fi retinut in TREE
SAFE_PTR(fileName = (char*) malloc((nameSize + 1) * sizeof (char)));
// se cireste numele fisierului, offsetul, size
SAFE_CALL(read(fd, fileName, nameSize * sizeof (char)));
fileName[nameSize] = '\0'; // END OF STRING
SAFE_CALL(read(fd, &mode, sizeof (mode_t)));
SAFE_CALL(read(fd, &offset, sizeof (long)));
SAFE_CALL(read(fd, &size, sizeof (long)));
// se completeaza structura cu informatiile despre fisier
files[i].nameSize = nameSize;
files[i].fileName = fileName;
files[i].mode = mode;
files[i].offset = offset;
files[i].size = size;
}
// inchidere fisier
close(fd);
_lastFiles = files;
_lastNrFiles = nrFisiere;
_lastFileRead = strdup(archive);
return 0;
}
/*
* functia va crea o arhiva cu numele archive
* fileNames este un vector de siruri de caractere cu numele fisierelor
* iar nrFisiere este nr de fisiere :D
*/
int writeFiles(char *archive, char **fileNames, int nrFiles) {
int i, n, fd, src;
long infoSize = 0, startOffset = 0;
TREE files[nrFiles];
char buff[2048];
struct stat fileStat;
// alocare memorie pentru fisiere
//files = (TREE*) malloc(nrFiles * sizeof (TREE));
for (i = 0; i < nrFiles; i++) {
SAFE_CALL(stat(fileNames[i], &fileStat));
if (!S_ISREG(fileStat.st_mode)) {
printf("EROARE: %s nu este un fisier de date!!!\n", fileNames[i]);
exit(-1);
}
files[i].nameSize = strlen(fileNames[i]);
files[i].fileName = fileNames[i];
files[i].size = fileStat.st_size;
files[i].offset = startOffset;
files[i].mode = fileStat.st_mode;
startOffset += fileStat.st_size;
infoSize += files[i].nameSize;
}
infoSize *= sizeof (char);
infoSize += sizeof (int) + nrFiles * (sizeof (TREE) - sizeof (char*));
// scriere
SAFE_CALL(fd = creat(archive, CREATE_MODE));
// numarul de fisiere
SAFE_CALL(write(fd, &nrFiles, sizeof (int)));
// headerul
for (i = 0; i < nrFiles; i++) {
// fisierele incep dupa informatiile despre fisiere
files[i].offset += infoSize;
// scriem structura
SAFE_CALL(write(fd, &(files[i].nameSize), sizeof (int)));
SAFE_CALL(write(fd, files[i].fileName, files[i].nameSize));
SAFE_CALL(write(fd, &(files[i].mode), sizeof (mode_t)));
SAFE_CALL(write(fd, &(files[i].offset), sizeof (long)));
SAFE_CALL(write(fd, &(files[i].size), sizeof (long)));
}
// fisierele
for (i = 0; i < nrFiles; i++) {
SAFE_CALL(src = open(fileNames[i], O_RDONLY));
while (n = read(src, buff, 2048)) {
SAFE_CALL(write(fd, buff, n));
}
close(src);
}
//free(files);
close(fd);
// verificam daca am modificat ultima arhiva citita
if (NULL != _lastFileRead && strcmp(_lastFileRead, archive) == 0) {
SAFE_FREE(_lastFiles);
SAFE_FREE(_lastFileRead);
_lastNrFiles = -1;
}
return 0;
}
/*
* functia citeste din arhiva archive fisierul cu numele fileName
* si il va scrie in fisierul fileDest
*/
int readFile(char *archive, char *fileName, char *fileDest) {
int i, src, dest, r, w, total, nrFiles;
TREE * files;
readHeader(archive);
files = _lastFiles;
nrFiles = _lastNrFiles;
char buff[2048];
for (i = 0; i < nrFiles; i++) {
if (strcmp(files[i].fileName, fileName) == 0) {
// am gasit fisierul
SAFE_CALL(dest = creat(fileDest, files[i].mode));
SAFE_CALL(src = open(archive, O_RDONLY));
SAFE_CALL(lseek(src, files[i].offset, SEEK_SET));
total = 0;
while (total < files[i].size) {
SAFE_CALL(r = read(src, buff, 2048));
SAFE_CALL(w = write(dest, buff, r));
total += w;
}
close(dest);
close(src);
return 0;
}
}
return -1;
}
/*
*
*/
int main(int argc, char** argv) {
int opt, i, n;
char buff[1024], *file, **fileList;
do {
printf("1 - creaza arhiva\n");
printf("2 - citeste arhiva\n");
if (NULL != _lastFileRead) {
printf("3 - scoate fisier din: %s [%d]\n", _lastFileRead, _lastNrFiles);
}
printf("0 - iesire program\n");
printf("optiunea: ");
scanf("%d", &opt);
printf("\n");
switch (opt) {
case 1:
// crearea arhivei
printf("nr de fisiere: ");
scanf("%d", &n);
SAFE_PTR(fileList = (char**) malloc(n * sizeof (char*)));
for (i = 0; i < n; i++) {
printf("fisierul %d: ", i);
scanf("%s", buff);
fileList[i] = strdup(buff);
}
printf("nume arhiva: ");
scanf("%s", buff);
writeFiles(buff, fileList, n);
for (i = 0; i < n; i++) {
SAFE_FREE(fileList[i]);
}
SAFE_FREE(fileList);
break;
case 2:
printf("nume arhiva: ");
scanf("%s", buff);
i = readHeader(buff);
printf("nr fisiere: %d\n", _lastNrFiles);
printf("nume: %s\n", _lastFileRead);
break;
case 3:
if (NULL != _lastFileRead) {
printf("0 - anulare (meniu precedent)\n");
for (i = 0; i < _lastNrFiles; i++) {
printf("%d > %s\n", i + 1, _lastFiles[i].fileName);
}
printf("optiunea: ");
scanf("%d", &i);
i--;
if (i >= 0 && i < _lastNrFiles) {
file = getFileName(_lastFiles[i].fileName);
printf("fisierul: %s a fost extras\n", file);
readFile(_lastFileRead, _lastFiles[i].fileName, file);
}
}
break;
}
} while (opt != 0);
printf("\n");
return (EXIT_SUCCESS);
}
char *getFileName(char *path) {
char *found, *ret = NULL;
while ((found = strstr(path, "/")) != NULL) {
ret = found;
}
if (NULL == ret) {
return path;
}
return ret;
}