Forum Programmation.c introduire redirection dans un minishell

Posté par  . Licence CC By‑SA.
Étiquettes : aucune
-1
6
mar.
2016

Bonjour,

Je bloque depuis un certain moment pour introduire dans un minishell des redirection > < >> << ,

Je ne comprend pas pourquoi mon code ne marche pas, je pense après plusieurs tentation que le problème vient de wait mais je ne vois pas comment le faire fonctionner.

# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <sys/types.h>
# include <sys/wait.h>
# include <assert.h>
# include <string.h>

enum {
  MaxLigne = 1024,              // longueur max d'une ligne de commandes
  MaxMot = MaxLigne / 2,        // nbre max de mot dans la ligne
  MaxDirs = 100,        // nbre max de repertoire dans PATH
  MaxPathLength = 512,      // longueur max d'un nom de fichier
};

void decouper(char *, char *, char **, int);
void usage(char *);
# define PROMPT "? "

int
main(int argc, char * argv[]){
  char ligne[MaxLigne];
  char pathname[MaxPathLength];
  char * mot[MaxMot];
  char * dirs[MaxDirs];
  int i, tmp,overrideOut,appendOut,overrideIn,appendIn;
  int status;

  /* Decouper UNE COPIE de PATH en repertoires */
  decouper(strdup(getenv("PATH")), ":", dirs, MaxDirs);

  /* Lire et traiter chaque ligne de commande */
  for(printf(PROMPT); fgets(ligne, sizeof ligne, stdin) != 0; printf(PROMPT)){
    decouper(ligne, " \t\n", mot, MaxMot);
    if (mot[0] == 0)            // ligne vide
      continue;

    tmp = fork();               // lancer le processus enfant
    if (tmp < 0){
      perror("fork");
      continue;
    }
    if (tmp != 0){
    printf("attendre l'enfant (%d)\n", tmp);
    tmp = wait(&status);
    printf("enfant (%d) fini\n", tmp);
    exit(0); 

    if(ligne[strlen(ligne)-1] == '\n'){
        ligne[strlen(ligne)-1] = '\0';
    }else{
        printf("\n");
        return 0;
    }
    argv[0] = strtok(ligne, " \n\0");

    for(argc=1; argv[argc-1]; argc++){

        if(!strcmp(">", argv[argc-1])){
        overrideOut = 1;
        }
        if(!strcmp(">>", argv[argc-1])){
        appendOut = 1;  
        }
        if(!strcmp("<", argv[argc-1])){
        overrideIn = 1;
        }
        if(!strcmp("<<", argv[argc-1])){
        appendIn = 1;
        }

        argv[argc] = strtok(NULL, " \n\0");

    }




    if(!strcmp(argv[0], "exit")) exit(0);


    if(overrideOut == 1){           
        freopen(argv[argc-2], "w+", stdout);
        argv[argc-2] = strtok(NULL, " \n\0");
        argv[argc-3] = strtok(NULL, " \n\0");
    }else if(appendOut == 1){
        freopen(argv[argc-2], "a", stdout);
        argv[argc-2] = strtok(NULL, " \n\0");
        argv[argc-3] = strtok(NULL, " \n\0");
    }else{
    freopen("/dev/tty", "rw", stdin);
    }


}

else{
        if(execvp(argv[0], argv)){
            printf("error");
            exit(1);
        }else{
            exit(0);
        }
    }


                // enfant : exec du programme
    for(i = 0; dirs[i] != 0; i++){
      snprintf(pathname, sizeof pathname, "%s/%s", dirs[i], mot[0]);
      execv(pathname, mot);
    }
                                // aucun exec n'a fonctionne
    fprintf(stderr, "%s: not found\n", mot[0]);
    exit(1);
  }

  printf("Bye\n");

  return 0;

}

/* decouper  --  decouper une chaine en mots */ 
void 
decouper(char * ligne, char * separ, char * mot[], int maxmot){ 
  int i; 

  mot[0] = strtok(ligne, separ); 
  for(i = 1; mot[i - 1] != 0; i++){ 
    if (i == maxmot){ 
      usage("Erreur dans la fonction decouper: trop de mots"); 
      mot[i - 1] = 0; 
      break; 
    } 
    mot[i] = strtok(NULL, separ); 
  } 
}

void usage(char * P) { printf("Usage : %s erreur", P);; exit(1) ;}

