/* * Copyright (C) 2008 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307, USA. * * Author: David Zeuthen */ #include "polkitbackendcommon.h" static void utils_child_watch_from_release_cb (GPid pid, gint status, gpointer user_data) { } static void utils_spawn_data_free (UtilsSpawnData *data) { if (data->timeout_source != NULL) { g_source_destroy (data->timeout_source); data->timeout_source = NULL; } /* Nuke the child, if necessary */ if (data->child_watch_source != NULL) { g_source_destroy (data->child_watch_source); data->child_watch_source = NULL; } if (data->child_pid != 0) { GSource *source; kill (data->child_pid, SIGTERM); /* OK, we need to reap for the child ourselves - we don't want * to use waitpid() because that might block the calling * thread (the child might handle SIGTERM and use several * seconds for cleanup/rollback). * * So we use GChildWatch instead. * * Avoid taking a references to ourselves. but note that we need * to pass the GSource so we can nuke it once handled. */ source = g_child_watch_source_new (data->child_pid); g_source_set_callback (source, (GSourceFunc) utils_child_watch_from_release_cb, source, (GDestroyNotify) g_source_destroy); g_source_attach (source, data->main_context); g_source_unref (source); data->child_pid = 0; } if (data->child_stdout != NULL) { g_string_free (data->child_stdout, TRUE); data->child_stdout = NULL; } if (data->child_stderr != NULL) { g_string_free (data->child_stderr, TRUE); data->child_stderr = NULL; } if (data->child_stdout_channel != NULL) { g_io_channel_unref (data->child_stdout_channel); data->child_stdout_channel = NULL; } if (data->child_stderr_channel != NULL) { g_io_channel_unref (data->child_stderr_channel); data->child_stderr_channel = NULL; } if (data->child_stdout_source != NULL) { g_source_destroy (data->child_stdout_source); data->child_stdout_source = NULL; } if (data->child_stderr_source != NULL) { g_source_destroy (data->child_stderr_source); data->child_stderr_source = NULL; } if (data->child_stdout_fd != -1) { g_warn_if_fail (close (data->child_stdout_fd) == 0); data->child_stdout_fd = -1; } if (data->child_stderr_fd != -1) { g_warn_if_fail (close (data->child_stderr_fd) == 0); data->child_stderr_fd = -1; } if (data->cancellable_handler_id > 0) { g_cancellable_disconnect (data->cancellable, data->cancellable_handler_id); data->cancellable_handler_id = 0; } if (data->main_context != NULL) g_main_context_unref (data->main_context); if (data->cancellable != NULL) g_object_unref (data->cancellable); g_slice_free (UtilsSpawnData, data); } /* called in the thread where @cancellable was cancelled */ static void utils_on_cancelled (GCancellable *cancellable, gpointer user_data) { UtilsSpawnData *data = (UtilsSpawnData *)user_data; GError *error; error = NULL; g_warn_if_fail (g_cancellable_set_error_if_cancelled (cancellable, &error)); g_simple_async_result_take_error (data->simple, error); g_simple_async_result_complete_in_idle (data->simple); g_object_unref (data->simple); } static gboolean utils_timeout_cb (gpointer user_data) { UtilsSpawnData *data = (UtilsSpawnData *)user_data; data->timed_out = TRUE; /* ok, timeout is history, make sure we don't free it in spawn_data_free() */ data->timeout_source = NULL; /* we're done */ g_simple_async_result_complete_in_idle (data->simple); g_object_unref (data->simple); return FALSE; /* remove source */ } static void utils_child_watch_cb (GPid pid, gint status, gpointer user_data) { UtilsSpawnData *data = (UtilsSpawnData *)user_data; gchar *buf; gsize buf_size; if (g_io_channel_read_to_end (data->child_stdout_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) { g_string_append_len (data->child_stdout, buf, buf_size); g_free (buf); } if (g_io_channel_read_to_end (data->child_stderr_channel, &buf, &buf_size, NULL) == G_IO_STATUS_NORMAL) { g_string_append_len (data->child_stderr, buf, buf_size); g_free (buf); } data->exit_status = status; /* ok, child watch is history, make sure we don't free it in spawn_data_free() */ data->child_pid = 0; data->child_watch_source = NULL; /* we're done */ g_simple_async_result_complete_in_idle (data->simple); g_object_unref (data->simple); } static gboolean utils_read_child_stderr (GIOChannel *channel, GIOCondition condition, gpointer user_data) { UtilsSpawnData *data = (UtilsSpawnData *)user_data; gchar buf[1024]; gsize bytes_read; g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); g_string_append_len (data->child_stderr, buf, bytes_read); return TRUE; } static gboolean utils_read_child_stdout (GIOChannel *channel, GIOCondition condition, gpointer user_data) { UtilsSpawnData *data = (UtilsSpawnData *)user_data; gchar buf[1024]; gsize bytes_read; g_io_channel_read_chars (channel, buf, sizeof buf, &bytes_read, NULL); g_string_append_len (data->child_stdout, buf, bytes_read); return TRUE; } void polkit_backend_common_spawn (const gchar *const *argv, guint timeout_seconds, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { UtilsSpawnData *data; GError *error; data = g_slice_new0 (UtilsSpawnData); data->timeout_seconds = timeout_seconds; data->simple = g_simple_async_result_new (NULL, callback, user_data, (gpointer*)polkit_backend_common_spawn); data->main_context = g_main_context_get_thread_default (); if (data->main_context != NULL) g_main_context_ref (data->main_context); data->cancellable = cancellable != NULL ? (GCancellable*)g_object_ref (cancellable) : NULL; data->child_stdout = g_string_new (NULL); data->child_stderr = g_string_new (NULL); data->child_stdout_fd = -1; data->child_stderr_fd = -1; /* the life-cycle of UtilsSpawnData is tied to its GSimpleAsyncResult */ g_simple_async_result_set_op_res_gpointer (data->simple, data, (GDestroyNotify) utils_spawn_data_free); error = NULL; if (data->cancellable != NULL) { /* could already be cancelled */ error = NULL; if (g_cancellable_set_error_if_cancelled (data->cancellable, &error)) { g_simple_async_result_take_error (data->simple, error); g_simple_async_result_complete_in_idle (data->simple); g_object_unref (data->simple); goto out; } data->cancellable_handler_id = g_cancellable_connect (data->cancellable, G_CALLBACK (utils_on_cancelled), data, NULL); } error = NULL; if (!g_spawn_async_with_pipes (NULL, /* working directory */ (gchar **) argv, NULL, /* envp */ G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, NULL, /* child_setup */ NULL, /* child_setup's user_data */ &(data->child_pid), NULL, /* gint *stdin_fd */ &(data->child_stdout_fd), &(data->child_stderr_fd), &error)) { g_prefix_error (&error, "Error spawning: "); g_simple_async_result_take_error (data->simple, error); g_simple_async_result_complete_in_idle (data->simple); g_object_unref (data->simple); goto out; } if (timeout_seconds > 0) { data->timeout_source = g_timeout_source_new_seconds (timeout_seconds); g_source_set_priority (data->timeout_source, G_PRIORITY_DEFAULT); g_source_set_callback (data->timeout_source, utils_timeout_cb, data, NULL); g_source_attach (data->timeout_source, data->main_context); g_source_unref (data->timeout_source); } data->child_watch_source = g_child_watch_source_new (data->child_pid); g_source_set_callback (data->child_watch_source, (GSourceFunc) utils_child_watch_cb, data, NULL); g_source_attach (data->child_watch_source, data->main_context); g_source_unref (data->child_watch_source); data->child_stdout_channel = g_io_channel_unix_new (data->child_stdout_fd); g_io_channel_set_flags (data->child_stdout_channel, G_IO_FLAG_NONBLOCK, NULL); data->child_stdout_source = g_io_create_watch (data->child_stdout_channel, G_IO_IN); g_source_set_callback (data->child_stdout_source, (GSourceFunc) utils_read_child_stdout, data, NULL); g_source_attach (data->child_stdout_source, data->main_context); g_source_unref (data->child_stdout_source); data->child_stderr_channel = g_io_channel_unix_new (data->child_stderr_fd); g_io_channel_set_flags (data->child_stderr_channel, G_IO_FLAG_NONBLOCK, NULL); data->child_stderr_source = g_io_create_watch (data->child_stderr_channel, G_IO_IN); g_source_set_callback (data->child_stderr_source, (GSourceFunc) utils_read_child_stderr, data, NULL); g_source_attach (data->child_stderr_source, data->main_context); g_source_unref (data->child_stderr_source); out: ; } void polkit_backend_common_on_dir_monitor_changed (GFileMonitor *monitor, GFile *file, GFile *other_file, GFileMonitorEvent event_type, gpointer user_data) { PolkitBackendJsAuthority *authority = POLKIT_BACKEND_JS_AUTHORITY (user_data); /* TODO: maybe rate-limit so storms of events are collapsed into one with a 500ms resolution? * Because when editing a file with emacs we get 4-8 events.. */ if (file != NULL) { gchar *name; name = g_file_get_basename (file); /* g_print ("event_type=%d file=%p name=%s\n", event_type, file, name); */ if (!g_str_has_prefix (name, ".") && !g_str_has_prefix (name, "#") && g_str_has_suffix (name, ".rules") && (event_type == G_FILE_MONITOR_EVENT_CREATED || event_type == G_FILE_MONITOR_EVENT_DELETED || event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT)) { polkit_backend_authority_log (POLKIT_BACKEND_AUTHORITY (authority), "Reloading rules"); polkit_backend_common_reload_scripts (authority); } g_free (name); } } gboolean polkit_backend_common_spawn_finish (GAsyncResult *res, gint *out_exit_status, gchar **out_standard_output, gchar **out_standard_error, GError **error) { GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (res); UtilsSpawnData *data; gboolean ret = FALSE; g_return_val_if_fail (G_IS_ASYNC_RESULT (res), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); g_warn_if_fail (g_simple_async_result_get_source_tag (simple) == polkit_backend_common_spawn); if (g_simple_async_result_propagate_error (simple, error)) goto out; data = (UtilsSpawnData*)g_simple_async_result_get_op_res_gpointer (simple); if (data->timed_out) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "Timed out after %d seconds", data->timeout_seconds); goto out; } if (out_exit_status != NULL) *out_exit_status = data->exit_status; if (out_standard_output != NULL) *out_standard_output = g_strdup (data->child_stdout->str); if (out_standard_error != NULL) *out_standard_error = g_strdup (data->child_stderr->str); ret = TRUE; out: return ret; } static const gchar * polkit_backend_js_authority_get_name (PolkitBackendAuthority *authority) { return "js"; } static const gchar * polkit_backend_js_authority_get_version (PolkitBackendAuthority *authority) { return PACKAGE_VERSION; } static PolkitAuthorityFeatures polkit_backend_js_authority_get_features (PolkitBackendAuthority *authority) { return POLKIT_AUTHORITY_FEATURES_TEMPORARY_AUTHORIZATION; } void polkit_backend_common_js_authority_class_init_common (PolkitBackendJsAuthorityClass *klass) { GObjectClass *gobject_class; PolkitBackendAuthorityClass *authority_class; PolkitBackendInteractiveAuthorityClass *interactive_authority_class; gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = polkit_backend_common_js_authority_finalize; gobject_class->set_property = polkit_backend_common_js_authority_set_property; gobject_class->constructed = polkit_backend_common_js_authority_constructed; authority_class = POLKIT_BACKEND_AUTHORITY_CLASS (klass); authority_class->get_name = polkit_backend_js_authority_get_name; authority_class->get_version = polkit_backend_js_authority_get_version; authority_class->get_features = polkit_backend_js_authority_get_features; interactive_authority_class = POLKIT_BACKEND_INTERACTIVE_AUTHORITY_CLASS (klass); interactive_authority_class->get_admin_identities = polkit_backend_common_js_authority_get_admin_auth_identities; interactive_authority_class->check_authorization_sync = polkit_backend_common_js_authority_check_authorization_sync; g_object_class_install_property (gobject_class, PROP_RULES_DIRS, g_param_spec_boxed ("rules-dirs", NULL, NULL, G_TYPE_STRV, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE)); } gint polkit_backend_common_rules_file_name_cmp (const gchar *a, const gchar *b) { gint ret; const gchar *a_base; const gchar *b_base; a_base = strrchr (a, '/'); b_base = strrchr (b, '/'); g_assert (a_base != NULL); g_assert (b_base != NULL); a_base += 1; b_base += 1; ret = g_strcmp0 (a_base, b_base); if (ret == 0) { /* /etc wins over /usr */ ret = g_strcmp0 (a, b); g_assert (ret != 0); } return ret; } const gchar * polkit_backend_common_get_signal_name (gint signal_number) { switch (signal_number) { #define _HANDLE_SIG(sig) case sig: return #sig; _HANDLE_SIG (SIGHUP); _HANDLE_SIG (SIGINT); _HANDLE_SIG (SIGQUIT); _HANDLE_SIG (SIGILL); _HANDLE_SIG (SIGABRT); _HANDLE_SIG (SIGFPE); _HANDLE_SIG (SIGKILL); _HANDLE_SIG (SIGSEGV); _HANDLE_SIG (SIGPIPE); _HANDLE_SIG (SIGALRM); _HANDLE_SIG (SIGTERM); _HANDLE_SIG (SIGUSR1); _HANDLE_SIG (SIGUSR2); _HANDLE_SIG (SIGCHLD); _HANDLE_SIG (SIGCONT); _HANDLE_SIG (SIGSTOP); _HANDLE_SIG (SIGTSTP); _HANDLE_SIG (SIGTTIN); _HANDLE_SIG (SIGTTOU); _HANDLE_SIG (SIGBUS); #ifdef SIGPOLL _HANDLE_SIG (SIGPOLL); #endif _HANDLE_SIG (SIGPROF); _HANDLE_SIG (SIGSYS); _HANDLE_SIG (SIGTRAP); _HANDLE_SIG (SIGURG); _HANDLE_SIG (SIGVTALRM); _HANDLE_SIG (SIGXCPU); _HANDLE_SIG (SIGXFSZ); #undef _HANDLE_SIG default: break; } return "UNKNOWN_SIGNAL"; } void polkit_backend_common_spawn_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) { SpawnData *data = (SpawnData *)user_data; data->res = (GAsyncResult*)g_object_ref (res); g_main_loop_quit (data->loop); }