Projet de Systèmes I (licence 2000-2001)

L'auteur de cette page est : Jean-Baptiste Yunes

Généralités

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.

Le systèmes de fichiers

Une disquette MS/DOS est une suite de secteurs (blocs physiques de taille fixe). Cette suite est logiquement partitionnée en quatre morceaux :

  1. une zone de démarrage
  2. une zone d'allocation
  3. la racine du système de fichiers
  4. la zone des fichiers

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...

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.

La zone de démarrage

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 :

Boot Sector
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 :

Identification
c'est une chaîne d'au plus huit caractères permettant d'identifier le système qui a formatté la disquette (ex: MSDOS3.3).
Octets/Secteur
contient le nombre d'octets par secteur sur la disquette. La rareté des disquettes autrement formattées que par secteurs de 512 octets vous autorise à refuser d'interpréter le contenu d'un disquette pour laquelle cette valeur n'est pas 512.
Secteurs/UnitéAllocation
les allocations de secteurs pour les objets du système de fichier se font par Unités d'Allocation pas par secteurs comme nous le verrons plus loin. Le secteur c'est l'unité d'entrées/sorties physiques. L'Unité d'Allocation c'est l'équivalent des blocs logiques des systèmes de fichiers à la Unix.
Secteurs/Boot
c'est le nombre total de secteurs utilisés par la zone de boot (au moins 1, celui que l'on décode).
FATs
c'est le nombre de FAT contenues dans la zone d'allocation (au moins une). Les FATs sont toutes identiques, en principe. Il peut en exister plusieurs afin de pouvoir en lire une lorsque les autres sont abimées.
MaxEntréesRacine
c'est le nombre maximal d'entrées que peut contenir la racine de la disquette. Cette limitation est bien entendu absurde mais vous devez en tenir compte.
Secteurs/Disquette
c'est le nombre total de secteurs constituant la disquette.
TypeDisquette
permet d'identifier le type de disquette. Les valeurs possibles, entre autres, sont :
Type de Disques
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
Secteurs/FAT
taille en secteurs de chaque FAT.

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.

La zone d'allocation

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...

La FAT

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 :

UA 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 :

Deux fichiers...(FAT12)
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 :

  1. soit n le numéro de l'entrée de la FAT12 que l'on désire retrouver
  2. calculer pe = (n*12)/8
  3. calculer re = (n*12)%8
  4. calculer valeur = (FAT[pe+1] << 8) + FAT[pe]
  5. si re!=0 alors calculer valeur >> = 4;
  6. calculer valeur &= 0x0FFF;

La racine

La racine est un simple répertoire dont la taille est limitée (voir MaxEntréesRacine).

Les répertoires

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 :

  1. les entrées standards parfois appellées alias (SFN : Standard File Name) qui décrivent des fichiers, répertoires ou nom de volume
  2. les entrées étendues (LFN : Long File Names) qui permettent d'utiliser des noms de fichier plus longs que 8 caractères plus 3 pour l'extension.

Les SNF

Chaque SFN est au format suivant :

Description d'une SFN
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)

Nom

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 :

Nom[0]
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.

Extension

L'extension peut être vide, mais comme pour le nom le caractère <espace> est utilisé comme bourrage.

Attributs

Le champ attributs est une combinaison des valeurs suivantes :

Attributs
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.

Heure

L'heure représentée ainsi :

Représentation des heures
Bits Description
0..4 secondes/2
5..10 minutes
11..15 heure

Date

Une date est représentée ainsi :

Représentation des dates
Bits Description
0..4 Jour dans le mois
5..8 Mois dans l'année
9..15 Années écoulées depuis 1980

UA du premier fragment

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 LFN

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 :

Description d'un LFN
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

Numéro dans le séquence

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

Attributs

L'attribut est obligatoirement égal à 0x0F, ie : Lecture seule, Objet caché, Object Système et Nom de volume (combinaison stupide).

Somme de contrôle

La somme de contrôle est calculée en utilisant le nom trouvé dans le SFN selon l'algorithme suivant :

  1. soit nomfichier le nom fabriqué par concaténation du nom et de l'extension du SFN.
  2. pour i de 0 à 10 faire somme = ((somme&0x01)<<7) || ((somme&0xFE)>>1) +nomfichier[i]

La zone des fichiers

Elle est simplement constitué des UA des fichiers ou des sous-répertoires (UAs contenant des LFN et/ou SFN).

