176
Programmation du syst` eme Unix en Objective Caml Xavier Leroy et Didier R´ emy 1 c 1991, 1992, 2003, 2004, 2005, 2006, 2008. 2 1. INRIA Rocquencourt 2. Droits r´ eserv´ es. Distribut´ e sous licence Creative Commons Paternit´ e–Pas d’Utilisation Commerciale– Partage des Conditions Initiales ` a l’Identique 2.0 France. Voir http://creativecommons.org/ licenses/by-nc-sa/2.0/fr/. pour les termes l´ egaux.

Cours Program Mat Ion Du Systeme Unix en OCaml

Embed Size (px)

DESCRIPTION

Cours Programmation du Systeme et Ocalm

Citation preview

Programmation du syst`me Unix e en Objective CamlXavier Leroy et Didier Rmy1 ec 1991, 1992, 2003, 2004, 2005, 2006, 2008.2

1. INRIA Rocquencourt 2. Droits rservs. Distribut sous licence Creative Commons PaternitPas dUtilisation Commerciale e e e e Partage des Conditions Initiales ` lIdentique 2.0 France. a Voir http://creativecommons.org/ licenses/by-nc-sa/2.0/fr/. pour les termes lgaux. e

Rsum e e Ce document est un cours dintroduction ` la programmation du syst`me Unix, a e mettant laccent sur la communication entre les processus. La principale nouveaut e de ce travail est lutilisation du langage Objective Caml, un dialecte du langage ML, a ` la place du langage C qui est dordinaire associ ` la programmation syst`me. Ceci ea e donne des points de vue nouveaux ` la fois sur la programmation syst`me et sur le a e langage ML.

Unix system programming in Objective Caml This document is an introductory course on Unix system programming, with an emphasis on communications between processes. The main novelty of this work is the use of the Objective Caml language, a dialect of the ML language, instead of the C language that is customary in systems programming. This gives an unusual perspective on systems programming and on the ML language.

2

Table des mati`res e1 Gnralits e e e 1.1 Les modules Sys et Unix . . 1.2 Interface avec le programme 1.3 Traitement des erreurs . . . 1.4 Fonctions de biblioth`que . e 7 . 7 . 8 . 9 . 10 13 13 15 15 18 19 21 23 24 25 26 27 30 31 34 34 36 43 43 44 44 46 47 51 51 52 53 54 55 57 59

. . . . . . appelant . . . . . . . . . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

2 Les chiers 2.1 Le syst`me de chiers . . . . . . . . . . . . . . . . . . . . . e 2.2 Noms de chiers, descripteurs de chiers . . . . . . . . . . . 2.3 Mta-donnes, types et permissions . . . . . . . . . . . . . . e e 2.4 Oprations sur les rpertoires . . . . . . . . . . . . . . . . . e e 2.5 Exemple complet : recherche dans la hirarchie . . . . . . . e 2.6 Ouverture dun chier . . . . . . . . . . . . . . . . . . . . . 2.7 Lecture et criture . . . . . . . . . . . . . . . . . . . . . . . e 2.8 Fermeture dun descripteur . . . . . . . . . . . . . . . . . . 2.9 Exemple complet : copie de chiers . . . . . . . . . . . . . . 2.10 Cot des appels syst`me. Les tampons. . . . . . . . . . . . . u e 2.11 Exemple complet : une petite biblioth`que dentres-sorties e e 2.12 Positionnement . . . . . . . . . . . . . . . . . . . . . . . . . 2.13 Oprations spciques ` certains types de chiers . . . . . . e e a 2.14 Verrous sur des chiers . . . . . . . . . . . . . . . . . . . . . 2.15 Exemple complet : copie rcursive de chiers . . . . . . . . e 2.16 Exemple : Tape ARchive . . . . . . . . . . . . . . . . . . . 3 Les 3.1 3.2 3.3 3.4 3.5 4 Les 4.1 4.2 4.3 4.4 4.5 4.6 4.7 processus Cration de processus . . . . . . . . . . e Exemple complet : la commande leave Attente de la terminaison dun processus Lancement dun programme . . . . . . . Exemple complet : un mini-shell . . . . signaux Le comportement par dfaut e Produire des signaux . . . . . Changer leet dun signal . . Masquer des signaux . . . . . Signaux et appels-syst`me . . e Le temps qui passe . . . . . . Probl`mes avec les signaux . e

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . . 3

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

. . . . . . .

5 Communications inter-processus classiques 5.1 Les tuyaux . . . . . . . . . . . . . . . . . . . . . . 5.2 Exemple complet : le crible dEratosth`ne parall`le e e 5.3 Les tuyaux nomms . . . . . . . . . . . . . . . . . e 5.4 Redirections de descripteurs . . . . . . . . . . . . . 5.5 Exemple complet : composer N commandes . . . . 5.6 Multiplexage dentres-sorties . . . . . . . . . . . . e 5.7 Miscelleaneous : write . . . . . . . . . . . . . . . . 6 Communications modernes : les prises 6.1 Les prises . . . . . . . . . . . . . . . . 6.2 Cration dune prise . . . . . . . . . . e 6.3 Adresses . . . . . . . . . . . . . . . . . 6.4 Connexion ` un serveur . . . . . . . . a 6.5 Dconnexion . . . . . . . . . . . . . . e 6.6 Exemple complet : Le client universel 6.7 Etablissement dun service . . . . . . . 6.8 Rglage des prises . . . . . . . . . . . e 6.9 Exemple complet : le serveur universel 6.10 Communication en mode dconnect . e e 6.11 Primitives de haut niveau . . . . . . . 6.12 Exemples de protocoles . . . . . . . . 6.13 Exemple complet : requtes http . . . e 7 Les 7.1 7.2 7.3 7.4 7.5 7.6 7.7 7.8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . .

61 61 63 66 66 68 70 74 77 78 79 80 80 81 81 83 85 86 88 88 89 93

coprocessus Gnralits . . . . . . . . . . . . . . . . . . . . . . . . . . . . e e e Cration et terminaison des coprocessus . . . . . . . . . . . . e Mise en attente . . . . . . . . . . . . . . . . . . . . . . . . . . Synchronisation entre coprocessus : les verrous . . . . . . . . Exemple complet : relais HTTP . . . . . . . . . . . . . . . . . Les conditions . . . . . . . . . . . . . . . . . . . . . . . . . . . Communication synchrone entre coprocessus par vnements e e Quelques dtails dimplmentation . . . . . . . . . . . . . . . e e

101 . 101 . 102 . 103 . 105 . 108 . 109 . 111 . 114 121

A Corrig des exercices e B Interfaces B.1 Module B.2 Module B.3 Module B.4 Module B.5 Module B.6 Module B.7 Module C Index Sys : System interface. . . . . . . . . . . . . . . . . . . . . . . . . Unix : Interface to the Unix system . . . . . . . . . . . . . . . . . Thread : Lightweight threads for Posix 1003.1c and Win32. . . . Mutex : Locks for mutual exclusion. . . . . . . . . . . . . . . . . . Condition : Condition variables to synchronize between threads. Event : First-class synchronous communication. . . . . . . . . . . Misc : miscelleaneous functions for the Unix library . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

135 135 138 166 168 169 170 171 173

4

IntroductionCes notes sont issues dun cours de programmation syst`me que Xavier Leroy a enseign en e e premi`re anne du Magist`re de Mathmatiques Fondamentales et Appliques et dInformatique e e e e e de lEcole Normale Suprieure en 1994. Cette premi`re version utilisait le langage Caml-Light [1]. e e Didier Rmy en a fait une traduction pour le langage OCaml [2] pour un cours enseign en e e ` Majeure dInformatique ` lEcole Polytechnique de 2003 ` 2006. A cette occasion, Gilles Roussel, a a Fabrice Le Fessant et Maxence Guesdon qui ont aid ` ce cours ont galement contribu ` e a e e a amliorer ces notes. Cette version comporte des ajouts et quelques mises ` jour : en presque e a une dcennie certains ordres de grandeur ont dcal leur virgule dun chire ; aussi, la toile e e e tait seulement en train dtre tisse et lexemple, aujourdhui classique, du relais HTTP aurait e e e presqueut un cot prcurseur en 1994. Mais surtout le langage OCaml a gagn en maturit e e e e depuis et a t utilis dans de vritables applications syst`me, telles que Unison [16]. ee e e e La tradition veut que la programmation du syst`me Unix se fasse dans le langage C. Dans e le cadre de ce cours, il a sembl plus intressant dutiliser un langage de plus haut niveau, Caml e e en loccurrence, pour expliquer la programmation du syst`me Unix. e La prsentation Caml des appels syst`mes est plus abstraite, utilisant toute la puissance de e e lalg`bre de types de ML pour reprsenter de mani`re claire les arguments et les rsultats, au e e e e lieu de devoir tout coder en termes dentiers et de champs de bits comme en C. En consquence, e il est plus facile dexpliquer la smantique des appels syst`mes, sans avoir ` se perdre dans e e a les dtails de lencodage des arguments et des rsultats. (Voir par exemple la prsentation de e e e lappel wait, page 44.) De plus, OCaml apporte une plus grande scurit de programmation que C, en particulier e e grce au typage statique et ` la clart de ses constructions de base. Ces traits, qui peuvent a a e appara tre au programmeur C chevronn comme de simples lments de confort, se rv`lent e ee e e cruciaux pour les programmeurs inexpriments comme ceux auxquels ce cours sadresse. e e Un deuxi`me but de cette prsentation de la programmation syst`me en OCaml est de mone e e trer le langage OCaml ` luvre dans un domaine qui sort nettement de ses applications usuelles, a ` savoir la dmonstration automatique, la compilation, et le calcul symbolique. OCaml se tire a e plutt bien de lexprience, essentiellement grce ` son solide noyau impratif, complt ponco e a a e ee tuellement par les autres traits plus novateurs du langage (polymorphisme, fonctions dordre suprieur, exceptions). Le fait que OCaml combine programmation applicative et programmae tion imprative, au lieu de les exclure mutuellement, rend possible lintgration dans un mme e e e programme de calculs symboliques compliqus et dune bonne interface avec le syst`me. e e Ces notes supposent le lecteur familier avec le syst`me OCaml, et avec lutilisation des e commandes Unix, en particulier du shell. On se reportera ` la documentation du syst`me OCaml a e [2] pour toutes les questions relatives au langage, et ` la section 1 du manuel Unix ou ` un livre a a dintroduction ` Unix [5, 6] pour toutes les questions relatives ` lutilisation dUnix. a a On dcrit ici uniquement linterface programmatique du syst`me Unix, et non son imple e e mentation ni son architecture interne. Larchitecture interne de BSD 4.3 est dcrite dans [8] ; e celle de System V, dans [9]. Les livres de Tanenbaum [11, 12] donnent une vue densemble des 5

