[C] Comportement peu compréhensible de processus

stdout stderr

Le problème exposé dans ce sujet a été résolu.

Bonjour,

Je m’amuse à faire un shell en C et j’ai vraiment du mal. En particulier j’implémente une redirection : >1. Celle-ci fait la chose suivante : elle met le sdout de la commande dans un fichier et le stderr de la commande juste en dessous. Par exemple : ls >1 fichier va créer un fichier qui s’appelle fichier, et mettre au début du fichier le résultat de ls puis en dessous si il y en a le stderr de ls (je sépare ces deux affichages par des #).

Voilà ma fonction :

firstRedirection

void firstRedirection (int typeStream, int fd, char* fileName) {
    //erasing the file or creating it if it doesn't exist
    FILE* testF = fopen(fileName, "ab+");
    fclose(testF);
    
    FILE* streamFile = NULL;
    streamFile = fopen(fileName, "a+");
    char c;

    //reading stream and writing in the file
    while (read(fd, &c, 1) > 0) {
        fputc(c, streamFile);
    }

    //if stdout then write 80 #
    if (typeStream == 1){
        fputc('\n', streamFile);
        for (int i = 0; i < 80; i++){
            fputc('#', streamFile);
        }

        fputc('\n', streamFile);
    }
    
    fclose(streamFile);
}

Voilà la fonction qui l’appelle :

splitStream

void splitStream (char *fileName, int argc, char **argv) {
    int stdout[2];
    int stderr[2];

    //creating the pipes
    if (pipe(stdout) == -1 || pipe(stderr) == -1) {
        perror("pipe : ");
    }

    //creating child
    int pid = fork();
    if (pid < 0) {
        perror("fork :");
    }


    if (pid == 0) { //child
        close(stdout[1]);
        close(stderr[1]);

            firstRedirection(1, stdout[0], "fichier");
            firstRedirection(2, stderr[0], "fichier");
    }
    else { //dad
        close(stdout[0]);
        close(stderr[0]);

        dup2(stdout[1], 1);
        dup2(stderr[1], 2);

        shell(argc, argv);
    }

}

J’ai alors le problème suivant :

  • si dans mon main je fais :
    char*test[] = {"find", ".", ">1", "fichier", NULL};
    splitStream("fichier",4, test);

J’ai exactement le résultat voulu : création du fichier qui s’appelle fichier et dans lequel on trouve le stdout de find . et en dessous le stderr de find. C’est étrange d’ailleurs non ? J’arrive à récupérer séparément le stdout et le stderr avec une seule pipe et un seul processus. Peut-être que la data reste coincé comme je close les pipe, je ne sais trop…

  • A l’inverse si je fais tout ça dans une boucle pour faire un shell :
 while (1) {
        //waiting for user instruction
        printf("$> ");
        fflush(NULL);
        
        if (!fgets(line, 1024, stdin))
            return 0;
       
      //getting rid of the \n character at the end of line
      line[strlen(line)-1] = '\0';
      char **cmd = parseString(line);
      int n = countWords(cmd);
      
      shell(n, cmd);
    
    }

Et que je tape find . >1 fichier et bien ça mouline dans le vide. Du coup je ne comprends pas du tout. Au début je pensais que le problème venait de ma fonction qui parse un string : elle prend une commande en argument puis la transforme en un array de mot. Mais en faisant des tests ça n’est pas le cas. De même je ne pense pas que le problème vienne de la fonction shell qui execute la commande. Je met néanmoins ces deux fonctions en cachés au cas-ou.

parseString

char** parseString (char* cmd) {
    char ** res  = NULL;
    char *  p    = strtok (cmd, " ");
    int n_spaces = 0;


   
    while (p) {
        res = realloc (res, sizeof (char*) * ++n_spaces);

        if (res == NULL){//memory allocation problem
            exit (-1);
        }
        res[n_spaces-1] = p;

        p = strtok (NULL, " ");
    }

    //adding extra last element with value NULL for execvp function
    res = realloc (res, sizeof (char*) * (n_spaces+1));
    res[n_spaces] = NULL;
    
    return res;
}

shell :

void shell (int argc, char ** argv) {
    if (argc > 0) {
        if (strcmp(argv[0], "exit") == 0) {
            exit(0);
        }
        else if (strcmp(argv[0], "cd") == 0) {
            chdir(argv[2]);
        }
        else {
            pid_t pid = fork();

            if (pid == -1) {
                printf("\nFailed forking child..");
                return;
            } else if (pid == 0) {
                if (execvp(argv[0], argv) < 0) {
                    printf("\nCould not execute command..");
                    return;
                }
                exit(0);
            } else {
                // waiting for child to terminate
                //int status;
                //waitpid(pid, &status, 0);
                wait(NULL);
                return;
            }
        }
    }
}

EDIT : il semblerait que le problème vienne du while(1) donc pourquoi le faire un while(1) interférai avec mon code je ne sais pas…

+0 -0

Salut,

Je n’ai pas de réponse à ton problème, néanmoins je remarque ceci.

res = realloc (res, sizeof (char*) * (n_spaces+1));
res[n_spaces] = NULL;

Tu ne vérifies pas le retour de la fonction realloc().

streamFile = fopen(fileName, "a+");

Tu ne vérifies pas le retour de la fonction fopen()

fputc('\n', streamFile);

Tu ne vérifies pas le retour de la fonction fputc().

//erasing the file or creating it if it doesn't exist
FILE* testF = fopen(fileName, "ab+");

Le mode a créer le fichier s’il n’existe pas, mais n’écrase pas son contenu s’il existe déjà, c’est le mode w qui produit ce comportement.

J’ai exactement le résultat voulu : création du fichier qui s’appelle fichier et dans lequel on trouve le stdout de find . et en dessous le stderr de find. C’est étrange d’ailleurs non ? J’arrive à récupérer séparément le stdout et le stderr avec une seule pipe et un seul processus.

SomeName

Tu utilises deux pipes. ;)

