~lightdm-team/lightdm/1.4

352 by robert.ancell at canonical
Split child process code out from session/xserver
1
/*
494 by Robert Ancell
Update copyright year
2
 * Copyright (C) 2010-2011 Robert Ancell.
352 by robert.ancell at canonical
Split child process code out from session/xserver
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 <pwd.h>
20
#include <grp.h>
21
#include <glib/gstdio.h>
22
23
#include "child-process.h"
24
25
enum {
353 by robert.ancell at canonical
Use a private pipe for greeter<->server communication instead of D-Bus (needs to be fixed in liblightdm-qt)
26
    GOT_DATA,
352 by robert.ancell at canonical
Split child process code out from session/xserver
27
    GOT_SIGNAL,  
28
    EXITED,
29
    TERMINATED,
30
    LAST_SIGNAL
31
};
32
static guint signals[LAST_SIGNAL] = { 0 };
33
34
struct ChildProcessPrivate
35
{  
36
    /* Environment variables */
37
    GHashTable *env;
38
39
    /* User to run as */
40
    gchar *username;
41
    uid_t uid;
42
    gid_t gid;
472 by Robert Ancell
Set working directory when logging in
43
    gchar *home_dir;
352 by robert.ancell at canonical
Split child process code out from session/xserver
44
45
    /* Path of file to log to */
46
    gchar *log_file;
353 by robert.ancell at canonical
Use a private pipe for greeter<->server communication instead of D-Bus (needs to be fixed in liblightdm-qt)
47
  
48
    /* Pipes to communicate with */
49
    GIOChannel *to_child_channel;
50
    GIOChannel *from_child_channel;
352 by robert.ancell at canonical
Split child process code out from session/xserver
51
52
    /* Process ID */
53
    GPid pid;
54
};
55
56
G_DEFINE_TYPE (ChildProcess, child_process, G_TYPE_OBJECT);
57
58
static ChildProcess *parent_process = NULL;
59
static GHashTable *processes = NULL;
60
static int signal_pipe[2];
61
62
ChildProcess *
63
child_process_get_parent (void)
64
{
65
    if (parent_process)
66
        return parent_process;
67
68
    parent_process = child_process_new ();
69
    parent_process->priv->pid = getpid ();
70
71
    return parent_process;
72
}
73
74
ChildProcess *
75
child_process_new (void)
76
{
77
    return g_object_new (CHILD_PROCESS_TYPE, NULL);
78
}
79
80
void
81
child_process_set_log_file (ChildProcess *process, const gchar *log_file)
82
{
83
    g_free (process->priv->log_file);
84
    process->priv->log_file = g_strdup (log_file);
85
}
86
87
const gchar *
88
child_process_get_log_file (ChildProcess *process)
89
{
90
    return process->priv->log_file;
91
}
92
  