architectures de syst`mes et de rseaux. e e Le syst`me Objective OCaml dont la biblioth`que dinterface avec Unix prsente ici fait e e e e partie intgrante est en acc`s libre ` lURL http://caml.inria.fr/ocaml/. e e a

6

Chapitre 1

Gnralits e e e1.1 Les modules Sys et Unix

Les fonctions qui donnent acc`s au syst`me depuis OCaml sont regroupes dans deux moe e e dules. Le premier module, Sys, contient les quelques fonctions communes ` Unix et aux autres a syst`mes dexploitation sous lesquels tourne OCaml. Le second module, Unix, contient tout ce e qui est spcique ` Unix. On trouvera dans lannexe C linterface du module Unix. e a Par la suite, on fait rfrence aux identicateurs des modules Sys et Unix sans prciser de ee e quel module ils proviennent. Autrement dit, on suppose quon est dans la porte des directives e open Sys et open Unix. Dans les exemples complets (ceux dont les lignes sont numrotes), on e e met explicitement les open, an dtre vraiment complet. e Les modules Sys et Unix peuvent rednir certains identicateurs du module Pervasives et e cacher leur anciennes dnitions. Par exemple, Pervasives.stdin est dirent de Unix.stdin. e e Les anciennes dnitions peuvent toujours tre obtenues en les prxant. e e e Pour compiler un programme OCaml qui utilise la biblioth`que Unix, il faut faire : e ocamlc -o prog unix.cma mod1.ml mod2.ml mod3.ml en supposant que le programme prog est compos des trois modules mod1, mod2 et mod3. On e peut aussi compiler sparment les modules : e e ocamlc -c mod1.ml ocamlc -c mod2.ml ocamlc -c mod3.ml puis faire pour ldition de lien : e ocamlc -o prog unix.cma mod1.cmo mod2.cmo mod3.cmo Dans les deux cas, largument unix.cma reprsente la biblioth`que Unix crite en OCaml. e e e Pour utiliser le compilateur natif plutt que le bytecode, on remplace ocamlc par ocamlopt o et unix.cma par unix.cmxa. On peut aussi accder au syst`me Unix depuis le syst`me interactif (le toplevel). Si le lien e e e dynamique des biblioth`ques C est possible sur votre plate-forme, il sut de lancer le toplevel e ocaml et de taper la directive #load "unix.cma";; Sinon, il faut dabord crer un syst`me interactif contenant les fonctions syst`mes pr-charges : e e e e e ocamlmktop -o ocamlunix unix.cma Ce syst`me se lance ensuite par : e ./camlunix 7

1.2

Interface avec le programme appelant

