Author: Christian.Queinnec@lip6.fr

## Descripteurs de fichier

Au chapitre 3, nous avons vu les fonctions standard du module `Pervasives`, elles permettent d'accéder aux fichiers via des canaux d'entrées-sorties. Il existe un moyen de plus bas niveau d'accès aux fichiers en ayant recours à leur descripteur.
Un descripteur de fichier est une valeur abstraite, de type `Unix.file_descr`, qui contient les informations nécessaires à l'utilisation d'un fichier : un pointeur vers ce fichier, les droits d'accès à ce fichier, les conditions d'accès au fichier (lecture ou écriture), la position courante dans le fichier, etc.
Trois descripteurs sont prédéfinis. Ils correspondent aux fichiers d'entrée standard, de sortie standard et d'erreur standard.

    # ( Unix.stdin , Unix.stdout , Unix.stderr ) ;;
    - : Unix.file_descr * Unix.file_descr * Unix.file_descr =
    <abstr>, <abstr>, <abstr>

Attention à ne pas les confondre avec les canaux d'entrées-sorties leur correspondant :

    # ( Pervasives.stdin , Pervasives.stdout , Pervasives.stderr ) ;;
    - : in_channel * out_channel * out_channel = <abstr>, <abstr>, <abstr>

Des fonctions de conversion entre canaux et descripteurs de fichier sont décrites à la page ??.

##### Droits d'accès aux fichiers

Sous Unix, des droits en lecture, en écriture et en exécution sont attachés à chaque fichier. Ils concernent trois catégories d'utilisateurs : le propriétaire du fichier, les membres du groupe1 du propriétaire ou l'ensemble de tous les utilisateurs.
Les droits d'un fichier sont représentés par 9 bits divisés en trois groupes de trois bits. Le premier groupe représente les droits du propriétaire, le deuxième les droits des membres du groupe du propriétaire et le dernier les droits des autres utilisateurs. Dans chaque groupe de trois bits, le premier représente le droit en lecture, le deuxième, le droit en écriture et le dernier, le droit en exécution. On a coutume de représenter ces droits respectifs par les lettres `r`, `w` et `x` , et l'absence de droit par un tiret (`-`). Par exemple, le droit en lecture pour tous et en écriture pour le propriétaire seul s'écrit `rw-r--r--`. C'est, en fait, l'entier 420 (soit le binaire 0b110100100). On utilisera souvent la notation octale 0o644 qui est d'un emploi plus aisé. Ces droits sur les fichiers ne sont pas utilisés sous Windows.

### Manipulation des fichiers

##### Ouverture d'un fichier

Ouvrir un fichier, c'est récupérer un descripteur de ce fichier. Suivant l'usage que l'on veut en faire, il existe plusieurs modes d'ouverture des fichiers. Chacun correspond à une valeur du type open_flag décrit figure 18.2.

> `O_RDONLY` | en lecture
> `O_WRONLY` | en écriture
> `O_RDWR` | en lecture et en écriture
> `O_NONBLOCK` | ouverture dans un mode non bloquant
> `O_APPEND` | en ajout à la fin du fichier
> `O_CREAT` | crée le fichier s'il n'existe pas
> `O_TRUNC` | remet le fichier à 0 s'il existe
> `O_EXCL` | échoue si le fichier existe

> Figure 18.2 : Valeurs de type open_flag

Ces modes peuvent être combinés, en conséquence la fonction `openfile` prendra en argument une liste de valeurs de type open_flag.

    # Unix.openfile ;;
    - : string -> Unix.open_flag list -> Unix.file_perm -> Unix.file_descr =
    <fun>

Le premier argument est le nom du fichier et le dernier est un entier2 codant les permissions à donner au fichier en cas de création.
Voici comment ouvrir en lecture un fichier, ou le créer avec les droits `rw-r--r--` s'il n'existe pas :

    # let fichier = Unix.openfile "essai.dat" [Unix.O_RDWR; Unix.O_CREAT] 0o644 ;;
    val fichier : Unix.file_descr = <abstr>

##### Fermeture d'un fichier

La fonction `Unix.close` permet de fermer un fichier. On l'applique au descripteur du fichier que l'on désire fermer.

    # Unix.close ;;
    - : Unix.file_descr -> unit = <fun>
    # Unix.close fichier ;;
    - : unit = ()

##### Redirection de fichiers

On peut attacher à une même entrée-sortie plusieurs descripteurs. Si l'on dispose d'un seul descripteur et que l'on veut en obtenir un nouveau, on utilisera :

    # Unix.dup ;;
    - : Unix.file_descr -> Unix.file_descr = <fun>

Si l'on dispose de deux descripteurs et que l'on veut réassigner au second l'entrée-sortie correspondant au premier, on utilisera la fonction :

    # Unix.dup2 ;;
    - : Unix.file_descr -> Unix.file_descr -> unit = <fun>

Par exemple, on redirige la sortie d'erreur sur un fichier :

    # let sortie_erreur = Unix.openfile "err.log" [Unix.O_WRONLY;Unix.O_CREAT] 0o644 ;;
    val sortie_erreur : Unix.file_descr = <abstr>
    # Unix.dup2 Unix.stderr sortie_erreur ;;
    - : unit = ()

