diff options
Diffstat (limited to 'gnome-2-24/daemon/gvfsbackenddnssd.c')
-rw-r--r-- | gnome-2-24/daemon/gvfsbackenddnssd.c | 731 |
1 files changed, 731 insertions, 0 deletions
diff --git a/gnome-2-24/daemon/gvfsbackenddnssd.c b/gnome-2-24/daemon/gvfsbackenddnssd.c new file mode 100644 index 00000000..3a224a50 --- /dev/null +++ b/gnome-2-24/daemon/gvfsbackenddnssd.c @@ -0,0 +1,731 @@ +/* GIO - GLib Input, Output and Streaming Library + * Original work, Copyright (C) 2003 Red Hat, Inc + * GVFS port, Copyright (c) 2008 Andrew Walton. + * + * 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 Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * Alexander Larsson <alexl@redhat.com> + */ + +#include <config.h> + +#include <string.h> +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gurifuncs.h> +#include <gio/gio.h> + +#include <avahi-client/client.h> +#include <avahi-client/lookup.h> +#include <avahi-common/error.h> +#include <avahi-common/timeval.h> +#include <avahi-glib/glib-watch.h> +#include <avahi-glib/glib-malloc.h> + +#include "gvfsbackenddnssd.h" + +#include "gvfsdaemonprotocol.h" +#include "gvfsjobcreatemonitor.h" +#include "gvfsjobenumerate.h" +#include "gvfsjobqueryinfo.h" +#include "gvfsmonitor.h" + +static struct { + char *type; + char *method; + char *icon; + gpointer handle; +} dns_sd_types[] = { + {"_ftp._tcp", "ftp", "folder-remote-ftp"}, + {"_webdav._tcp", "dav", "folder-remote"}, + {"_webdavs._tcp", "davs", "folder-remote"}, + {"_sftp-ssh._tcp", "sftp", "folder-remote-ssh"}, +}; + +static AvahiClient *global_client = NULL; +static gboolean avahi_initialized = FALSE; + +static GList *dnssd_backends = NULL; + +typedef struct { + char *file_name; + char *name; + char *type; + char *target_uri; + GIcon *icon; +} LinkFile; + +static LinkFile root = { "/" }; + +struct _GVfsBackendDnsSd +{ + GVfsBackend parent_instance; + GVfsMonitor *root_monitor; + char *domain; + GMountSpec *mount_spec; + GList *files; /* list of LinkFiles */ + + GList *browsers; +}; + +typedef struct _GVfsBackendDnsSd GVfsBackendDnsSd; + +G_DEFINE_TYPE (GVfsBackendDnsSd, g_vfs_backend_dns_sd, G_VFS_TYPE_BACKEND) + +static void add_browsers (GVfsBackendDnsSd *backend); +static void remove_browsers (GVfsBackendDnsSd *backend); +static AvahiClient *get_global_avahi_client (void); + +/* Callback for state changes on the Client */ +static void +avahi_client_callback (AvahiClient *client, AvahiClientState state, void *userdata) +{ + /* We need to set this early, as the add_browsers call below may reenter + when this is called from the client creation call */ + if (global_client == NULL) + global_client = client; + + if (state == AVAHI_CLIENT_FAILURE) + { + if (avahi_client_errno (client) == AVAHI_ERR_DISCONNECTED) + { + /* Remove the service browsers from the handles */ + g_list_foreach (dnssd_backends, (GFunc)remove_browsers, NULL); + + /* Destroy old client */ + avahi_client_free (client); + global_client = NULL; + avahi_initialized = FALSE; + + /* Reconnect */ + get_global_avahi_client (); + } + } + else if (state == AVAHI_CLIENT_S_RUNNING) + { + /* Start browsing again */ + g_list_foreach (dnssd_backends, (GFunc)add_browsers, NULL); + } +} + +static AvahiClient * +get_global_avahi_client (void) +{ + static AvahiGLibPoll *glib_poll = NULL; + int error; + + if (!avahi_initialized) + { + avahi_initialized = TRUE; + + if (glib_poll == NULL) + { + avahi_set_allocator (avahi_glib_allocator ()); + glib_poll = avahi_glib_poll_new (NULL, G_PRIORITY_DEFAULT); + } + + /* Create a new AvahiClient instance */ + global_client = avahi_client_new (avahi_glib_poll_get (glib_poll), + AVAHI_CLIENT_NO_FAIL, + avahi_client_callback, + glib_poll, + &error); + + if (global_client == NULL) + { + /* Print out the error string */ + g_warning ("Error initializing Avahi: %s", avahi_strerror (error)); + return NULL; + } + } + + return global_client; +} + +static GIcon * +get_icon_for_type (const char *type) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (dns_sd_types); i++) + { + if (strcmp (type, dns_sd_types[i].type) == 0) + return g_themed_icon_new (dns_sd_types[i].icon); + } + + return g_themed_icon_new ("text-x-generic"); +} + +static const char * +get_method_for_type (const char *type) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (dns_sd_types); i++) + { + if (strcmp (type, dns_sd_types[i].type) == 0) + return dns_sd_types[i].method; + } + + return NULL; +} + +static char * +encode_filename (const char *service, + const char *type) +{ + GString *string; + const char *p; + + string = g_string_new (NULL); + + p = service; + + while (*p) + { + if (*p == '\\') + g_string_append (string, "\\\\"); + else if (*p == '.') + g_string_append (string, "\\."); + else if (*p == '/') + g_string_append (string, "\\s"); + else + g_string_append_c (string, *p); + p++; + } + + g_string_append_c (string, '.'); + g_string_append (string, type); + + return g_string_free (string, FALSE); +} + +static LinkFile * +link_file_new (const char *name, + const char *type, + const char *domain, + const char *host_name, + AvahiProtocol protocol, + const AvahiAddress *address, + uint16_t port, + AvahiStringList *txt) +{ + LinkFile *file; + char *path, *user, *user_str; + AvahiStringList *path_l, *user_l; + char a[128]; + const char *method; + + file = g_slice_new0 (LinkFile); + + file->name = g_strdup (name); + file->type = g_strdup (type); + file->file_name = encode_filename (name, type); + file->icon = get_icon_for_type (type); + + + path = NULL; + user_str = NULL; + if (txt != NULL) + { + path_l = avahi_string_list_find (txt, "path"); + if (path_l != NULL) + avahi_string_list_get_pair (path_l, NULL, &path, NULL); + + user_l = avahi_string_list_find (txt, "u"); + if (user_l != NULL) + { + avahi_string_list_get_pair (user_l, NULL, &user, NULL); + + user_str = g_strconcat (user, "@", NULL); + } + } + + if (path == NULL) + path = g_strdup ("/"); + + avahi_address_snprint (a, sizeof(a), address); + + method = get_method_for_type (type); + + if (protocol == AVAHI_PROTO_INET6) + /* an ipv6 address, follow rfc2732 */ + file->target_uri = g_strdup_printf ("%s://%s[%s]:%d%s", + method, + user_str?user_str:"", + a, port, path); + else + file->target_uri = g_strdup_printf ("%s://%s%s:%d%s", + method, + user_str?user_str:"", + a, port, path); + g_free (user_str); + g_free (path); + + return file; +} + +static void +link_file_free (LinkFile *file) +{ + g_free (file->file_name); + g_free (file->name); + g_free (file->type); + g_free (file->target_uri); + + if (file->icon) + g_object_unref (file->icon); + + g_slice_free (LinkFile, file); +} + +static LinkFile * +lookup_link_file_by_name_and_type (GVfsBackendDnsSd *backend, + const char *name, + const char *type) +{ + GList *l; + LinkFile *file; + + for (l = backend->files; l != NULL; l = l->next) + { + file = l->data; + if (strcmp (file->name, name) == 0 && + strcmp (file->type, type) == 0) + return file; + } + + return NULL; +} + +static LinkFile * +lookup_link_file (GVfsBackendDnsSd *backend, + GVfsJob *job, + const char *file_name) +{ + GList *l; + LinkFile *file; + + if (*file_name != '/') + goto out; + + while (*file_name == '/') + file_name++; + + if (*file_name == 0) + return &root; + + if (strchr (file_name, '/') != NULL) + goto out; + + for (l = backend->files; l != NULL; l = l->next) + { + file = l->data; + if (strcmp (file->file_name, file_name) == 0) + return file; + } + + out: + g_vfs_job_failed (job, G_IO_ERROR, + G_IO_ERROR_NOT_FOUND, + _("File doesn't exist")); + + return NULL; +} + + +static void +file_info_from_file (LinkFile *file, + GFileInfo *info) +{ + g_return_if_fail (file != NULL || info != NULL); + + g_file_info_set_name (info, file->file_name); + g_file_info_set_display_name (info, file->name); + + if (file->icon) + g_file_info_set_icon (info, file->icon); + + g_file_info_set_file_type (info, G_FILE_TYPE_SHORTCUT); + g_file_info_set_size(info, 0); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_VIRTUAL, TRUE); + g_file_info_set_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_TARGET_URI, + file->target_uri); +} + +/* Backend Functions */ +static gboolean +try_enumerate (GVfsBackend *backend, + GVfsJobEnumerate *job, + const char *file_name, + GFileAttributeMatcher *attribute_matcher, + GFileQueryInfoFlags flags) +{ + LinkFile *file; + GList *l; + GFileInfo *info; + + file = lookup_link_file (G_VFS_BACKEND_DNS_SD (backend), + G_VFS_JOB (job), file_name); + + if (file != &root) + { + if (file != NULL) + g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, + G_IO_ERROR_NOT_DIRECTORY, + _("The file is not a directory")); + return TRUE; + } + + g_vfs_job_succeeded (G_VFS_JOB(job)); + + /* Enumerate root */ + for (l = G_VFS_BACKEND_DNS_SD (backend)->files; l != NULL; l = l->next) + { + file = l->data; + info = g_file_info_new (); + file_info_from_file (file, info); + g_vfs_job_enumerate_add_info (job, info); + g_object_unref (info); + } + + g_vfs_job_enumerate_done (job); + + return TRUE; +} + +static gboolean +try_query_info (GVfsBackend *backend, + GVfsJobQueryInfo *job, + const char *file_name, + GFileQueryInfoFlags flags, + GFileInfo *info, + GFileAttributeMatcher *matcher) +{ + LinkFile *file; + + file = lookup_link_file (G_VFS_BACKEND_DNS_SD (backend), + G_VFS_JOB (job), file_name); + + if (file == &root) + { + GIcon *icon; + g_file_info_set_name (info, "/"); + g_file_info_set_file_type (info, G_FILE_TYPE_DIRECTORY); + /* TODO: Name */ + g_file_info_set_display_name (info, _("dns-sd")); + icon = g_themed_icon_new ("network-workgroup"); + g_file_info_set_icon (info, icon); + g_object_unref (icon); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE, FALSE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_DELETE, FALSE); + g_file_info_set_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_TRASH, FALSE); + g_file_info_set_content_type (info, "inode/directory"); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } + else if (file != NULL) + { + file_info_from_file (file, info); + g_vfs_job_succeeded (G_VFS_JOB (job)); + } + + return TRUE; +} + + +static void +resolve_callback (AvahiServiceResolver *r, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiResolverEvent event, + const char *name, + const char *type, + const char *domain, + const char *host_name, + const AvahiAddress *address, + uint16_t port, + AvahiStringList *txt, + AvahiLookupResultFlags flags, + void *userdata) +{ + GVfsBackendDnsSd *backend = userdata; + LinkFile *file; + char *path; + + if (event == AVAHI_RESOLVER_FAILURE) + return; + + /* Link-local ipv6 address, can't make a uri from this, ignore */ + if (address->proto == AVAHI_PROTO_INET6 && + address->data.ipv6.address[0] == 0xfe && + address->data.ipv6.address[1] == 0x80) + return; + + file = lookup_link_file_by_name_and_type (backend, + name, type); + + if (file != NULL) + return; + + file = link_file_new (name, type, domain, host_name, protocol, + address, port, txt); + + backend->files = g_list_prepend (backend->files, file); + + path = g_strconcat ("/", file->file_name, NULL); + g_vfs_monitor_emit_event (backend->root_monitor, + G_FILE_MONITOR_EVENT_CREATED, + path, + NULL); + g_free (path); +} + +static void +browse_callback (AvahiServiceBrowser *b, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *type, + const char *domain, + AvahiLookupResultFlags flags, + void *userdata) +{ + GVfsBackendDnsSd *backend = userdata; + AvahiServiceResolver *sr; + AvahiClient *client; + LinkFile *file; + char *path; + + switch (event) + { + case AVAHI_BROWSER_FAILURE: + break; + + case AVAHI_BROWSER_NEW: + client = get_global_avahi_client (); + + /* We ignore the returned resolver object. In the callback + function we free it. If the server is terminated before + the callback function is called the server will free + the resolver for us. */ + + sr = avahi_service_resolver_new (client, interface, protocol, + name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolve_callback, backend); + + if (sr == NULL) + g_warning ("Failed to resolve service name '%s': %s\n", name, avahi_strerror (avahi_client_errno (client))); + + break; + + case AVAHI_BROWSER_REMOVE: + file = lookup_link_file_by_name_and_type (backend, + name, type); + + if (file != NULL) + { + backend->files = g_list_remove (backend->files, file); + + path = g_strconcat ("/", file->file_name, NULL); + g_vfs_monitor_emit_event (backend->root_monitor, + G_FILE_MONITOR_EVENT_DELETED, + path, + NULL); + g_free (path); + + link_file_free (file); + } + + break; + + case AVAHI_BROWSER_ALL_FOR_NOW: + case AVAHI_BROWSER_CACHE_EXHAUSTED: + break; + } +} + +static void +browse_type (GVfsBackendDnsSd *backend, + const char *type) +{ + AvahiClient *client; + AvahiServiceBrowser *sb; + const char *domain; + + client = get_global_avahi_client (); + + domain = NULL; + if (strcmp (backend->domain, "local") != 0) + domain = backend->domain; + + sb = avahi_service_browser_new (client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + type, domain, 0, browse_callback, backend); + + if (sb == NULL) + { + g_warning ("Failed to create service browser: %s\n", avahi_strerror( avahi_client_errno (client))); + return; + } + + backend->browsers = g_list_prepend (backend->browsers, sb); + +} + +static void +add_browsers (GVfsBackendDnsSd *backend) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (dns_sd_types); i++) + browse_type (backend, dns_sd_types[i].type); +} + +static void +remove_browsers (GVfsBackendDnsSd *backend) +{ + g_list_free (backend->browsers); + backend->browsers = NULL; +} + +static gboolean +try_mount (GVfsBackend *backend, + GVfsJobMount *job, + GMountSpec *mount_spec, + GMountSource *mount_source, + gboolean is_automount) +{ + GVfsBackendDnsSd *op_backend = G_VFS_BACKEND_DNS_SD (backend); + GMountSpec *real_mount_spec; + const char *domain; + + domain = g_mount_spec_get (mount_spec, "host"); + + if (domain == NULL) + { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, + "No domain specified for dns-sd share"); + return TRUE; + } + + op_backend->domain = g_strdup (domain); + + if (get_global_avahi_client() == NULL) + { + g_vfs_job_failed (G_VFS_JOB (job), + G_IO_ERROR, G_IO_ERROR_FAILED, + "Unable to initialize avahi"); + return TRUE; + } + + real_mount_spec = g_mount_spec_new ("dns-sd"); + g_mount_spec_set (real_mount_spec, "host", op_backend->domain); + g_vfs_backend_set_mount_spec (backend, real_mount_spec); + op_backend->mount_spec = real_mount_spec; + + op_backend->root_monitor = g_vfs_monitor_new (backend); + + g_vfs_job_succeeded (G_VFS_JOB (job)); + + return TRUE; +} + +/* handles both file and dir monitors, + * as we really don't "support" (e.g. fire events for) either, yet. */ +static gboolean +try_create_monitor (GVfsBackend *backend, + GVfsJobCreateMonitor *job, + const char *file_name, + GFileMonitorFlags flags) +{ + LinkFile *file; + GVfsBackendDnsSd *network_backend; + + network_backend = G_VFS_BACKEND_DNS_SD (backend); + + file = lookup_link_file (network_backend, G_VFS_JOB (job), file_name); + + if (file != &root) + { + g_vfs_job_failed (G_VFS_JOB (job), G_IO_ERROR, + G_IO_ERROR_NOT_SUPPORTED, + _("Can't monitor file or directory.")); + return TRUE; + } + + g_vfs_job_create_monitor_set_monitor (job, network_backend->root_monitor); + g_vfs_job_succeeded (G_VFS_JOB (job)); + + return TRUE; +} + +static void +g_vfs_backend_dns_sd_init (GVfsBackendDnsSd *network_backend) +{ + GVfsBackend *backend = G_VFS_BACKEND (network_backend); + + dnssd_backends = g_list_prepend (dnssd_backends, backend); + + /* TODO: Names, etc */ + g_vfs_backend_set_display_name (backend, _("Dns-SD")); + g_vfs_backend_set_stable_name (backend, _("Network")); + g_vfs_backend_set_icon_name (backend, "network-workgroup"); + g_vfs_backend_set_user_visible (backend, FALSE); + +} + +static void +g_vfs_backend_dns_sd_finalize (GObject *object) +{ + GVfsBackendDnsSd *backend; + + backend = G_VFS_BACKEND_DNS_SD (object); + + dnssd_backends = g_list_remove (dnssd_backends, backend); + + if (backend->mount_spec) + g_mount_spec_unref (backend->mount_spec); + + if (backend->root_monitor) + g_object_unref (backend->root_monitor); + + g_free (backend->domain); + + g_list_foreach (backend->files, (GFunc)link_file_free, NULL); + + if (G_OBJECT_CLASS (g_vfs_backend_dns_sd_parent_class)->finalize) + (*G_OBJECT_CLASS (g_vfs_backend_dns_sd_parent_class)->finalize) (object); +} + +static void +g_vfs_backend_dns_sd_class_init (GVfsBackendDnsSdClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GVfsBackendClass *backend_class = G_VFS_BACKEND_CLASS (klass); + + gobject_class->finalize = g_vfs_backend_dns_sd_finalize; + + backend_class->try_mount = try_mount; + backend_class->try_query_info = try_query_info; + backend_class->try_enumerate = try_enumerate; + backend_class->try_create_dir_monitor = try_create_monitor; + backend_class->try_create_file_monitor = try_create_monitor; +} + |