Add dbus-run-session
[platform/upstream/dbus.git] / tools / dbus-run-session.c
1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /* dbus-run-session.c - run a child process in its own session
3  *
4  * Copyright © 2003-2006 Red Hat, Inc.
5  * Copyright © 2006 Thiago Macieira <thiago@kde.org>
6  * Copyright © 2011-2012 Nokia Corporation
7  *
8  * Licensed under the Academic Free License version 2.1
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
23  *
24  */
25
26 #include <config.h>
27
28 #include <assert.h>
29 #include <errno.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34
35 #include <sys/types.h>
36 #include <sys/wait.h>
37
38 #define MAX_ADDR_LEN 512
39 #define PIPE_READ_END  0
40 #define PIPE_WRITE_END 1
41
42 /* PROCESSES
43  *
44  * If you are in a shell and run "dbus-run-session myapp", here is what
45  * happens (compare and contrast with dbus-launch):
46  *
47  * shell
48  *   \- dbus-run-session myapp
49  *      \- dbus-daemon --nofork --print-address --session
50  *      \- myapp
51  *
52  * All processes are long-running.
53  *
54  * When myapp exits, dbus-run-session kills dbus-daemon and terminates.
55  *
56  * If dbus-daemon exits, dbus-run-session warns and continues to run.
57  *
58  * PIPES
59  *
60  * dbus-daemon --print-address -> bus_address_pipe -> d-r-s
61  */
62
63 static const char me[] = "dbus-run-session";
64
65 static void
66 usage (int ecode)
67 {
68   fprintf (stderr,
69       "%s [OPTIONS] [--] PROGRAM [ARGUMENTS]\n"
70       "%s --version\n"
71       "%s --help\n"
72       "\n"
73       "Options:\n"
74       "--dbus-daemon=BINARY       run BINARY instead of dbus-daemon\n"
75       "--config-file=FILENAME     pass to dbus-daemon instead of --session\n"
76       "\n",
77       me, me, me);
78   exit (ecode);
79 }
80
81 static void
82 version (void)
83 {
84   printf ("%s %s\n"
85           "Copyright (C) 2003-2006 Red Hat, Inc.\n"
86           "Copyright (C) 2006 Thiago Macieira\n"
87           "Copyright © 2011-2012 Nokia Corporation\n"
88           "\n"
89           "This is free software; see the source for copying conditions.\n"
90           "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n",
91           me, VERSION);
92   exit (0);
93 }
94
95 static void
96 oom (void)
97 {
98   fprintf (stderr, "%s: out of memory\n", me);
99   exit (1);
100 }
101
102 static void *
103 xmalloc (size_t bytes)
104 {
105   void *ret;
106
107   if (bytes == 0)
108     bytes = 1;
109
110   ret = malloc (bytes);
111
112   if (ret == NULL)
113     oom ();
114
115   return ret;
116 }
117
118 typedef enum
119 {
120   READ_STATUS_OK,    /**< Read succeeded */
121   READ_STATUS_ERROR, /**< Some kind of error */
122   READ_STATUS_EOF    /**< EOF returned */
123 } ReadStatus;
124
125 static ReadStatus
126 read_line (int        fd,
127            char      *buf,
128            size_t     maxlen)
129 {
130   size_t bytes = 0;
131   ReadStatus retval;
132
133   memset (buf, '\0', maxlen);
134   maxlen -= 1; /* ensure nul term */
135
136   retval = READ_STATUS_OK;
137
138   while (1)
139     {
140       ssize_t chunk;
141       size_t to_read;
142
143     again:
144       to_read = maxlen - bytes;
145
146       if (to_read == 0)
147         break;
148
149       chunk = read (fd,
150                     buf + bytes,
151                     to_read);
152       if (chunk < 0 && errno == EINTR)
153         goto again;
154
155       if (chunk < 0)
156         {
157           retval = READ_STATUS_ERROR;
158           break;
159         }
160       else if (chunk == 0)
161         {
162           retval = READ_STATUS_EOF;
163           break; /* EOF */
164         }
165       else /* chunk > 0 */
166         bytes += chunk;
167     }
168
169   if (retval == READ_STATUS_EOF &&
170       bytes > 0)
171     retval = READ_STATUS_OK;
172
173   /* whack newline */
174   if (retval != READ_STATUS_ERROR &&
175       bytes > 0 &&
176       buf[bytes-1] == '\n')
177     buf[bytes-1] = '\0';
178
179   return retval;
180 }
181
182 static void
183 exec_dbus_daemon (const char *dbus_daemon,
184                   int         bus_address_pipe[2],
185                   const char *config_file)
186 {
187   /* Child process, which execs dbus-daemon or dies trying */
188 #define MAX_FD_LEN 64
189   char write_address_fd_as_string[MAX_FD_LEN];
190
191   close (bus_address_pipe[PIPE_READ_END]);
192
193   sprintf (write_address_fd_as_string, "%d", bus_address_pipe[PIPE_WRITE_END]);
194
195   execlp (dbus_daemon,
196           dbus_daemon,
197           "--nofork",
198           "--print-address", write_address_fd_as_string,
199           config_file ? "--config-file" : "--session",
200           config_file, /* has to be last in this varargs list */
201           NULL);
202
203   fprintf (stderr, "%s: failed to execute message bus daemon '%s': %s\n",
204            me, dbus_daemon, strerror (errno));
205 }
206
207 static void
208 exec_app (int prog_arg, char **argv)
209 {
210   execvp (argv[prog_arg], argv + prog_arg);
211
212   fprintf (stderr, "%s: failed to exec '%s': %s\n", me, argv[prog_arg],
213            strerror (errno));
214   exit (1);
215 }
216
217 int
218 main (int argc, char **argv)
219 {
220   int prog_arg = 0;
221   int bus_address_pipe[2] = { 0, 0 };
222   const char *config_file = NULL;
223   const char *dbus_daemon = NULL;
224   char bus_address[MAX_ADDR_LEN] = { 0 };
225   const char *prev_arg = NULL;
226   int i = 1;
227   int requires_arg = 0;
228   pid_t bus_pid;
229   pid_t app_pid;
230   char *envvar;
231
232   while (i < argc)
233     {
234       const char *arg = argv[i];
235
236       if (requires_arg)
237         {
238           const char **arg_dest;
239
240           assert (prev_arg != NULL);
241
242           if (strcmp (prev_arg, "--config-file") == 0)
243             {
244               arg_dest = &config_file;
245             }
246           else if (strcmp (prev_arg, "--dbus-daemon") == 0)
247             {
248               arg_dest = &dbus_daemon;
249             }
250           else
251             {
252               /* shouldn't happen */
253               fprintf (stderr, "%s: internal error: %s not fully implemented\n",
254                        me, prev_arg);
255               return 127;
256             }
257
258           if (*arg_dest != NULL)
259             {
260               fprintf (stderr, "%s: %s given twice\n", me, prev_arg);
261               return 127;
262             }
263
264           *arg_dest = arg;
265           requires_arg = 0;
266           prev_arg = arg;
267           ++i;
268           continue;
269         }
270
271       if (strcmp (arg, "--help") == 0 ||
272           strcmp (arg, "-h") == 0 ||
273           strcmp (arg, "-?") == 0)
274         {
275           usage (0);
276         }
277       else if (strcmp (arg, "--version") == 0)
278         {
279           version ();
280         }
281       else if (strstr (arg, "--config-file=") == arg)
282         {
283           const char *file;
284
285           if (config_file != NULL)
286             {
287               fprintf (stderr, "%s: --config-file given twice\n", me);
288               return 127;
289             }
290
291           file = strchr (arg, '=');
292           ++file;
293
294           config_file = file;
295         }
296       else if (strstr (arg, "--dbus-daemon=") == arg)
297         {
298           const char *file;
299
300           if (dbus_daemon != NULL)
301             {
302               fprintf (stderr, "%s: --dbus-daemon given twice\n", me);
303               return 127;
304             }
305
306           file = strchr (arg, '=');
307           ++file;
308
309           dbus_daemon = file;
310         }
311       else if (strcmp (arg, "--config-file") == 0 ||
312                strcmp (arg, "--dbus-daemon") == 0)
313         {
314           requires_arg = 1;
315         }
316       else if (arg[0] == '-')
317         {
318           if (strcmp (arg, "--") != 0)
319             {
320               fprintf (stderr, "%s: option '%s' is unknown\n", me, arg);
321               return 127;
322             }
323           else
324             {
325               prog_arg = i + 1;
326               break;
327             }
328         }
329       else
330         {
331           prog_arg = i;
332           break;
333         }
334
335       prev_arg = arg;
336       ++i;
337     }
338
339   /* "dbus-run-session" and "dbus-run-session ... --" are not allowed:
340    * there must be something to run */
341   if (prog_arg < 1 || prog_arg >= argc)
342     {
343       fprintf (stderr, "%s: a non-option argument is required\n", me);
344       return 127;
345     }
346
347   if (requires_arg)
348     {
349       fprintf (stderr, "%s: option '%s' requires an argument\n", me, prev_arg);
350       return 127;
351     }
352
353   if (dbus_daemon == NULL)
354     dbus_daemon = "dbus-daemon";
355
356   if (pipe (bus_address_pipe) < 0)
357     {
358       fprintf (stderr, "%s: failed to create pipe: %s\n", me, strerror (errno));
359       return 127;
360     }
361
362   bus_pid = fork ();
363
364   if (bus_pid < 0)
365     {
366       fprintf (stderr, "%s: failed to fork: %s\n", me, strerror (errno));
367       return 127;
368     }
369
370   if (bus_pid == 0)
371     {
372       /* child */
373       exec_dbus_daemon (dbus_daemon, bus_address_pipe, config_file);
374       /* not reached */
375       return 127;
376     }
377
378   close (bus_address_pipe[PIPE_WRITE_END]);
379
380   switch (read_line (bus_address_pipe[PIPE_READ_END], bus_address, MAX_ADDR_LEN))
381     {
382     case READ_STATUS_OK:
383       break;
384
385     case READ_STATUS_EOF:
386       fprintf (stderr, "%s: EOF reading address from bus daemon\n", me);
387       return 127;
388       break;
389
390     case READ_STATUS_ERROR:
391       fprintf (stderr, "%s: error reading address from bus daemon: %s\n",
392                me, strerror (errno));
393       return 127;
394       break;
395     }
396
397   close (bus_address_pipe[PIPE_READ_END]);
398
399   envvar = xmalloc (strlen ("DBUS_SESSION_BUS_ADDRESS=") +
400                     strlen (bus_address) + 1);
401   strcpy (envvar, "DBUS_SESSION_BUS_ADDRESS=");
402   strcat (envvar, bus_address);
403   putenv (envvar);
404
405   app_pid = fork ();
406
407   if (app_pid < 0)
408     {
409       fprintf (stderr, "%s: failed to fork: %s\n", me, strerror (errno));
410       return 127;
411     }
412
413   if (app_pid == 0)
414     {
415       /* child */
416       exec_app (prog_arg, argv);
417       /* not reached */
418       return 127;
419     }
420
421   while (1)
422     {
423       int child_status;
424       pid_t child_pid = waitpid (-1, &child_status, 0);
425
426       if (child_pid == (pid_t) -1)
427         {
428           int errsv = errno;
429
430           if (errsv == EINTR)
431             continue;
432
433           /* shouldn't happen: the only other documented errors are ECHILD,
434            * which shouldn't happen because we terminate when all our children
435            * have died, and EINVAL, which would indicate programming error */
436           fprintf (stderr, "%s: waitpid() failed: %s\n", me, strerror (errsv));
437           return 127;
438         }
439       else if (child_pid == bus_pid)
440         {
441           /* no need to kill it, now */
442           bus_pid = 0;
443
444           if (WIFEXITED (child_status))
445             fprintf (stderr, "%s: dbus-daemon exited with code %d\n",
446                 me, WEXITSTATUS (child_status));
447           else if (WIFSIGNALED (child_status))
448             fprintf (stderr, "%s: dbus-daemon terminated by signal %d\n",
449                 me, WTERMSIG (child_status));
450           else
451             fprintf (stderr, "%s: dbus-daemon died or something\n", me);
452         }
453       else if (child_pid == app_pid)
454         {
455           if (bus_pid != 0)
456             kill (bus_pid, SIGTERM);
457
458           if (WIFEXITED (child_status))
459             return WEXITSTATUS (child_status);
460
461           /* if it died from a signal, behave like sh(1) */
462           if (WIFSIGNALED (child_status))
463             return 128 + WTERMSIG (child_status);
464
465           /* I give up (this should never be reached) */
466           fprintf (stderr, "%s: child process died or something\n", me);
467           return 127;
468         }
469       else
470         {
471           fprintf (stderr, "%s: ignoring unknown child process %ld\n", me,
472               (long) child_pid);
473         }
474     }
475
476   return 0;
477 }