1 #include "cado.h" // IWYU pragma: keep
2 // IWYU pragma: no_include <bits/types/struct_rusage.h>
3 #include <stdio.h>
4 #include <errno.h>
5 #include <string.h>
6 #include <unistd.h>
7 #include <stdlib.h>
8 #include <sys/time.h> // IWYU pragma: keep
9 #include <sys/wait.h> // IWYU pragma: keep
10 #ifdef HAVE_GETRUSAGE
11 #include <sys/resource.h> // IWYU pragma: keep
12 #endif
13 #include <pthread.h>
14 
15 #include "macros.h"     // MAYBE_UNUSED
16 #include "cado_popen.h"
17 
18 /* We need to close file descriptors underlying other popen()-ed calls on
19  * the children processes.  Not the underlying streams though, since
20  * closing is done in userland, and only once.
21  */
22 static struct {
23     pthread_mutex_t m[1];
24     int n;
25     struct { int fd; pid_t kid; } p[1024];
26 } popenlist[1] = {{{PTHREAD_MUTEX_INITIALIZER}, 0, {{0,0},}}};
27 
cado_fd_popen(const char * command,const char * mode)28 int cado_fd_popen(const char * command, const char * mode)
29 {
30     /* Is this a pipe for reading or for writing ? */
31     int imode = -1; /* 0: reading; 1: writing */
32     if (strcmp(mode, "r") == 0) {
33         imode = 0;
34     } else if (strcmp(mode, "rb") == 0) {
35         imode = 0;
36     } else if (strcmp(mode, "w") == 0) {
37         imode = 1;
38     } else if (strcmp(mode, "wb") == 0) {
39         imode = 1;
40     } else {
41         fprintf(stderr, "Please fix %s\n", __func__);
42         abort();
43     }
44     int pipefd[2];
45     if (pipe(pipefd) < 0) {
46         perror("pipe");
47         return -1;
48     }
49     /* pipefd[0] is the read end, pipefd[1] is the write end */
50     pthread_mutex_lock(popenlist->m);
51 
52     popenlist->p[popenlist->n].fd = pipefd[imode];
53     pid_t * kid = &(popenlist->p[popenlist->n].kid);
54     popenlist->n++;
55 
56     pid_t child = fork();
57     if (child < 0) {
58         pthread_mutex_unlock(popenlist->m);
59         perror("fork");
60         return -1;
61     }
62     if (child) {
63         /* I'm the father. I only want to use pipefd[imode]. */
64         close(pipefd[!imode]);
65         *kid = child;
66         pthread_mutex_unlock(popenlist->m);
67         /*
68         fprintf(stderr, "%s child %d (%s) through fd %d\n",
69                 imode ?  "Writing to" : "Reading from",
70                 child, command, pipefd[imode]);
71                 */
72         return pipefd[imode];
73     } else {
74         /* if father wants to read, we close our standard input
75          * (0==imode), and bind our standard output (1==!imode) to the
76          * other end. */
77         /* We still have the lock here. We don't care much about
78          * releasing it, since we're going to do exec() anyway */
79         close(imode);
80         close(pipefd[imode]);
81         dup2(pipefd[!imode], !imode);
82         for(int i = 0 ; i < popenlist->n - 1 ; i++) {
83             int fd = popenlist->p[i].fd;
84             int kid = popenlist->p[i].kid;
85             int rc = close(fd);
86             if (rc < 0) {
87                 fprintf(stderr, "Process %d closing fd %d (%s pid %d)"
88                         ": %s\n",
89                         getpid(), fd,
90                         imode ? "->" : "<-", kid, strerror(errno));
91             }
92         }
93         popenlist->n = 0;       /* who cares, we're exec()'ing anyway */
94         /*
95         fprintf(stderr, "Child process %d (parent %d) executing %s\n",
96                 getpid(),
97                 getppid(),
98                 command);
99                 */
100         execl("/bin/sh", "sh", "-c", command, NULL);
101         perror("execl() failed");
102         fprintf (stderr, "execl command size is %zu\n",
103                 strlen (command) + strlen ("sh -c "));
104         exit(EXIT_FAILURE);
105     }
106     return -1;
107 }
108 
cado_popen(const char * command,const char * mode)109 FILE * cado_popen(const char * command, const char * mode)
110 {
111     int fd = cado_fd_popen(command, mode);
112     if (fd < 0) return NULL;
113     return fdopen(fd, mode);
114 }
115 
116 /* remove the fd from our list of popen'ed files, and return the kid id.
117  */
reap_fd(int fd)118 static int reap_fd(int fd)
119 {
120     pid_t kid = 0;
121     pthread_mutex_lock(popenlist->m);
122     int nn = 0;
123     for(int i = 0 ; i < popenlist->n ; i++) {
124         popenlist->p[nn] = popenlist->p[i];
125         if (popenlist->p[i].fd == fd) {
126             if (kid) {
127                 fprintf(stderr, "Error: two or more child processes (%d and %d) hold a reference to fd %d\n", kid, popenlist->p[i].kid, fd);
128                 abort();
129             }
130             ASSERT_ALWAYS(kid == 0);
131             kid = popenlist->p[i].kid;
132         } else {
133             nn++;
134         }
135     }
136     popenlist->n = nn;
137     pthread_mutex_unlock(popenlist->m);
138     return kid;
139 }
140 
141 #ifdef HAVE_GETRUSAGE
cado_fd_pclose2(int fd,struct rusage * nr)142 int cado_fd_pclose2(int fd, struct rusage * nr)
143 #else
144 int cado_fd_pclose2(int fd, void * nr MAYBE_UNUSED)
145 #endif
146 {
147     int kid = reap_fd(fd);
148     /* close the kid's fd, which will trigger its termination -- at least
149      * if it's reading from it. If it's writing to it, at this point we
150      * expect the child's source to have been consumed, and thus the
151      * write end of the pipe to have been closed already */
152     close(fd);
153     /* we must wait() for the kid now */
154     int status, error;
155 #ifdef HAVE_GETRUSAGE
156     struct rusage r[1];
157     error = wait4(kid, &status, 0, r);
158 #else
159     /* if we have no getrusage(), we probably don't have wait4 either */
160     error = waitpid(kid, &status, 0);
161 #endif
162     if (error == -1) {
163         return -1;
164     }
165     char long_status[80] = {'\0'};
166 
167     if (WIFEXITED(status)) {
168         snprintf(long_status, sizeof(long_status),
169                 "exited with code %d", WEXITSTATUS(status));
170     } else if (WIFSIGNALED(status)) {
171         snprintf(long_status, sizeof(long_status),
172                 "terminated by signal %d%s",
173                 WEXITSTATUS(status), WCOREDUMP(status) ? ", with core" : "");
174     } else {
175         snprintf(long_status, sizeof(long_status), "[weird status %d]", status);
176     }
177 
178     /*
179     double u = r->ru_utime.tv_sec + (r->ru_utime.tv_usec / 1.0e6);
180     double s = r->ru_stime.tv_sec + (r->ru_stime.tv_usec / 1.0e6);
181     fprintf(stderr, "Child process %d %s, having spent %.2fs+%.2fs on cpu\n",
182             kid, long_status, u, s);
183             */
184 #ifdef HAVE_GETRUSAGE
185     if (nr) memcpy(nr, r, sizeof(struct rusage));
186 #endif
187     /* Linux man page:
188        "returns the exit status of the command as returned by wait4(2)"
189     */
190     return status;
191 }
192 
193 #ifdef HAVE_GETRUSAGE
cado_pclose2(FILE * stream,struct rusage * nr)194 int cado_pclose2(FILE * stream, struct rusage * nr)
195 #else
196 int cado_pclose2(FILE * stream, void * nr MAYBE_UNUSED)
197 #endif
198 {
199     int fd = fileno(stream);
200     fclose(stream);
201     return cado_fd_pclose2(fd, nr);
202 }
203 
204