Provided by: manpages-fr-dev_3.57d1p1-1_all bug

NOM

       select,  pselect,  FD_CLR,  FD_ISSET,  FD_SET,  FD_ZERO  -  Multiplexage d'entrées-sorties
       synchrones

SYNOPSIS

       /* D'après POSIX.1-2001 */
       #include <sys/select.h>

       /* D'après les standards précédents */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *utimeout);

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

       #include <sys/select.h>

       int pselect(int nfds, fd_set *readfds, fd_set *writefds,
                   fd_set *exceptfds, const struct timespec *ntimeout,
                   const sigset_t *sigmask);

   Exigences   de   macros   de   test   de   fonctionnalités   pour    la    glibc    (consultez
   feature_test_macros(7)) :

       pselect() : _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600

DESCRIPTION

       select() (ou pselect()) est utilisé pour superviser efficacement plusieurs descripteurs de
       fichiers pour vérifier si l'un d'entre eux est ou devient « prêt » ;  c'est-à-dire  savoir
       si  des  entrées-sorties  deviennent  possibles ou si une « condition exceptionnelle » est
       survenue sur l'un des descripteurs.

       Ses paramètres principaux sont trois « ensembles » de descripteurs de fichiers :  readfds,
       writefds  et  exceptfds.  Chaque  ensemble  est  de  type fd_set, et son contenu peut être
       manipulé avec les  macros  FD_CLR(),  FD_ISSET(),  FD_SET(),  et  FD_ZERO().  Un  ensemble
       nouvellement  déclaré doit d'abord être effacé en utilisant FD_ZERO(). select() modifie le
       contenu de ces ensembles selon les règles ci-dessous. Après  un  appel  à  select(),  vous
       pouvez vérifier si un descripteur de fichier est toujours présent dans l'ensemble à l'aide
       de la macro FD_ISSET(). FD_ISSET() renvoie une valeur  non  nulle  si  un  descripteur  de
       fichier indiqué est présent dans un ensemble et zéro s'il ne l'est pas. FD_CLR() retire un
       descripteur de fichier d'un ensemble.

   Arguments
       readfds
              Cet ensemble est examiné afin de déterminer si  des  données  sont  disponibles  en
              lecture  à partir d'un de ses descripteurs de fichier. Suite à un appel à select(),
              readfds ne contient plus aucun de ses descripteurs de  fichiers  à  l'exception  de
              ceux qui sont immédiatement disponibles pour une lecture.

       writefds
              Cet  ensemble est examiné afin de déterminer s'il y a de l'espace afin d'écrire des
              données dans un de ses descripteurs de  fichier.  Suite  à  un  appel  à  select(),
              writefds  ne  contient  plus aucun de ses descripteurs de fichiers à l'exception de
              ceux qui sont immédiatement disponibles pour une écriture.

       exceptfds
              Cet ensemble est examiné pour  des  « conditions  exceptionnelles ».  En  pratique,
              seule  une  condition  exceptionnelle  est  courante :  la disponibilité de données
              hors-bande (OOB : Out Of Band) en lecture sur une socket  TCP.  Consultez  recv(2),
              send(2)  et  tcp(7)  pour  plus de détails sur les données hors bande. Un autre cas
              moins courant dans lequel select(2) indique une condition  exceptionnelle  survient
              avec des pseudoterminaux en mode paquet ; consultez tty_ioctl(4).) Suite à un appel
              à select(), exceptfds ne contient plus aucun  de  ses  descripteurs  de  fichier  à
              l'exception de ceux pour lesquels une condition exceptionnelle est survenue.

       nfds   Il  s'agit  d'un  entier valant un de plus que n'importe lequel des descripteurs de
              fichier de tous les  ensembles.  En  d'autres  termes,  lorsque  vous  ajoutez  des
              descripteurs  de  fichier  à  chacun des ensembles, vous devez déterminer la valeur
              entière maximale de tous ces derniers, puis ajouter un à cette valeur, et la passer
              comme paramètre nfds.

       utimeout
              Il  s'agit  du temps le plus long que select() pourrait attendre avant de rendre la
              main, même si rien d'intéressant n'est arrivé. Si cette valeur  est  positionnée  à
              NULL,  alors,  select()  bloque  indéfiniment  dans  l'attente qu'un descripteur de
              fichier devienne prêt. utimeout  peut  être  positionné  à  zéro  seconde,  ce  qui
              provoque  le  retour  immédiat  de  select(),  en  indiquant  quels descripteurs de
              fichiers étaient prêts au moment  de  l'appel.  La  structure  struct  timeval  est
              définie comme :

                  struct timeval {
                      time_t tv_sec;    /* secondes */
                      long tv_usec;     /* microsecondes */
                  };

       ntimeout
              Ce  paramètre  de  pselect()  a  la  même  signification  que utimeout, mais struct
              timespec a une précision à la nanoseconde comme explicité ci-dessous :

                  struct timespec {
                      long tv_sec;    /* secondes */
                      long tv_nsec;   /* nanosecondes */
                  };

       sigmask
              Cet  argument  renferme  un  ensemble  de  signaux  que  le  noyau  doit  débloquer
              (c'est-à-dire  supprimer  du  masque  de  signaux  du  thread appelant) pendant que
              l'appelant est bloqué par pselect() (consultez sigaddset(3) et sigprocmask(2)).  Il
              peut  valoir  NULL  et,  dans  ce cas, il ne modifie pas l'ensemble des signaux non
              bloqués à l'entrée et la sortie de la fonction. Dans ce cas, pselect() se  comporte
              alors de façon identique à select().

   Combinaison d'événements de signaux et de données
       pselect()  est utile si vous attendez un signal ou qu'un descripteur de fichier deviennent
       prêt pour  des  entrées-sorties.  Les  programmes  qui  reçoivent  des  signaux  utilisent
       généralement le gestionnaire de signal uniquement pour lever un drapeau global. Le drapeau
       global indique que l'événement doit être traité dans la boucle principale du programme. Un
       signal provoque l'arrêt de l'appel select() (ou pselect()) avec errno positionnée à EINTR.
       Ce comportement est essentiel afin que les signaux puissent être traités  dans  la  boucle
       principale  du  programme,  sinon  select() bloquerait indéfiniment. Ceci étant, la boucle
       principale implante quelque part une condition vérifiant le drapeau global, et  l'on  doit
       donc  se  demander : que se passe-t-il si un signal est levé après la condition mais avant
       l'appel à select() ? La réponse est que  select()  bloquerait  indéfiniment,  même  si  un
       signal  est  en fait en attente. Cette "race condition" est résolue par l'appel pselect().
       Cet appel peut être utilisé afin de définir le masque des signaux  qui  sont  censés  être
       reçus  que durant l'appel à pselect(). Par exemple, disons que l'événement en question est
       la fin d'un processus fils. Avant le démarrage de la boucle principale,  nous  bloquerions
       SIGCHLD  en  utilisant  sigprocmask(2).  Notre  appel  pselect()  débloquerait  SIGCHLD en
       utilisant le masque de signaux vide. Le programme ressemblerait à ceci :

       static volatile sig_atomic_t got_SIGCHLD = 0;

       static void
       child_sig_handler(int sig)
       {
           got_SIGCHLD = 1;
       }

       int
       main(int argc, char *argv[])
       {
           sigset_t sigmask, empty_mask;
           struct sigaction sa;
           fd_set readfds, writefds, exceptfds;
           int r;

           sigemptyset(&sigmask);
           sigaddset(&sigmask, SIGCHLD);
           if (sigprocmask(SIG_BLOCK, &sigmask, NULL) == -1) {
               perror("sigprocmask");
               exit(EXIT_FAILURE);
           }

           sa.sa_flags = 0;
           sa.sa_handler = child_sig_handler;
           sigemptyset(&sa.sa_mask);
           if (sigaction(SIGCHLD, &sa, NULL) == -1) {
               perror("sigaction");
               exit(EXIT_FAILURE);
           }

           sigemptyset(&empty_mask);

           for (;;) {          /* main loop */
               /* Initialiser readfds, writefds et exceptfds
                  avant l'appel à pselect(). (Code omis.) */

               r = pselect(nfds, &readfds, &writefds, &exceptfds,
                           NULL, &empty_mask);
               if (r == -1 && errno != EINTR) {
                   /* Gérer les erreurs */
               }

               if (got_SIGCHLD) {
                   got_SIGCHLD = 0;

                   /* Gérer les événements signalés ici; e.g., wait() pour
                      que tous les fils se terminent. (Code omis.) */
               }

               /* corps principal du programme */
           }
       }

   Pratique
       Quelle est donc la finalité de select() ? Ne peut on pas simplement lire  et  écrire  dans
       les  descripteurs chaque fois qu'on le souhaite ? L'objet de select() est de surveiller de
       multiples descripteurs simultanément et d'endormir proprement le processus s'il n'y a  pas
       d'activité.  Les  programmeurs UNIX se retrouvent souvent dans une situation dans laquelle
       ils doivent gérer des entrées-sorties provenant de plus d'un  descripteur  de  fichier  et
       dans  laquelle  le  flux  de  données  est intermittent. Si vous deviez créer une séquence
       d'appels read(2) et write(2), vous vous retrouveriez potentiellement bloqué sur un de  vos
       appels  attendant pour lire ou écrire des données à partir/vers un descripteur de fichier,
       alors qu'un autre descripteur de fichier est inutilisé  bien  qu'il  soit  prêt  pour  des
       entrées-sorties. select() gère efficacement cette situation.

   Règles de select
       De  nombreuses  personnes  qui  essaient  d'utiliser  select()  obtiennent un comportement
       difficile à comprendre et produisent des résultats non portables ou des  effets  de  bord.
       Par  exemple,  le  programme  ci-dessus est écrit avec précaution afin de ne bloquer nulle
       part, même s'il ne positionne pas ses descripteurs de fichier en mode non bloquant.Il  est
       facile  d'introduire  des erreurs subtiles qui annuleraient l'avantage de l'utilisation de
       select(), aussi, voici une liste de points essentiels à contrôler lors de l'utilisation de
       select().

       1.  Vous  devriez  toujours  essayer  d'utiliser select() sans timeout. Votre programme ne
           devrait rien avoir à faire s'il n'y a pas de données disponibles. Le code dépendant de
           timeouts n'est en général pas portable et difficile à déboguer.

       2.  La  valeur  nfds  doit  être calculée correctement pour des raisons d'efficacité comme
           expliqué plus haut.

       3.  Aucun descripteur de fichier ne doit être ajouté à un quelconque ensemble si  vous  ne
           projetez  pas  de  vérifier  son état après un appel à select(), et de réagir de façon
           adéquate. Voir la règle suivante.

       4.  Après le retour de select(), tous les descripteurs de fichier dans tous les  ensembles
           devraient être testés pour savoir s'ils sont prêts.

       5.  Les  fonctions  read(2),  recv(2),  write(2)  et  send(2)  ne lisent ou n'écrivent pas
           forcément la quantité  totale  de  données  spécifiée.  Si  elles  lisent/écrivent  la
           quantité  totale,  c'est  parce  que  vous avez une faible charge de trafic et un flux
           rapide. Ce n'est pas toujours le cas. Vous devriez  gérer  le  cas  où  vos  fonctions
           traitent seulement l'envoi ou la réception d'un unique octet.

       6.  Ne  lisez/n'écrivez  jamais  seulement  quelques  octets à la fois à moins que vous ne
           soyez absolument sûr de n'avoir qu'une faible quantité de données à  traiter.  Il  est
           parfaitement  inefficace  de  ne  pas lire/écrire autant de données que vous pouvez en
           stocker à chaque fois. Les tampons de  l'exemple  ci-dessous  font  1024  octets  bien
           qu'ils aient facilement pu être rendus plus grands.

       7.  Les  fonctions  read(2),  recv(2),  write(2)  et  send(2)  tout comme l'appel select()
           peuvent renvoyer -1 avec errno positionné à EINTR ou EAGAIN (EWOULDBLOCK)  ce  qui  ne
           relève pas d'une erreur. Ces résultats doivent être correctement gérés (cela n'est pas
           fait correctement ci-dessus). Si votre programme n'est pas censé recevoir  de  signal,
           alors, il est hautement improbable que vous obteniez EINTR. Si votre programme n'a pas
           configuré les entrées-sorties en mode non bloquant, vous n'obtiendrez pas de EAGAIN.

       8.  N'appelez jamais read(2), recv(2), write(2) ou send(2) avec un tampon de taille nulle.

       9.  Si l'une des fonctions read(2), recv(2), write(2) et send(2) échoue  avec  une  erreur
           autre  que  celles  indiquées  en  7.,  ou  si l'une des fonctions d'entrée renvoie 0,
           indiquant une fin de fichier, vous ne devriez pas utiliser ce  descripteur  à  nouveau
           pour  un appel à select(). Dans l'exemple ci-dessous, le descripteur est immédiatement
           fermé et ensuite est positionné à -1 afin qu'il ne soit pas inclus dans un ensemble.

       10. La valeur de timeout doit être initialisée à chaque nouvel appel à  select(),  puisque
           des  systèmes  d'exploitation  modifient la structure. Cependant, pselect() ne modifie
           pas sa structure de timeout.

       11. Comme select() modifie ses ensembles de  descripteurs  de  fichiers,  si  l'appel  est
           effectué  dans  une boucle alors les ensembles doivent être réinitialisés avant chaque
           appel.

   Émulation de usleep
       Sur les systèmes qui ne possèdent pas la fonction usleep(3), vous pouvez appeler  select()
       avec un timeout à valeur finie et sans descripteur de fichier de la façon suivante :

           struct timeval tv;
           tv.tv_sec = 0;
           tv.tv_usec = 200000;  /* 0.2 secondes */
           select(0, NULL, NULL, NULL, &tv);

       Le fonctionnement n'est cependant garanti que sur les systèmes UNIX.