Lorsquon lance un programme depuis un shell (interprteur de commandes), le shell transe met au programme des arguments et un environnement. Les arguments sont les mots de la ligne de commande qui suivent le nom de la commande. Lenvironnement est un ensemble de cha nes de la forme variable=valeur, reprsentant les liaisons globales de variables denvie ronnements : les liaisons faites avec setenv var=val dans le cas du shell csh, ou bien avec var=val; export var dans le cas du shell sh. Les arguments passs au programme sont placs dans le vecteur de cha e e nes argv : Sys.argv : string array Lenvironnement du programme tout entier sobtient par la fonction environment : Unix.environment : unit -> string array Une mani`re plus commode de consulter lenvironnement est par la fonction getenv : e Unix.getenv : string -> string getenv v renvoie la valeur associe ` la variable de nom v dans lenvironnement, et dclenche e a e lexception Not_found si cette variable nest pas lie. e Exemple: Comme premier exemple, voici le programme echo qui ache la liste de ses arguments, comme le fait la commande Unix de mme nom. e 1 let echo() = 2 let len = Array.length Sys.argv in 3 if len > 1 then 4 begin 5 print_string Sys.argv.(1); 6 for i = 2 to len - 1 do 7 print_char ; 8 print_string Sys.argv.(i); 9 done; 10 print_newline(); 11 end;; 12 echo();; Un programme peut terminer prmaturment par lappel exit : e e val exit : int -> a Largument est le code de retour ` renvoyer au programme appelant. La convention est de rena voyer zro comme code de retour quand tout sest bien pass, et un code de retour non nul e e pour signaler une erreur. Le shell sh, dans les constructions conditionnelles, interpr`te le code e de retour 0 comme le boolen vrai et tout code de retour non nul comme le boolen faux. e e Lorsquun programme termine normalement apr`s avoir excut toutes les phrases qui le come e e posent, il eectue un appel implicite ` exit 0. Lorsquun programme termine prmaturment a e e parce quune exception leve na pas t rattrape, il eectue un appel implicite ` exit 2. La e ee e a fonction exit vide toujours les tampons des canaux ouverts en criture. La fonction at_exit e permet denregistrer dautres actions ` eectuer au moment de la terminaison du programme. a val at_exit : (unit -> unit) -> unit La fonction enregistre la derni`re est appele en premier. Lenregistrement dune fonction e e e avec at_exit ne peut pas tre ultrieurement annul. Cependant, ceci nest pas une vritable e e e e restriction, car on peut facilement obtenir cet eet en enregistrant une fonction dont lexcution e dpend dune variable globale. e 8

1.3

Traitement des erreurs

Sauf mention du contraire, toutes les fonctions du module Unix dclenchent lexception e Unix_error en cas derreur. exception Unix_error of error * string * string Le deuxi`me argument de lexception Unix_error est le nom de lappel syst`me qui a dclench e e e e lerreur. Le troisi`me argument identie, si possible, lobjet sur lequel lerreur sest produite ; e par exemple, pour un appel syst`me qui prend en argument un nom de chier, cest ce nom qui e se retrouve en troisi`me position dans Unix_error. Enn, le premier argument de lexception e est un code derreur, indiquant la nature de lerreur. Il appartient au type concret numr e ee error (voir page 138 pour une description compl`te) : e type error = E2BIG | EACCES | EAGAIN | ... | EUNKNOWNERR of int Les constructeurs de ce type reprennent les mmes noms et les mmes signications que ceux e e employs dans la norme POSIX plus certaines erreurs de UNIX98 et BSD. Toutes les autres e erreurs sont rapportes avec le constructeur EUNKNOWNERR. e Etant donn la smantique des exceptions, une erreur qui nest pas spcialement prvue et e e e e intercepte par un try se propage jusquau sommet du programme, et le termine prmaturment. e e e Quune erreur imprvue soit fatale, cest gnralement la bonne smantique pour des petites e e e e applications. Il convient nanmoins de lacher de mani`re claire. Pour ce faire, le module Unix e e fournit la fonctionnelle handle_unix_error. val handle_unix_error : (a -> b) -> a -> b Lappel handle_unix_error f x applique la fonction f ` largument x. Si cette application a dclenche lexception Unix_error, un message dcrivant lerreur est ach, et on sort par e e e exit 2. Lutilisation typique est handle_unix_error prog ();; o` la fonction prog : unit -> unit excute le corps du programme prog. u e Pour rfrence, voici comment est implmente handle_unix_error. ee e e 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 ` la a dirence quelles crivent dans le ux derreur stderr au lieu dcrire dans le ux standard e e e stdout. De plus prerr_endline vide le tampon stderr (alors que print_endline ne le fait pas). 9

La primitive error_message, de type error -> string, renvoie un message dcrivant lere reur donne en argument (ligne 16). Largument numro zro de la commande, Sys.argv.(0), e e e contient le nom de commande utilis pour invoquer le programme (ligne 6). e La fonction handle_unix_error traite des erreurs fatales, i.e. des erreurs qui arrtent le e programme. Cest un avantage de OCaml dobliger les erreurs ` tre prises en compte, ne seraitae ce quau niveau le plus haut provoquant larrt du programme. En eet, toute erreur dans un e appel syst`me l`ve une exception, et le l dexcution en cours est interrompu jusqu` un niveau e e e a o` elle est explicitement rattrape et donc traite. Cela vite de continuer le programme dans u e e e une situation incohrente. e Les erreurs de type Unix_error peuvent aussi, bien sr, tre ltre slectivement. Par u e e e exemple, on retrouvera souvent plus loin la fonction suivante let rec restart_on_EINTR f x = try f x with Unix_error (EINTR, _, _) -> restart_on_EINTR f x qui est utilis pour excuter une fonction et la relancer automatiquement lorsque elle est intere e rompue par un appel syst`me (voir 4.5). e

1.4

Fonctions de biblioth`que e

Nous verrons au travers dexemples que la programmation syst`me reproduit souvent les e mmes motifs. Nous seront donc souvent tents de dnir des fonctions de biblioth`que permete e e e tant de factoriser les parties communes et ainsi de rduire le code de chaque application ` sa e a partie essentielle. Alors que dans un programme complet on conna prcisment les erreurs qui peuvent tre t e e e leves et celles-ci sont souvent fatales (on arrte le programme), on ne conna pas en gnral e e t e e le contexte dexcution dune fonction de biblioth`que. On ne peut pas supposer que les erreurs e e sont fatales. Il faut donc laisser lerreur retourner ` lappelant qui pourra dcider dune action a e approprie (arrter le programme, traiter ou ignorer lerreur). Cependant, la fonction de libraire e e ne va pas en gnral pas se contenter de regarder lerreur passer, elle doit maintenir le syst`me e e e dans un tat cohrent. Par exemple, une fonction de biblioth`que qui ouvre un chier puis e e e applique une opration sur le descripteur associ ` ce chier devra prendre soin de refermer e e a le descripteur dans tous les cas de gure, y compris lorsque le traitement du chier provoque une erreur. Ceci an dviter une fuite mmoire conduisant ` lpuisement des descripteurs de e e a e chiers. De plus le traitement appliqu au chier peut tre donn par une fonction reu en argument e e e c et on ne sait donc pas prcisment quand ni comment le traitement peut chouer (mais lappelant e e e en gnral le sait). On sera donc souvent amen ` protger le corps du traitement par un code e e ea e dit de nalisation qui devra tre excut juste avant le retour de la fonction que celui-ci soit e e e normal ou exceptionnel. Il ny a pas de construction primitive de nalisation try ... finalize dans le langage OCaml mais on peut facilement la dnir 1 : e let try_finalize f x finally y = let res = try f x with exn -> finally y; raise exn in finally y; res Cette fonction reoit le corps principal f et le traitement de nalisation finally, chacun sous c la forme dune fonction, et deux param`tres x et y ` passer respectivement ` chacune des deux e a a fonctions pour les lancer. Le corps du programme f x est excut en premier et son rsultat est e e e1. Une construction primitive nen serait pas moins avantageuse.

10

gard de cot pour tre retourn apr`s lexcution du code de nalisation finally y. Lorsque le e e e e e e corps du programme choue, i.e. l`ve une exception exn, alors le code de nalisation est excut e e e e puis lexception exn est relance. Si ` la fois le code principal et le code de nalisation chouent, e a e lexception lance est celle du code de nalisation (on pourrait faire le choix inverse). e Note Dans le reste du cours, nous utiliserons une biblioth`que auxiliaire Misc qui regroupe e quelques fonctions dusage gnral, telle que try_finalize, souvent utilises dans les exemples e e e et que nous introduirons au besoin. Linterface du module Misc est donne en appendice B.7. e Pour compiler les exemples du cours, il faut donc dans un premier temps rassembler les dnitions e du module Misc et le compiler. Le module Misc contient galement certaines fonctions ajoutes ` titre dillustration qui e e a ne sont pas utilises directement dans le cours. Elles enrichissent simplement la biblioth`que e e Unix ou en rednissent le comportement de certaines fonctions. Le module Misc doit prendre e priorit sur le module Unix. e Exemples Le cours comporte de nombreux exemples. Ceux-ci ont t compils avec OCaml, ee e version 3.10). Certains programmes doivent tre lg`rement modis pour tre adapts ` une e e e e e e a version plus ancienne. Les exemples sont essentiellement de deux types : soit ce sont des fonctions rutilisables e fonctions de biblioth`que, soit ce sont de petites applications. Il dusage assez gnral, dites e e e est important de faire la dirence entre ces deux types dexemples. Dans le premier cas, on e voudra laisser le contexte dutilisation de la fonction le plus large possible, et on prendra donc soin de bien spcier son interface et de bien traiter tous les cas particuliers. Dans le second e cas, une erreur est souvent fatale et entra larrt du programme en cours. Il sut alors de ne e rapporter correctement la cause de lerreur, sans quil soit besoin de revenir ` un tat cohrent, a e e puisque le programme sera arrt immdiatement apr`s le report de lerreur. ee e e

11

12

Chapitre 2

Les chiersLe terme chier en Unix recouvre plusieurs types dobjets : les chiers normaux : les suites nies doctets contenant du texte ou des informations binaires quon appelle dordinaire chiers les rpertoires e les liens symboliques les chiers spciaux (devices), qui donnent en particulier acc`s aux priphriques de la e e e e machine les tuyaux nomms (named pipes) e les prises (sockets) nommes dans le domaine Unix. e La reprsentation dun chier contient ` la fois les donnes contenues dans le chier et des e a e informations sur le chier (aussi appeles mta-donnes) telles que son type, les droits dacc`s, e e e e les derni`res dates dacc`s, etc. e e

2.1

Le syst`me de chiers e

En premi`re approximation, le syst`me de chier est un arbre. La racine est note /. Les e e e arcs sont tiquets par des noms (de chiers), forms dune cha de caract`res quelconques e e e ne e ` lexception des seuls caract`res \000 et /, mais il est de bon usage dviter galement a e e e les caract`res non imprimables ainsi que les espaces. Les nuds non terminaux du syst`me de e e chiers sont appels rpertoires : il contiennent toujours deux arcs et e e qui dsignent respece tivement le rpertoire lui-mme et le rpertoire parent. Les autres nuds sont parfois appels e e e e chiers, par opposition aux rpertoires, mais cela reste ambigu, car on peut aussi dsigner par e e chier un nud quelconque. Pour viter toute ambigu e, on pourra parler de chiers non e t rpertoires . e Les nuds du syst`me de chiers sont dsigns par des chemins. Ceux-ci peuvent se rfrer e e e ee ` lorigine de la hirarchie et on parlera de chemins absolus, ou ` un rpertoire (en gnral le a e a e e e rpertoire de travail). Un chemin relatif est une suite de noms de chiers spars par le caract`re e e e e / ; un chemin absolu est un chemin relatif prcd par le caract`re / (notez le double usage e e e e de ce caract`re comme sparateur de chemin et comme le nom de la racine). e e La biblioth`que Filename permet de manipuler les chemins de faon portable. Notamment e c Filename.concat permet de concatner des chemins sans faire rfrence au caract`re /, ce qui e ee e permettra au code de fonctionner galement sur dautres architectures (par exemple le caract`re e e de sparation des chemins est \ sous Windows). De mme, le module Filename donne des e e noms currentdir et parentdir pour dsigner les arcs et . Les fonctions Filename.basename e et Filename.dirname extraient dun chemin p un prxe d et un suxe b tel que les chemins e p et d/b dsignent le mme chier, d dsigne le rpertoire dans lequel se trouve le chier et b le e e e e 13

3 2 bin [pdf] ./ 1 usr tmp 9 5 bin lib /usr foo bar ls cd 4 cc 6 8 10 11 ? /gnu gcc 7

Liens inverses omis Liens durs 7 a deux antcdents 2 et 6 e e Liens symboliques 10 dsigne 5 e 11 ne dsigne aucun nud e Chemins quivalents de 9 ` 8 ? e a /usr/lib / /usr/lib, etc. foo/lib

Figure 2.1 Un petit exemple de hirarchie de chiers e

nom du chier dans ce rpertoire. Les oprations dnies dans Filename op`rent uniquement e e e e sur les chemins indpendemment de leur existence dans la hirarchie. e e En fait, la hirarchie nest pas un arbre. Dabord les rpertoires conventionnels et e e permettent de sauto-rfrencer et de remonter dans la hirarchie, donc de crer des chemins ee e e menant dun rpertoire ` lui-mme. Dautre part les chiers non rpertoires peuvent avoir e a e e plusieurs antcdents. On dit alors quil a plusieurs liens durs. Enn, il existe aussi des liens e e symboliques qui se prtent ` une double interprtation. Un lien symbolique est un chier e a e non rpertoire dont le contenu est un chemin. On peut donc interprter un lien symbolique e e comme un chier ordinaire et simplement lire son contenu, un lien. Mais on peut aussi suivre le lien symbolique de faon transparente et ne voir que le chier cible. Cette derni`re est la c e seule interprtation possible lorsque le lien appara au milieu dun chemin : Si s est un lien e t symbolique dont la valeur est le chemin , alors le chemin p/s/q dsigne le chier /q si est e un lien absolu ou le chier ou p//q si est un lien relatif. La gure 2.1 donne un exemple de hirarchie de chiers. Le lien symbolique 11 dsign par le e e e chemin /tmp/bar, dont la valeur est le chemin relatif /gnu, ne dsigne aucun chier existant e dans la hirarchie (` cet instant). e a En gnral un parcours rcursif de la hirarchie eectue une lecture arborescente de la e e e e hirarchie : e les rpertoires currentdir et parentdir sont ignors. e e les liens symboliques ne sont pas suivis. Si lon veut suivre les liens symboliques, on est alors ramen ` un parcourt de graphe et il faut ea garder trace des nuds dj` visits et des nuds en cours de visite. ea e Chaque processus a un rpertoire de travail. Celui-ci peut tre consult par la commande e e e getcwd et chang par la commande chdir. Il est possible de restreindre la vision de la hirarchie. e e Lappel chroot p fait du nud p, qui doit tre un rpertoire, la racine de la hirarchie. Les e e e chemins absolus sont alors interprts par rapport ` la nouvelle racine (et le chemin appliqu ee a e a ` la nouvelle racine reste bien entendu a la racine). ` 14

2.2

Noms de chiers, descripteurs de chiers

Il y a deux mani`res daccder ` un chier. La premi`re est par son nom, ou chemin dacc`s e e a e e ` lintrieur de la hirarchie de chiers. Un chier peut avoir plusieurs noms dirents, du fait a e e e des liens durs. Les noms sont reprsents par des cha e e nes de caract`res (type string). Voici e quelques exemples dappels syst`me qui op`rent au niveau des noms de chiers : e e unlink f link f1 f2 symlink f1 f2 rename f1 f2 eace le chier de nom f (comme la commande rm -f f ) cre un lien dur nomm f2 sur le chier de nom e e f1 (comme la commande ln f1 f2 ) cre un lien symbolique nomm f2 sur le chier e e de nom f1 (comme la commande ln -s f1 f2 ) renomme en f2 le chier de nom f1 (comme la commande mv f1 f2 ).

Lautre mani`re daccder ` un chier est par lintermdiaire dun descripteur. Un descripteur e e a e reprsente un pointeur vers un chier, plus des informations comme la position courante de e lecture/criture dans ce chier, des permissions sur ce chier (peut-on lire ? peut-on crire ?), et e e des drapeaux gouvernant le comportement des lectures et des critures (critures en ajout ou e e en crasement, lectures bloquantes ou non). Les descripteurs sont reprsents par des valeurs e e e du type abstrait file_descr. Les acc`s ` travers un descripteur sont en grande partie indpendants des acc`s via le nom e a e e du chier. En particulier, lorsquon a obtenu un descripteur sur un chier, le chier peut tre e dtruit ou renomm, le descripteur pointera toujours sur le chier dorigine. e e Au lancement dun programme, trois descripteurs ont t prallous et lis aux variables ee e e e stdin, stdout et stderr du module Unix : stdin : file_descr lentre standard du processus e stdout : file_descr la sortie standard du processus stderr : file_descr la sortie derreur standard du processus Lorsque le programme est lanc depuis un interprteur de commandes interactif et sans redie e rections, les trois descripteurs font rfrence au terminal. Mais si, par exemple, lentre a t ee e ee redirige par la notation cmd < f , alors le descripteur stdin fait rfrence au chier de nom f e ee pendant lexcition de la commande cmd. De mme cmd > f (respectivement cmd 2> f ) fait e e en sorte que le descripteur stdout (respectivement stderr) fasse refrence au chier f pendant e lexcution de la commande cmd. e

2.3

Mta-donnes, types et permissions e e

Les appels syst`me stat, lstat et fstat retournent les mta-donnes sur un chier, ceste e e `-dire les informations portant sur le nud lui-mme plutt que son contenu. Entre autres, ces a e o informations dcrivent lidentit du chier, son type du chier, les droits dacc`s, les dates des e e e derniers dacc`s, plus un certain nombre dinformations supplmentaires. e e val stat : string -> stats val lstat : string -> stats val fstat : file_descr -> stats Les appels stat et lstat prennent un nom de chier en argument. Lappel fstat prend en argument un descripteur dj` ouvert et donne les informations sur le chier quil dsigne. La ea e dirence entre stat et lstat se voit sur les liens symboliques : lstat renvoie les informations e 15

st_dev : int st_ino : int

st_kind : file_kind

Un identicateur de la partition disque o` se trouve le chier u Un identicateur du chier ` lintrieur de sa partition. Le a e couple (st_dev, st_ino) identie de mani`re unique un e chier dans le syst`me de chier. e Le type du chier. Le type file_kind est un type concret numr, de constructeurs : e ee S_REG S_DIR S_CHR S_BLK S_LNK S_FIFO S_SOCK chier normal rpertoire e chier spcial de type caract`re e e chier spcial de type bloc e lien symbolique tuyau prise

st_perm : int st_nlink : int st_uid : int st_gid : int st_rdev : int st_size : int st_atime : int st_mtime : int st_ctime : int

Les droits dacc`s au chier e Pour un rpertoire : le nombre dentres dans le rpertoire. e e e Pour les autres : le nombre de liens durs sur ce chier. Le numro de lutilisateur propritaire du chier. e e Le numro du groupe propritaire du chier. e e Lidenticateur du priphrique associ (pour les chiers e e e spciaux). e La taille du chier, en octets. La date du dernier acc`s au contenu du chier. (En secondes e ier janvier 1970, minuit). depuis le 1 La date de la derni`re modication du contenu du chier. e (Idem.) La date du dernier changement de ltat du chier : ou e bien criture dans le chier, ou bien changement des droits e dacc`s, du propritaire, du groupe propritaire, du nombre e e e de liens. Table 2.1 Champs de la structure stats

