SE4 2022/2023 EC1

De wiki-se.plil.fr
Aller à la navigation Aller à la recherche

Objectifs

Il vous est demandé de :

  • réaliser un micro système de fichiers ;
  • le système de fichiers doit résider dans un fichier de 8 Mo ;
  • le système de fichiers est géré par un exécutable obtenu à partir d'un programme C ;
  • L'éxécutable prend deux arguments, le premier est le chemin du fichier dans lequel réside le système de fichiers, les paramètres suivants concernent l'action à appliquer sur le système de fichiers ;
  • le micro système de fichier ne comporte qu'un répertoire : le répertoire principal, le répertoire principal peut comporter au maximum 64 fichiers, un fichier est caractérisé par un nom de 16 caractères au maximum et ses blocs de données, un fichier peut comporter au maximum 2040 blocs de données ;
  • un bloc de données fait 256 octets et les blocs sont numérotés sur 2 octets ;
  • les différentes actions possibles sur le système de fichiers sont :
    • TYPE pour créer un fichier si possible, le nom du fichier suit la commande, le contenu du fichier est donné en entrée standard de l'exécutable ;
    • CAT pour afficher un fichier, le nom du fichier suit la commande ;
    • RM pour détruire un fichier, le nom du fichier suit la commande ;
    • MV pour renommer un fichier, les noms original et nouveau du fichier suivent la commande ;
    • CP pour copier un fichier, les noms de l'original et de la copie du fichier suivent la commande ;

Matériel nécessaire

Un PC sous Linux.

Travail réalisé

Semaine 1

ReX : attention, ce micro-système de fichiers est prévu pour un microcontrôleur, vos variables globales ne peuvent pas dépasser quelques centaines d'octets.

ReX : pour la création du système de fichiers la commande dd suffit, vous voulez dire le formatage du système de fichiers ?

  • création du programme programme.c contenant les différentes structures et les différentes fonctions.
  • Piste d'amélioration: faire en sorte que la fonction CAT affiche le contenu exact des fichiers passés en argument.


Le code fourni est un programme en langage C qui simule un système de fichiers basique. Ce programme permet aux utilisateurs d'effectuer diverses actions telles que créer, afficher, supprimer, renommer et copier des fichiers dans un système de fichiers simulé. Le système de fichiers est stocké dans un fichier binaire.

ReX : vous n'avez pas tenu compte de la première remarque, vous chargez tout le superbloc et le répertoire racine, votre code ne convient pas.

ReX : vos structures utilisent des types entiers dont la taille n'est pas explicitée, utilisez les types de stdint.h.

ReX : la création de fichier n'est pas fonctionnelle, vous ne cherchez pas les blocs libres, vous ne copiez pas le contenu du fichier dans les blocs, même remarque pour toutes les autres fonctions.

ReX : il manque les fonctions d'accès aux pages du système de fichiers.

Explication du programme programme.c

