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