/* 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., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * Author: Hans Petter Jansson */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* stuff from common/ */ #include #include #include #define FUSE_USE_VERSION 30 #include #include #ifndef RENAME_NOREPLACE #define RENAME_NOREPLACE (1 << 0) #endif #ifndef RENAME_EXCHANGE #define RENAME_EXCHANGE (1 << 1) #endif #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; goffset pos; goffset size; } 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 = {NULL}; static time_t daemon_creation_time; static uid_t daemon_uid; static gid_t daemon_gid; static GMutex global_mutex = {NULL}; static GHashTable *global_path_to_fh_map = NULL; static GHashTable *global_active_fh_map = NULL; static GDBusConnection *dbus_conn = NULL; static guint daemon_name_watcher; /* ------- * * Helpers * * ------- */ static void log_debug (const gchar *log_domain, GLogLevelFlags log_level, const gchar *message, gpointer unused_data) { struct fuse_context *context; context = fuse_get_context (); if (gvfs_get_debug ()) g_print ("fuse(%u): %s", context ? context->pid : 0, message); } 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 }, { G_IO_ERROR_WOULD_BLOCK, EAGAIN }, { G_IO_ERROR_WOULD_RECURSE, EXDEV }, { G_IO_ERROR_WOULD_MERGE, ENOTEMPTY }, { -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; } /* Conveys the pid of the client to filesystem backends; see * get_pid_for_file() in gdaemonfile.c * * can only be called during a filesystem op */ static void set_pid_for_file (GFile *file) { struct fuse_context *context; if (file == NULL) goto out; context = fuse_get_context (); if (context == NULL) goto out; g_object_set_data (G_OBJECT (file), "gvfs-fuse-client-pid", GUINT_TO_POINTER (context->pid)); out: ; } static FileHandle * file_handle_new (const gchar *path) { FileHandle *file_handle; file_handle = g_new0 (FileHandle, 1); file_handle->refcount = 1; g_mutex_init (&file_handle->mutex); file_handle->op = FILE_OP_NONE; file_handle->path = g_strdup (path); file_handle->size = -1; g_hash_table_insert (global_active_fh_map, file_handle, file_handle); 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)) { gint refs; g_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 global lock. */ refs = g_atomic_int_get (&file_handle->refcount); if (refs == 0) g_hash_table_remove (global_path_to_fh_map, file_handle->path); g_mutex_unlock (&global_mutex); } } static void file_handle_close_stream (FileHandle *file_handle) { g_debug ("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; file_handle->size = -1; } } /* Called on hash table removal */ static void file_handle_free (FileHandle *file_handle) { g_hash_table_remove (global_active_fh_map, file_handle); file_handle_close_stream (file_handle); g_mutex_clear (&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_mutex_lock (&global_mutex); fh = g_hash_table_lookup (global_path_to_fh_map, path); if (fh) file_handle_ref (fh); g_mutex_unlock (&global_mutex); return fh; } static FileHandle * get_or_create_file_handle_for_path (const gchar *path) { FileHandle *fh; g_mutex_lock (&global_mutex); fh = g_hash_table_lookup (global_path_to_fh_map, path); if (fh) { file_handle_ref (fh); } else { fh = file_handle_new (path); g_hash_table_insert (global_path_to_fh_map, fh->path, fh); } g_mutex_unlock (&global_mutex); return fh; } static FileHandle * get_file_handle_from_info (struct fuse_file_info *fi) { FileHandle *fh; g_mutex_lock (&global_mutex); fh = GET_FILE_HANDLE (fi); /* If the file handle is still valid, its value won't change. If * invalid, it's set to NULL. */ fh = g_hash_table_lookup (global_active_fh_map, fh); if (fh) file_handle_ref (fh); g_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_mutex_lock (&global_mutex); if (!g_hash_table_lookup_extended (global_path_to_fh_map, old_path, (gpointer *) &old_path_internal, (gpointer *) &fh)) goto out; g_hash_table_steal (global_path_to_fh_map, old_path); g_free (fh->path); fh->path = g_strdup (new_path); g_hash_table_insert (global_path_to_fh_map, fh->path, fh); out: g_mutex_unlock (&global_mutex); } static MountRecord * mount_record_new (GMount *mount) { MountRecord *mount_record; mount_record = g_new (MountRecord, 1); mount_record->root = g_mount_get_root (mount); mount_record->name = g_strdup (g_object_get_data (G_OBJECT (mount), "g-stable-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_free_full (mount_list, (GDestroyNotify) mount_record_free); 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 guint64 file_info_get_attribute_as_uint (GFileInfo *file_info, const gchar *attribute) { GFileAttributeType attribute_type; guint64 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_debug ("attribute: %s type: %d\n", attribute, attribute_type); 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 * * ------------- */ static gint vfs_statfs (const gchar *path, struct statvfs *stbuf) { GFile *file; GError *error = NULL; gint result = 0; g_debug ("vfs_statfs: %s\n", path); memset (stbuf, 0, sizeof (*stbuf)); /* Fallback case */ 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 ((file = file_from_full_path (path))) { GFileInfo *file_info; file_info = g_file_query_filesystem_info (file, "filesystem::*", NULL, &error); if (file_info) { if (g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE)) { guint64 size = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_FILESYSTEM_SIZE); stbuf->f_blocks = (size > 0) ? ((size - 1) / 4096 + 1) : 0; } 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; g_object_unref (file_info); } else if (error) { result = -errno_from_error (error); g_error_free (error); } else { result = -EIO; } g_object_unref (file); } g_debug ("vfs_statfs: -> %s\n", g_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 (g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_UNIX_MODE)) { mode_t mode = g_file_info_get_attribute_uint32 (file_info, G_FILE_ATTRIBUTE_UNIX_MODE); unix_mode |= mode & (S_IRWXU | S_IRWXG | S_IRWXO); } else { 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 void set_attributes_from_info (GFileInfo *file_info, struct stat *sbuf) { 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; if (g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED)) { sbuf->st_mtime = file_info_get_attribute_as_uint (file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED); sbuf->st_ctime = sbuf->st_mtime; sbuf->st_atime = sbuf->st_mtime; } 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); if (g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE)) sbuf->st_blksize = file_info_get_attribute_as_uint (file_info, G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE); if (g_file_info_has_attribute (file_info, G_FILE_ATTRIBUTE_UNIX_BLOCKS)) sbuf->st_blocks = file_info_get_attribute_as_uint (file_info, G_FILE_ATTRIBUTE_UNIX_BLOCKS); else /* fake it to make 'du' work like 'du --apparent'. */ sbuf->st_blocks = (sbuf->st_size + 511) / 512; /* Setting st_nlink to 1 for directories makes 'find' work */ sbuf->st_nlink = 1; } static const char *query_attributes = G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK "," G_FILE_ATTRIBUTE_STANDARD_SIZE "," G_FILE_ATTRIBUTE_UNIX_MODE "," G_FILE_ATTRIBUTE_TIME_CHANGED "," G_FILE_ATTRIBUTE_TIME_MODIFIED "," G_FILE_ATTRIBUTE_TIME_ACCESS "," G_FILE_ATTRIBUTE_UNIX_BLOCK_SIZE "," G_FILE_ATTRIBUTE_UNIX_BLOCKS "," G_FILE_ATTRIBUTE_ACCESS_CAN_READ "," G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE "," G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE; 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, query_attributes, 0, NULL, &error); if (file_info) { set_attributes_from_info (file_info, sbuf); g_object_unref (file_info); } else { if (error) { g_debug ("Error from GVFS: %s\n", error->message); result = -errno_from_error (error); g_error_free (error); } else { g_debug ("No file info, but no error from GVFS.\n"); result = -EIO; } } return result; } /* Call with fh locked */ static gint getattr_for_file_handle (FileHandle *fh, struct stat *sbuf) { GFileInfo *file_info; GError *error = NULL; gint result = 0; switch (fh->op) { case FILE_OP_READ: file_info = g_file_input_stream_query_info (G_FILE_INPUT_STREAM (fh->stream), query_attributes, NULL, &error); break; case FILE_OP_WRITE: file_info = g_file_output_stream_query_info (G_FILE_OUTPUT_STREAM (fh->stream), query_attributes, NULL, &error); break; default: return -ENOTSUP; } if (file_info) { set_attributes_from_info (file_info, sbuf); g_object_unref (file_info); } else { if (error) { g_debug ("Error from GVFS: %s\n", error->message); result = -errno_from_error (error); g_error_free (error); } else { g_debug ("No file info, but no error from GVFS.\n"); result = -EIO; } } return result; } static gint vfs_getattr (const gchar *path, struct stat *sbuf, struct fuse_file_info *fi) { GFile *file; gint result = 0; g_debug ("vfs_getattr: %s\n", path); 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 */ FileHandle *fh = get_file_handle_for_path (path); if (fh) { goffset size; g_mutex_lock (&fh->mutex); result = getattr_for_file_handle (fh, sbuf); size = fh->size; g_mutex_unlock (&fh->mutex); file_handle_unref (fh); if (result == -ENOTSUP) { result = getattr_for_file (file, sbuf); /* Some backends don't create new files until their stream has * been closed. So, if the path doesn't exist, but we have a * stream associated with it, pretend it's there. */ /* If we're tracking an open file's size, use that in preference * to the stat information since it may be incorrect if * g_file_replace writes to a temporary file. */ if (size != -1) sbuf->st_size = size; if (result != 0) { result = 0; sbuf->st_mode = S_IFREG | S_IRUSR | S_IWUSR | S_IXUSR; sbuf->st_uid = daemon_uid; sbuf->st_gid = daemon_gid; sbuf->st_nlink = 1; sbuf->st_blksize = 512; sbuf->st_blocks = (sbuf->st_size + 511) / 512; } } } else { result = getattr_for_file (file, sbuf); } g_object_unref (file); } else { result = -ENOENT; } g_debug ("vfs_getattr: -> %s\n", g_strerror (-result)); return result; } static gint vfs_readlink (const gchar *path, gchar *target, size_t size) { g_debug ("vfs_readlink: %s\n", path); /* This is not implemented because it would allow remote servers to launch * symlink attacks on the local machine. There's not much of a use for * "readlink" anyway since we don't pass G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, * so non-broken symlinks will be followed transparently. */ return -ENOSYS; } static gint setup_input_stream (GFile *file, FileHandle *fh) { GError *error = NULL; gint result = 0; if (fh->stream) { g_debug ("setup_input_stream: have stream\n"); if (fh->op == FILE_OP_READ) { g_debug ("setup_input_stream: doing read\n"); } else { g_debug ("setup_input_stream: doing write\n"); g_output_stream_close (fh->stream, NULL, NULL); g_object_unref (fh->stream); fh->stream = NULL; fh->size = -1; } } if (!fh->stream) { g_debug ("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) { g_debug ("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, int flags) { 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) { if (flags & O_TRUNC) { fh->stream = g_file_replace (file, NULL, FALSE, 0, NULL, &error); fh->size = 0; } else if (flags & O_APPEND) fh->stream = g_file_append_to (file, 0, NULL, &error); else result = -ENOTSUP; 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 open_common (const gchar *path, struct fuse_file_info *fi, GFile *file, int output_flags) { gint result; FileHandle *fh = get_or_create_file_handle_for_path (path); g_mutex_lock (&fh->mutex); SET_FILE_HANDLE (fi, fh); g_debug ("open_common: flags=%o (%s%s%s%s)\n", fi->flags, fi->flags & O_WRONLY ? "O_WRONLY " : "O_RDONLY ", fi->flags & O_RDWR ? "O_RDWR " : "", fi->flags & O_APPEND ? "O_APPEND " : "", fi->flags & O_TRUNC ? "O_TRUNC " : ""); /* Set up a stream here, so we can check for errors */ set_pid_for_file (file); if (fi->flags & O_WRONLY || fi->flags & O_RDWR) result = setup_output_stream (file, fh, fi->flags | output_flags); else result = setup_input_stream (file, fh); if (fh->stream) fi->nonseekable = !g_seekable_can_seek (G_SEEKABLE (fh->stream)); g_mutex_unlock (&fh->mutex); if (result < 0) file_handle_unref (fh); /* The added reference to the file handle is released in vfs_release() */ return result; } static gint vfs_open (const gchar *path, struct fuse_file_info *fi) { GFile *file; gint result = 0; g_debug ("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, G_FILE_ATTRIBUTE_STANDARD_TYPE, 0, NULL, &error); if (file_info) { GFileType file_type = g_file_info_get_file_type (file_info); if (file_type == G_FILE_TYPE_REGULAR) { result = open_common (path, fi, file, 0); } 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) { g_debug ("Error from GVFS: %s\n", error->message); result = -errno_from_error (error); g_error_free (error); } else { g_debug ("No file info, but no error from GVFS.\n"); result = -EIO; } } g_object_unref (file); } else { result = -ENOENT; } g_debug ("vfs_open: -> %s\n", g_strerror (-result)); return result; } static gint vfs_create (const gchar *path, mode_t mode, struct fuse_file_info *fi) { GFile *file; gint result = 0; g_debug ("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; set_pid_for_file (file); file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TYPE, 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 */ g_mutex_lock (&fh->mutex); SET_FILE_HANDLE (fi, fh); file_handle_close_stream (fh); fh->stream = file_output_stream; fh->size = 0; fh->op = FILE_OP_WRITE; g_mutex_unlock (&fh->mutex); /* The reference added 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; } g_debug ("vfs_create: -> %s\n", g_strerror (-result)); return result; } static gint vfs_release (const gchar *path, struct fuse_file_info *fi) { FileHandle *fh = get_file_handle_from_info (fi); g_debug ("vfs_release: %s\n", path); if (fh) { g_mutex_lock (&fh->mutex); file_handle_close_stream (fh); g_mutex_unlock (&fh->mutex); /* get_file_handle_from_info () adds a "working ref", so unref twice. */ file_handle_unref (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; gsize 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 */ g_debug ("read_stream: seeking to offset %jd.\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 */ g_debug ("read_stream: skipping to offset %jd.\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 (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 */ g_debug ("read_stream: can't seek nor skip to offset %jd!\n", offset); result = -ENOTSUP; } } if (result == 0) { g_input_stream_read_all (input_stream, output_buf, output_buf_size, &n_bytes_read, NULL, &error); fh->pos += n_bytes_read; result = n_bytes_read; if (error != NULL) { g_debug ("read_stream: wanted %zd bytes, but got %zd.\n", output_buf_size, n_bytes_read); 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; g_debug ("vfs_read: %s\n", path); if ((file = file_from_full_path (path))) { FileHandle *fh = get_file_handle_from_info (fi); if (fh) { g_mutex_lock (&fh->mutex); result = setup_input_stream (file, fh); if (result == 0) { result = read_stream (fh, buf, size, offset); if (result == -ENOTSUP && offset < fh->pos) { file_handle_close_stream (fh); result = setup_input_stream (file, fh); if (result == 0) result = read_stream (fh, buf, size, offset); } } else { g_debug ("vfs_read: failed to setup input_stream!\n"); } g_mutex_unlock (&fh->mutex); file_handle_unref (fh); } else { result = -EINVAL; } g_object_unref (file); } else { result = -EIO; } if (result < 0) g_debug ("vfs_read: -> %s\n", g_strerror (-result)); else g_debug ("vfs_read: -> %d bytes read.\n", result); return result; } static gint write_stream (FileHandle *fh, gboolean is_append, const gchar *input_buf, size_t input_buf_size, off_t offset) { GOutputStream *output_stream; gsize n_bytes_written = 0; gint result = 0; GError *error = NULL; g_debug ("write_stream: %zd bytes at offset %ju.\n", input_buf_size, offset); output_stream = fh->stream; if (!is_append && 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) { g_output_stream_write_all (output_stream, input_buf, input_buf_size, &n_bytes_written, NULL, &error); fh->pos += n_bytes_written; result = n_bytes_written; 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); } } if (fh->size != -1 && fh->pos > fh->size) fh->size = fh->pos; 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; g_debug ("vfs_write: %s\n", path); g_debug ("vfs_write: flags=%o (%s%s%s%s)\n", fi->flags, fi->flags & O_WRONLY ? "O_WRONLY " : "O_RDONLY ", fi->flags & O_RDWR ? "O_RDWR " : "", fi->flags & O_APPEND ? "O_APPEND " : "", fi->flags & O_TRUNC ? "O_TRUNC " : ""); if ((file = file_from_full_path (path))) { FileHandle *fh = get_file_handle_from_info (fi); if (fh) { g_mutex_lock (&fh->mutex); result = setup_output_stream (file, fh, fi->flags & O_APPEND); if (result == 0) { result = write_stream (fh, fi->flags & O_APPEND, buf, len, offset); } g_mutex_unlock (&fh->mutex); file_handle_unref (fh); } else { result = -EINVAL; } g_object_unref (file); } else { result = -EIO; } if (result < 0) g_debug ("vfs_write: -> %s\n", g_strerror (-result)); else g_debug ("vfs_write: -> %d bytes written.\n", result); return result; } static gint vfs_opendir (const gchar *path, struct fuse_file_info *fi) { GFile *file; gint result = 0; g_debug ("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) { g_debug ("Error from GVFS: %s\n", error->message); result = -errno_from_error (error); g_error_free (error); } else { g_debug ("No file info, but no error from GVFS.\n"); result = -EIO; } return result; } filler (buf, ".", NULL, 0, 0); filler (buf, "..", NULL, 0, 0); while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &error)) != NULL) { filler (buf, g_file_info_get_name (file_info), NULL, 0, 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, enum fuse_readdir_flags fl) { GFile *base_file; gint result = 0; g_debug ("vfs_readdir: %s\n", path); if (path_is_mount_list (path)) { GList *l; /* Mount list */ filler (buf, ".", NULL, 0, 0); filler (buf, "..", NULL, 0, 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, 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, unsigned int vfs_flags) { GFile *old_file; GFile *new_file; GFileCopyFlags flags = G_FILE_COPY_OVERWRITE; GError *error = NULL; gint result = 0; /* Can not implement this flag because limitation of GFile. */ if (vfs_flags & RENAME_EXCHANGE) return -EINVAL; if (vfs_flags & RENAME_NOREPLACE) flags = G_FILE_COPY_NONE; g_debug ("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 = 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, flags, NULL, NULL, NULL, &error); if (error) { g_debug ("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); } } else { result = -ENOENT; } if (old_file) g_object_unref (old_file); if (new_file) g_object_unref (new_file); g_debug ("vfs_rename: -> %s\n", g_strerror (-result)); return result; } static gint vfs_unlink (const gchar *path) { GFile *file; GError *error = NULL; gint result = 0; g_debug ("vfs_unlink: %s\n", path); file = file_from_full_path (path); if (file) { FileHandle *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) { g_debug ("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; } g_debug ("vfs_unlink: -> %s\n", g_strerror (-result)); return result; } static gint vfs_mkdir (const gchar *path, mode_t mode) { GFile *file; GError *error = NULL; gint result = 0; g_debug ("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; } g_debug ("vfs_mkdir: -> %s\n", g_strerror (-result)); return result; } static gint vfs_rmdir (const gchar *path) { GFile *file; GError *error = NULL; gint result = 0; g_debug ("vfs_rmdir: %s\n", path); file = file_from_full_path (path); if (file) { GFileInfo *file_info; file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TYPE, 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; } g_debug ("vfs_rmdir: -> %s\n", g_strerror (-result)); return result; } static gboolean file_handle_get_size (FileHandle *fh, goffset *size) { GFileInfo *info; gboolean res; if (fh->stream == NULL) return FALSE; info = NULL; if (fh->op == FILE_OP_READ) info = g_file_input_stream_query_info (fh->stream, G_FILE_ATTRIBUTE_STANDARD_SIZE, NULL, NULL); else if (fh->op == FILE_OP_WRITE) info = g_file_output_stream_query_info (fh->stream, G_FILE_ATTRIBUTE_STANDARD_SIZE, NULL, NULL); res = FALSE; if (info) { if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_SIZE)) { *size = g_file_info_get_size (info); res = TRUE; } g_object_unref (info); } return res; } #define PAD_BLOCK_SIZE 65536 static gint pad_file (FileHandle *fh, gsize num, goffset current_size) { gpointer buf; gsize written; gint res; res = 0; buf = g_malloc0 (PAD_BLOCK_SIZE); for (written = 0; written < num; written += PAD_BLOCK_SIZE) { res = write_stream (fh, FALSE, buf, MIN (num - written, PAD_BLOCK_SIZE), current_size + written); if (res < 0) break; } g_free (buf); return (res < 0 ? res : 0); } static int truncate_stream (GFile *file, FileHandle *fh, off_t size) { goffset current_size; GError *error = NULL; int 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); if (fh->stream != NULL) { /* The stream created by g_file_replace() won't always replace * the file until it's been closed. So close it now to make * future operations consistent. */ g_output_stream_close (fh->stream, NULL, NULL); g_object_unref (fh->stream); fh->stream = NULL; } } else if (file_handle_get_size (fh, ¤t_size)) { if (current_size == size) { /* Don't have to do anything to succeed */ } else if ((current_size < size) && g_seekable_can_seek (G_SEEKABLE (fh->stream))) { /* If the truncated size is larger than the current size * then we need to pad out the difference with 0's */ goffset orig_pos = g_seekable_tell (G_SEEKABLE (fh->stream)); result = pad_file (fh, size - current_size, current_size); if (result == 0) g_seekable_seek (G_SEEKABLE (fh->stream), orig_pos, G_SEEK_SET, NULL, &error); } } else { result = -ENOTSUP; } if (error) { result = -errno_from_error (error); g_error_free (error); } if (result == 0 && fh->size != -1) fh->size = size; return result; } static gint vfs_ftruncate (const gchar *path, off_t size, struct fuse_file_info *fi) { GFile *file; gint result = 0; g_debug ("vfs_ftruncate: %s\n", path); file = file_from_full_path (path); if (file) { FileHandle *fh = get_file_handle_from_info (fi); if (fh) { g_mutex_lock (&fh->mutex); result = setup_output_stream (file, fh, fi->flags & O_APPEND); if (result == 0) result = truncate_stream (file, fh, size); g_mutex_unlock (&fh->mutex); file_handle_unref (fh); } else { result = -EINVAL; } g_object_unref (file); } else { result = -ENOENT; } g_debug ("vfs_ftruncate: -> %s\n", g_strerror (-result)); return result; } static gint vfs_truncate (const gchar *path, off_t size) { GFile *file; GError *error = NULL; gint result = 0; g_debug ("vfs_truncate: %s\n", path); file = file_from_full_path (path); if (file) { GFileOutputStream *file_output_stream = NULL; FileHandle *fh; /* Get a file handle just to lock the path while we're working */ fh = get_file_handle_for_path (path); if (fh) g_mutex_lock (&fh->mutex); if (fh && fh->stream && fh->op == FILE_OP_WRITE) { result = truncate_stream (file, fh, size); } else { 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; } g_debug ("vfs_truncate: -> %s\n", g_strerror (-result)); return result; } static gint vfs_truncate_dispatch (const gchar *path, off_t size, struct fuse_file_info *fi) { if (fi) return vfs_ftruncate (path, size, fi); return vfs_truncate (path, size); } static gint vfs_symlink (const gchar *path_old, const gchar *path_new) { GFile *file; GError *error = NULL; gint result = 0; g_debug ("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; } g_debug ("vfs_symlink: -> %s\n", g_strerror (-result)); return result; } static gint vfs_access (const gchar *path, gint mode) { GFile *file; GError *error = NULL; gint result = 0; g_debug ("vfs_access: %s\n", path); file = file_from_full_path (path); if (file) { GFileInfo *file_info; file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_ACCESS_CAN_READ "," G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE "," G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE ",", 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; } if (result != 0) { FileHandle *fh = get_file_handle_for_path (path); /* Some backends don't create new files until their stream has * been closed. So, if the path doesn't exist, but we have a stream * associated with it, pretend it's there. */ if (fh != NULL) { file_handle_unref (fh); /* The file was presumably created by us, so indicate full access. */ result = 0; } } g_object_unref (file); } else if (path_is_mount_list (path)) { result = 0; } else { result = -ENOENT; } g_debug ("vfs_access: -> %s\n", g_strerror (-result)); return result; } static gint vfs_utimens (const gchar *path, const struct timespec tv [2], struct fuse_file_info *fi) { GFile *file; GError *error = NULL; gint result = 0; g_debug ("vfs_utimens: %s\n", path); file = file_from_full_path (path); if (file) { guint64 atime; guint32 atime_usec; guint64 mtime; guint32 mtime_usec; GFileInfo *info; if (tv) { atime = (guint64) tv [0].tv_sec; atime_usec = (guint32) tv [0].tv_nsec / (guint32) 1000; mtime = (guint64) tv [1].tv_sec; mtime_usec = (guint32) tv [1].tv_nsec / (guint32) 1000; } else { struct timeval tiv; gettimeofday (&tiv, NULL); atime = (guint64) tiv.tv_sec; atime_usec = (guint32) tiv.tv_usec; mtime = atime; mtime_usec = atime_usec; } info = g_file_info_new (); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED, mtime); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC, mtime_usec); g_file_info_set_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_ACCESS, atime); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_TIME_ACCESS_USEC, atime_usec); g_file_set_attributes_from_info (file, info, 0, NULL, &error); if (error) { /* As long as not all backends support all attributes we set, report failure only if neither mtime and atime have been set. */ if (g_file_info_get_attribute_status (info, G_FILE_ATTRIBUTE_TIME_ACCESS) == G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING && g_file_info_get_attribute_status (info, G_FILE_ATTRIBUTE_TIME_MODIFIED) == G_FILE_ATTRIBUTE_STATUS_ERROR_SETTING) { /* Note: we only get first error from the attributes we try to set, might not be accurate (a limitation of g_file_set_attributes_from_info()). */ result = -errno_from_error (error); } g_error_free (error); } g_object_unref (file); g_object_unref (info); } else if (path_is_mount_list (path)) { /* */ } else { result = -ENOENT; } g_debug ("vfs_utimens: -> %s\n", g_strerror (-result)); return result; } static gint vfs_chmod (const gchar *path, mode_t mode, struct fuse_file_info *fi) { 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 (); 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_object_unref (volume_monitor); volume_monitor = NULL; return NULL; } static void name_vanished_handler (GDBusConnection *connection, const gchar *name, gpointer user_data) { /* The daemon died, unmount */ kill (getpid (), SIGHUP); } static void dbus_connection_closed (GDBusConnection *connection, gboolean remote_peer_vanished, GError *error, gpointer user_data) { /* Session bus died, unmount */ kill (getpid (), SIGHUP); } static void register_fuse_cb (GVfsDBusMountTracker *proxy, GAsyncResult *res, gpointer user_data) { GError *error = NULL; if (! gvfs_dbus_mount_tracker_call_register_fuse_finish (proxy, res, &error)) { g_printerr ("register_fuse_cb: Error sending a message: %s (%s, %d)\n", error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); } } static gpointer vfs_init (struct fuse_conn_info *conn, struct fuse_config *cfg) { GVfsDBusMountTracker *proxy; GError *error; g_log_set_handler (NULL, G_LOG_LEVEL_DEBUG, log_debug, NULL); gvfs_setup_debug_handler (); if (g_getenv ("GVFS_DEBUG_FUSE")) { gvfs_set_debug (TRUE); } daemon_creation_time = time (NULL); daemon_uid = getuid (); daemon_gid = getgid (); global_path_to_fh_map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) file_handle_free); global_active_fh_map = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL); error = NULL; dbus_conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error); if (! dbus_conn) { g_warning ("Failed to connect to the D-BUS daemon: %s (%s, %d)", error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); return NULL; } g_dbus_connection_set_exit_on_close (dbus_conn, FALSE); g_signal_connect (dbus_conn, "closed", G_CALLBACK (dbus_connection_closed), NULL); proxy = gvfs_dbus_mount_tracker_proxy_new_sync (dbus_conn, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, G_VFS_DBUS_DAEMON_NAME, G_VFS_DBUS_MOUNTTRACKER_PATH, NULL, &error); if (proxy == NULL) { g_printerr ("vfs_init(): Error creating proxy: %s (%s, %d)\n", error->message, g_quark_to_string (error->domain), error->code); g_error_free (error); return NULL; } /* Allow the gvfs daemon autostart */ daemon_name_watcher = g_bus_watch_name_on_connection (dbus_conn, G_VFS_DBUS_DAEMON_NAME, G_BUS_NAME_WATCHER_FLAGS_AUTO_START, NULL, name_vanished_handler, NULL, NULL); gvfs_dbus_mount_tracker_call_register_fuse (proxy, NULL, (GAsyncReadyCallback) register_fuse_cb, NULL); g_object_unref (proxy); 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_new ("gvfs-fuse-sub", (GThreadFunc) subthread_main, NULL); /* Indicate O_TRUNC support for open() */ conn->want |= FUSE_CAP_ATOMIC_O_TRUNC; /* Prevent out-of-order readahead */ conn->want &= ~FUSE_CAP_ASYNC_READ; return NULL; } static void vfs_destroy (gpointer param) { if (daemon_name_watcher) g_bus_unwatch_name (daemon_name_watcher); g_clear_object (&dbus_conn); if (subthread_main_loop != NULL) g_main_loop_quit (subthread_main_loop); if (subthread) g_thread_join (subthread); g_clear_pointer (&subthread_main_loop, g_main_loop_unref); mount_list_free (); } static struct fuse_operations vfs_oper = { .init = vfs_init, .destroy = vfs_destroy, .getattr = vfs_getattr, .statfs = vfs_statfs, .opendir = vfs_opendir, .readdir = vfs_readdir, .readlink = vfs_readlink, .open = vfs_open, .create = vfs_create, .release = vfs_release, .read = vfs_read, .write = vfs_write, .rename = vfs_rename, .unlink = vfs_unlink, .mkdir = vfs_mkdir, .rmdir = vfs_rmdir, .truncate = vfs_truncate_dispatch, .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, .ftruncate = vfs_ftruncate, #endif }; static void set_custom_signal_handlers (void (*handler)(int)) { struct sigaction sa; sigemptyset (&sa.sa_mask); sa.sa_handler = handler; sa.sa_flags = 0; sigaction (SIGHUP, &sa, NULL); sigaction (SIGINT, &sa, NULL); sigaction (SIGTERM, &sa, NULL); } gint main (gint argc, gchar *argv []) { struct fuse *fuse; struct fuse_session *se; int res; struct fuse_cmdline_opts opts; struct fuse_args args = FUSE_ARGS_INIT (argc, argv); if (fuse_opt_parse (&args, NULL, NULL, NULL) == -1) return 1; if (fuse_parse_cmdline (&args, &opts) != 0) return 1; if (opts.show_version) { printf ("%s\n", PACKAGE_STRING); fuse_lowlevel_version (); return 0; } if (opts.show_help) { printf ("usage: %s [options] \n\n", argv[0]); printf ("FUSE options:\n"); fuse_cmdline_help (); return 0; } if (!opts.mountpoint) { fprintf (stderr, "error: no mountpoint specified\n"); return 1; } fuse = fuse_new (&args, &vfs_oper, sizeof (vfs_oper), NULL /* user data */); if (fuse == NULL) return 1; if (fuse_mount (fuse, opts.mountpoint) != 0) return 1; if (fuse_daemonize (opts.foreground) != 0) return 1; se = fuse_get_session (fuse); if (fuse_set_signal_handlers (se) != 0) return 1; if (opts.singlethread) res = fuse_loop (fuse); else res = fuse_loop_mt (fuse, opts.clone_fd); /* Ignore new signals during exit procedure in order to terminate properly */ set_custom_signal_handlers (SIG_IGN); fuse_remove_signal_handlers (se); fuse_unmount (fuse); fuse_destroy (fuse); free (opts.mountpoint); fuse_opt_free_args (&args); return res; }