sur le lien symbolique lui-mme, alors que stat renvoie les informations sur le chier vers lequel e pointe le lien symbolique. Le rsultat de ces trois appels est un objet enregistrement (record) e de type stats dcrit dans la table 2.1. e

IdenticationUn chier est identi de faon unique par la paire compos de son numro de priphrique e c e e e e (typiquement la partition sur laquelle il se trouve) st_dev et de son numro dinode st_ino. e

Propritaires eUn chier a un propritaire st_uid et un groupe propritaire st_gid. Lensemble des utie e lisateurs et des groupes dutilisateurs sur la machine est habituellement dcrit dans les chiers e /etc/passwd et /etc/groups. On peut les interroger de faon portable par nom ` laide des comc a mandes getpwnam et getgrnam ou par numro ` laide des commandes getpwuid et getgrgid. e a 16

Le nom de lutilisateur dun processus en train de tourner et lensemble des groupes auxquels il appartient peuvent tre rcuprs par les commandes getlogin et getgroups. e e ee Lappel chown modie le propritaire (deuxi`me argument) et le groupe propritaire (troisie e e `me argument) dun chier (premier argument). Seul le super utilisateur a le droit de changer e arbitrairement ces informations. Lorsque le chier est tenu par un descripteur, on utilisera fchown en passant les descripteur au lieu du nom de chier.

DroitsLes droits sont cods sous forme de bits dans un entier et le type file_perm est simplement e une abrviation pour le type int : Les droits comportent une information en lecture, criture e e et excution pour lutilisateur, le groupe et les autres, plus des bits spciaux. Les droits sont e e donc reprsents par un vecteur de bits : e e Special User Group Other 0oSUGO o` pour chacun des champs user, group et other on indique dans lordre les droits en lecture u (r), criture (w) et excution (x). Les permissions sur un chier sont lunion des permissions e e individuelles : Bit (octal) 0o100 0o200 0o400 0o10 0o20 0o40 0o1 0o2 0o4 0o1000 0o2000 0o4000 Notation ls -l --x------w------r------------x------w------r-----------x -------w------r---------t -----s----s-----Droit excution, pour le propritaire e e criture, pour le propritaire e e lecture, pour le propritaire e excution, pour les membres des groupes du propritaire e e criture, pour les membres des groupes du propritaire e e lecture, pour les membres des groupes du propritaire e excution, pour les autres utilisateurs e criture, pour les autres utilisateurs e lecture, pour les autres utilisateurs le bit t sur le groupe (sticky bit) le bit s sur le groupe (set-gid) le bit s sur lutilisateur (set-uid)

Le sens des droits de lecture et dcrire est vident ainsi que le droit dexcution pour un chier. e e e Pour un rpertoire, le droit dexcution signie le droit de se placer sur le rpertoire (faire chdir e e e sur ce rpertoire). Le droit de lecture sur un rpertoire est ncessaire pour en lister son contenu e e e mais pas pour en lire ses chiers ou sous-rpertoires (mais il faut alors en conna le nom). e tre Les bits spciaux ne prennent de sens quen prsence du bit x (lorsquil sont prsents sans e e e le bit x, ils ne donnent pas de droits supplmentaires). Cest pour cela que leur reprsentation e e se superpose ` celle du bit x et on utilise les lettres S et T au lieu de s et t lorsque le bit x nest a pas simultanment prsent. Le bit t permet aux sous-rpertoires crs dhriter des droits du e e e ee e rpertoire parent. Pour un rpertoire, le bit s permet dutiliser le uid ou le gid de propritaire du e e e rpertoire plutt que de lutilisateur ` la cration des rpertoires. Pour un chier excutable, le e o a e e e bit s permet de changer au lancement lidentit eective de lutilisateur (setuid) ou du groupe e (setgid). Le processus conserve galement ses identits dorigine, ` moins quil ait les privil`ges e e a e du super utilisateur, auquel cas, setuid et setgid changent ` la fois son identit eective et a e son identit dorigine. Lidentit eective est celle sous laquelle le processus sexcute. Lidentit e e e e dorigine est maintenue pour permettre au processus de reprendre ultrieurement celle-ci comme e 17

eective sans avoir besoin de privil`ges. Les appels syst`me getuid et getgid retournent les e e identits dorigine et geteuid et getegid retournent les identits eectives. e e Un processus poss`de galement un masque de cration de chiers reprsent de la mme e e e e e e faon. Comme son nom lindique, le masque est spcie des interdictions (droits ` masquer) : c e a lors de la cration dun chier tous les bits ` 1 dans le masque de cration sont mis ` zro dans e a e a e les droits du chier cr. Le masque peut tre consult et chang par la fonction ee e e e val umask : int -> int Comme pour de nombreux appels syst`me qui modient une variable syst`me, lancienne valeur e e de la variable est retourne par la fonction de modication. Pour simplement consulter la valeur, e il faut donc la modier deux fois, une fois avec une valeur arbitraire, puis remettre lancienne valeur en place. Par exemple, en faisant : let m = umask 0 in ignore (umask m); m Les droits dacc`s peuvent tre modis avec lappel chmod. e e e On peut galement tester les droits dacc`s dynamiquement avec lappel syst`me access e e e type access_permission = R_OK | W_OK | X_OK | F_OK val access : string -> access_permission list -> unit o` les acc`s demands sont reprsents pas le type access_permission dont le sens est immdiat u e e e e e sauf pour F_OK qui signie seulement que le chier existe (ventuellement sans que le processus e ait les droits correspondants). Notez que access peut retourner une information plus restrictive que celle calcule ` partir e a de linformation statique retourne par lstat car une hirarchie de chiers peut tre montre e e e e avec des droits restreints, par exemple en lecture seule. Dans ce cas, access refusera le droit dcrire alors que linformation contenue dans les mta-donnes relative au chier peut lautorie e e ser. Cest pour cela quon parle dinformation dynamique (ce que le processus peut rellement e faire) par opposition ` statique (ce que le syst`me de chier indique). a e

2.4

Oprations sur les rpertoires e e

Seul le noyau crit dans les rpertoires (lorsque des chiers sont crs). Il est donc interdit e e ee douvrir un rpertoire en criture. Dans certaines versions dUnix on peut ouvrir un rpertoire e e e en lecture seule et le lire avec read, mais dautres versions linterdise. Cependant, mme si e cest possible, il est prfrable de ne pas le faire car le format des entres des rpertoires varie ee e e suivant les versions dUnix, et il est souvent complexe. Les fonctions suivantes permettent de lire squentiellement un rpertoire de mani`re portable : e e e val opendir : string -> dir_handle val readdir : dir_handle -> string val rewinddir : dir_handle -> unit val closedir : dir_handle -> unit La fonction opendir renvoie un descripteur de lecture sur un rpertoire. La fonction readdir lit e la prochaine entre dun rpertoire (ou dclenche lexception End_of_file si la n du rpertoire e e e e est atteinte). La cha renvoye est un nom de chier relatif au rpertoire lu. La fonction ne e e rewinddir repositionne le descripteur au dbut du rpertoire. e e Pour crer un rpertoire, ou dtruire un rpertoire vide, on dispose de : e e e e val mkdir : string -> file_perm -> unit val rmdir : string -> unit Le deuxi`me argument de mkdir encode les droits dacc`s donns au nouveau rpertoire. Notez e e e e quon ne peut dtruire quun rpertoire dj` vide. Pour dtruire un rpertoire et son contenu, il e e ea e e 18

faut donc dabord aller rcursivement vider le contenu du rpertoire puis dtruire le rpertoire. e e e e Par exemple, on peut crire une fonction dintrt gnral dans le module Misc qui it`re sur e ee e e e les entres dun rpertoire. e e 1 let iter_dir f dirname = 2 let d = opendir dirname in 3 try while true do f (readdir d) done 4 with End_of_file -> closedir d

2.5

Exemple complet : recherche dans la hirarchie e

La commande Unix find permet de rechercher rcursivement des chiers dans la hirarchie e e selon certains crit`res (nom, type et droits du chier) etc. Nous nous proposons ici de raliser e e dune part une fonction de biblioth`que Findlib.find permettant deectuer de telles ree cherches et une commande find fournissant une version restreinte de la commande Unix find nimplantant que les options -follow et -maxdepth. Nous imposons linterface suivante pour la biblioth`que Findlib : e val find : (Unix.error * string * string -> unit) -> (string -> Unix.stats -> bool) -> bool -> int -> string list -> unit Lappel de fonction find handler action follow depth roots parcourt la hirarchie de chiers ` e a partir des racines indiques dans la liste roots (absolues ou relatives au rpertoire courant au e e moment de lappel) jusqu` une profondeur maximale depth en suivant les liens symboliques si a le drapeau f ollow est vrai. Les chemins trouvs sous une racine r incluent r comme prxe. e e Chaque chemin trouv p est pass ` la fonction action. En fait, action reoit galement les e e a c e informations Unix.stat p si le drapeau f ollow est vrai ou Unix.lstat p sinon. La fonction action retourne un boolen indiquant galement dans le cas dun rpertoire sil faut poursuivre e e e la recherche en profondeur (true) ou linterrompre (false). La fonction handler sert au traitement des erreurs de parcours, ncessairement de type e Unix_error : les arguments de lexception sont alors passs ` la fonction handler et le parcours e a continue. En cas dinterruption, lexception est remonte ` la fonction appelante. Lorsquune e a exception est leve par les fonctions action ou handler, elle arrte le parcours de faon abrupte e e c et est remonte immdiatement ` lappelant. e e a Pour remonter une exception Unix_error sans quelle puisse tre attrape comme une erreur e e de parcours, nous la cachons sous une autre exception. 1 exception Hidden of exn 2 let hide_exn f x = try f x with exn -> raise (Hidden exn);; 3 let reveal_exn f x = try f x with Hidden exn -> raise exn;; Voici le code de la fonction de parcours. 4 open Unix;; 5 let find on_error on_path follow depth roots = 6 let rec find_rec depth visiting filename = 7 try 8 let infos = (if follow then stat else lstat) filename in 9 let continue = hide_exn (on_path filename) infos in 10 let id = infos.st_dev, infos.st_ino in 11 if infos.st_kind = S_DIR && depth > 0 && continue && 12 (not follow || not (List.mem id visiting)) 19

13 14 15 16 17 18 19 20 21 22 23

then let process_child child = if (child Filename.current_dir_name && child Filename.parent_dir_name) then let child_name = Filename.concat filename child in let visiting = if follow then id :: visiting else visiting in find_rec (depth-1) visiting child_name in Misc.iter_dir process_child filename with Unix_error (e, b, c) -> hide_exn on_error (e, b, c) in reveal_exn (List.iter (find_rec depth [])) roots;;

