]> git.ozlabs.org Git - ccan/blob - ccan/pipecmd/pipecmd.c
pipecmd: new module.
[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, const char *cmd, va_list ap)
27 {
28         int tochild[2], fromchild[2], execfail[2];
29         pid_t childpid;
30         int err;
31
32         if (fd_tochild) {
33                 if (pipe(tochild) != 0)
34                         goto fail;
35         } else {
36                 tochild[0] = open("/dev/null", O_RDONLY);
37                 if (tochild[0] < 0)
38                         goto fail;
39         }
40         if (fd_fromchild) {
41                 if (pipe(fromchild) != 0)
42                         goto close_tochild_fail;
43         } else {
44                 fromchild[1] = open("/dev/null", O_WRONLY);
45                 if (fromchild[1] < 0)
46                         goto close_tochild_fail;
47         }
48         if (pipe(execfail) != 0)
49                 goto close_fromchild_fail;
50
51         if (fcntl(execfail[1], F_SETFD, fcntl(execfail[1], F_GETFD)
52                   | FD_CLOEXEC) < 0)
53                 goto close_execfail_fail;
54
55         childpid = fork();
56         if (childpid < 0)
57                 goto close_execfail_fail;
58
59         if (childpid == 0) {
60                 char **args = gather_args(cmd, ap);
61
62                 if (fd_tochild)
63                         close(tochild[1]);
64                 if (fd_fromchild)
65                         close(fromchild[0]);
66                 close(execfail[0]);
67
68                 // Child runs command.
69                 if (!args)
70                         err = ENOMEM;
71                 else {
72                         if (tochild[0] != STDIN_FILENO) {
73                                 if (dup2(tochild[0], STDIN_FILENO) == -1)
74                                         goto child_errno_fail;
75                                 close(tochild[0]);
76                         }
77                         if (fromchild[1] != STDOUT_FILENO) {
78                                 if (dup2(fromchild[1], STDOUT_FILENO) == -1)
79                                         goto child_errno_fail;
80                                 close(fromchild[1]);
81                         }
82                         execvp(cmd, args);
83                 child_errno_fail:
84                         err = errno;
85                 }
86                 write(execfail[1], &err, sizeof(err));
87                 exit(127);
88         }
89
90         close(tochild[0]);
91         close(fromchild[1]);
92         close(execfail[1]);
93         /* Child will close this without writing on successful exec. */
94         if (read(execfail[0], &err, sizeof(err)) == sizeof(err)) {
95                 waitpid(childpid, NULL, 0);
96                 errno = err;
97                 return -1;
98         }
99         if (fd_tochild)
100                 *fd_tochild = tochild[1];
101         if (fd_fromchild)
102                 *fd_fromchild = fromchild[0];
103         return childpid;
104
105 close_execfail_fail:
106         close_noerr(execfail[0]);
107         close_noerr(execfail[1]);
108 close_fromchild_fail:
109         if (fd_fromchild)
110                 close_noerr(fromchild[0]);
111         close_noerr(fromchild[1]);
112 close_tochild_fail:
113         close_noerr(tochild[0]);
114         if (fd_tochild)
115                 close_noerr(tochild[1]);
116 fail:
117         return -1;
118 }
119
120 pid_t pipecmd(int *fd_fromchild, int *fd_tochild, const char *cmd, ...)
121 {
122         pid_t childpid;
123
124         va_list ap;
125         va_start(ap, cmd);
126         childpid = pipecmdv(fd_fromchild, fd_tochild, cmd, ap);
127         va_end(ap);
128
129         return childpid;
130 }