Merci de m'aider je bloque vraiment

  • # Quelques problèmes

    Posté par  . Évalué à 2.

    • au moment de l'exec par le fils, il "exec" le shell lui même (argv[0]) au lieu du programme entré.

    • exit(0) après le wait ne devrait pas être là.

    • les modifications sur stdin/stdout doivent se faire dans le fils (avant l'exec). Là tu le fais dans le père/shell et, en plus, après le fork, ce qui n'a aucune incidence sur le fils, ce n'est certainement pas ce que tu veux..

    • surcharger l'argv du main pour construire ton argv pour l'execv est, en plus d'être carrément dégueulasse et illisible, dangereux car rien ne t'indique qu'il est bien dimensionné. Construis en un autre à l'aide de malloc! (btw fgets ne devrait pas non plus être utilisé, je l'avais déja dit à ton collègue ou à toi.

    • conseil: continue de découper ton code en fonctions, par exemple en faire une pour le fork+exec+modification stdou/stdin serait déja pas mal, voire une autre pour construire l'argv ou encore pour "parser" la ligne d'entrée

    • conseil: utilise "strace -o /tmp/trace -f -t ./shell" afin de savoir quels syscalls sont effectués par ton programme. L'argument "-f" est important, il demande de tracer aussi les fils. Avec le code posté, tu obtiens qq chose du genre (j'ai retirés les lignes non pertinentes).

    ...
    3062  20:19:53 write(1, "? ", 2)        = 2
    3062  20:19:53 read(0, "ls > /tmp/ls\n", 1024) = 13
    3062  20:19:57 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f956ce1e9d0) = 3063
    3062  20:19:57 write(1, "attendre l'enfant (3063)\n", 25 <unfinished ...>
    3063  20:19:57 execve("./a.out", ["./a.out"], [/* 56 vars */] <unfinished ...>
    3062  20:19:57 <... write resumed> )    = 25
    3062  20:19:57 wait4(-1,  <unfinished ...>
    ...
    3063  20:19:57 <... execve resumed> )   = 0
    3063  20:19:57 write(1, "? ", 2)        = 2
    3063  20:19:57 read(0, "\n", 1024)      = 1
    3063  20:20:02 write(1, "? ", 2)        = 2
    3063  20:20:02 read(0, "", 1024)        = 0
    3063  20:20:04 write(1, "Bye\n", 4)     = 4
    3063  20:20:04 exit_group(0)            = ?
    3063  20:20:04 +++ exited with 0 +++
    3062  20:20:04 <... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 3063
    3062  20:20:04 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3063, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
    3062  20:20:04 write(1, "enfant (3063) fini\n", 19) = 19
    3062  20:20:04 exit_group(0)            = ?
    3062  20:20:04 +++ exited with 0 +++
    

    La première ligne indique le pid. On voit bien que le wait fonctionne bien, mais que le fils (pid 3063) fait une lecture sur stdin (fd=0) et ne quitte qu'au moment on on entre CTRL+D (read=0 => EOF). Note que le fork est en fait le "clone", c'est la primitive noyau ("syscall") que la fonction fork de la libc utilise. Tu peux aussi utiliser ltrace (avec un L pour librairies), qui normalement te donne les noms de fonctions appellées, mais en règle générale, c'est beaucoup plus lent et verbeux, et il faut avoir les infos de débuggages.

    • [^] # Re: Quelques problèmes

      Posté par  . Évalué à 2. Dernière modification le 06 mars 2016 à 23:04.

      (btw fgets ne devrait pas non plus être utilisé, je l'avais déja dit à ton collègue ou à toi.

      Au temps pour moi: évidemment (argument sizeof…) fgets ne souffre pas de ce problème, contrairement à gets.

    • [^] # Re: Quelques problèmes

      Posté par  . Évalué à 1.

      J'oubliais : je pense que tu t'es quelques peu emmêlé les pinceaux au niveau des if et, ce qui n'aide pas, l'indentation n'est pas correcte. Le code après le commentaire "//enfant: exec du programme" n'a rien à faire là, il devrait être dans la clause "else" du if(tmp)… Bref en pseudo code ton programme devrait avoir cette structure:

      main() {
        while(lire_entree()) {
          parse_commande();
          if (entree_valide) {
             do_exec()
             wait(pid)
          }
        }
      }
      
      do_exec() {
        pid = fork()
        if(pid) { // pere
          return pid;
        }
        else {
          set_stdio();
          for(dir in path)
             exec();
          // not found
         print("command not found")
         exit()
        }
        return 0; // non atteint
      }

      Ensuite tu as 3 options pour passer tous les paramètres: soit tu utilises des globales, le plus simple dans un premier temps mais le moins facilement "maintenable"; soit tu passes tous les arguments (éventuellement par références), par exemple `pid_t do_exec(const char* bin, const char* stdin, enum redir_mode stdin_redir, const char* stdout, enum redir_mode stdout_redir), soit tu mets tout dans une structure genre "struct commande { const char* binary; …}". C'est cette dernière méthode que je te conseillerais. Tu pourrais avoir alors le corps de ta boucle qui ressemblerait à "struct commande* cmd = parse_commande(); if (cmd) { … //commande_valide… do_exec(cmd); wait(pid); free(cmd);}" ce qui me semble assez lisible. Enfin bon ça dépend un peu du style que l'on vous a conseillé au cours…

      • [^] # Re: Quelques problèmes

        Posté par  . Évalué à 1. Dernière modification le 07 mars 2016 à 17:07.

        Note bien que ce code est buggé, lui aussi :p

            set_stdio();
            for(dir in path)
               exec();
            // not found
           print("command not found")

        Pourquoi ?
        Parce que le print() n'a rien à faire dans le fils ! Le stdout/stderr est (potentiellement) déja modifé par set_stdio(). Tu n'as pas d'autre choix que de faire toi-même une recherche exhautive dans path à coups de fstat, au bien tu laisse tomber car c'est sujet à race condition (i.e. on supprimer le programme juste après que tu l'aie trouvé, je ne sais pas si posix prévois une sorte de exec avec le fd du binaire déja ouvert), ou bien tu fais un exit(valeur_magique) et tu regardes après dans le père si le résultat du wait() te renvois cette valeur, mais bon ce n'est pas infaillible car rien n'empêche le programme de renvoyer légitimement cette valeur (je crois que bash combine ces deux méthodes), merci POSIX…  ;-)

  • # Conseils pour se débrouiller

    Posté par  . Évalué à 4. Dernière modification le 06 mars 2016 à 21:28.

    • Quand on programme et que l'on a un problème (ce qui arrive régulièrement) la meilleure solution reste de reproduire un programme minimal qui reproduise le problème: ça rend le problème plus simple à cerner, et donc à résoudre.

    • Quand on demande de l'aide sur un forum, il est très important de décrire le problème. Sinon les gens sont moins motivés pour t'aider, vu qu'ils se demandent si tu as réellement cherché (cf. point 1).

      • Décrire ce que tu as tenté peut aider les autres à localiser le problème.

    Bon, sinon, en vrac:

    • c'est la première fois que je vois une boucle for sur du printf. M'es avis que ton code serait plus propre avec juste une while() qui utilise le 2nde bloc de ton for. Moins il y a de code, mieux c'est (la gestion des erreurs n'entre pas dans ce principe, je précise).
    • fork renvoie un pid_t, pas un int. Ok, dans linux c'est juste un typedef, mais utiliser le type int est une catastrophe en terme de portabilité (selon la plate-forme, int ne correspond pas toujours à un int32_t… entres autres.). Mieux vaut éviter le transtypage implicite. Ça cause des bugs parfois très subtils.
    • évites les macros. Genre, ton define (dont l'intérêt est d'ailleurs franchement douteux mais bon…) serait peut-être mieux en const char PROMPT[] = "? "; encore une fois pour une question de typage. Les macros sont utiles, mais quand on peut s'en passer, c'est mieux, parce que quand tu as un problème causé par une macro, c'est une horreur à déboguer. Et en utilisant de véritables constantes, le compilateur est plus à même de t'indiquer des erreurs (via des warnings éventuellement).
    • ne pas passer de valeurs optionnelles que tu n'utilises de toute façon pas: wait(&status); fait qu'on se dit que la valeur de status sera utilisée, hors tu ne t'en sers pas. Autant utiliser wait(NULL); au moins ton intention est claire. Et être clair est important en programmation.
    • toujours compiler avec "-Wall -Weverything". Tu saurais ainsi que certaines portions de ton code ne seront jamais exécutées.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.