Skip to Content

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 flags = O_WRONLY | O_CREAT | O_TRUNC este echivalent cu apelul functiei creat (creaza un fisier)

 

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