lib/util/run_cmd: use a cleanup function instead of a destructor
[samba.git] / lib / util / util_runcmd.c
1 /*
2    Unix SMB/CIFS implementation.
3
4    run a child command
5
6    Copyright (C) Andrew Tridgell 2010
7
8    This program is free software; you can redistribute it and/or modify
9    it under the terms of the GNU General Public License as published by
10    the Free Software Foundation; either version 3 of the License, or
11    (at your option) any later version.
12
13    This program is distributed in the hope that it will be useful,
14    but WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16    GNU General Public License for more details.
17
18    You should have received a copy of the GNU General Public License
19    along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21 */
22
23 /*
24   this runs a child command with stdout and stderr going to the Samba
25   log
26  */
27
28 #include "includes.h"
29 #include "system/filesys.h"
30 #include "../lib/util/tevent_unix.h"
31 #include "../lib/util/util_runcmd.h"
32 #include "../lib/util/tfork.h"
33 #include "../lib/util/sys_rw.h"
34
35 static void samba_runcmd_cleanup_fn(struct tevent_req *req,
36                                     enum tevent_req_state req_state)
37 {
38         struct samba_runcmd_state *state = tevent_req_data(
39                 req, struct samba_runcmd_state);
40
41         if (state->tfork != NULL) {
42                 tfork_destroy(&state->tfork);
43         }
44         state->pid = -1;
45
46         if (state->fd_stdin != -1) {
47                 close(state->fd_stdin);
48                 state->fd_stdin = -1;
49         }
50 }
51
52 static void samba_runcmd_io_handler(struct tevent_context *ev,
53                                     struct tevent_fd *fde,
54                                     uint16_t flags,
55                                     void *private_data);
56
57 /*
58   run a command as a child process, with a timeout.
59
60   any stdout/stderr from the child will appear in the Samba logs with
61   the specified log levels
62  */
63 struct tevent_req *samba_runcmd_send(TALLOC_CTX *mem_ctx,
64                                      struct tevent_context *ev,
65                                      struct timeval endtime,
66                                      int stdout_log_level,
67                                      int stderr_log_level,
68                                      const char * const *argv0, ...)
69 {
70         struct tevent_req *req;
71         struct samba_runcmd_state *state;
72         int p1[2], p2[2], p3[2];
73         char **argv;
74         va_list ap;
75
76         if (argv0 == NULL) {
77                 return NULL;
78         }
79
80         req = tevent_req_create(mem_ctx, &state,
81                                 struct samba_runcmd_state);
82         if (req == NULL) {
83                 return NULL;
84         }
85
86         state->stdout_log_level = stdout_log_level;
87         state->stderr_log_level = stderr_log_level;
88         state->fd_stdin = -1;
89
90         state->arg0 = talloc_strdup(state, argv0[0]);
91         if (tevent_req_nomem(state->arg0, req)) {
92                 return tevent_req_post(req, ev);
93         }
94
95         if (pipe(p1) != 0) {
96                 tevent_req_error(req, errno);
97                 return tevent_req_post(req, ev);
98         }
99         if (pipe(p2) != 0) {
100                 close(p1[0]);
101                 close(p1[1]);
102                 tevent_req_error(req, errno);
103                 return tevent_req_post(req, ev);
104         }
105         if (pipe(p3) != 0) {
106                 close(p1[0]);
107                 close(p1[1]);
108                 close(p2[0]);
109                 close(p2[1]);
110                 tevent_req_error(req, errno);
111                 return tevent_req_post(req, ev);
112         }
113
114         state->tfork = tfork_create();
115         if (state->tfork == NULL) {
116                 close(p1[0]);
117                 close(p1[1]);
118                 close(p2[0]);
119                 close(p2[1]);
120                 close(p3[0]);
121                 close(p3[1]);
122                 tevent_req_error(req, errno);
123                 return tevent_req_post(req, ev);
124         }
125         state->pid = tfork_child_pid(state->tfork);
126         if (state->pid != 0) {
127                 /* the parent */
128                 close(p1[1]);
129                 close(p2[1]);
130                 close(p3[0]);
131                 state->fd_stdout = p1[0];
132                 state->fd_stderr = p2[0];
133                 state->fd_stdin  = p3[1];
134                 state->fd_status = tfork_event_fd(state->tfork);
135
136                 set_blocking(state->fd_stdout, false);
137                 set_blocking(state->fd_stderr, false);
138                 set_blocking(state->fd_stdin,  false);
139                 set_blocking(state->fd_status, false);
140
141                 smb_set_close_on_exec(state->fd_stdin);
142                 smb_set_close_on_exec(state->fd_stdout);
143                 smb_set_close_on_exec(state->fd_stderr);
144                 smb_set_close_on_exec(state->fd_status);
145
146                 tevent_req_set_cleanup_fn(req, samba_runcmd_cleanup_fn);
147
148                 state->fde_stdout = tevent_add_fd(ev, state,
149                                                   state->fd_stdout,
150                                                   TEVENT_FD_READ,
151                                                   samba_runcmd_io_handler,
152                                                   req);
153                 if (tevent_req_nomem(state->fde_stdout, req)) {
154                         close(state->fd_stdout);
155                         close(state->fd_stderr);
156                         close(state->fd_status);
157                         return tevent_req_post(req, ev);
158                 }
159                 tevent_fd_set_auto_close(state->fde_stdout);
160
161                 state->fde_stderr = tevent_add_fd(ev, state,
162                                                   state->fd_stderr,
163                                                   TEVENT_FD_READ,
164                                                   samba_runcmd_io_handler,
165                                                   req);
166                 if (tevent_req_nomem(state->fde_stdout, req)) {
167                         close(state->fd_stdout);
168                         close(state->fd_stderr);
169                         close(state->fd_status);
170                         return tevent_req_post(req, ev);
171                 }
172                 tevent_fd_set_auto_close(state->fde_stderr);
173
174                 state->fde_status = tevent_add_fd(ev, state,
175                                                   state->fd_status,
176                                                   TEVENT_FD_READ,
177                                                   samba_runcmd_io_handler,
178                                                   req);
179                 if (tevent_req_nomem(state->fde_stdout, req)) {
180                         close(state->fd_stdout);
181                         close(state->fd_stderr);
182                         close(state->fd_status);
183                         return tevent_req_post(req, ev);
184                 }
185                 tevent_fd_set_auto_close(state->fde_status);
186
187                 if (!timeval_is_zero(&endtime)) {
188                         tevent_req_set_endtime(req, ev, endtime);
189                 }
190
191                 return req;
192         }
193
194         /* the child */
195         close(p1[0]);
196         close(p2[0]);
197         close(p3[1]);
198         close(0);
199         close(1);
200         close(2);
201
202         /* we want to ensure that all of the network sockets we had
203            open are closed */
204         tevent_re_initialise(ev);
205
206         /* setup for logging to go to the parents debug log */
207         dup2(p3[0], 0);
208         dup2(p1[1], 1);
209         dup2(p2[1], 2);
210
211         close(p1[1]);
212         close(p2[1]);
213         close(p3[0]);
214
215         argv = str_list_copy(state, discard_const_p(const char *, argv0));
216         if (!argv) {
217                 fprintf(stderr, "Out of memory in child\n");
218                 _exit(255);
219         }
220
221         va_start(ap, argv0);
222         while (1) {
223                 const char **l;
224                 char *arg = va_arg(ap, char *);
225                 if (arg == NULL) break;
226                 l = discard_const_p(const char *, argv);
227                 l = str_list_add(l, arg);
228                 if (l == NULL) {
229                         fprintf(stderr, "Out of memory in child\n");
230                         _exit(255);
231                 }
232                 argv = discard_const_p(char *, l);
233         }
234         va_end(ap);
235
236         (void)execvp(state->arg0, argv);
237         fprintf(stderr, "Failed to exec child - %s\n", strerror(errno));
238         _exit(255);
239         return NULL;
240 }
241
242 /*
243   handle stdout/stderr from the child
244  */
245 static void samba_runcmd_io_handler(struct tevent_context *ev,
246                                     struct tevent_fd *fde,
247                                     uint16_t flags,
248                                     void *private_data)
249 {
250         struct tevent_req *req = talloc_get_type_abort(private_data,
251                                  struct tevent_req);
252         struct samba_runcmd_state *state = tevent_req_data(req,
253                                            struct samba_runcmd_state);
254         int level;
255         char *p;
256         int n, fd;
257
258         if (!(flags & TEVENT_FD_READ)) {
259                 return;
260         }
261
262         if (fde == state->fde_stdout) {
263                 level = state->stdout_log_level;
264                 fd = state->fd_stdout;
265         } else if (fde == state->fde_stderr) {
266                 level = state->stderr_log_level;
267                 fd = state->fd_stderr;
268         } else {
269                 int status;
270
271                 status = tfork_status(&state->tfork, false);
272                 if (status == -1) {
273                         if (errno == EAGAIN || errno == EWOULDBLOCK) {
274                                 return;
275                         }
276                         DBG_ERR("Bad read on status pipe\n");
277                         tevent_req_error(req, errno);
278                         return;
279                 }
280                 state->pid = -1;
281                 TALLOC_FREE(fde);
282
283                 if (WIFEXITED(status)) {
284                         status = WEXITSTATUS(status);
285                 } else if (WIFSIGNALED(status)) {
286                         status = WTERMSIG(status);
287                 } else {
288                         status = ECHILD;
289                 }
290
291                 DBG_NOTICE("Child %s exited %d\n", state->arg0, status);
292                 if (status != 0) {
293                         tevent_req_error(req, status);
294                         return;
295                 }
296
297                 tevent_req_done(req);
298                 return;
299         }
300
301         n = read(fd, &state->buf[state->buf_used],
302                  sizeof(state->buf) - state->buf_used);
303         if (n > 0) {
304                 state->buf_used += n;
305         } else if (n == 0) {
306                 if (fde == state->fde_stdout) {
307                         talloc_free(fde);
308                         state->fde_stdout = NULL;
309                         return;
310                 }
311                 if (fde == state->fde_stderr) {
312                         talloc_free(fde);
313                         state->fde_stderr = NULL;
314                         return;
315                 }
316                 return;
317         }
318
319         while (state->buf_used > 0 &&
320                (p = (char *)memchr(state->buf, '\n', state->buf_used)) != NULL) {
321                 int n1 = (p - state->buf)+1;
322                 int n2 = n1 - 1;
323                 /* swallow \r from child processes */
324                 if (n2 > 0 && state->buf[n2-1] == '\r') {
325                         n2--;
326                 }
327                 DEBUG(level,("%s: %*.*s\n", state->arg0, n2, n2, state->buf));
328                 memmove(state->buf, p+1, sizeof(state->buf) - n1);
329                 state->buf_used -= n1;
330         }
331
332         /* the buffer could have completely filled - unfortunately we have
333            no choice but to dump it out straight away */
334         if (state->buf_used == sizeof(state->buf)) {
335                 DEBUG(level,("%s: %*.*s\n",
336                              state->arg0, state->buf_used,
337                              state->buf_used, state->buf));
338                 state->buf_used = 0;
339         }
340 }
341
342 int samba_runcmd_recv(struct tevent_req *req, int *perrno)
343 {
344         if (tevent_req_is_unix_error(req, perrno)) {
345                 tevent_req_received(req);
346                 return -1;
347         }
348
349         tevent_req_received(req);
350         return 0;
351 }