Les rpertoires sont identis par la paire id (ligne 21) constitue de leur numro de priphrique e e e e e e et de leur numro dinode. La liste visiting contient lensemble des rpertoires en train dtre e e e visits. En fait cette information nest utile que si lon suit les liens symboliques (ligne 19). e On peut maintenant en dduire facilement la commande find. e 1 let find () = 2 let follow = ref false in 3 let maxdepth = ref max_int in 4 let roots = ref [] in 5 let usage_string = 6 ("Usage: " ^ Sys.argv.(0) ^ " [files...] [options...]") in 7 let opt_list = [ 8 "-maxdepth", Arg.Int ((:=) maxdepth), "max depth search"; 9 "-follow", Arg.Set follow, "follow symbolic links"; 10 ] in 11 Arg.parse opt_list (fun f -> roots := f :: !roots) usage_string; 12 let action p infos = print_endline p; true in 13 let errors = ref false in 14 let on_error (e, b, c) = 15 errors := true; prerr_endline (c ^ ": " ^ Unix.error_message e) in 16 Findlib.find on_error action !follow !maxdepth 17 (if !roots = [] then [ Filename.current_dir_name ] 18 else List.rev !roots); 19 if !errors then exit 1;;20 21

Unix.handle_unix_error find ();;

Lessentiel du code est constitu par lanalyse de la ligne de commande, pour laquelle nous e utilisons la biblioth`que Arg. e Bien que la commande find implante ci-dessus soit assez restreinte, la fonction de bie blioth`que Findlib.find est quant ` elle tr`s gnrale, comme le montre lexercice suivant. e a e e e

Exercice 1 Utiliser la biblioth`que Findlib pour crire un programme find_but_CVS quivalent e e e a la commande Unix find . -type d -name CVS -prune -o -print qui imprime rcursivement ` e les chiers a partir du rpertoire courant mais sans voir (ni imprimer, ni visiter) les rpertoires ` e e de nom CVS. (Voir le corrig) e Exercice 2 La fonction getcwd nest pas un appel syst`me mais dnie en biblioth`que. Donner e e e une implmentation primitive de getcwd. Dcrire le principe de lalgorithme. e e 20

(Voir le corrig) e Puis crire lalgorithme (on vitera de rpter plusieurs fois le mme appel syst`me). e e e e e e

2.6

Ouverture dun chier

La primitive openfile permet dobtenir un descripteur sur un chier dun certain nom (lappel syst`me correspond est open, mais open est un mot cl en OCaml). e e val openfile : string -> open_flag list -> file_perm -> file_descr Le premier argument est le nom du chier ` ouvrir. Le deuxi`me argument est une liste de a e drapeaux pris dans le type numr open_flag, et dcrivant dans quel mode le chier doit e ee e tre ouvert, et que faire sil nexiste pas. Le troisi`me argument de type file_perm indique e e avec quels droits dacc`s crer le chier, le cas chant. Le rsultat est un descripteur de chier e e e e e pointant vers le chier indiqu. La position de lecture/criture est initialement xe au dbut e e e e du chier. La liste des modes douverture (deuxi`me argument) doit contenir exactement un des trois e drapeaux suivants : O_RDONLY ouverture en lecture seule O_WRONLY ouverture en lecture seule O_RDWR ouverture en lecture et en criture e Ces drapeaux conditionnent la possibilit de faire par la suite des oprations de lecture ou e e dcriture ` travers le descripteur. Lappel openfile choue si on demande ` ouvrir en criture e a e a e un chier sur lequel le processus na pas le droit dcrire, ou si on demande ` ouvrir en lece a ture un chier que le processus na pas le droit de lire. Cest pourquoi il ne faut pas ouvrir systmatiquement en mode O_RDWR. e La liste des modes douverture peut contenir en plus un ou plusieurs des drapeaux parmi les suivants : O_APPEND O_CREAT O_TRUNC O_EXCL ouverture en ajout crer le chier sil nexiste pas e tronquer le chier ` zro sil existe dj` a e ea chouer si le chier existe dj` e ea

O_NONBLOCK ouverture en mode non bloquant O_NOCTTY O_SYNC O_DSYNC O_RSYNC ne pas fonctionner en mode terminal de contrle o eectuer les critures en mode synchronis e e eectuer les critures de donnes en mode synchronis e e e eectuer les lectures en mode synchronis e

