10 janvier 2006 | 
~/triche qu'il interdit en lecture et place
son programme dans un sous-répertoire de ~/triche
avec un nom «biscornu» qu'il communique seulement à Alice. 
Donner la liste des commandes, dans l'ordre, exécutées par Bob avant
d'envoyer de chuchoter un secret à Alice.
(On pourra utiliser les commandes shell
mkdir,
chmod,
cp.)
# Bob crée un sous-répertoire de /tmp # (où tout le monde a le droit d'accès) mkdir /tmp/triche # Il interdit la lecture sur le répertoire (en retirant le bit r) # mais autorise l'accès aux sous-répertoires de ce répertoire (en ajoutant le # bit x) chmod og=x /tmp/triche # maintenant, il peut créer un sous-répertoire avec un nom difficile # à deviner puis y placer ses fichiers mkdir /tmp/triche/4748850757602420532 cp *.ml* /tmp/triche/4748850757602420532 # il faut donner les droits de lecture au sous-répertoire et à tout son # contenu. chmod -R og=r /tmp/triche/4748850757602420532 # Maintenant, Bob peut donner le nom du répertoire # /tmp/triche/4748850757602420532 à Alice.  | 
cd pour se placer dans son répertoire de login ~bob,
puis il a fait une fausse manipulation: il a exécuté 
la commande chmod -x . alors qu'il voulait 
faire chmod og-x . et s'affole lorsqu'il 
fait ls -ld et voit le résultat. Essayant de réparer, il fait
chmod og+x . mais il panique à la vue du résultat.chmod -x . retirer les droit d'exécution à tout le
monde. Pour un répertoire, cela interdit de se déplacer dans les
sous-répertoires. La commande ls -ld sans argument droit être comprise
comme ls -ld . et comme la commande chmod +x .
elle désigne une chemin relatif par rapport au répertoire courant donc de se
déplacer dans un sous-répertoire du
mon_true et mon_false comparables aux
 commandes true et false du système (tester man true et man false)
 qui ne font rien et dont les
 valeurs de retour sont respectivement toujours 0 et toujours 1.
 Pour préciser la valeur de retour, on utilisera la fonction exit
 du module Pervasives. Ce module est ouvert par défaut, il n'est
 donc pas nécessaire de préfixer cette fonction par le nom du module,
 ni d'ouvrir le module (open Pervasives).
 Pour afficher la valeur de retour d'une commande qui vient d'être
 exécutée par l'interpréteur de commandes on utilisera la commande
 echo $?. (Testez vos programmes!)
mon_true.ml contient seulement: 
 exit 0;;  | 
exit 0 à la fin du
programme qui sera donc exécuté si un autre appel à exit n'est défini 
pas effectué par l'utilisateur. Le fichier mon_false.ml contient:
 exit 1;;  | 
