Il s'agit d'implémenter différents outils et bibliothèques permettant de manipuler une disquette MS/DOS à la manière des outils mtools.
Ce projet devra être réalisé en langage C en utilisant les fonctions POSIX étudiées en cours. Il devra impérativement fonctionner sur les machines de l'UFR. Le travail pourra être effectué par groupe de quatre étudiants au plus. Un rapport devra être rédigé et rendu avant la soutenance. Les dates de remise des rapports et soutenances seront précisées ultérieurement.
Une disquette MS/DOS est une suite de secteurs (blocs physiques de taille fixe). Cette suite est logiquement partitionnée en quatre morceaux :
La zone de démarrage contient normalement certaines informations sur le support physique et le système de fichiers. Plus un programme de démarrage dont on négligera l'existence.
La zone d'allocation contient pour chaque objet du système de fichiers la liste ordonnée des ses blocs de données. Mais on y trouve aussi la liste des blocs défectueux, des blocs libres et des blocs réservés.
La racine du système de fichier est le sommet du graphe d'objets, c'est donc un répertoire (de taille fixe).
La zone des fichiers contient les blocs de données de chaque objet du système de fichiers.
La disquette sera simulée par des lectures/écritures dans un fichier. Les images de deux véritables disquettes MS/DOS (que nous appellerons donc disquettes) sont disponibles sur les machines de l'UFR sous les références /ens/yunes/public/systemes2000/disk.dos et /ens/yunes/public/systemes2000/truc.dos. Mais vous êtes libre d'en trouver d'autres ou d'en créer vous-même pourvu que vous respectiez la spécification. La disquette est de type 1.44 Mo. De plus un exécutable pour machines HP est disponible sous la référence /ens/yunes/public/systemes2000/dos.hp. Vous pouvez tenter de l'exécuter il permet d'obtenir quelques informations sur le système de fichiers, de lister les répertoires et fichiers et d'afficher le contenu d'un fichier à l'écran. Son interface est assez désagréable et il contient sûrement beaucoup de bogues...
L'unité minimale de lecture ou écriture sur une disquette est le secteur : suite d'octets de longueur 512. Chaque secteur est numéroté de 0 (pour le premier) jusqu'à N-1 (pour le dernier). Une disquette contient au moins le premier secteur (sinon pas de disquette!). La première chose à réaliser est donc une bibliothèque de manipulation de la disquette : lecture/écriture de secteurs.
Elle est communément appellée Boot Sector. Sa taille est d'au moins un secteur : le premier du disque.
On y trouve les informations suivantes :
Position
(en octets) |
Longueur
(en octets) |
Symbole |
---|---|---|
3 | 8 | Identification |
11 | 2 | Octets/Secteur |
13 | 1 | Secteurs/UnitéAllocation |
14 | 2 | Secteurs/Boot |
16 | 1 | FATs |
17 | 2 | MaxEntréesRacine |
19 | 2 | Secteurs/Disquette |
21 | 1 | TypeDisquette |
22 | 2 | Secteurs/FAT |
Soit en détail :
Valeur | Type de disque |
---|---|
0xff | DOS 1.1 5 1/4, 320 Ko |
0xf9 | DOS 3.0 5 /14, 1.2 Mo |
0xf8 | DOS 2.0 Disque Dur |
0xf0 | DOS 3.3 3 1/2, 1.44 Mo |
Attention : Il est important de rappeller que la représentation des nombres dans le monde Intel est little-endian. Il faudra donc prendre la précaution de lire correctement les valeurs représentées sur plusieurs octets.
Elle contient une voir plusieurs FATs (File Allocation Table). Les FATs contiennent en temps normal les mêmes informations. Toutefois, il est recommandé de commencer par lire/écrire la première, si cela conduit à une échec tenter avec la seconde, etc...
C'est une zone permettant de retrouver pour chaque fichier les Unités d'Allocation qui le constitue (chaînage simple de fragments de taille fixe), la liste des Unités d'Allocation libres et la liste des Unités d'Allocation défectueuses.
Une Unité d'Allocation est un regroupement de secteurs contigus situés dans la zone des fichiers. Le numéro du premier secteur constituant une UA peut être obtenu de la façon suivante :
#secteur = (#UA-2) x Secteurs/UA + |zone démarrage| + |zone d'allocation| + |racine|
et s'étend donc sur Secteurs/UA secteurs.
Les numéros d'UA sont normalement représentés sur 12, 16 ou 32 bits (FAT12, FAT16 ou FAT32) selon certains critères. Mais on peut remarquer que les disquettes utilisent normalement 12 bits. De plus les deux premiêres entrées sont réservées d'où le -2 dans la formule précédente.
Certaines valeurs d'UAs sont réservées :
FAT12 | FAT16 | FAT32 | Sémantique |
---|---|---|---|
0x000 | 0x0000 | 0x00000000 | UA libre |
0xFF8 - 0xFFF | 0xFFF8 - 0xFFFF | 0xFFFFFFF8 - 0xFFFFFFFF | Fin de fichier |
0xFF7 | 0xFFF7 | 0xFFFFFFF7 | UA défectueuse |
Toute autre valeur fait référence à l'UA suivante dans la chaîne. Par exemple :
Dans le cas de deux fichiers de longueurs telles que f1 utilise 2 UA et f2 trois UA sur un disque contenant 8 UA on pourrait avoir la situation suivante :
UA | 0x003 | 0x006 | 0x000 | 0xFFF | 0x000 | 0x001 | 0xFFF | 0x000 |
Fragment | UA1(f1) | UA2(f2) | UA2(f1) | UA1(f2) | UA3(f2) |
et la FAT contiendrait alors les valeurs suivantes (en plus des deux valeurs réservées, et en little-endian pour rigoler) :
03 60 00 00 F0 FF 00 10 00 FF 0F 00
Il existe un petit algorithme permettant de retrouver la valeur d'une entrée de la FAT12 et la convertir en big-endian :
La racine est un simple répertoire dont la taille est limitée (voir MaxEntréesRacine).
Un répertoire contient une liste d'objets (fichiers, répertoires, etc.). Chaque objet y est décrit par une ou plusieurs entrées.
Une entrée est de taille fixe : 32 octets. Il existe plusieurs types d'entrées :
Chaque SFN est au format suivant :
Position
(en octets) |
Taille
(en octets) |
Description |
---|---|---|
0 | 8 | Nom |
8 | 3 | Extension |
11 | 1 | Attributs |
12 | 1 | Réservé |
13 | 1 | Réservé |
14 | 2 | Heure de création |
16 | 2 | Date de création |
18 | 2 | Date de dernier accès |
20 | 2 | Réservé |
22 | 2 | Heure de dernière modification |
24 | 2 | Date de dernière modification |
26 | 2 | UA du premier fragment |
28 | 4 | Taille (en octets) |
Le champ nom est toujours de taille 8 (pour cela le caractère <espace> est utilisé comme caractère de bourrage); mais attention il ne peut être constitué que de seuls caractères d'espacement. Ex : le fichier de nom "toto" est enregistré sous le nom "TOTO "; notez par ailleurs la conversion des minuscules en majuscules De plus le premiere caractère est utilisé comme suit :
Valeur | Description |
---|---|
0x00 | Emplacement libre et dernier du répertoire |
0x05 | Le caractère est en réalité 0xE5 |
0xE5 | L'entrée a été effacée |
autre | L'entrée est valide |
La valeur 0xE5 indique que le fichier a été effacé mais qu'il reste possible de le récuperer car ce type d'effacement n'est que logique : ses UAs n'ont pas été libérées, et seul le premier caractère de la SFN est altéré (pour les LFN associés c'est le numéro de séquence qui est positionné à 0xE5), ainsi le nom long n'est pas altéré. Les UAs ne seront libérées que lorsqu'on tentera de réutiliser cette entrée pour créer un autre fichier ou répertoire ou lorsqu'on utilisera une commande d'effacement physique.
L'extension peut être vide, mais comme pour le nom le caractère <espace> est utilisé comme bourrage.
Le champ attributs est une combinaison des valeurs suivantes :
Valeur | Description |
---|---|
0x01 | Lecture seulement |
0x02 | Objet caché |
0x04 | Objet système |
0x08 | Nom de disquette |
0x10 | Répertoire |
0x20 | Archive |
Un objet en lecture seule ne peut être modifié ni supprimé. Les objets cachés sont normalement invisibles mais certaines options de certaines commandes permettent de les voir (ie: similaire aux fichiers .* sous UNIX). Les objets systèmes ne doivent jamais être détruits et comme les objets cachés il est interdit de déplacer leur contenu (lors d'une défragmentation par exemple).
Si une entrée possède l'attribut Nom de disquette (seul attribut normalement positionné dans ce cas) alors le nom est simplement le nom de la disquette. Il n'existe normalement qu'au plus un objet de ce type et situé dans le répertoire racine.
L'attribut archive est utilisé par certains outils de sauvegarde.
L'attribut répertoire indique qu'il s'agit d'un sous-répertoire. Il faut noter que dans chaque répertoire (sauf la racine) existe deux SFN particuliers de nom . et .. qui font référence au répertoire lui-même et au répertoire père (respectivement); de plus leurs attributs doivent être 0x10 et rien d'autre. Lorsque le répertoire père est la racine le cluster est le numéro 0. Les SFN de . et .. n'ont pas de LFN associés.
La combinaison 0xF (lecture seule, caché, système, volume) représente une entrée LFN.
L'heure représentée ainsi :
Bits | Description |
---|---|
0..4 | secondes/2 |
5..10 | minutes |
11..15 | heure |
Une date est représentée ainsi :
Bits | Description |
---|---|
0..4 | Jour dans le mois |
5..8 | Mois dans l'année |
9..15 | Années écoulées depuis 1980 |
L'UA du premier fragment est le numéro d'UA dans lequel on trouvera le début du fichier. Pour retrouver l'UA suivante il faut consulter la FAT afin de retrouver le numéro situé dans l'entrée de l'UA courante (voir FAT).
Les noms longs (jusqu'à 256 caractères) sont représentés par une suite de LFN précédant immédiatement le SFN. Chaque LFN ne permet que d'enregistrer 13 caractères (chacun sur deux octets pour l'Unicode). Le format d'un LFN est le suivant :
Position
(en octets) |
Longueur
(en octets) |
Description |
---|---|---|
0 | 1 | Numéro dans la séquence |
1 | 10 | Les 5 premiers caractères |
11 | 1 | Attributs |
12 | 1 | Réservé |
13 | 1 | Somme de contrôle |
14 | 12 | Les 6 caractères suivants |
26 | 2 | valeur 0x0000 |
28 | 4 | Les deux caractères suivants |
Le numéro de séquence permet de reconstituer le nom du fichier, sachant que le dernier numéro est obtenu par ou-bit-a-bit avec 0x40. Le premier de la séquence est numéroté 1. Mais il faut préciser que les entrées sont enregistrées en sens inverse. Soit pour un nom long utilisant 3 LFN on trouvera : LFN(3), puis LFN(2), puis LFN(1) et SFN. Par exemple :
Pour représenter le nom "Voici un nom de fichier très long" associé au fichier VOICI.TXT on trouvera une séquence comme suit (en hexadécimal et caractères) :
LFN hexa : 43 E8 00 73 00 20 00 6C 00 6F 00 0F 00 ?? 6E 00 67 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ascii: è s l o n g LFN hexa : 02 64 00 65 00 20 00 66 00 69 00 0F 00 ?? 63 00 68 00 69 00 65 00 72 00 20 00 00 00 74 00 72 00 ascii: d e f i c h i e r t r LFN hexa : 01 56 00 6F 00 69 00 63 00 69 00 0F 00 ?? 20 00 6E 00 6F 00 20 00 6e 00 6f 00 00 00 6d 00 20 00 ascii: V o i c i u n n o m SFN hexa : 56 4F 49 43 49 20 20 20 54 58 54 00 réservés..................... heure date. 1erua taille..... ascii: V O I C I T X T
L'attribut est obligatoirement égal à 0x0F, ie : Lecture seule, Objet caché, Object Système et Nom de volume (combinaison stupide).
La somme de contrôle est calculée en utilisant le nom trouvé dans le SFN selon l'algorithme suivant :
Elle est simplement constitué des UA des fichiers ou des sous-répertoires (UAs contenant des LFN et/ou SFN).
Il est demandé de réaliser les commandes Unix suivantes (par ordre approximatif de difficulté croissante) :
Bien sur la liste n'est pas exhaustive, et chacun pourra s'amuser (?!) à rajouter des commandes supplémentaires.
Il est recommandé de procéder par étapes et petites touches. On peut par exemple écrire une bibliothèque de bas niveau contenant des fonctions comme :
Ensuite on peut écrire une autre bibliothèque construite au-dessus de la première contenant des fonctions comme :
Ce qui devrait permettre d'écrire les fonctions suivantes :
etc.
Bien entendu rien de cela n'est rééllement contractuel.