Mis à jour le 7 mars 2006
TD 2 : Entrées-sorties
On considère une application mini-base de données qui conserve des
données persistantes dans un fichier data. On suppose que
deux applications ne peuvent pas tourner en parallèle, mais
seulement l'une après l'autre. Une session lit les données en
mémoire, en modifie une partie puis réécrit le résultat dans le
fichier data.
Quel est le problème essentiel auquel il faut penser?
On voudrait qu'en cas de panne les données soit toujours correctes, ie.
le fichier data contiennent les nouvelles données correctement
écrites ou les anciennes données. Comment réaliser cela?
La solution est nécessairement imparfaite. Expliquez pourquoi.
Que manque-t-il pour s'en sortir?
On désire écrire une commande mon_cat
équivalente à la commande
cat
du système. Celle-ci copie sur sa sortie standard le contenu
des fichiers dont les noms lui sont donnés en argument.
Écrire une fonction à deux arguments
copy_data:Unix.file_descr -> Unix.file_descr -> unit
,
qui prend deux descripteurs
de fichier en argument et copie tout ce qui peut être lu sur le premier
dans le second, en utilisant les fonctions Unix.read
et
Unix.write
.
Écrire la commande mon_cat
, en utilisant la fonction précédente
sur chacun de ses arguments et en copiant dans la sortie standard
(Unix.stdout
). Pour simplifier, on considèrera toute erreur de
lecture/écriture comme fatale.
On désire écrire une commande mon_tr
qui remplace certains
caractères reçus sur l'entrée standard avant de les retourner sur la
sortie standard et qui a le même comportement que la commande
tr
du système (sans options). Par exemple, mon_tr abcd ijk
remplace les caractères a
, b
et c
par, respectivement, i
, j
et k
et le caractère d
par k
.
Modifier la fonction copy_data
précédente pour qu'elle prenne un
argument supplémentaire, une fonction de type char -> char
qu'elle
applique sur chaque caractère lu avant de le copier.
Écrire une fonction à deux arguments tr_function : string -> string -> (char -> char)
qui retourne une fonction de traduction
qui traduit chaque caractère qui lui sera passé en argument selon la
correspondance définie par les chaînes de caractères reçues en
premier et second argument de la fonction tr_function
(la longueur d'une chaîne de caractères est retournée par String.length
). Pour
effectuer la traduction, on utilisera une chaîne de caractères de
longueur 256 (créée par la fonction String.create 256
) telle que le caractère
à la position i dans cette chaîne (s.[i]
) corresponde à la
traduction du caractère de code i (Char.code c
).
Inversement, le caractère de code i est obtenu par Char.chr i
.
Ensuite, écrire la fonction principale qui lit sur l'entrée standard
(Unix.stdin
) et copie sur la sortie standard les données
après traduction. Pour simplifier, on considèrera toute erreur de
lecture/écriture comme fatale.
On désire écrire une commande mon_tail -n N file
qui affiche les
N
dernières lignes du fichier régulier file
. Pour cela, il faut
lire le fichier en « sens inverse ».
Décrire le schéma général du programme.
Écrire une fonction really_read
qui force la lecture
d'exactement size
caractères sur un descripteur déjà ouvert et qui
les place dans une chaîne de caractères à partir d'un indice passé
en argument. Si la lecture n'est pas possible, lever une exception
End_of_file
.
Écrire une fonction set_pos : Unix.file_descr -> int -> unit
qui
positionne le descripteur de fichier à la position donnée en argument et
vérifie que la position d'arrivée est correcte. On
utilisera la fonction Unix.lseek
pour se déplacer dans le fichier.
Écrire une fonction get_size : Unix.file_descr -> int
qui
retourne la taille du fichier associé à un descripteur.
Écrire une fonction find_lines
, telle que find_lines buffer size nb
retourne l'indice de début de la (nb-1)
-ème ligne en partant de
la fin (size
) de la chaîne de caractères buffer
. Si la chaîne de
caractères contient moins de nb
retours à la ligne la fonction lèvera
une exception contenant le nombre de lignes qu'il reste à lire.
Écrire une fonction récursive tail
, telle que
tail desc size nb
retourne la position absolue dans le fichier de la
(nb-1)
-ème ligne sur le descripteur
desc
à partir de la position absolue size
. Attention, il faut faire
attention à ne pas demander de lire plus que la taille disponible sur
le descripteur sous peine que really_read
lève une exception.
Finalement, écrire la fonction tail_lines
telle que
tail_lines desc nb
affiche les nb
dernières lignes du
descripteur desc
, en ne tenant pas compte du dernier caractère
du fichier. On pourra aussi utiliser la fonction copy_data
des
exercices précédents.
On désire ajouter un option -f
, équivalente à celle de la commande
tail
du système, qui permet d'afficher les modifications apportées au
fichier au fur et à mesure quelles sont effectuées. Donner le principe
ce cette fonction.
Écrire une fonction monitor
qui implante l'option -f
. On pourra
utiliser une fonction récursive intermédiaire qui affiche toutes les
données lues entre la position courante du descripteur et la fin de
fichier. On supposera pour cette fonction que le fichier ne peut pas
être tronqué.
Écrire une nouvelle fonction monitor qui prend en compte le fait que
le fichier puisse être tronqué. Dans ce cas, la commande doit
commencer à afficher les lignes à partir de la nouvelle taille du
fichier.
Terminer le programme. Attention, le fichier peut ne pas se terminer
par un retour à la ligne.
Écrire une commande mon_split n file
qui découpe le fichier file
en plusieurs fichiers (de noms file-0
, file-1
, etc.) de taille
n
(sauf éventuellement le dernier) et qui a un comportement
équivalent à la commande split
du système.
Pour réaliser cette commande, on suppose que la taille n
peut être
très grande et donc qu'il n'est pas envisageable d'allouer un tampon
de cette taille pour lire puis écrire en deux opérations. On
utilisera donc un tampon de taille fixe pour lire les données. Il
faut alors faire attention au cas ou la taille des fichiers est
inférieure à la taille des données lues.
On pourra en particulier modifier la fonction copy_data
des
exercices précédents en lui ajoutant en paramètre la quantité de caractères
à copier, et en retournant le nombre de caractères non-copiés (quand la
fin du fichier est atteinte).
Écrire une fonction mon_write
qui se comporte comme write
,
sans utiliser celle-ci! —on utilisera seuleument single_write
.
Rappeler un des problèmes de single_write
? Comment pourriez-vous
corriger celui-ci?
This document was translated from LATEX by
HEVEA and HACHA.