[134] | 1 | /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- |
---|
| 2 | * |
---|
| 3 | * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> |
---|
| 4 | * |
---|
| 5 | * This program is free software; you can redistribute it and/or modify |
---|
| 6 | * it under the terms of the GNU General Public License as published by |
---|
| 7 | * the Free Software Foundation; either version 2 of the License, or |
---|
| 8 | * (at your option) any later version. |
---|
| 9 | * |
---|
| 10 | * This program is distributed in the hope that it will be useful, |
---|
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
| 13 | * GNU General Public License for more details. |
---|
| 14 | * |
---|
| 15 | * You should have received a copy of the GNU General Public License |
---|
| 16 | * along with this program; if not, write to the Free Software |
---|
| 17 | * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
---|
| 18 | * |
---|
| 19 | */ |
---|
| 20 | |
---|
| 21 | #include "config.h" |
---|
| 22 | |
---|
| 23 | #include <stdlib.h> |
---|
| 24 | #include <stdio.h> |
---|
| 25 | #include <fcntl.h> |
---|
| 26 | #include <unistd.h> |
---|
| 27 | #include <string.h> |
---|
| 28 | #include <sys/types.h> |
---|
| 29 | #include <sys/wait.h> |
---|
| 30 | #include <errno.h> |
---|
| 31 | #include <ctype.h> |
---|
| 32 | #include <pwd.h> |
---|
| 33 | #include <grp.h> |
---|
| 34 | #include <signal.h> |
---|
| 35 | |
---|
| 36 | #include <glib.h> |
---|
| 37 | #include <glib/gi18n.h> |
---|
| 38 | #include <glib-object.h> |
---|
| 39 | #define DBUS_API_SUBJECT_TO_CHANGE |
---|
| 40 | #include <dbus/dbus-glib.h> |
---|
| 41 | #include <dbus/dbus-glib-lowlevel.h> |
---|
| 42 | |
---|
| 43 | #include "gdm-common.h" |
---|
| 44 | |
---|
| 45 | #include "gdm-session-worker-job.h" |
---|
| 46 | |
---|
| 47 | #define GDM_SESSION_SERVER_DBUS_PATH "/org/gnome/DisplayManager/SessionServer" |
---|
| 48 | #define GDM_SESSION_SERVER_DBUS_INTERFACE "org.gnome.DisplayManager.SessionServer" |
---|
| 49 | |
---|
| 50 | extern char **environ; |
---|
| 51 | |
---|
| 52 | #define GDM_SESSION_WORKER_JOB_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_SESSION_WORKER_JOB, GdmSessionWorkerJobPrivate)) |
---|
| 53 | |
---|
| 54 | struct GdmSessionWorkerJobPrivate |
---|
| 55 | { |
---|
| 56 | char *command; |
---|
| 57 | GPid pid; |
---|
| 58 | |
---|
| 59 | guint child_watch_id; |
---|
| 60 | |
---|
| 61 | char *server_address; |
---|
| 62 | }; |
---|
| 63 | |
---|
| 64 | enum { |
---|
| 65 | PROP_0, |
---|
| 66 | PROP_SERVER_ADDRESS, |
---|
| 67 | }; |
---|
| 68 | |
---|
| 69 | enum { |
---|
| 70 | STARTED, |
---|
| 71 | STOPPED, |
---|
| 72 | EXITED, |
---|
| 73 | DIED, |
---|
| 74 | LAST_SIGNAL |
---|
| 75 | }; |
---|
| 76 | |
---|
| 77 | static guint signals [LAST_SIGNAL] = { 0, }; |
---|
| 78 | |
---|
| 79 | static void gdm_session_worker_job_class_init (GdmSessionWorkerJobClass *klass); |
---|
| 80 | static void gdm_session_worker_job_init (GdmSessionWorkerJob *session_worker_job); |
---|
| 81 | static void gdm_session_worker_job_finalize (GObject *object); |
---|
| 82 | |
---|
| 83 | G_DEFINE_TYPE (GdmSessionWorkerJob, gdm_session_worker_job, G_TYPE_OBJECT) |
---|
| 84 | |
---|
| 85 | static void |
---|
| 86 | session_worker_job_child_setup (GdmSessionWorkerJob *session_worker_job) |
---|
| 87 | { |
---|
| 88 | } |
---|
| 89 | |
---|
| 90 | static void |
---|
| 91 | session_worker_job_child_watch (GPid pid, |
---|
| 92 | int status, |
---|
| 93 | GdmSessionWorkerJob *job) |
---|
| 94 | { |
---|
| 95 | g_debug ("GdmSessionWorkerJob: child (pid:%d) done (%s:%d)", |
---|
| 96 | (int) pid, |
---|
| 97 | WIFEXITED (status) ? "status" |
---|
| 98 | : WIFSIGNALED (status) ? "signal" |
---|
| 99 | : "unknown", |
---|
| 100 | WIFEXITED (status) ? WEXITSTATUS (status) |
---|
| 101 | : WIFSIGNALED (status) ? WTERMSIG (status) |
---|
| 102 | : -1); |
---|
| 103 | |
---|
| 104 | g_spawn_close_pid (job->priv->pid); |
---|
| 105 | job->priv->pid = -1; |
---|
| 106 | |
---|
| 107 | if (WIFEXITED (status)) { |
---|
| 108 | int code = WEXITSTATUS (status); |
---|
| 109 | g_signal_emit (job, signals [EXITED], 0, code); |
---|
| 110 | } else if (WIFSIGNALED (status)) { |
---|
| 111 | int num = WTERMSIG (status); |
---|
| 112 | g_signal_emit (job, signals [DIED], 0, num); |
---|
| 113 | } |
---|
| 114 | } |
---|
| 115 | |
---|
| 116 | static void |
---|
| 117 | listify_hash (const char *key, |
---|
| 118 | const char *value, |
---|
| 119 | GPtrArray *env) |
---|
| 120 | { |
---|
| 121 | char *str; |
---|
| 122 | |
---|
| 123 | if (value == NULL) |
---|
| 124 | value = ""; |
---|
| 125 | |
---|
| 126 | str = g_strdup_printf ("%s=%s", key, value); |
---|
| 127 | g_ptr_array_add (env, str); |
---|
| 128 | } |
---|
| 129 | |
---|
| 130 | static void |
---|
| 131 | copy_environment_to_hash (GHashTable *hash) |
---|
| 132 | { |
---|
| 133 | gchar **env_strv; |
---|
| 134 | gint i; |
---|
| 135 | |
---|
| 136 | env_strv = g_listenv (); |
---|
| 137 | |
---|
| 138 | for (i = 0; env_strv [i]; i++) { |
---|
| 139 | gchar *key = env_strv [i]; |
---|
| 140 | const gchar *value; |
---|
| 141 | |
---|
| 142 | value = g_getenv (key); |
---|
| 143 | if (!value) |
---|
| 144 | continue; |
---|
| 145 | |
---|
| 146 | g_hash_table_insert (hash, g_strdup (key), g_strdup (value)); |
---|
| 147 | } |
---|
| 148 | |
---|
| 149 | g_strfreev (env_strv); |
---|
| 150 | } |
---|
| 151 | |
---|
| 152 | static GPtrArray * |
---|
| 153 | get_job_environment (GdmSessionWorkerJob *job) |
---|
| 154 | { |
---|
| 155 | GPtrArray *env; |
---|
| 156 | GHashTable *hash; |
---|
| 157 | |
---|
| 158 | env = g_ptr_array_new (); |
---|
| 159 | |
---|
| 160 | /* create a hash table of current environment, then update keys has necessary */ |
---|
| 161 | hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); |
---|
| 162 | copy_environment_to_hash (hash); |
---|
| 163 | |
---|
| 164 | g_hash_table_insert (hash, g_strdup ("GDM_SESSION_DBUS_ADDRESS"), g_strdup (job->priv->server_address)); |
---|
| 165 | |
---|
| 166 | g_hash_table_foreach (hash, (GHFunc)listify_hash, env); |
---|
| 167 | g_hash_table_destroy (hash); |
---|
| 168 | |
---|
| 169 | g_ptr_array_add (env, NULL); |
---|
| 170 | |
---|
| 171 | return env; |
---|
| 172 | } |
---|
| 173 | |
---|
| 174 | static gboolean |
---|
| 175 | gdm_session_worker_job_spawn (GdmSessionWorkerJob *session_worker_job) |
---|
| 176 | { |
---|
| 177 | gchar **argv; |
---|
| 178 | GError *error; |
---|
| 179 | gboolean ret; |
---|
| 180 | GPtrArray *env; |
---|
| 181 | |
---|
| 182 | ret = FALSE; |
---|
| 183 | |
---|
| 184 | g_debug ("GdmSessionWorkerJob: Running session_worker_job process: %s", session_worker_job->priv->command); |
---|
| 185 | |
---|
| 186 | argv = NULL; |
---|
| 187 | if (! g_shell_parse_argv (session_worker_job->priv->command, NULL, &argv, &error)) { |
---|
| 188 | g_warning ("Could not parse command: %s", error->message); |
---|
| 189 | g_error_free (error); |
---|
| 190 | goto out; |
---|
| 191 | } |
---|
| 192 | |
---|
| 193 | env = get_job_environment (session_worker_job); |
---|
| 194 | |
---|
| 195 | error = NULL; |
---|
| 196 | ret = g_spawn_async_with_pipes (NULL, |
---|
| 197 | argv, |
---|
| 198 | (char **)env->pdata, |
---|
| 199 | G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, |
---|
| 200 | (GSpawnChildSetupFunc)session_worker_job_child_setup, |
---|
| 201 | session_worker_job, |
---|
| 202 | &session_worker_job->priv->pid, |
---|
| 203 | NULL, |
---|
| 204 | NULL, |
---|
| 205 | NULL, |
---|
| 206 | &error); |
---|
| 207 | |
---|
| 208 | g_ptr_array_foreach (env, (GFunc)g_free, NULL); |
---|
| 209 | g_ptr_array_free (env, TRUE); |
---|
| 210 | |
---|
| 211 | if (! ret) { |
---|
| 212 | g_warning ("Could not start command '%s': %s", |
---|
| 213 | session_worker_job->priv->command, |
---|
| 214 | error->message); |
---|
| 215 | g_error_free (error); |
---|
| 216 | } else { |
---|
| 217 | g_debug ("GdmSessionWorkerJob: : SessionWorkerJob on pid %d", (int)session_worker_job->priv->pid); |
---|
| 218 | } |
---|
| 219 | |
---|
| 220 | session_worker_job->priv->child_watch_id = g_child_watch_add (session_worker_job->priv->pid, |
---|
| 221 | (GChildWatchFunc)session_worker_job_child_watch, |
---|
| 222 | session_worker_job); |
---|
| 223 | |
---|
| 224 | g_strfreev (argv); |
---|
| 225 | out: |
---|
| 226 | |
---|
| 227 | return ret; |
---|
| 228 | } |
---|
| 229 | |
---|
| 230 | /** |
---|
| 231 | * gdm_session_worker_job_start: |
---|
| 232 | * @disp: Pointer to a GdmDisplay structure |
---|
| 233 | * |
---|
| 234 | * Starts a local X session_worker_job. Handles retries and fatal errors properly. |
---|
| 235 | */ |
---|
| 236 | gboolean |
---|
| 237 | gdm_session_worker_job_start (GdmSessionWorkerJob *session_worker_job) |
---|
| 238 | { |
---|
| 239 | gboolean res; |
---|
| 240 | |
---|
| 241 | g_debug ("GdmSessionWorkerJob: Starting worker..."); |
---|
| 242 | |
---|
| 243 | res = gdm_session_worker_job_spawn (session_worker_job); |
---|
| 244 | |
---|
| 245 | if (res) { |
---|
| 246 | |
---|
| 247 | } |
---|
| 248 | |
---|
| 249 | |
---|
| 250 | return res; |
---|
| 251 | } |
---|
| 252 | |
---|
| 253 | static void |
---|
| 254 | session_worker_job_died (GdmSessionWorkerJob *session_worker_job) |
---|
| 255 | { |
---|
| 256 | int exit_status; |
---|
| 257 | |
---|
| 258 | g_debug ("GdmSessionWorkerJob: Waiting on process %d", session_worker_job->priv->pid); |
---|
| 259 | exit_status = gdm_wait_on_pid (session_worker_job->priv->pid); |
---|
| 260 | |
---|
| 261 | if (WIFEXITED (exit_status) && (WEXITSTATUS (exit_status) != 0)) { |
---|
| 262 | g_debug ("GdmSessionWorkerJob: Wait on child process failed"); |
---|
| 263 | } else { |
---|
| 264 | /* exited normally */ |
---|
| 265 | } |
---|
| 266 | |
---|
| 267 | g_spawn_close_pid (session_worker_job->priv->pid); |
---|
| 268 | session_worker_job->priv->pid = -1; |
---|
| 269 | |
---|
| 270 | g_debug ("GdmSessionWorkerJob: SessionWorkerJob died"); |
---|
| 271 | } |
---|
| 272 | |
---|
| 273 | gboolean |
---|
| 274 | gdm_session_worker_job_stop (GdmSessionWorkerJob *session_worker_job) |
---|
| 275 | { |
---|
| 276 | int res; |
---|
| 277 | |
---|
| 278 | if (session_worker_job->priv->pid <= 1) { |
---|
| 279 | return TRUE; |
---|
| 280 | } |
---|
| 281 | |
---|
| 282 | /* remove watch source before we can wait on child */ |
---|
| 283 | if (session_worker_job->priv->child_watch_id > 0) { |
---|
| 284 | g_source_remove (session_worker_job->priv->child_watch_id); |
---|
| 285 | session_worker_job->priv->child_watch_id = 0; |
---|
| 286 | } |
---|
| 287 | |
---|
| 288 | g_debug ("GdmSessionWorkerJob: Stopping job pid:%d", session_worker_job->priv->pid); |
---|
| 289 | |
---|
| 290 | res = gdm_signal_pid (session_worker_job->priv->pid, SIGTERM); |
---|
| 291 | if (res < 0) { |
---|
| 292 | g_warning ("Unable to kill session worker process"); |
---|
| 293 | } else { |
---|
| 294 | session_worker_job_died (session_worker_job); |
---|
| 295 | } |
---|
| 296 | |
---|
| 297 | return TRUE; |
---|
| 298 | } |
---|
| 299 | |
---|
| 300 | void |
---|
| 301 | gdm_session_worker_job_set_server_address (GdmSessionWorkerJob *session_worker_job, |
---|
| 302 | const char *address) |
---|
| 303 | { |
---|
| 304 | g_return_if_fail (GDM_IS_SESSION_WORKER_JOB (session_worker_job)); |
---|
| 305 | |
---|
| 306 | g_free (session_worker_job->priv->server_address); |
---|
| 307 | session_worker_job->priv->server_address = g_strdup (address); |
---|
| 308 | } |
---|
| 309 | |
---|
| 310 | static void |
---|
| 311 | gdm_session_worker_job_set_property (GObject *object, |
---|
| 312 | guint prop_id, |
---|
| 313 | const GValue *value, |
---|
| 314 | GParamSpec *pspec) |
---|
| 315 | { |
---|
| 316 | GdmSessionWorkerJob *self; |
---|
| 317 | |
---|
| 318 | self = GDM_SESSION_WORKER_JOB (object); |
---|
| 319 | |
---|
| 320 | switch (prop_id) { |
---|
| 321 | case PROP_SERVER_ADDRESS: |
---|
| 322 | gdm_session_worker_job_set_server_address (self, g_value_get_string (value)); |
---|
| 323 | break; |
---|
| 324 | default: |
---|
| 325 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
---|
| 326 | break; |
---|
| 327 | } |
---|
| 328 | } |
---|
| 329 | |
---|
| 330 | static void |
---|
| 331 | gdm_session_worker_job_get_property (GObject *object, |
---|
| 332 | guint prop_id, |
---|
| 333 | GValue *value, |
---|
| 334 | GParamSpec *pspec) |
---|
| 335 | { |
---|
| 336 | GdmSessionWorkerJob *self; |
---|
| 337 | |
---|
| 338 | self = GDM_SESSION_WORKER_JOB (object); |
---|
| 339 | |
---|
| 340 | switch (prop_id) { |
---|
| 341 | case PROP_SERVER_ADDRESS: |
---|
| 342 | g_value_set_string (value, self->priv->server_address); |
---|
| 343 | break; |
---|
| 344 | default: |
---|
| 345 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
---|
| 346 | break; |
---|
| 347 | } |
---|
| 348 | } |
---|
| 349 | |
---|
| 350 | static GObject * |
---|
| 351 | gdm_session_worker_job_constructor (GType type, |
---|
| 352 | guint n_construct_properties, |
---|
| 353 | GObjectConstructParam *construct_properties) |
---|
| 354 | { |
---|
| 355 | GdmSessionWorkerJob *session_worker_job; |
---|
| 356 | |
---|
| 357 | session_worker_job = GDM_SESSION_WORKER_JOB (G_OBJECT_CLASS (gdm_session_worker_job_parent_class)->constructor (type, |
---|
| 358 | n_construct_properties, |
---|
| 359 | construct_properties)); |
---|
| 360 | |
---|
| 361 | return G_OBJECT (session_worker_job); |
---|
| 362 | } |
---|
| 363 | |
---|
| 364 | static void |
---|
| 365 | gdm_session_worker_job_class_init (GdmSessionWorkerJobClass *klass) |
---|
| 366 | { |
---|
| 367 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
---|
| 368 | |
---|
| 369 | object_class->get_property = gdm_session_worker_job_get_property; |
---|
| 370 | object_class->set_property = gdm_session_worker_job_set_property; |
---|
| 371 | object_class->constructor = gdm_session_worker_job_constructor; |
---|
| 372 | object_class->finalize = gdm_session_worker_job_finalize; |
---|
| 373 | |
---|
| 374 | g_type_class_add_private (klass, sizeof (GdmSessionWorkerJobPrivate)); |
---|
| 375 | |
---|
| 376 | g_object_class_install_property (object_class, |
---|
| 377 | PROP_SERVER_ADDRESS, |
---|
| 378 | g_param_spec_string ("server-address", |
---|
| 379 | "server address", |
---|
| 380 | "server address", |
---|
| 381 | NULL, |
---|
| 382 | G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); |
---|
| 383 | signals [STARTED] = |
---|
| 384 | g_signal_new ("started", |
---|
| 385 | G_OBJECT_CLASS_TYPE (object_class), |
---|
| 386 | G_SIGNAL_RUN_FIRST, |
---|
| 387 | G_STRUCT_OFFSET (GdmSessionWorkerJobClass, started), |
---|
| 388 | NULL, |
---|
| 389 | NULL, |
---|
| 390 | g_cclosure_marshal_VOID__VOID, |
---|
| 391 | G_TYPE_NONE, |
---|
| 392 | 0); |
---|
| 393 | signals [STOPPED] = |
---|
| 394 | g_signal_new ("stopped", |
---|
| 395 | G_OBJECT_CLASS_TYPE (object_class), |
---|
| 396 | G_SIGNAL_RUN_FIRST, |
---|
| 397 | G_STRUCT_OFFSET (GdmSessionWorkerJobClass, stopped), |
---|
| 398 | NULL, |
---|
| 399 | NULL, |
---|
| 400 | g_cclosure_marshal_VOID__VOID, |
---|
| 401 | G_TYPE_NONE, |
---|
| 402 | 0); |
---|
| 403 | signals [EXITED] = |
---|
| 404 | g_signal_new ("exited", |
---|
| 405 | G_OBJECT_CLASS_TYPE (object_class), |
---|
| 406 | G_SIGNAL_RUN_FIRST, |
---|
| 407 | G_STRUCT_OFFSET (GdmSessionWorkerJobClass, exited), |
---|
| 408 | NULL, |
---|
| 409 | NULL, |
---|
| 410 | g_cclosure_marshal_VOID__INT, |
---|
| 411 | G_TYPE_NONE, |
---|
| 412 | 1, |
---|
| 413 | G_TYPE_INT); |
---|
| 414 | |
---|
| 415 | signals [DIED] = |
---|
| 416 | g_signal_new ("died", |
---|
| 417 | G_OBJECT_CLASS_TYPE (object_class), |
---|
| 418 | G_SIGNAL_RUN_FIRST, |
---|
| 419 | G_STRUCT_OFFSET (GdmSessionWorkerJobClass, died), |
---|
| 420 | NULL, |
---|
| 421 | NULL, |
---|
| 422 | g_cclosure_marshal_VOID__INT, |
---|
| 423 | G_TYPE_NONE, |
---|
| 424 | 1, |
---|
| 425 | G_TYPE_INT); |
---|
| 426 | } |
---|
| 427 | |
---|
| 428 | static void |
---|
| 429 | gdm_session_worker_job_init (GdmSessionWorkerJob *session_worker_job) |
---|
| 430 | { |
---|
| 431 | |
---|
| 432 | session_worker_job->priv = GDM_SESSION_WORKER_JOB_GET_PRIVATE (session_worker_job); |
---|
| 433 | |
---|
| 434 | session_worker_job->priv->pid = -1; |
---|
| 435 | |
---|
| 436 | session_worker_job->priv->command = g_strdup (LIBEXECDIR "/gdm-session-worker"); |
---|
| 437 | } |
---|
| 438 | |
---|
| 439 | static void |
---|
| 440 | gdm_session_worker_job_finalize (GObject *object) |
---|
| 441 | { |
---|
| 442 | GdmSessionWorkerJob *session_worker_job; |
---|
| 443 | |
---|
| 444 | g_return_if_fail (object != NULL); |
---|
| 445 | g_return_if_fail (GDM_IS_SESSION_WORKER_JOB (object)); |
---|
| 446 | |
---|
| 447 | session_worker_job = GDM_SESSION_WORKER_JOB (object); |
---|
| 448 | |
---|
| 449 | g_return_if_fail (session_worker_job->priv != NULL); |
---|
| 450 | |
---|
| 451 | gdm_session_worker_job_stop (session_worker_job); |
---|
| 452 | |
---|
| 453 | G_OBJECT_CLASS (gdm_session_worker_job_parent_class)->finalize (object); |
---|
| 454 | } |
---|
| 455 | |
---|
| 456 | GdmSessionWorkerJob * |
---|
| 457 | gdm_session_worker_job_new (void) |
---|
| 458 | { |
---|
| 459 | GObject *object; |
---|
| 460 | |
---|
| 461 | object = g_object_new (GDM_TYPE_SESSION_WORKER_JOB, |
---|
| 462 | NULL); |
---|
| 463 | |
---|
| 464 | return GDM_SESSION_WORKER_JOB (object); |
---|
| 465 | } |
---|