Voici une brève explication des principaux éléments et fonctions du code :

  1. Structures :
    • Fichier : Représente un fichier dans le système de fichiers. Il contient un nom (nom) avec une longueur maximale de 16 caractères, un tableau de numéros de bloc (blocs) où le contenu du fichier est stocké (jusqu'à 2040 blocs), et le nombre de blocs utilisés par le fichier (nbBlocs).
    • Repertoire : Représente le répertoire principal du système de fichiers. Il contient un tableau de fichiers (fichiers) et le nombre de fichiers dans le répertoire (nbFichiers).
  2. Fonctions :
    • chargerSystemeFichiers : Charge le système de fichiers à partir d'un fichier binaire donné (chemin) dans une structure Repertoire.
    • sauvegarderSystemeFichiers : Sauvegarde le système de fichiers stocké dans la structure Repertoire dans un fichier binaire (chemin).
    • creerFichier : Crée un nouveau fichier dans le système de fichiers avec un nom et un contenu donnés. Le contenu du fichier est simulé en le divisant en blocs.
    • afficherFichier : Affiche le contenu d'un fichier avec le nom spécifié.
    • detruireFichier : Supprime un fichier avec le nom spécifié du système de fichiers.
    • renommerFichier : Renomme un fichier avec l'ancien nom spécifié en un nouveau nom.
    • copierFichier : Copie un fichier avec le nom spécifié en créant un nouveau fichier avec le nom de copie spécifié.
  3. Fonction main :
    • La fonction main est le point d'entrée du programme.
    • Elle prend des arguments en ligne de commande : <chemin_systeme_fichiers> et <action>.
    • <chemin_systeme_fichiers> est le chemin vers le fichier binaire où le système de fichiers est stocké.
    • <action> détermine quelle opération effectuer, comme TYPE, CAT, RM, MV ou CP.
    • En fonction de l'action spécifiée, le programme appelle la fonction correspondante pour effectuer l'opération souhaitée sur le système de fichiers.

Remarque : Le code fourni une simulation basique d'un système de fichiers et de ses opérations, mais il n'interagit pas réellement avec un système de fichiers réel sur le disque.

Comment utiliser le programme programme.c

étape 1: création du système de fichiers respectant le cahier des charges:

dd if=/dev/zero of=systeme_fichiers.bin bs=1M count=8

étape 2: compilation du programme C:

gcc programme.c -o gestionnaire_fs

étape 3: création d'un fichier dans le système de fichier:

./gestionnaire_fs systeme_fichiers.bin TYPE fichier1.txt

-> entrer le texte en entrée standard

étape 4: manipulation des différentes fonctions. Exemples:

./gestionnaire_fs systeme_fichiers.bin CAT fichier1.txt
./gestionnaire_fs systeme_fichiers.bin CP fichier1.txt copie.txt
./gestionnaire_fs systeme_fichiers.bin RM copie.txt
./gestionnaire_fs systeme_fichiers.bin MV fichier1.txt fichier2.txt

Semaine 2

Amine: Merci pour vos remarques. Désolé de ne pas avoir suivi votre première remarque, je pensais avoir compris ce qu'il fallait faire mais je me rend compte que non. Je vais tout reprendre depuis le début afin de repartir sur de bonnes bases.

Création du système de fichier avec la commande "dd"

A quoi sert la commande dd?

La commande dd permet de copier tout ou partie d'un disque par blocs d'octets, indépendamment de la structure du contenu du disque en fichiers et en répertoires (source : [1]).


Structure de la commande

dd if=<source> of=<cible> bs=<taille des blocs>

source = données à copier

cible = endroit où les copier

if = input file

of = output file

bs = block size, habituellement une puissance de 2 supérieure ou égale à 512, représentant un nombre d'octets


Choix de la commande:

Pour la source, on prends /dev/zero: cela permet de créer un fichier rempli de 0. Ce fichier servira de base pour notre système de fichiers.

Pour la cible, on va l'appeler filesystem.bin : ce sera notre système de fichier.

Pour la taille des blocs, on veut des blocs de données de 256 octets d'après l'enoncé. On va donc prendre bs=256.

Enfin, on veut que le système de fichier réside dans un fichier de 8 Mo. On va donc ajouter count=31250: cela indique que nous voulons écrire 32768 blocs de données dans le fichier (31250 * 256 octets = 8 Mo).


Résultat:

dd if=/dev/zero of=filesystem.bin bs=256 count=31250

Question pour M. Redon : j'ai ici essayé de respecter votre remarque "pour la création du système de fichiers la commande dd suffit". Cependant, pour votre seconde remarque "vous chargez tout le superbloc et le répertoire racine, votre code ne convient pas.", je ne suis pas sûr de comprendre où je fais cela: est-ce lors de la commande dd ou est-ce par la suite avec mon programme C? J'ai un peu modifié la commande dd comme vous pouvez le voir ci-dessus. Ai-je corrigé mon erreur avec cette nouvelle commande? J'ai essayé de justifier au maximum la commande dd que j'ai choisie.

ReX : Pas de problème avec la commande dd (mettez tous les extraits de code entre des balises code). Le problème est au niveau du programme C.

Amine: Ok je comprends mieux merci. Je vais donc reprendre le code étape par étape afin de mieux répondre au cahier des charges.

Gestion du système de fichier par un programme C

Etape 1: création des structures

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

// Structure pour représenter un bloc de données

struct DataBlock {
   uint8_t data[256]; // 256 octets pour chaque bloc de données
};

// Structure pour représenter un fichier

struct File {
   char filename[17]; // 16 caractères pour le nom du fichier + 1 caractère null-terminator
   struct DataBlock data_blocks[2040]; // Tableau de blocs de données pour stocker le contenu du fichier
   uint16_t num_data_blocks; // Nombre de blocs de données utilisés par le fichier
};

// Structure pour représenter un répertoire

struct Directory {
   struct File files[64]; // Tableau de fichiers pour le répertoire principal
   uint8_t num_files; // Nombre de fichiers dans le répertoire principal
}; 

Modification faite : suite à votre remarque, j'ai explicité la taille des entiers grâce aux types de stdint.h. (j'ai également mis les variables en anglais)

