Cet exercice combine l’exercice précédent (exercice 7) et la copie récursive de fichiers (exercice 6).

La seule petite difficulté est la gestion des droits: il faut créer les répertoires de l’archive avec les droits en écriture et ne mettre ceux-ci à leur valeur final qu’après extractions de tous les fichiers.

Écrivons d’abord une fonction annexe pour mkpath p m qui les directory manquant le long du chemin p avec les permissions m (avec la particularité que p peut-être terminé par une / superflu).

     
   let warning mes = prerr_string mes;prerr_newline();;
   open Filename
   let mkpath p perm =
     let normal_path =
       if basename p = "" then dirname p else p in
     let path_to_dir = dirname normal_path in
     let rec make p =
       try ignore (stat p)
       with Unix_error (ENOENT__) ->
         if p = current_dir_name then ()
         else if p = parent_dir_name then
           warning "Ill formed archive: path contains \"..\""
         else begin
           make (dirname p);
           mkdir p perm
         end in
     make path_to_dir;;

Nous définissons également une fonction set_infos analogue à la version utilisée pour la copie de fichiers (section 2.15):

     
   let set_infos header =
     chmod header.name header.perm;
     let mtime = float header.mtime in
     utimes header.name mtime mtime;
     begin match header.kind with
       LNK f -> ()
     | _ ->  chmod header.name header.perm
     end;
     try chown header.name  header.uid header.gid
     with Unix_error(EPERM,_,_) -> ();;

Le corps du programme est la fonction untar_file_collect_dirs qui traite une seule entrée en accumulant les répertoires explicitement créés par l’archive.

     
   let verbose = ref true;;
   let default_dir_perm = 0o777;;
   let default_file_perm = 0o666;;
   
   let protect f x g y = try f xg y with z -> g yraise z
   let file_exists f = try ignore (stat f); true with _ -> false;;
   
   let untar_file_collect_dirs file dirs =
     let fh = file.header in
     if !verbose then begin print_string fh.nameprint_newline() end;
     match fh.kind with
     | CHR (_,_) | BLK(_,_) | FIFO ->
         warning (fh.name ^ "Ignoring special files");
         dirs
     | DIR ->
         mkpath fh.name default_dir_perm;
         if file_exists fh.name then dirs
         else begin mkdir fh.name default_dir_permfh :: dirs end
     | x ->
         mkpath fh.name default_dir_perm;
         begin match x with
         | REG | CONT ->
             let flags = [ O_WRONLYO_TRUNCO_CREAT; ] in
             let out = openfile fh.name flags default_file_perm in
             protect (copy_file fileout close out
         | LNK f ->
             symlink f fh.name
         | LINK f ->
             begin
               try if (stat fh.name).st_kind = S_REG then unlink fh.name
               with Unix_error(_,_,_) -> ();
             end;
             Unix.link f fh.name;
         | _ -> assert false
         end;
         set_infos fh;
         dirs;;

Nous omettons la fin du programme qui consiste à itérer le corps du programme sur l’archive puis à mettre à jour les droits des répertoires en tout dernier.