]> git.ozlabs.org Git - ccan/blob - ccan/pipecmd/pipecmd.c
pipecmd: close fds in child.
[ccan] / ccan / pipecmd / pipecmd.c
1 /* CC0 license (public domain) - see LICENSE file for details */
2 #include <ccan/pipecmd/pipecmd.h>
3 #include <ccan/noerr/noerr.h>
4 #include <stdlib.h>
5 #include <errno.h>
6 #include <unistd.h>
7 #include <fcntl.h>
8
9 int pipecmd_preserve;
10
11 static char **gather_args(const char *arg0, va_list ap)
12 {
13         size_t n = 1;
14         char **arr = calloc(sizeof(char *), n + 1);
15
16         if (!arr)
17                 return NULL;
18         arr[0] = (char *)arg0;
19
20         while ((arr[n++] = va_arg(ap, char *)) != NULL) {
21                 char **narr = realloc(arr, sizeof(char *) * (n + 1));
22                 if (!narr) {
23                         free(arr);
24                         return NULL;
25                 }
26                 arr = narr;
27         }
28         return arr;
29 }
30
31 pid_t pipecmdv(int *fd_tochild, int *fd_fromchild, int *fd_errfromchild,
32                const char *cmd, va_list ap)
33 {
34         char **arr = gather_args(cmd, ap);
35         pid_t ret;
36
37         if (!arr) {
38                 errno = ENOMEM;
39                 return -1;
40         }
41         ret = pipecmdarr(fd_tochild, fd_fromchild, fd_errfromchild, arr);
42         free_noerr(arr);
43         return ret;
44 }
45
46 pid_t pipecmdarr(int *fd_tochild, int *fd_fromchild, int *fd_errfromchild,
47                  char *const *arr)
48 {
49         int tochild[2], fromchild[2], errfromchild[2], execfail[2];
50         /* fds for parent to close */
51         int par_close[4], num_par_close = 0;
52         /* fds for child to close */
53         int child_close[4], num_child_close = 0;
54         pid_t childpid;
55         int err;
56
57         if (fd_tochild) {
58                 if (fd_tochild == &pipecmd_preserve) {
59                         tochild[0] = STDIN_FILENO;
60                 } else if (pipe(tochild) == 0) {
61                         par_close[num_par_close++] = tochild[0];
62                         child_close[num_child_close++] = tochild[1];
63                 } else
64                         goto fail;
65         } else {
66                 tochild[0] = open("/dev/null", O_RDONLY);
67                 if (tochild[0] < 0)
68                         goto fail;
69                 par_close[num_par_close++] = tochild[0];
70         }
71         if (fd_fromchild) {
72                 if (fd_fromchild == &pipecmd_preserve) {
73                         fromchild[1] = STDOUT_FILENO;
74                 } else if (pipe(fromchild) == 0) {
75                         par_close[num_par_close++] = fromchild[1];
76                         child_close[num_child_close++] = fromchild[0];
77                 } else
78                         goto fail;
79         } else {
80                 fromchild[1] = open("/dev/null", O_WRONLY);
81                 if (fromchild[1] < 0)
82                         goto fail;
83                 par_close[num_par_close++] = fromchild[1];
84         }
85         if (fd_errfromchild) {
86                 if (fd_errfromchild == &pipecmd_preserve) {
87                         errfromchild[1] = STDERR_FILENO;
88                 } else if (fd_errfromchild == fd_fromchild) {
89                         errfromchild[0] = fromchild[0];
90                         errfromchild[1] = fromchild[1];
91                 } else if (pipe(errfromchild) == 0) {
92                         par_close[num_par_close++] = errfromchild[1];
93                         child_close[num_child_close++] = errfromchild[0];
94                 } else
95                         goto fail;
96         } else {
97                 errfromchild[1] = open("/dev/null", O_WRONLY);
98                 if (errfromchild[1] < 0)
99                         goto fail;
100                 par_close[num_par_close++] = errfromchild[1];
101         }
102
103         if (pipe(execfail) != 0)
104                 goto fail;
105
106         par_close[num_par_close++] = execfail[1];
107         child_close[num_child_close++] = execfail[0];
108
109         if (fcntl(execfail[1], F_SETFD, fcntl(execfail[1], F_GETFD)
110                   | FD_CLOEXEC) < 0)
111                 goto fail;
112
113         childpid = fork();
114         if (childpid < 0)
115                 goto fail;
116
117         if (childpid == 0) {
118                 for (int i = 0; i < num_child_close; i++)
119                         close(child_close[i]);
120
121                 // Child runs command.
122                 if (tochild[0] != STDIN_FILENO) {
123                         if (dup2(tochild[0], STDIN_FILENO) == -1)
124                                 goto child_errno_fail;
125                         close(tochild[0]);
126                 }
127                 if (fromchild[1] != STDOUT_FILENO) {
128                         if (dup2(fromchild[1], STDOUT_FILENO) == -1)
129                                 goto child_errno_fail;
130                         close(fromchild[1]);
131                 }
132                 if (fd_errfromchild && fd_errfromchild == fd_fromchild) {
133                         if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1)
134                                 goto child_errno_fail;
135                 } else if (errfromchild[1] != STDERR_FILENO) {
136                         if (dup2(errfromchild[1], STDERR_FILENO) == -1)
137                                 goto child_errno_fail;
138                         close(errfromchild[1]);
139                 }
140
141                 /* Make (fairly!) sure all other fds are closed. */
142                 int max = sysconf(_SC_OPEN_MAX);
143                 for (int i = 3; i < max; i++)
144                         if (i != execfail[1])
145                                 close(i);
146
147                 execvp(arr[0], arr);
148
149         child_errno_fail:
150                 err = errno;
151                 /* Gcc's warn-unused-result fail. */
152                 if (write(execfail[1], &err, sizeof(err))) {
153                         ;
154                 }
155                 exit(127);
156         }
157
158         for (int i = 0; i < num_par_close; i++)
159                 close(par_close[i]);
160
161         /* Child will close this without writing on successful exec. */
162         if (read(execfail[0], &err, sizeof(err)) == sizeof(err)) {
163                 close(execfail[0]);
164                 waitpid(childpid, NULL, 0);
165                 errno = err;
166                 return -1;
167         }
168         close(execfail[0]);
169         if (fd_tochild && fd_tochild != &pipecmd_preserve)
170                 *fd_tochild = tochild[1];
171         if (fd_fromchild && fd_fromchild != &pipecmd_preserve)
172                 *fd_fromchild = fromchild[0];
173         if (fd_errfromchild && fd_errfromchild != &pipecmd_preserve)
174                 *fd_errfromchild = errfromchild[0];
175         return childpid;
176
177 fail:
178         for (int i = 0; i < num_par_close; i++)
179                 close_noerr(par_close[i]);
180         return -1;
181 }
182
183 pid_t pipecmd(int *fd_tochild, int *fd_fromchild, int *fd_errfromchild,
184               const char *cmd, ...)
185 {
186         pid_t childpid;
187
188         va_list ap;
189         va_start(ap, cmd);
190         childpid = pipecmdv(fd_tochild, fd_fromchild, fd_errfromchild, cmd, ap);
191         va_end(ap);
192
193         return childpid;
194 }