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