This question follows from this and this.
To bolster my understanding of spawning processes and redirecting pipes, I've written popen
-like function popen2()
-- below -- that returns the pid_t
of the spawned child process.
Note: the implementation of popen2()
spawns the child process by exec
ing sh -c cmd
instead of just cmd
because of the explanations in favor of this approach at the second linked question.
The code at bottom is not terribly long, but to cut to the chase: a.out
spawns child.out
as well as ps aux | grep child
to get visual confirmation of child processes' stats before printing out what it thinks is child.out
's pid.
A commenter at the second linked question pointed out that processes spawned via sh -c
may end up being either child or grandchild processes, depending on what sh
is.
I unintentionally verified this by observing that on my host -- where sh
resolves to /bin/bash
-- running a.out
shows that child.out
is run as a child process:
$ g++ --version && gcc -Wall -Wextra -pedantic -Werror ./main.c && ./a.out
g++ (Debian 6.3.0-18+deb9u1) 6.3.0 20170516
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
p2 stdout:
user 3004534 0.0 0.0 4028 732 pts/14 S+ 17:51 0:00 ./child.out
user 3004535 0.0 0.0 11176 2932 pts/14 S+ 17:51 0:00 sh -c ps aux | grep child
user 3004537 0.0 0.0 12780 968 pts/14 S+ 17:51 0:00 grep child
p.pid[3004534]
...whereas in a docker container on the same host -- where sh
resolves to /bin/dash
-- running a.out
shows that child.out
is run as a grandchild process:
Step 63/63 : RUN ./a.out
---> Running in 7a355740577b
p2 stdout:
root 7 0.0 0.0 2384 760 ? S 00:55 0:00 sh -c ./child.out
root 8 0.0 0.0 2384 760 ? S 00:55 0:00 sh -c ps aux | grep child
root 9 0.0 0.0 2132 680 ? S 00:55 0:00 ./child.out
root 11 0.0 0.0 3080 880 ? S 00:55 0:00 grep child
p.pid[7]
My question is: in a.out
's code, is there a way to get the pid_t
of the executed command in a way that abstracts whether the actual command is a child process' or a grandchild process?
To give some context: I want to be able to kill child.out
. By observation, in the environment where my popen2()
spawns child and grandchild processes, sending the child process a SIGTERM
kills only the child process, i.e. sh -c child.out
but not the grandchild process, i.e. child.out
, which is what I really want to kill.
The code:
// main.c
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define INVALID_FD (-1)
#define INVALID_PID (-1)
typedef enum PipeEnd {
READ_END = 0,
WRITE_END = 1
} PipeEnd;
typedef int Pipe[2];
/** Encapsulates information about a created child process. */
typedef struct popen2_t {
bool success; ///< true if the child process was spawned.
Pipe stdin; ///< parent -> stdin[WRITE_END] -> child's stdin
Pipe stdout; ///< child -> stdout[WRITE_END] -> parent reads stdout[READ_END]
Pipe stderr; ///< child -> stderr[WRITE_END] -> parent reads stderr[READ_END]
pid_t pid; ///< child process' pid
} popen2_t;
/** dup2( p[pe] ) then close and invalidate both ends of p */
static void dupFd( Pipe p, const PipeEnd pe, const int fd ) {
dup2( p[pe], fd);
close( p[READ_END] );
close( p[WRITE_END] );
p[READ_END] = INVALID_FD;
p[WRITE_END] = INVALID_FD;
}
/**
* Redirect a parent-accessible pipe to the child's stdin, and redirect the
* child's stdout and stderr to parent-accesible pipes.
*/
popen2_t popen2( const char* cmd ) {
popen2_t r = { false,
{ INVALID_FD, INVALID_FD },
{ INVALID_FD, INVALID_FD },
{ INVALID_FD, INVALID_FD },
INVALID_PID };
if ( -1 == pipe( r.stdin ) ) { goto end; }
if ( -1 == pipe( r.stdout ) ) { goto end; }
if ( -1 == pipe( r.stderr ) ) { goto end; }
switch ( (r.pid = fork()) ) {
case -1: // Error
goto end;
case 0: // Child process
dupFd( r.stdin, READ_END, STDIN_FILENO );
dupFd( r.stdout, WRITE_END, STDOUT_FILENO );
dupFd( r.stderr, WRITE_END, STDERR_FILENO );
{
char* argv[] = { (char*)"sh", (char*)"-c", (char*)cmd, NULL };
if ( -1 == execvp( argv[0], argv ) ) { exit(0); }
}
}
// Parent process
close( r.stdin[READ_END] );
r.stdin[READ_END] = INVALID_FD;
close( r.stdout[WRITE_END] );
r.stdout[WRITE_END] = INVALID_FD;
close( r.stderr[WRITE_END] );
r.stderr[WRITE_END] = INVALID_FD;
r.success = true;
end:
if ( ! r.success ) {
if ( INVALID_FD != r.stdin[READ_END] ) { close( r.stdin[READ_END] ); }
if ( INVALID_FD != r.stdin[WRITE_END] ) { close( r.stdin[WRITE_END] ); }
if ( INVALID_FD != r.stdout[READ_END] ) { close( r.stdout[READ_END] ); }
if ( INVALID_FD != r.stdout[WRITE_END] ) { close( r.stdout[WRITE_END] ); }
if ( INVALID_FD != r.stderr[READ_END] ) { close( r.stderr[READ_END] ); }
if ( INVALID_FD != r.stderr[WRITE_END] ) { close( r.stderr[WRITE_END] ); }
r.stdin[READ_END] = r.stdin[WRITE_END] =
r.stdout[READ_END] = r.stdout[WRITE_END] =
r.stderr[READ_END] = r.stderr[WRITE_END] = INVALID_FD;
}
return r;
}
int main( int argc, char* argv[] ) {
(void)argc;
(void)argv;
popen2_t p = popen2( "./child.out" );
int status = 0;
{
char buf[4096] = { '\0' };
popen2_t p2 = popen2( "ps aux | grep child" );
waitpid( p2.pid, &status, 0 );
read( p2.stdout[READ_END], buf, sizeof buf );
printf( "p2 stdout:\n%s\n", buf );
}
printf( "p.pid[%d]\n", p.pid );
{
pid_t wpid = waitpid( p.pid, &status, 0 );
return wpid == p.pid && WIFEXITED( status ) ? WEXITSTATUS( status ) : -1;
}
}
// child.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main( int argc, char* argv[] ) {
char buf[128] = { '\0' };
snprintf( buf, sizeof buf, "%s:%d\n", __FILE__, __LINE__ );
write( STDOUT_FILENO, buf, strlen( buf ) );
sleep( 1 );
snprintf( buf, sizeof buf, "%s:%d\n", __FILE__, __LINE__ );
write( STDOUT_FILENO, buf, strlen( buf ) );
sleep( 1 );
snprintf( buf, sizeof buf, "%s:%d\n", __FILE__, __LINE__ );
write( STDOUT_FILENO, buf, strlen( buf ) );
sleep( 1 );
snprintf( buf, sizeof buf, "%s:%d\n", __FILE__, __LINE__ );
write( STDOUT_FILENO, buf, strlen( buf ) );
sleep( 1 );
return 0;
}