Le premier groupe indique le comportement ` suivre selon que le chier existe ou non. a Si O_APPEND est fourni, le pointeur de lecture/criture sera positionn ` la n du chier e e a avant chaque criture. En consquence, toutes les critures sajouteront ` la n du chier. Au e e e a contraire, sans O_APPEND, les critures se font ` la position courante (initialement, le dbut du e a e chier). Si O_TRUNC est fourni, le chier est tronqu au moment de louverture : la longueur du chier e est ramene ` zro, et les octets contenus dans le chier sont perdus. Les critures repartent e a e e donc dun chier vide. Au contraire, sans O_TRUNC, les critures se font par dessus les octets e dj` prsents, ou ` la suite. ea e a 21

Si O_CREAT est fourni, le chier est cr sil nexiste pas dj`. Le chier est cr avec une ee ea ee taille nulle, et avec pour droits dacc`s les droits indiqus par le troisi`me argument, modis e e e e par le masque de cration du processus. (Le masque de cration est consultable et modiable e e par la commande umask, et par lappel syst`me de mme nom). e e Exemple: la plupart des programmes prennent 0o666 comme troisi`me argument de openfile, e cest-`-dire rw-rw-rw- en notation symbolique. Avec le masque de cration standard de 0o022, a e le chier est donc cr avec les droits rw-r--r--. Avec un masque plus conant de 0o002, le ee chier est cr avec les droits rw-rw-r--. ee Si O_EXCL est fourni, openfile choue si le chier existe dj`. Ce drapeau, employ en e ea e conjonction avec O_CREAT, permet dutiliser des chiers comme verrous (locks). 1 Un processus qui veut prendre le verrou appelle openfile sur le chier avec les modes O_EXCL et O_CREAT. Si le chier existe dj`, cela signie quun autre processus dtient le verrou. Dans ce cas, openfile ea e dclenche une erreur, et il faut attendre un peu, puis ressayer. Si le chier nexiste pas, openfile e e retourne sans erreur et le chier est cr, empchant les autres processus de prendre le verrou. ee e Pour librer le verrou, le processus qui le dtient fait unlink dessus. La cration dun chier est e e e une opration atomique : si deux processus essayent de crer un mme chier en parall`le avec e e e e les options O_EXCL et O_CREAT, au plus un seul des deux seulement peut russir. Evidemment e cette mthode nest pas tr`s satisfaisante car dune part le processus qui na pas le verrou doit e e tre en attente active, dautre part un processus qui se termine anormalement peux laisser le e verrou bloqu. e Exemple: pour se prparer ` lire un chier : e a openfile filename [O_RDONLY] 0 Le troisi`me argument peut tre quelconque, puisque O_CREAT nest pas spci. On prend e e e e conventionnellement 0. Pour crire un chier ` partir de rien, sans se proccuper de ce quil e a e contenait ventuellement : e openfile filename [O_WRONLY; O_TRUNC; O_CREAT] 0o666 Si le chier quon ouvre va contenir du code excutable (cas des chiers crs par ld), ou un e ee script de commandes, on ajoute les droits dexcution dans le troisi`me argument : e e openfile filename [O_WRONLY; O_TRUNC; O_CREAT] 0o777 Si le chier quon ouvre est condentiel, comme par exemple les chiers bo aux lettres te dans lesquels mail stocke les messages lus, on le cre en restreignant la lecture et lcriture au e e propritaire uniquement : e openfile filename [O_WRONLY; O_TRUNC; O_CREAT] 0o600 Pour se prparer ` ajouter des donnes ` la n dun chier existant, et le crer vide sil nexiste e a e a e pas : openfile filename [O_WRONLY; O_APPEND; O_CREAT] 0o666 Le drapeau O_NONBLOCK assure que si le support est un tuyau nomm ou un chier spcial, e e alors louverture du chier ainsi que les lectures et critures ultrieur se feront en mode non e e bloquant. Le drapeau O_NOCTYY assure que si le support est un terminal de contrle (clavier, fentre, o e etc.), alors celui-ci ne devient pas le terminal de contrle du processus appelant. o1. Ce nest pas possible si le chier verrou rside sur une partition NFS, car NFS nimplmente pas correce e tement loption O_CREAT de open.

22

Le dernier groupe de drapeaux indique comment synchroniser les oprations de lectures et e critures. Par dfaut, ces oprations ne sont pas synchronises. e e e e Si O_DSYNC est fourni, les donnes sont crites de faon synchronise de telle faon que la e e c e c commande est bloquante et ne retourne que lorsque toutes les critures auront t eectues e ee e physiquement sur le support (disque en gnral). e e Si O_SYNC est fourni, ce sont ` la fois les donnes et les informations sur le chier qui sont a e synchronises. e Si O_RSYNC est fourni en prsence de O_DSYNC les lectures des donnes sont galement syne e e chronises : il est assur que toutes les critures en cours (demandes mais pas ncessairement e e e e e enregistres) sur ce chier seront eectivement crites sur le support avant la prochaine lecture. e e Si O_RSYNC est fourni en prsence de O_SYNC cela sapplique galement aux informations sur le e e chier.

2.7

Lecture et criture e

Les appels syst`mes read et write permettent de lire et dcrire les octets dun chier. Pour e e des raisons historiques, lappel syst`me write est relev en OCaml sous le nom single_write : e e val read : file_descr -> string -> int -> int -> int val single_write : file_descr -> string -> int -> int -> int Les deux appels read et single_write ont la mme interface. Le premier argument est le e descripteur sur lequel la lecture ou lcriture doit avoir lieu. Le deuxi`me argument est une e e cha de caract`res contenant les octets ` crire (cas de single_write), ou dans laquelle vont ne e ae tre stocks les octets lus (cas de read). Le troisi`me argument est la position, dans la cha de e e e ne caract`res, du premier octet ` crire ou ` lire. Le quatri`me argument est le nombre doctets ` e ae a e a lire ou ` crire. Le troisi`me argument et le quatri`me argument dsignent donc une sous-cha ae e e e ne de la cha passe en deuxi`me argument. (Cette sous-cha ne doit pas dborder de la cha ne e e ne e ne dorigine ; read et single_write ne vrient pas ce fait.) e fd read fd s d n s d n Lentier renvoy par read ou single_write est le nombre doctets rellement lus ou crits. e e e Les lectures et les critures ont lieu ` partir de la position courante de lecture/criture. (Si e a e le chier a t ouvert en mode O_APPEND, cette position est place ` la n du chier avant toute ee e a criture.) Cette position est avance du nombre doctets lus ou crits. e e e Dans le cas dune criture, le nombre doctets eectivement crits est normalement le nombre e e doctets demands, mais il y a plusieurs exceptions ` ce comportement : (i) dans le cas o` e a u il nest pas possible dcrire les octets (si le disque est plein, par exemple) ; (ii) lorsquon e crit sur un descripteur de chiers qui rfrence un tuyau ou une prise plac dans le mode e ee e entres/sorties non bloquantes, les critures peuvent tre partielles ; enn, (iii) OCaml qui fait e e e une copie supplmentaire dans un tampon auxiliaire et crit celui-ci limite la taille du tampon e e auxiliaire ` une valeur maximale (qui est en gnral la taille utilise par le syst`me pour ses a e e e e propres tampons) ceci pour viter dallouer de trop gros tampons ; si le le nombre doctets ` e a crire est suprieure ` cette limite, alors lcriture sera forcment partielle mme si le syst`me e e a e e e e aurait assez de ressource pour eectuer une criture totale. e 23 single_write fd s d n

Pour contourner le probl`me de la limite des tampons, OCaml fournit galement une fonction e e write qui rp`te plusieurs critures tant quil ny a pas eu derreur dcriture. Cependant, en cas e e e e derreur, la fonction retourne lerreur et ne permet pas de savoir le nombre doctets eectivement crits. On utilisera donc plutt la fonction single_write que write parce quelle prserve e o e latomicit (on sait exactement ce qui a t crit) et est donc plus d`le ` lappel syst`me e ee e e a e dUnix (voir galement limplmentation de single_write dcrite dans le chapitre suivant 5.7). e e e Nous verrons dans le chapitre suivant que lorsquon crit sur un descripteur de chier qui e rfrence un tuyau ou une prise qui est plac dans le mode entres/sorties bloquantes et que ee e e lappel est interrompu par un signal, lappel single_write retourne une erreur EINTR. Exemple: supposant fd li ` un descripteur ouvert en criture, ea e write fd "Hello world!" 3 7 crit les caract`res lo worl dans le chier correspondant, et renvoie 7. e e Dans le cas dune lecture, il se peut que le nombre doctets eectivement lus soit strictement infrieur au nombre doctets demands. Premier cas : lorsque la n du chier est proche, ceste e a `-dire lorsque le nombre doctets entre la position courante et la n du chier est infrieur au e nombre doctets requis. En particulier, lorsque la position courante est sur la n du chier, read renvoie zro. Cette convention zro gal n de chier sapplique aussi aux lectures depuis des e e e chiers spciaux ou des dispositifs de communication. Par exemple, read sur le terminal renvoie e zro si on frappe ctrl-D en dbut de ligne. e e Deuxi`me cas o` le nombre doctets lus peut tre infrieur au nombre doctets demands : e u e e e lorsquon lit depuis un chier spcial tel quun terminal, ou depuis un dispositif de communicae tion comme un tuyau ou une prise. Par exemple, lorsquon lit depuis le terminal, read bloque jusqu` ce quune ligne enti`re soit disponible. Si la longueur de la ligne dpasse le nombre a e e doctets requis, read retourne le nombre doctets requis. Sinon, read retourne immdiatement e avec la ligne lue, sans forcer la lecture dautres lignes pour atteindre le nombre doctets requis. (Cest le comportement par dfaut du terminal ; on peut aussi mettre le terminal dans un mode e de lecture caract`re par caract`re au lieu de ligne ` ligne. Voir section 2.13 ou page 163 pour e e a avoir tous les dtails.) e Exemple: lexpression suivante lit au plus 100 caract`res depuis lentre standard, et renvoie e e la cha des caract`res lus. ne e let buffer = String.create 100 in let n = read stdin buffer 0 100 in String.sub buffer 0 n Exemple: la fonction really_read ci-dessous a la mme interface que read, mais fait plusieurs e tentatives de lecture si ncessaire pour essayer de lire le nombre doctets requis. Si, ce faisant, e elle rencontre une n de chier, elle dclenche lexception End_of_file. e let rec really_read fd buffer start length = if length raise End_of_file | r -> really_read fd buffer (start + r) (length - r);;

2.8

Fermeture dun descripteur

Lappel syst`me close ferme le descripteur pass en argument. e e 24

val close : file_descr -> unit Une fois quun descripteur a t ferm, toute tentative de lire, dcrire, ou de faire quoi que ee e e ce soit avec ce descripteur choue. Il est recommand de fermer les descripteurs d`s quils ne e e e sont plus utiliss. Ce nest pas obligatoire ; en particulier, contrairement ` ce qui se passe avec e a la biblioth`que standard Pervasives, il nest pas ncessaire de fermer les descripteurs pour e e tre certain que les critures en attente ont t eectues : les critures faites avec write sont e e ee e e immdiatement transmises au noyau. Dun autre ct, le nombre de descripteurs quun processus e oe peut allouer est limit par le noyau (plusieurs centaines a quelques milliers). Faire close sur un e ` descripteur inutile permet de le dsallouer, et donc dviter de tomber ` court de descripteurs. e e a

2.9

Exemple complet : copie de chiers

On va programmer une commande file_copy, ` deux arguments f1 et f2 , qui recopie dans a le chier de nom f2 les octets contenus dans le chier de nom f1 . 1 open Unix;;2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

let buffer_size = 8192;; let buffer = String.create buffer_size;; let file_copy input_name output_name = let fd_in = openfile input_name [O_RDONLY] 0 in let fd_out = openfile output_name [O_WRONLY; O_CREAT; O_TRUNC] 0o666 in let rec copy_loop () = match read fd_in buffer 0 buffer_size with 0 -> () | r -> ignore (write fd_out buffer 0 r); copy_loop () in copy_loop (); close fd_in; close fd_out;; let copy () = if Array.length Sys.argv = 3 then begin file_copy Sys.argv.(1) Sys.argv.(2); exit 0 end else begin prerr_endline ("Usage: " ^Sys.argv.(0)^ " "); exit 1 end;; handle_unix_error copy ();;

Lessentiel du travail est fait par la fonction file_copy des lignes 615. On commence par ouvrir un descripteur en lecture seule sur le chier dentre (ligne 7), et un descripteur en criture seule e e sur le chier de sortie (ligne 8). Le chier de sortie est tronqu sil existe dj` (option O_TRUNC), e ea et cr sil nexiste pas (option O_CREAT), avec les droits rw-rw-rw- modis par le masque de ee e cration. (Ceci nest pas satisfaisant : si on copie un chier excutable, on voudrait que la copie e e soit galement excutable. On verra plus loin comment attribuer ` la copie les mmes droits e e a e dacc`s qu` loriginal.) Dans les lignes 913, on eectue la copie par blocs de buffer_size e a 25

caract`res. On demande ` lire buffer_size caract`res (ligne 10). Si read renvoie zro, cest e a e e quon a atteint la n du chier dentre, et la copie est termine (ligne 11). Sinon (ligne 12), on e e crit les r octets quon vient de lire sur le chier de destination, et on recommence. Finalement, e on ferme les deux descripteurs. Le programme principal (lignes 1724) vrie que la commande e a reu deux arguments, et les passe ` la fonction file_copy. c a Toute erreur pendant la copie, comme par exemple limpossibilit douvrir le chier dentre, e e parce quil nexiste pas ou parce quil nest pas permis de le lire, ou encore lchec dune e criture par manque de place sur le disque, se traduit par une exception Unix_error qui se e propage jusquau niveau le plus externe du programme, o` elle est intercepte et ache par u e e handle_unix_error. Exercice 3 Ajouter une option -a au programme, telle que file_copy -a f1 f2 ajoute le contenu de f1 a la n de f2 si f2 existe dj`. ` ea (Voir le corrig) e

2.10

Co t des appels syst`me. Les tampons. u e

Dans lexemple file_copy, les lectures se font par blocs de 8192 octets. Pourquoi pas octet par octet ? ou mgaoctet par mgaoctet ? Pour des raisons decacit. La gure 2.2 montre la e e e vitesse de copie, en octets par seconde, du programme file_copy, quand on fait varier la taille des blocs (la variable buffer_size) de 1 octet a 8 mgaoctets, en doublant ` chaque fois. e a Pour de petites tailles de blocs, la vitesse de copie est ` peu pr`s proportionnelle ` la taille a e a des blocs. Cependant, la quantit de donnes transfres est la mme quelle que soit la taille e e ee e des blocs. Lessentiel du temps ne passe donc pas dans le transfert de donnes proprement dit, e mais dans la gestion de la boucle copy_loop, et dans les appels read et write. En mesurant plus nement, on voit que ce sont les appels read et write qui prennent lessentiel du temps. On en conclut donc quun appel syst`me, mme lorsquil na pas grand chose ` faire (read dun e e a caract`re), prend un temps minimum denviron 4 micro-secondes (sur la machine employe e e pour faire le testun Pentium 4 ` 2.8 GHz), disons 1 ` 10 micro-secondes. Pour des blocs a a dentre/sortie de petite taille, cest ce temps dappel syst`me qui prdomine. e e e Pour des blocs plus gros, entre 4K et 1M, la vitesse est constante et maximale. Ici, le temps li aux appels syst`mes et ` la boucle de copie est petit devant le temps de transfert des donnes. e e a e Dautre part la taille du tampon devient suprieur ` la tailles des caches utiliss par le syst`me. e a e e Et le temps pass par le syst`me ` grer le transfert devient prpondrant sur le cot dun appel e e a e e e u syst`me 2 e Enn, pour de tr`s gros blocs (8M et plus), la vitesse passe lg`rement au-dessous du maxie e e mum. Entre en jeu ici le temps ncessaire pour allouer le bloc et lui attribuer des pages de e mmoire relles au fur et ` mesure quil se remplit. e e a Moralit : un appel syst`me, mme sil fait tr`s peu de travail, cote cher beaucoup plus e e e e u cher quun appel de fonction normale : en gros, de 2 ` 20 micro-secondes par appel syst`me, suia e vant les architectures. Il est donc important dviter de faire des appels syst`me trop frquents. e e e En particulier, les oprations de lecture et dcriture doivent se faire par blocs de taille susante, e e et non caract`re par caract`re. e e Dans des exemples comme file_copy, il nest pas dicile de faire les entres/sorties par e gros blocs. En revanche, dautres types de programmes scrivent naturellement avec des entres e e caract`re par caract`re (exemples : lecture dune ligne depuis un chier, analyse lexicale), et des e e2. En fait, OCaml limite la tailles des donnes transfres ` 16K (dans la version courante) en rptant e ee a e e plusieurs appels syst`me write pour eectuer le transfert completvoir la discussion la section 5.7. Mais cette e limite est au del` de la taille des caches du syst`me et nest pas observable. a e

26

1000

333100

33

3 3

3333

33

3Vitesse de copie 10 (mgaoctet/s) e

3

3 3 3 3 3 3 3 3

1

0.1

31 10 100 1000 10000 100000 Taille des blocs (octets) 1e+06 1e+07

Figure 2.2 Vitesse de copie en fonction de la taille des blocs

sorties de quelques caract`res ` la fois (exemple : achage dun nombre). Pour rpondre aux bee a e soins de ces programmes, la plupart des syst`mes fournissent des biblioth`ques dentres-sorties, e e e qui intercalent une couche de logiciel supplmentaire entre lapplication et le syst`me dexploie e tation. Par exemple, en OCaml, on dispose du module Pervasives de la biblioth`que standard, e qui fournit deux types abstraits in_channel et out_channel, analogues aux descripteurs de chiers, et des oprations sur ces types, comme input_char, input_line, output_char, ou e output_string. Cette couche supplmentaire utilise des tampons (buers) pour transformer e des suites de lectures ou dcritures caract`re par caract`re en une lecture ou une criture dun e e e e bloc. On obtient donc de bien meilleures performances pour les programmes qui proc`dent cae ract`re par caract`re. De plus, cette couche supplmentaire permet une plus grande portabilit e e e e des programmes : il sut dadapter cette biblioth`que aux appels syst`me fournis par un autre e e syst`me dexploitation, et tous les programmes qui utilisent la biblioth`que sont immdiatement e e e portables vers cet autre syst`me dexploitation. e

2.11

Exemple complet : une petite biblioth`que dentres-sorties e e

Pour illustrer les techniques de lecture/criture par tampon, voici une implmentation simple e e dun fragment de la biblioth`que Pervasives de OCaml. Linterface est la suivante : e type in_channel exception End_of_file val open_in : string -> in_channel val input_char : in_channel -> char val close_in : in_channel -> unit type out_channel val open_out : string -> out_channel val output_char : out_channel -> char -> unit val close_out : out_channel -> unit 27

Commenons par la partie lecture. Le type abstrait in_channel est implment comme c e e suit : 1 open Unix;;2

type in_channel = { in_buffer: string; 5 in_fd: file_descr; 6 mutable in_pos: int; 7 mutable in_end: int };; 8 exception End_of_file La cha de caract`res du champ in_buffer est le tampon proprement dit. Le champ in_fd ne e est un descripteur de chier (Unix), ouvert sur le chier en cours de lecture. Le champ in_pos est la position courante de lecture dans le tampon. Le champ in_end est le nombre de caract`res e valides dans le tampon. position courante de lecture sur in_fd3 4

in_fd caract`res caract`res e e lus prchargs e e a ` lire in_buffer in_pos in_end Les champs in_pos et in_end vont tre modis en place ` loccasion des oprations de e e a e lecture ; on les dclare donc mutable. e 9 let buffer_size = 8192;; 10 let open_in filename = 11 { in_buffer = String.create buffer_size; 12 in_fd = openfile filename [O_RDONLY] 0; 13 in_pos = 0; 14 in_end = 0 };; ` louverture dun chier en lecture, on cre le tampon avec une taille raisonnable (susamment A e grande pour ne pas faire dappels syst`me trop souvent ; susamment petite pour ne pas gcher e a de mmoire), et on initialise le champ in_fd par un descripteur de chier Unix ouvert en lecture e seule sur le chier en question. Le tampon est initialement vide (il ne contient aucun caract`re e du chier) ; le champ in_end est donc initialis ` zro. ea e 15 let input_char chan = 16 if chan.in_pos < chan.in_end then begin 17 let c = chan.in_buffer.[chan.in_pos] in 18 chan.in_pos raise End_of_file 23 | r -> chan.in_end int -> unit val ftruncate : file_descr -> int -> unit Le premier argument dsigne le chier a tronquer (par son nom, ou via un descripteur ouvert e ` sur ce chier). Le deuxi`me argument est la taille dsire. Toutes les donnes situes ` partir e e e e e a de cette position sont perdues.

Liens symboliquesLa plupart des oprations sur chiers suivent les liens symboliques : cest-`-dire, elles e a sappliquent au chier vers lequel pointe le lien symbolique, et non pas au lien symbolique luimme. Exemples : openfile, stat, truncate, opendir. On dispose de deux oprations sur les e e liens symboliques : val symlink : string -> string -> unit val readlink : string -> string Lappel symlink f1 f2 cr le chier f2 comme tant un lien symbolique vers f1 . (Comme la ee e commande ln -s f1 f2 .) Lappel readlink renvoie le contenu dun lien symbolique, cest-`-dire a le nom du chier vers lequel il pointe.

Fichiers spciaux eLes chiers spciaux peuvent tre de type caract`re ou de type block. Les premiers sont des e e e ux de caract`res : on ne peut lire ou crire les caract`res que dans lordre. Ce sont typiquement e e e 31

les terminaux, les priphriques sons, imprimantes, etc. Les seconds, typiquement les disques, e e ont un support rmanent ou temporis : on peut lire les caract`res par blocs, voir ` une certaine e e e a distance donne sous forme absolue ou relative par rapport ` la position courante. Parmi les e a chiers spciaux, on peut distinguer : e /dev/null Cest le trou noir qui avale tout ce quon met dedans et dont il ne sort rien. Tr`s utile e pour ignorer les rsultats dun processus : on redirige sa sortie vers /dev/null (voir le e chapitre 5). /dev/tty* Ce sont les terminaux de contrle. o /dev/pty* Ce sont les pseudo-terminaux de contrle : ils ne sont pas de vrais terminaux mais les o simulent (ils rpondent ` la mme interface). e a e /dev/hd* Ce sont les disques. /proc Sous Linux, permet de lire et dcrire certains param`tres du syst`me en les organisant e e e comme un syst`me de chiers. e Les chiers spciaux ont des comportements assez variables en rponse aux appels syst`me e e e gnraux sur chiers. La plupart des chiers spciaux (terminaux, lecteurs de bandes, disques, e e e . . .) obissent ` read et write de la mani`re vidente (mais parfois avec des restrictions sur le e a e e nombre doctets crits ou lus). Beaucoup de chiers spciaux ignorent lseek. e e En plus des appels syst`mes gnraux, les chiers spciaux qui correspondent ` des prie e e e a e phriques doivent pouvoir tre paramtrs ou commands dynamiquement. Exemples de telles e e e e e possibilits : pour un drouleur de bande, le rembobinage ou lavance rapide ; pour un termie e nal, le choix du mode ddition de ligne, des caract`res spciaux, des param`tres de la liaison e e e e srie (vitesse, parit, etc). Ces oprations sont ralises en Unix par lappel syst`me ioctl qui e e e e e e regroupe tous les cas particuliers. Cependant, cet appel syst`me nest pas relev en OCaml... e e parce quil est mal dni et ne peut pas tre trait de faon uniforme. e e e c Terminaux de contrle o Les terminaux (ou pseudo-terminaux) de contrle sont un cas particulier de chiers spciaux o e de type caract`re pour lequel OCaml donne acc`s ` la conguration. Lappel tcgetattr prend e e a en argument un descripteur de chier ouvert sur le chier spcial en question et retourne une e structure de type terminal_io qui dcrit le statut du terminal reprsent par ce chier selon e e e la norme POSIX (Voir page 163 pour une description compl`te). e val tcgetattr : file_descr -> terminal_io type terminal_io = { c_ignbrk : bool; c_brk_int : bool; ...; c_vstop : char } Cette structure peut tre modie puis passe ` la fonction tcsetattr pour changer les attributs e e e a du priphrique. e e val tcsetattr : file_descr -> setattr_when -> terminal_io -> unit Le premier argument est le descripteur de chier dsignant le priphrique. Le dernier argument e e e est une structure de type tcgetattr dcrivant les param`tres du priphrique tels quon veut e e e e les tablir. Le second argument est un drapeau du type numr setattr_when indiquant le e e ee moment ` partir duquel la modication doit prendre eet : immdiatement (TCSANOW), apr`s a e e 32