ReX : Vous ne pouvez pas utiliser ces structures, une instanciation d'une de ces structure prend trop d'espace mémoire, vous devez faire sans structure. Par ailleurs la façon dont vous représentez vos fichiers ne convient pas, la description d'un fichier contient des numéros de blocs, pas les blocs eux-même. Faites aussi attention qu'un fichier soit décrit par un nombre entier de blocs.

Question pratique: je n'arrive pas à mettre tout le code dans le même bloc comme vous l'avez fait ci-dessus dans l'étape 4 de la semaine 1. Je vois que c'est en format "préformaté" mais j'obtiens des blocs séparés lorsque j'applique ce format à mon code.

ReX : j'ai corrigé, pour un code entier la syntaxe n'est pas la même (un espace en début de chaque ligne), la balise c'est uniquement pour de très courts extraits de code.

Etape 2: Fonction readBlock

Cette fonction est utilisée pour lire un bloc de données à partir du fichier filesystem.bin et le stocker dans un tableau de caractères (storage).

Fonction:

   #define BLOCK_SIZE 256
   // Fonction pour lire un bloc de données
   void readBlock(unsigned int num, int offset, unsigned char *storage, int size) {
       FILE *file = fopen("filesystem.bin", "rb");
       if (file == NULL) {
           perror("Erreur lors de l'ouverture du fichier");
           return;
       }
       fseek(file, num * BLOCK_SIZE + offset, SEEK_SET);
       fread(storage, 1, size, file);
       fclose(file);
   }


Paramètres :

  • num : Numéro du bloc à lire. Chaque bloc contient 256 octets (1 bloc = 256 octets).
  • offset : Décalage (en octets) à partir du début du bloc pour commencer la lecture.
  • storage : Pointeur vers un tableau de caractères où les données lues seront stockées.
  • size : Taille du tableau de caractères storage.


Fonctionnement :

  • La fonction commence par ouvrir le fichier filesystem.bin en mode lecture binaire ("rb").
  • Elle utilise fseek pour positionner le curseur de lecture dans le fichier à l'endroit approprié pour commencer la lecture du bloc spécifié (num) à partir de l'offset (offset).
  • Elle utilise ensuite fread pour lire size octets à partir du fichier et les stocker dans le tableau storage.
  • Enfin, elle ferme le fichier avec fclose.


Note : Le paramètre offset est utilisé pour spécifier à partir de quel octet du bloc on souhaite commencer la lecture. Si offset est égal à 0, la lecture commencera depuis le début du bloc. Si offset est différent de 0, la lecture commencera à l'octet spécifié.


Remarque: dans votre mail, vous définissez "readBlock(unsigned int num,int offset,unsigned storage,int size)": storage n'est alors pas un pointeur vers un tableau de caractère. Je me suis donc permis de faire la modification.

ReX : OK pour la fonction, pas la peine d'en mettre autant dans le Wiki pour une fonction aussi simple.

Etape 3: Fonction writeBlock

Cette fonction est utilisée pour écrire un bloc de données dans le fichier filesystem.bin à partir d'un tableau de caractères (storage).


Fonction:

   // Fonction pour écrire un bloc de données
   void writeBlock(unsigned int num, int offset, const unsigned char *storage, int size) {
       FILE *file = fopen("filesystem.bin", "rb+");
       if (file == NULL) {
           perror("Erreur lors de l'ouverture du fichier");
           return;
       }
       fseek(file, num * BLOCK_SIZE + offset, SEEK_SET);
       fwrite(storage, 1, size, file);
       fclose(file);
   }

