/* * Test program to see if process groups work the way I think they do. * * Scenario (names are derived from an email conversation about this * and admittedly make little sense in isolation - uppercase letters * refer to processes, or with "pgrp", process groups): * * B forks C. * B puts C into its own pgrp (pgrp C). * C process C' (naively, ie, no pgrp fiddling). * C dies (orphaning C'). * B reaps C's zombie. * B forks D, to be part of the same job as C (and C'). * B puts D into pgrp C. * * The question is whether this last step works. That is, does C' * still sticking around keep pgrp C alive and usable, or does pgrp C * die, in some sense, with process C, even though C' is still in it? * * As its sole author, I explicitly place this file in the public domain. */ #include #include #include #include #include #include #include extern const char *__progname; /* * B, C, C_, and D are the PIDs of B, C, C', and D, respectively. * * p_C, p_C_, and p_D are socketpairs between B and, respectively, C, * C', and D. * * The socketpairs are for synchronization, to make sure things happen * in the correct order. In comments below, these socketpairs are * (admittedly rather loosely) spoken of as pipes, but I don't use * pipes because pipes are unidirectional. Only one of these needs * bidirectional data flow; I could use pipes and create four of them, * instead, but, well, shrug. */ static pid_t B; static pid_t C; static pid_t C_; static pid_t D; static int p_C[2]; static int p_C_[2]; static int p_D[2]; /* * "Never-fail" (ie, errors caught and handled) versions of fork, * setpgid, socketpair, and recv, with some slight argument list and * return value massaging. getdeath() is a similar wrappar around * wait4(), to wait for a specific child to die. */ static pid_t ckfork(void) { pid_t p; p = fork(); if (p < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } return(p); } static void cksetpgid(pid_t proc, pid_t pg) { if (setpgid(proc,pg) < 0) { fprintf(stderr,"%s: setpgid(%d,%d): %s\n",__progname,(int)proc,(int)pg,strerror(errno)); exit(1); } } static void cksocketpair(int *pv) { if (socketpair(AF_LOCAL,SOCK_STREAM,0,pv) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } } /* We use MSG_WAITALL for conveinence, rather than writing out the loop necessary to get the same effect out of read(). Since we're using a socketpair rather than a pipe, we can use socket-specific calls. */ static void ckread(int fd, void *data, int len, const char *what) { int n; n = recv(fd,data,len,MSG_WAITALL); if (n < 0) { fprintf(stderr,"%s: recv %s: %s\n",__progname,what,strerror(errno)); exit(1); } if (n != len) { fprintf(stderr,"%s: recv %s: wanted %d, got %d\n",__progname,what,len,n); exit(1); } } static void getdeath(pid_t p) { pid_t d; d = wait4(p,0,0,0); if (d < 0) { fprintf(stderr,"%s: wait4 %d: %s\n",__progname,(int)p,strerror(errno)); exit(1); } if (d != p) { fprintf(stderr,"%s: wait4 %d: got %d\n",__progname,(int)p,(int)d); exit(1); } } /* * Try it and see what happens. */ int main(void); int main(void) { char junk; /* Create the pipes to C and C'. Since C' is forked by C, we have to create the C' pipe before forking C. */ cksocketpair(&p_C[0]); cksocketpair(&p_C_[0]); /* Note our PID. This actually doesn't matter... */ B = getpid(); printf("B = %d [%d]\n",(int)B,(int)getpgid(B)); /* Fork C. */ C = ckfork(); if (C == 0) { /* In process C. Close B's end of the B<->C and B<->C' pipes; then block until B tells us to conitnue. */ close(p_C[1]); close(p_C_[1]); read(p_C[0],&junk,1); /* Fork C'. */ C_ = ckfork(); if (C_ == 0) { /* In C'. Close the B<->C pipe, which we shouldn't be using at all; then send our PID to B through our pipe. */ close(p_C[0]); C_ = getpid(); write(p_C_[0],&C_,sizeof(C_)); /* Wait until B dies, then die ourselves. */ read(p_C_[0],&junk,1); exit(0); } /* In C, after forking C'. Close the C' pipe, which we have no further use for, now that we've passed it to C'; then block until B tells us to die, at which point we do so. */ close(p_C_[0]); read(p_C[0],&junk,1); exit(0); } /* In B, after forking C. Close the C and C' ends of their pipes. */ close(p_C[0]); close(p_C_[0]); /* Put C into its own process group. */ cksetpgid(C,C); /* Print out C's PID and pgrp. */ printf("C = %d [%d]\n",(int)C,(int)getpgid(C)); /* Tell C to fork C'. */ write(p_C[1],&junk,1); /* Once C' starts, it sends us its PID. Receive it. */ ckread(p_C_[1],&C_,sizeof(C_),"C' pid"); /* Print C''s PID and pgrp. */ printf("C' = %d [%d]\n",(int)C_,(int)getpgid(C_)); /* Tell C to die. We could write something, but since C doesn't care what read returns, only *that* it returns, this works. */ close(p_C[1]); /* Wait for C to die, and reap the zombie. */ getdeath(C); /* Verify that nothing has changed with C'. */ printf("C' = %d [%d]\n",(int)C_,(int)getpgid(C_)); /* Create the pipe to D. */ cksocketpair(&p_D[0]); /* Fork D. */ fflush(0); /* no duplicated output please */ D = ckfork(); if (D == 0) { /* In D. Close B's ends of the C' and D pipes, neither of which we should be doing anything with. (The C pipe was closed up above, to tell C to die.) */ close(p_D[1]); close(p_C_[1]); /* Wait until B dies, then die ourselves. */ read(p_D[0],&junk,1); exit(0); } /* Close D's end of its pipe. */ close(p_D[0]); /* Try to put D into pgrp C. */ cksetpgid(D,C); /* The attempt appears to have succeeded; see what resulted. */ printf("D = %d [%d]\n",(int)D,(int)getpgid(D)); /* Exit. This closes all remaining pipes, which tells any remaining processes to die. */ exit(0); }