%; whizzy -format babel
\documentclass[12pt]{article}

\usepackage[latin1]{inputenc}
\usepackage[french]{babel}
\usepackage{color}
\usepackage{graphicx}
\usepackage{advi}
\usepackage{alltt}

\pagestyle{empty}

\def\adviheader{\vspace*{-0.14\textheight}{\vbox to 0em{\vbox {\vfill
\noindent\small{{\bf Cash, un shell Caml}
\hfill 3 mai 2002}}
\vss}}
% \includegraphics[width=\textwidth]{../../tex/bar.jpg.eps}
\bigskip\bigskip
\par}

\def\advifooter{\vbox to 0em{\vbox to \vsize {\vfill
\small{INRIA-Rocquencourt
\hfill \thepage
\hfill Bruno.Verlyck@inria.fr}}
\vss}}

\let \Newpage \newpage
\def \newpage {\Newpage \advifooter\adviheader}

\begin{document}

\large{

\newpage

\vfill
\vfill
\begin{center}\begin{Large}
{\bf Cash, le Caml Shell}
\end{Large}\end{center}
\vfill
\vfill
\begin{center}\begin{Large}
{\bf un GT sans règle de typage}
\end{Large}\end{center}
\vfill
\vfill

\newpage\bigskip

\subsection* {Cash, c'est quoi ?}\bigskip

\noindent
Cash est un portage de Scsh, qui est une spécification d'interface système pour
Scheme, par Olin Shivers.  Il a aussi écrit la 1ère implantation (la seule
largement diffusée).

\begin{itemize}
\item la spécification est conçue pour un langage fonctionnel
\item elle englobe toute la programmation système du niveau libc au niveau shell
\end{itemize}

\adviwait

\subsubsection* {Concrètement, c'est un module}\bigskip

\noindent Donc
\begin{itemize}
\item on programme en Ocaml
\item pas un langage de commande
\item clarté plutôt que la concision
\end{itemize}

\newpage\bigskip

\subsection* {Scsh vu de loin} \bigskip

\noindent
a deux grandes composantes~:
\begin{itemize}
\item des extensions de syntaxe (notamment des notations pour les pipe-lines)
\item des fonctions pour les appels système (Posix)
\item les mécanismes de base pour implanter la syntaxe (visibles)
\item une couche intermédiaire (scripts)
\end{itemize}

\adviwait

\subsubsection* {Cash vu de loin} \bigskip

\begin{itemize}
\item a beaucoup moins de syntaxe
\item a presque toutes les fonctions (objectif 98\%)
\end{itemize}

\newpage

\subsection* {Scsh vu de plus près} \bigskip

\noindent
a plusieurs catégories d'extensions de syntaxe~:
\begin{itemize}
\item pipe-lines avec redirections
\item une notation de regexps (composables)
\item une boucle awk
\item des petites macros, essentiellement pour écrire (opérateur expressions) au
      lieu de (opérateur* (lambda () expressions)).  Exemple:\\
        \verb"    (with-cwd dir (action))"
\end{itemize}

\noindent
\subsubsection* {Cash n'a rien de tout ça} \bigskip

\begin{itemize}
\item Pcre
\item pipe-lines
\end{itemize}

\newpage

\subsection* {Interface fonctionnelle au système et scripts}

\bigskip\noindent
Scsh est plus riche que vu ci-dessus~:

\subsubsection*{Appels système / Bibliothèque d'exécution}\bigskip

\noindent
Noter qu'\verb"Unix" fournit déjà les exceptions et l'allocation mémoire
\bigskip

\noindent
En comparaison~:
\begin{itemize}
\item fichiers~: peu différent
\item I/O standard~: Scsh permet qu'un fd remplace un port presque partout~
\item bufferisation
\item pipe~: rend des channels
\item exec~: liste d'arguments (plutôt qu'array)
\item select~: mélange fd / channels
\item tty, flock, signaux~: (pas encore porté)
\item création / destruction~: plus près des scripts
\item attributs des fichiers~: riche (trop ?)
\item réseau / sockets~: peu différent, mais 2 channels par socket
\end{itemize}

\newpage

\subsubsection* {Globalement} \bigskip

\begin{itemize}
\item le type fd est concret
\item les opérations sur fd existent sur channel
\item le typage est explicite dans les noms de fonctions
\item ajout de beaucoup de petites fonctions directes, genre test -d (type),
      -f/-e (existence), -r/-w/-x (droits)
\item pas d'opendir~; il y a directory\_files, glob, file\_match, fold\_directory
\item le mécanisme de wait est renforcé (3 modes, wait multiple)
\end{itemize}

\noindent
Bref, on n'hésite pas à aller au-delà de la libc

\newpage

\subsubsection*{Écriture de scripts} \bigskip

\begin{itemize}
\item date et time très complets (dont format)
\item variables d'environnement (l'environnement est une liste~; substitution~;
      \verb"setenv/setenv_from_alist/with_env")
\item fichiers temporaires (création, mais aussi transactions atomiques
      quelconques basées sur un nom de fichier)
\item pathnames (comme emacs~: \verb"directory_as_file_name"...), split,
      extensions, \verb"expand_file_name, absolute_file_name, home_file"
\item charsets (SRFI 14)
\item regexps: déléguées à \verb"Pcre"
\item lecture de lignes délimitées par charset (4 traitements du délimiteur)
\item découpage de ligne en champs
\end{itemize}

\newpage

\subsection*{La syntaxe des pipe-lines}

\bigskip\noindent
Comme elle n'existe pas, voici celle de Scsh en quotations Camlp4:

\bigskip\noindent
On lance(ra) un pipe-line par:

\begin{tabular}{ll}
\verb"«exec " {\it pipe-line} \verb"»" &    Remplace le processus courant \\

\verb"«bg " {\it pipe-line} \verb"»"  &     Lance en background~; rend un objet proc \\

\verb"«run " {\it pipe-line} \verb"»" &     Lance et attend~; rend un `exit status'
\end{tabular}

\bigskip\noindent
Un pipe-line, c'est la composition d'un processus et des redirections:

\verb"("{\it proc redir1 ... redirn}\verb")"

\bigskip\noindent
Une redirection peut avoir l'une de ces formes:

\begin{tabular}{ll}
\verb"(< "{\it [fdes] fname}\verb")" &     Ouvre fname en lecture \\

\verb"(> "{\it [fdes] fname}\verb")" &     Ouvre fname en écriture (création ou troncature) \\

\verb"(<< "{\it [fdes] string}\verb")" &   Utilise string \\

\verb"(>> "{\it [fdes] fname}\verb")" &    Écriture en fin de fname \\

\verb"(= "{\it fdes fdes/channel}\verb")"& Dup2: (\verb"fd>&channel") \\

\verb"(- "{\it fdes/channel}\verb")" &     Ferme fd (channel)
\end{tabular}

\bigskip\noindent
Un processus n'est pas forcément simple~:

\begin{tabular}{ll}
\verb"("{\it prog arg1 ... argn}\verb")" &         Par défaut: exécute un programme  \\

\verb"(| "{\it proc1 ... procn}\verb")" &          Pipe-line simple \\

\verb"(|+ "{\it connexions proc1 ... procn}\verb")" & Pipe-line complexe\\

\verb"(epf "{\it pipe-line}\verb")" &              Pipe-line avec redirections \\

\verb"(begin "{\it code source Caml}\verb")" &     Exécute le code Caml après fork \\
\end{tabular}

\bigskip\noindent
Il y a aussi \verb"&&" et \verb"||" pour l'enchaînement conditionnel.

\newpage

\subsection*{Interface fonctionnelle pour les pipe-lines}

\subsubsection*{Contrôle élémentaire des processus}

\begin{verbatim}
 (* a & <=> fork_child a *)
 fork_child : (unit -> unit) -> proc;

 fork_child_with_pipe : (unit -> unit) -> proc;
 (* a | b &     <=> fork_child (fun () -> fork_child_with_pipe a; b)
    a | b | c & <=> fork_child (fun () -> fork_child_with_pipe a;
                                          fork_child_with_pipe b;
                                          c)
  *)

 exec_path : string -> list string -> 'a;
 wait : ?wflags: (list wait_flag) -> proc -> process_status;

 (* ls <=> wait (fork_child (exec_path ``ls'' [])) *)
\end{verbatim}
\newpage
\subsubsection*{Lire ce qu'écrit un processus} \bigskip

\begin{verbatim}

 (* var=`a` *)
 run_with_string : (unit -> unit) -> string;

 (* var[@]=`a` ou plutôt en perl @var = <pipe_venant_de_a> *)
 run_with_strings : (unit -> unit) -> string list;

 (* a|&  while read -p line; do ... done -- (ksh seulement); ou open (chan,``a|'') en perl. *)
 run_with_in_channel : (unit -> unit) -> in_channel;

 (* a >temp$$ -- rend le nom du fichier temporaire *)
 run_with_file : (unit -> unit) -> string;

\end{verbatim}
\adviwait
\noindent
On n'est pas obligé de forker

\begin{verbatim}
 (* run_with_string(s) *)
 value string_of_in_channel : in_channel -> string;
 value string_list_of_in_channel : in_channel -> list string;
\end{verbatim}
 Pourquoi seulement des chaînes ?
\begin{verbatim}
 value list_of_in_channel : (in_channel -> 'a) -> in_channel -> list 'a;
\end{verbatim}
Pourquoi seulement des listes ?
\begin{verbatim}
 value fold_in_channel :
   in_channel -> (in_channel -> 'a) -> ('a -> 'b -> 'b) -> 'b -> 'b;
\end{verbatim}

\newpage
\subsubsection*{Encore plus compliqué}

\bigskip\noindent
Si on a besoin de l'''exit status''~:
\begin{verbatim}
     value run_with_chan_plus_proc : (unit -> unit) -> (in_channel * proc);
\end{verbatim}
\noindent
On fera wait sur le proc rendu.

\bigskip\bigskip\noindent
Le processus écrit sur plusieurs fd et on veut tous les lire~:
\begin{verbatim}
 value run_with_collecting :
   list fd -> (unit -> unit) -> (process_status * list in_channel);
\end{verbatim}

\bigskip\noindent
Le cas fréquent est~: on veut lire stderr en cas d'erreur, et stdout si tout se passe bien.
Note~: plus de parallélisme.

\newpage

\subsubsection*{Les redirections}

\begin{verbatim}
 value with_stdin : in_channel -> (unit -> 'a) -> 'a;
 value with_stdout : out_channel -> (unit -> 'a) -> 'a;
 value with_stderr : out_channel -> (unit -> 'a) -> 'a;
 value set_stdin : in_channel -> unit;
 ...
\end{verbatim}

\noindent
Conversions fd en channel et vice versa~:

\begin{verbatim}
 (* Trivial: *)
 value fd_of_in_channel : in_channel -> fd;
 value fd_of_out_channel : out_channel -> fd;

 (* Moins trivial: *)
 value in_channel_of_fd : fd -> in_channel;
 value out_channel_of_fd : fd -> out_channel;
\end{verbatim}

\noindent
L'opération vraiment problématique~: \verb"dup2".  Les 9 opérations existent.

\begin{verbatim}
 value fdes_of_dup_in : ?newfd: fd -> in_channel -> fd;
 value out_channel_of_dup_fd : ?newfd: fd -> fd -> out_channel;
 (* Par exemple pour un fichier ouvert avec Unix.O_RDWR (ou une socquette) *)
 value in_channel_of_dup_out : ?newfd: fd -> out_channel -> in_channel;
\end{verbatim}

\noindent
Enfin, on peut changer le fd d'un channel (i.e ajouter un \verb"close")~:
\begin{verbatim}
 value move_in_channel_to_fdes : in_channel -> fd -> in_channel;
\end{verbatim}

\newpage

\subsection* {Un exemple}

\begin{alltt}
-rw-rw-r--    1 root     root         6812 May  2 19:58 /var/log/cronlog
-rw-r--r--    1 root     root          868 May  1 00:05 /var/log/cron.0.gz
-rw-r--r--    1 root     root         6769 May  1 00:05 /var/log/cronlog.0.gz
-rw-r--r--    1 root     root         6703 Apr  1 00:05 /var/log/cron.1.gz
-rw-r--r--    1 root     root         7280 Mar  1 00:05 /var/log/cron.2.gz
-rw-r--r--    1 root     root        12099 Feb  1 00:05 /var/log/cron.3.gz
-rw-r--r--    1 root     root        11949 Jan  1 00:05 /var/log/cron.4.gz
-rw-r--r--    1 root     root        11999 Dec  1 00:05 /var/log/cron.5.gz
-rw-r--r--    1 root     root          137 Dec  1 00:05 /var/log/cronlog.1.gz

#!/usr/local/bin/cash
open Cash;;
...
let rename_cronlogs_with_args () =
  let nonono = ref false
  and silent = ref true
  and dir = ref "/var/log" in
  let spec =
    ["-n", Arg.Set nonono, "\(\backslash\)tne rien faire";
     "-v", Arg.Clear silent, "\(\backslash\)tdire ce qu'on fait (ou ferait si -n)";
     "-d", Arg.String (fun s -> dir := s), "\(\backslash\)tchanger le directory des logs"]
  and usage =
    "Usage: rn-cronlogs [options]\(\backslash\)ngarde 8 mois de logs de cron en les nommant tous cronlog"
  in
  Arg.parse spec (fun arg -> failwith ("Je ne sais quoi faire de: " ^ arg)) usage;
  \textcolor{red}{with_cwd} !dir (rename_cronlogs !nonono !silent)
;;
if !(Sys.interactive) then () else rename_cronlogs_with_args ();;
\end{alltt}

\newpage
\vspace*{-0.01\textheight}
\begin{alltt}
let rename_cronlogs nonono silent () =
  let loginfos =
    List.map (fun name -> \textcolor{red}{file_last_mod_fn} name, name)
      (\textcolor{red}{glob} ["cron.*.gz"; "cronlog.*.gz"])
  in
  let loginfos =
    List.fold_left
      (fun newinfos (t, name as info) ->
         let size = \textcolor{red}{file_size_fn} name
         and too_old = \textcolor{red}{time} () -. t > float (8 * 31 * 24 * 3600) in
         let atd_only = size < 1024 && has_atd_only name in
         if too_old || atd_only then 
           (if not silent then
              Printf.printf "%s %6d\(\backslash\)t%s -> REMOVE\(\backslash\)n" (string_of_time t) size name;
            if not nonono then \textcolor{red}{delete_file} name;
            newinfos)
         else info :: newinfos)
      [] loginfos
  in
  List.fold_left
    (fun i (t, name) ->
       let newname = "cronlog." ^ string_of_int i ^ ".gz" in
       if newname = name then ()
       else
         (if not silent then
            Printf.printf "%s\(\backslash\)t%s -> %s\(\backslash\)n" (string_of_time t) name newname;
          if not nonono then \textcolor{red}{rename_file} name newname);
       i - 1)
    (List.length loginfos - 1)
    (List.sort (fun (xt, _) (yt, _) -> compare xt yt) loginfos)
;;

\end{alltt}
\newpage

\begin{alltt}

let string_of_time t = \textcolor{red}{string_of_date} (\textcolor{red}{date_of_time} t);;

let drain chan =
  ignore (\textcolor{red}{string_of_in_channel} chan)
;;

let has_atd_only name =
  ignore (\textcolor{red}{fork_child_with_pipe} (fun () -> \textcolor{red}{exec_path} "zcat" [name]));
  ignore (\textcolor{red}{fork_child_with_pipe} (fun () -> \textcolor{red}{exec_path} "egrep" ["-v"; " atd\(\backslash\)\(\backslash\)[[0-9]+\(\backslash\)\(\backslash\)]"]));
  let pipe_in = \textcolor{red}{in_channel_of_fd} 0 in
  try ignore (\textcolor{red}{read_line} ~handle_newline:\textcolor{red}{Concat} pipe_in); drain pipe_in; false with
    End_of_file -> true
;;

\end{alltt}

\newpage

\subsection*{Qu'a-t-on obtenu ?}

\bigskip
\subsubsection*{Un langage de processus dans un langage de fonctions}

\noindent
que l'on peut composer indifféremment~:

\bigskip
\begin{tabular}{|l|l|l|}
\hline
producteur/consommateur  &      fonction        & processus \\ \hline \hline
fonction &      \verb"cons (prod x)"            & \verb"<<run (cons (<< ,(prod x)))>>" \\ \hline
processus &     \verb"cons (run_with_string "   & \verb"<<run (| (prod) (cons)))>>" \\
        &       \verb§ (fun () -> exec "prod"))§ & \\ \hline
\end{tabular}

\newpage

\subsection*{2 problèmes d'implémentation}

\verb'in_channel_of_fd' et \verb'with_stdin'  
\bigskip

\includegraphics[width={2.0\textwidth}]{channels.eps}
\newpage

\subsubsection*{Le problème de Unix.dup2}

\bigskip\noindent
Il est inutilisable si fd cible est \verb|>| 2.

\bigskip\noindent
Même si Unix.fdescr était concret, il reste les channels.

\bigskip\noindent
Exemple:

\begin{verbatim}
 << run (prog) (> 1 output)
               (> 2 error)
               (> 3 log)
               (= 0 ,inchan) >>;;
\end{verbatim}

\noindent
Et si \verb"fd_of_in_channel inchan" vaut  3~?

\adviwait

\bigskip
\noindent\bigskip
Pour éviter ce problème~:

\noindent
{\it La fermeture d'un fd ne ferme pas le channel qui lui est associé, mais
déplace le channel sur un autre fd (par dup)}

\newpage

\subsubsection*{Les channels `révélés'}

Cash maintient l'information~: le fd de ce channel a été divulgué {\it n} fois,
pour 2 raisons~:

\begin{itemize}
\item on peut fermer ce fd au moment d'un \verb"exec" (par
      \verb"set_close_on_exec") si le channel n'est pas révélé.
\item on pourrait fermer le fd quand on perd toute référence au channel *et*
      qu'il n'est pas révélé.
\end{itemize}

}
\end{document}
