Imported Upstream version 3.2.0
[debian/amanda] / xfer-src / filter-process.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 2008, 2009, 2010 Zmanda, Inc.  All Rights Reserved.
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 as published
7  * by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
17  *
18  * Contact information: Zmanda Inc., 465 S. Mathilda Ave., Suite 300
19  * Sunnyvale, CA 94085, USA, or: http://www.zmanda.com
20  */
21
22 #include "amanda.h"
23 #include "amxfer.h"
24 #include "event.h"
25 #include "util.h"
26
27 /*
28  * Class declaration
29  *
30  * This declaration is entirely private; nothing but xfer_filter_process() references
31  * it directly.
32  */
33
34 GType xfer_filter_process_get_type(void);
35 #define XFER_FILTER_PROCESS_TYPE (xfer_filter_process_get_type())
36 #define XFER_FILTER_PROCESS(obj) G_TYPE_CHECK_INSTANCE_CAST((obj), xfer_filter_process_get_type(), XferFilterProcess)
37 #define XFER_FILTER_PROCESS_CONST(obj) G_TYPE_CHECK_INSTANCE_CAST((obj), xfer_filter_process_get_type(), XferFilterProcess const)
38 #define XFER_FILTER_PROCESS_CLASS(klass) G_TYPE_CHECK_CLASS_CAST((klass), xfer_filter_process_get_type(), XferFilterProcessClass)
39 #define IS_XFER_FILTER_PROCESS(obj) G_TYPE_CHECK_INSTANCE_TYPE((obj), xfer_filter_process_get_type ())
40 #define XFER_FILTER_PROCESS_GET_CLASS(obj) G_TYPE_INSTANCE_GET_CLASS((obj), xfer_filter_process_get_type(), XferFilterProcessClass)
41
42 static GObjectClass *parent_class = NULL;
43
44 /*
45  * Main object structure
46  */
47
48 typedef struct XferFilterProcess {
49     XferElement __parent__;
50
51     gchar **argv;
52     gboolean need_root;
53     gboolean log_stderr;
54
55     pid_t child_pid;
56     GSource *child_watch;
57     gboolean child_killed;
58 } XferFilterProcess;
59
60 /*
61  * Class definition
62  */
63
64 typedef struct {
65     XferElementClass __parent__;
66 } XferFilterProcessClass;
67
68
69 /*
70  * Implementation
71  */
72
73 static void
74 child_watch_callback(
75     pid_t pid,
76     gint status,
77     gpointer data)
78 {
79     XferFilterProcess *self = XFER_FILTER_PROCESS(data);
80     XferElement *elt = (XferElement *)self;
81     XMsg *msg;
82     char *errmsg = NULL;
83
84     g_assert(pid == self->child_pid);
85     self->child_pid = -1; /* it's gone now.. */
86
87     if (WIFEXITED(status)) {
88         int exitcode = WEXITSTATUS(status);
89         g_debug("%s: process exited with status %d", xfer_element_repr(elt), exitcode);
90         if (exitcode != 0) {
91             errmsg = g_strdup_printf("%s exited with status %d",
92                 self->argv[0], exitcode);
93         }
94     } else if (WIFSIGNALED(status)) {
95         int signal = WTERMSIG(status);
96         if (signal != SIGKILL || !self->child_killed) {
97             errmsg = g_strdup_printf("%s died on signal %d", self->argv[0], signal);
98             g_debug("%s: %s", xfer_element_repr(elt), errmsg);
99         }
100     }
101
102     /* if this is an error exit, send an XMSG_ERROR and cancel */
103     if (errmsg) {
104         msg = xmsg_new(XFER_ELEMENT(self), XMSG_ERROR, 0);
105         msg->message = errmsg;
106         xfer_queue_message(XFER_ELEMENT(self)->xfer, msg);
107
108         xfer_cancel(elt->xfer);
109
110         /* this element is as good as cancelled already, so fall through to XMSG_DONE */
111     }
112
113     xfer_queue_message(XFER_ELEMENT(self)->xfer, xmsg_new(XFER_ELEMENT(self), XMSG_DONE, 0));
114 }
115
116 static gboolean
117 start_impl(
118     XferElement *elt)
119 {
120     XferFilterProcess *self = (XferFilterProcess *)elt;
121     char *cmd_str;
122     char **argv;
123     char *errmsg;
124     char **env;
125     int rfd, wfd;
126
127     /* first build up a log message of what we're going to do, properly shell quoted */
128     argv = self->argv;
129     cmd_str = g_shell_quote(*(argv++));
130     while (*argv) {
131         char *qarg = g_shell_quote(*(argv++));
132         cmd_str = newvstralloc(cmd_str, cmd_str, " ", qarg, NULL);
133         g_free(qarg);
134     }
135     g_debug("%s spawning: %s", xfer_element_repr(elt), cmd_str);
136
137     rfd = xfer_element_swap_output_fd(elt->upstream, -1);
138     wfd = xfer_element_swap_input_fd(elt->downstream, -1);
139
140     /* now fork off the child and connect the pipes */
141     switch (self->child_pid = fork()) {
142         case -1:
143             error("cannot fork: %s", strerror(errno));
144             /* NOTREACHED */
145
146         case 0: /* child */
147             /* first, copy our fd's out of the stdio range */
148             while (rfd <= STDERR_FILENO)
149                 rfd = dup(rfd);
150             while (wfd <= STDERR_FILENO)
151                 wfd = dup(wfd);
152
153             /* set up stdin, stdout, and stderr, overwriting anything already open
154              * on those fd's */
155             dup2(rfd, STDIN_FILENO);
156             dup2(wfd, STDOUT_FILENO);
157             if (!self->log_stderr)
158                 debug_dup_stderr_to_debug();
159
160             /* and close everything else */
161             safe_fd(-1, 0);
162             env = safe_env();
163
164             if (self->need_root && !become_root()) {
165                 errmsg = g_strdup_printf("could not become root: %s\n", strerror(errno));
166                 full_write(STDERR_FILENO, errmsg, strlen(errmsg));
167                 exit(1);
168             }
169
170             execve(self->argv[0], self->argv, env);
171             errmsg = g_strdup_printf("exec failed: %s\n", strerror(errno));
172             full_write(STDERR_FILENO, errmsg, strlen(errmsg));
173             exit(1);
174
175         default: /* parent */
176             break;
177     }
178     g_free(cmd_str);
179
180     /* close the pipe fd's */
181     close(rfd);
182     close(wfd);
183
184     /* watch for child death */
185     self->child_watch = new_child_watch_source(self->child_pid);
186     g_source_set_callback(self->child_watch,
187             (GSourceFunc)child_watch_callback, self, NULL);
188     g_source_attach(self->child_watch, NULL);
189     g_source_unref(self->child_watch);
190
191     return TRUE;
192 }
193
194 static gboolean
195 cancel_impl(
196     XferElement *elt,
197     gboolean expect_eof)
198 {
199     XferFilterProcess *self = (XferFilterProcess *)elt;
200
201     /* chain up first */
202     XFER_ELEMENT_CLASS(parent_class)->cancel(elt, expect_eof);
203
204     /* if the process is running as root, we can't do anything but wait until
205      * we get an upstream EOF, or downstream does something to trigger a
206      * SIGPIPE */
207     if (self->need_root)
208         return expect_eof;
209
210     /* avoid the risk of SIGPIPEs by not killing the process if it is already
211      * expecting an EOF */
212     if (expect_eof) {
213         return expect_eof;
214     }
215
216     /* and kill the process, if it's not already dead; this will likely send
217      * SIGPIPE to anything upstream. */
218     if (self->child_pid != -1) {
219         g_debug("%s: killing child process", xfer_element_repr(elt));
220         if (kill(self->child_pid, SIGKILL) < 0) {
221             /* log but ignore */
222             g_debug("while killing child process: %s", strerror(errno));
223             return FALSE; /* downstream should not expect EOF */
224         }
225
226         /* make sure we don't send an XMSG_ERROR about this */
227         self->child_killed = 1;
228     }
229
230     return TRUE; /* downstream should expect an EOF */
231 }
232
233 static void
234 instance_init(
235     XferElement *elt)
236 {
237     XferFilterProcess *self = (XferFilterProcess *)elt;
238
239     /* we can generate an EOF *unless* the process is running as root */
240     elt->can_generate_eof = !self->need_root;
241
242     self->argv = NULL;
243     self->child_pid = -1;
244     self->child_killed = FALSE;
245 }
246
247 static void
248 finalize_impl(
249     GObject * obj_self)
250 {
251     XferFilterProcess *self = XFER_FILTER_PROCESS(obj_self);
252
253     if (self->argv)
254         g_strfreev(self->argv);
255
256     /* chain up */
257     G_OBJECT_CLASS(parent_class)->finalize(obj_self);
258 }
259
260 static void
261 class_init(
262     XferFilterProcessClass * selfc)
263 {
264     XferElementClass *klass = XFER_ELEMENT_CLASS(selfc);
265     GObjectClass *goc = (GObjectClass*) klass;
266     static xfer_element_mech_pair_t mech_pairs[] = {
267         { XFER_MECH_READFD, XFER_MECH_WRITEFD, 1, 0},
268         { XFER_MECH_NONE, XFER_MECH_NONE, 0, 0},
269     };
270
271     klass->start = start_impl;
272     klass->cancel = cancel_impl;
273
274     klass->perl_class = "Amanda::Xfer::Filter::Process";
275     klass->mech_pairs = mech_pairs;
276
277     goc->finalize = finalize_impl;
278
279     parent_class = g_type_class_peek_parent(selfc);
280 }
281
282 GType
283 xfer_filter_process_get_type (void)
284 {
285     static GType type = 0;
286
287     if G_UNLIKELY(type == 0) {
288         static const GTypeInfo info = {
289             sizeof (XferFilterProcessClass),
290             (GBaseInitFunc) NULL,
291             (GBaseFinalizeFunc) NULL,
292             (GClassInitFunc) class_init,
293             (GClassFinalizeFunc) NULL,
294             NULL /* class_data */,
295             sizeof (XferFilterProcess),
296             0 /* n_preallocs */,
297             (GInstanceInitFunc) instance_init,
298             NULL
299         };
300
301         type = g_type_register_static (XFER_ELEMENT_TYPE, "XferFilterProcess", &info, 0);
302     }
303
304     return type;
305 }
306
307 /* create an element of this class; prototype is in xfer-element.h */
308 XferElement *
309 xfer_filter_process(
310     gchar **argv,
311     gboolean need_root,
312     gboolean log_stderr)
313 {
314     XferFilterProcess *xfp = (XferFilterProcess *)g_object_new(XFER_FILTER_PROCESS_TYPE, NULL);
315     XferElement *elt = XFER_ELEMENT(xfp);
316
317     if (!argv || !*argv)
318         error("xfer_filter_process got a NULL or empty argv");
319
320     xfp->argv = argv;
321     xfp->need_root = need_root;
322     xfp->log_stderr = log_stderr;
323
324     return elt;
325 }