tal: make tal_next() only return immediate children.
[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 static char **gather_args(const char *arg0, va_list ap)
10 {
11         size_t n = 1;
12         char **arr = calloc(sizeof(char *), n + 1);
13
14         if (!arr)
15                 return NULL;
16         arr[0] = (char *)arg0;
17
18         while ((arr[n++] = va_arg(ap, char *)) != NULL) {
19                 arr = realloc(arr, sizeof(char *) * (n + 1));
20                 if (!arr)
21                         return NULL;
22         }
23         return arr;
24 }
25
26 pid_t pipecmdv(int *fd_fromchild, int *fd_tochild, int *fd_errfromchild,
27                const char *cmd, va_list ap)
28 {
29         char **arr = gather_args(cmd, ap);
30         pid_t ret;
31
32         if (!arr) {
33                 errno = ENOMEM;
34                 return -1;
35         }
36         ret = pipecmdarr(fd_fromchild, fd_tochild, fd_errfromchild, arr);
37         free_noerr(arr);
38         return ret;
39 }
40
41 pid_t pipecmdarr(int *fd_fromchild, int *fd_tochild, int *fd_errfromchild,
42                  char *const *arr)
43 {
44         int tochild[2], fromchild[2], errfromchild[2], execfail[2];
45         pid_t childpid;
46         int err;
47
48         if (fd_tochild) {
49                 if (pipe(tochild) != 0)
50                         goto fail;
51         } else {
52                 tochild[0] = open("/dev/null", O_RDONLY);
53                 if (tochild[0] < 0)
54                         goto fail;
55         }
56         if (fd_fromchild) {
57                 if (pipe(fromchild) != 0)
58                         goto close_tochild_fail;
59         } else {
60                 fromchild[1] = open("/dev/null", O_WRONLY);
61                 if (fromchild[1] < 0)
62                         goto close_tochild_fail;
63         }
64         if (fd_errfromchild) {
65                 if (fd_errfromchild == fd_fromchild) {
66                         errfromchild[0] = fromchild[0];
67                         errfromchild[1] = fromchild[1];
68                 } else {
69                         if (pipe(errfromchild) != 0)
70                                 goto close_fromchild_fail;
71                 }
72         } else {
73                 errfromchild[1] = open("/dev/null", O_WRONLY);
74                 if (errfromchild[1] < 0)
75                         goto close_fromchild_fail;
76         }
77                 
78         if (pipe(execfail) != 0)
79                 goto close_errfromchild_fail;
80
81         if (fcntl(execfail[1], F_SETFD, fcntl(execfail[1], F_GETFD)
82                   | FD_CLOEXEC) < 0)
83                 goto close_execfail_fail;
84
85         childpid = fork();
86         if (childpid < 0)
87                 goto close_execfail_fail;
88
89         if (childpid == 0) {
90                 if (fd_tochild)
91                         close(tochild[1]);
92                 if (fd_fromchild)
93                         close(fromchild[0]);
94                 if (fd_errfromchild && fd_errfromchild != fd_fromchild)
95                         close(errfromchild[0]);
96
97                 close(execfail[0]);
98
99                 // Child runs command.
100                 if (tochild[0] != STDIN_FILENO) {
101                         if (dup2(tochild[0], STDIN_FILENO) == -1)
102                                 goto child_errno_fail;
103                         close(tochild[0]);
104                 }
105                 if (fromchild[1] != STDOUT_FILENO) {
106                         if (dup2(fromchild[1], STDOUT_FILENO) == -1)
107                                 goto child_errno_fail;
108                         close(fromchild[1]);
109                 }
110                 if (fd_errfromchild && fd_errfromchild == fd_fromchild) {
111                         if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1)
112                                 goto child_errno_fail;
113                 } else if (errfromchild[1] != STDERR_FILENO) {
114                         if (dup2(errfromchild[1], STDERR_FILENO) == -1)
115                                 goto child_errno_fail;
116                         close(errfromchild[1]);
117                 }
118                 execvp(arr[0], arr);
119
120         child_errno_fail:
121                 err = errno;
122                 write(execfail[1], &err, sizeof(err));
123                 exit(127);
124         }
125
126         close(tochild[0]);
127         close(fromchild[1]);
128         close(errfromchild[1]);
129         close(execfail[1]);
130         /* Child will close this without writing on successful exec. */
131         if (read(execfail[0], &err, sizeof(err)) == sizeof(err)) {
132                 close(execfail[0]);
133                 waitpid(childpid, NULL, 0);
134                 errno = err;
135                 return -1;
136         }
137         close(execfail[0]);
138         if (fd_tochild)
139                 *fd_tochild = tochild[1];
140         if (fd_fromchild)
141                 *fd_fromchild = fromchild[0];
142         if (fd_errfromchild)
143                 *fd_errfromchild = errfromchild[0];
144         return childpid;
145
146 close_execfail_fail:
147         close_noerr(execfail[0]);
148         close_noerr(execfail[1]);
149 close_errfromchild_fail:
150         if (fd_errfromchild)
151                 close_noerr(errfromchild[0]);
152         close_noerr(errfromchild[1]);
153 close_fromchild_fail:
154         if (fd_fromchild)
155                 close_noerr(fromchild[0]);
156         close_noerr(fromchild[1]);
157 close_tochild_fail:
158         close_noerr(tochild[0]);
159         if (fd_tochild)
160                 close_noerr(tochild[1]);
161 fail:
162         return -1;
163 }
164
165 pid_t pipecmd(int *fd_fromchild, int *fd_tochild, int *fd_errfromchild,
166               const char *cmd, ...)
167 {
168         pid_t childpid;
169
170         va_list ap;
171         va_start(ap, cmd);
172         childpid = pipecmdv(fd_fromchild, fd_tochild, fd_errfromchild, cmd, ap);
173         va_end(ap);
174
175         return childpid;
176 }