VALEUR RENVOYÉE

       En  cas  de  succès,  select()  renvoie le nombre total de descripteurs de fichiers encore
       présents dans les ensembles de descripteurs de fichier.

       En cas de timeout échu, alors les descripteurs de fichier devraient tous être vides  (mais
       peuvent ne pas l'être sur certains systèmes). Par contre, la valeur renvoyée est zéro.

       Une  valeur  de  retour égale à -1 indique une erreur, errno est alors positionné de façon
       adéquate. En cas d'erreur, le contenu des ensembles renvoyés et le contenu de la structure
       de  timeout  sont  indéfinis  et  ne  devraient  pas  être exploités. pselect() ne modifie
       cependant jamais ntimeout.

NOTES

       De façon générale, tous les systèmes  d'exploitation  qui  gèrent  les  sockets  proposent
       également select(). select() peut être utilisé pour résoudre de façon portable et efficace
       de nombreux problèmes que des programmeurs naïfs essaient de résoudre  avec  des  threads,
       des  forks,  des  IPC,  des  signaux,  des  mémoires  partagées  et  d'autres méthodes peu
       élégantes.

       L'appel système poll(2) a les mêmes fonctionnalités que select(), tout en étant légèrement
       plus  efficace  quand  il  doit  surveiller  des  ensembles  de descripteurs creux. Il est
       disponible sur la plupart des systèmes de  nos  jours,  mais  était  historiquement  moins
       portable que select().

       L'API  epoll(7)  spécifique  à  Linux fournit une interface plus efficace que select(2) et
       poll(2) lorsque l'on surveille un grand nombre de descripteurs de fichier.

EXEMPLE

       Voici un exemple qui montre  mieux  l'utilité  réelle  de  select().  Le  code  ci-dessous
       consiste en un programme de « TCP forwarding » qui redirige un port TCP vers un autre.

       #include <stdlib.h>
       #include <stdio.h>
       #include <unistd.h>
       #include <sys/time.h>
       #include <sys/types.h>
       #include <string.h>
       #include <signal.h>
       #include <sys/socket.h>
       #include <netinet/in.h>
       #include <arpa/inet.h>
       #include <errno.h>

       static int forward_port;

       #undef max
       #define max(x,y) ((x) > (y) ? (x) : (y))

       static int
       listen_socket(int listen_port)
       {
           struct sockaddr_in a;
           int s;
           int yes;

           s = socket(AF_INET, SOCK_STREAM, 0);
           if (s == -1) {
               perror("socket");
               return -1;
           }
           yes = 1;
           if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
                   &yes, sizeof(yes)) == -1) {
               perror("setsockopt");
               close(s);
               return -1;
           }
           memset(&a, 0, sizeof(a));
           a.sin_port = htons(listen_port);
           a.sin_family = AF_INET;
           if (bind(s, (struct sockaddr *) &a, sizeof(a)) == -1) {
               perror("bind");
               close(s);
               return -1;
           }
           printf("accepting connections on port %d\n", listen_port);
           listen(s, 10);
           return s;
       }

       static int
       connect_socket(int connect_port, char *address)
       {
           struct sockaddr_in a;
           int s;

           s = socket(AF_INET, SOCK_STREAM, 0);
           if (s == -1) {
               perror("socket");
               close(s);
               return -1;
           }

           memset(&a, 0, sizeof(a));
           a.sin_port = htons(connect_port);
           a.sin_family = AF_INET;

           if (!inet_aton(address, (struct in_addr *) &a.sin_addr.s_addr)) {
               perror("bad IP address format");
               close(s);
               return -1;
           }

           if (connect(s, (struct sockaddr *) &a, sizeof(a)) == -1) {
               perror("connect()");
               shutdown(s, SHUT_RDWR);
               close(s);
               return -1;
           }
           return s;
       }

       #define SHUT_FD1 do {                                \
                            if (fd1 >= 0) {                 \
                                shutdown(fd1, SHUT_RDWR);   \
                                close(fd1);                 \
                                fd1 = -1;                   \
                            }                               \
                        } while (0)

       #define SHUT_FD2 do {                                \
                            if (fd2 >= 0) {                 \
                                shutdown(fd2, SHUT_RDWR);   \
                                close(fd2);                 \
                                fd2 = -1;                   \
                            }                               \
                        } while (0)

       #define BUF_SIZE 1024

       int
       main(int argc, char *argv[])
       {
           int h;
           int fd1 = -1, fd2 = -1;
           char buf1[BUF_SIZE], buf2[BUF_SIZE];
           int buf1_avail, buf1_written;
           int buf2_avail, buf2_written;

           if (argc != 4) {
               fprintf(stderr, "Utilisation\n\tfwd <listen-port> "
                        "<forward-to-port> <forward-to-ip-address>\n");
               exit(EXIT_FAILURE);
           }

           signal(SIGPIPE, SIG_IGN);

           forward_port = atoi(argv[2]);

           h = listen_socket(atoi(argv[1]));
           if (h == -1)
               exit(EXIT_FAILURE);

           for (;;) {
               int r, nfds = 0;
               fd_set rd, wr, er;

               FD_ZERO(&rd);
               FD_ZERO(&wr);
               FD_ZERO(&er);
               FD_SET(h, &rd);
               nfds = max(nfds, h);
               if (fd1 > 0 && buf1_avail < BUF_SIZE) {
                   FD_SET(fd1, &rd);
                   nfds = max(nfds, fd1);
               }
               if (fd2 > 0 && buf2_avail < BUF_SIZE) {
                   FD_SET(fd2, &rd);
                   nfds = max(nfds, fd2);
               }
               if (fd1 > 0 && buf2_avail - buf2_written > 0) {
                   FD_SET(fd1, &wr);
                   nfds = max(nfds, fd1);
               }
               if (fd2 > 0 && buf1_avail - buf1_written > 0) {
                   FD_SET(fd2, &wr);
                   nfds = max(nfds, fd2);
               }
               if (fd1 > 0) {
                   FD_SET(fd1, &er);
                   nfds = max(nfds, fd1);
               }
               if (fd2 > 0) {
                   FD_SET(fd2, &er);
                   nfds = max(nfds, fd2);
               }

               r = select(nfds + 1, &rd, &wr, &er, NULL);

               if (r == -1 && errno == EINTR)
                   continue;

               if (r == -1) {
                   perror("select()");
                   exit(EXIT_FAILURE);
               }

               if (FD_ISSET(h, &rd)) {
                   unsigned int l;
                   struct sockaddr_in client_address;

                   memset(&client_address, 0, l = sizeof(client_address));
                   r = accept(h, (struct sockaddr *) &client_address, &l);
                   if (r == -1) {
                       perror("accept()");
                   } else {
                       SHUT_FD1;
                       SHUT_FD2;
                       buf1_avail = buf1_written = 0;
                       buf2_avail = buf2_written = 0;
                       fd1 = r;
                       fd2 = connect_socket(forward_port, argv[3]);
                       if (fd2 == -1)
                           SHUT_FD1;
                       else
                           printf("connexion de %s\n",
                                   inet_ntoa(client_address.sin_addr));
                   }
               }

               /* NB : lecture des données hors bande avant les lectures normales */

               if (fd1 > 0)
                   if (FD_ISSET(fd1, &er)) {
                       char c;

                       r = recv(fd1, &c, 1, MSG_OOB);
                       if (r < 1)
                           SHUT_FD1;
                       else
                           send(fd2, &c, 1, MSG_OOB);
                   }
               if (fd2 > 0)
                   if (FD_ISSET(fd2, &er)) {
                       char c;

                       r = recv(fd2, &c, 1, MSG_OOB);
                       if (r < 1)
                           SHUT_FD2;
                       else
                           send(fd1, &c, 1, MSG_OOB);
                   }
               if (fd1 > 0)
                   if (FD_ISSET(fd1, &rd)) {
                       r = read(fd1, buf1 + buf1_avail,
                                 BUF_SIZE - buf1_avail);
                       if (r < 1)
                           SHUT_FD1;
                       else
                           buf1_avail += r;
                   }
               if (fd2 > 0)
                   if (FD_ISSET(fd2, &rd)) {
                       r = read(fd2, buf2 + buf2_avail,
                                 BUF_SIZE - buf2_avail);
                       if (r < 1)
                           SHUT_FD2;
                       else
                           buf2_avail += r;
                   }
               if (fd1 > 0)
                   if (FD_ISSET(fd1, &wr)) {
                       r = write(fd1, buf2 + buf2_written,
                                  buf2_avail - buf2_written);
                       if (r < 1)
                           SHUT_FD1;
                       else
                           buf2_written += r;
                   }
               if (fd2 > 0)
                   if (FD_ISSET(fd2, &wr)) {
                       r = write(fd2, buf1 + buf1_written,
                                  buf1_avail - buf1_written);
                       if (r < 1)
                           SHUT_FD2;
                       else
                           buf1_written += r;
                   }

               /* Vérifie si l'écriture de données a rattrapé la lecture de données */

               if (buf1_written == buf1_avail)
                   buf1_written = buf1_avail = 0;
               if (buf2_written == buf2_avail)
                   buf2_written = buf2_avail = 0;

               /* une extrémité a fermé la connexion, continue
                  d'écrire vers l'autre extrémité jusqu'à ce
                  que ce soit vide */

               if (fd1 < 0 && buf1_avail - buf1_written == 0)
                   SHUT_FD2;
               if (fd2 < 0 && buf2_avail - buf2_written == 0)
                   SHUT_FD1;
           }
           exit(EXIT_SUCCESS);
       }

       Le  programme  ci-dessus  redirige  correctement  la plupart des types de connexions TCP y
       compris les signaux de données hors bande OOB transmis par les serveurs telnet. Il gère le
       problème  épineux  des  flux  de  données bidirectionnels simultanés. Vous pourriez penser
       qu'il est plus efficace d'utiliser un appel fork(2) et de dédier une tâche à chaque  flux.
       Cela  devient  alors plus délicat que vous ne l'imaginez. Une autre idée est de configurer
       les entrées-sorties comme non  bloquantes  en  utilisant  fcntl(2).  Cela  pose  également
       problème puisque ça vous force à utiliser des timeouts inefficaces.

       Le  programme  ne  gère  pas  plus  d'une  connexion  à  la  fois bien qu'il soit aisément
       extensible à une telle fonctionnalité en utilisant une liste chaînée de tampons — un  pour
       chaque  connexion.  Pour  l'instant,  de  nouvelles  connexions provoquent l'abandon de la
       connexion courante.