avoir transmis toutes les donnes crites (TCSADRAIN) ou apr`s avoir lu toutes les donnes reues e e e e c (TCAFLUSH). Le choix TCSADRAIN est recommand pour modier les param`tres dcriture et e e e TCSAFLUSH pour modier les param`tres de lecture. e Exemple: Pendant la lecture dun mot de passe, il faut retirer lcho des caract`res taps par e e e lutilisateur si le ux dentre standard est connect ` un terminal ou pseudo-terminal. e ea 1 let read_passwd message = 2 match 3 try 4 let default = tcgetattr stdin in 5 let silent = 6 { default with 7 c_echo = false; 8 c_echoe = false; 9 c_echok = false; 10 c_echonl = false; 11 } in 12 Some (default, silent) 13 with _ -> None 14 with 15 | None -> input_line Pervasives.stdin 16 | Some (default, silent) -> 17 print_string message; 18 flush Pervasives.stdout; 19 tcsetattr stdin TCSANOW silent; 20 try 21 let s = input_line Pervasives.stdin in 22 tcsetattr stdin TCSANOW default; s 23 with x -> 24 tcsetattr stdin TCSANOW default; raise x;; La fonction read_passwd commence par rcuprer la valeur par dfaut des param`tres du e e e e terminal associ ` stdin et construire une version modie dans laquelle les caract`res nont e a e e plus dcho. En cas dchec, cest que le ux dentre nest pas un terminal de contrle, on se e e e o contente de lire une ligne. Sinon, on ache un message, on change le terminal, on lit la rponse e et on remet le terminal dans son tat normal. Il faut faire attention ` bien remettre le terminal e a dans son tat normal galement lorsque la lecture a chou. e e e e Il arrive quune application ait besoin den lancer une autre en liant son ux dentre ` un e a terminal (ou pseudo terminal) de contrle. Le syst`me OCaml ne fournit pas daide pour cela 3 : o e il faut manuellement rechercher parmi lensemble des pseudo-terminaux (en gnral, ce sont des e e chiers de nom de la forme /dev/tty[a-z][a-f0-9]) et trouver un de ces chiers qui ne soit pas dj` ouvert, pour louvrir puis lancer lapplication avec ce chier en ux dentre. ea e Quatre autres fonctions permettent de contrler le ux (vider les donnes en attente, attendre o e la n de la transmission, relancer la communication). val tcsendbreak : file_descr -> int -> unit La fonction tcsendbreak envoie une interruption au priphrique. Son deuxi`me argument est e e e la dure de linterruption (0 tant interprt comme la valeur par dfaut pour le priphrique). e e ee e e e val tcdrain : file_descr -> unit3. La biblioth`que de Cash [3] fournit de telles fonctions. e

33

La fonction tcdrain attend que toutes les donnes crites aient t transmises. e e ee val tcflush : file_descr -> flush_queue -> unit Selon la valeur du drapeau pass en second argument, la fonction tcflush abandonne les e donnes crites pas encore transmises (TCIFLUSH), ou les donnes reues mais pas encore lues e e e c (TCOFLUSH) ou les deux (TCIOFLUSH). val tcflow : file_descr -> flow_action -> unit Selon la valeur du drapeau pass en second argument, la fonction tcflow suspend lmission e e (TCOOFF), redmarre lmission (TCOON), envoie un caract`re de contrle STOP ou START pour e e e o demander que la transmission soit suspendue (TCIOFF) ou relance (TCION). e val setsid : unit -> int La fonction setsid place le processus dans une nouvelle session et le dtache de son terminal e de contrle. o

2.14

Verrous sur des chiers

Deux processus peuvent modier un mme chier en parall`le au risque que certaines e e critures en crasent dautres. Dans certains cas, louverture en mode O_APPEND permet de e e sen sortir, par exemple, pour un chier de log o` on se contente dcrire des informations u e toujours ` la n du chier. Mais ce mcanisme ne rsout pas le cas plus gnral o` les critures a e e e e u e sont ` des positions a priori arbitraires, par exemple, lorsquun chier reprsente une base de a e donnes . Il faut alors que les dirents processus utilisant ce chier collaborent ensemble pour e e ne pas se marcher sur les pieds. Un verrouillage de tout le chier est toujours possible en crant e un chier verrou auxiliaire (voir page 22). Lappel syst`me lockf permet une synchronisation e plus ne qui en ne verrouillant quune partie du chier.

2.15

Exemple complet : copie rcursive de chiers e

On va tendre la commande file_copy pour copier, en plus des chiers normaux, les liens e symboliques et les rpertoires. Pour les rpertoires, on copie rcursivement leur contenu. e e e On commence par rcuprer la fonction file_copy de lexemple du mme nom pour copier e e e les chiers normaux (page 25). 1 open Unix ...5

let file_copy input_name output_name = ...

La fonction set_infos ci-dessous modie le propritaire, les droits dacc`s et les dates de dernier e e acc`s/derni`re modication dun chier. Son but est de prserver ces informations pendant la e e e copie. 16 let set_infos filename infos = 17 utimes filename infos.st_atime infos.st_mtime; 18 chmod filename infos.st_perm; 19 try 20 chown filename infos.st_uid infos.st_gid 21 with Unix_error(EPERM,_,_) -> 22 () 34

