web-dev-qa-db-fra.com

Implémentation de Shell en C et besoin d'aide pour gérer la redirection d'entrée / sortie

2ème round

Après avoir lu certaines des réponses, mon code révisé est:

int pid = fork();

if (pid == -1) {
    perror("fork");
} else if (pid == 0) {   

    if (in) { //if '<' char was found in string inputted by user
        int fd0 = open(input, O_RDONLY, 0);
        dup2(fd0, STDIN_FILENO);
        close(fd0);
        in = 0;
    }

    if (out) { //if '>' was found in string inputted by user
        int fd1 = creat(output, 0644);
        dup2(fd1, STDOUT_FILENO);
        close(fd1);
        out = 0;
    }   

    execvp(res[0], res);
    perror("execvp");
    _exit(1);
} else {
    waitpid(pid, 0, 0);
    free(res);
}

Cela fonctionne, mais il semble que la sortie standard ne soit pas reconnectée ou quelque chose dans ce sens. Voici l'exécution:

Shell$ cat > file
hello, world
this is a test
Shell$ cat < file //no output
Shell$ ls //no output

'<' et '>' fonctionnent tous les deux, mais après leur exécution, il n'y a pas de sortie.


Tour 1

Je travaille sur un shell relativement simple en C depuis un certain temps maintenant, mais j'ai du mal à implémenter la redirection d'entrée (<) et de sortie (>). Aidez-moi à trouver les problèmes dans le code suivant:

int fd;
int pid = fork();
int current_out;

if (in) { //if '<' char was found in string inputted by user
    fd = open(input, O_RDONLY, 0);
    dup2(fd, STDIN_FILENO);
    in = 0;
    current_out = dup(0);
}

if (out) { //if '>' was found in string inputted by user
    fd = creat(output, 0644);
    dup2(fd, STDOUT_FILENO);
    out = 0;
    current_out = dup(1);
}

if (pid == -1) {
    perror("fork");
} else if (pid == 0) {       
    execvp(res[0], res);
    perror("execvp");
    _exit(1);
} else {
    waitpid(pid, 0, 0);
    dup2(current_out, 1);
    free(res);
}

J'ai peut-être du matériel inutile là-dedans parce que j'ai essayé différentes choses pour le faire fonctionner. Je ne sais pas ce qui ne va pas.

14
Jordan

Vous avez beaucoup trop de descripteurs de fichiers ouverts après votre redirection. Décortiquons les deux paragraphes:

if (in) { //if '<' char was found in string inputted by user
    fd = open(input, O_RDONLY, 0);
    dup2(fd, STDIN_FILENO);
    in = 0;
    current_in = dup(0);  // Fix for symmetry with second paragraph
}

if (out) { //if '>' was found in string inputted by user
    fd = creat(output, 0644);
    dup2(fd, STDOUT_FILENO);
    out = 0;
    current_out = dup(1);
}

Je vais être charitable et ignorer le fait que vous ignorez les erreurs. Cependant, vous devrez vérifier vos appels système.

Dans le premier paragraphe, vous ouvrez un fichier et capturez le descripteur de fichier (il pourrait bien être 3) dans la variable fd. Vous dupliquez ensuite le descripteur de fichier sur une entrée standard (STDIN_FILENO). Notez cependant que le descripteur de fichier 3 est toujours ouvert. Ensuite, vous faites une dup(0) (qui, pour des raisons de cohérence, devrait être STDIN_FILENO), Obtenant un autre descripteur de fichier, peut-être 4. Vous avez donc des descripteurs de fichier 0, 3 et 4 pointant vers le même fichier (et, en effet, la même description de fichier ouvert - notant qu'une description de fichier ouvert est différente d'un descripteur de fichier ouvert). Si votre intention avec current_in Était de conserver l'entrée standard du shell (parent), vous devez faire cela dup() avant de faire dup2() qui écrase la sortie. Cependant, il serait préférable de ne pas modifier les descripteurs de fichiers du shell parent; c'est moins de frais généraux que la duplication des descripteurs de fichiers.

Ensuite, vous répétez plus ou moins le processus dans le deuxième paragraphe, en écrasant d'abord le seul enregistrement du descripteur de fichier 3 ouvert avec l'appel fd = creat(...) mais en obtenant un nouveau descripteur, peut-être 5, puis en le dupliquant sur la sortie standard. Vous effectuez ensuite une dup(1), donnant un autre descripteur de fichier, peut-être 6.

