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