cfe0034a32f240fd36aa5cb1b1aea6990476d883
[mat/samba.git] / lib / util / util_runcmd.c
1 /*
2    Unix SMB/CIFS mplementation.
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 <tevent.h>
31 #include "../lib/util/tevent_unix.h"
32
33 struct samba_runcmd_state {
34         int stdout_log_level;
35         int stderr_log_level;
36         struct tevent_fd *fde_stdout;
37         struct tevent_fd *fde_stderr;
38         int fd_stdout, fd_stderr;
39         char *arg0;
40         pid_t pid;
41         char buf[1024];
42         uint16_t buf_used;
43 };
44
45 static int samba_runcmd_state_destructor(struct samba_runcmd_state *state)
46 {
47         if (state->pid > 0) {
48                 kill(state->pid, SIGKILL);
49                 waitpid(state->pid, NULL, 0);
50                 state->pid = -1;
51         }
52         return 0;
53 }
54
55 static void samba_runcmd_io_handler(struct tevent_context *ev,
56                                     struct tevent_fd *fde,
57                                     uint16_t flags,
58                                     void *private_data);
59
60 /*
61   run a command as a child process, with a timeout.
62
63   any stdout/stderr from the child will appear in the Samba logs with
64   the specified log levels
65  */
66 struct tevent_req *samba_runcmd_send(TALLOC_CTX *mem_ctx,
67                                      struct tevent_context *ev,
68                                      struct timeval endtime,
69                                      int stdout_log_level,
70                                      int stderr_log_level,
71                                      const char * const *argv0, ...)
72 {
73         struct tevent_req *req;
74         struct samba_runcmd_state *state;
75         int p1[2], p2[2];
76         char **argv;
77         va_list ap;
78
79         req = tevent_req_create(mem_ctx, &state,
80                                 struct samba_runcmd_state);
81         if (req == NULL) {
82                 return NULL;
83         }
84
85         state->stdout_log_level = stdout_log_level;
86         state->stderr_log_level = stderr_log_level;
87
88         state->arg0 = talloc_strdup(state, argv0[0]);
89         if (tevent_req_nomem(state->arg0, req)) {
90                 return tevent_req_post(req, ev);
91         }
92
93         if (pipe(p1) != 0) {
94                 tevent_req_error(req, errno);
95                 return tevent_req_post(req, ev);
96         }
97         if (pipe(p2) != 0) {
98                 close(p1[0]);
99                 close(p1[1]);
100                 tevent_req_error(req, errno);
101                 return tevent_req_post(req, ev);
102         }
103
104         state->pid = fork();
105         if (state->pid == (pid_t)-1) {
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         if (state->pid != 0) {
115                 /* the parent */
116                 close(p1[1]);
117                 close(p2[1]);
118                 state->fd_stdout = p1[0];
119                 state->fd_stderr = p2[0];
120                 set_blocking(state->fd_stdout, false);
121                 set_blocking(state->fd_stderr, false);
122
123                 talloc_set_destructor(state, samba_runcmd_state_destructor);
124
125                 state->fde_stdout = tevent_add_fd(ev, state,
126                                                   state->fd_stdout,
127                                                   TEVENT_FD_READ,
128                                                   samba_runcmd_io_handler,
129                                                   req);
130                 if (tevent_req_nomem(state->fde_stdout, req)) {
131                         close(p1[0]);
132                         close(p2[0]);
133                         return tevent_req_post(req, ev);
134                 }
135                 tevent_fd_set_auto_close(state->fde_stdout);
136
137                 state->fde_stderr = tevent_add_fd(ev, state,
138                                                   state->fd_stderr,
139                                                   TEVENT_FD_READ,
140                                                   samba_runcmd_io_handler,
141                                                   req);
142                 if (tevent_req_nomem(state->fde_stdout, req)) {
143                         close(p2[0]);
144                         return tevent_req_post(req, ev);
145                 }
146                 tevent_fd_set_auto_close(state->fde_stderr);
147
148                 if (!timeval_is_zero(&endtime)) {
149                         tevent_req_set_endtime(req, ev, endtime);
150                 }
151
152                 return req;
153         }
154
155         /* the child */
156         close(p1[0]);
157         close(p2[0]);
158         close(0);
159         close(1);
160         close(2);
161
162         /* we want to ensure that all of the network sockets we had
163            open are closed */
164         tevent_re_initialise(ev);
165
166         /* setup for logging to go to the parents debug log */
167         open("/dev/null", O_RDONLY); /* for stdin */
168         dup2(p1[1], 1);
169         dup2(p2[1], 2);
170
171         argv = str_list_copy(state, discard_const_p(const char *, argv0));
172         if (!argv) {
173                 fprintf(stderr, "Out of memory in child\n");
174                 _exit(255);
175         }
176
177         va_start(ap, argv0);
178         while (1) {
179                 char *arg = va_arg(ap, char *);
180                 if (arg == NULL) break;
181                 argv = discard_const_p(char *, str_list_add((const char **)argv, arg));
182                 if (!argv) {
183                         fprintf(stderr, "Out of memory in child\n");
184                         _exit(255);
185                 }
186         }
187         va_end(ap);
188
189         (void)execvp(state->arg0, argv);
190         fprintf(stderr, "Failed to exec child - %s\n", strerror(errno));
191         _exit(255);
192         return NULL;
193 }
194
195 /*
196   handle stdout/stderr from the child
197  */
198 static void samba_runcmd_io_handler(struct tevent_context *ev,
199                                     struct tevent_fd *fde,
200                                     uint16_t flags,
201                                     void *private_data)
202 {
203         struct tevent_req *req = talloc_get_type_abort(private_data,
204                                  struct tevent_req);
205         struct samba_runcmd_state *state = tevent_req_data(req,
206                                            struct samba_runcmd_state);
207         int level;
208         char *p;
209         int n, fd;
210
211         if (fde == state->fde_stdout) {
212                 level = state->stdout_log_level;
213                 fd = state->fd_stdout;
214         } else {
215                 level = state->stderr_log_level;
216                 fd = state->fd_stderr;
217         }
218
219         if (!(flags & TEVENT_FD_READ)) {
220                 return;
221         }
222
223         n = read(fd, &state->buf[state->buf_used],
224                  sizeof(state->buf) - state->buf_used);
225         if (n > 0) {
226                 state->buf_used += n;
227         } else if (n == 0) {
228                 if (fde == state->fde_stdout) {
229                         talloc_free(fde);
230                         state->fde_stdout = NULL;
231                 }
232                 if (fde == state->fde_stderr) {
233                         talloc_free(fde);
234                         state->fde_stderr = NULL;
235                 }
236                 if (state->fde_stdout == NULL &&
237                     state->fde_stderr == NULL) {
238                         int status;
239                         /* the child has closed both stdout and
240                          * stderr, assume its dead */
241                         pid_t pid = waitpid(state->pid, &status, 0);
242                         if (pid != state->pid) {
243                                 if (errno == ECHILD) {
244                                         /* this happens when the
245                                            parent has set SIGCHLD to
246                                            SIG_IGN. In that case we
247                                            can only get error
248                                            information for the child
249                                            via its logging. We should
250                                            stop using SIG_IGN on
251                                            SIGCHLD in the standard
252                                            process model.
253                                         */
254                                         tevent_req_done(req);
255                                         return;
256                                 }
257                                 DEBUG(0,("Error in waitpid() for child %s - %s \n",
258                                          state->arg0, strerror(errno)));
259                                 if (errno == 0) {
260                                         errno = ECHILD;
261                                 }
262                                 tevent_req_error(req, errno);
263                                 return;
264                         }
265                         status = WEXITSTATUS(status);
266                         DEBUG(3,("Child %s exited with status %d - %s\n",
267                                  state->arg0, status, strerror(status)));
268                         if (status != 0) {
269                                 tevent_req_error(req, status);
270                                 return;
271                         }
272
273                         tevent_req_done(req);
274                         return;
275                 }
276                 return;
277         }
278
279         while (state->buf_used > 0 &&
280                (p = (char *)memchr(state->buf, '\n', state->buf_used)) != NULL) {
281                 int n1 = (p - state->buf)+1;
282                 int n2 = n1 - 1;
283                 /* swallow \r from child processes */
284                 if (n2 > 0 && state->buf[n2-1] == '\r') {
285                         n2--;
286                 }
287                 DEBUG(level,("%s: %*.*s\n", state->arg0, n2, n2, state->buf));
288                 memmove(state->buf, p+1, sizeof(state->buf) - n1);
289                 state->buf_used -= n1;
290         }
291
292         /* the buffer could have completely filled - unfortunately we have
293            no choice but to dump it out straight away */
294         if (state->buf_used == sizeof(state->buf)) {
295                 DEBUG(level,("%s: %*.*s\n",
296                              state->arg0, state->buf_used,
297                              state->buf_used, state->buf));
298                 state->buf_used = 0;
299         }
300 }
301
302 int samba_runcmd_recv(struct tevent_req *req, int *perrno)
303 {
304         if (tevent_req_is_unix_error(req, perrno)) {
305                 tevent_req_received(req);
306                 return -1;
307         }
308
309         tevent_req_received(req);
310         return 0;
311 }