8776ab7f7df8c3dc136f86598aeb07bbba646813
[debian/amanda] / xfer-src / filter-process.c
1 /*
2  * Amanda, The Advanced Maryland Automatic Network Disk Archiver
3  * Copyright (c) 2008,2009 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 "amxfer.h"
23 #include "amanda.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
54     pid_t child_pid;
55     GSource *child_watch;
56     gboolean child_killed;
57 } XferFilterProcess;
58
59 /*
60  * Class definition
61  */
62
63 typedef struct {
64     XferElementClass __parent__;
65 } XferFilterProcessClass;
66
67
68 /*
69  * Implementation
70  */
71
72 static void
73 child_watch_callback(
74     pid_t pid,
75     gint status,
76     gpointer data)
77 {
78     XferFilterProcess *self = XFER_FILTER_PROCESS(data);
79     XferElement *elt = (XferElement *)self;
80     XMsg *msg;
81     char *errmsg = NULL;
82
83     g_assert(pid == self->child_pid);
84     self->child_pid = -1; /* it's gone now.. */
85
86     if (WIFEXITED(status)) {
87         int exitcode = WEXITSTATUS(status);
88         g_debug("%s: process exited with status %d", xfer_element_repr(elt), exitcode);
89         if (exitcode != 0) {
90             errmsg = g_strdup_printf("%s exited with status %d",
91                 self->argv[0], exitcode);
92         }
93     } else if (WIFSIGNALED(status)) {
94         int signal = WTERMSIG(status);
95         if (signal != SIGKILL || !self->child_killed) {
96             errmsg = g_strdup_printf("%s died on signal %d", self->argv[0], signal);
97             g_debug("%s: %s", xfer_element_repr(elt), errmsg);
98         }
99     }
100
101     /* if this is an error exit, send an XMSG_ERROR and cancel */
102     if (errmsg) {
103         msg = xmsg_new(XFER_ELEMENT(self), XMSG_ERROR, 0);
104         msg->message = errmsg;
105         xfer_queue_message(XFER_ELEMENT(self)->xfer, msg);
106
107         xfer_cancel(elt->xfer);
108
109         /* this element is as good as cancelled already, so fall through to XMSG_DONE */
110     }
111
112     xfer_queue_message(XFER_ELEMENT(self)->xfer, xmsg_new(XFER_ELEMENT(self), XMSG_DONE, 0));
113 }
114
115 static gboolean
116 start_impl(
117     XferElement *elt)
118 {
119     XferFilterProcess *self = (XferFilterProcess *)elt;
120     char *cmd_str;
121     char **argv;
122     char *errmsg;
123     char **env;
124
125     /* first build up a log message of what we're going to do, properly shell quoted */
126     argv = self->argv;
127     cmd_str = g_shell_quote(*(argv++));
128     while (*argv) {
129         char *qarg = g_shell_quote(*(argv++));
130         cmd_str = newvstralloc(cmd_str, cmd_str, " ", qarg, NULL);
131         g_free(qarg);
132     }
133     g_debug("%s spawning: %s", xfer_element_repr(elt), cmd_str);
134
135     /* now fork off the child and connect the pipes */
136     switch (self->child_pid = fork()) {
137         case -1:
138             error("cannot fork: %s", strerror(errno));
139             /* NOTREACHED */
140
141         case 0: /* child */
142             /* set up stdin, stdout, and stderr */
143             dup2(elt->upstream->output_fd, STDIN_FILENO);
144             dup2(elt->downstream->input_fd, STDOUT_FILENO);
145             debug_dup_stderr_to_debug();
146
147             /* and close everything else */
148             safe_fd(-1, 0);
149             env = safe_env();
150
151             if (self->need_root && !become_root()) {
152                 errmsg = g_strdup_printf("could not become root: %s\n", strerror(errno));
153                 full_write(STDERR_FILENO, errmsg, strlen(errmsg));
154                 exit(1);
155             }
156
157             execve(self->argv[0], self->argv, env);
158             errmsg = g_strdup_printf("exec failed: %s\n", strerror(errno));
159             full_write(STDERR_FILENO, errmsg, strlen(errmsg));
160             exit(1);
161
162         default: /* parent */
163             break;
164     }
165     g_free(cmd_str);
166
167     /* close the pipe fd's */
168     close(elt->upstream->output_fd);
169     close(elt->downstream->input_fd);
170
171     /* watch for child death */
172     self->child_watch = new_child_watch_source(self->child_pid);
173     g_source_set_callback(self->child_watch,
174             (GSourceFunc)child_watch_callback, self, NULL);
175     g_source_attach(self->child_watch, NULL);
176     g_source_unref(self->child_watch);
177
178     return TRUE;
179 }
180
181 static gboolean
182 cancel_impl(
183     XferElement *elt,
184     gboolean expect_eof)
185 {
186     XferFilterProcess *self = (XferFilterProcess *)elt;
187
188     /* chain up first */
189     XFER_ELEMENT_CLASS(parent_class)->cancel(elt, expect_eof);
190
191     /* if the process is running as root, we can't do anything but wait until
192      * we get an upstream EOF, or downstream does something to trigger a
193      * SIGPIPE */
194     if (self->need_root)
195         return expect_eof;
196
197     /* avoid the risk of SIGPIPEs by not killing the process if it is already
198      * expecting an EOF */
199     if (expect_eof) {
200         return expect_eof;
201     }
202
203     /* and kill the process, if it's not already dead; this will likely send
204      * SIGPIPE to anything upstream. */
205     if (self->child_pid != -1) {
206         g_debug("%s: killing child process", xfer_element_repr(elt));
207         if (kill(self->child_pid, SIGKILL) < 0) {
208             /* log but ignore */
209             g_debug("while killing child process: %s", strerror(errno));
210             return FALSE; /* downstream should not expect EOF */
211         }
212
213         /* make sure we don't send an XMSG_ERROR about this */
214         self->child_killed = 1;
215     }
216
217     return TRUE; /* downstream should expect an EOF */
218 }
219
220 static void
221 instance_init(
222     XferElement *elt)
223 {
224     XferFilterProcess *self = (XferFilterProcess *)elt;
225
226     /* we can generate an EOF *unless* the process is running as root */
227     elt->can_generate_eof = !self->need_root;
228
229     self->argv = NULL;
230     self->child_pid = -1;
231     self->child_killed = FALSE;
232 }
233
234 static void
235 finalize_impl(
236     GObject * obj_self)
237 {
238     XferFilterProcess *self = XFER_FILTER_PROCESS(obj_self);
239
240     if (self->argv)
241         g_strfreev(self->argv);
242
243     /* chain up */
244     G_OBJECT_CLASS(parent_class)->finalize(obj_self);
245 }
246
247 static void
248 class_init(
249     XferFilterProcessClass * selfc)
250 {
251     XferElementClass *klass = XFER_ELEMENT_CLASS(selfc);
252     GObjectClass *goc = (GObjectClass*) klass;
253     static xfer_element_mech_pair_t mech_pairs[] = {
254         { XFER_MECH_READFD, XFER_MECH_WRITEFD, 1, 0},
255         { XFER_MECH_NONE, XFER_MECH_NONE, 0, 0},
256     };
257
258     klass->start = start_impl;
259     klass->cancel = cancel_impl;
260
261     klass->perl_class = "Amanda::Xfer::Filter::Process";
262     klass->mech_pairs = mech_pairs;
263
264     goc->finalize = finalize_impl;
265
266     parent_class = g_type_class_peek_parent(selfc);
267 }
268
269 GType
270 xfer_filter_process_get_type (void)
271 {
272     static GType type = 0;
273
274     if G_UNLIKELY(type == 0) {
275         static const GTypeInfo info = {
276             sizeof (XferFilterProcessClass),
277             (GBaseInitFunc) NULL,
278             (GBaseFinalizeFunc) NULL,
279             (GClassInitFunc) class_init,
280             (GClassFinalizeFunc) NULL,
281             NULL /* class_data */,
282             sizeof (XferFilterProcess),
283             0 /* n_preallocs */,
284             (GInstanceInitFunc) instance_init,
285             NULL
286         };
287
288         type = g_type_register_static (XFER_ELEMENT_TYPE, "XferFilterProcess", &info, 0);
289     }
290
291     return type;
292 }
293
294 /* create an element of this class; prototype is in xfer-element.h */
295 XferElement *
296 xfer_filter_process(
297     gchar **argv,
298     gboolean need_root)
299 {
300     XferFilterProcess *xfp = (XferFilterProcess *)g_object_new(XFER_FILTER_PROCESS_TYPE, NULL);
301     XferElement *elt = XFER_ELEMENT(xfp);
302
303     if (!argv || !*argv)
304         error("xfer_filter_process got a NULL or empty argv");
305
306     xfp->argv = argv;
307     xfp->need_root = need_root;
308
309     return elt;
310 }