3

I am trying to get the exist status of the child process using popen().

Case 1: Calling function with the shell command returning error. This is working as expected.

func("du -sh _invalid_file_");

Output:

du: cannot access '_invalid_file_': No such file or directory
Child exit value: 1

Here the child exist status is same as the exit value of du which ran in bash.

$ du -sh _invalid_file_
du: cannot access '_invalid_file_': No such file or directory
$ 
$ echo $?
 1
$

Case 2: (Error case) Calling function with the below shell command returning success. In my code, WEXITSTATUS() returning non-zero value but the same command in bash returning 0.

func("du -sh");

Output:

Child exit value: 141

Please suggest to fix this issue. Pasted the code below.

int func(char *cmd)
{
    FILE *pfp = NULL;
    int retval, status;

    pfp = popen(cmd, "r");
    if (pfp == NULL) {
        perror("popen failed");
        exit(1);
    }

    status = pclose(pfp);
    if (status < 0) {
        perror("pclose error");
    } else {
        if (WIFEXITED(status)) {
            retval = WEXITSTATUS(status);
            printf("Child exit value: %d\n", retval);
        }
    }

    return 0;
}
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
Kirubakaran
  • 378
  • 4
  • 20
  • I can't replicate your problem. If I do `func("du -sh");` then it exits with signal 13 (`SIGPIPE`) which you get from not emptying the pipe. If I read all output from the pipe then I get exit value `0` as expected. – Some programmer dude Jul 21 '22 at 05:59
  • I have reviewed the code and it is working for me. `Child exit value: 141` – Kirubakaran Jul 21 '22 at 06:06
  • How do you build? How do you run? What environment are you using to build and run? What distribution are you using? What version of the distribution? – Some programmer dude Jul 21 '22 at 06:08
  • I was running in Ubuntu 20. Compiled with GCC. Here the complete source code. https://pastebin.com/raw/ZjyT4kD1 – Kirubakaran Jul 21 '22 at 06:12

1 Answers1

5

You closed the pipe before actually reading any data from it. This would cause the child process to receive SIGPIPE (13) and be killed.

popen() would use /bin/sh -c to run the command so it's actually sh -c "du ..." running. When du was killed by SIGPIPE (13), sh would set the exit status to 128+13=141 and since du is the last command in the shell session so 141 would be the final exit status of the sh -c ... command and you got 141.

To fix it you need to read data from the pipe before pclose().

int func(char *cmd)
{
    FILE *pfp = NULL;
    char buf[1024];
    int retval, status;

    pfp = popen(cmd, "r");
    if (pfp == NULL) {
        perror("popen failed");
        exit(1);
    }

    /* read from the pipe or close() may cause the process to receive SIGPIPE */
    while (fgets(buf, sizeof buf, pfp) ) {
        printf("%s", buf);
    }

    status = pclose(pfp);
    if (status < 0) {
        perror("pclose error");
    } else {
        if (WIFEXITED(status)) {
            retval = WEXITSTATUS(status);
            printf("Child exit value: %d\n", retval);
        }
    }

    return 0;
}

The following is from man 7 signal:

   Signal     Value     Action   Comment
   ----------------------------------------------------------------------
   ... ...

   SIGPIPE      13       Term    Broken pipe: write to pipe with no
                                 readers; see pipe(7)
   ... ...

This SIGPIPE behavior happens every day though it is not obvious. You can try like this:

$ od /dev/urandom | head -n1
0000000 067255 052464 166240 113163 122024 054232 015444 110641
$ declare -p PIPESTATUS    # see PIPESTATUS in `man bash'
declare -a PIPESTATUS=([0]="141" [1]="0")
$
$ { od /dev/urandom; echo EXIT=$? >&2; } | head -n1
0000000 020553 063350 025451 116300 006505 151644 122151 175763
EXIT=141

Regarding the shell's 128+signal behavior, the following is from man bash (Bash is sh-compatible so this also applies for sh.):

When a command terminates on a fatal signal N, bash uses the value of 128+N as the exit status.

pynexj
  • 19,215
  • 5
  • 38
  • 56
  • Thanks @Pynexj. It is worked and marked as answer. In your comment, in exit status what is 128? – Kirubakaran Jul 21 '22 at 06:30
  • that's shell's feature. if a cmd is killed by a signal, the shell would set the exit status as 128+signal. you can try like this in an interactive bash: 1) `sleep 100`; 2) press ctrl-c to kill the sleep; 3) `echo $?` and you'll get 130 which is 128+2 and 2 is `SIGINT` (generated by ctrl-c). – pynexj Jul 21 '22 at 06:34
  • note that SIGPIPE killing a process is only the DEFAULT behavior. a program can explicitly ignore it or do whatever it wants on receiving SIGPIPE. – pynexj Jul 21 '22 at 06:47