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