Ainsi, vous avez stdin et stdout du shell principal redirigés vers les fichiers (et aucun moyen de les restaurer aux valeurs d'origine). Par conséquent, votre premier problème est que vous effectuez la redirection avant de fork(); vous devriez le faire après la fork() - bien que lorsque vous arrivez à la tuyauterie entre les processus, vous devrez créer des tuyaux avant de bifurquer.

Votre deuxième problème est que vous devez fermer une pléthore de descripteurs de fichiers, dont vous n'avez plus de référence.

Donc, vous pourriez avoir besoin de:

if ((pid = fork()) < 0)
    ...error...
else if (pid == 0)
{
    /* Be childish */
    if (in)
    {
        int fd0 = open(input, O_RDONLY);
        dup2(fd0, STDIN_FILENO);
        close(fd0);
    }

    if (out)
    {
        int fd1 = creat(output , 0644) ;
        dup2(fd1, STDOUT_FILENO);
        close(fd1);
    }
    ...now the child has stdin coming from the input file, 
    ...stdout going to the output file, and no extra files open.
    ...it is safe to execute the command to be executed.
    execve(cmd[0], cmd, env);   // Or your preferred alternative
    fprintf(stderr, "Failed to exec %s\n", cmd[0]);
    exit(1);
}
else
{
    /* Be parental */
    ...wait for child to die, etc...
}

Avant de faire quoi que ce soit, vous devez vous assurer que vous avez déjà vidé les canaux d'E/S standard du shell, probablement en utilisant fflush(0), de sorte que si l'enfant forké écrit dans l'erreur standard en raison d'un problème, il n'y a pas de sortie dupliquée superflue.

Notez également que les différents appels open() doivent être vérifiés.

15
Jonathan Leffler

Vous avez beaucoup trop de descripteurs de fichiers ouverts après votre redirection. Le code dont vous avez besoin est le suivant.

    if (pid == 0)
{          /* for the child process:         */

    // function for redirection ( '<' , '>' )

    int fd0,fd1,i,in=0,out=0;
    char input[64],output[64];

    // finds where '<' or '>' occurs and make that argv[i] = NULL , to ensure that command wont't read that

    for(i=0;argv[i]!='\0';i++)
    {
        if(strcmp(argv[i],"<")==0)
        {        
            argv[i]=NULL;
            strcpy(input,argv[i+1]);
            in=2;           
        }               

        if(strcmp(argv[i],">")==0)
        {      
            argv[i]=NULL;
            strcpy(output,argv[i+1]);
            out=2;
        }         
    }

    //if '<' char was found in string inputted by user
    if(in)
    {   

        // fdo is file-descriptor
        int fd0;
        if ((fd0 = open(input, O_RDONLY, 0)) < 0) {
            perror("Couldn't open input file");
            exit(0);
        }           
        // dup2() copies content of fdo in input of preceeding file
        dup2(fd0, 0); // STDIN_FILENO here can be replaced by 0 

        close(fd0); // necessary
    }

    //if '>' char was found in string inputted by user 
    if (out)
    {

        int fd1 ;
        if ((fd1 = creat(output , 0644)) < 0) {
            perror("Couldn't open the output file");
            exit(0);
        }           

        dup2(fd1, STDOUT_FILENO); // 1 here can be replaced by STDOUT_FILENO
        close(fd1);
    }

    execvp(*argv, argv);
    perror("execvp");
    _exit(1);

    // another syntax
    /*      if (!(execvp(*argv, argv) >= 0)) {     // execute the command  
            printf("*** ERROR: exec failed\n");
            exit(1);
     */ 
}


    else if((pid) < 0)
    {     
        printf("fork() failed!\n");
        exit(1);
    }

    else {                                  /* for the parent:      */

        while (!(wait(&status) == pid)) ; // good coding to avoid race_conditions(errors) 
    }
}
7
Shriyansh Agrawal

Voici ce qui se passe. Après avoir appelé fork(), deux processus en cours d'exécution sont des doublons du processus d'origine. La différence réside dans la valeur de retour de fork() qui est stockée dans pid.

Ensuite, les deux processus (le shell et l'enfant) redirigent leur stdin et stdout vers les mêmes fichiers. Je pense que vous essayez de sauvegarder le fd précédent dans current_out, mais comme le souligne Seth Robertson, cela ne fonctionne pas actuellement, car le mauvais descripteur de fichier est enregistré. Le parent restaure également sa sortie standard, mais pas stdin.

Vous pouvez corriger ce bogue, mais vous pouvez faire mieux. Vous n'avez pas réellement à rediriger la sortie du parent, juste celle de l'enfant. Vérifiez donc tout d'abord pid. Ensuite, il n'est également pas nécessaire de restaurer les descripteurs de fichiers.

4
Greg Inozemtsev