URI:
       # Pourquoi le langage C?
       
       Je n'ai jamais eu de formation en programmation/développement/code. 
       Enfin presque, j'ai eu droit à un module d'algorithmique en première 
       année de fac de sciences. Mais j'ai toujours aimé programmer. Pour moi, 
       c'est comme une partie d'Age of empire/Rimworld/échecs/ 
       Smallworld/go/... : il faut anticiper, s'adapter et chercher le 
       meilleur moyen d'arriver à ses fins. Quand je dis "meilleur moyen", ça 
       signifie à la fois rapide et élégant.
       
       J'ai appris énormément grâce à l'esprit du logiciel libre, en lisant ce 
       que d'autres ont bien voulu partager sur le web. Aussi j'ai pu 
       découvrir des scripts shell (si on peut parler de programmation), puis 
       le python, le PHP, le javascript et le go. En passant, j'ai appris le C 
       via le site du zéro (pour ceux qui s'en souviennent). Cependant je n'ai 
       pas approfondi : grave erreur.
       
       Pendant la période de confinement de 2020, j'en ai profité pour m'y 
       remettre et réécrire certains projets en C. J'ai donc de nouveau lu du 
       code, notamment celui de
  HTML suckless.org
       mais aussi en partie celui d'
  HTML OpenBSD
       Cette expérience a été une aventure exaltante qui me fait dire 
       aujourd'hui : j'adore le C.
       
       Certes, je ne suis pas un expert, loin de là. Cependant, je suis 
       convaincu qu'il s'agit d'un des meilleurs langages existants. Voilà 
       pourquoi...
       
       ## Pas de compromis
       
       La syntaxe en C demande de la rigueur. Certains la désapprouve. Je 
       trouve au contraire qu'elle permet d'écrire du code plus lisible et 
       moins souvent source d'erreur.
       
       Une erreur, et le compilateur ne voudra pas vous créer un binaire. Pas 
       de pitié. Oui, mais en même temps, ça évite de mauvaises surprises. Au 
       passage, clang sous OpenBSD donne de bons conseils :)
       
       ## C'est léger
       
       La bibliothèque standard est à la fois complète et restreinte. 
       Autrement dit, il y a tout ce qu'il faut dedans, mais il faut parfois 
       assembler quelques pièces entre elles pour parvenir à ses fins.
       
       ## Ça force à mieux programmer
       
       Les exigences du langage poussent à se poser davantage de questions, à 
       bien réfléchir à son projet et comment y parvenir. Ça force à mieux 
       programmer.
       
       ## C'est sur-documenté
       
       C'est un langage qui date. Il y a énormément de ressources et de 
       conseils sur le web pour apprendre.
       
       ## C'est rapide
       
       Comparé aux autres langage que j'ai pu appréhender, la différence est 
       flagrante.
       
       ## C'est beau
       
       Argument complètement subjectif : je trouve le code C beau.
       
       # Tout ce que j'aurais aimé qu'on me dise quand j'ai appris le C
       
       Je code comme certains colorient un carreau sur 2, gribouillent des 
       petits dessins rigolos ou font des arabesques pour s'occuper pendant 
       les réunions. Je ne pense pas être bon à ça, mais j'aime beaucoup 
       m'occuper l'esprit en écrivant du C. C'est un peu comme jouer à 
       résoudre une énigme en s'amusant avec les règles du jeu.
       
       J'ai appris le C il y a longtemps, en lisant le tutoriel du "site du 
       zero" (j'ai dit, c'était il y a longtemps). Je m'y suis remis 
       récemment, et avec du recul, sans être un expert, je m'aperçois que 
       j'aurais bien aimé savoir certaines choses à l'époque. Voici donc tout 
       ce que j'aurais aimé lire la première fois que j'ai appris le C.
       
       ## Macros utiles
       
       Quand on déclare une chaîne de caractères, on ne sait jamais trop 
       quelle doit être sa longueur. Il existe en fait des limites déjà bien 
       définies pour nous :
       
       * `PATH_MAX` : La taille maximale d'un chemin vers un fichier (#include 
       limits.h)
       * `FILENAME_MAX` : La taille maximale d'un nom de fichie
       * `BUFSIZ` : Taille d'un buffer, souvent utiliser pour traiter du texte. 
         (`#include stdio.h`)
       
       ## Lecture
       
       Aller regarder dans les fichiers `/usr/include` peut parfois aider à 
       mieux comprendre. Mais par dessus tout, il faut lire les pages man. 
       J'ignore si c'est spécifique à OpenBSD, mais les sections (3) regorgent 
       d'explication, d'exemples, et de suggestions de fonctions associées 
       dont on ignore peut-être l'existence. Par exemple, `man strncat` propose 
       de lire des infos sur `strlcpy` pour la compléter/remplacer. Cette 
       dernière nous montre carrément des exemples.
       
       ## ternaire
       
       Bon, c'est pas original, mais je n'avais jamais vraiment compris avant:
       
       ```
       (condition) ? expression_si_vrai : expression_si_faux ;
       ```
       
       ## Les chaînes de caractères
       
       En C, il y a le type "char". Les chaînes de caractères ne sont pas un 
       type. En C, il n'y a que des tableaux de "char", terminés par un NUL : 
       `\0`. C'est idiot, mais en fait très important à garder en tête pour 
       travailler sur du texte. La confusion est facile car on mélange les 
       expressions avec des " ou des '. En bref :
       
       * `'a'` : c'est le char a
       * `"a"` : c'est la "chaîne" constituée de 'a' puis '\0'.
       
       On ne peut pas "additionner" ou modifier un tableau de char aussi 
       "facilement" que dans d'autres langages. Pourtant, dans `string.h`, on 
       peut trouver tout ce qu'il faut pour gérer les chaînes de caractères.
       
       Il vaut mieux se garder une bibliothèque personnelle de fonctions 
       gérant les chaînes, et les réemployer au besoin.
       
       Si une manipulation sur des chaînes de caractères nécessite d'allouer 
       de la mémoire avec malloc, alors il existe certainement une solution 
       plus simple. Ce n'est pas toujours vrai ceci dit ^^.
       
       Utiliser la fonction `strdup()` permet de copier une chaîne de 
       caractères. Il faut penser à appeler `free()` ensuite avec cette 
       fonction.
       
       La fonction `strsep()` modifie la chaîne qui lui est donnée. Il faut donc 
       parfois utiliser `strdup()` avant, car il faut donner une chaîne 
       modifiable et non constante. Autrement dit:
       
       ```
       char *str = "coucou"; /* n'est pas modifiable */
       char *str = strdup("coucou"); /* modifiable */
       ```
       
       `strchr()` et `strstr()` sont très utiles pour récupérer l'emplacement
       d'un caractère ou d'une chaîne de caractères. On peut directement
       intervenir à partir de ce point ensuite.
       
       Par exemple
       
       ```
       char *pos = NULL;
       char *str = "Respirer de la compote fait tousser";
       
       pos = strstr(str, "compote");
       puts(str); /* affiche "compote fait tousser" /*
       ```
       
       Toujours initialiser une chaîne avec des 0 : ainsi, on est sûr qu'elle 
       est toujours terminée correctement (par un `\0`).
       
       ```
       char str[BUFSIZ] = {'\0'};
       char *str2       = calloc(BUFSIZ, sizeof(char));
       ```
       
       Pour "vider" (remettre à zéro) une chaine de caractère (reset) :
       
       ```
       bzero(s, sizeof(s));
       ```
       
       Pour modifier une chaîne de caratères à l'aide d'une fonction, cette 
       dernière doit prendre en argument la taille de la chaîne. En effet, 
       elle ne reçoit en réalité qu'un pointeur vers un tableau de char, et un 
       `sizeof()` ne retournerait que la taille du pointeur. Exemple :
       
       ```
       void
       modif_str(char *str, ssize_t strsiz)
       {
           strlcpy(str, "coucou", strsiz);
       }
       
       char    test[256] = {'\0'};
       
       modif_str(test, sizeof(test));
       ```
       
       ## Fichiers
       
       Il vaut mieux utiliser les fonctions `fread()`/`fwrite()` plutôt que 
       `read()`/`write()` car elles sont plus efficaces (utilisation d'un cache).
       
       ```
       size_t nread = 0;
       FILE *fd = NULL;
       char           *buffer[BUFSIZ];
       
       if ((fd = fopen(fp, "r")) == NULL) { goto err; }
       
       while ((nread = fread(buffer, 1, sizeof(buffer), fd)) != 0)
           fwrite(buffer, 1, nread, stdout);
       fclose(fd);
       if (ferror(fd)) {
           err(1,"closefile");
       }
       ```
       
       Pour lister le contenu répertoire: `scandir()`
       
       ```
       #include <sys/types.h>
       #include <dirent.h>
       int n = 0;
       struct dirent **namelist;
       
       if ((n = scandir(path, &namelist, NULL, alphasort)) < 0) {
           err(1, "Can't scan %s", path);
       } else {
           for(int j = 0; j < n; j++) {
               if (!strcmp(namelist[j]->d_name, ".")) {
                   continue;
               }
               if (namelist[j]->d_type == DT_DIR) {
                   printf("%s\n", namelist[j]->d_name);
               }
               free(namelist[j]);
       }
       free(namelist);
       }
       ```
       
       ## Gestion des erreurs
       
       ```
       #include <err.h>
       #include <errno.h>
       ```
       
       Pour afficher une erreur et quitter : `err(1, "erreur :%s", blabla);`
       Pour afficher un avertissement et quitter :
       `warn("attention :%s", blabla);`
       
       ## Compiler
       
       Dans la plupart des cours en C, on n'apprend pas à compiler. Pour un 
       simple fichier "main.c", il suffit d'un:
       
       ```
       make main
       ```
       
       Qui est en fait un raccourci pour:
       
       ```
       cc -o main main.c
       ```
       
       Pour compiler en utilisant une bibliothèque, il faut utiliser les 
       options `-l` `-L`, et `-I`. Le `-I` permet de dire où se trouvent les 
       entêtes (.h), `-L` indique où se trouvent les bibliothèques et `-l` 
       indique quelle bibilothèque utiliser.
       
       C'est à partir de ce moment qu'un fichier Makefile devient intéressant. 
       Voici le modèle que j'utilise. Il est assez strict au niveau de la 
       gestion des erreurs, ça me force à écrire du code moins sensible:
       
       ```
       NAME = thename
       VERSION = 0.1
       
       PREFIX?=/usr/local/
       INCS     = -I/usr/local/include
       LIBS     = -L/usr/local/lib -lCHANGEME -lOTHERLIB
       LDFLAGS  = ${LIBS}
       CPPFLAGS = -DVERSION=\"${VERSION}\"
       
       CFLAGS  += -pedantic -Wall -Wextra -Wmissing-prototypes \
                  -Werror -Wshadow -Wstrict-overflow -fno-strict-aliasing \
                  -Wstrict-prototypes -Wwrite-strings \
                  ${CPPFLAGS} \
                   ${INCS} \
                  -Os 
       
       .SUFFIXES: .c .o
       
       SRC != find . -type f -name \*.c
       H != find . -type f -name \*.h
       OBJ = ${SRC:.c=.o}
       
       .c.o:
           ${CC} ${CFLAGS} -c $<
       
       all: ${NAME}
       
       ${OBJ}: ${H}
       
       clean:
           rm -f ${NAME} *.core *.o
       
       ${NAME}: ${OBJ}
           ${CC} -o $@ ${OBJ} ${CFLAGS} ${LDFLAGS}
       ```
       
       Bien sûr, il faudra changer "NAME", les "CHANGEME" après les `-l` et 
       pourquoi pas lire `man make` :)
       
       Si on veut compiler un binaire pour un chroot, il faut ajouter l'option 
       `-static`.
       
       ## Modestie
       
       Parfois, on lit du code très difficile à comprendre, avec des 
       opérations sur pointeurs pour gérer des chaînes de caractère par 
       exemple.
       
       Il faut rester parfois modeste, et arrêter de vouloir un code 
       surpuissant meilleur que les autres. Il n'y a pas de code parfait.
       
       Par exemple, c'est parfois tout aussi efficace de lire un fichier 
       caractère après caractères plutôt que de chercher à le découper en 
       morceaux à la recherche d'un motif particulier. Le C est très rapide :)
       
       ## Ressources
       
  HTML Excellent site pour apprendre les bases du C
       
  HTML Learn C the Hard Way de Zed A. Shaw
       
  HTML Des remarques intéressantes pour écrire du C récent
       
  HTML Notions pour présenter du code propre
       
  HTML Idem, à la suckless
       
  HTML Des exemples de code très instructifs
       
  HTML Beej's Guide to C Programming
       
       ## Snippets C
       
       Cette partie sera alimentée de fonctions et extraits de code en C en 
       guise d'aide mémoire. Ne prenez rien pour parfait, ces extraits peuvent 
       certainement être améliorés.
       
       ### Lire un fichier ligne après lignes
       
       `getline()`, et hop :) Le man présente même un exemple.
       
       Ceci dit, c'est souvent aussi rapide d'utiliser `fgets()` ou `fgetc()`.
       
       ### Initialiser une structure
       
       Le mieux reste de le faire à la déclaration : 
       
       ```
       struct thing localThing = {0};
       ```
       
       ```
       memset(&structure, 0, sizeof(struct structure_type));
       ```
       
       ### Utiliser fork() et éviter les zombies
       
       En appelant un signal qui matche tous les processus enfants. Attention, 
       cela ne fait pas bon ménage avec `kqueue()`.
       
       ```
       #include <signal.h>
       
       static void sigchld(int unused);
       
       void
       sigchld(int unused)
       {
           (void)unused; // avoid unused variable warning
           while (waitpid(WAIT_ANY, NULL, WNOHANG) > 0);
       }
       
       int
       main(int argc, char *argv[])
           /* ... */
           if (signal(SIGCHLD, sigchld) == SIG_ERR)
               err(1, "can't install SIGCHLD handler:");
           /* ... */
       ```
       
       Ou avec un `wait`/`waitpid` bien placé :
       
       ```
       const char *cmd[] = { "/usr/bin/top", "-s", "0.5", NULL };
       pid_t pid = fork();
       if (pid == -1) {
           err(1, "fork");
       } else if (pid == 0) {
           /* child */
           execv(cmd[0], (char **)cmd);
           err(1, "execv");
       } else {
           /* parent */
           wait(NULL);  // blocking
           /* or : 
           waitpid(pid, NULL, WNOHANG); // non-blocking
           */
       }
       ```
       
       ### Modifier une chaîne de caratères dans une fonction
       
       Il est nécessaire de connaître la taille de la chaîne pour que tout 
       fonctionne correctement :
       
       ```
       void
       modif_str(char *str, ssize_t strsiz)
       {
           strlcpy(str, "coucou", strsiz);
       }
       
       char    test[256] = {'\0'};
       
       modif_str(test, sizeof(test));
       ```
       
       ### ecalloc, erealloc : allocation avec gestion d'erreur
       
       ```
       /* same thing that calloc and reallocarray but check for
        * error and exit if necessary
        */
       
       void *
       ecalloc(size_t nmemb, size_t size)
       {
           void *p;
       
           if ((p = calloc(nmemb, size)) == NULL)
               err(1, "calloc");
           return p;
       }
       
       void *
       ereallocarray(void *ptr, size_t nmemb, size_t size)
       {
           if ((ptr = reallocarray(ptr, nmemb, size)) == NULL)
               err(1, "reallocarray");
       
           return ptr;
       }
       ```
       
       ### get_stream_txt: Récupérer le contenu d'un fichier ouvert
       
       ```
       /* return text in fp
        * the fp must be closed properly after
        * the returned string is mallocated and must be freed.
        */
       
       char *
       get_stream_txt(FILE *fp)
       {
           char *buf = NULL;
           size_t s;
           size_t len = 0;
           size_t bsize = 2 * BUFSIZ;
       
           buf = ecalloc(bsize, sizeof(char));
           while((s = fread(buf + len, 1, BUFSIZ, fp))) {
               len += s;
               if(BUFSIZ + len + 1 > bsize) {
                   bsize += BUFSIZ;
                   buf = ereallocarray(buf, bsize, sizeof(char));
               }
           }
           buf[len] = '\0';
       
           return(buf);
       }
       ```
       
       ### lire un fichier
       
       ```
       FILE *fd = NULL;
       char buf[BUFSIZ] = {0};
       size_t nread = 0;
       
       if ((fd = fopen(path, "rb")) == NULL) {
               err(1, "%s: %s", "Fail to open", path);
       }
       
       while ((nread = fread(buf, 1, BUFSIZ, fd)) != 0 ) {
               fwrite(buf, 1, nread, stdout);
       }
       fclose(fd);
       if (ferror(fd)) {
               err(1, "%s: %s", "close", path);
       }
       ```
       
       ### get_cmd_output: Récupérer la sortie d'une commande
       
       ```
       /* return the output of cmd in an allocated
        * string created by get_stream_txt().
        * It is the caller responsibility to free it later
        */
       char *
       get_cmd_output(const char *cmd)
       {
           FILE *fp = NULL;
           char *res = NULL;
       
           if ((fp = popen(cmd, "r")) == NULL)
               err(1, "%s: %s", "Error opening pipe for cmd", cmd);
       
           res = get_stream_txt(fp);
           if (pclose(fp) != 0)
               err(1, "%s %s", cmd, "not found or exited with error status");
       
           return res;
       }
       ```
       
       La fonction `pope`n peut poser des soucis dans le cas où ce n'est pas le 
       développeur qui précise la commande à entrer : les variables 
       d'environnement sont conservées. Voici donc une autre façon de faire :
       
       Créer un "pipe" pour lequel "0" est la sortie et "1" l'entrée.
       Indiquer que "stdout" correspond à la sortie du pipe avec la fonction 
       `dup2()`. Ceci permet de relier la sortie de la commande qu'on veut 
       lancer au pipe pour pouvoir le lire ensuite comme un fichier normal.
       Lancer la commande avec `execlp()` après avoir `fork()`.
       Lecture du retour de la commande dans le processus parent. On utilise 
       `fdopen()` puisque tout ce qu'on connaît, c'est le "file descriptor", pas 
       un chemin vers un fichier.
       
       ```
       /* use pipe and fork to avoid shell with popen */
       int fildes[2] = {0};
       int status = 0;
       pid_t pid = 0;
       char *str = "blabla";
       char *cmd = "hello";
       
       if (pipe(fildes) != 0) {
           err(1, "pipe failed");
       }
       
       pid = fork();
       
       if (pid < 0) {
           close(fildes[0]);
           close(fildes[1]);
           err(1, "fork failed");
       }
       
       if (pid > 0) { /* parent */
           char buf[3];
           size_t nread = 0;
           FILE *output = NULL;
       
           close(fildes[1]); /* make sure entry is closed so fread() gets EOF */
       
           /* use fread/fwrite because are buffered */
           output = fdopen(fildes[0], "r");
           if (output == NULL) {
               err(1, "fdopen failed");
           }
       
           /* read pipe output */
           while ((nread = fread(buf, 1, sizeof(buf), output)) != 0) {
               fwrite(buf, 1, nread, stdout);
           }
           close(fildes[0]);
           fclose(output);
       
           /* wait for child to terminate */
           waitpid(pid, &status, 0);
       
           exit(0);
       
       } else if (pid == 0) { /* child */
           dup2(fildes[1], STDOUT_FILENO); /* set pipe output equal to stdout */
           dup2(fildes[1], STDERR_FILENO);
           close(fildes[1]); /* no need this file descriptor : it is now stdout of the command below */
           execlp(cmd, cmd, NULL);
           /* if execlp is ok, this will never be reached */
           err(1, "execlp failed to run: %s", "ls");
       }
       ```
       
       ### startswith
       
       ```
       int
       startswith(const char *str, const char *start)
       {
               size_t str_len = strlen(str);
               size_t start_len = strlen(start);
       
               int ret = 0;
               if ((str_len >= start_len) &&
                       (strncmp(str, start, start_len) == 0)) {
                       ret = 1;
               }
       
               return ret;
       }
       ```
       
       ### endswith
       
       ```
       int
       endswith(const char *str, const char *end)
       {
           size_t str_len = strlen(str);
           size_t end_len = strlen(end);
       
           int ret = 0;
           if ((str_len >= end_len) &&
               (strcmp(str + (str_len -end_len), end) == 0)) {
               ret = 1;
           }
       
           return ret;
       }
       ```
       
       ### (v)estrlcat
       
       ```
       /* strlcat with error handling */
       size_t
       estrlcat(char *dst, const char *src, size_t dstsize)
       {
           size_t size;
           if ((size = strlcat(dst, src, dstsize)) >= dstsize)
               err(1, "strlcat");
       
           return size;
       }
       
       /* variadic strlcat
        * a call must end with NULL
        * to append "bar", "zoo", yo" to foo, call:
        * vestrlcat(foo, sizeof(foo), "bar", "zoo", "yo", NULL);
        */
       size_t
       vestrlcat(char *dst, size_t dstsize, ...)
       {
           size_t size = 0;
           char *s;
           va_list ap;
       
           va_start(ap, dstsize);
       
           while ((s = va_arg(ap, char *)) != NULL) {
               size += estrlcat(dst, s, dstsize);
           }
           va_end(ap);
       
           return size;
       }
       ```
       
       ### stralloc
       
       Et ma favorite qui permet de fabriquer n'importe quelle chaîne de 
       caractères. On y passe un pointeur (qui peut être NULL au départ. Tous 
       les arguments sont assemblés pour former une longue chaîne, cette 
       dernière étant allouée dynamiquement au besoin.
       
       ```
       /* 
        * Make any string 
        * add in str every argument and realloc str if necessary
        * if str is NULL, it is allocated
        * argument list **must** end with NULL
        * return str
        * str should be dynamically allocated and freed later
        * example: stralloc(&foo, "hello", " there.", NULL);
        */
       
       char *
       stralloc(char **str, ...)
       {
           size_t len = 0;    /* length of str at last*/
           char *s = NULL;
           va_list ap;
           va_list apcopy;
       
           va_copy(apcopy, ap);
       
           /* count required length */
           va_start(ap, str);
           while ((s = va_arg(ap, char *)) != NULL) {
               len += strlen(s);
           }
           va_end(ap);
           len++; /* \0 */
       
           if (*str != NULL) {
               len += strlen(*str);
               *str = ereallocarray(*str, len, sizeof(char));
           } else {
               *str = ecalloc(len, sizeof(char));
           }
       
           /* copy */
           va_start(apcopy, str);
       
           while ((s = va_arg(apcopy, char *)) != NULL) {
               estrlcat(*str, s, len);
           }
           va_end(apcopy);
           return(*str);
       }
       ```
       
       Je lui préfère une version qui ne nécessite aucune allocation, et en 
       profite pour vous présenter "estrlcpy" et "estrlcat" qui font comme 
       strlcpy et strlcat mais en gérant les erreurs :
       
       ```
       size_t
       estrlcpy(char *dst, const char *src, size_t dstsize)
       {
           size_t n = 0;
       
           n = strlcpy(dst, src, dstsize);
           if (n >= dstsize) {
               err(1, "strlcpy failed for %s = %s", dst, src);
           }
       
           return n;
       }
       
       size_t
       estrlcat(char *dst, const char *src, size_t dstsize)
       {
           size_t size;
           if ((size = strlcat(dst, src, dstsize)) >= dstsize)
               err(1, "strlcat on %s + %s", dst, src);
       
           return size;
       }
       
       /* usage:
        * mkstr(str, sizeof(str), optarg, "/INBOX/", NULL);
        */
       size_t
       mkstr(char *str, size_t dstsize, ...)
       {
           char *s = NULL;
           size_t size = 0;
           va_list ap;
       
           va_start(ap, dstsize);
       
           while ((s = va_arg(ap, char *)) != NULL) {
               if (size == 0) {
                   size += estrlcpy(str, s, dstsize);
               } else {
                   size += estrlcat(str, s, dstsize);
               }
           }
           va_end(ap);
           return(size);
       }
       ```
       
       ### trim
       
       Comme son nom l'indique, elle vire les espaces et sauts de ligne en 
       début et fin de chaîne. Il faut passer un pointeur vers la chaîne à 
       modifier (et donc allouée dynamiquement).
       
       ```
       /* return a trimmed copy of str */
       /* trim(&str); */
       void
       trim(char **str)
       {
           size_t begin = 0;
           size_t end = strlen(*str);
       
           while (isspace((*str)[begin])) {
               begin++;
           }
       
           while (isspace((*str)[end-1])) {
               end--;
           }
           for (size_t i = begin, j=0; i < end; i++, j++) {
               (*str)[j] = (*str)[i];
           }
           (*str)[end - begin] = '\0';
       }
       ```
       
       ### (v)esnprintf
       
       Cette fonction fait comme snprintf avec gestion d'erreur. Il faut avoir 
       un tableau de char déjà prêt, par exemple char s[256]. Ce code est en 
       partie tiré de celui utilisé souvent par suckless.org. Elle est très 
       pratique car permet de définir le format des variables qu'une chaîne 
       doit contenir. Par exemple :
       
       ```
       esnprintf(s, sizeof(s), "%s%d%c", "blabla", 42, 'a');
       
       static int
       evsnprintf(char *str, size_t size, const char *format, va_list ap)
       {
           int ret = vsnprintf(str, size, format, ap);
       
           if (ret < 0 || (size_t)ret >= size) {
               warn("%s", "vsnprintf: Output truncated");
           }
       
           return ret;
       }
       
       int
       esnprintf(char *str, size_t size, const char *format, ...)
       {
           va_list ap;
           int ret = 0;
       
           va_start(ap, format);
           ret = evsnprintf(str, size, format, ap);
           va_end(ap);
       
           return ret;
       }
       ```
       
       ### strrstr : rechercher le dernier morceau
       
       strrstr() fait comme strstr, mais pointe vers la dernière occurence, 
       pas la première :
       
       ```
       char *   strrstr(const char *, const char *);
       
       char *
       strrstr(const char *big, const char *little)
       {
           char *p = NULL;
           char *ret = NULL;
       
           if ((p = strstr(big, little)) == NULL) {
               return NULL;
           }
       
           /* loop until last occurence */
           while ((p = strstr(p, little)) != NULL) {
               ret = p;
               p++;
           }
       
           return ret;
       }
       ```
       
       ### efopen, efclose, evfprintf : gestion des fichiers
       
       On ouvre et on ferme les fichiers en gérant les erreurs
       
       ```
       FILE *
       efopen(const char *path, const char *mode)
       {
           FILE *f = NULL;
           if ((f = fopen(path, mode)) == NULL)
               err(1, "%s: %s", "Fail to open", path);
       
           return f;
       }
       
       int
       efclose(FILE *stream)
       {
           int ret = 0;
           ret = fclose(stream);
           if (ferror(stream)) {
               err(1,"closefile");
           }
           return ret;
       }
       ```
       
       On écrit dans un fichier autant de choses que l'on veut en une fois. 
       C'est comme fprint mais avec une liste d'arguments variable.
       
       ```
       int
       evfprintf(FILE *stream, const char *format, va_list ap)
       {
           int ret;
       
           ret = vfprintf(stream, format, ap);
           if (ret < 0)
               err(1,"evfprintf:");
       
           return ret;
       }
       
       int
       efprintf(FILE *stream, const char *format, ...)
       {
           va_list ap;
           int ret;
       
           va_start(ap, format);
           ret = evfprintf(stream, format, ap);
           va_end(ap);
       
           return ret;
       }
       ```
       
       ### OpenBSD unveil et pledge
       
       Deux macros pratiques et un exemple pour éviter des erreurs de 
       compilation sur un autre système:
       
       ```
       #define PLEDGEORDIE(prom) if (pledge(prom, NULL) == -1) { err(1, "pledge"); }
       #define UNVEILORDIE(path,perms) if (unveil(path, perms) == -1) { err(1, "unveil"); }
       ```
       
       Exemple:
       
       ```
       #ifdef __OpenBSD__
           UNVEILORDIE(argv[1], "rwc");
           PLEDGEORDIE("stdio rpath cpath wpath exec proc");
       #endif
       ```
       
       ### Retirer le "\n" de fin de ligne
       
       ```
       line[strcspn(line, "\r\n")] = '\0'; /* remove newline */
       ```
       
       ### de la bonne façon d'utiliser strsep
       
       `strsep` modifie la chaîne, et donc fait perdre la référence du pointeur 
       correspondant. Il faut donc en garder une trace, ce qui donnerait :
       
       ```
       char *r = strdup(str);
       
       char *tofree = r;
       char *tok;
       while ((tok = strsep(&r, " ")) != NULL) {
           puts(tok);
       }
       
       free(tofree);
       ```
       
       ### récupérer les substrings d'une regex (regex.h)
       
       ```
       #include <regex.h>
       #include <sys/types.h>
       
       #define LEN(X)               (sizeof X / sizeof X[0])
       
       #define SE_MAX 4 /* max subexpression, in nmatch. This is the number of 
       () + 1 */
       
       int 
       main(int argc, char *argv[])
       {
           regex_t greg;   /* compiled gemini regex */
           size_t nmatch = SE_MAX;
           regmatch_t match[SE_MAX];
           int ret = 0;
       
           char buf[BUFSIZ] = {'\0'};
       
           char *gemini_regex = "^gemini://([^/]*)/?([^\?]*)[\?]?(.*)?$";
           char *lines[] = {
               "gemini://si3t.ch",
               "gemini://si3t.ch/subdir/index.gmi?query?weird",
               "gemini://si3t.ch/subdir?query",
               "gemini://si3t.ch?query",
               "gemini://si3t.ch//?query",
           };
       
           if ((ret = regcomp(&greg, gemini_regex, REG_EXTENDED)) != 0) {
               regerror(ret, &greg, buf, sizeof(buf));
               regfree(&greg);
               err(1, "%s", buf);
           }
       
           for (size_t l = 0; l < LEN(lines); l++) {
               printf("[%s]\n", lines[l]);
       
               if ((ret = regexec(&greg, lines[l], nmatch, match, 0)) != 0) {
                   regerror(ret, &greg, buf, sizeof(buf));
                   if (ret == REG_NOMATCH)
                       warnx("No match :(");
                   else {
                       regfree(&greg);
                       errx(1, "%s", buf);
                   }
               }
       
               for (int i = 1; i <= greg.re_nsub; i++) {
                   if (match[i].rm_so != match[i].rm_eo)
                       size_t len = match[i].rm_eo - match[i].rm_so;
                       memcpy(buf, lines[l] + match[i].rm_so, len);
                       puts(buf);
                   }
               }
           }
       
           regfree(&greg);
           return 0;
       }
       ```
       
       ### Listes et queues (queue.h)
       
       Beaucoup plus simple que de réimplémenter des listes chaînées.
       
       Voici un exemple. Le reste, c'est dans `man queue`. Il n'y a pas de 
       code de vérification d'erreur !
       
       J'utilise les conventions suivantes :
       
       * Les structures qui seront dans les listes ont un nom commençant par une 
         majuscule
       * Les entête de liste portent le nom de la structure suivi de "_head".
       * Le nom des entrées porte le nom de la structure en minuscule.
       
       ```
       #include <limits.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <string.h>
       #include <sys/queue.h>
       
       struct Item {
           int id;
           char path[PATH_MAX];
           char title[256];    /* 256 is more than enough */
           SIMPLEQ_ENTRY(Item) item;
       };
       SIMPLEQ_HEAD(Item_head, Item);
       
       int 
       main(int argc, char *argv[])
       {
       
           struct Item_head itemh;
           struct Item *i1, *ip;
       
           SIMPLEQ_INIT(&itemh);
       
           i1 = malloc(sizeof(struct Item));
       
           strlcpy(i1->title, "coucou", sizeof(i1->title));
       
           SIMPLEQ_INSERT_TAIL(&itemh, i1, item);
           SIMPLEQ_FOREACH(ip, &itemh, item) {
               puts(ip->title);
           }
       
           free(i1);
       
           return 0;
       }
       ```