c2b514df5e97189230dbc282f3b673293a77028c
[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         pid_t childpid;
51         int err;
52
53         if (fd_tochild) {
54                 if (fd_tochild == &pipecmd_preserve) {
55                         tochild[0] = STDIN_FILENO;
56                         fd_tochild = NULL;
57                 } else if (pipe(tochild) != 0)
58                         goto fail;
59         } else {
60                 tochild[0] = open("/dev/null", O_RDONLY);
61                 if (tochild[0] < 0)
62                         goto fail;
63         }
64         if (fd_fromchild) {
65                 if (fd_fromchild == &pipecmd_preserve) {
66                         fromchild[1] = STDOUT_FILENO;
67                         fd_fromchild = NULL;
68                 } else if (pipe(fromchild) != 0)
69                         goto close_tochild_fail;
70         } else {
71                 fromchild[1] = open("/dev/null", O_WRONLY);
72                 if (fromchild[1] < 0)
73                         goto close_tochild_fail;
74         }
75         if (fd_errfromchild) {
76                 if (fd_errfromchild == &pipecmd_preserve) {
77                         errfromchild[1] = STDERR_FILENO;
78                         fd_errfromchild = NULL;
79                 } else if (fd_errfromchild == fd_fromchild) {
80                         errfromchild[0] = fromchild[0];
81                         errfromchild[1] = fromchild[1];
82                 } else {
83                         if (pipe(errfromchild) != 0)
84                                 goto close_fromchild_fail;
85                 }
86         } else {
87                 errfromchild[1] = open("/dev/null", O_WRONLY);
88                 if (errfromchild[1] < 0)
89                         goto close_fromchild_fail;
90         }
91
92         if (pipe(execfail) != 0)
93                 goto close_errfromchild_fail;
94
95         if (fcntl(execfail[1], F_SETFD, fcntl(execfail[1], F_GETFD)
96                   | FD_CLOEXEC) < 0)
97                 goto close_execfail_fail;
98
99         childpid = fork();
100         if (childpid < 0)
101                 goto close_execfail_fail;
102
103         if (childpid == 0) {
104                 if (fd_tochild)
105                         close(tochild[1]);
106                 if (fd_fromchild)
107                         close(fromchild[0]);
108                 if (fd_errfromchild && fd_errfromchild != fd_fromchild)
109                         close(errfromchild[0]);
110
111                 close(execfail[0]);
112
113                 // Child runs command.
114                 if (tochild[0] != STDIN_FILENO) {
115                         if (dup2(tochild[0], STDIN_FILENO) == -1)
116                                 goto child_errno_fail;
117                         close(tochild[0]);
118                 }
119                 if (fromchild[1] != STDOUT_FILENO) {
120                         if (dup2(fromchild[1], STDOUT_FILENO) == -1)
121                                 goto child_errno_fail;
122                         close(fromchild[1]);
123                 }
124                 if (fd_errfromchild && fd_errfromchild == fd_fromchild) {
125                         if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1)
126                                 goto child_errno_fail;
127                 } else if (errfromchild[1] != STDERR_FILENO) {
128                         if (dup2(errfromchild[1], STDERR_FILENO) == -1)
129                                 goto child_errno_fail;
130                         close(errfromchild[1]);
131                 }
132                 execvp(arr[0], arr);
133
134         child_errno_fail:
135                 err = errno;
136                 /* Gcc's warn-unused-result fail. */
137                 if (write(execfail[1], &err, sizeof(err))) {
138                         ;
139                 }
140                 exit(127);
141         }
142
143         close(tochild[0]);
144         close(fromchild[1]);
145         close(errfromchild[1]);
146         close(execfail[1]);
147         /* Child will close this without writing on successful exec. */
148         if (read(execfail[0], &err, sizeof(err)) == sizeof(err)) {
149                 close(execfail[0]);
150                 waitpid(childpid, NULL, 0);
151                 errno = err;
152                 return -1;
153         }
154         close(execfail[0]);
155         if (fd_tochild)
156                 *fd_tochild = tochild[1];
157         if (fd_fromchild)
158                 *fd_fromchild = fromchild[0];
159         if (fd_errfromchild)
160                 *fd_errfromchild = errfromchild[0];
161         return childpid;
162
163 close_execfail_fail:
164         close_noerr(execfail[0]);
165         close_noerr(execfail[1]);
166 close_errfromchild_fail:
167         if (fd_errfromchild)
168                 close_noerr(errfromchild[0]);
169         close_noerr(errfromchild[1]);
170 close_fromchild_fail:
171         if (fd_fromchild)
172                 close_noerr(fromchild[0]);
173         close_noerr(fromchild[1]);
174 close_tochild_fail:
175         close_noerr(tochild[0]);
176         if (fd_tochild)
177                 close_noerr(tochild[1]);
178 fail:
179         return -1;
180 }
181
182 pid_t pipecmd(int *fd_tochild, int *fd_fromchild, int *fd_errfromchild,
183               const char *cmd, ...)
184 {
185         pid_t childpid;
186
187         va_list ap;
188         va_start(ap, cmd);
189         childpid = pipecmdv(fd_tochild, fd_fromchild, fd_errfromchild, cmd, ap);
190         va_end(ap);
191
192         return childpid;
193 }