Lappel syst`me utime modie les dates dacc`s et de modication. On utilise chmod et chown e e pour rtablir les droits dacc`s et le propritaire. Pour les utilisateurs normaux, il y a un certain e e e nombres de cas o` chown va chouer avec une erreur permission denied. On rattrape donc u e cette erreur l` et on lignore. a Voici la fonction rcursive principale. e 23 let rec copy_rec source dest = 24 let infos = lstat source in 25 match infos.st_kind with 26 S_REG -> 27 file_copy source dest; 28 set_infos dest infos 29 | S_LNK -> 30 let link = readlink source in 31 symlink link dest 32 | S_DIR -> 33 mkdir dest 0o200; 34 Misc.iter_dir 35 (fun file -> 36 if file Filename.current_dir_name 37 && file Filename.parent_dir_name 38 then 39 copy_rec 40 (Filename.concat source file) 41 (Filename.concat dest file)) 42 source; 43 set_infos dest infos 44 | _ -> 45 prerr_endline ("Cant cope with special file " ^ source) On commence par lire les informations du chier source. Si cest un chier normal, on copie son contenu avec file_copy, puis ses informations avec set_infos. Si cest un lien symbolique, on lit ce vers quoi il pointe, et on cre un lien qui pointe vers la mme chose. Si cest e e un rpertoire, on cre un rpertoire comme destination, puis on lit les entres du rpertoire e e e e e source (en ignorant les entres du rpertoire vers lui-mme Filename.current_dir_name et e e e vers son parent Filename.parent_dir_name, quil ne faut certainement pas copier), et on appelle rcursivement copy pour chaque entre. Les autres types de chiers sont ignors, avec un e e e message davertissement. Le programme principal est sans surprise : 46 let copyrec () = 47 if Array.length Sys.argv 3 then begin 48 prerr_endline ("Usage: " ^Sys.argv.(0)^ " "); 49 exit 2 50 end else begin 51 copy_rec Sys.argv.(1) Sys.argv.(2); 52 exit 0 53 end 54 ;; 55 handle_unix_error copyrec ();; Exercice 6 Copier intelligemment les liens durs. Tel que prsent ci-dessus, copyrec duplique e e 35

Oset Length 1 Codage 2 Nom Description 0 100 cha ne name Nom du chier 100 8 octal perm Mode du chier 108 8 octal uid ID de lutilisateur 116 8 octal gid ID du groupe de lutilisateur 124 12 octal size Taille du chier 3 136 12 octal mtime Date de la derni`re modication e 148 8 octal checksum Checksum de lentte e 156 1 caract`re e kind Type de chier 157 100 octal link Lien 257 8 cha ne magic Signature ("ustar\032\032\0") 265 32 cha ne user Nom de lutilisateur 297 32 cha ne group Nom du groupe de lutilisateur 329 8 octal major Identicateur majeur du priphrique e e 337 8 octal minor Identicateur mineur du priphrique e e 345 167 Padding 1 en octets. 2 tous les champs sont cods sur des cha e nes de caract`res et termins par le caract`re e e e nul \000, sauf les champs kind (Type de chier ) et le champ size (Taille du chier ) (\000 optionnel). Table 2.2 Reprsentation de lentte e e

N fois un mme chier qui apparat sous N noms dirents dans la hirarchie de chiers a e e e ` copier. Essayer de dtecter cette situation, de ne copier quune fois le chier, et de faire des e liens durs dans la hirarchie de destination. e (Voir le corrig) e

2.16

Exemple : Tape ARchive

Le format tar (pour tape archive) permet de reprsenter un ensemble de chiers en un seul e chier. (Entre autre il permet de stocker toute une hirarchie de chiers sur une bande.) Cest e donc dune certaine faon un mini syst`me de chiers. c e Dans cette section nous dcrivons un ensemble de fonctions qui permettent de lire et dcrire e e des archives au format tar. La premi`re partie, dcrite compl`tement, consiste ` crire une e e e a e commande readtar telle que readtar a ache la liste des chiers contenus dans larchive a et readtar a f ache le contenu du chier f contenu dans larchive a. Nous proposons en exercice lextraction de tous les chiers contenus dans une archive, ainsi que la fabrication dune archive ` partir dun ensemble de chiers. a Description du format Une archive tar est une suite denregistrements, chaque enregistrement reprsentant un chier. Un enregistrement est compos dun entte qui code les informae e e tions sur le chier (son nom, son type, sa taille, son propritaire etc.) et du contenu du chier. e Lentte est reprsent sur un bloc (512 octets) comme indiqu dans le tableau 2.2. Le contenu e e e e est reprsent ` la suite de lentte sur un nombre entier de blocs. Les enregistrements sont e e a e reprsents les uns ` la suite des autres. Le chier est ventuellement complt par des blocs e e a e ee vides pour atteindre au moins 20 blocs. Comme les archives sont aussi conues pour tre crites sur des supports fragiles et relues c e e plusieurs annes apr`s, lentte comporte un champ checksum qui permet de dtecter les archives e e e e 36

dont lentte est endommag (ou dutiliser comme une archive un chier qui nen serait pas une.) e e Sa valeur est la somme des codes des caract`res de lentte (pendant ce calcul, on prend comme e e hypoth`se que le le champ checksum, qui nest pas encore connu est compos de blancs et e e termin par le caract`re nul). e e Le champ kind reprsente le type des chiers sur un octet. Les valeurs signicatives sont les e caract`res indiqus dans le tableau ci-dessous 4 : e e \0 ou 0 REG 1 LINK 2 LNK 3 CHR 4 BLK 5 DIR 6 FIFO 7 CONT

La plupart des cas correspondent au type st_link des types de chier Unix. Le cas LINK reprsente des liens durs : ceux-ci ont le mme nud (inode) mais accessible par deux chemins e e dirents ; dans ce cas, le lien doit obligatoirement mener ` un autre chier dj` dni dans e a ea e larchive. Le cas CONT reprsente un chier ordinaire, mais qui est reprsent par une zone e e e mmoire contige (cest une particularit de certains syst`mes de chiers, on pourra donc le e u e e traiter comme un chier ordinaire). Le champ link reprsente le lien lorsque kind vaut LNK ou e LINK. Les champs major et minor reprsentent les numros majeur et mineur du priphrique e e e e dans le cas o` le champ kind vaut CHR ou BLK. Ces trois champs sont inutiliss dans les autres u e cas. La valeur du champ kind est naturellement reprsente par un type concret et lentte par e e e un enregistrement :1 2 3 4 5 6 7 8 9 10 11 12

open Sys open Unix type kind = | REG | LNK of string | LINK of string | CHR of int * int | BLK of int * int | DIR | FIFO | CONT

13 14 15 16 17 18 19 20 21 22 23

type header = { name : string; perm : int; uid : int; gid : int; size : int; mtime : int; kind : kind; user : string; group : string }

Lecture dun entte La lecture dun entte nest pas tr`s intressante, mais elle est incone e e e tournable. 24 exception Error of string * string 25 let error err mes = raise (Error (err, mes));; 26 let handle_error f s = 27 try f s with 28 | Error (err, mes) -> 29 Printf.eprintf "Error: %s: %s" err mes; 30 exit 231 32 33

let substring s offset len = let max_length = min (offset + len + 1) (String.length s) in

4. Ce champ peut galement prendre dautres valeurs pour coder des cas pathologiques, par exemple lorsque e la valeur dun champ dborde la place rserve dans lentte, ou dans des extensions de la commande tar. e e e e

37

34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67

let rec real_length j = if j < max_length && s.[j] \000 then real_length (succ j) else j - offset in String.sub s offset (real_length offset);; let integer_of_octal nbytes s offset = let i = int_of_string ("0o" ^ substring s offset nbytes) in if i < 0 then error "Corrupted archive" "integer too large" else i;; let kind s i = match s.[i] with \000 | 0 -> REG | 1 -> LINK (substring s (succ i) 99) | 2 -> LNK (substring s (succ i) 99) | 3 -> CHR (integer_of_octal 8 s 329, integer_of_octal 8 s 329) | 4 -> BLK (integer_of_octal 8 s 329, integer_of_octal 8 s 337) | 5 -> DIR | 6 -> FIFO | 7 -> CONT | _ -> error "Corrupted archive" "kind" let header_of_string s = { name = substring s 0 99; perm = integer_of_octal 8 s 100; uid = integer_of_octal 8 s 108; gid = integer_of_octal 8 s 116; size = integer_of_octal 12 s 124; mtime = integer_of_octal 12 s 136; kind = kind s 156; user = substring s 265 32; group = substring s 297 32; } let block_size = 512;; let total_size size = block_size + ((block_size -1 + size) / block_size) * block_size;;

La n de larchive sarrte soit sur une n de chier l` ou devrait commencer un nouvel ene a registrement, soit sur un bloc complet mais vide. Pour lire lentte, nous devons donc essayer e de lire un bloc, qui doit tre vide ou complet. Nous rutilisons la fonction really_read dnie e e e plus haut pour lire un bloc complet. La n de chier ne doit pas tre rencontre en dehors de e e la lecture de lentte. e 73 let buffer_size = block_size;; 74 let buffer = String.create buffer_size;;75 76 77 78 79 80 81 82

let end_of_file_error() = error "Corrupted archive" "unexpected end of file" let without_end_of_file f x = try f x with End_of_file -> end_of_file_error() let read_header fd = let len = read fd buffer 0 buffer_size in 38

83 84 85 86 87 88

if len = 0 || buffer.[0] = \000 then None else begin if len < buffer_size then without_end_of_file (really_read fd buffer len) (buffer_size - len); Some (header_of_string buffer) end;;

Lecture dune archive Pour eectuer une quelconque opration dans une archive, il est e ncessaire de lire lensemble des enregistrements dans lordre au moins jusqu` trouver celui qui e a correspond ` lopration ` eectuer. Par dfaut, il sut de lire lentte de chaque enregistrement, a e a e e sans avoir ` en lire le contenu. Souvent, il sura de lire le contenu de lenregistrement recherch a e ou de lire le contenu apr`s coup dun enregistrement le prcdent. Pour cela, il faut garder pour e e e chaque enregistrement une information sur sa position dans larchive, en plus de son entte. e Nous utilisons le type suivant pour les enregistrements : type record = { header : header; offset :