/* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2006-2007 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., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * Author: Alexander Larsson */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include enum { PROP_0 }; enum { SHUTDOWN, LAST_SIGNAL }; typedef struct { char *obj_path; GVfsRegisterPathCallback callback; gpointer data; GDBusInterfaceSkeleton *session_skeleton; GHashTable *client_skeletons; } RegisteredPath; struct _GVfsDaemon { GObject parent_instance; GMutex lock; gboolean main_daemon; GThreadPool *thread_pool; GHashTable *registered_paths; GHashTable *client_connections; GList *jobs; GList *job_sources; guint exit_tag; gint mount_counter; GDBusAuthObserver *auth_observer; GDBusConnection *conn; GVfsDBusDaemon *daemon_skeleton; GVfsDBusMountable *mountable_skeleton; guint name_watcher; gboolean lost_main_daemon; }; static guint signals[LAST_SIGNAL] = { 0 }; static void g_vfs_daemon_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void g_vfs_daemon_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static gboolean handle_get_connection (GVfsDBusDaemon *object, GDBusMethodInvocation *invocation, gpointer user_data); static gboolean handle_cancel (GVfsDBusDaemon *object, GDBusMethodInvocation *invocation, guint arg_serial, gpointer user_data); static gboolean handle_list_monitor_implementations (GVfsDBusDaemon *object, GDBusMethodInvocation *invocation, gpointer user_data); static gboolean daemon_handle_mount (GVfsDBusMountable *object, GDBusMethodInvocation *invocation, GVariant *arg_mount_spec, gboolean arg_automount, GVariant *arg_mount_source, gpointer user_data); static void g_vfs_daemon_re_register_job_sources (GVfsDaemon *daemon); G_DEFINE_TYPE (GVfsDaemon, g_vfs_daemon, G_TYPE_OBJECT) static void registered_path_free (RegisteredPath *data) { g_free (data->obj_path); if (data->session_skeleton) { /* Unexport the interface skeleton on session bus */ g_dbus_interface_skeleton_unexport (data->session_skeleton); g_object_unref (data->session_skeleton); } g_hash_table_destroy (data->client_skeletons); g_free (data); } static void g_vfs_daemon_finalize (GObject *object) { GVfsDaemon *daemon; daemon = G_VFS_DAEMON (object); /* There may be some jobs outstanding if we've been force unmounted. */ if (daemon->jobs) g_warning ("daemon->jobs != NULL when finalizing daemon!"); if (daemon->name_watcher) g_bus_unwatch_name (daemon->name_watcher); if (daemon->daemon_skeleton != NULL) { g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (daemon->daemon_skeleton)); g_object_unref (daemon->daemon_skeleton); } if (daemon->mountable_skeleton != NULL) { g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (daemon->mountable_skeleton)); g_object_unref (daemon->mountable_skeleton); } if (daemon->conn != NULL) g_object_unref (daemon->conn); if (daemon->auth_observer != NULL) g_object_unref (daemon->auth_observer); g_hash_table_destroy (daemon->registered_paths); g_hash_table_destroy (daemon->client_connections); g_mutex_clear (&daemon->lock); if (G_OBJECT_CLASS (g_vfs_daemon_parent_class)->finalize) (*G_OBJECT_CLASS (g_vfs_daemon_parent_class)->finalize) (object); } static void g_vfs_daemon_class_init (GVfsDaemonClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = g_vfs_daemon_finalize; gobject_class->set_property = g_vfs_daemon_set_property; gobject_class->get_property = g_vfs_daemon_get_property; signals[SHUTDOWN] = g_signal_new ("shutdown", G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GVfsDaemonClass, shutdown), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void job_handler_callback (gpointer data, gpointer user_data) { GVfsJob *job = G_VFS_JOB (data); g_vfs_job_run (job); } static void name_appeared_handler (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) { GVfsDaemon *daemon = G_VFS_DAEMON (user_data); if (strcmp (name, G_VFS_DBUS_DAEMON_NAME) == 0 && *name_owner != 0 && daemon->lost_main_daemon) { /* There is a new owner. Register mounts with it */ g_vfs_daemon_re_register_job_sources (daemon); } } static void name_vanished_handler (GDBusConnection *connection, const gchar *name, gpointer user_data) { GVfsDaemon *daemon = G_VFS_DAEMON (user_data); /* Ensure we react only to really lost daemon */ daemon->lost_main_daemon = TRUE; } /* * Authentication observer signal handler that rejects all authentication * mechanisms except for EXTERNAL (credentials-passing), which is the * recommended authentication mechanism for AF_UNIX sockets. */ static gboolean allow_mechanism_cb (GDBusAuthObserver *observer, const gchar *mechanism, G_GNUC_UNUSED gpointer user_data) { if (g_strcmp0 (mechanism, "EXTERNAL") == 0) return TRUE; return FALSE; } /* * Authentication observer signal handler that authorizes connections * from the same uid as this process. This matches the behaviour of a * libdbus DBusServer/DBusConnection when no DBusAllowUnixUserFunction * has been set, but is not the default in GDBus. */ static gboolean authorize_authenticated_peer_cb (GDBusAuthObserver *observer, G_GNUC_UNUSED GIOStream *stream, GCredentials *credentials, G_GNUC_UNUSED gpointer user_data) { gboolean authorized = FALSE; if (credentials != NULL) { GCredentials *own_credentials; own_credentials = g_credentials_new (); if (g_credentials_is_same_user (credentials, own_credentials, NULL)) authorized = TRUE; g_object_unref (own_credentials); } return authorized; } static void g_vfs_daemon_init (GVfsDaemon *daemon) { GError *error; gint max_threads = 1; /* TODO: handle max threads */ daemon->thread_pool = g_thread_pool_new (job_handler_callback, daemon, max_threads, FALSE, NULL); /* TODO: verify thread_pool != NULL in a nicer way */ g_assert (daemon->thread_pool != NULL); g_mutex_init (&daemon->lock); daemon->mount_counter = 0; daemon->jobs = NULL; daemon->registered_paths = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)registered_path_free); /* This is where we store active client connections so when a new filter is registered, * we re-register them on all active connections */ daemon->client_connections = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL); daemon->conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); g_assert (daemon->conn != NULL); daemon->auth_observer = g_dbus_auth_observer_new (); g_signal_connect (daemon->auth_observer, "allow-mechanism", G_CALLBACK (allow_mechanism_cb), NULL); g_signal_connect (daemon->auth_observer, "authorize-authenticated-peer", G_CALLBACK (authorize_authenticated_peer_cb), NULL); daemon->daemon_skeleton = gvfs_dbus_daemon_skeleton_new (); g_signal_connect (daemon->daemon_skeleton, "handle-get-connection", G_CALLBACK (handle_get_connection), daemon); g_signal_connect (daemon->daemon_skeleton, "handle-cancel", G_CALLBACK (handle_cancel), daemon); g_signal_connect (daemon->daemon_skeleton, "handle-list-monitor-implementations", G_CALLBACK (handle_list_monitor_implementations), daemon); error = NULL; if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (daemon->daemon_skeleton), daemon->conn, G_VFS_DBUS_DAEMON_PATH, &error)) { g_warning ("Error exporting daemon interface: %s (%s, %d)\n", error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); } daemon->mountable_skeleton = gvfs_dbus_mountable_skeleton_new (); g_signal_connect (daemon->mountable_skeleton, "handle-mount", G_CALLBACK (daemon_handle_mount), daemon); error = NULL; if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (daemon->mountable_skeleton), daemon->conn, G_VFS_DBUS_MOUNTABLE_PATH, &error)) { g_warning ("Error exporting mountable interface: %s (%s, %d)\n", error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); } } static void g_vfs_daemon_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void g_vfs_daemon_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } GVfsDaemon * g_vfs_daemon_new (gboolean main_daemon, gboolean replace) { GVfsDaemon *daemon; GDBusConnection *conn; GError *error; error = NULL; conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); if (!conn) { g_printerr ("Failed to connect to the D-BUS daemon: %s (%s, %d)\n", error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); return NULL; } daemon = g_object_new (G_VFS_TYPE_DAEMON, NULL); daemon->main_daemon = main_daemon; if (! main_daemon) { daemon->name_watcher = g_bus_watch_name_on_connection (conn, G_VFS_DBUS_DAEMON_NAME, G_BUS_NAME_WATCHER_FLAGS_AUTO_START, name_appeared_handler, name_vanished_handler, daemon, NULL); } g_object_unref (conn); return daemon; } void g_vfs_daemon_set_max_threads (GVfsDaemon *daemon, gint max_threads) { g_thread_pool_set_max_threads (daemon->thread_pool, max_threads, NULL); } static gboolean exit_at_idle (GVfsDaemon *daemon) { g_signal_emit (daemon, signals[SHUTDOWN], 0); return FALSE; } static void daemon_unschedule_exit (GVfsDaemon *daemon) { if (daemon->exit_tag != 0) { g_source_remove (daemon->exit_tag); daemon->exit_tag = 0; } } static void daemon_schedule_exit (GVfsDaemon *daemon) { if (daemon->exit_tag == 0) daemon->exit_tag = g_timeout_add_seconds (1, (GSourceFunc)exit_at_idle, daemon); } static void job_source_new_job_callback (GVfsJobSource *job_source, GVfsJob *job, GVfsDaemon *daemon) { g_vfs_daemon_queue_job (daemon, job); } static void job_source_closed_callback (GVfsJobSource *job_source, GVfsDaemon *daemon) { g_mutex_lock (&daemon->lock); daemon->job_sources = g_list_remove (daemon->job_sources, job_source); g_signal_handlers_disconnect_by_func (job_source, (GCallback)job_source_new_job_callback, daemon); g_signal_handlers_disconnect_by_func (job_source, (GCallback)job_source_closed_callback, daemon); g_object_unref (job_source); if (daemon->job_sources == NULL) daemon_schedule_exit (daemon); g_mutex_unlock (&daemon->lock); } static void re_register_jobs_cb (GVfsBackend *backend, GAsyncResult *res, gpointer user_data) { GError *error = NULL; g_vfs_backend_register_mount_finish (backend, res, &error); g_debug ("re_register_jobs_cb, error: %p\n", error); g_clear_error (&error); } static void g_vfs_daemon_re_register_job_sources (GVfsDaemon *daemon) { GList *l; g_mutex_lock (&daemon->lock); for (l = daemon->job_sources; l != NULL; l = l->next) { if (G_VFS_IS_BACKEND (l->data)) { GVfsBackend *backend = G_VFS_BACKEND (l->data); /* Only re-register if we registered before, not e.g if we're currently mounting. */ if (g_vfs_backend_is_mounted (backend)) g_vfs_backend_register_mount (backend, (GAsyncReadyCallback) re_register_jobs_cb, NULL); } } g_mutex_unlock (&daemon->lock); } void g_vfs_daemon_add_job_source (GVfsDaemon *daemon, GVfsJobSource *job_source) { g_debug ("Added new job source %p (%s)\n", job_source, g_type_name_from_instance ((gpointer)job_source)); g_mutex_lock (&daemon->lock); daemon_unschedule_exit (daemon); g_object_ref (job_source); daemon->job_sources = g_list_append (daemon->job_sources, job_source); g_signal_connect (job_source, "new_job", (GCallback)job_source_new_job_callback, daemon); g_signal_connect (job_source, "closed", (GCallback)job_source_closed_callback, daemon); g_mutex_unlock (&daemon->lock); } static void unref_skeleton (gpointer object) { GDBusInterfaceSkeleton *skeleton = object; g_dbus_interface_skeleton_unexport (skeleton); g_object_unref (skeleton); } static void peer_register_skeleton (const gchar *obj_path, RegisteredPath *reg_path, GDBusConnection *dbus_conn) { GDBusInterfaceSkeleton *skeleton; if (! g_hash_table_contains (reg_path->client_skeletons, dbus_conn)) { skeleton = reg_path->callback (dbus_conn, obj_path, reg_path->data); g_hash_table_insert (reg_path->client_skeletons, dbus_conn, skeleton); } else { /* Interface skeleton has been already registered on the connection, skipping */ } } static void client_conn_register_skeleton (GDBusConnection *dbus_conn, gpointer value, RegisteredPath *reg_path) { peer_register_skeleton (reg_path->obj_path, reg_path, dbus_conn); } /* This registers a dbus interface skeleton on *all* connections, client and session bus */ /* The object path needs to be unique globally. */ void g_vfs_daemon_register_path (GVfsDaemon *daemon, const char *obj_path, GVfsRegisterPathCallback callback, gpointer user_data) { RegisteredPath *data; data = g_new0 (RegisteredPath, 1); data->obj_path = g_strdup (obj_path); data->callback = callback; data->data = user_data; data->client_skeletons = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)unref_skeleton); g_hash_table_insert (daemon->registered_paths, g_strdup (obj_path), data); /* Export the newly registered interface skeleton on session bus */ /* TODO: change the way we export skeletons on connections once * https://bugzilla.gnome.org/show_bug.cgi?id=662718 is in place. */ data->session_skeleton = callback (daemon->conn, obj_path, user_data); /* Export this newly registered path to all active client connections */ g_hash_table_foreach (daemon->client_connections, (GHFunc) client_conn_register_skeleton, data); } void g_vfs_daemon_unregister_path (GVfsDaemon *daemon, const char *obj_path) { g_hash_table_remove (daemon->registered_paths, obj_path); } /* NOTE: Might be emitted on a thread */ static void job_new_source_callback (GVfsJob *job, GVfsJobSource *job_source, GVfsDaemon *daemon) { g_vfs_daemon_add_job_source (daemon, job_source); } /* NOTE: Might be emitted on a thread */ static void job_finished_callback (GVfsJob *job, GVfsDaemon *daemon) { g_signal_handlers_disconnect_by_func (job, (GCallback)job_new_source_callback, daemon); g_signal_handlers_disconnect_by_func (job, (GCallback)job_finished_callback, daemon); g_mutex_lock (&daemon->lock); daemon->jobs = g_list_remove (daemon->jobs, job); g_mutex_unlock (&daemon->lock); g_object_unref (job); } void g_vfs_daemon_queue_job (GVfsDaemon *daemon, GVfsJob *job) { g_debug ("Queued new job %p (%s)\n", job, g_type_name_from_instance ((gpointer)job)); g_object_ref (job); g_signal_connect (job, "finished", (GCallback)job_finished_callback, daemon); g_signal_connect (job, "new_source", (GCallback)job_new_source_callback, daemon); g_mutex_lock (&daemon->lock); daemon->jobs = g_list_prepend (daemon->jobs, job); g_mutex_unlock (&daemon->lock); /* Can we start the job immediately / async */ if (!g_vfs_job_try (job)) { /* Couldn't finish / run async, queue worker thread */ g_thread_pool_push (daemon->thread_pool, job, NULL); /* TODO: Check error */ } } static void peer_unregister_skeleton (const gchar *obj_path, RegisteredPath *reg_path, GDBusConnection *dbus_conn) { g_hash_table_remove (reg_path->client_skeletons, dbus_conn); } static void peer_connection_closed (GDBusConnection *connection, gboolean remote_peer_vanished, GError *error, gpointer user_data) { GVfsDaemon *daemon = G_VFS_DAEMON (user_data); GList *l; GVfsDBusDaemon *daemon_skeleton; GVfsJob *job_to_cancel; do { job_to_cancel = NULL; g_mutex_lock (&daemon->lock); for (l = daemon->jobs; l != NULL; l = l->next) { GVfsJob *job = G_VFS_JOB (l->data); if (G_VFS_IS_JOB_DBUS (job) && !g_vfs_job_is_cancelled (job) && G_VFS_JOB_DBUS (job)->invocation && g_dbus_method_invocation_get_connection (G_VFS_JOB_DBUS (job)->invocation) == connection) { job_to_cancel = g_object_ref (job); break; } } g_mutex_unlock (&daemon->lock); if (job_to_cancel) { g_vfs_job_cancel (job_to_cancel); g_object_unref (job_to_cancel); } } while (job_to_cancel != NULL); daemon_skeleton = g_object_get_data (G_OBJECT (connection), "daemon_skeleton"); /* daemon_skeleton should be always valid in this case */ g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (daemon_skeleton)); /* Unexport the registered interface skeletons */ g_hash_table_foreach (daemon->registered_paths, (GHFunc) peer_unregister_skeleton, connection); /* The peer-to-peer connection was disconnected */ g_signal_handlers_disconnect_by_data (connection, user_data); g_hash_table_remove (daemon->client_connections, connection); } static void daemon_peer_connection_setup (GVfsDaemon *daemon, GDBusConnection *dbus_conn) { GVfsDBusDaemon *daemon_skeleton; GError *error; daemon_skeleton = gvfs_dbus_daemon_skeleton_new (); g_signal_connect (daemon_skeleton, "handle-cancel", G_CALLBACK (handle_cancel), daemon); error = NULL; if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (daemon_skeleton), dbus_conn, G_VFS_DBUS_DAEMON_PATH, &error)) { g_warning ("Failed to accept client: %s, %s (%s, %d)", "object registration failed", error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); return; } g_object_set_data_full (G_OBJECT (dbus_conn), "daemon_skeleton", daemon_skeleton, (GDestroyNotify) g_object_unref); /* Export registered interface skeletons on this new connection */ g_hash_table_foreach (daemon->registered_paths, (GHFunc) peer_register_skeleton, dbus_conn); g_hash_table_insert (daemon->client_connections, g_object_ref (dbus_conn), NULL); g_signal_connect (dbus_conn, "closed", G_CALLBACK (peer_connection_closed), daemon); } static void generate_address (gchar **address, gchar **socket_path) { gchar tmp[16] = "socket-"; gchar *socket_dir; gvfs_randomize_string (tmp + 7, 8); tmp[15] = '\0'; socket_dir = gvfs_get_socket_dir (); *socket_path = g_build_filename (socket_dir, tmp, NULL); *address = g_strdup_printf ("unix:path=%s", *socket_path); g_free (socket_dir); } static gboolean daemon_new_connection_func (GDBusServer *server, GDBusConnection *connection, gpointer user_data) { GVfsDaemon *daemon = user_data; daemon_peer_connection_setup (daemon, connection); /* Kill the server, no more need for it */ g_dbus_server_stop (server); g_object_unref (server); return TRUE; } static gboolean handle_get_connection (GVfsDBusDaemon *object, GDBusMethodInvocation *invocation, gpointer user_data) { GVfsDaemon *daemon = G_VFS_DAEMON (user_data); GDBusServer *server; GError *error; gchar *address1; gchar *socket_path; gchar *guid; const char *pkexec_uid; generate_address (&address1, &socket_path); guid = g_dbus_generate_guid (); error = NULL; server = g_dbus_server_new_sync (address1, G_DBUS_SERVER_FLAGS_NONE, guid, daemon->auth_observer, NULL, /* GCancellable */ &error); g_free (guid); if (server == NULL) { g_dbus_method_invocation_return_gerror (invocation, error); g_printerr ("daemon: Error creating server at address %s: %s\n", address1, error->message); g_error_free (error); goto error_out; } g_dbus_server_start (server); /* This is needed for gvfsd-admin to ensure correct ownership. */ pkexec_uid = g_getenv ("PKEXEC_UID"); if (pkexec_uid != NULL) { uid_t uid; uid = strtol (pkexec_uid, NULL, 10); if (uid != 0) if (chown (socket_path, uid, (gid_t)-1) < 0) g_warning ("Failed to change socket ownership: %s", g_strerror (errno)); } g_signal_connect (server, "new-connection", G_CALLBACK (daemon_new_connection_func), daemon); gvfs_dbus_daemon_complete_get_connection (object, invocation, address1, ""); g_free (address1); g_free (socket_path); return TRUE; error_out: g_free (address1); g_unlink (socket_path); g_free (socket_path); return TRUE; } static gboolean handle_cancel (GVfsDBusDaemon *object, GDBusMethodInvocation *invocation, guint arg_serial, gpointer user_data) { GVfsDaemon *daemon = G_VFS_DAEMON (user_data); GList *l; GVfsJob *job_to_cancel = NULL; g_mutex_lock (&daemon->lock); for (l = daemon->jobs; l != NULL; l = l->next) { GVfsJob *job = G_VFS_JOB (l->data); if (G_VFS_IS_JOB_DBUS (job) && g_vfs_job_dbus_is_serial (G_VFS_JOB_DBUS (job), g_dbus_method_invocation_get_connection (invocation), arg_serial)) { job_to_cancel = g_object_ref (job); break; } } g_mutex_unlock (&daemon->lock); if (job_to_cancel) { g_vfs_job_cancel (job_to_cancel); g_object_unref (job_to_cancel); } gvfs_dbus_daemon_complete_cancel (object, invocation); return TRUE; } static gboolean handle_list_monitor_implementations (GVfsDBusDaemon *object, GDBusMethodInvocation *invocation, gpointer user_data) { GList *impls, *l; GVariantBuilder builder; impls = g_vfs_list_monitor_implementations (); g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ssbia{sv})")); for (l = impls; l != NULL; l = l->next) { GVfsMonitorImplementation *impl = l->data; g_variant_builder_add_value (&builder, g_vfs_monitor_implementation_to_dbus (impl)); } g_list_free_full (impls, (GDestroyNotify)g_vfs_monitor_implementation_free); gvfs_dbus_daemon_complete_list_monitor_implementations (object, invocation, g_variant_builder_end (&builder)); return TRUE; } static gboolean daemon_handle_mount (GVfsDBusMountable *object, GDBusMethodInvocation *invocation, GVariant *arg_mount_spec, gboolean arg_automount, GVariant *arg_mount_source, gpointer user_data) { GVfsDaemon *daemon = G_VFS_DAEMON (user_data); GMountSpec *mount_spec; GMountSource *mount_source; mount_spec = g_mount_spec_from_dbus (arg_mount_spec); if (mount_spec == NULL) g_dbus_method_invocation_return_error_literal (invocation, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Error in mount spec"); else { mount_source = g_mount_source_from_dbus (arg_mount_source); g_vfs_daemon_initiate_mount (daemon, mount_spec, mount_source, arg_automount, object, invocation); g_object_unref (mount_source); g_mount_spec_unref (mount_spec); } return TRUE; } void g_vfs_daemon_initiate_mount (GVfsDaemon *daemon, GMountSpec *mount_spec, GMountSource *mount_source, gboolean is_automount, GVfsDBusMountable *object, GDBusMethodInvocation *invocation) { const char *type; GType backend_type; char *obj_path; GVfsJob *job; GVfsBackend *backend; type = g_mount_spec_get_type (mount_spec); backend_type = G_TYPE_INVALID; if (type) backend_type = g_vfs_lookup_backend (type); if (backend_type == G_TYPE_INVALID) { if (invocation) g_dbus_method_invocation_return_error_literal (invocation, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid backend type"); else g_warning ("Error mounting: invalid backend type\n"); return; } obj_path = g_strdup_printf ("/org/gtk/vfs/mount/%d", ++daemon->mount_counter); backend = g_object_new (backend_type, "daemon", daemon, "object-path", obj_path, NULL); g_free (obj_path); g_vfs_daemon_add_job_source (daemon, G_VFS_JOB_SOURCE (backend)); g_object_unref (backend); job = g_vfs_job_mount_new (mount_spec, mount_source, is_automount, object, invocation, backend); g_vfs_daemon_queue_job (daemon, job); g_object_unref (job); } /** * g_vfs_daemon_get_blocking_processes: * @daemon: A #GVfsDaemon. * * Gets all processes that blocks unmounting, e.g. processes with open * file handles. Returned array could be empty in spite of * g_vfs_daemon_has_blocking_processes returns TRUE, because for jobs without * channel we can't get #GPid. * * Returns: An array of #GPid. Free with g_array_unref(). */ GArray * g_vfs_daemon_get_blocking_processes (GVfsDaemon *daemon) { GArray *processes; GList *l; g_mutex_lock (&daemon->lock); processes = g_array_new (FALSE, FALSE, sizeof (GPid)); for (l = daemon->job_sources; l != NULL; l = l->next) { if (G_VFS_IS_CHANNEL (l->data)) { GPid pid; pid = g_vfs_channel_get_actual_consumer (G_VFS_CHANNEL (l->data)); g_array_append_val (processes, pid); } } g_mutex_unlock (&daemon->lock); return processes; } /** * g_vfs_daemon_has_blocking_processes: * @daemon: A #GVfsDaemon. * * Determines if there are any jobs blocking unmounting, all jobs excepting * unmount job. * * Returns: TRUE if there are any blocking processes, or FALSE otherwise. */ gboolean g_vfs_daemon_has_blocking_processes (GVfsDaemon *daemon) { GList *l; g_mutex_lock (&daemon->lock); for (l = daemon->jobs; l != NULL; l = l->next) { if (!G_VFS_IS_JOB_UNMOUNT (l->data)) { g_debug ("blocking job: %p\n", l->data); g_mutex_unlock (&daemon->lock); return TRUE; } } g_mutex_unlock (&daemon->lock); return FALSE; } void g_vfs_daemon_run_job_in_thread (GVfsDaemon *daemon, GVfsJob *job) { g_thread_pool_push (daemon->thread_pool, job, NULL); /* TODO: Check error */ } void g_vfs_daemon_close_active_channels (GVfsDaemon *daemon, GVfsBackend *backend) { GList *l; GVfsChannel *channel_to_close; do { channel_to_close = NULL; g_mutex_lock (&daemon->lock); for (l = daemon->job_sources; l != NULL; l = l->next) { if (G_VFS_IS_CHANNEL (l->data) && g_vfs_channel_get_backend (G_VFS_CHANNEL (l->data)) == backend) { channel_to_close = g_object_ref (G_VFS_CHANNEL (l->data)); break; } } g_mutex_unlock (&daemon->lock); if (channel_to_close) { g_vfs_channel_force_close (channel_to_close); g_object_unref (channel_to_close); } } while (channel_to_close != NULL); }