Paramètres :

  • num : Numéro du bloc où écrire.
  • offset : Décalage (en octets) à partir du début du bloc pour commencer l'écriture.
  • storage : Pointeur vers un tableau de caractères contenant les données à écrire dans le fichier.
  • size : Taille du tableau de caractères storage.


Fonctionnement :

  • La fonction commence par ouvrir le fichier filesystem.bin en mode lecture et écriture binaire ("rb+").
  • Elle utilise fseek pour positionner le curseur de lecture/écriture dans le fichier à l'endroit approprié pour commencer l'écriture du bloc spécifié (num) à partir de l'offset (offset).
  • Elle utilise ensuite fwrite pour écrire size octets à partir du tableau storage dans le fichier.
  • Enfin, elle ferme le fichier avec fclose.

ReX : Même remarque que pour la fonction précédente.

Etape 4: test des fonctions readBlock et writeBlock dans le main

   // Exemple de fonction principale pour tester les opérations de lecture et d'écriture
   int main() {
       unsigned char data[BLOCK_SIZE];
       // Test de la fonction readBlock
       readBlock(0, 0, data, BLOCK_SIZE);
       printf("Block 0, offset 0: ");
       for (int i = 0; i < BLOCK_SIZE; i++) {
           printf("%02x ", data[i]);
       }
       printf("\n");
       // Test de la fonction writeBlock
       unsigned char newData[BLOCK_SIZE] = {
           0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
           // ... Autres données du bloc ...
       };
       writeBlock(1, 0, newData, BLOCK_SIZE);
       // Lecture du bloc nouvellement écrit pour vérifier
       readBlock(1, 0, data, BLOCK_SIZE);
       printf("Block 1, offset 0: ");
       for (int i = 0; i < BLOCK_SIZE; i++) {
           printf("%02x ", data[i]);
       }
       printf("\n");
       return 0;
   }

Fonctionnement :

  • On déclare tout d'abord un tableau de caractères data de taille BLOCK_SIZE pour stocker les données lues du bloc.
  • Ensuite, la fonction readBlock est appelée avec les arguments (0, 0, data, BLOCK_SIZE) pour lire le premier bloc du fichier (num=0, offset=0) et stocker les données dans data.
  • Ensuite, la fonction printf est utilisée pour afficher les données lues du bloc (data) en format hexadécimal.
  • Ensuite, un nouveau tableau de caractères newData est créé avec des données spécifiques pour tester la fonction writeBlock.
  • La fonction writeBlock est appelée avec les arguments (1, 0, newData, BLOCK_SIZE) pour écrire le tableau newData dans le deuxième bloc du fichier (num=1, offset=0).
  • Enfin, la fonction readBlock est appelée à nouveau pour lire le deuxième bloc nouvellement écrit et afficher les données lues en format hexadécimal.


Question générale : ce que j'avais la première semaine n'est plus forcément d'actualité. Puis-je supprimer les choses qui ne le sont plus afin d'alléger la page ou préférez-vous que je laisse?

ReX : Laissez.

Pour la suite : j'ai donc pour l'instant crée deux fonctions, l'une permettant la lecture et l'autre l'écriture d'un bloc, de manière à économiser la mémoire. Pour la suite, je vais essayer de créer la fonction LS en utilisant la fonction readBlock.

ReX : Oui c'est le début du projet. Mais si vous n'avez pas une bonne description de fichier cela ne donnera rien. Voir remarques plus haut.

Etape 5: fonction LS

Objectif: créer la fonction LS avec la fonction readBlock.

Question: Souhaitez-vous la fonction LS classique, c'est à dire la fonction LS qui affiche simplement le nom des fichiers présent dans le répertoire ?

ReX : Oui. Tu peux ajouter la taille si tu trouves cela trop simple.