Les écritures sur la sortie erreur standard sont détournées vers le fichier `err.log`.

### Entrées-sorties sur un fichier

Les fonctions de lecture et d'écriture sur un fichier `Unix.read` et `Unix.write` utilisent une chaîne de caractères comme média entre le fichier et le programme Objective CAML.

    # Unix.read ;;
    - : Unix.file_descr -> string -> int -> int -> int = <fun>
    # Unix.write ;;
    - : Unix.file_descr -> string -> int -> int -> int = <fun>

Outre le descripteur de fichier et la chaîne, les fonctions prennent deux entiers en argument qui sont l'indice du premier caractère et le nombre souhaité de caractères à lire ou à écrire. L'entier retourné est le nombre de caractères effectivement lus ou écrits.

    # let mode = [Unix.O_WRONLY;Unix.O_CREAT;Unix.O_TRUNC] in
     let fic = Unix.openfile "fichier" mode 0o644 in
     let str = "012345678901234565789" in
     let n = Unix.write fic str 4 5
     in Printf.printf "On a écrit %s dans le fichier\n" (String.sub str 4 n) ;
        Unix.close fic ;;
    On a écrit 45678 dans le fichier
    - : unit = ()

La lecture procède du même principe :

    # let fic = Unix.openfile "fichier" [Unix.O_RDONLY] 0o644 in
     let str = String.make 20 '.' in
     let n = Unix.read fic str 2 10 in
      Printf.printf "On n'a lu que %d caractères" n;
      Printf.printf " et on obtient la chaîne %s\n" str;
      Unix.close fic ;;
    On n'a lu que 5 caractères et on obtient la chaîne ..45678.............
    - : unit = ()

Un accès à un fichier se fait toujours à la position courante contenue dans son descripteur. On peut cependant modifier cette position grâce à la fonction :

    # Unix.lseek ;;
    - : Unix.file_descr -> int -> Unix.seek_command -> int = <fun>

Le premier argument est le descripteur du fichier, le second est la taille, en caractères, du déplacement et le troisième argument, de type Unix.seek_command, indique l'origine. Ce dernier argument a trois valeurs possibles :

- `SEEK_SET` : relativement au début du fichier,
- `SEEK_CUR` : relativement à la position courante,
- `SEEK_END` : relativement à la fin du fichier.

Une position erronée provoque soit le déclenchement d'une exception, soit une valeur de retour égale à 0 suivant les fonctions d'entrées-sorties utilisées.

##### Canal d'entrée-sortie

Le module `Unix` fournit des fonctions de conversion entre les descripteurs de fichier et les canaux d'entrées-sorties du module `Pervasives` :

    # Unix.in_channel_of_descr  ;;
    - : Unix.file_descr -> in_channel = <fun>
    # Unix.out_channel_of_descr ;;
    - : Unix.file_descr -> out_channel = <fun>
    # Unix.descr_of_in_channel  ;;
    - : in_channel -> Unix.file_descr = <fun>
    # Unix.descr_of_out_channel ;;
    - : out_channel -> Unix.file_descr = <fun>

Il est nécessaire de préciser si les canaux d'entrées-sorties obtenus par conversion transfèrent des données binaires ou des caractères :

    # set_binary_mode_in ;;
    - : in_channel -> bool -> unit = <fun>
    # set_binary_mode_out ;;
    - : out_channel -> bool -> unit = <fun>

Dans l'exemple suivant on crée un fichier en utilisant les fonctions du module `Unix` et on le lit en utilisant la fonction d'ouverture du module `Unix` et la fonction d'entrée de plus haut niveau `input_line`.

    # let mode = [Unix.O_WRONLY;Unix.O_CREAT;Unix.O_TRUNC] in
     let f = Unix.openfile "fichier" mode 0o666 in
     let s = "0123456789\n0123456789\n" in
     let n = Unix.write f s 0 (String.length s)
     in Unix.close f ;;
    - : unit = ()
    # let f = Unix.openfile "fichier" [Unix.O_RDONLY;Unix.O_NONBLOCK] 0 in
     let c = Unix.in_channel_of_descr f in
     let s = input_line c
     in print_string s ;
        close_in c ;;
    0123456789- : unit = ()

##### Disponibilité

Un programme peut avoir à gérer une multiplicité d'entrées-sorties. Or celles-ci ne sont pas toujours disponibles : d'autres programmes ont pu précédemment en réclamer l'usage. La fonction suivante permet de connaître la liste des entrées-sorties disponibles à un moment donné parmi une liste donnée :

    # Unix.select ;;
    - : Unix.file_descr list ->
        Unix.file_descr list ->
        Unix.file_descr list ->
        float ->
        Unix.file_descr list * Unix.file_descr list * Unix.file_descr list
    = <fun>

Les trois premiers arguments représentent les listes respectivement des entrées (lecture), des sorties (écriture) et des sorties en erreur. Le dernier argument indique un délai d'attente en secondes (une valeur négative indique un délai nul). Le résultat est la liste des entrées-sorties disponibles en lecture, écriture et erreur.

### Warning

`select` n'est pas implantée pour Windows