summaryrefslogtreecommitdiff
path: root/gnome-2-24/client/gvfsfusedaemon.c
diff options
context:
space:
mode:
Diffstat (limited to 'gnome-2-24/client/gvfsfusedaemon.c')
-rw-r--r--gnome-2-24/client/gvfsfusedaemon.c2207
1 files changed, 2207 insertions, 0 deletions
diff --git a/gnome-2-24/client/gvfsfusedaemon.c b/gnome-2-24/client/gvfsfusedaemon.c
new file mode 100644
index 00000000..ef3314c3
--- /dev/null
+++ b/gnome-2-24/client/gvfsfusedaemon.c
@@ -0,0 +1,2207 @@
+/* gvfsfusedaemon.c - FUSE file system mapping daemon for GVFS
+ *
+ * Copyright (C) 2007-2008 Hans Petter Jansson
+ *
+ * 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: Hans Petter Jansson <hpj@novell.com>
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/vfs.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <dbus/dbus.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gprintf.h>
+#include <gio/gio.h>
+
+#include <glib/gurifuncs.h>
+
+/* stuff from common/ */
+#include <gdaemonmount.h>
+#include <gvfsdaemonprotocol.h>
+#include <gdbusutils.h>
+
+#define FUSE_USE_VERSION 26
+#include <fuse.h>
+
+#define DEBUG_ENABLED 0
+
+#define GET_FILE_HANDLE(fi) ((gpointer) (fi)->fh)
+#define SET_FILE_HANDLE(fi, fh) ((fi)->fh = (guint64) (fh))
+
+typedef struct {
+ time_t creation_time;
+ char *name;
+ GFile *root;
+} MountRecord;
+
+typedef enum {
+ FILE_OP_NONE,
+ FILE_OP_READ,
+ FILE_OP_WRITE
+} FileOp;
+
+typedef struct {
+ gint refcount;
+
+ GMutex *mutex;
+ gchar *path;
+ FileOp op;
+ gpointer stream;
+ gint length;
+ size_t pos;
+} FileHandle;
+
+static GThread *subthread = NULL;
+static GMainLoop *subthread_main_loop = NULL;
+static GVfs *gvfs = NULL;
+
+static GVolumeMonitor *volume_monitor = NULL;
+
+/* Contains pointers to MountRecord */
+static GList *mount_list = NULL;
+static GMutex *mount_list_mutex;
+
+static time_t daemon_creation_time;
+static uid_t daemon_uid;
+static gid_t daemon_gid;
+
+static GStaticMutex global_mutex = G_STATIC_MUTEX_INIT;
+static GHashTable *global_fh_table = NULL;
+
+/* ------- *
+ * Helpers *
+ * ------- */
+
+static void
+debug_print (const gchar *message, ...)
+{
+#if DEBUG_ENABLED
+
+ static FILE *debug_fhd = NULL;
+ va_list var_args;
+ char *file;
+
+ if (!debug_fhd)
+ {
+ file = g_build_filename (g_get_home_dir (), "vfs.debug", NULL);
+ debug_fhd = fopen (file, "at");
+ g_free (file);
+ }
+
+ if (!debug_fhd)
+ return;
+
+ va_start (var_args, message);
+ g_vfprintf (debug_fhd, message, var_args);
+ va_end (var_args);
+
+ fflush (debug_fhd);
+
+#endif
+}
+
+typedef struct {
+ gint gioerror;
+ gint errno_value;
+} ErrorMap;
+
+static gint
+errno_from_error (GError *error)
+{
+ gint i;
+ static const ErrorMap error_map [] =
+ {
+ { G_IO_ERROR_FAILED, EIO },
+ { G_IO_ERROR_NOT_FOUND, ENOENT },
+ { G_IO_ERROR_EXISTS, EEXIST },
+ { G_IO_ERROR_IS_DIRECTORY, EISDIR },
+ { G_IO_ERROR_NOT_DIRECTORY, ENOTDIR },
+ { G_IO_ERROR_NOT_EMPTY, ENOTEMPTY },
+ { G_IO_ERROR_NOT_REGULAR_FILE, EIO },
+ { G_IO_ERROR_NOT_SYMBOLIC_LINK, EIO },
+ { G_IO_ERROR_NOT_MOUNTABLE_FILE, EIO },
+ { G_IO_ERROR_FILENAME_TOO_LONG, ENAMETOOLONG },
+ { G_IO_ERROR_INVALID_FILENAME, EIO },
+ { G_IO_ERROR_TOO_MANY_LINKS, ELOOP },
+ { G_IO_ERROR_NO_SPACE, ENOSPC },
+ { G_IO_ERROR_INVALID_ARGUMENT, EINVAL },
+ { G_IO_ERROR_PERMISSION_DENIED, EACCES },
+ { G_IO_ERROR_NOT_SUPPORTED, ENOTSUP },
+ { G_IO_ERROR_NOT_MOUNTED, EIO },
+ { G_IO_ERROR_ALREADY_MOUNTED, EIO },
+ { G_IO_ERROR_CLOSED, EIO },
+ { G_IO_ERROR_CANCELLED, EIO },
+ { G_IO_ERROR_PENDING, EIO },
+ { G_IO_ERROR_READ_ONLY, EACCES },
+ { G_IO_ERROR_CANT_CREATE_BACKUP, EIO },
+ { G_IO_ERROR_WRONG_ETAG, EIO },
+ { G_IO_ERROR_TIMED_OUT, EIO },
+ { G_IO_ERROR_BUSY, EBUSY },
+ { -1, -1 }
+ };
+
+ if (error->domain != G_IO_ERROR)
+ return EIO;
+
+ for (i = 0; error_map [i].gioerror >= 0; i++)
+ {
+ if (error_map [i].gioerror == error->code)
+ break;
+ }
+
+ if (error_map [i].gioerror >= 0)
+ return error_map [i].errno_value;
+
+ return EIO;
+}
+
+static FileHandle *
+file_handle_new (const gchar *path)
+{
+ FileHandle *file_handle;
+
+ file_handle = g_new0 (FileHandle, 1);
+ file_handle->refcount = 1;
+ file_handle->mutex = g_mutex_new ();
+ file_handle->op = FILE_OP_NONE;
+ file_handle->path = g_strdup (path);
+
+ return file_handle;
+}
+
+static FileHandle *
+file_handle_ref (FileHandle *file_handle)
+{
+ g_atomic_int_inc (&file_handle->refcount);
+ return file_handle;
+}
+
+static void
+file_handle_unref (FileHandle *file_handle)
+{
+ if (g_atomic_int_dec_and_test (&file_handle->refcount))
+ {
+ g_static_mutex_lock (&global_mutex);
+
+ /* Test again, since e.g. get_file_handle_for_path() might have
+ * snatched the global mutex and revived the file handle between
+ * g_atomic_int_dec_and_test() and us obtaining the lock. */
+
+ if (g_atomic_int_get (&file_handle->refcount) == 0)
+ g_hash_table_remove (global_fh_table, file_handle->path);
+
+ g_static_mutex_unlock (&global_mutex);
+ }
+}
+
+static void
+file_handle_close_stream (FileHandle *file_handle)
+{
+ debug_print ("file_handle_close_stream\n");
+ if (file_handle->stream)
+ {
+ switch (file_handle->op)
+ {
+ case FILE_OP_READ:
+ g_input_stream_close (file_handle->stream, NULL, NULL);
+ break;
+
+ case FILE_OP_WRITE:
+ g_output_stream_close (file_handle->stream, NULL, NULL);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_object_unref (file_handle->stream);
+ file_handle->stream = NULL;
+ file_handle->op = FILE_OP_NONE;
+ }
+}
+
+/* Called on hash table removal */
+static void
+file_handle_free (FileHandle *file_handle)
+{
+ file_handle_close_stream (file_handle);
+ g_mutex_free (file_handle->mutex);
+ g_free (file_handle->path);
+ g_free (file_handle);
+}
+
+static FileHandle *
+get_file_handle_for_path (const gchar *path)
+{
+ FileHandle *fh;
+
+ g_static_mutex_lock (&global_mutex);
+
+ fh = g_hash_table_lookup (global_fh_table, path);
+ if (fh)
+ file_handle_ref (fh);
+
+ g_static_mutex_unlock (&global_mutex);
+
+ return fh;
+}
+
+static FileHandle *
+get_or_create_file_handle_for_path (const gchar *path)
+{
+ FileHandle *fh;
+
+ g_static_mutex_lock (&global_mutex);
+
+ fh = g_hash_table_lookup (global_fh_table, path);
+
+ if (fh)
+ {
+ file_handle_ref (fh);
+ }
+ else
+ {
+ fh = file_handle_new (path);
+ g_hash_table_insert (global_fh_table, fh->path, fh);
+ }
+
+ g_static_mutex_unlock (&global_mutex);
+
+ return fh;
+}
+
+static void
+reindex_file_handle_for_path (const gchar *old_path, const gchar *new_path)
+{
+ gchar *old_path_internal;
+ FileHandle *fh;
+
+ g_static_mutex_lock (&global_mutex);
+
+ if (!g_hash_table_lookup_extended (global_fh_table, old_path,
+ (gpointer *) &old_path_internal,
+ (gpointer *) &fh))
+ goto out;
+
+ g_hash_table_steal (global_fh_table, old_path);
+
+ g_free (fh->path);
+ fh->path = g_strdup (new_path);
+
+ g_hash_table_insert (global_fh_table, fh->path, fh);
+
+ out:
+ g_static_mutex_unlock (&global_mutex);
+}
+
+static MountRecord *
+mount_record_new (GMount *mount)
+{
+ MountRecord *mount_record;
+ char *name;
+
+ mount_record = g_new (MountRecord, 1);
+
+ mount_record->root = g_mount_get_root (mount);
+ name = g_object_get_data (G_OBJECT (mount), "g-stable-name");
+ if (name != NULL && *name != 0)
+ name = g_strdup (name);
+ else
+ name = g_mount_get_name (mount);
+
+ /* Keep in sync with gvfs daemon mount tracker */
+ mount_record->name = g_uri_escape_string (name, "+@#$., ", TRUE);
+ g_free (name);
+ mount_record->creation_time = time (NULL);
+
+ return mount_record;
+}
+
+static void
+mount_record_free (MountRecord *mount_record)
+{
+ g_object_unref (mount_record->root);
+ g_free (mount_record->name);
+ g_free (mount_record);
+}
+
+static void
+mount_list_lock (void)
+{
+ g_mutex_lock (mount_list_mutex);
+}
+
+static void
+mount_list_unlock (void)
+{
+ g_mutex_unlock (mount_list_mutex);
+}
+
+static void
+mount_list_free (void)
+{
+ g_list_foreach (mount_list, (GFunc) mount_record_free, NULL);
+ g_list_free (mount_list);
+ mount_list = NULL;
+}
+
+static gboolean
+mount_record_for_mount_exists (GMount *mount)
+{
+ GList *l;
+ GFile *root;
+ gboolean res;
+
+ g_assert (mount != NULL);
+
+ root = g_mount_get_root (mount);
+
+ res = FALSE;
+
+ mount_list_lock ();
+
+ for (l = mount_list; l != NULL; l = l->next)
+ {
+ MountRecord *this_mount_record = l->data;
+
+ if (g_file_equal (root, this_mount_record->root))
+ {
+ res = TRUE;
+ break;
+ }
+ }
+
+ mount_list_unlock ();
+
+ g_object_unref (root);
+
+ return res;
+}
+
+static GFile *
+mount_record_find_root_by_mount_name (const gchar *mount_name)
+{
+ GList *l;
+ GFile *root;
+
+ g_assert (mount_name != NULL);
+
+ root = NULL;
+
+ mount_list_lock ();
+
+ for (l = mount_list; l != NULL; l = l->next)
+ {
+ MountRecord *mount_record = l->data;
+
+ if (strcmp (mount_name, mount_record->name) == 0)
+ {
+ root = g_object_ref (mount_record->root);
+ break;
+ }
+ }
+
+ mount_list_unlock ();
+
+ return root;
+}
+
+static void
+mount_list_update (void)
+{
+ GList *mounts;
+ GList *l;
+
+ mounts = g_volume_monitor_get_mounts (volume_monitor);
+
+ for (l = mounts; l != NULL; l = l->next)
+ {
+ GMount *mount = l->data;
+
+ if (!mount_record_for_mount_exists (mount))
+ {
+ mount_list_lock ();
+ mount_list = g_list_prepend (mount_list, mount_record_new (mount));
+ mount_list_unlock ();
+ }
+
+ g_object_unref (mount);
+ }
+
+ g_list_free (mounts);
+}
+
+#if 0
+
+static gint
+file_info_get_attribute_as_int (GFileInfo *file_info, const gchar *attribute)
+{
+ GFileAttributeType attribute_type;
+ gint int_result;
+
+ attribute_type = g_file_info_get_attribute_type (file_info, attribute);
+
+ switch (attribute_type)
+ {
+ case G_FILE_ATTRIBUTE_TYPE_UINT32:
+ int_result = g_file_info_get_attribute_uint32 (file_info, attribute);
+ break;
+
+ case G_FILE_ATTRIBUTE_TYPE_INT32:
+ int_result = g_file_info_get_attribute_int32 (file_info, attribute);
+ break;
+
+ case G_FILE_ATTRIBUTE_TYPE_UINT64:
+ int_result = g_file_info_get_attribute_uint64 (file_info, attribute);
+ break;
+
+ case G_FILE_ATTRIBUTE_TYPE_INT64:
+ int_result = g_file_info_get_attribute_int64 (file_info, attribute);
+ break;
+
+ default:
+ int_result = 0;
+ g_assert_not_reached ();
+ break;
+ }
+
+ return int_result;
+}
+
+#endif
+
+static guint
+file_info_get_attribute_as_uint (GFileInfo *file_info, const gchar *attribute)
+{
+ GFileAttributeType attribute_type;
+ guint uint_result;
+
+ attribute_type = g_file_info_get_attribute_type (file_info, attribute);
+
+ switch (attribute_type)
+ {
+ case G_FILE_ATTRIBUTE_TYPE_UINT32:
+ uint_result = g_file_info_get_attribute_uint32 (file_info, attribute);
+ break;
+
+ case G_FILE_ATTRIBUTE_TYPE_INT32:
+ uint_result = g_file_info_get_attribute_int32 (file_info, attribute);
+ break;
+
+ case G_FILE_ATTRIBUTE_TYPE_UINT64:
+ uint_result = g_file_info_get_attribute_uint64 (file_info, attribute);
+ break;
+
+ case G_FILE_ATTRIBUTE_TYPE_INT64:
+ uint_result = g_file_info_get_attribute_int64 (file_info, attribute);
+ break;
+
+ default:
+ uint_result = 0;
+ g_assert_not_reached ();
+ break;
+ }
+
+ return uint_result;
+}
+
+static gboolean
+path_is_mount_list (const gchar *path)
+{
+ while (*path == '/')
+ path++;
+
+ return *path == 0;
+}
+
+
+static GFile *
+file_from_full_path (const gchar *path)
+{
+ gchar *mount_name;
+ GFile *file = NULL;
+ const gchar *s1, *s2;
+ GFile *root;
+
+ file = NULL;
+
+ s1 = path;
+ while (*s1 == '/')
+ s1++;
+
+ if (*s1)
+ {
+ s2 = strchr (s1, '/');
+ if (s2 == NULL)
+ s2 = s1 + strlen (s1);
+
+ mount_name = g_strndup (s1, s2 - s1);
+ root = mount_record_find_root_by_mount_name (mount_name);
+ g_free (mount_name);
+
+ if (root)
+ {
+ while (*s2 == '/')
+ s2++;
+ file = g_file_resolve_relative_path (root, s2);
+ g_object_unref (root);
+ }
+ }
+
+ return file;
+}
+
+/* ------------- *
+ * VFS functions *
+ * ------------- */
+
+#if 0
+
+/* A stat -f <path> on a FUSE mount always results in this function being called with a path
+ * of '/'. This means we can't return valid info for the various mounts. Hopefully we can
+ * fix this in the future. */
+
+static gint
+vfs_statfs (const gchar *path, struct statvfs *stbuf)
+{
+ GFile *file;
+ GError *error = NULL;
+ gint result = 0;
+
+ debug_print ("vfs_statfs: %s\n", path);
+
+ if ((file = file_from_full_path (path)))
+ {
+ GFileInfo *file_info;
+
+ file_info = g_file_get_filesystem_info (file, "*", NULL, &error);
+
+ if (file_info)
+ {
+ memset (stbuf, 0, sizeof (*stbuf));
+
+ stbuf->f_bsize = 4096;
+ stbuf->f_frsize = 4096; /* Ignored by FUSE */
+ stbuf->f_blocks = 0;
+ stbuf->f_bfree = 0;
+ stbuf->f_bavail = 0;
+ stbuf->f_files = 0;
+ stbuf->f_ffree = 0;
+ stbuf->f_favail = 0; /* Ignored by FUSE */
+ stbuf->f_fsid = 1; /* Ignored by FUSE */
+ stbuf->f_flag = 0; /* Ignored by FUSE */
+ stbuf->f_namemax = 1024;
+
+ if (g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE))
+ stbuf->f_blocks = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE) / 4096;
+ if (g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE))
+ stbuf->f_bfree = stbuf->f_bavail = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_FILESYSTEM_FREE) / 4096;
+ }
+ else if (error)
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ else
+ {
+ result = -EIO;
+ }
+
+ g_object_unref (file);
+ }
+
+ debug_print ("vfs_statfs: -> %s\n", strerror (-result));
+
+ return result;
+}
+
+#endif
+
+static gint
+vfs_statfs (const gchar *path, struct statvfs *stbuf)
+{
+ gint result = 0;
+
+ debug_print ("vfs_statfs: %s\n", path);
+
+ memset (stbuf, 0, sizeof (*stbuf));
+
+ stbuf->f_bsize = 4096;
+ stbuf->f_frsize = 4096; /* Ignored by FUSE */
+ stbuf->f_blocks = 0;
+ stbuf->f_bfree = 0;
+ stbuf->f_bavail = 0;
+ stbuf->f_files = 0;
+ stbuf->f_ffree = 0;
+ stbuf->f_favail = 0; /* Ignored by FUSE */
+ stbuf->f_fsid = 1; /* Ignored by FUSE */
+ stbuf->f_flag = 0; /* Ignored by FUSE */
+ stbuf->f_namemax = 1024;
+
+ debug_print ("vfs_statfs: -> %s\n", strerror (-result));
+
+ return result;
+}
+
+static mode_t
+file_info_get_stat_mode (GFileInfo *file_info)
+{
+ GFileType file_type;
+ mode_t unix_mode;
+
+ file_type = g_file_info_get_file_type (file_info);
+
+ switch (file_type)
+ {
+ case G_FILE_TYPE_REGULAR:
+ unix_mode = S_IFREG;
+ break;
+
+ case G_FILE_TYPE_DIRECTORY:
+ case G_FILE_TYPE_MOUNTABLE:
+ unix_mode = S_IFDIR;
+ break;
+
+ case G_FILE_TYPE_SYMBOLIC_LINK:
+ case G_FILE_TYPE_SHORTCUT:
+ unix_mode = S_IFLNK;
+ break;
+
+ case G_FILE_TYPE_SPECIAL:
+ default:
+ unix_mode = 0;
+ break;
+ }
+
+ if (file_type == G_FILE_TYPE_DIRECTORY ||
+ !g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ) ||
+ g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ))
+ unix_mode |= S_IRUSR;
+ if (!g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) ||
+ g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+ unix_mode |= S_IWUSR;
+ if (file_type == G_FILE_TYPE_DIRECTORY ||
+ !g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE) ||
+ g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
+ unix_mode |= S_IXUSR;
+
+ return unix_mode;
+}
+
+static gint
+getattr_for_file (GFile *file, struct stat *sbuf)
+{
+ GFileInfo *file_info;
+ GError *error = NULL;
+ gint result = 0;
+
+ file_info = g_file_query_info (file, "*", 0, NULL, &error);
+
+ if (file_info)
+ {
+ GTimeVal mod_time;
+
+ sbuf->st_mode = file_info_get_stat_mode (file_info);
+ sbuf->st_size = g_file_info_get_size (file_info);
+ sbuf->st_uid = daemon_uid;
+ sbuf->st_gid = daemon_gid;
+
+ g_file_info_get_modification_time (file_info, &mod_time);
+ sbuf->st_mtime = mod_time.tv_sec;
+ sbuf->st_ctime = mod_time.tv_sec;
+ sbuf->st_atime = mod_time.tv_sec;
+
+ if (g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_TIME_CHANGED))
+ sbuf->st_ctime = file_info_get_attribute_as_uint (file_info, G_FILE_ATTRIBUTE_TIME_CHANGED);
+ if (g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_TIME_ACCESS))
+ sbuf->st_atime = file_info_get_attribute_as_uint (file_info, G_FILE_ATTRIBUTE_TIME_ACCESS);
+
+ /* Setting st_nlink to 1 for directories makes 'find' work */
+ sbuf->st_nlink = 1;
+
+ g_object_unref (file_info);
+ }
+ else
+ {
+ if (error)
+ {
+ debug_print ("Error from GVFS: %s\n", error->message);
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ else
+ {
+ debug_print ("No file info, but no error from GVFS.\n");
+ result = -EIO;
+ }
+ }
+
+ return result;
+}
+
+static gint
+vfs_getattr (const gchar *path, struct stat *sbuf)
+{
+ GFile *file;
+ gint result = 0;
+ FileHandle *fh;
+
+ debug_print ("vfs_getattr: %s\n", path);
+
+ fh = get_file_handle_for_path (path);
+ if (fh)
+ g_mutex_lock (fh->mutex);
+
+ memset (sbuf, 0, sizeof (*sbuf));
+
+ sbuf->st_dev = 0; /* dev_t ID of device containing file */
+ sbuf->st_ino = 0; /* ino_t inode number */
+ sbuf->st_uid = 0; /* uid_t user ID of owner */
+ sbuf->st_gid = 0; /* gid_t group ID of owner */
+ sbuf->st_rdev = 0; /* dev_t device ID (if special file) */
+ sbuf->st_size = 0; /* off_t total size, in bytes */
+ sbuf->st_blocks = 0; /* blkcnt_t number of blocks allocated */
+ sbuf->st_atime = 0; /* time_t time of last access */
+ sbuf->st_mtime = 0; /* time_t time of last modification */
+ sbuf->st_ctime = 0; /* time_t time of last status change */
+ sbuf->st_blksize = 4096; /* blksize_t blocksize for filesystem I/O */
+
+ if (path_is_mount_list (path))
+ {
+ /* Mount list */
+
+ sbuf->st_mode = S_IFDIR | 0500; /* mode_t protection */
+ sbuf->st_nlink = 2 + g_list_length (mount_list); /* nlink_t number of hard links */
+ sbuf->st_atime = daemon_creation_time;
+ sbuf->st_mtime = daemon_creation_time;
+ sbuf->st_ctime = daemon_creation_time;
+ sbuf->st_uid = daemon_uid;
+ sbuf->st_gid = daemon_gid;
+ }
+ else if ((file = file_from_full_path (path)))
+ {
+ /* Submount */
+
+ result = getattr_for_file (file, sbuf);
+ g_object_unref (file);
+ }
+ else
+ {
+ result = -ENOENT;
+ }
+
+ if (fh)
+ {
+ g_mutex_unlock (fh->mutex);
+ file_handle_unref (fh);
+ }
+
+ debug_print ("vfs_getattr: -> %s\n", strerror (-result));
+
+ return result;
+}
+
+static gint
+vfs_readlink (const gchar *path, gchar *target, size_t size)
+{
+ debug_print ("vfs_readlink: %s\n", path);
+
+ return 0;
+}
+
+static gint
+setup_input_stream (GFile *file, FileHandle *fh)
+{
+ GError *error = NULL;
+ gint result = 0;
+
+ if (fh->stream)
+ {
+ debug_print ("setup_input_stream: have stream\n");
+
+ if (fh->op == FILE_OP_READ)
+ {
+ debug_print ("setup_input_stream: doing read\n");
+ }
+ else
+ {
+ debug_print ("setup_input_stream: doing write\n");
+
+ g_output_stream_close (fh->stream, NULL, NULL);
+ g_object_unref (fh->stream);
+ fh->stream = NULL;
+ }
+ }
+
+ if (!fh->stream)
+ {
+ debug_print ("setup_input_stream: no stream\n");
+ fh->stream = g_file_read (file, NULL, &error);
+ fh->pos = 0;
+ }
+
+ if (fh->stream)
+ fh->op = FILE_OP_READ;
+ else
+ fh->op = FILE_OP_NONE;
+
+ if (error)
+ {
+ debug_print ("setup_input_stream: error\n");
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+
+ return result;
+}
+
+static gint
+setup_output_stream (GFile *file, FileHandle *fh)
+{
+ GError *error = NULL;
+ gint result = 0;
+
+ if (fh->stream)
+ {
+ if (fh->op == FILE_OP_WRITE)
+ {
+ }
+ else
+ {
+ g_input_stream_close (fh->stream, NULL, NULL);
+ g_object_unref (fh->stream);
+ fh->stream = NULL;
+ }
+ }
+
+ if (!fh->stream)
+ {
+ fh->stream = g_file_append_to (file, 0, NULL, &error);
+ if (fh->stream)
+ fh->pos = g_seekable_tell (G_SEEKABLE (fh->stream));
+ }
+
+ if (fh->stream)
+ fh->op = FILE_OP_WRITE;
+ else
+ fh->op = FILE_OP_NONE;
+
+ if (error)
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+
+ return result;
+}
+
+static gint
+vfs_open (const gchar *path, struct fuse_file_info *fi)
+{
+ GFile *file;
+ gint result = 0;
+
+ debug_print ("vfs_open: %s\n", path);
+
+ if (path_is_mount_list (path))
+ result = -EISDIR;
+ else if ((file = file_from_full_path (path)))
+ {
+ GFileInfo *file_info;
+ GError *error = NULL;
+
+ file_info = g_file_query_info (file, "*", 0, NULL, &error);
+
+ if (file_info)
+ {
+ GFileType file_type = g_file_info_get_file_type (file_info);
+
+ if (file_type == G_FILE_TYPE_REGULAR)
+ {
+ FileHandle *fh = get_or_create_file_handle_for_path (path);
+
+ /* File exists */
+
+ SET_FILE_HANDLE (fi, fh);
+
+ debug_print ("vfs_open: flags=%o\n", fi->flags);
+
+ /* Set up a stream here, so we can check for errors */
+
+ g_mutex_lock (fh->mutex);
+
+ if (fi->flags & O_WRONLY || fi->flags & O_RDWR)
+ result = setup_output_stream (file, fh);
+ else
+ result = setup_input_stream (file, fh);
+
+ g_mutex_unlock (fh->mutex);
+
+ /* The added reference to the file handle is released in vfs_release() */
+ }
+ else if (file_type == G_FILE_TYPE_DIRECTORY)
+ {
+ /* EISDIR is supposedly only for attempts to write to directory handles,
+ * but outside readdir(), we don't support reading them either. */
+ result = -EISDIR;
+ }
+ else
+ {
+ result = -EACCES;
+ }
+
+ g_object_unref (file_info);
+ }
+ else
+ {
+ if (error)
+ {
+ debug_print ("Error from GVFS: %s\n", error->message);
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ else
+ {
+ debug_print ("No file info, but no error from GVFS.\n");
+ result = -EIO;
+ }
+ }
+
+ g_object_unref (file);
+ }
+ else
+ {
+ result = -ENOENT;
+ }
+
+ debug_print ("vfs_open: -> %s\n", strerror (-result));
+
+ return result;
+}
+
+static gint
+vfs_create (const gchar *path, mode_t mode, struct fuse_file_info *fi)
+{
+ GFile *file;
+ gint result = 0;
+
+ debug_print ("vfs_create: %s\n", path);
+
+ if (path_is_mount_list (path))
+ result = -EEXIST;
+ if ((file = file_from_full_path (path)))
+ {
+ GFileInfo *file_info;
+ GError *error = NULL;
+
+ file_info = g_file_query_info (file, "*", 0, NULL, &error);
+
+ if (file_info)
+ {
+ result = -EEXIST;
+ g_object_unref (file_info);
+ }
+ else
+ {
+ GFileOutputStream *file_output_stream;
+
+ if (error)
+ {
+ g_error_free (error);
+ error = NULL;
+ }
+
+ file_output_stream = g_file_create (file, 0, NULL, &error);
+ if (file_output_stream)
+ {
+ FileHandle *fh = get_or_create_file_handle_for_path (path);
+
+ /* Success */
+
+ SET_FILE_HANDLE (fi, fh);
+
+ g_mutex_lock (fh->mutex);
+
+ file_handle_close_stream (fh);
+ fh->stream = file_output_stream;
+ fh->op = FILE_OP_WRITE;
+
+ g_mutex_unlock (fh->mutex);
+
+ /* The added reference to the file handle is released in vfs_release() */
+ }
+ else
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ }
+
+ g_object_unref (file);
+ }
+ else
+ {
+ result = -ENOENT;
+ }
+
+ debug_print ("vfs_create: -> %s\n", strerror (-result));
+
+ return result;
+}
+
+static gint
+vfs_release (const gchar *path, struct fuse_file_info *fi)
+{
+ FileHandle *fh = GET_FILE_HANDLE (fi);
+
+ debug_print ("vfs_release: %s\n", path);
+
+ if (fh)
+ file_handle_unref (fh);
+
+ return 0;
+}
+
+static gint
+read_stream (FileHandle *fh, gchar *output_buf, size_t output_buf_size, off_t offset)
+{
+ GInputStream *input_stream;
+ gint n_bytes_skipped = 0;
+ gint n_bytes_read = 0;
+ gint result = 0;
+ GError *error = NULL;
+
+ input_stream = fh->stream;
+
+ if (offset != fh->pos)
+ {
+ if (g_seekable_can_seek (G_SEEKABLE (input_stream)))
+ {
+ /* Can seek */
+
+ debug_print ("read_stream: seeking to offset %d.\n", offset);
+
+ if (g_seekable_seek (G_SEEKABLE (input_stream), offset, G_SEEK_SET, NULL, &error))
+ {
+ fh->pos = offset;
+ }
+ else
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ }
+ else if (offset > fh->pos)
+ {
+ /* Can skip ahead */
+
+ debug_print ("read_stream: skipping to offset %d.\n", offset);
+
+ n_bytes_skipped = g_input_stream_skip (input_stream, offset - fh->pos, NULL, &error);
+
+ if (n_bytes_skipped > 0)
+ fh->pos += n_bytes_skipped;
+
+ if (n_bytes_skipped != offset - fh->pos)
+ {
+ if (error)
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ else
+ {
+ result = -EIO;
+ }
+ }
+ }
+ else
+ {
+ /* Can't seek, can't skip backwards */
+
+ debug_print ("read_stream: can't seek nor skip to offset %d!\n", offset);
+
+ result = -ENOTSUP;
+ }
+ }
+
+ if (result == 0)
+ {
+ while (n_bytes_read < output_buf_size)
+ {
+ gboolean part_result;
+ gsize part_bytes_read = 0;
+
+ part_result = g_input_stream_read_all (input_stream,
+ output_buf + n_bytes_read,
+ output_buf_size - n_bytes_read,
+ &part_bytes_read,
+ NULL,
+ &error);
+
+ n_bytes_read += part_bytes_read;
+ fh->pos += part_bytes_read;
+
+ if (!part_result || part_bytes_read == 0)
+ break;
+ }
+
+ result = n_bytes_read;
+
+ if (n_bytes_read < output_buf_size)
+ {
+ debug_print ("read_stream: wanted %d bytes, but got %d.\n", output_buf_size, n_bytes_read);
+
+ if (error)
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ }
+ }
+
+ return result;
+}
+
+static gint
+vfs_read (const gchar *path, gchar *buf, size_t size,
+ off_t offset, struct fuse_file_info *fi)
+{
+ GFile *file;
+ gint result = 0;
+
+ debug_print ("vfs_read: %s\n", path);
+
+ if ((file = file_from_full_path (path)))
+ {
+ FileHandle *fh = GET_FILE_HANDLE (fi);
+
+ g_mutex_lock (fh->mutex);
+
+ result = setup_input_stream (file, fh);
+
+ if (result == 0)
+ {
+ result = read_stream (fh, buf, size, offset);
+ }
+ else
+ {
+ debug_print ("vfs_read: failed to setup input_stream!\n");
+ }
+
+ g_object_unref (file);
+ g_mutex_unlock (fh->mutex);
+ }
+ else
+ {
+ result = -EIO;
+ }
+
+ if (result < 0)
+ debug_print ("vfs_read: -> %s\n", strerror (-result));
+ else
+ debug_print ("vfs_read: -> %d bytes read.\n", result);
+
+ return result;
+}
+
+static gint
+write_stream (FileHandle *fh, const gchar *input_buf, size_t input_buf_size, off_t offset)
+{
+ GOutputStream *output_stream;
+ gint n_bytes_written = 0;
+ gint result = 0;
+ GError *error = NULL;
+
+ debug_print ("write_stream: %d bytes at offset %d.\n", input_buf_size, offset);
+
+ output_stream = fh->stream;
+
+ if (offset != fh->pos)
+ {
+ if (g_seekable_can_seek (G_SEEKABLE (output_stream)))
+ {
+ /* Can seek */
+
+ if (g_seekable_seek (G_SEEKABLE (output_stream), offset, G_SEEK_SET, NULL, &error))
+ {
+ fh->pos = offset;
+ }
+ else
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ }
+ else
+ {
+ /* Can't seek, and output streams can't skip */
+
+ result = -ENOTSUP;
+ }
+ }
+
+ if (result == 0)
+ {
+ while (n_bytes_written < input_buf_size)
+ {
+ gboolean part_result;
+ gsize part_bytes_written = 0;
+
+ part_result = g_output_stream_write_all (output_stream,
+ (void *) (input_buf + n_bytes_written),
+ input_buf_size - n_bytes_written,
+ &part_bytes_written,
+ NULL,
+ &error);
+
+ n_bytes_written += part_bytes_written;
+ fh->pos += part_bytes_written;
+
+ if (!part_result)
+ break;
+ }
+
+ result = n_bytes_written;
+
+ if (n_bytes_written < input_buf_size)
+ {
+ if (error)
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ }
+ else if (!g_output_stream_flush (output_stream, NULL, &error))
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ }
+
+ return result;
+}
+
+static gint
+vfs_write (const gchar *path, const gchar *buf, size_t len, off_t offset,
+ struct fuse_file_info *fi)
+{
+ GFile *file;
+ gint result = 0;
+
+ debug_print ("vfs_write: %s\n", path);
+
+ if ((file = file_from_full_path (path)))
+ {
+ FileHandle *fh = GET_FILE_HANDLE (fi);
+
+ g_mutex_lock (fh->mutex);
+
+ result = setup_output_stream (file, fh);
+ if (result == 0)
+ {
+ result = write_stream (fh, buf, len, offset);
+ }
+
+ g_object_unref (file);
+ g_mutex_unlock (fh->mutex);
+ }
+ else
+ {
+ result = -EIO;
+ }
+
+ if (result < 0)
+ debug_print ("vfs_write: -> %s\n", strerror (-result));
+ else
+ debug_print ("vfs_write: -> %d bytes written.\n", result);
+
+ return result;
+}
+
+static gint
+vfs_flush (const gchar *path, struct fuse_file_info *fi)
+{
+ debug_print ("vfs_flush: %s\n", path);
+
+ return 0;
+}
+
+static gint
+vfs_opendir (const gchar *path, struct fuse_file_info *fi)
+{
+ GFile *file;
+ gint result = 0;
+
+ debug_print ("vfs_opendir: %s\n", path);
+
+ if (path_is_mount_list (path))
+ {
+ /* Mount list */
+ }
+ else if ((file = file_from_full_path (path)) != NULL)
+ {
+ /* Submount */
+
+ /* TODO: Check that path exists */
+
+ g_object_unref (file);
+ }
+ else
+ {
+ /* Not found */
+
+ result = -ENOENT;
+ }
+
+ return result;
+}
+
+static gint
+readdir_for_file (GFile *base_file, gpointer buf, fuse_fill_dir_t filler)
+{
+ GFileEnumerator *enumerator;
+ GFileInfo *file_info;
+ GError *error = NULL;
+
+ g_assert (base_file != NULL);
+
+ enumerator = g_file_enumerate_children (base_file, G_FILE_ATTRIBUTE_STANDARD_NAME, 0, NULL, &error);
+ if (!enumerator)
+ {
+ gint result;
+
+ if (error)
+ {
+ debug_print ("Error from GVFS: %s\n", error->message);
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ else
+ {
+ debug_print ("No file info, but no error from GVFS.\n");
+ result = -EIO;
+ }
+
+ return result;
+ }
+
+ filler (buf, ".", NULL, 0);
+ filler (buf, "..", NULL, 0);
+
+ while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &error)) != NULL)
+ {
+ filler (buf, g_file_info_get_name (file_info), NULL, 0);
+ g_object_unref (file_info);
+ }
+
+ g_object_unref (enumerator);
+
+ return 0;
+}
+
+static gint
+vfs_readdir (const gchar *path, gpointer buf, fuse_fill_dir_t filler, off_t offset,
+ struct fuse_file_info *fi)
+{
+ GFile *base_file;
+ gint result = 0;
+
+ debug_print ("vfs_readdir: %s\n", path);
+
+ if (path_is_mount_list (path))
+ {
+ GList *l;
+
+ /* Mount list */
+
+ filler (buf, ".", NULL, 0);
+ filler (buf, "..", NULL, 0);
+
+ mount_list_lock ();
+
+ for (l = mount_list; l; l = g_list_next (l))
+ {
+ MountRecord *mount_record = l->data;
+
+ filler (buf, mount_record->name, NULL, 0);
+ }
+
+ mount_list_unlock ();
+ }
+ else if ((base_file = file_from_full_path (path)))
+ {
+ /* Submount */
+
+ result = readdir_for_file (base_file, buf, filler);
+
+ g_object_unref (base_file);
+ }
+ else
+ {
+ /* Not found */
+
+ result = -ENOENT;
+ }
+
+ return result;
+}
+
+static gint
+vfs_rename (const gchar *old_path, const gchar *new_path)
+{
+ GFile *old_file;
+ GFile *new_file;
+ GError *error = NULL;
+ gint result = 0;
+
+ debug_print ("vfs_rename: %s -> %s\n", old_path, new_path);
+
+ old_file = file_from_full_path (old_path);
+ new_file = file_from_full_path (new_path);
+
+ if (old_file && new_file)
+ {
+ FileHandle *fh;
+
+ fh = get_file_handle_for_path (old_path);
+
+ if (fh)
+ {
+ g_mutex_lock (fh->mutex);
+ file_handle_close_stream (fh);
+ }
+
+ g_file_move (old_file, new_file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, &error);
+
+ if (error)
+ {
+ debug_print ("vfs_rename failed: %s\n", error->message);
+
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ else
+ {
+ reindex_file_handle_for_path (old_path, new_path);
+ }
+
+ if (fh)
+ {
+ g_mutex_unlock (fh->mutex);
+ file_handle_unref (fh);
+ }
+
+ if (result == -EISDIR)
+ {
+ /* This makes e.g. 'mv' fall back to a recursive copy + delete operation */
+ result = -EXDEV;
+ }
+ }
+ else
+ {
+ result = -ENOENT;
+ }
+
+ if (old_file)
+ g_object_unref (old_file);
+ if (new_file)
+ g_object_unref (new_file);
+
+ debug_print ("vfs_rename: -> %s\n", strerror (-result));
+
+ return result;
+}
+
+static gint
+vfs_unlink (const gchar *path)
+{
+ GFile *file;
+ GError *error = NULL;
+ gint result = 0;
+
+ debug_print ("vfs_unlink: %s\n", path);
+
+ file = file_from_full_path (path);
+
+ if (file)
+ {
+ FileHandle *fh;
+
+ fh = get_file_handle_for_path (path);
+
+ if (fh)
+ {
+ g_mutex_lock (fh->mutex);
+ file_handle_close_stream (fh);
+ }
+
+ g_file_delete (file, NULL, &error);
+
+ if (fh)
+ {
+ g_mutex_unlock (fh->mutex);
+ file_handle_unref (fh);
+ }
+
+ if (error)
+ {
+ debug_print ("vfs_unlink failed: %s (%s)\n", path, error->message);
+
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+
+ g_object_unref (file);
+ }
+ else
+ {
+ result = -ENOENT;
+ }
+
+ debug_print ("vfs_unlink: -> %s\n", strerror (-result));
+
+ return result;
+}
+
+static gint
+vfs_mkdir (const gchar *path, mode_t mode)
+{
+ GFile *file;
+ GError *error = NULL;
+ gint result = 0;
+
+ debug_print ("vfs_mkdir: %s\n", path);
+
+ file = file_from_full_path (path);
+
+ if (file)
+ {
+ if (g_file_make_directory (file, NULL, &error))
+ {
+ /* Ignore errors setting the mode. We already created the directory, and that's
+ * good enough. */
+ g_file_set_attribute_uint32 (file, G_FILE_ATTRIBUTE_UNIX_MODE, mode, 0, NULL, NULL);
+ }
+
+ if (error)
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+
+ g_object_unref (file);
+ }
+ else
+ {
+ result = -ENOENT;
+ }
+
+ debug_print ("vfs_mkdir: -> %s\n", strerror (-result));
+
+ return result;
+}
+
+static gint
+vfs_rmdir (const gchar *path)
+{
+ GFile *file;
+ GError *error = NULL;
+ gint result = 0;
+
+ debug_print ("vfs_rmdir: %s\n", path);
+
+ file = file_from_full_path (path);
+
+ if (file)
+ {
+ GFileInfo *file_info;
+
+ file_info = g_file_query_info (file, "*", 0, NULL, &error);
+ if (file_info)
+ {
+ if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
+ {
+ g_file_delete (file, NULL, &error);
+
+ if (error)
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ }
+ else
+ {
+ result = -ENOTDIR;
+ }
+ }
+ else
+ {
+ if (error)
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ else
+ {
+ result = -ENOENT;
+ }
+ }
+ }
+ else
+ {
+ result = -ENOENT;
+ }
+
+ debug_print ("vfs_rmdir: -> %s\n", strerror (-result));
+
+ return result;
+}
+
+static gint
+vfs_ftruncate (const gchar *path, off_t size, struct fuse_file_info *fi)
+{
+ GFile *file;
+ GError *error = NULL;
+ gint result = 0;
+
+ debug_print ("vfs_ftruncate: %s\n", path);
+
+ file = file_from_full_path (path);
+
+ if (file)
+ {
+ FileHandle *fh = GET_FILE_HANDLE (fi);
+
+ g_mutex_lock (fh->mutex);
+
+ result = setup_output_stream (file, fh);
+
+ if (result == 0)
+ {
+ if (g_seekable_can_truncate (G_SEEKABLE (fh->stream)))
+ {
+ g_seekable_truncate (fh->stream, size, NULL, &error);
+ }
+ else if (size == 0)
+ {
+ g_output_stream_close (fh->stream, NULL, NULL);
+ g_object_unref (fh->stream);
+ fh->stream = NULL;
+
+ fh->stream = g_file_replace (file, 0, FALSE, 0, NULL, &error);
+ }
+ else
+ {
+ result = -ENOTSUP;
+ }
+
+ if (error)
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ }
+
+ g_object_unref (file);
+ g_mutex_unlock (fh->mutex);
+ }
+ else
+ {
+ result = -ENOENT;
+ }
+
+ debug_print ("vfs_ftruncate: -> %s\n", strerror (-result));
+
+ return result;
+}
+
+static gint
+vfs_truncate (const gchar *path, off_t size)
+{
+ GFile *file;
+ GError *error = NULL;
+ gint result = 0;
+
+ debug_print ("vfs_truncate: %s\n", path);
+
+ file = file_from_full_path (path);
+
+ if (file)
+ {
+ GFileOutputStream *file_output_stream = NULL;
+ FileHandle *fh;
+
+ fh = get_file_handle_for_path (path);
+ if (fh)
+ g_mutex_lock (fh->mutex);
+
+ if (size == 0)
+ {
+ file_output_stream = g_file_replace (file, 0, FALSE, 0, NULL, &error);
+ }
+ else
+ {
+ file_output_stream = g_file_append_to (file, 0, NULL, &error);
+ if (file_output_stream)
+ g_seekable_truncate (G_SEEKABLE (file_output_stream), size, NULL, &error);
+ }
+
+ if (error)
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+
+ if (file_output_stream)
+ {
+ g_output_stream_close (G_OUTPUT_STREAM (file_output_stream), NULL, NULL);
+ g_object_unref (file_output_stream);
+ }
+
+ if (fh)
+ {
+ g_mutex_unlock (fh->mutex);
+ file_handle_unref (fh);
+ }
+
+ g_object_unref (file);
+ }
+ else
+ {
+ result = -ENOENT;
+ }
+
+ debug_print ("vfs_truncate: -> %s\n", strerror (-result));
+
+ return result;
+}
+
+static gint
+vfs_symlink (const gchar *path_old, const gchar *path_new)
+{
+ GFile *file;
+ GError *error = NULL;
+ gint result = 0;
+
+ debug_print ("vfs_symlink: %s -> %s\n", path_new, path_old);
+
+ file = file_from_full_path (path_new);
+
+ if (file)
+ {
+ g_file_make_symbolic_link (file, path_old, NULL, &error);
+
+ if (error)
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ }
+ else
+ {
+ result = -ENOENT;
+ }
+
+ debug_print ("vfs_symlink: -> %s\n", strerror (-result));
+
+ return result;
+}
+
+static gint
+vfs_access (const gchar *path, gint mode)
+{
+ GFile *file;
+ GError *error = NULL;
+ gint result = 0;
+
+ debug_print ("vfs_access: %s\n", path);
+
+ file = file_from_full_path (path);
+
+ if (file)
+ {
+ GFileInfo *file_info;
+
+ file_info = g_file_query_info (file, "*", 0, NULL, &error);
+ if (file_info)
+ {
+ if ((mode & R_OK && (g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ) &&
+ !g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_READ))) ||
+ (mode & W_OK && (g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE) &&
+ !g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))) ||
+ (mode & X_OK && (g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE) &&
+ !g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))))
+ result = -EACCES;
+
+ g_object_unref (file_info);
+ }
+ else if (error)
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+ else
+ {
+ result = -EIO;
+ }
+
+ g_object_unref (file);
+ }
+ else if (path_is_mount_list (path))
+ {
+ result = 0;
+ }
+ else
+ {
+ result = -ENOENT;
+ }
+
+ debug_print ("vfs_access: -> %s\n", strerror (-result));
+ return result;
+}
+
+static gint
+vfs_utimens (const gchar *path, const struct timespec tv [2])
+{
+ GFile *file;
+ GError *error = NULL;
+ gint result = 0;
+
+ debug_print ("vfs_utimens: %s\n", path);
+
+ file = file_from_full_path (path);
+
+ if (file)
+ {
+ guint64 atime;
+ guint64 mtime;
+
+ if (tv)
+ {
+ atime = (guint64) tv [0].tv_sec * 1000000 + (guint64) tv [0].tv_nsec / (guint64) 1000;
+ mtime = (guint64) tv [1].tv_sec * 1000000 + (guint64) tv [1].tv_nsec / (guint64) 1000;
+ }
+ else
+ {
+ struct timeval tiv;
+
+ gettimeofday (&tiv, NULL);
+ atime = (guint64) tiv.tv_sec * (guint64) 1000000 + (guint64) tiv.tv_usec;
+ mtime = atime;
+ }
+
+ g_file_set_attribute (file, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC,
+ G_FILE_ATTRIBUTE_TYPE_UINT64, &atime,
+ 0, NULL, &error);
+
+ if (!error)
+ {
+ g_file_set_attribute (file, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC,
+ G_FILE_ATTRIBUTE_TYPE_UINT64, &mtime,
+ 0, NULL, &error);
+ }
+
+ if (error)
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+
+ g_object_unref (file);
+ }
+ else if (path_is_mount_list (path))
+ {
+ /* */
+ }
+ else
+ {
+ result = -ENOENT;
+ }
+
+ debug_print ("vfs_utimens: -> %s\n", strerror (-result));
+ return result;
+}
+
+static gint
+vfs_chmod (const gchar *path, mode_t mode)
+{
+ GFile *file;
+ GError *error = NULL;
+ gint result = 0;
+
+ file = file_from_full_path (path);
+
+ if (file)
+ {
+ g_file_set_attribute_uint32 (file, G_FILE_ATTRIBUTE_UNIX_MODE, mode, 0, NULL, &error);
+
+ if (error)
+ {
+ result = -errno_from_error (error);
+ g_error_free (error);
+ }
+
+ g_object_unref (file);
+ }
+
+ return result;
+}
+
+static void
+mount_tracker_mounted_cb (GVolumeMonitor *volume_monitor,
+ GMount *mount)
+{
+ MountRecord *mount_record;
+
+ if (mount_record_for_mount_exists (mount))
+ return;
+
+ mount_record = mount_record_new (mount);
+
+ mount_list_lock ();
+ mount_list = g_list_prepend (mount_list, mount_record);
+ mount_list_unlock ();
+}
+
+static void
+mount_tracker_unmounted_cb (GVolumeMonitor *volume_monitor,
+ GMount *mount)
+{
+ GFile *root;
+ GList *l;
+
+ root = g_mount_get_root (mount);
+
+ mount_list_lock ();
+
+ root = g_mount_get_root (mount);
+
+ for (l = mount_list; l != NULL; l = l->next)
+ {
+ MountRecord *mount_record = l->data;
+
+ if (g_file_equal (root, mount_record->root))
+ {
+ mount_list = g_list_delete_link (mount_list, l);
+ mount_record_free (mount_record);
+ break;
+ }
+ }
+
+ mount_list_unlock ();
+
+ g_object_unref (root);
+}
+
+static gpointer
+subthread_main (gpointer data)
+{
+
+ mount_list_update ();
+
+ g_signal_connect (volume_monitor, "mount_added", (GCallback) mount_tracker_mounted_cb, NULL);
+ g_signal_connect (volume_monitor, "mount_removed", (GCallback) mount_tracker_unmounted_cb, NULL);
+
+ g_main_loop_run (subthread_main_loop);
+
+ g_signal_handlers_disconnect_by_func (volume_monitor, mount_tracker_mounted_cb, NULL);
+ g_signal_handlers_disconnect_by_func (volume_monitor, mount_tracker_unmounted_cb, NULL);
+
+ g_main_loop_unref (subthread_main_loop);
+ subthread_main_loop = NULL;
+
+ g_object_unref (volume_monitor);
+ volume_monitor = NULL;
+
+ /* Tell the main thread to unmount. Using kill() is necessary according to FUSE maintainers. */
+ kill (getpid (), SIGHUP);
+
+ return NULL;
+}
+
+static DBusHandlerResult
+dbus_filter_func (DBusConnection *connection,
+ DBusMessage *message,
+ void *data)
+{
+ if (dbus_message_is_signal (message,
+ DBUS_INTERFACE_DBUS,
+ "NameOwnerChanged"))
+ {
+ const char *service, *old_owner, *new_owner;
+
+ dbus_message_get_args (message,
+ NULL,
+ DBUS_TYPE_STRING, &service,
+ DBUS_TYPE_STRING, &old_owner,
+ DBUS_TYPE_STRING, &new_owner,
+ DBUS_TYPE_INVALID);
+
+ /* Handle monitor owner going away */
+ if (service != NULL &&
+ strcmp (G_VFS_DBUS_DAEMON_NAME, service) == 0 &&
+ *new_owner == 0)
+ {
+ /* The daemon died, unmount */
+ g_main_loop_quit (subthread_main_loop);
+ }
+ }
+ else if (dbus_message_is_signal (message,
+ DBUS_INTERFACE_LOCAL,
+ "Disconnected"))
+ {
+ /* Session bus died, unmount */
+ g_main_loop_quit (subthread_main_loop);
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static gpointer
+vfs_init (struct fuse_conn_info *conn)
+{
+ DBusConnection *dbus_conn;
+ DBusMessage *message;
+ DBusError error;
+
+ daemon_creation_time = time (NULL);
+ daemon_uid = getuid ();
+ daemon_gid = getgid ();
+
+ mount_list_mutex = g_mutex_new ();
+ global_fh_table = g_hash_table_new_full (g_str_hash, g_str_equal,
+ NULL, (GDestroyNotify) file_handle_free);
+
+ dbus_error_init (&error);
+
+ dbus_conn = dbus_bus_get (DBUS_BUS_SESSION, &error);
+ if (dbus_conn == NULL)
+ {
+ g_printerr ("Failed to connect to the D-BUS daemon: %s\n",
+ error.message);
+ dbus_error_free (&error);
+ return NULL;
+ }
+
+ dbus_connection_set_exit_on_disconnect (dbus_conn, FALSE);
+
+ _g_dbus_connection_integrate_with_main (dbus_conn);
+
+ dbus_bus_add_match (dbus_conn,
+ "type='signal',sender='" DBUS_SERVICE_DBUS "',"
+ "interface='" DBUS_INTERFACE_DBUS "',"
+ "member='NameOwnerChanged',"
+ "arg0='"G_VFS_DBUS_DAEMON_NAME"'",
+ NULL);
+ dbus_connection_add_filter (dbus_conn,
+ dbus_filter_func,
+ NULL,
+ NULL);
+
+ message = dbus_message_new_method_call (G_VFS_DBUS_DAEMON_NAME,
+ G_VFS_DBUS_MOUNTTRACKER_PATH,
+ G_VFS_DBUS_MOUNTTRACKER_INTERFACE,
+ G_VFS_DBUS_MOUNTTRACKER_OP_REGISTER_FUSE);
+ dbus_message_set_auto_start (message, TRUE);
+ dbus_connection_send (dbus_conn, message, NULL);
+ dbus_connection_flush (dbus_conn);
+
+ gvfs = g_vfs_get_default ();
+
+ volume_monitor = g_object_new (g_type_from_name ("GDaemonVolumeMonitor"), NULL);
+
+ subthread_main_loop = g_main_loop_new (NULL, FALSE);
+ subthread = g_thread_create ((GThreadFunc) subthread_main, NULL, FALSE, NULL);
+
+ return NULL;
+}
+
+static void
+vfs_destroy (gpointer param)
+{
+ mount_list_free ();
+ if (subthread_main_loop != NULL)
+ g_main_loop_quit (subthread_main_loop);
+ g_mutex_free (mount_list_mutex);
+ g_object_unref (gvfs);
+}
+
+static struct fuse_operations vfs_oper =
+{
+ .init = vfs_init,
+ .destroy = vfs_destroy,
+
+ .getattr = vfs_getattr,
+ .readdir = vfs_readdir,
+
+ .statfs = vfs_statfs,
+
+ .opendir = vfs_opendir,
+ .readdir = vfs_readdir,
+ .readlink = vfs_readlink,
+
+ .open = vfs_open,
+ .create = vfs_create,
+ .release = vfs_release,
+ .flush = vfs_flush,
+
+ .read = vfs_read,
+ .write = vfs_write,
+
+ .rename = vfs_rename,
+ .unlink = vfs_unlink,
+ .mkdir = vfs_mkdir,
+ .rmdir = vfs_rmdir,
+ .ftruncate = vfs_ftruncate,
+ .truncate = vfs_truncate,
+ .symlink = vfs_symlink,
+ .access = vfs_access,
+ .utimens = vfs_utimens,
+ .chmod = vfs_chmod,
+
+#if 0
+ .chown = vfs_chown,
+ .setxattr = vfs_setxattr,
+ .getxattr = vfs_getxattr,
+ .listxattr = vfs_listxattr,
+ .removexattr = vfs_removexattr,
+#endif
+};
+
+gint
+main (gint argc, gchar *argv [])
+{
+ g_type_init ();
+ g_thread_init (NULL);
+
+ return fuse_main (argc, argv, &vfs_oper, NULL /* user data */);
+}