93
void
94
child_process_set_env (ChildProcess *process, const gchar *name, const gchar *value)
95
{
96
    g_hash_table_insert (process->priv->env, g_strdup (name), g_strdup (value));
97
}
98
99
static void
100
child_process_watch_cb (GPid pid, gint status, gpointer data)
101
{
102
    ChildProcess *process = data;
103
104
    if (WIFEXITED (status))
105
    {
106
        g_debug ("Process %d exited with return value %d", pid, WEXITSTATUS (status));
107
        g_signal_emit (process, signals[EXITED], 0, WEXITSTATUS (status));
108
    }
109
    else if (WIFSIGNALED (status))
110
    {
111
        g_debug ("Process %d terminated with signal %d", pid, WTERMSIG (status));
112
        g_signal_emit (process, signals[TERMINATED], 0, WTERMSIG (status));
113
    }
114
115
    g_hash_table_remove (processes, GINT_TO_POINTER (process->priv->pid));
116
    process->priv->pid = 0;
117
}
118
119
static void
353 by robert.ancell at canonical
Use a private pipe for greeter<->server communication instead of D-Bus (needs to be fixed in liblightdm-qt)
120
run_child_process (ChildProcess *process, char *const argv[])
352 by robert.ancell at canonical
Split child process code out from session/xserver
121
{
122
    GHashTableIter iter;
123
    gpointer key, value;
124
    int fd;
125
353 by robert.ancell at canonical
Use a private pipe for greeter<->server communication instead of D-Bus (needs to be fixed in liblightdm-qt)
126
    /* FIXME: Close existing file descriptors */
127
352 by robert.ancell at canonical
Split child process code out from session/xserver
128
    /* Make input non-blocking */
129
    fd = g_open ("/dev/null", O_RDONLY);
130
    dup2 (fd, STDIN_FILENO);
131
    close (fd);
132
133
    /* Set environment */
134
    g_hash_table_iter_init (&iter, process->priv->env);
135
    while (g_hash_table_iter_next (&iter, &key, &value))
136
        g_setenv ((gchar *)key, (gchar *)value, TRUE);
137
138
    /* Clear signal handlers so we don't handle them here */
139
    signal (SIGTERM, SIG_IGN);
140
    signal (SIGINT, SIG_IGN);
141
    signal (SIGHUP, SIG_IGN);
142
    signal (SIGUSR1, SIG_IGN);
143
    signal (SIGUSR2, SIG_IGN);
144
145
    if (process->priv->username)
146
    {
147
        if (initgroups (process->priv->username, process->priv->gid) < 0)
148
        {
149
            g_warning ("Failed to initialize supplementary groups for %s: %s", process->priv->username, strerror (errno));
150
            //_exit(1);
151
        }
152
153
        if (setgid (process->priv->gid) != 0)
154
        {
155
            g_warning ("Failed to set group ID: %s", strerror (errno));
156
            _exit(1);
157
        }
158
159
        if (setuid (process->priv->uid) != 0)
160
        {
161
            g_warning ("Failed to set user ID: %s", strerror (errno));
162
            _exit(1);
163
        }
472 by Robert Ancell
Set working directory when logging in
164
165
        if (chdir (process->priv->home_dir) != 0)
166
        {
167
            g_warning ("Failed to change to home directory: %s", strerror (errno));
168
            _exit(1);
169
        }
352 by robert.ancell at canonical
Split child process code out from session/xserver
170
    }
171
  
172
    /* Redirect output to logfile */
173
    if (process->priv->log_file)
174
    {
175
         int fd;
176
177
         fd = g_open (process->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
178
         if (fd < 0)
179
             g_warning ("Failed to open log file %s: %s", process->priv->log_file, g_strerror (errno));
180
         else
181
         {
182
             dup2 (fd, STDOUT_FILENO);
183
             dup2 (fd, STDERR_FILENO);
184
             close (fd);
185
         }
186
    }
353 by robert.ancell at canonical
Use a private pipe for greeter<->server communication instead of D-Bus (needs to be fixed in liblightdm-qt)
187
  
188
    execvp (argv[0], argv);
189
}
190
191
static gboolean
192
from_child_cb (GIOChannel *source, GIOCondition condition, gpointer data)
193
{
194
    ChildProcess *process = data;
426 by Robert Ancell
Stop poll loop when greeter disconnects
195
196
    if (condition == G_IO_HUP)
197
    {
198
        g_debug ("Process %d closed communication channel", process->priv->pid);
199
        return FALSE;
200
    }
201
353 by robert.ancell at canonical
Use a private pipe for greeter<->server communication instead of D-Bus (needs to be fixed in liblightdm-qt)
202
    g_signal_emit (process, signals[GOT_DATA], 0);
203
    return TRUE;
352 by robert.ancell at canonical
Split child process code out from session/xserver
204
}
205
206
gboolean
207
child_process_start (ChildProcess *process,
208
                     const gchar *username,
209
                     const gchar *working_dir,
210
                     const gchar *command,
353 by robert.ancell at canonical
Use a private pipe for greeter<->server communication instead of D-Bus (needs to be fixed in liblightdm-qt)
211
                     gboolean create_pipe,
352 by robert.ancell at canonical
Split child process code out from session/xserver
212
                     GError **error)
213
{
214
    gboolean result;
215
    gint argc;
216
    gchar **argv;
217
    GString *string;
218
    gpointer key, value;
219
    GHashTableIter iter;
353 by robert.ancell at canonical
Use a private pipe for greeter<->server communication instead of D-Bus (needs to be fixed in liblightdm-qt)
220
    pid_t pid;
221
    int from_server_fd = -1, to_server_fd = -1;
352 by robert.ancell at canonical
Split child process code out from session/xserver
222
223
    g_return_val_if_fail (process->priv->pid == 0, FALSE);
224
225
    if (username)
226
    {
227
        struct passwd *user_info;
228
229
        user_info = getpwnam (username);
230
        if (!user_info)
231
        {
232
            if (errno == 0)
233
                g_warning ("Unable to get information on user %s: User does not exist", username);
234
            else
235
                g_warning ("Unable to get information on user %s: %s", username, strerror (errno));
236
            return FALSE;
237
        }
238
239
        process->priv->username = g_strdup (username);
240
        process->priv->uid = user_info->pw_uid;
241
        process->priv->gid = user_info->pw_gid;
472 by Robert Ancell
Set working directory when logging in
242
        process->priv->home_dir = g_strdup (user_info->pw_dir);
352 by robert.ancell at canonical
Split child process code out from session/xserver
243
    }
244
245
    /* Create the log file owned by the target user */
246
    if (username && process->priv->log_file)
247
    {
248
        gint fd = g_open (process->priv->log_file, O_WRONLY | O_CREAT | O_TRUNC, 0600);
249
        close (fd);
250
        if (chown (process->priv->log_file, process->priv->uid, process->priv->gid) != 0)
353 by robert.ancell at canonical
Use a private pipe for greeter<->server communication instead of D-Bus (needs to be fixed in liblightdm-qt)
251
            g_warning ("Failed to set process log file ownership: %s", strerror (errno));
252
    }
253
254
    if (create_pipe)
255
    {
256
        gchar *fd;
257
        int to_child_pipe[2];
258
        int from_child_pipe[2];
259
260
        if (pipe (to_child_pipe) != 0 || 
261
            pipe (from_child_pipe) != 0)
262
        {
263
            g_warning ("Failed to create pipes: %s", strerror (errno));            
264
            return FALSE;
265
        }
266
267
        process->priv->to_child_channel = g_io_channel_unix_new (to_child_pipe[1]);
268
        g_io_channel_set_encoding (process->priv->to_child_channel, NULL, NULL);
269
270
        /* Watch for data from the child process */
271
        process->priv->from_child_channel = g_io_channel_unix_new (from_child_pipe[0]);
272
        g_io_channel_set_encoding (process->priv->from_child_channel, NULL, NULL);
385 by Robert Ancell
Fix reading from greeter
273
        g_io_channel_set_buffered (process->priv->from_child_channel, FALSE);
360 by robert.ancell at canonical
Make server greeter protocol non-blocking
274
426 by Robert Ancell
Stop poll loop when greeter disconnects
275
        g_io_add_watch (process->priv->from_child_channel, G_IO_IN | G_IO_HUP, from_child_cb, process);
353 by robert.ancell at canonical
Use a private pipe for greeter<->server communication instead of D-Bus (needs to be fixed in liblightdm-qt)
276
277
        to_server_fd = from_child_pipe[1];
278
        from_server_fd = to_child_pipe[0];
279
280
        fd = g_strdup_printf ("%d", to_server_fd);
281
        child_process_set_env (process, "LDM_TO_SERVER_FD", fd);
282
        g_free (fd);
283
        fd = g_strdup_printf ("%d", from_server_fd);
284
        child_process_set_env (process, "LDM_FROM_SERVER_FD", fd);
285
        g_free (fd);
286
    }
287
288
    result = g_shell_parse_argv (command, &argc, &argv, error);
289
    if (!result)
290
        return FALSE;
291
292
    pid = fork ();
293
    if (pid < 0)
294
    {
295
        g_warning ("Failed to fork: %s", strerror (errno));
296
        return FALSE;
297
    }
298
299
    if (pid == 0)
300
    {
301
        /* Close pipes */
302
        // TEMP: Remove this when have more generic file closing
303
        if (process->priv->to_child_channel)
304
            close (g_io_channel_unix_get_fd (process->priv->to_child_channel));
305
        if (process->priv->from_child_channel)
306
            close (g_io_channel_unix_get_fd (process->priv->from_child_channel));
307
308
        run_child_process (process, argv);
309
        _exit (EXIT_FAILURE);
310
    }
311
    close (from_server_fd);
312
    close (to_server_fd);
352 by robert.ancell at canonical
Split child process code out from session/xserver
313
314
    string = g_string_new ("");
315
    g_hash_table_iter_init (&iter, process->priv->env);
353 by robert.ancell at canonical
Use a private pipe for greeter<->server communication instead of D-Bus (needs to be fixed in liblightdm-qt)
316
    while (g_hash_table_iter_next (&iter, &key, &value))
352 by robert.ancell at canonical
Split child process code out from session/xserver
317
        g_string_append_printf (string, "%s=%s ", (gchar *)key, (gchar *)value);
318
    g_string_append (string, command);
353 by robert.ancell at canonical
Use a private pipe for greeter<->server communication instead of D-Bus (needs to be fixed in liblightdm-qt)
319
    g_debug ("Launching process %d: %s", pid, string->str);
352 by robert.ancell at canonical
Split child process code out from session/xserver
320
    g_string_free (string, TRUE);
321
353 by robert.ancell at canonical
Use a private pipe for greeter<->server communication instead of D-Bus (needs to be fixed in liblightdm-qt)
322
    process->priv->pid = pid;
352 by robert.ancell at canonical
Split child process code out from session/xserver
323
324
    g_strfreev (argv);
325
326
    g_hash_table_insert (processes, GINT_TO_POINTER (process->priv->pid), process);
327
    g_child_watch_add (process->priv->pid, child_process_watch_cb, process);
328
329
    return TRUE;
330
}
331
332
GPid
333
child_process_get_pid (ChildProcess *process)
334
{
335
    return process->priv->pid;
336
  
337
}
338
339
void
340
child_process_signal (ChildProcess *process, int signum)
341
{
342
    if (process->priv->pid)
343
        kill (process->priv->pid, signum);
344
}
345
360 by robert.ancell at canonical
Make server greeter protocol non-blocking
346
GIOChannel *
347
child_process_get_to_child_channel (ChildProcess *process)
348
{
349
    return process->priv->to_child_channel;
350
}
351
352
GIOChannel *
353
child_process_get_from_child_channel (ChildProcess *process)
354
{
355
    return process->priv->from_child_channel;
353 by robert.ancell at canonical
Use a private pipe for greeter<->server communication instead of D-Bus (needs to be fixed in liblightdm-qt)
356
}
357
352 by robert.ancell at canonical
Split child process code out from session/xserver
358
static void
359
child_process_init (ChildProcess *process)
360
{
361
    process->priv = G_TYPE_INSTANCE_GET_PRIVATE (process, CHILD_PROCESS_TYPE, ChildProcessPrivate);
362
    process->priv->env = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
363
}
364
365
static void
366
child_process_finalize (GObject *object)
367
{
368
    ChildProcess *self;
369
370
    self = CHILD_PROCESS (object);
371
  
372
    g_free (self->priv->username);
373
374
    if (self->priv->pid > 0)
375
        g_hash_table_remove (processes, GINT_TO_POINTER (self->priv->pid));
376
377
    if (self->priv->pid)
378
        kill (self->priv->pid, SIGTERM);
379
380
    g_hash_table_unref (self->priv->env);
443 by Robert Ancell
Correctly clean up processes on exit
381
382
    G_OBJECT_CLASS (child_process_parent_class)->finalize (object);
352 by robert.ancell at canonical
Split child process code out from session/xserver
383
}
384
385
static void
386
signal_cb (int signum, siginfo_t *info, void *data)
387
{
388
    if (write (signal_pipe[1], &info->si_signo, sizeof (int)) < 0 ||
389
        write (signal_pipe[1], &info->si_pid, sizeof (pid_t)) < 0)
390
        g_warning ("Failed to write to signal pipe");
391
}
392
393
static gboolean
394
handle_signal (GIOChannel *source, GIOCondition condition, gpointer data)
395
{
396
    int signo;
397
    pid_t pid;
398
    ChildProcess *process;
399
400
    if (read (signal_pipe[0], &signo, sizeof (int)) < 0 || 
401
        read (signal_pipe[0], &pid, sizeof (pid_t)) < 0)
402
        return TRUE;
403
357 by robert.ancell at canonical
Fix SIGINT handling
404
    if (pid == 0)
405
        process = child_process_get_parent ();
406
    else
407
        process = g_hash_table_lookup (processes, GINT_TO_POINTER (pid));
352 by robert.ancell at canonical
Split child process code out from session/xserver
408
    if (process)
409
        g_signal_emit (process, signals[GOT_SIGNAL], 0, signo);
410
411
    return TRUE;
412
}
413
414
static void
415
child_process_class_init (ChildProcessClass *klass)
416
{
417
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
418
    struct sigaction action;
419
420
    object_class->finalize = child_process_finalize;  
421
422
    g_type_class_add_private (klass, sizeof (ChildProcessPrivate));
423
353 by robert.ancell at canonical
Use a private pipe for greeter<->server communication instead of D-Bus (needs to be fixed in liblightdm-qt)
424
    signals[GOT_DATA] =
425
        g_signal_new ("got-data",
426
                      G_TYPE_FROM_CLASS (klass),
427
                      G_SIGNAL_RUN_LAST,
428
                      G_STRUCT_OFFSET (ChildProcessClass, got_data),
429
                      NULL, NULL,
430
                      g_cclosure_marshal_VOID__VOID,
431
                      G_TYPE_NONE, 0); 
352 by robert.ancell at canonical
Split child process code out from session/xserver
432
    signals[GOT_SIGNAL] =
433
        g_signal_new ("got-signal",
434
                      G_TYPE_FROM_CLASS (klass),
435
                      G_SIGNAL_RUN_LAST,
436
                      G_STRUCT_OFFSET (ChildProcessClass, got_signal),
437
                      NULL, NULL,
438
                      g_cclosure_marshal_VOID__INT,
439
                      G_TYPE_NONE, 1, G_TYPE_INT);
440
    signals[EXITED] =
441
        g_signal_new ("exited",
442
                      G_TYPE_FROM_CLASS (klass),
443
                      G_SIGNAL_RUN_LAST,
444
                      G_STRUCT_OFFSET (ChildProcessClass, exited),
445
                      NULL, NULL,
446
                      g_cclosure_marshal_VOID__INT,
447
                      G_TYPE_NONE, 1, G_TYPE_INT);
448
    signals[TERMINATED] =
449
        g_signal_new ("terminated",
450
                      G_TYPE_FROM_CLASS (klass),
451
                      G_SIGNAL_RUN_LAST,
452
                      G_STRUCT_OFFSET (ChildProcessClass, terminated),
453
                      NULL, NULL,
454
                      g_cclosure_marshal_VOID__INT,
455
                      G_TYPE_NONE, 1, G_TYPE_INT);
456
457
    /* Catch signals and feed them to the main loop via a pipe */
458
    processes = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
459
    if (pipe (signal_pipe) != 0)
460
        g_critical ("Failed to create signal pipe");
461
    g_io_add_watch (g_io_channel_unix_new (signal_pipe[0]), G_IO_IN, handle_signal, NULL);
462
    action.sa_sigaction = signal_cb;
463
    sigemptyset (&action.sa_mask);
464
    action.sa_flags = SA_SIGINFO;
465
    sigaction (SIGTERM, &action, NULL);
466
    sigaction (SIGINT, &action, NULL);
467
    sigaction (SIGHUP, &action, NULL);
468
    sigaction (SIGUSR1, &action, NULL);
469
    sigaction (SIGUSR2, &action, NULL);
470
}