]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blob - src/process.c
e4e931476db7e6a60d1cdee7323d85667859e740
[sojka/lightdm.git] / src / process.c
1 /*
2  * Copyright (C) 2010-2011 Robert Ancell.
3  * Author: Robert Ancell <robert.ancell@canonical.com>
4  * 
5  * This program is free software: you can redistribute it and/or modify it under
6  * the terms of the GNU General Public License as published by the Free Software
7  * Foundation, either version 3 of the License, or (at your option) any later
8  * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
9  * license.
10  */
11
12 #include <stdlib.h>
13 #include <string.h>
14 #include <unistd.h>
15 #include <errno.h>
16 #include <sys/wait.h>
17 #include <fcntl.h>
18 #include <signal.h>
19 #include <grp.h>
20 #include <glib/gstdio.h>
21
22 #include "process.h"
23
24 enum {
25     RUN,
26     STARTED,
27     GOT_DATA,
28     GOT_SIGNAL,  
29     EXITED,
30     TERMINATED,
31     STOPPED,
32     LAST_SIGNAL
33 };
34 static guint signals[LAST_SIGNAL] = { 0 };
35
36 struct ProcessPrivate
37 {  
38     /* Environment variables */
39     GHashTable *env;
40
41     /* Command to run */
42     gchar *command;
43
44     /* Working directory */
45     gchar *working_directory;
46
47     /* User to run as */
48     User *user;
49
50     /* Path of file to log to */
51     gchar *log_file; 
52  
53     /* Process ID */
54     GPid pid;
55
56     /* Timeout waiting for process to quit */
57     guint quit_timeout;
58 };
59
60 G_DEFINE_TYPE (Process, process, G_TYPE_OBJECT);
61
62 static Process *current_process = NULL;
63 static GHashTable *processes = NULL;
64 static int signal_pipe[2];
65
66 Process *
67 process_get_current (void)
68 {
69     if (current_process)
70         return current_process;
71
72     current_process = process_new ();
73     current_process->priv->pid = getpid ();
74
75     return current_process;
76 }
77
78 Process *
79 process_new (void)
80 {
81     return g_object_new (PROCESS_TYPE, NULL);
82 }
83
84 void
85 process_set_command (Process *process, const gchar *command)
86 {
87     g_return_if_fail (process != NULL);
88
89     g_free (process->priv->command);
90     process->priv->command = g_strdup (command);
91 }
92
93 const gchar *
94 process_get_command (Process *process)
95 {
96     g_return_val_if_fail (process != NULL, NULL);
97     return process->priv->command;
98 }
99
100 void
101 process_set_log_file (Process *process, const gchar *log_file)
102 {
103     g_return_if_fail (process != NULL);
104
105     g_free (process->priv->log_file);
106     process->priv->log_file = g_strdup (log_file);
107 }
108
109 const gchar *
110 process_get_log_file (Process *process)
111 {
112     g_return_val_if_fail (process != NULL, NULL);
113     return process->priv->log_file;
114 }
115
116 void
117 process_set_working_directory (Process *process, const gchar *working_directory)
118 {
119     g_return_if_fail (process != NULL);
120
121     g_free (process->priv->working_directory);
122     process->priv->working_directory = g_strdup (working_directory);
123 }
124
125 const gchar *
126 process_get_working_directory (Process *process)
127 {
128     g_return_val_if_fail (process != NULL, NULL);
129     return process->priv->working_directory;
130 }
131
132 void
133 process_set_user (Process *process, User *user)
134 {
135     g_return_if_fail (process != NULL);
136
137     if (process->priv->user)
138         g_object_unref (process->priv->user);
139     process->priv->user = g_object_ref (user);
140 }
141
142 User *
143 process_get_user (Process *process)
144 {
145     g_return_val_if_fail (process != NULL, NULL);
146     return process->priv->user;
147 }
148
149 void
150 process_set_env (Process *process, const gchar *name, const gchar *value)
151 {
152     g_return_if_fail (process != NULL);
153     g_hash_table_insert (process->priv->env, g_strdup (name), g_strdup (value));
154 }
155
156 const gchar *
157 process_get_env (Process *process, const gchar *name)
158 {
159     g_return_val_if_fail (process != NULL, FALSE);
160     return g_hash_table_lookup (process->priv->env, name);
161 }
162
163 static void
164 process_watch_cb (GPid pid, gint status, gpointer data)
165 {
166     Process *process = data;
167
168     if (WIFEXITED (status))
169     {
170         g_debug ("Process %d exited with return value %d", pid, WEXITSTATUS (status));
171         g_signal_emit (process, signals[EXITED], 0, WEXITSTATUS (status));
172     }
173     else if (WIFSIGNALED (status))
174     {
175         g_debug ("Process %d terminated with signal %d", pid, WTERMSIG (status));
176         g_signal_emit (process, signals[TERMINATED], 0, WTERMSIG (status));
177     }
178
179     if (process->priv->quit_timeout)
180         g_source_remove (process->priv->quit_timeout);
181     process->priv->quit_timeout = 0;  
182     process->priv->pid = 0;
183     g_hash_table_remove (processes, GINT_TO_POINTER (pid));
184
185     g_signal_emit (process, signals[STOPPED], 0);
186 }
187
188 static void
189 process_run (Process *process)
190 {
191     gint argc;
192     gchar **argv;
193     GError *error = NULL;
194
195     if (!g_shell_parse_argv (process->priv->command, &argc, &argv, &error))
196     {
197         g_warning ("Error parsing command %s: %s", process->priv->command, error->message);
198         _exit (EXIT_FAILURE);
199     }
200
201     execv (argv[0], argv);
202
203     g_warning ("Error executing child process %s: %s", argv[0], g_strerror (errno));
204     _exit (EXIT_FAILURE);
205 }
206
207 static void
208 run (Process *process)
209 {
210     GHashTableIter iter;
211     gpointer key, value;
212
213     /* FIXME: Close existing file descriptors */
214
215     /* Set environment */
216     clearenv ();
217     g_hash_table_iter_init (&iter, process->priv->env);
218     while (g_hash_table_iter_next (&iter, &key, &value))
219         g_setenv ((gchar *)key, (gchar *)value, TRUE);
220
221     /* Make this process its own session */
222     if (setsid () < 0)
223         g_warning ("Failed to make process a new session: %s", strerror (errno));
224
225     if (process->priv->user)
226     {
227         if (getuid () == 0)
228         {
229             if (initgroups (user_get_name (process->priv->user), user_get_gid (process->priv->user)) < 0)
230             {
231                 g_warning ("Failed to initialize supplementary groups for %s: %s", user_get_name (process->priv->user), strerror (errno));
232                 _exit (EXIT_FAILURE);
233             }
234
235             if (setgid (user_get_gid (process->priv->user)) != 0)
236             {
237                 g_warning ("Failed to set group ID to %d: %s", user_get_gid (process->priv->user), strerror (errno));
238                 _exit (EXIT_FAILURE);
239             }
240
241             if (setuid (user_get_uid (process->priv->user)) != 0)
242             {
243                 g_warning ("Failed to set user ID to %d: %s", user_get_uid (process->priv->user), strerror (errno));
244                 _exit (EXIT_FAILURE);
245             }
246         }
247
248         if (chdir (user_get_home_directory (process->priv->user)) != 0)
249         {
250             g_warning ("Failed to change to home directory %s: %s", user_get_home_directory (process->priv->user), strerror (errno));
251             _exit (EXIT_FAILURE);
252         }
253     }
254   
255     /* Redirect output to logfile */
256     if (process->priv->log_file)
257     {
258          int fd;
259
260          fd = g_open (process->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
261          if (fd < 0)
262              g_warning ("Failed to open log file %s: %s", process->priv->log_file, g_strerror (errno));
263          else
264          {
265              dup2 (fd, STDOUT_FILENO);
266              dup2 (fd, STDERR_FILENO);
267              close (fd);
268          }
269     }
270
271     g_signal_emit (process, signals[RUN], 0); 
272 }
273
274 gboolean
275 process_start (Process *process)
276 {
277     GString *string;
278     gpointer key, value;
279     GHashTableIter iter;
280     pid_t pid;
281
282     g_return_val_if_fail (process != NULL, FALSE);
283     g_return_val_if_fail (process->priv->command != NULL, FALSE);  
284     g_return_val_if_fail (process->priv->pid == 0, FALSE);
285
286     /* Create the log file owned by the target user */
287     if (process->priv->log_file)
288     {
289         gint fd = g_open (process->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
290         close (fd);
291         if (getuid () == 0 && chown (process->priv->log_file, user_get_uid (process->priv->user), user_get_gid (process->priv->user)) != 0)
292             g_warning ("Failed to set process log file ownership: %s", strerror (errno));
293     }
294
295     pid = fork ();
296     if (pid < 0)
297     {
298         g_warning ("Failed to fork: %s", strerror (errno));
299         return FALSE;
300     }
301
302     if (pid == 0)
303         run (process);
304
305     string = g_string_new ("");
306     g_hash_table_iter_init (&iter, process->priv->env);
307     while (g_hash_table_iter_next (&iter, &key, &value))
308         g_string_append_printf (string, "%s=%s ", (gchar *)key, (gchar *)value);
309     g_string_append (string, process->priv->command);
310     g_debug ("Launching process %d: %s", pid, string->str);
311     g_string_free (string, TRUE);
312
313     process->priv->pid = pid;
314
315     g_hash_table_insert (processes, GINT_TO_POINTER (process->priv->pid), g_object_ref (process));
316     g_child_watch_add (process->priv->pid, process_watch_cb, process);
317
318     g_signal_emit (process, signals[STARTED], 0);  
319
320     return TRUE;
321 }
322
323 gboolean
324 process_get_is_running (Process *process)
325 {
326     g_return_val_if_fail (process != NULL, FALSE);
327     return process->priv->pid != 0;
328 }
329
330 GPid
331 process_get_pid (Process *process)
332 {
333     g_return_val_if_fail (process != NULL, 0);
334     return process->priv->pid;
335 }
336
337 void
338 process_signal (Process *process, int signum)
339 {
340     g_return_if_fail (process != NULL);
341
342     if (process->priv->pid == 0)
343         return;
344
345     g_debug ("Sending signal %d to process %d", signum, process->priv->pid);
346
347     if (kill (process->priv->pid, signum) < 0)
348         g_warning ("Error sending signal %d to process %d: %s", signum, process->priv->pid, strerror (errno));
349 }
350
351 static gboolean
352 quit_timeout_cb (Process *process)
353 {
354     process->priv->quit_timeout = 0;
355     process_signal (process, SIGKILL);
356     return FALSE;
357 }
358
359 void
360 process_stop (Process *process)
361 {
362     /* Send SIGTERM, and then SIGKILL if no response */
363     process->priv->quit_timeout = g_timeout_add (5000, (GSourceFunc) quit_timeout_cb, process);
364     process_signal (process, SIGTERM);
365 }
366
367 static void
368 process_init (Process *process)
369 {
370     process->priv = G_TYPE_INSTANCE_GET_PRIVATE (process, PROCESS_TYPE, ProcessPrivate);
371     process->priv->env = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
372 }
373
374 static void
375 process_stopped (Process *process)
376 {
377 }
378
379 static void
380 process_finalize (GObject *object)
381 {
382     Process *self;
383
384     self = PROCESS (object);
385
386     if (self->priv->pid > 0)
387         g_hash_table_remove (processes, GINT_TO_POINTER (self->priv->pid));
388
389     g_free (self->priv->command);
390     g_free (self->priv->working_directory);
391     g_free (self->priv->log_file);
392     if (self->priv->user)
393         g_object_unref (self->priv->user);
394
395     if (self->priv->pid)
396         kill (self->priv->pid, SIGTERM);
397
398     g_hash_table_unref (self->priv->env);
399
400     G_OBJECT_CLASS (process_parent_class)->finalize (object);
401 }
402
403 static void
404 signal_cb (int signum, siginfo_t *info, void *data)
405 {
406     /* NOTE: Using g_printerr as can't call g_warning from a signal callback */
407     if (write (signal_pipe[1], &info->si_signo, sizeof (int)) < 0 ||
408         write (signal_pipe[1], &info->si_pid, sizeof (pid_t)) < 0)
409         g_printerr ("Failed to write to signal pipe: %s", strerror (errno));
410 }
411
412 static gboolean
413 handle_signal (GIOChannel *source, GIOCondition condition, gpointer data)
414 {
415     int signo;
416     pid_t pid;
417     Process *process;
418
419     if (read (signal_pipe[0], &signo, sizeof (int)) < 0 || 
420         read (signal_pipe[0], &pid, sizeof (pid_t)) < 0)
421     {
422         g_warning ("Error reading from signal pipe: %s", strerror (errno));
423         return TRUE;
424     }
425
426     g_debug ("Got signal %d from process %d", signo, pid);
427
428     process = g_hash_table_lookup (processes, GINT_TO_POINTER (pid));
429     if (process == NULL)
430         process = process_get_current ();
431     if (process)
432         g_signal_emit (process, signals[GOT_SIGNAL], 0, signo);
433
434     return TRUE;
435 }
436
437 static void
438 process_class_init (ProcessClass *klass)
439 {
440     GObjectClass *object_class = G_OBJECT_CLASS (klass);
441     struct sigaction action;
442
443     klass->run = process_run;
444     klass->stopped = process_stopped;
445     object_class->finalize = process_finalize;  
446
447     g_type_class_add_private (klass, sizeof (ProcessPrivate));
448
449     signals[RUN] =
450         g_signal_new ("run",
451                       G_TYPE_FROM_CLASS (klass),
452                       G_SIGNAL_RUN_LAST,
453                       G_STRUCT_OFFSET (ProcessClass, run),
454                       NULL, NULL,
455                       g_cclosure_marshal_VOID__VOID,
456                       G_TYPE_NONE, 0); 
457     signals[STARTED] =
458         g_signal_new ("started",
459                       G_TYPE_FROM_CLASS (klass),
460                       G_SIGNAL_RUN_LAST,
461                       G_STRUCT_OFFSET (ProcessClass, started),
462                       NULL, NULL,
463                       g_cclosure_marshal_VOID__VOID,
464                       G_TYPE_NONE, 0); 
465     signals[GOT_DATA] =
466         g_signal_new ("got-data",
467                       G_TYPE_FROM_CLASS (klass),
468                       G_SIGNAL_RUN_LAST,
469                       G_STRUCT_OFFSET (ProcessClass, got_data),
470                       NULL, NULL,
471                       g_cclosure_marshal_VOID__VOID,
472                       G_TYPE_NONE, 0); 
473     signals[GOT_SIGNAL] =
474         g_signal_new ("got-signal",
475                       G_TYPE_FROM_CLASS (klass),
476                       G_SIGNAL_RUN_LAST,
477                       G_STRUCT_OFFSET (ProcessClass, got_signal),
478                       NULL, NULL,
479                       g_cclosure_marshal_VOID__INT,
480                       G_TYPE_NONE, 1, G_TYPE_INT);
481     signals[EXITED] =
482         g_signal_new ("exited",
483                       G_TYPE_FROM_CLASS (klass),
484                       G_SIGNAL_RUN_LAST,
485                       G_STRUCT_OFFSET (ProcessClass, exited),
486                       NULL, NULL,
487                       g_cclosure_marshal_VOID__INT,
488                       G_TYPE_NONE, 1, G_TYPE_INT);
489     signals[TERMINATED] =
490         g_signal_new ("terminated",
491                       G_TYPE_FROM_CLASS (klass),
492                       G_SIGNAL_RUN_LAST,
493                       G_STRUCT_OFFSET (ProcessClass, terminated),
494                       NULL, NULL,
495                       g_cclosure_marshal_VOID__INT,
496                       G_TYPE_NONE, 1, G_TYPE_INT);
497     signals[STOPPED] =
498         g_signal_new ("stopped",
499                       G_TYPE_FROM_CLASS (klass),
500                       G_SIGNAL_RUN_LAST,
501                       G_STRUCT_OFFSET (ProcessClass, stopped),
502                       NULL, NULL,
503                       g_cclosure_marshal_VOID__VOID,
504                       G_TYPE_NONE, 0);
505
506     /* Catch signals and feed them to the main loop via a pipe */
507     processes = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
508     if (pipe (signal_pipe) != 0)
509         g_critical ("Failed to create signal pipe");
510     g_io_add_watch (g_io_channel_unix_new (signal_pipe[0]), G_IO_IN, handle_signal, NULL);
511     action.sa_sigaction = signal_cb;
512     sigemptyset (&action.sa_mask);
513     action.sa_flags = SA_SIGINFO;
514     sigaction (SIGTERM, &action, NULL);
515     sigaction (SIGINT, &action, NULL);
516     sigaction (SIGHUP, &action, NULL);
517     sigaction (SIGUSR1, &action, NULL);
518     sigaction (SIGUSR2, &action, NULL);
519 }