Le travail à réaliser

Il est demandé de réaliser les commandes Unix suivantes (par ordre approximatif de difficulté croissante) :

dos_infos <disquette>
qui renvoie des informations sur la disquette : nom de volume, taille totale, place libre, place occupée, nombre de fichiers, etc.
dos_dir <disquette> [<nom_court> | <nom_long>]
qui permet d'obtenir en lisant la <disquette> soit les informations relatives au fichier désigné par <nom_court> ou <nom_long>, soit la liste des entrées et les informations correspondantes si <nom_court> ou <nom_long> désigne un répertoire. Les noms sont toujours absolus et commencent donc par le caractère \ qui sert aussi de séparateur. Exemple : dos_dir disk.dos FIC.TXT ou dos_dir disk.dos "Fichier avec un nom très long".
dos_dir_all <disquette> [<nom>]
qui permet d'obtenir la liste de tous les objets du répertoire <nom>; y compris les objets cachés, systèmes ou logiquement effacés.
dos_type <disquette> <nom>
qui permet d'obtenir à l'écran le contenu du fichier <nom> (court ou long) sur la <disquette>. Le nom désigne aussi bien un nom court qu'un nom long.
dos_copy_to_unix <disquette> <nom> <nom_unix>
qui permet de copier le fichier <nom> (court ou long) situé sur la disquette dans le système de fichiers Unix sous le nom <nom_unix>.
dos_rename <disquette> <ancien_nom> <nouveau_nom_court> [<nouveau_nom_long>]
qui permet de renommer/déplacer le fichier <ancien_nom> en <nouveau_nom_court> (plus le <nouveau_nom_long>) sur la <disquette>.
dos_delete <disquette> <nom>
qui supprime logiquement le fichier <nom> (court ou long) situé sur la <disquette>.
dos_undelete <disquette> <nom>
qui permet de "retrouver" un fichier logiquement effacé. Le nom est soit un nom long, soit un nom court mais en ce cas le premier caractère n'est pas significatif.
dos_remove <disquette> <nom>
qui supprime physiquement le fichier <nom> (court ou long).
dos_mkdir <disquette> <nom> [<nom_long>]
qui permet de créer un nouveau répertoire.
dos_rmdir <disquette> <nom>
qui supprime le répertoire s'il est vide
dos_copy_from_unix <disquette> <nom_unix> <nom_court> [<nom_long>]
qui réalise l'opération "inverse" en copiant le fichier Unix <nom_unix> sur la <disquette> sous le <nom_court> (plus l'optionnel <nom_long>).

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 :

Error lire_secteur(Disk disquette,Sector secteur,unsigned short numéro);
qui lit le secteur de numéro spécifié sur la disquette.
Error écrire_secteur(Disk disquette,Sector secteur,unsigned short numéro);
qui écrit le secteur de numéro spécifié sur la disquette.
Disk démarre_moteur(char *fichier);
qui permet d'associer au fichier Unix la structure de manipulation de la disquette.
void stoppe_moteur(Disk disquette);
qui permet de "libérer" la disquette.

Ensuite on peut écrire une autre bibliothèque construite au-dessus de la première contenant des fonctions comme :

unsigned short UA_FAT12(Disk disquette,unsigned short numéro);
qui permet de trouver l'entrée de numéro spécifié de la FAT12 de la disquette.
unsigned short UA_libre_FAT12(Disk disquette);
qui permet de trouver une entrée libre dans la FAT de la disquette.
unsigned short UA_vers_secteur(unsigned short numéro);
qui convertit un numéro d'UA en numéro du premier secteur de l'UA.
SLFN lit_slfn(Disk disquette,unsigned short numéro);
qui permet de lire la première SFN ou LFN contenue dans l'UA de numéro spécifié.
SLFN lit_slfn_suivante(Disk disquette,SLFN fn);
qui permet de lire la SFN ou LFN qui suit immédiatement l'entrée fn.

Ce qui devrait permettre d'écrire les fonctions suivantes :

Dir ouvre_répertoire(Disk disquette,char *répertoire);
qui permet d'obtenir un "handler" pour manipuler le répertoire.
Dir_Entry lit_répertoire(Dir répertoire);
qui permet d'obtenir l'entrée suivante du répertoire.
void ferme_répertoire(Dir répertoire);
qui libère le répertoire.

etc.

Bien entendu rien de cela n'est rééllement contractuel.