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;
}
CodePudding user response:
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 of128 N
as the exit status.