int stdout[2];
int stderr[2];

Sinon, tu ne nous a pas donné le code des fonctions countWords() et dumsh().

+0 -0

Merci beaucoup d’avoir pris le temps de lire tout ça.

Je corrige les vérifications de retour que je n’ai pas fais.

Effectivement c’est w ! J’ai bien corrigé.

Ensuite effectivement j’utilise deux pipes. Néanmoins normalement stdout et stderr sont entremêlés non ? Par exemple si je fais un find . stderr peut s’afficher au milieu des stdout. Or moi je lis une pipe après l’autre. Donc comment la pipe de stderr fait pour enregistrer une sortie stderr au milieu de stdout ? Je ne sais pas si je suis très clair, mais c’est la raison pour laquelle je ne comprends pas pourquoi ça me donne le bon résultat.

Aussi j’ai avancé et une partie de l’erreur vient du fait que je ne close pas bien mes fichiers. Du coup les fputc ne s’effectuent pas. Ainsi j’ai rajouté des fflush en dessous de chaque fputc ça me permet pour le moment d’obtenir stdout mais ça n’affiche pas les dièses ni stderr.

Je me suis trompé c’est pas dumsh c’est shell le nom de la fonction… (j’ai édité). Et pour countWord :

int countWords (char** words) {
    int count = 0;
    
    //until we are not reaching NULL we continue
    while (words[count] != NULL) {
        count++;
    }
    
    return count;
}

Ensuite effectivement j’utilise deux pipes. Néanmoins normalement stdout et stderr sont entremêlés non ? Par exemple si je fais un find . stderr peut s’afficher au milieu des stdout. Or moi je lis une pipe après l’autre. Donc comment la pipe de stderr fait pour enregistrer une sortie stderr au milieu de stdout ? Je ne sais pas si je suis très clair, mais c’est la raison pour laquelle je ne comprends pas pourquoi ça me donne le bon résultat.

SomeName

L’affichage dans le terminal peut effectivement être entremêlé parce que ce dernier affiche les données en provenance des deux flux. Toutefois, ce sont bien deux flux distincts : stdout et stderr. Ce que tu envoies dans l’un ne se retrouve pas dans l’autre.

Je me suis trompé c’est pas dumsh c’est shell le nom de la fonction… (j’ai édité).

SomeName

Mmm… Mais tu ne fais pas appel à ta fonction de redirection dans ta boucle, non, ou je me trompe ?

while (1) {
        //waiting for user instruction
        printf("$> ");
        fflush(NULL);
        
        if (!fgets(line, 1024, stdin))
            return 0;
       
      line[strlen(line)-1] = '\0';
      char **cmd = parseString(line);
      int n = countWords(cmd);
      
      shell(n, cmd); /* SplitStream ? */
}

Note, à supposer que cela soit ça, fait attention : si tu utilises dup2 pour associer les descripteurs 1 et 2 à d’autres fichiers dans le processus père, tu n’auras plus accès à stdout ou stderr lors des itérations suivantes.

+0 -0

Merci pour ta réponse.

Hmmm d’accord je vois pour stdout et stderr.

Effectivement shell utilise splitStream (c’est même la seul chose qu’il fait).

Mais du coup comment palier ce problème ?

J’ai remplacé le else (donc quand on est dans le processus père) du spliStream par :

else { //dad
        close(stdout[0]);
        close(stderr[0]);

        int saved_stdout = dup(1);
        int saved_stderr = dup(2);

        dup2(stdout[1], 1);
        dup2(stderr[1], 2);

        dumshShell(argc, argv);

        dup2(saved_stdout, 1);
        dup2(saved_stderr, 2);

    }

Et c’est un peu mieux mais encore une fois ça bloque au milieu de l’écriture de mon fichier. Du coup je suis un peu perdu…

+0 -0

Oui bien sûr.

Alors j’ai changé de stratégie pour résoudre mon problème. Tout d’abord je fais deux processus enfants. Le premier processus s’occupe de stdout et le second de stderr. Ensuite le premier processus appel une fonction qui va écrire la sortie stdout dans un fichier. Le second va faire pareil pour la sortie stderr dans un autre fichier. Ensuite à la fin de la fonction qui fait les deux processus je merge les deux fichiers. Le problème c’est que le merge se passe mal et je ne sais pas pourquoi (les lignes se mélangent).

