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