bonjour qui affiche la chaîne de caractères
"Bonjour monde !"
suivie d'un retour à la ligne sur la sortie standard et qui a toujours pour
valeur de retour 0. Pour réaliser l'affichage d'une chaîne de
caractères suivie d'un retour à la ligne, on utilisera la fonction
print_endline du module Pervasives.
print_endline "Bonjour monde!";;  | 
mon_echo qui affiche les chaînes de caractères
passées en argument sur la sortie standard séparées par un espace (pas
d'espace à la fin !) et a pour valeur de retour 0. Les arguments de
la ligne de commande sont placés dans le tableau argv du
module Sys dont l'élément d'indice i est récupéré par Sys.argv.(i).
La longueur d'un tableau est retournée par la fonction Array.length.
L'affichage d'une chaîne de caractères sans retour à la ligne se fait par la
fonction print_string du module Pervasives.
(* on ignore Sys.argv.(0) qui est le nom de la commande et pas un argument de la commande *) if Array.length Sys.argv > 1 then begin print_string Sys.argv.(1); for i = 2 to Array.length Sys.argv - 1 do print_char ' '; print_string Sys.argv.(i) done; end; print_newline ();;  | 
mon_printenv qui affiche des informations sur
 les variables d'environnement et qui a le même comportement que la
 commande printenv du système (man):0 lorsque
 tout s'est bien passé. Pour cela,
 on utilisera la fonction Unix.environment de type unit -> string array
 qui retourne l'ensemble des variables d'environnement sous la forme
 d'un tableau et la fonction Array.iter de type
 ('a -> unit) -> 'a array -> unit qui permet d'itérer une fonction passée
 en premier argument 
 sur les éléments d'un tableau passé en second argument. 1, sinon elle retourne 0. Pour récupérer la
 valeur d'une variable d'environnement en fonction de son nom, on
 utilisera la fonction Sys.getenv. Cette dernière lève une
 exception Not_found lorsque le nom passé en argument ne correspond
 pas à une variable d'environnement valide. 
match Array.length Sys.argv with 1 -> Array.iter print_endline (Unix.environment ()) | n -> let errors = ref false in for i = 1 to n - 1 do try print_endline (Unix.getenv Sys.argv.(i)) with Not_found -> errors := true done; if !errors then exit 1;;  | 
mon_pwd qui affiche le
répertoire courant équivalente à la commande /bin/pwd du système.Unix.getcwd. 
Unix.handle_unix_error print_endline (Unix.getcwd());;  | 
Unix.handle_unix_error de type
('a -> 'b) -> 'a -> 'b pour récupérer et afficher les éventuelles formes
de l'exception 
Unix_error levées dans le module Unix lors des appels système.Unix.getcwd n'est pas toujours directement un appel système mais
simplement une fonction écrite en C (qui elle-même fait des appels
système). On se propose donc de réécrire la fonction getcwd en OCaml.
Cette fonction doit pouvoir être utilisée comme fonction de librairie : elle
ne doit donc pas modifier les variables globales du programme ni créer une
fuite de mémoire. Cette fonction remonte récursivement dans la hiérarchie
jusqu'à la racine du système en recherchant à chaque étape le répertoire
courant « . » dans le répertoire supérieur « .. ». Décrire le schéma
général du programme.
cwd) à partir duquel sont résolus les chemins
 relatifs, mais le chemin absolu de ce répertoire n'est pas
 mémorisé. equal_node de type stats -> stats -> bool
qui teste si deux noeuds de la hiérarchie de fichiers sont
identiques. Deux noeuds sont identiques si et seulement si leurs
numéros de noeuds et leurs numéros de partition sont égaux.open Unix;; let equal_node n1 n2 = n1.st_ino = n2.st_ino && n1.st_dev = n2.st_dev;;  | 
try_finally qui prend quatre arguments f, x,
finally et y et qui effectue le calcul f x, puis, avant de retourner
le résultat, exécute finally y, y compris lorsque le résultat est une
exception.
let try_finalize f x finally y = let res = try f x with exn -> finally y; raise exn in finally y; res;;  | 
dir_find qui prend en arguments un prédicat f string -> bool
et un nom de répertoire et recherche dans celui-ci le nom d'un fichier
qui satisfait le prédicat f. Si la fonction trouve le fichier elle
retourne son nom, sinon elle lève l'exception Not_found. Pour écrire
cette fonction, on utilisera les fonctions Unix.opendir,
Unix.readdir et Unix.closedir. 
Pour ne pas créer une fuite de mémoire, on fera bien attention à
refermer le répertoire ouvert, y compris lorsqu'une exception est
levée, avant de rendre le résultat ou de relever une exception.
let dir_find f path = let dir_handle = opendir path in let rec find () = let name = readdir dir_handle in if f name then name else find () in try try_finalize find () closedir dir_handle with End_of_file -> raise Not_found;;  | 
mon_getcwd qui se comporte comme Unix.getcwd.
L'algorithme manipule des chemins et des noeuds. Les informations sur les noeuds, indispensables à la
comparaison de noeuds, sont obtenues par l'appel système
Unix.lstat qui prend en argument un chemin (on
n'utilise pas Unix.stat ici car on recherche un chemin directe issu
de la racine qui ne traverse pas de liens symboliques). Pour être
portable, on utilisera les fonctions concat, current_dir_name (.) et parent_dir_name
(..) du module Filename pour manipuler les chemins.
On évitera l'utilisation de chdir qui affecterait alors le reste du
programme.
Unix.lstat est coûteux, on évite donc de répéter
 plusieurs fois. Pour cela, on va maintenir pour chaque noeud une
 structure qui regroupe le chemin et l'information sur le
 noeud associé au chemin.
 type info = { path : string; lstat : stats };; let info path = { path = path; lstat = lstat path };;  | 
mon_getcwd, elle peut lever l'exception
Not_found lorsqu'on ne réussit pas à trouver de chemin, ce qui peut se
produire si l'arborescence a été modifiée en cours de calcul (par un autre
programme s'exécutant en parallèle). 
 open Filename;; let mon_getcwd() = let rec find_root node = let parent_node = info (concat parent_dir_name node.path) in if equal_node node.lstat parent_node.lstat then "/" else let found name = name <> current_dir_name && name <> parent_dir_name && equal_node node.lstat (lstat (concat parent_node.path name)) in let name = dir_find found parent_node.path in concat (find_root parent_node) name in find_root (info current_dir_name);;  | 
current_dir_name et parent_dir_name car on sait à coup sûr qu'ils
ne peuvent pas convenir. On évite ainsi deux appels systèmes inutiles.
Terminer le programme. On n'oubliera pas d'appeler
handle_unix_error pour reporter les messages d'erreurs éventuels 
qui peuvent se produire pendant le parcours de la hiérarchie.
let mon_pwd () = try print_endline (mon_getcwd ()) with Not_found -> prerr_endline "Root directory could not be found!"; exit 1;; handle_unix_error mon_pwd ();;  | 
mon_find qui permet d'effectuer diverses
recherches dans le système de fichiers. On se limite ici à un sous-ensemble
des possibilités de la commande find du système.find d'Unix (man find) permettant d'exécuter les opérations
suivantes: 
find .
find . -type d
find . -maxdepth 2 -type d
find ~ -type d -atime 2
mon_find0 équivalente à find . -maxdepth 1.
open Unix;; open Filename;; let mon_find0 () = print_endline current_dir_name; (* pour faire comme find *) let dir_handle = opendir current_dir_name in let rec print_dir () = let name = readdir dir_handle in if name <> current_dir_name && name <> parent_dir_name then print_endline (concat current_dir_name name); print_dir () in try print_dir () with End_of_file -> closedir dir_handle; exit 0; | e -> closedir dir_handle; raise e;; handle_unix_error mon_find0 ();;  | 
mon_find1 qui prend en argument une
profondeur p et affiche la liste des chemins accessibles depuis le
répertoire courant avec une profondeur inférieure à p.
open Unix;; open Filename;; let usage_string = ("Usage: " ^ Sys.argv.(0) ^ " profondeur");; let prerr_usage () = prerr_endline usage_string;; let rec mon_find depth file = let dir_handle = opendir file in let rec rec_print_dir () = let name = readdir dir_handle in if name <> current_dir_name && name <> parent_dir_name then begin let full_name = (concat file name) in print_endline full_name; if depth <> 1 then begin let stat = lstat full_name in if stat.st_kind = S_DIR then mon_find (depth - 1) full_name end; end; rec_print_dir () in try rec_print_dir () with End_of_file -> closedir dir_handle | e -> closedir dir_handle; raise e;; let mon_find1 () = if Array.length Sys.argv <> 2 then begin prerr_usage (); exit 1 end else try let max_depth = int_of_string Sys.argv.(1) in print_endline current_dir_name; mon_find max_depth current_dir_name; exit 0 with _ -> prerr_usage (); exit 1;; handle_unix_error mon_find1 ();;  | 
-maxdepth ;
-type ;
-atime ;
-follow ;
-regexp. 
Arg pour
récupérer les arguments de la ligne de commande et le module Str (dont la 
librairie str.cma est à charger explicitement pour compiler)
pour manipuler les expressions régulières.
open Unix;; (* analyse des arguments *) let options = ref false;; let file_list = ref [];; let max_depth = ref max_int;; let filter_list = ref [];; let stat_function = ref lstat;; let usage_string = ("Usage: " ^ Sys.argv.(0) ^ " [files...] [options...]");; let prerr_usage () = prerr_string usage_string; prerr_newline ();; let maxdepth_opt n = options := true; max_depth := n;; let type_opt t = let f = fun t n s -> s.st_kind = t in match t with "f" -> filter_list := (f S_REG) :: !filter_list | "l" -> filter_list := (f S_LNK) :: !filter_list | "d" -> filter_list := (f S_DIR) :: !filter_list | "b" -> filter_list := (f S_BLK) :: !filter_list | "c" -> filter_list := (f S_CHR) :: !filter_list | "s" -> filter_list := (f S_SOCK) :: !filter_list | "p" -> filter_list := (f S_FIFO) :: !filter_list | _ -> raise (Arg.Bad t) ;; let atime_opt d = let f = fun n s -> let access_time = int_of_float (Unix.time() -. s.st_atime) in (d + 1) * 86400 > access_time && access_time > d * 86400 in filter_list := f ::!filter_list;; let follow_opt () = stat_function := stat;; let regex_opt r = let regexp = Str.regexp r in let f n s = Str.string_match regexp n 0 in filter_list := f :: !filter_list;; let file_arg name = if !options then prerr_usage () else file_list := name :: !file_list;; (* fonctions auxilaires *) let filter filename filestat filters = List.for_all (fun f -> f filename filestat) filters;; let iter_dir f d = let dir_handle = opendir d in try while true do f (readdir dir_handle) done with End_of_file -> closedir dir_handle | x -> closedir dir_handle; raise x;; let errors = ref false;; let allow_unix_error f x = try f x with Unix_error (e,b,c) -> errors := true; prerr_string (Sys.argv.(0)^": " ^c^ ": " ^(error_message e)); prerr_newline();; (* fonction principale *) let rec find depth filename = let filestat = !stat_function filename in if filter filename filestat !filter_list then print_endline filename; if depth < !max_depth && filestat.st_kind = S_DIR 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 childstat = !stat_function child_name in find (depth+1) child_name in (* process_child et iter_dir peuvent générer des erreurs *) (* et doivent donc être protégées *) allow_unix_error (iter_dir (allow_unix_error process_child)) filename;; (* le programme *) let mon_find () = let opt_list = [ ("-maxdepth", Arg.Int maxdepth_opt, "max depth search"); ("-type", Arg.String type_opt, "file type"); ("-atime", Arg.Int atime_opt, "file accessed n*24 hours ago"); ("-follow", Arg.Unit follow_opt, "follow symlinks"); ("-regex", Arg.String regex_opt, "file name matches regexp") ] in Arg.parse opt_list file_arg usage_string; filter_list := List.rev !filter_list; begin match !file_list with [] -> find 0 Filename.current_dir_name | _ -> List.iter (find 0) (List.rev !file_list) end; if !errors then exit 1;; handle_unix_error mon_find();;  | 
Ce document a été traduit de LATEX par HEVEA