Piste : si c'est ce que vous voulez:

  • il va tout d'abord falloir que je crée des fichiers, un fichier étant composé d'un nom et de blocs (création d'une fonction createFile)
  • Il faudra ensuite que je stocke ces fichiers dans le répertoire (création d'une fonction addFileToDirectory par exemple)
  • enfin, il faudra que j'affiche le nom des fichiers. Pour cela, il faudra que je parcours le répertoire (structure "Directory"), puis que pour chaque fichier présent dans le répertoire que j'affiche son nom (création de la fonction LS)

Je devrais surement utiliser la fonction readBlock afin de transférer les blocs dans le fichier au travers de la variable "storage".

ReX : Créer des fichiers n'est pas nécessaire tout de suite je verrais bien si ton code est bon sans test. Laisse tomber createFile et addFileToDirectory pour l'instant.

ReX : Ton algorithme ne fonctionne pas pour un microcontrôleur où il faut économiser la mémoire, tu ne peux pas t'aider de structures. Il faudra que tu charges les noms et seulement les noms, pour la taille il faudra se baser sur le nombre de blocs.

Etape 6: rectification du code suite à vos remarques

Modifications apportées:

  • suppression des structures afin d’économiser de la mémoire.
  • le problème quant à la description des fichiers est normalement résolu puisque plus de structures. On ne charge plus les blocs mais seulement les noms de fichiers.

ReX : Non car si les noms ne sont pas répartis de façon régulière dans les blocs de 256 octets tu vas avoir du mal à les charger. Mais écrit la fonction LS et tu verras ce que je veux dire.

J'ai également ajouté deux nouvelles fonctions:

  1. void readFileName(unsigned int file_num, char *file_name): Cette fonction permet de lire le nom du fichier associé au numéro de fichier donné (file_num). Elle stocke le nom du fichier lu dans le tableau file_name.
  2. void writeFileName(unsigned int file_num, const char *file_name): Cette fonction permet d'écrire le nom du fichier dans le système de fichiers, associé au numéro de fichier donné (file_num). Elle prend en entrée le nom du fichier à écrire (file_name).

Suite: si vous validez cette base, je pourrai passer à la création de la fonction LS.

ReX : tu peux y aller. Je ne vois pas le code des tes deux fonctions readFileName et writeFileName.

Voici le code des fonctions readFileName et writeFileName :

Fonction readFileName:

// Fonction pour lire le nom du fichier 
void readFileName(unsigned int file_num, char *file_name) {
     readBlock(file_num, 0, (unsigned char *)file_name, MAX_FILENAME_LENGTH); 
     file_name[MAX_FILENAME_LENGTH] = '\0'; // Ajout du caractère null-terminator pour former une chaîne de caractères 
}

ReX : Non, aucune raison que le fichier de numéro n soit au bloc n. Dessine la représentation d'un fichier sur le SF en comptant les octets si cela peut aider.

Fonction writeFileName:

// Fonction pour écrire le nom du fichier
void writeFileName(unsigned int file_num, const char *file_name) {
    writeBlock(file_num, 0, (const unsigned char *)file_name, MAX_FILENAME_LENGTH);
}

ReX : Faux et inutile pour l'instant. Problème avec la constante MAX_FILENAME_LENGTH.

Fonction LS:

// Fonction LS pour afficher les fichiers présents dans le système de fichiers
void LS(const char *filesystem_path) {
    FILE *file = fopen(filesystem_path, "rb");
    if (file == NULL) {
        perror("Erreur lors de l'ouverture du fichier système");
        return;
    }
    uint16_t num_files;
    fread(&num_files, sizeof(uint16_t), 1, file); // Lecture du nombre de fichiers dans le système
    char filename[17];
    printf("Fichiers présents dans le système de fichiers:\n");
    for (int i = 0; i < num_files; i++) {
        readFileName(i, filename); // Lecture du nom du fichier
        printf("%s\n", filename); // Affichage du nom du fichier
    }
    fclose(file);
}

La fonction LS ouvre le fichier système, lit le nombre de fichiers qu'il contient, puis affiche les noms des fichiers un par un, chacun sur une ligne séparée.

ReX : Il y a le nombre de fichiers dans ton superbloc ? Un dessin du format du superbloc avec les numéros des octets ?

ReX : Tout accès au système de fichiers doit se faire avec les deux fonctions readBlock et writeBlock.

Semaine 3

Documents Rendus