Exam Rank 04 : Processus & Parsing
Télécharger les fichiers du sujet :
Pourquoi C’est Important
Section intitulée « Pourquoi C’est Important »L’Exam Rank 04 teste votre capacité à travailler avec les appels système Unix. Ces compétences sont essentielles pour écrire des shells, des daemons et tout programme qui doit créer d’autres processus.
IMPORTANT : L’Exam Rank 04 est en C pur, pas en C++. Ces exercices testent les fondamentaux de la programmation système Unix : processus, pipes, descripteurs de fichiers et parsing.
L’examen a deux niveaux : Level 1 teste la gestion des processus, Level 2 teste le parsing. Vous obtiendrez un exercice de chaque niveau.
Structure de l’Examen :
- Level 1 : picoshell, ft_popen ou sandbox (orienté processus)
- Level 2 : argo ou vbc (orienté parsing)
Concepts Clés :
- Création de processus avec
fork() - Communication inter-processus avec
pipe() - Manipulation de descripteurs de fichiers avec
dup2() - Gestion des signaux avec
sigaction() - Parsing par descente récursive
- Évaluation d’expressions avec priorité des opérateurs
Comprendre les Opérateurs Système Unix
Section intitulée « Comprendre les Opérateurs Système Unix »Cet examen couvre la programmation système Unix. Voici les opérateurs C que vous utiliserez :
La Valeur de Retour de fork()
Section intitulée « La Valeur de Retour de fork() »pid_t pid = fork();if (pid == -1) { /* erreur */ }else if (pid == 0) { /* enfant */ }else { /* parent - pid est l'ID de l'enfant */ }fork()retourne des valeurs différentes dans le parent et l’enfant-1signifie erreur0dans l’enfant signifie “je suis l’enfant”- Nombre positif dans le parent est l’ID de processus (PID) de l’enfant
- C’est ainsi que vous différenciez les chemins de code parent et enfant
Le Tableau pipefd[2]
Section intitulée « Le Tableau pipefd[2] »int pipefd[2];pipe(pipefd);// pipefd[0] = extrémité lecture// pipefd[1] = extrémité écriturepipefd[2]est un tableau de deux descripteurs de fichierspipefd[0]est l’extrémité lecture (0 ressemble à un O - “ouvrir pour lire”)pipefd[1]est l’extrémité écriture (1 ressemble à un stylo - “écrire”)- Mnémonique : 0 = entrée, 1 = sortie
La Fonction dup2()
Section intitulée « La Fonction dup2() »dup2(pipefd[1], STDOUT_FILENO); // Rediriger stdout vers le pipedup2(old, new)fait pointer le descripteur de fichiernewvers le même fichier queold- Après cela, écrire dans
newécrit en fait dans la destination deold STDOUT_FILENO= 1 (sortie standard)STDIN_FILENO= 0 (entrée standard)STDERR_FILENO= 2 (erreur standard)
Les Fonctions wait() et waitpid()
Section intitulée « Les Fonctions wait() et waitpid() »wait(NULL); // Attendre N'IMPORTE quel enfantwaitpid(pid, &status, 0); // Attendre un enfant SPÉCIFIQUEwait(NULL)met le parent en pause jusqu’à ce qu’un processus enfant se terminewaitpid(pid, &status, 0)attend l’enfant spécifique avec le PIDpid&statusstocke des informations sur comment l’enfant s’est terminé- Sans
wait(), les processus enfants deviennent des “zombies” (terminés mais non nettoyés)
Les Macros WIFEXITED et WEXITSTATUS
Section intitulée « Les Macros WIFEXITED et WEXITSTATUS »if (WIFEXITED(status)) { int exit_code = WEXITSTATUS(status);}WIFEXITED(status)vérifie si l’enfant s’est terminé normalement (viaexit()ou retour demain())WEXITSTATUS(status)extrait le code de sortie (0-255)WIFSIGNALED(status)vérifie si l’enfant a été tué par un signalWTERMSIG(status)extrait quel signal a tué l’enfant
L’Opérateur -> (Accès par Pointeur)
Section intitulée « L’Opérateur -> (Accès par Pointeur) »struct_ptr->member; // Équivalent à (*struct_ptr).member->combine le déréférencement de pointeur avec l’accès aux membres- Utilisé avec les pointeurs vers des structures
ptr->fieldest équivalent à(*ptr).field- Le
*déréférence le pointeur,.accède au membre
Le Double Pointeur **
Section intitulée « Le Double Pointeur ** »char **argv; // Tableau de chaînes (tableau de pointeurs char)**signifie “pointeur vers pointeur”char**est un tableau de chaînes (chaque chaîne estchar*)argv[0]est la première chaîne (nom du programme)argv[1]est la deuxième chaîne (premier argument)- Commun dans
main(int argc, char **argv)
L’Opérateur de Déréférencement *
Section intitulée « L’Opérateur de Déréférencement * »*s++; // Avancer le pointeur dans le parsing par descente récursive*sdéréférence le pointeur pour obtenir le caractère actuel(*s)++obtient le caractère actuel puis avance le pointeur- Utilisé dans le parsing pour consommer les caractères un par un
1. Fondamentaux de la Gestion des Processus
Section intitulée « 1. Fondamentaux de la Gestion des Processus »fork() - Créer des Processus Enfants
Section intitulée « fork() - Créer des Processus Enfants »fork() crée une copie exacte du processus actuel.
#include <unistd.h>#include <sys/wait.h>
pid_t pid = fork();
if (pid == -1) { // Erreur - fork a échoué perror("fork"); exit(1);}else if (pid == 0) { // Processus enfant // pid == 0 signifie "je suis l'enfant" printf("Je suis l'enfant (PID: %d)\n", getpid()); exit(0);}else { // Processus parent // pid == PID de l'enfant printf("Je suis le parent, PID de l'enfant: %d\n", pid); wait(NULL); // Attendre que l'enfant se termine}Points Clés sur fork()
Section intitulée « Points Clés sur fork() »| Parent | Enfant |
|---|---|
| Retourne le PID de l’enfant | Retourne 0 |
| Continue l’exécution | Démarre au même point |
| A les descripteurs de fichiers originaux | Obtient des copies des descripteurs |
| Doit wait() ses enfants | Devrait exit() quand terminé |
2. Pipes - Communication Inter-Processus
Section intitulée « 2. Pipes - Communication Inter-Processus »Comment Fonctionnent les Pipes
Section intitulée « Comment Fonctionnent les Pipes »Un pipe crée un canal de données unidirectionnel :
- pipefd[0] = extrémité lecture
- pipefd[1] = extrémité écriture
int pipefd[2];
if (pipe(pipefd) == -1) { perror("pipe"); exit(1);}
// pipefd[0] - lire depuis le pipe// pipefd[1] - écrire dans le pipeConnecter des Processus avec des Pipes
Section intitulée « Connecter des Processus avec des Pipes »Pour connecter cmd1 | cmd2 :
- Créer le pipe
- Fork pour cmd1 : rediriger stdout vers l’extrémité écriture du pipe
- Fork pour cmd2 : rediriger stdin depuis l’extrémité lecture du pipe
- Fermer les extrémités de pipe inutilisées dans tous les processus
int pipefd[2];pipe(pipefd);
pid_t pid1 = fork();if (pid1 == 0) { // Enfant 1 (cmd1) : écrit dans le pipe close(pipefd[0]); // Fermer l'extrémité lecture dup2(pipefd[1], STDOUT_FILENO); // Rediriger stdout vers le pipe close(pipefd[1]); // Fermer l'extrémité écriture originale execvp(cmd1[0], cmd1); exit(1);}
pid_t pid2 = fork();if (pid2 == 0) { // Enfant 2 (cmd2) : lit depuis le pipe close(pipefd[1]); // Fermer l'extrémité écriture dup2(pipefd[0], STDIN_FILENO); // Rediriger stdin depuis le pipe close(pipefd[0]); // Fermer l'extrémité lecture originale execvp(cmd2[0], cmd2); exit(1);}
// Parent : fermer les deux extrémités et attendreclose(pipefd[0]);close(pipefd[1]);waitpid(pid1, NULL, 0);waitpid(pid2, NULL, 0);Règle Critique : Fermer les Extrémités de Pipe Inutilisées !
Section intitulée « Règle Critique : Fermer les Extrémités de Pipe Inutilisées ! »Pourquoi ? L’extrémité lecture d’un pipe retourne EOF uniquement quand TOUTES les extrémités écriture sont fermées. Si vous oubliez de fermer l’extrémité écriture dans le processus lecteur, il attendra indéfiniment des données.
// FAUX - cmd2 va bloquer !pid_t pid2 = fork();if (pid2 == 0) { dup2(pipefd[0], STDIN_FILENO); // Oublié de fermer pipefd[1] ! execvp(cmd2[0], cmd2);}
// CORRECTpid_t pid2 = fork();if (pid2 == 0) { close(pipefd[1]); // DOIT fermer l'extrémité écriture ! dup2(pipefd[0], STDIN_FILENO); close(pipefd[0]); execvp(cmd2[0], cmd2);}3. dup2() - Redirection de Descripteurs de Fichiers
Section intitulée « 3. dup2() - Redirection de Descripteurs de Fichiers »Utilisation de Base
Section intitulée « Utilisation de Base »dup2(oldfd, newfd) fait pointer newfd vers le même fichier que oldfd.
// Rediriger stdout vers un fichierint fd = open("output.txt", O_WRONLY | O_CREAT, 0644);dup2(fd, STDOUT_FILENO); // Maintenant stdout écrit dans output.txtclose(fd); // Fermer le fd original (stdout fonctionne toujours)printf("Ceci va dans output.txt\n");Patterns Courants
Section intitulée « Patterns Courants »// Rediriger stdout vers l'extrémité écriture du pipedup2(pipefd[1], STDOUT_FILENO);
// Rediriger stdin depuis l'extrémité lecture du pipedup2(pipefd[0], STDIN_FILENO);
// Rediriger stderr vers stdoutdup2(STDOUT_FILENO, STDERR_FILENO);4. execvp() - Exécuter des Programmes
Section intitulée « 4. execvp() - Exécuter des Programmes »execvp vs execve
Section intitulée « execvp vs execve »| Fonction | Résolution de Chemin | Environnement |
|---|---|---|
execve(path, argv, envp) | Doit être chemin complet | Vous fournissez envp |
execvp(file, argv) | Recherche dans PATH | Utilise l’environ actuel |
Utilisation
Section intitulée « Utilisation »// execvp - plus facile, recherche dans PATHchar *argv[] = {"ls", "-la", NULL};execvp("ls", argv); // Trouvera /bin/ls automatiquement
// execve - plus de contrôlechar *envp[] = {"PATH=/bin", NULL};execve("/bin/ls", argv, envp);Important : execvp Ne Retourne Jamais en Cas de Succès !
Section intitulée « Important : execvp Ne Retourne Jamais en Cas de Succès ! »execvp(cmd[0], cmd);// Si nous arrivons ici, execvp a échoué !perror("execvp");exit(1); // L'enfant doit quitter en cas d'échec d'exec5. Gestion des Signaux
Section intitulée « 5. Gestion des Signaux »sigaction() - Gestion Moderne des Signaux
Section intitulée « sigaction() - Gestion Moderne des Signaux »#include <signal.h>
void handler(int sig) { // Gérer le signal write(1, "Signal capturé\n", 15);}
int main() { struct sigaction sa; sa.sa_handler = handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0;
sigaction(SIGALRM, &sa, NULL);
alarm(5); // Envoyer SIGALRM dans 5 secondes pause(); // Attendre le signal
return 0;}Utiliser alarm() pour les Timeouts
Section intitulée « Utiliser alarm() pour les Timeouts »alarm(timeout); // Planifier SIGALRM dans 'timeout' secondes
// ... faire le travail ...
alarm(0); // Annuler l'alarmeVérifier Comment un Processus S’est Terminé
Section intitulée « Vérifier Comment un Processus S’est Terminé »int status;waitpid(pid, &status, 0);
if (WIFEXITED(status)) { // Le processus s'est terminé normalement int exit_code = WEXITSTATUS(status); printf("Terminé avec le code %d\n", exit_code);}else if (WIFSIGNALED(status)) { // Processus tué par un signal int sig = WTERMSIG(status); printf("Tué par le signal : %s\n", strsignal(sig));}6. Parsing par Descente Récursive
Section intitulée « 6. Parsing par Descente Récursive »L’Approche par Grammaire
Section intitulée « L’Approche par Grammaire »Pour le parsing d’expressions, définir une grammaire :
expr = term (('+') term)*term = factor (('*') factor)*factor = '(' expr ')' | NUMBERPattern d’Implémentation
Section intitulée « Pattern d’Implémentation »int parse_expr(const char **s);int parse_term(const char **s);int parse_factor(const char **s);
// Factor : nombre ou (expr)int parse_factor(const char **s) { if (**s == '(') { (*s)++; // Sauter '(' int result = parse_expr(s); if (**s != ')') error("')' attendu"); (*s)++; // Sauter ')' return result; } if (isdigit(**s)) { int num = **s - '0'; (*s)++; return num; } error("Token inattendu"); return 0;}
// Term : factor (* factor)*int parse_term(const char **s) { int result = parse_factor(s); while (**s == '*') { (*s)++; result *= parse_factor(s); } return result;}
// Expr : term (+ term)*int parse_expr(const char **s) { int result = parse_term(s); while (**s == '+') { (*s)++; result += parse_term(s); } return result;}Pourquoi Ça Marche pour la Priorité des Opérateurs
Section intitulée « Pourquoi Ça Marche pour la Priorité des Opérateurs »*a une priorité plus élevée que+- En parsant
*dansterm(appelé en premier), il lie plus fort 3+4*5est parsé comme3+(4*5)= 23, pas(3+4)*5= 35
7. Bases du Parsing JSON
Section intitulée « 7. Bases du Parsing JSON »Structure JSON
Section intitulée « Structure JSON »value = string | number | objectobject = '{' (pair (',' pair)*)? '}'pair = string ':' valuestring = '"' characters '"'number = digitsApproche de Parsing
Section intitulée « Approche de Parsing »int parse_value(FILE *f, json *dst) { int c = getc(f);
if (c == '"') { return parse_string(f, dst); } else if (isdigit(c)) { ungetc(c, f); return parse_number(f, dst); } else if (c == '{') { return parse_object(f, dst); } else if (c == EOF) { printf("Fin d'entrée inattendue\n"); return -1; } else { printf("Token inattendu '%c'\n", c); return -1; }}Gérer les Séquences d’Échappement
Section intitulée « Gérer les Séquences d’Échappement »int parse_string(FILE *f, json *dst) { // " d'ouverture déjà consommé char buffer[1024]; int i = 0; int c;
while ((c = getc(f)) != EOF && c != '"') { if (c == '\\') { c = getc(f); if (c == '"' || c == '\\') { buffer[i++] = c; } else { // Échappement invalide printf("Token inattendu '%c'\n", c); return -1; } } else { buffer[i++] = c; } }
if (c == EOF) { printf("Fin d'entrée inattendue\n"); return -1; }
buffer[i] = '\0'; // Stocker la chaîne dans dst... return 1;}8. Pièges Courants
Section intitulée « 8. Pièges Courants »Fuites de Descripteurs de Fichiers
Section intitulée « Fuites de Descripteurs de Fichiers »// FAUX - fuite de fdsint pipefd[2];pipe(pipefd);pid_t pid = fork();if (pid == 0) { dup2(pipefd[1], 1); execvp(cmd[0], cmd);}// Le parent a oublié de fermer pipefd[0] et pipefd[1] !
// CORRECTif (pid == 0) { close(pipefd[0]); dup2(pipefd[1], 1); close(pipefd[1]); execvp(cmd[0], cmd); exit(1);}close(pipefd[0]);close(pipefd[1]);Processus Zombies
Section intitulée « Processus Zombies »// FAUX - crée des zombiesfor (int i = 0; i < n; i++) { if (fork() == 0) { execvp(cmds[i][0], cmds[i]); exit(1); }}// Le parent quitte sans attendre !
// CORRECTfor (int i = 0; i < n; i++) { pids[i] = fork(); if (pids[i] == 0) { execvp(cmds[i][0], cmds[i]); exit(1); }}for (int i = 0; i < n; i++) { waitpid(pids[i], NULL, 0);}Gestion d’Échec execvp
Section intitulée « Gestion d’Échec execvp »// FAUX - l'enfant continue après un exec échouéif (fork() == 0) { execvp(cmd[0], cmd); // Si execvp échoue, l'enfant continue à exécuter le code du parent !}
// CORRECTif (fork() == 0) { execvp(cmd[0], cmd); perror("execvp"); exit(1); // DOIT quitter !}Level 1 : Gestion des Processus
Section intitulée « Level 1 : Gestion des Processus »picoshell
Section intitulée « picoshell »Analyse du Sujet
Section intitulée « Analyse du Sujet »Implémenter une fonction qui exécute un pipeline de commandes :
- Entrée : tableau de commandes (chaque commande est un tableau de chaînes)
- Connecter stdout de chaque commande à stdin de la suivante
- Retourner 0 en cas de succès, 1 en cas d’erreur
- Fermer tous les descripteurs de fichiers, attendre tous les enfants
Exemple de pipeline : ls | grep .c | wc -l
char *cmd1[] = {"ls", NULL};char *cmd2[] = {"grep", ".c", NULL};char *cmd3[] = {"wc", "-l", NULL};char **cmds[] = {cmd1, cmd2, cmd3, NULL};picoshell(cmds);Stratégie d’Approche
Section intitulée « Stratégie d’Approche »Réfléchir avant de coder :
-
Qu’est-ce qu’un pipe ?
pipe(fd)crée deux descripteurs de fichiersfd[0]= extrémité lecture,fd[1]= extrémité écriture- Les données écrites dans
fd[1]peuvent être lues depuisfd[0]
-
Pattern de pipeline :
cmd1 stdout --> [pipe1] --> stdin cmd2 stdout --> [pipe2] --> stdin cmd3 -
Pour chaque commande :
- Fork un processus enfant
- Dans l’enfant : rediriger stdin/stdout, exec la commande
- Dans le parent : suivre les descripteurs de fichiers, attendre les enfants
-
Règle critique : Fermer TOUS les descripteurs de fichiers inutilisés dans le parent ET l’enfant !
Construction Progressive du Code
Section intitulée « Construction Progressive du Code »Étape 1 : Structure de base
#include <unistd.h>#include <sys/wait.h>#include <stdlib.h>
int picoshell(char **cmds[]){ int i = 0; int prev_fd = -1; // Extrémité lecture du pipe précédent int pipefd[2]; pid_t pid;
// Compter les commandes while (cmds[i]) i++; int cmd_count = i;
// Traiter chaque commande for (i = 0; cmds[i]; i++) { // ... implémentation ici }
// Attendre tous les enfants for (i = 0; i < cmd_count; i++) wait(NULL);
return 0;}Étape 2 : Créer les pipes et fork
for (i = 0; cmds[i]; i++){ // Créer le pipe si pas la dernière commande if (cmds[i + 1] && pipe(pipefd) == -1) { if (prev_fd != -1) close(prev_fd); return 1; }
pid = fork(); if (pid == -1) { if (prev_fd != -1) close(prev_fd); if (cmds[i + 1]) { close(pipefd[0]); close(pipefd[1]); } return 1; }
// Gestion enfant ou parent...}Étape 3 : Processus enfant - rediriger et exec
if (pid == 0){ // Processus enfant
// Rediriger stdin depuis le pipe précédent (si pas le premier) if (prev_fd != -1) { dup2(prev_fd, STDIN_FILENO); close(prev_fd); }
// Rediriger stdout vers le pipe actuel (si pas le dernier) if (cmds[i + 1]) { close(pipefd[0]); // Fermer extrémité lecture (non utilisée) dup2(pipefd[1], STDOUT_FILENO); close(pipefd[1]); }
execvp(cmds[i][0], cmds[i]); exit(1); // exec échoué - DOIT quitter !}Étape 4 : Processus parent - gérer les fds
// Processus parentif (prev_fd != -1) close(prev_fd); // Terminé avec l'extrémité lecture précédente
if (cmds[i + 1]){ close(pipefd[1]); // Fermer extrémité écriture (l'enfant l'utilise) prev_fd = pipefd[0]; // Sauver extrémité lecture pour commande suivante}Explication Ligne par Ligne
Section intitulée « Explication Ligne par Ligne »Comprendre dup2 :
| Appel | Effet |
|---|---|
dup2(pipefd[1], STDOUT_FILENO) | stdout écrit maintenant dans le pipe |
dup2(prev_fd, STDIN_FILENO) | stdin lit maintenant depuis le pipe précédent |
Flux de données du pipe :
Avant dup2 : stdin (0) --> clavier stdout (1) --> terminal
Après dup2 dans la commande du milieu : stdin (0) --> extrémité lecture pipe précédent stdout (1) --> extrémité écriture pipe actuelPourquoi fermer après dup2 ?
dup2(pipefd[1], STDOUT_FILENO); // Crée une copie au fd 1close(pipefd[1]); // L'original n'est plus nécessaire
// Maintenant : stdout (1) pointe vers l'écriture du pipe// pipefd[1] est fermé (pas de fuite)Pièges Courants
Section intitulée « Pièges Courants »1. Oublier de fermer les extrémités de pipe :
// FAUX : Extrémité lecture du pipe jamais ferméeif (fork() == 0) { dup2(pipefd[1], 1); execvp(cmd, argv);}
// CORRECT : Fermer toutes les extrémités inutiliséesif (fork() == 0) { close(pipefd[0]); // Ne lit pas dup2(pipefd[1], 1); close(pipefd[1]); // Fermé après dup2 execvp(cmd, argv); exit(1);}2. Ne pas quitter après un exec échoué :
// FAUX : L'enfant continue comme parent !execvp(cmd, argv);// Si exec échoue, continue ici...
// CORRECT : Quitter en cas d'échecexecvp(cmd, argv);exit(1); // Atteint uniquement si exec échoue3. Créer des processus zombies :
// FAUX : N'attend pas les enfantsfor (i = 0; cmds[i]; i++) { fork(); // Pas de wait !}
// CORRECT : Attendre tousfor (i = 0; i < cmd_count; i++) wait(NULL);4. Le parent ne ferme pas l’extrémité écriture :
// FAUX : L'enfant bloque en lecture car le parent garde l'extrémité écriture// Le parent garde pipefd[1] ouvert -> la lecture de l'enfant n'obtient jamais EOF
// CORRECT : Le parent ferme l'extrémité écritureclose(pipefd[1]);prev_fd = pipefd[0];Conseils de Test
Section intitulée « Conseils de Test »# Compilergcc -Wall -Wextra -Werror picoshell.c main.c -o picoshell
# Tester un pipeline simple./picoshell echo hello "|" cat
# Tester plusieurs pipes./picoshell ls "|" grep .c "|" wc -l
# Vérifier les fuites de fdlsof -c picoshell
# Vérifier les zombiesps aux | grep defunctCode Final
Section intitulée « Code Final »#include <unistd.h>#include <sys/wait.h>#include <stdlib.h>
int picoshell(char **cmds[]){ int i = 0; int prev_fd = -1; int pipefd[2]; pid_t pid;
while (cmds[i]) i++; int cmd_count = i;
for (i = 0; cmds[i]; i++) { if (cmds[i + 1] && pipe(pipefd) == -1) { if (prev_fd != -1) close(prev_fd); return 1; }
pid = fork(); if (pid == -1) { if (prev_fd != -1) close(prev_fd); if (cmds[i + 1]) { close(pipefd[0]); close(pipefd[1]); } return 1; }
if (pid == 0) { if (prev_fd != -1) { dup2(prev_fd, 0); close(prev_fd); } if (cmds[i + 1]) { close(pipefd[0]); dup2(pipefd[1], 1); close(pipefd[1]); } execvp(cmds[i][0], cmds[i]); exit(1); }
if (prev_fd != -1) close(prev_fd); if (cmds[i + 1]) { close(pipefd[1]); prev_fd = pipefd[0]; } }
for (i = 0; i < cmd_count; i++) wait(NULL);
return 0;}ft_popen
Section intitulée « ft_popen »Analyse du Sujet
Section intitulée « Analyse du Sujet »Implémenter un popen() simplifié :
- Prendre le chemin de la commande, les arguments et le mode (‘r’ ou ‘w’)
- Retourner un descripteur de fichier connecté au stdin ou stdout de la commande
- Mode ‘r’ : retourner fd pour lire la sortie de la commande
- Mode ‘w’ : retourner fd pour écrire à l’entrée de la commande
Stratégie d’Approche
Section intitulée « Stratégie d’Approche »Réfléchir avant de coder :
-
Que signifie le mode ‘r’ ?
- “Je veux LIRE la sortie de la commande”
- L’enfant redirige stdout vers l’extrémité écriture du pipe
- Le parent obtient l’extrémité lecture du pipe
-
Que signifie le mode ‘w’ ?
- “Je veux ÉCRIRE à l’entrée de la commande”
- L’enfant redirige stdin depuis l’extrémité lecture du pipe
- Le parent obtient l’extrémité écriture du pipe
-
Visualisation :
mode 'r' : parent <--lecture-- pipe <--écriture-- enfant stdoutmode 'w' : parent --écriture--> pipe --lecture--> enfant stdin
Construction Progressive du Code
Section intitulée « Construction Progressive du Code »Étape 1 : Configuration et validation
#include <unistd.h>#include <stdlib.h>
int ft_popen(const char *file, char *const argv[], char type){ int pipefd[2]; pid_t pid;
// Valider le type if (type != 'r' && type != 'w') return -1;
// Créer le pipe if (pipe(pipefd) == -1) return -1;
pid = fork(); if (pid == -1) { close(pipefd[0]); close(pipefd[1]); return -1; }
// ... gestion enfant/parent}Étape 2 : Processus enfant
if (pid == 0){ if (type == 'r') { // Le parent lit -> l'enfant écrit sur stdout close(pipefd[0]); // L'enfant ne lira pas dup2(pipefd[1], STDOUT_FILENO); close(pipefd[1]); } else // type == 'w' { // Le parent écrit -> l'enfant lit depuis stdin close(pipefd[1]); // L'enfant n'écrira pas dup2(pipefd[0], STDIN_FILENO); close(pipefd[0]); }
execvp(file, argv); exit(1);}Étape 3 : Processus parent
// Processus parentif (type == 'r'){ close(pipefd[1]); // Le parent n'écrira pas return pipefd[0]; // Retourner extrémité lecture}else // type == 'w'{ close(pipefd[0]); // Le parent ne lira pas return pipefd[1]; // Retourner extrémité écriture}Explication Ligne par Ligne
Section intitulée « Explication Ligne par Ligne »Table de direction des modes :
| Mode | Parent | Enfant | Parent retourne |
|---|---|---|---|
| ’r’ | lit depuis le pipe | écrit dans le pipe (stdout) | pipefd[0] |
| ’w’ | écrit dans le pipe | lit depuis le pipe (stdin) | pipefd[1] |
Astuce mémoire :
- ‘r’ = “Je Lis” = le parent lit = l’enfant produit = stdout de l’enfant
- ‘w’ = “J’Écris” = le parent écrit = l’enfant consomme = stdin de l’enfant
Pièges Courants
Section intitulée « Pièges Courants »1. Direction inversée :
// FAUX : mode 'r' mais retourne extrémité écritureif (type == 'r') return pipefd[1]; // Faux !
// CORRECT : mode 'r' retourne extrémité lectureif (type == 'r') return pipefd[0]; // Correct2. L’enfant redirige le mauvais fd :
// FAUX : mode 'r' mais l'enfant redirige stdinif (type == 'r') dup2(pipefd[0], STDIN_FILENO); // Faux !
// CORRECT : mode 'r', l'enfant redirige stdoutif (type == 'r') dup2(pipefd[1], STDOUT_FILENO); // CorrectCode Final
Section intitulée « Code Final »#include <unistd.h>#include <stdlib.h>
int ft_popen(const char *file, char *const argv[], char type){ int pipefd[2]; pid_t pid;
if (type != 'r' && type != 'w') return -1;
if (pipe(pipefd) == -1) return -1;
pid = fork(); if (pid == -1) { close(pipefd[0]); close(pipefd[1]); return -1; }
if (pid == 0) { if (type == 'r') { close(pipefd[0]); dup2(pipefd[1], STDOUT_FILENO); close(pipefd[1]); } else { close(pipefd[1]); dup2(pipefd[0], STDIN_FILENO); close(pipefd[0]); } execvp(file, argv); exit(1); }
if (type == 'r') { close(pipefd[1]); return pipefd[0]; } close(pipefd[0]); return pipefd[1];}Analyse du Sujet
Section intitulée « Analyse du Sujet »Tester si une fonction est “gentille” ou “méchante” :
- Gentille : quitte avec le code 0, pas de signal, pas de timeout
- Méchante : crash (signal), quitte avec non-zéro, ou timeout
- Retourner 1 pour gentille, 0 pour méchante, -1 pour erreur de sandbox
Stratégie d’Approche
Section intitulée « Stratégie d’Approche »Réfléchir avant de coder :
-
Pourquoi fork ?
- Exécuter la fonction non fiable dans un processus isolé
- Si elle crash, le parent survit
-
Comment détecter les problèmes ?
WIFEXITED(status)- s’est-il terminé normalement ?WEXITSTATUS(status)- quel code de sortie ?WIFSIGNALED(status)- tué par un signal ?WTERMSIG(status)- quel signal ?
-
Comment gérer le timeout ?
alarm(seconds)envoie SIGALRM après délai- Dans le handler, définir un flag
- Après wait, tuer l’enfant si timeout
Code Final
Section intitulée « Code Final »#include <unistd.h>#include <sys/wait.h>#include <signal.h>#include <stdlib.h>#include <stdio.h>#include <string.h>#include <stdbool.h>
static volatile sig_atomic_t g_timeout = 0;
static void alarm_handler(int sig) { (void)sig; g_timeout = 1; }
int sandbox(void (*f)(void), unsigned int timeout, bool verbose){ pid_t pid; int status; struct sigaction sa;
if (!f) return -1;
sa.sa_handler = alarm_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if (sigaction(SIGALRM, &sa, NULL) == -1) return -1;
g_timeout = 0; pid = fork(); if (pid == -1) return -1;
if (pid == 0) { f(); exit(0); }
if (timeout > 0) alarm(timeout); if (waitpid(pid, &status, 0) == -1) { alarm(0); return -1; } alarm(0);
if (g_timeout) { kill(pid, SIGKILL); waitpid(pid, NULL, 0); if (verbose) printf("Mauvaise fonction : timeout après %u secondes\n", timeout); return 0; }
if (WIFEXITED(status)) { int code = WEXITSTATUS(status); if (code == 0) { if (verbose) printf("Bonne fonction !\n"); return 1; } if (verbose) printf("Mauvaise fonction : terminée avec le code %d\n", code); return 0; }
if (WIFSIGNALED(status)) { if (verbose) printf("Mauvaise fonction : %s\n", strsignal(WTERMSIG(status))); return 0; }
return -1;}Level 2 : Parsing
Section intitulée « Level 2 : Parsing »vbc (Calculatrice d’Expressions)
Section intitulée « vbc (Calculatrice d’Expressions) »Analyse du Sujet
Section intitulée « Analyse du Sujet »Évaluer des expressions mathématiques :
- Opérateurs :
+et* - Parenthèses :
(et) - Nombres : chiffres uniques 0-9
- Priorité des opérateurs :
*avant+
Exemples :
3+4*5 = 23 (pas 35 !)(3+4)*5 = 351+2+3 = 6Stratégie d’Approche
Section intitulée « Stratégie d’Approche »Réfléchir avant de coder :
-
Pourquoi
*lie plus fort ?3+4*5devrait être3+(4*5) = 23- Pas
(3+4)*5 = 35
-
Grammaire pour la priorité :
expr = term (('+') term)* // + est le plus basterm = factor (('*') factor)* // * est plus hautfactor = '(' expr ')' | DIGIT // () et nombres les plus hauts -
Descente récursive :
parse_exprgère+parse_termgère*parse_factorgère()et les chiffres- Priorité plus haute = plus profond dans la pile d’appels
Code Final
Section intitulée « Code Final »#include <stdio.h>#include <stdlib.h>#include <ctype.h>
static int parse_expr(const char **s);
static void error_token(char c){ if (c == '\0') printf("Fin d'entrée inattendue\n"); else printf("Token inattendu '%c'\n", c); exit(1);}
static int parse_factor(const char **s){ if (**s == '(') { (*s)++; int result = parse_expr(s); if (**s != ')') error_token(**s); (*s)++; return result; } if (isdigit(**s)) { int num = **s - '0'; (*s)++; return num; } error_token(**s); return 0;}
static int parse_term(const char **s){ int result = parse_factor(s); while (**s == '*') { (*s)++; result *= parse_factor(s); } return result;}
static int parse_expr(const char **s){ int result = parse_term(s); while (**s == '+') { (*s)++; result += parse_term(s); } return result;}
int main(int argc, char **argv){ if (argc != 2) { printf("Usage: %s 'expression'\n", argv[0]); return 1; } const char *s = argv[1]; int result = parse_expr(&s); if (*s != '\0') error_token(*s); printf("%d\n", result); return 0;}Conseils de Test
Section intitulée « Conseils de Test »./vbc '3+4*5' # 23./vbc '(3+4)*5' # 35./vbc '1+2+3+4+5' # 15./vbc '2*3*4' # 24./vbc '((1+2))' # 3./vbc '1+2)' # Erreur : Token inattendu ')'./vbc '(1+2' # Erreur : Fin d'entrée inattendue./vbc '1 + 2' # Erreur : Token inattendu ' 'argo (Parseur JSON)
Section intitulée « argo (Parseur JSON) »Analyse du Sujet
Section intitulée « Analyse du Sujet »Parser du JSON simplifié :
- Types : nombres, chaînes, objets (pas de tableaux, booléens, null)
- Pas de gestion des espaces (les espaces sont des erreurs)
- Gérer les séquences d’échappement :
\"et\\seulement - Afficher les messages d’erreur exacts
Conseils de Test
Section intitulée « Conseils de Test »# Tester les nombresecho -n '42' | ./argo /dev/stdin
# Tester les chaînesecho -n '"hello"' | ./argo /dev/stdin
# Tester les objetsecho -n '{"a":1}' | ./argo /dev/stdinecho -n '{"name":"test","value":42}' | ./argo /dev/stdin
# Tester imbriquéecho -n '{"outer":{"inner":1}}' | ./argo /dev/stdin
# Tester les échappementsecho -n '"hello\"world"' | ./argo /dev/stdin
# Tester les erreursecho -n '{"a":}' | ./argo /dev/stdin # Valeur manquanteecho -n '{a:1}' | ./argo /dev/stdin # Clé pas une chaîne9. Référence Rapide
Section intitulée « 9. Référence Rapide »Fonctions de Processus
Section intitulée « Fonctions de Processus »| Fonction | Objectif |
|---|---|
fork() | Créer un processus enfant |
wait(NULL) | Attendre n’importe quel enfant |
waitpid(pid, &status, 0) | Attendre un enfant spécifique |
exit(code) | Terminer le processus |
Pipe & Redirection
Section intitulée « Pipe & Redirection »| Fonction | Objectif |
|---|---|
pipe(int fd[2]) | Créer pipe (fd[0]=lecture, fd[1]=écriture) |
dup2(old, new) | Rediriger new vers old |
close(fd) | Fermer descripteur de fichier |
Exécution
Section intitulée « Exécution »| Fonction | Objectif |
|---|---|
execvp(file, argv) | Exécuter avec recherche PATH |
execve(path, argv, envp) | Exécuter avec chemin complet |
| Fonction | Objectif |
|---|---|
sigaction(sig, &sa, NULL) | Définir gestionnaire de signal |
alarm(seconds) | Planifier SIGALRM |
kill(pid, sig) | Envoyer signal au processus |
Macros de Statut
Section intitulée « Macros de Statut »| Macro | Objectif |
|---|---|
WIFEXITED(status) | Vrai si terminé normalement |
WEXITSTATUS(status) | Obtenir code de sortie |
WIFSIGNALED(status) | Vrai si tué par signal |
WTERMSIG(status) | Obtenir numéro du signal |
Résumé de l’Examen
Section intitulée « Résumé de l’Examen »| Level | Exercice | Concept Clé |
|---|---|---|
| 1 | picoshell | Chaînes de pipes, fork, exec, gestion fd |
| 1 | ft_popen | Direction du pipe selon le mode |
| 1 | sandbox | Signaux, timeout, statut de processus |
| 2 | vbc | Descente récursive, priorité des opérateurs |
| 2 | argo | Parsing récursif, séquences d’échappement |
Erreurs courantes à éviter :
- Ne pas fermer toutes les extrémités de pipe
- Ne pas quitter après un execvp échoué
- Mauvaise direction de pipe pour mode lecture/écriture
- Mauvaise priorité des opérateurs dans vbc
- Mauvais format de message d’erreur (doit être exact !)