Search code examples
coperating-systempipestdoutstderr

How can I read the error message from stderr?


We are studying Pipes and I have to create a C code that reads the error message from compiling another c program. I am using dup2 to copy stederr to writing side of the pipe, after that I use read method to read from the other side of the pipe but in some cases I am not able to read the whole error message. Here is what I am trying to do:

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

int main(int argc, char *argv[]) {
    const char *const cmd0 = argv[1];
    int fds_pair[2];
    pipe(fds_pair);
    
    pid_t pid;
    if ((pid = fork()) == 0) {

        close(fds_pair[0]);

        /// stderr <- pipe's write
        dup2(fds_pair[1], 2);
        execlp("g++", "g++", cmd0, "-o", "my_program", NULL);
        return 0;

    } else {
        close(fds_pair[1]);
    
        char expression[256];
        memset(expression, '\0', sizeof(expression));
        size_t readret;            
        readret = read(fds_pair[0], &expression, sizeof(expression));
        printf("%s", expression);

        while (readret > 0) {
            readret = read(fds_pair[0], expression, sizeof(expression));
            printf("%s", expression);
        }
    }
    close(fds_pair[0]);
    return 0;
}

the problem that I am facing is that when I try to get the error of program :

1   #include <stdio.h>
2
3   int main () {
4     ;float v_05778c89;
5     float v_ char v_ int v_ double v_ double v_
6   }

Here is the error message that I read from the code above:

main2.c: In function ‘int main()’:
main2.c:5:5: error: expected initializer before ‘float’
5 |     float v_
  |     ^~~~~

I don't get the error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘attribute’ before ‘}’ token 6 | }

I am confused about what I am doing wrong or what I have to fix


Solution

  • The error message you expect (expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘attribute’ before ‘}’ token 6 | }) is typical of MS Visual C++, not GNU g++. That may be the main problem — unrealistic expectations.

    I amended your code to include the first paragraph of code from my version, shown below, so that argc is used and it passes my default compilation options. When I run your amended code (source file re23.c compiled to re23), I get the output:

    $ re23 main2.c
    main2.c: In function ‘int main()’:
    main2.c:5:12: error: expected initializer before ‘char’
        5 |   float v_ char v_ int v_ double v_ double v_
          |            ^~~~
    main2.c: In function ‘int main()’:
    main2.c:5:12: error: expected initializer before ‘char’
        5 |   float v_ char v_ int v_ double v_ double v_
          |            ^~~~
    $
    

    The message appears twice because you don't deal with the I/O from the child process (the C++ compiler) correctly. Basically, you need to read the data and check that it worked at all before you print anything.

    Compilation (GCC 11.2.0 running on macOS Big Sur 11.6.4):

    $ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes -fno-common re23.c -o re23
    $
    

    I mentioned various problems in the comments, too. I believe they're all fixed in the code below, which works OK according to my standards. It's similar to yours, and is closely based on yours, but has a few key differences.

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <errno.h>
    
    int main(int argc, char *argv[])
    {
        if (argc != 2)
        {
            fprintf(stderr, "Usage: %s sourcefile.cpp\n", argv[0]);
            exit(EXIT_FAILURE);
        }
    
        const char *const cmd0 = argv[1];
        int fds_pair[2];
        if (pipe(fds_pair) < 0)
        {
            fprintf(stderr, "%s: failed to create pipe: %d %s\n",
                    argv[0], errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
    
        pid_t pid = fork();
        if (pid < 0)
        {
            fprintf(stderr, "%s: failed to fork: %d %s\n",
                    argv[0], errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
        else if (pid == 0)
        {
            dup2(fds_pair[1], STDERR_FILENO);
            close(fds_pair[0]);
            close(fds_pair[1]);
            execlp("g++", "g++", cmd0, "-o", "my_program", NULL);
            fprintf(stderr, "%s: failed to exec '%s': %d %s\n",
                    argv[0], "g++", errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
        else
        {
            close(fds_pair[1]);
            char expression[256];
            ssize_t readret;
            while ((readret = read(fds_pair[0], &expression, sizeof(expression))) > 0)
            {
                printf("%.*s", (int)readret, expression);
            }
            close(fds_pair[0]);
            int corpse;
            int status;
            while ((corpse = wait(&status)) > 0)
                printf("%s: child %d exited with status 0x%.4X\n", argv[0], corpse, status);
        }
        return 0;
    }
    

    Here are some sample runs — I used source code re37.c for the source above, creating the program re37:

    $ ./re37
    Usage: ./re37 sourcefile.cpp
    $ ./re37 nonexistent.cpp
    cc1plus: fatal error: nonexistent.cpp: No such file or directory
    compilation terminated.
    ./re37: child 4644 exited with status 0x0100
    $ cat main2.c
    #include <stdio.h>
    
    int main () {
      ;float v_05778c89;
      float v_ char v_ int v_ double v_ double v_
    }
    $ ./re37 main2.c
    main2.c: In function ‘int main()’:
    main2.c:5:12: error: expected initializer before ‘char’
        5 |   float v_ char v_ int v_ double v_ double v_
          |            ^~~~
    ./re37: child 4670 exited with status 0x0100
    $