VOIR AUSSI

       accept(2),  connect(2),  ioctl(2),  poll(2),   read(2),   recv(2),   select(2),   send(2),
       sigprocmask(2),   write(2),  sigaddset(3),  sigdelset(3),  sigemptyset(3),  sigfillset(3),
       sigismember(3), epoll(7)

COLOPHON

       Cette page fait partie de la publication 3.57 du projet man-pages Linux.  Une  description
       du  projet  et  des  instructions  pour  signaler  des  anomalies  peuvent être trouvées à
       l'adresse http://www.kernel.org/doc/man-pages/.

TRADUCTION

       Depuis   2010,   cette   traduction   est   maintenue   à   l'aide   de    l'outil    po4a
       <http://po4a.alioth.debian.org/>  par l'équipe de traduction francophone au sein du projet
       perkamon <http://perkamon.alioth.debian.org/>.

       Stéphan Rafin (2002), Alain Portal <http://manpagesfr.free.fr/> (2006). Julien Cristau  et
       l'équipe francophone de traduction de Debian (2006-2009).

       Veuillez     signaler     toute     erreur     de     traduction     en     écrivant     à
       <debian-l10n-french@lists.debian.org>  ou  par  un  rapport  de  bogue   sur   le   paquet
       manpages-fr.

       Vous  pouvez  toujours  avoir  accès  à la version anglaise de ce document en utilisant la
       commande « man -L C <section> <page_de_man> ».