Traitement des erreurs
Sauf mention du contraire, toutes les fonctions du module Unix declenchent l’exception Unix_error en cas d’erreur. exception Unix_error of error * string * string Le deuxi`eme argument de l’exception Unix_error est le nom de l’appel syst`eme qui a d´eclench´e l’erreur. Le troisi`eme argument identifie, si possible, l’objet sur lequel l’erreur s’est produite; par exemple, pour un appel syst`eme qui prend en argument un nom de fichier, c’est ce nom qui se retrouve en troisi`eme position dans Unix_error. Enfin, le premier argument de l’exception est un code d’erreur, indiquant la nature de l’erreur. Il appartient au type concret ´enum´er´e error (voir page 138 pour une description compl`ete) : type error = E2BIG | EACCES | EAGAIN | … | EUNKNOWNERR of int Les constructeurs de ce type reprennent les memes noms et les memes significations que ceux employes dans la norme POSIX plus certaines erreurs de UNIX98 et BSD. Toutes les autres erreurs sont rapportees avec le constructeur EUNKNOWNERR. ´ Etant donn´e la s´emantique des exceptions, une erreur qui n’est pas sp´ecialement pr´evue et interceptee par un try se propage jusqu’au sommet du programme, et le termine pr´ematur´ement. Qu’une erreur imprevue soit fatale, c’est g´en´eralement la bonne s´emantique pour des petites applications. Il convient n´eanmoins de l’afficher de mani`ere claire. Pour ce faire, le module Unix fournit la fonctionnelle handle_unix_error. val handle_unix_error : (’a -> ’b) -> ’a -> ’b L’appel handle_unix_error f x applique la fonction f `a l’argument x. Si cette application d´eclenche l’exception Unix_error, un message d´ecrivant l’erreur est affiche, et on sort par exit 2. L’utilisation typique est handle_unix_error prog ();; ou` la fonction prog : unit -> unit ex´ecute le corps du programme prog. Pour reference, voici comment est implementee handle_unix_error. 1 open Unix;; 2 let handle_unix_error f arg = 3 try 4 f arg 5 with Unix_error(err, fun_name, arg) -> 6 prerr_string Sys.argv.(0); 7 prerr_string « : \ » »; 8 prerr_string fun_name; 9 prerr_string « \ » failed »; 10 if String.length arg > 0 then begin 11 prerr_string » on \ » »; 12 prerr_string arg; 13 prerr_string « \ » » 14 end; 15 prerr_string « : « ; 16 prerr_endline (error_message err); 17 exit 2;; Les fonctions de la forme prerr_xxx sont comporte comme les fonction print_xxx mais `a la diff´erence qu’elles ´ecrivent dans le flux d’erreur stderr au lieu d’´ecrire dans le flux standard stdout. De plus prerr_endline vide le tampon stderr (alors que print_endline ne le fait pas).
Fonctions de bibliotheque
Nous verrons au travers d’exemples que la programmation systeme reproduit souvent les memes motifs. Nous seront donc souvent tentes de d´efinir des fonctions de bibliotheque permettant de factoriser les parties communes et ainsi de r´eduire le code de chaque application `a sa partie essentielle. Alors que dans un programme complet on connait precisement les erreurs qui peuvent etre levees et celles-ci sont souvent fatales (on arrˆete le programme), on ne connait pas en general le contexte d’execution d’une fonction de bibliotheque. On ne peut pas supposer que les erreurs sont fatales. Il faut donc laisser l’erreur retourner a l’appelant qui pourra d´ecider d’une action appropri´ee (arreter le programme, traiter ou ignorer l’erreur). Cependant, la fonction de libraire ne va pas en g´en´eral pas se contenter de regarder l’erreur passer, elle doit maintenir le systeme dans un etat coherent. Par exemple, une fonction de biblioth`eque qui ouvre un fichier puis applique une op´eration sur le descripteur associe a ce fichier devra prendre soin de refermer le descripteur dans tous les cas de figure, y compris lorsque le traitement du fichier provoque une erreur. Ceci afin d’´eviter une fuite m´emoire conduisant `a l’´epuisement des descripteurs de fichiers. De plus le traitement appliqu´e au fichier peut ˆetre donn´e par une fonction rec¸u en argument et on ne sait donc pas pr´ecis´ement quand ni comment le traitement peut´echouer (mais l’appelant en g´en´eral le sait). On sera donc souvent amen´e `a prot´eger le corps du traitement par un code dit de ≪finalisation≫ qui devra etre execute juste avant le retour de la fonction que celui-ci soit normal ou exceptionnel. Il n’y a pas de construction primitive de finalisation try … finalize dans le langage OCaml mais on peut facilement la d´efinir 1 : let try_finalize f x finally y = let res = try f x with exn -> finally y; raise exn in finally y; res Cette fonction rec¸oit le corps principal f et le traitement de finalisation finally, chacun sous la forme d’une fonction, et deux param`etres x et y a passer respectivement a chacune des deux fonctions pour les lancer. Le corps du programme f x est execute en premier et son resultat est:
1. Une construction primitive n’en serait pas moins avantageuse.
gard´e de cot´e pour ˆetre retourn´e apr`es l’ex´ecution du code de finalisation finally y. Lorsque le corps du programme ´echoue, i.e. l`eve une exception exn, alors le code de finalisation est execute puis l’exception exn est relanc´ee. Si `a la fois le code principal et le code de finalisation echouent, l’exception lanc´ee est celle du code de finalisation (on pourrait faire le choix inverse).