Voilà le code qui permet de créer les deux processus enfants :

void redir1Stream (int argc, char ** argv, char* fileName, char* mkFile) {
    int stdout[2];
    int stderr[2];
    //creating the pipes
    if (pipe(stdout) == -1 || pipe(stderr) == -1) {
        perror("pipe : ");
    }

    //creating first child
    int pid = fork();
    if (pid < 0) {
        perror("fork :");
    }


    if(pid == 0) { //first child
        
        //creating second child
        int pid1 = fork();
        if (pid1 < 0) {
            perror("fork1 : ");
        }

        if(pid1 == 0) { //second child
            close(stderr[1]);
            close(stdout[1]);
            
            redirection(1, stdout[0], fileName);
            exit(0);
            
        } else {
            close(stderr[1]);
            close(stdout[1]);
            
            redirection(2, stderr[0], mkFile);
            exit(0);
        }
    } else {
         close(stdout[0]);
         close(stderr[0]);

         int saved_stdout = dup(1);
         int saved_stderr = dup(2);

         dup2(stdout[1], 1);
         dup2(stderr[1], 2);

         dumshShell(argc, argv);

         close(stdout[1]);
         close(stderr[1]);

         dup2(saved_stdout, 1);
         dup2(saved_stderr, 2);
    }
    close(stdout[0]);
    close(stderr[0]);
    close(stderr[1]);
    close(stdout[1]);

    merge(fileName, mkFile);
}

Voilà le code qui s’occupe d’écrire dans un fichier :

void redirection (int typeStream, int fd, char* fileName) {
    if (typeStream == 1) { //in stdout
        //erasing the file or creating it if it doesn't exist
        int streamFile = open(fileName,  O_CREAT|O_RDWR|O_APPEND, S_IRUSR | S_IWUSR);
        char c;

        //reading stream and writing in the file
        while (read(fd, &c, 1) > 0) {
            write(streamFile, &c, sizeof(c));
        }
        
        close(streamFile);
    }
    else if (typeStream == 2) { //in stderr

        int streamFile = open(fileName,  O_CREAT|O_RDWR|O_APPEND, S_IRUSR | S_IWUSR);
        char c;

        //reading stream and writing in the file
        while (read(fd, &c, 1) > 0) {
            write(streamFile, &c,  sizeof(c));
        }
        
        close(streamFile);
    }
    else{}
}

et enfin voilà le code qui merge :

void merge (char* fileName, char *fileName1) {
    
    
    int fd = open(fileName, O_RDWR);
    char c;
    while (read(fd, &c, 1) > 0) { //going at the end of the file
    }
    //int fd = open(fileName, O_RDWR |O_APPEND);
    //char c;
    char retour[] = "\n";
    char diese[] = "#";
    
    write (fd, retour, strlen(retour));

    for (int i = 0; i < 80; i++) {
        if (write (fd, diese, strlen(diese)) == -1) {
            perror("Details : ");
        }
    }
    
    write (fd, retour, strlen(retour));
    
    int fd1 = open(fileName1, O_RDWR);
    while(read(fd1, &c, 1) > 0) {
        write(fd, &c, sizeof(c));
    }
    
    close(fd1);
    close(fd);
}

Pour avoir le code complet c’est là : https://onlinegdb.com/r1CSCJAA8 (du coup il faut faire find . >1 fichier pour mettre la sortie stdout et stderr dans fichier)

Voilà ma structure en gros :

void createTwoChild() {
    int stdout[2];
    int stderr[2];
    //creating the pipes
    if (pipe(stdout) == -1 || pipe(stderr) == -1) {
        perror("pipe : ");
    }

    //creating first child
    int pid = fork();
    if (pid < 0) {
        perror("fork :");
    }


    if(pid == 0) { //first child
        
        //creating second child
        int pid1 = fork();
        if (pid1 < 0) {
            perror("fork1 : ");
        }

        if(pid1 == 0) { //second child
            close(stderr[1]);
            close(stdout[1]);

            //here I write stdout in a file by reading file descriptor stdout[0]
            exit(0);
            
        } else {
            close(stderr[1]);
            close(stdout[1]);
            
            //here I write stderr in a file by reading file descriptor stderr[0]

            exit(0);
        }
    } else {
         close(stdout[0]);
         close(stderr[0]);

         int saved_stdout = dup(1);
         int saved_stderr = dup(2);

         dup2(stdout[1], 1);
         dup2(stderr[1], 2);

         //here I use execvp to run a shell instruction

         close(stdout[1]);
         close(stderr[1]);

         dup2(saved_stdout, 1);
         dup2(saved_stderr, 2);
    }
    close(stdout[0]);
    close(stderr[0]);
    close(stderr[1]);
    close(stdout[1]);

    //here I merge the two files created by the child process
}

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte