Home > Enterprise >  STDERR output is not being captured when running invalid command
STDERR output is not being captured when running invalid command

Time:03-31

I am trying to catch the output from running the command /bin/lsx -lah /.

The output should be: bash: /bin/lsx: no such file or directory

However, I am just not getting anything in the printf().

This is the code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define die(e) do { fprintf(stderr, "%s\n", e); exit(EXIT_FAILURE); } while (0);

int main() {
  int link[2];
  pid_t pid;
  char foo[4096];

  if (pipe(link)==-1)
    die("pipe");

  if ((pid = fork()) == -1)
    die("fork");

  if(pid == 0) {

    dup2 (link[1], STDOUT_FILENO);
    close(link[0]);
    close(link[1]);
    execl("/bin/lsx", "ls", "-1", (char *)0);
    die("execl");

  } else {

    close(link[1]);
    int nbytes = read(link[0], foo, sizeof(foo));
    printf("Output: (%.*s)\n", nbytes, foo);
    wait(NULL);

  }
  return 0;
}

I am just wondering why the output is not being captured and printed in the bottom printf().

CodePudding user response:

As John pointed out, you are only capturing stdout, not stderr, and well-formed programs usually send error messages to stderr (the die macro you have shown is an example of this).

A quick solution is to redirect the child processes' stderr to its stdout with another call to dup2

dup2(link[1], STDOUT_FILENO);
dup2(STDOUT_FILENO, STDERR_FILENO);

which will redirect both output streams to the single pipe.

Alternatively, capture both streams with two pipes to retain their separation as they propagate.

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

#define die(e) do { fprintf(stderr, "%s\n", e); exit(EXIT_FAILURE); } while (0);

ssize_t push_through(const char *prefix,
        FILE *out, int in,
        char *buf, size_t bufsz)
{
    ssize_t bytes = read(in, buf, bufsz);

    if (bytes > 0)
        fprintf(out, "%s: (%.*s)\n", prefix, (int) bytes, buf);

    return bytes;
}

int main(void)
{
    pid_t pid;
    int stdout_pipe[2];
    int stderr_pipe[2];
    char buffer[4096];

    if (pipe(stdout_pipe) == -1 || pipe(stderr_pipe) == -1)
        die("pipe");

    if ((pid = fork()) == -1)
        die("fork");

    if (pid == 0) {
        close(stdout_pipe[0]);
        close(stderr_pipe[0]);

        dup2(stdout_pipe[1], STDOUT_FILENO);
        dup2(stderr_pipe[1], STDERR_FILENO);

        close(stdout_pipe[1]);
        close(stderr_pipe[1]);

        execl("/bin/lsx", "ls", "-1", (char *) NULL);
        perror("execl failed because");
        die("execl");
    } else {
        ssize_t outbytes, errbytes;

        close(stdout_pipe[1]);
        close(stderr_pipe[1]);

        do {
            outbytes = push_through("out", stdout, stdout_pipe[0],
                    buffer, sizeof buffer);
            errbytes = push_through("err", stderr, stderr_pipe[0],
                    buffer, sizeof buffer);
        } while (outbytes > 0 || errbytes > 0);

        wait(NULL);
    }
}

In the event that execl fails, it might be useful to call perror to print a more detailed error message.

Note that expecting the error

bash: /bin/lsx: no such file or directory

is possibly misguided, as exec* functions only defer to a shell under certain conditions.

The only mention of a shell in man 3 exec is with regards to execlp, execvp, and execvpe:

If the header of a file isn't recognized (the attempted execve(2) failed with the error ENOEXEC), these functions will execute the shell (/bin/sh) with the path of the file as its first argument. (If this attempt fails, no further searching is done.)

  • Related