From 7a61eaa91db11e1d8f70eb36cfd42f91e103e201 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 8 Mar 2016 16:02:17 +0100 Subject: [PATCH] Reimplement fuse backend The magic inode numbers we used before are problematic. The inode nrs are tied to the file names, so inode changes on rename, which breaks posix expectations. Also, it relied on 64bit inode space which is not true on i386. So, this is a new implementation that uses a more traditional approach of dynamically allocating inodes as needed. --- document-portal/xdp-fuse.c | 3595 ++++++++++++++++++------------------ document-portal/xdp-fuse.h | 10 +- document-portal/xdp-main.c | 40 +- document-portal/xdp-util.c | 53 - document-portal/xdp-util.h | 5 - 5 files changed, 1814 insertions(+), 1889 deletions(-) diff --git a/document-portal/xdp-fuse.c b/document-portal/xdp-fuse.c index 1bd6a9eb..c20618a6 100644 --- a/document-portal/xdp-fuse.c +++ b/document-portal/xdp-fuse.c @@ -20,47 +20,81 @@ #include "xdp-util.h" #include "xdg-app-utils.h" -/* Layout: - - "/ (STD_DIRS:1) - "by-app/" (STD_DIRS:2) - "org.gnome.gedit/" (APP_DIR:app id) - "$id/" (APP_DOC_DIR:app_id<<32|doc_id) - - "$id" (APP_DOC_DIR:(app_id==0)<<32|doc_idid) - $basename (APP_DOC_FILE:app_id<<32|doc_id) (app id == 0 if not in app dir) - $tmpfile (TMPFILE:tmp_id) -*/ - -#define BY_APP_INO 2 - #define NON_DOC_DIR_PERMS 0500 #define DOC_DIR_PERMS 0700 -/* The (fake) directories don't really change */ -#define DIRS_ATTR_CACHE_TIME 60.0 +/* TODO: What do we put here */ +#define ATTR_CACHE_TIME 60.0 +#define ENTRY_CACHE_TIME 60.0 /* We pretend that the file is hardlinked. This causes most apps to do a truncating overwrite, which suits us better, as we do the atomic - rename ourselves anyway. This way we don't weirdly change the inode - after the rename. */ + rename ourselves anyway. */ #define DOC_FILE_NLINK 2 typedef enum { - STD_DIRS_INO_CLASS, - TMPFILE_INO_CLASS, - APP_DIR_INO_CLASS, - APP_DOC_DIR_INO_CLASS, - APP_DOC_FILE_INO_CLASS, -} XdpInodeClass; + XDP_INODE_ROOT, + XDP_INODE_BY_APP, + XDP_INODE_APP_DIR, /* app id */ + XDP_INODE_APP_DOC_DIR, /* app_id + doc id */ + XDP_INODE_DOC_DIR, /* doc id */ + XDP_INODE_DOC_FILE, /* doc id (NULL if tmp), name (== basename) */ +} XdpInodeType; +typedef struct _XdpInode XdpInode; + +struct _XdpInode { + gint ref_count; /* atomic */ + + /* These are all immutable */ + fuse_ino_t ino; + XdpInodeType type; + XdpInode *parent; + char *app_id; + char *doc_id; + + /* For doc dirs */ + char *dirname; + dev_t dir_dev; + ino_t dir_ino; + + /* mutable data */ + + GList *children; /* lazily filled, protected by inodes lock */ + char *filename; /* variable (for non-dirs), null if deleted, + protected by inodes lock *and* mutex */ + gboolean is_doc; /* True if this is the document file for this dir */ + + /* Used when the file is open, protected by mutex */ + GMutex mutex; /* Always lock inodes lock (if needed) before mutex */ + GList *open_files; + int dir_fd; + int fd; /* RW fd for tempfiles, RO fd for doc files */ + char *backing_filename; + char *trunc_filename; + int trunc_fd; + gboolean truncated; +}; + +typedef struct _XdpFile XdpFile; + +struct _XdpFile { + XdpInode *inode; + int open_mode; +}; + +#define ROOT_INODE 1 +#define BY_APP_INODE 2 #define BY_APP_NAME "by-app" -static GHashTable *app_name_to_id; -static GHashTable *app_id_to_name; -static guint32 next_app_id = 1; +static GHashTable *dir_to_inode_nr; -G_LOCK_DEFINE(app_id); +static GHashTable *inodes; /* The in memory XdpInode:s, protected by inodes lock */ +static XdpInode *root_inode; +static XdpInode *by_app_inode; +static fuse_ino_t next_inode_nr = 3; + +G_LOCK_DEFINE(inodes); static GThread *fuse_thread = NULL; static struct fuse_session *session = NULL; @@ -69,13 +103,685 @@ static char *mount_path = NULL; static pthread_t fuse_pthread = 0; static int -steal_fd (int *fdp) +reopen_fd (int fd, int flags) { - int fd = *fdp; - *fdp = -1; - return fd; + g_autofree char *path = g_strdup_printf ("/proc/self/fd/%d", fd); + return open (path, flags | O_CLOEXEC); } +/* Call with inodes lock held */ +static fuse_ino_t +allocate_inode_unlocked (void) +{ + fuse_ino_t next = next_inode_nr++; + + /* Bail out on overflow, to avoid reuse */ + if (next <= 0) + g_assert_not_reached (); + + return next; +} + +static fuse_ino_t +get_dir_inode_nr_unlocked (const char *app_id, const char *doc_id) +{ + gpointer res; + fuse_ino_t allocated; + g_autofree char *dir = NULL; + + if (app_id == NULL) + dir = g_strdup (doc_id); + else + { + if (doc_id == NULL) + dir = g_strconcat (app_id, "/", NULL); + else + dir = g_build_filename (app_id, doc_id, NULL); + } + + res = g_hash_table_lookup (dir_to_inode_nr, dir); + if (res != NULL) + return (fuse_ino_t)(gsize)res; + + allocated = allocate_inode_unlocked (); + g_hash_table_insert (dir_to_inode_nr, g_strdup (dir), (gpointer)allocated); + return allocated; +} + +static fuse_ino_t +get_dir_inode_nr (const char *app_id, const char *doc_id) +{ + AUTOLOCK(inodes); + return get_dir_inode_nr_unlocked (app_id, doc_id); +} + +static void +allocate_app_dir_inode_nr (char **app_ids) +{ + int i; + AUTOLOCK(inodes); + for (i = 0; app_ids[i] != NULL; i++) + get_dir_inode_nr_unlocked (app_ids[i], NULL); +} + +static char ** +get_allocated_app_dirs (void) +{ + GHashTableIter iter; + gpointer key, value; + GPtrArray *array = g_ptr_array_new (); + + AUTOLOCK(inodes); + g_hash_table_iter_init (&iter, dir_to_inode_nr); + while (g_hash_table_iter_next (&iter, &key, &value)) + { + const char *name = key; + + if (g_str_has_suffix (name, "/")) + { + char *app = strndup (name, strlen (name) - 1); + g_ptr_array_add (array, app); + } + } + g_ptr_array_add (array, NULL); + return (char **)g_ptr_array_free (array, FALSE); +} + +static void xdp_inode_unref_internal (XdpInode *inode, gboolean locked); +static void xdp_inode_unref (XdpInode *inode); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC(XdpInode, xdp_inode_unref) + +static void +xdp_inode_destroy (XdpInode *inode, gboolean locked) +{ + g_assert (inode->dir_fd == -1); + g_assert (inode->fd == -1); + g_assert (inode->trunc_fd == -1); + g_assert (inode->trunc_filename == NULL); + g_assert (inode->children == NULL); + xdp_inode_unref_internal (inode->parent, locked); + g_free (inode->backing_filename); + g_free (inode->filename); + g_free (inode->dirname); + g_free (inode->app_id); + g_free (inode->doc_id); + g_free (inode); +} + +static XdpInode * +xdp_inode_ref (XdpInode *inode) +{ + if (inode) + g_atomic_int_inc (&inode->ref_count); + return inode; +} + +static void +xdp_inode_unref_internal (XdpInode *inode, gboolean locked) +{ + gint old_ref; + + if (inode == NULL) + return; + + /* here we want to atomically do: if (ref_count>1) { ref_count--; return; } */ + retry_atomic_decrement1: + old_ref = g_atomic_int_get (&inode->ref_count); + if (old_ref > 1) + { + if (!g_atomic_int_compare_and_exchange ((int *)&inode->ref_count, old_ref, old_ref - 1)) + goto retry_atomic_decrement1; + } + else + { + if (old_ref <= 0) + { + g_warning ("Can't unref dead inode"); + return; + } + /* Protect against revival from xdp_inode_lookup() */ + if (!locked) + G_LOCK(inodes); + if (!g_atomic_int_compare_and_exchange ((int *)&inode->ref_count, old_ref, old_ref - 1)) + { + if (!locked) + G_UNLOCK(inodes); + goto retry_atomic_decrement1; + } + + g_hash_table_remove (inodes, (gpointer)inode->ino); + if (inode->parent) + inode->parent->children = g_list_remove (inode->parent->children, inode); + + if (!locked) + G_UNLOCK(inodes); + + xdp_inode_destroy (inode, locked); + } +} + +static void +xdp_inode_unref (XdpInode *inode) +{ + return xdp_inode_unref_internal (inode, FALSE); +} + +static XdpInode * +xdp_inode_new_unlocked (fuse_ino_t ino, + XdpInodeType type, + XdpInode *parent, + const char *filename, + const char *app_id, + const char *doc_id) +{ + XdpInode *inode; + + inode = g_new0 (XdpInode, 1); + inode->ino = ino; + inode->type = type; + inode->parent = xdp_inode_ref (parent); + inode->filename = g_strdup (filename); + inode->app_id = g_strdup (app_id); + inode->doc_id = g_strdup (doc_id); + inode->ref_count = 1; + inode->dir_fd = -1; + inode->fd = -1; + inode->trunc_fd = -1; + + if (parent) + parent->children = g_list_prepend (parent->children, inode); + g_hash_table_insert (inodes, (gpointer)ino, inode); + + return inode; +} + +static XdpInode * +xdp_inode_new (fuse_ino_t ino, + XdpInodeType type, + XdpInode *parent, + const char *filename, + const char *app_id, + const char *doc_id) +{ + AUTOLOCK(inodes); + return xdp_inode_new_unlocked (ino, type, parent, filename, app_id, doc_id); +} + +static XdpInode * +xdp_inode_lookup_unlocked (fuse_ino_t inode_nr) +{ + XdpInode *inode; + + inode = g_hash_table_lookup (inodes, (gpointer)inode_nr); + if (inode != NULL) + return xdp_inode_ref (inode); + return NULL; +} + +static GList * +xdp_inode_list_children (XdpInode *inode) +{ + GList *list = NULL, *l; + + AUTOLOCK(inodes); + for (l = inode->children; l != NULL; l = l->next) + { + XdpInode *child = l->data; + + list = g_list_prepend (list, xdp_inode_ref (child)); + } + + return g_list_reverse (list); +} + +static XdpInode * +xdp_inode_lookup_child_unlocked (XdpInode *inode, const char *filename) +{ + GList *l; + + for (l = inode->children; l != NULL; l = l->next) + { + XdpInode *child = l->data; + if (child->filename != NULL && strcmp (child->filename, filename) == 0) + return xdp_inode_ref (child); + } + + return NULL; +} + +static XdpInode * +xdp_inode_lookup_child (XdpInode *inode, const char *filename) +{ + AUTOLOCK(inodes); + return xdp_inode_lookup_child_unlocked (inode, filename); +} + +static int +xdp_inode_open_dir_fd (XdpInode *dir) +{ + struct stat st_buf; + glnx_fd_close int fd = -1; + + g_assert (dir->dirname != NULL); + + fd = open (dir->dirname, O_CLOEXEC | O_PATH | O_DIRECTORY); + if (fd == -1) + return -1; + + if (fstat (fd, &st_buf) < 0) + { + errno = ENOENT; + return -1; + } + + if (st_buf.st_ino != dir->dir_ino || + st_buf.st_dev != dir->dir_dev) + { + errno = ENOENT; + return -1; + } + + return glnx_steal_fd (&fd); +} + +static void +xdp_inode_unlink_backing_files (XdpInode *child_inode, int dir_fd) +{ + if (dir_fd == -1) + { + g_debug ("Can't unlink child inode due to no dir_fd"); + return; + } + + if (child_inode->is_doc) + { + g_debug ("unlinking doc file %s", child_inode->filename); + unlinkat (dir_fd, child_inode->filename, 0); + if (child_inode->trunc_filename != NULL) + { + g_debug ("unlinking doc trunc_file %s", child_inode->trunc_filename); + unlinkat (dir_fd, child_inode->trunc_filename, 0); + } + } + else + { + g_debug ("unlinking tmp_file %s", child_inode->backing_filename); + unlinkat (dir_fd, child_inode->backing_filename, 0); + } +} + +static void +xdp_inode_do_unlink (XdpInode *child_inode, int dir_fd, gboolean unlink_backing) +{ + if (unlink_backing) + xdp_inode_unlink_backing_files (child_inode, dir_fd); + + /* Zero out filename to mark it deleted */ + g_free (child_inode->filename); + child_inode->filename = NULL; + + /* Drop keep-alive-until-unlink ref */ + if (!child_inode->is_doc) + xdp_inode_unref (child_inode); +} + +static XdpInode * +xdp_inode_unlink_child (XdpInode *dir, const char *filename) +{ + XdpInode *child_inode; + glnx_fd_close int dir_fd = -1; + + AUTOLOCK(inodes); + child_inode = xdp_inode_lookup_child_unlocked (dir, filename); + if (child_inode == NULL) + return NULL; + + g_assert (child_inode->type == XDP_INODE_DOC_FILE); + g_assert (child_inode->filename != NULL); + + /* Here we take *both* the inodes lock and the mutex. + The inodes lock is to make this safe against concurrent lookups, + but the mutex is to make it safe to access inode->filename inside + a mutex-only lock */ + g_mutex_lock (&child_inode->mutex); + + dir_fd = xdp_inode_open_dir_fd (dir); + + xdp_inode_do_unlink (child_inode, dir_fd, TRUE); + + g_mutex_unlock (&child_inode->mutex); + + return child_inode; +} + +/* Sets errno */ +static int +xdp_inode_rename_child (XdpInode *dir, + const char *src_filename, + const char *dst_filename, + const char *doc_basename) +{ + g_autoptr(XdpInode) src_inode = NULL; + g_autoptr(XdpInode) dst_inode = NULL; + glnx_fd_close int dir_fd = -1; + int res; + + AUTOLOCK(inodes); + src_inode = xdp_inode_lookup_child_unlocked (dir, src_filename); + if (src_inode == NULL) + { + errno = ENOENT; + return -1; + } + + g_assert (src_inode->type == XDP_INODE_DOC_FILE); + g_assert (src_inode->filename != NULL); + + dst_inode = xdp_inode_lookup_child_unlocked (dir, dst_filename); + if (dst_inode) + { + g_assert (dst_inode->type == XDP_INODE_DOC_FILE); + g_assert (dst_inode->filename != NULL); + } + + /* Here we take *both* the inodes lock and the mutex. + The inodes lock is to make this safe against concurrent lookups, + but the mutex is to make it safe to access inode->filename inside + a mutex-only lock */ + g_mutex_lock (&src_inode->mutex); + if (dst_inode) + g_mutex_lock (&dst_inode->mutex); + + dir_fd = xdp_inode_open_dir_fd (dir); + res = 0; + + if (src_inode->is_doc) + { + /* doc -> tmp */ + + /* We don't want to allow renaming an exiting doc file, because + doing so would make a tmpfile of the real doc-file which some + host-side app may have open. You have to make a copy and + remove instead. */ + errno = EACCES; + res = -1; + } + else if (strcmp (dst_filename, doc_basename) != 0) + { + /* tmp -> tmp */ + + if (dst_inode) + xdp_inode_do_unlink (dst_inode, dir_fd, TRUE); + + g_free (src_inode->filename); + src_inode->filename = g_strdup (dst_filename); + } + else + { + /* tmp -> doc */ + + g_debug ("atomic renaming %s to %s", src_inode->backing_filename, dst_filename); + res = renameat (dir_fd, src_inode->backing_filename, + dir_fd, dst_filename); + if (res == 0) + { + if (dst_inode != NULL) + { + /* Unlink, but don't remove backing files, which are now the new one */ + xdp_inode_do_unlink (dst_inode, dir_fd, FALSE); + + /* However, unlink trunc_file if its there */ + if (dst_inode->trunc_filename) + unlinkat (dir_fd, dst_inode->trunc_filename, 0); + } + + src_inode->is_doc = TRUE; + g_free (src_inode->filename); + src_inode->filename = g_strdup (dst_filename); + g_free (src_inode->backing_filename); + src_inode->backing_filename = g_strdup (dst_filename); + + /* Convert ->fd to read-only */ + if (src_inode->fd != -1) + { + int new_fd = reopen_fd (src_inode->fd, O_RDONLY); + close (src_inode->fd); + src_inode->fd = new_fd; + } + + /* This neuters any outstanding write files, since we have no trunc_fd at this point. + However, that is not really a problem, we would not support them well anyway as + a newly opened trunc file would have to have a truncate operation initially for + it to work anyway */ + } + } + + g_mutex_unlock (&src_inode->mutex); + if (dst_inode) + g_mutex_unlock (&dst_inode->mutex); + + return res; +} + +/* NULL if removed */ +char * +xdp_inode_get_filename (XdpInode *inode) +{ + AUTOLOCK(inodes); + return g_strdup (inode->filename); +} + +static XdpInode * +xdp_inode_ensure_document_file (XdpInode *dir, + XdgAppDbEntry *entry) +{ + g_autofree char *basename = xdp_entry_dup_basename (entry); + XdpInode *inode; + + g_assert (dir->type == XDP_INODE_APP_DOC_DIR || dir->type == XDP_INODE_DOC_DIR); + + AUTOLOCK(inodes); + + inode = xdp_inode_lookup_child_unlocked (dir, basename); + if (inode == NULL) + { + inode = xdp_inode_new_unlocked (allocate_inode_unlocked (), + XDP_INODE_DOC_FILE, + dir, + basename, + dir->app_id, + dir->doc_id); + inode->backing_filename = g_steal_pointer (&basename); + inode->is_doc = TRUE; + } + + return inode; +} + +static char * +create_tmp_for_doc (XdgAppDbEntry *entry, int dir_fd, int flags, mode_t mode, int *fd_out) +{ + g_autofree char *basename = xdp_entry_dup_basename (entry); + g_autofree char *template = g_strconcat (".xdp_", basename, ".XXXXXX", NULL); + int fd; + + fd = xdg_app_mkstempat (dir_fd, template, flags|O_CLOEXEC, mode); + if (fd == -1) + return NULL; + + g_debug ("Created temp file %s", template); + *fd_out = fd; + return g_steal_pointer (&template); +} + +/* sets errno */ +static XdpInode * +xdp_inode_create_file (XdpInode *dir, + XdgAppDbEntry *entry, + const char *filename, + mode_t mode, + gboolean truncate, + gboolean exclusive) +{ + XdpInode *inode; + g_autofree char *basename = xdp_entry_dup_basename (entry); + g_autofree char *backing_filename = NULL; + g_autofree char *trunc_filename = NULL; + gboolean is_doc; + glnx_fd_close int dir_fd = -1; + glnx_fd_close int fd = -1; + glnx_fd_close int trunc_fd = -1; + + g_assert (dir->type == XDP_INODE_APP_DOC_DIR || dir->type == XDP_INODE_DOC_DIR); + + AUTOLOCK(inodes); + + inode = xdp_inode_lookup_child_unlocked (dir, filename); + if (inode != NULL) + { + if (exclusive) + { + xdp_inode_unref (inode); + errno = EEXIST; + return NULL; + } + + if (truncate) + { + /* TODO: Handle extra truncate for existing file */ + errno = ENOSYS; + return NULL; + } + + return inode; + } + + dir_fd = xdp_inode_open_dir_fd (dir); + if (dir_fd == -1) + return NULL; + + is_doc = strcmp (basename, filename) == 0; + + if (is_doc) + { + backing_filename = g_strdup (filename); + int flags = O_CREAT|O_RDONLY|O_NOFOLLOW|O_CLOEXEC; + + if (exclusive) + flags |= O_EXCL; + + g_debug ("Creating doc file %s", basename); + fd = openat (dir_fd, basename, flags, mode & 0777); + if (fd < 0) + return NULL; + + trunc_filename = create_tmp_for_doc (entry, dir_fd, O_RDWR, mode & 0777, &trunc_fd); + if (trunc_filename == NULL) + return NULL; + } + else + { + backing_filename = create_tmp_for_doc (entry, dir_fd, O_RDWR, mode & 0777, &fd); + if (backing_filename == NULL) + return NULL; + } + + inode = xdp_inode_new_unlocked (allocate_inode_unlocked (), + XDP_INODE_DOC_FILE, + dir, + filename, + dir->app_id, + dir->doc_id); + inode->backing_filename = g_steal_pointer (&backing_filename); + inode->trunc_filename = g_steal_pointer (&trunc_filename); + inode->is_doc = is_doc; + inode->dir_fd = glnx_steal_fd (&dir_fd); + inode->fd = glnx_steal_fd (&fd); + inode->trunc_fd = glnx_steal_fd (&trunc_fd); + if (inode->trunc_fd != -1 && truncate) + { + inode->truncated = TRUE; + g_free (inode->backing_filename); + inode->backing_filename = g_strdup (inode->trunc_filename); + } + + /* We add an extra ref for tmp files to keep them alive until unlink */ + if (!is_doc) + xdp_inode_ref (inode); + + return inode; +} + +static XdpInode * +xdp_inode_lookup (fuse_ino_t inode_nr) +{ + AUTOLOCK(inodes); + return xdp_inode_lookup_unlocked (inode_nr); +} + +static XdpInode * +xdp_inode_get_dir_unlocked (const char *app_id, const char *doc_id, XdgAppDbEntry *entry) +{ + fuse_ino_t ino; + XdpInode *inode; + XdpInode *parent = NULL; + XdpInodeType type; + const char *filename; + + ino = get_dir_inode_nr_unlocked (app_id, doc_id); + + inode = xdp_inode_lookup_unlocked (ino); + if (inode) + return inode; + + if (app_id == NULL) + { + g_assert (doc_id != NULL); + parent = xdp_inode_ref (root_inode); + type = XDP_INODE_DOC_DIR; + filename = doc_id; + } + else + { + if (doc_id == NULL) + { + parent = xdp_inode_ref (by_app_inode); + filename = app_id; + type = XDP_INODE_APP_DIR; + } + else + { + parent = xdp_inode_get_dir_unlocked (app_id, NULL, NULL); + filename = doc_id; + type = XDP_INODE_APP_DOC_DIR; + } + } + + inode = xdp_inode_new_unlocked (ino, type, parent, filename, app_id, doc_id); + xdp_inode_unref_internal (parent, TRUE); + + if (entry) + { + inode->dirname = xdp_entry_dup_dirname (entry); + inode->dir_ino = xdp_entry_get_inode (entry); + inode->dir_dev = xdp_entry_get_device (entry); + } + + return inode; +} + +static XdpInode * +xdp_inode_get_dir (const char *app_id, const char *doc_id, XdgAppDbEntry *entry) +{ + AUTOLOCK(inodes); + return xdp_inode_get_dir_unlocked (app_id, doc_id, entry); +} + +/********************************************************************** \ + * FUSE Implementation +\***********************************************************************/ + static int get_user_perms (const struct stat *stbuf) { @@ -83,710 +789,125 @@ get_user_perms (const struct stat *stbuf) return stbuf->st_mode & 0666; } -static double -get_attr_cache_time (int st_mode) -{ - if (S_ISDIR (st_mode)) - return DIRS_ATTR_CACHE_TIME; - return 0.0; -} - -static double -get_entry_cache_time (fuse_ino_t inode) -{ - /* We have to disable entry caches because otherwise we have a race - on rename. The kernel set the target inode as NOEXIST after a - rename, which breaks in the tmp over real case due to us reusing - the old non-temp inode. */ - return 0.0; -} - -/******************************* XdpTmp ******************************* - * - * XdpTmp is a ref-counted object representing a temporary file created - * on the outer filesystem which is stored next to a real file in the fuse - * filesystem. Its useful to support write-to-tmp-then-rename-over-target - * operations. - * - * locking: - * - * The global list of outstanding Tmp are protected by the tmp_files - * lock. Use it when doing lookups by name or id, or when changing - * the list (add/remove) or name of a tmpfile. - * - * Each instance has a mutex that locks access to the backing path, - * as it can be removed at runtime. Use get/steal_backing_basename() to - * safely access it. - * - ******************************* XdpTmp *******************************/ - -static volatile gint next_tmp_id = 1; - -typedef struct -{ - volatile gint ref_count; - - /* These are immutable, no lock needed */ - guint64 parent_inode; - guint32 tmp_id; - XdgAppDbEntry *entry; - - /* Changes always done under tmp_files lock */ - char *name; - - GMutex mutex; - - /* protected by mutex */ - char *backing_basename; -} XdpTmp; - -/* Owns a ref to the files */ -static GList *tmp_files = NULL; -G_LOCK_DEFINE(tmp_files); - -static XdpTmp * -xdp_tmp_ref (XdpTmp *tmp) -{ - g_atomic_int_inc (&tmp->ref_count); - return tmp; -} - -static void -xdp_tmp_unref (XdpTmp *tmp) -{ - if (g_atomic_int_dec_and_test (&tmp->ref_count)) - { - xdg_app_db_entry_unref (tmp->entry); - g_free (tmp->name); - g_free (tmp->backing_basename); - g_free (tmp); - } -} - -char * -xdp_tmp_get_backing_basename (XdpTmp *tmp) -{ - char *res; - g_mutex_lock (&tmp->mutex); - res = g_strdup (tmp->backing_basename); - g_mutex_unlock (&tmp->mutex); - - return res; -} - -char * -xdp_tmp_steal_backing_basename (XdpTmp *tmp) -{ - char *res; - g_mutex_lock (&tmp->mutex); - res = tmp->backing_basename; - tmp->backing_basename = NULL; - g_mutex_unlock (&tmp->mutex); - - return res; -} - -G_DEFINE_AUTOPTR_CLEANUP_FUNC(XdpTmp, xdp_tmp_unref) - -/* Must first take tmp_files lock */ -static XdpTmp * -find_tmp_by_name_nolock (guint64 parent_inode, - const char *name) -{ - GList *l; - - for (l = tmp_files; l != NULL; l = l->next) - { - XdpTmp *tmp = l->data; - if (tmp->parent_inode == parent_inode && - strcmp (tmp->name, name) == 0) - return xdp_tmp_ref (tmp); - } - - return NULL; -} - -/* Takes tmp_files lock */ -static XdpTmp * -find_tmp_by_name (guint64 parent_inode, - const char *name) -{ - AUTOLOCK(tmp_files); - return find_tmp_by_name_nolock (parent_inode, name); -} - -/* Takes tmp_files lock */ -static XdpTmp * -find_tmp_by_id (guint32 tmp_id) -{ - GList *l; - - AUTOLOCK(tmp_files); - - for (l = tmp_files; l != NULL; l = l->next) - { - XdpTmp *tmp = l->data; - if (tmp->tmp_id == tmp_id) - return xdp_tmp_ref (tmp); - } - - return NULL; -} - -/* Caller must hold tmp_files lock */ -static XdpTmp * -xdp_tmp_new_nolock (fuse_ino_t parent, - XdgAppDbEntry *entry, - const char *name, - const char *tmp_basename) -{ - XdpTmp *tmp; - g_autofree char *tmp_dirname = NULL; - - /* We store the pathname instead of dir_fd + basename, because - its very easy to get a lot of tempfiles leaking and that would - mean quite a lot of open fds */ - tmp_dirname = xdp_entry_dup_dirname (entry); - - tmp = g_new0 (XdpTmp, 1); - tmp->ref_count = 2; /* One owned by tmp_files */ - tmp->tmp_id = g_atomic_int_add (&next_tmp_id, 1); - tmp->parent_inode = parent; - tmp->name = g_strdup (name); - tmp->entry = xdg_app_db_entry_ref (entry); - tmp->backing_basename = g_strdup (tmp_basename); - - tmp_files = g_list_prepend (tmp_files, tmp); - - return tmp; -} - -/* Caller must own tmp_files lock */ -static void -xdp_tmp_unlink_nolock (XdpTmp *tmp) -{ - - g_autofree char *backing_basename = NULL; - - backing_basename = xdp_tmp_steal_backing_basename (tmp); - if (backing_basename) - { - glnx_fd_close int dir_fd = xdp_entry_open_dir (tmp->entry); - if (dir_fd) - unlinkat (dir_fd, backing_basename, 0); - } - - tmp_files = g_list_remove (tmp_files, tmp); - xdp_tmp_unref (tmp); -} - -/******************************* XdpFh ******************************* - * - * XdpFh is a ref-counted object representing an open file on the - * filesystem. Normally it has a regular fd you can do only the allowed - * i/o on, although in the case of a direct write to a document file - * it has two fds, one is the read-only fd to the file, and the other - * is a read-write to a temporary file which is only used once the - * file is truncated (and is renamed over the real file on close). - * - * locking: - * - * The global list of outstanding Fh is protected by the open_files - * lock. Use it when doing lookups by inode, or when changing - * the list (add/remove), or when otherwise traversing the list. - * - * Each instance has a mutex that must be locked when doing some - * kind of operation on the file handle, to serialize both lower - * layer i/o as well as access to the members. - * - * To avoid deadlocks or just slow locking, never aquire the - * open_files lock and a lock on a Fh at the same time. - * - ******************************* XdpFh *******************************/ - - -typedef struct -{ - volatile gint ref_count; - - /* These are immutable, no lock needed */ - guint32 tmp_id; - fuse_ino_t inode; - int dir_fd; - char *trunc_basename; - char *real_basename; - gboolean can_write; - - /* These need a lock whenever they are used */ - int fd; - int trunc_fd; - gboolean truncated; - gboolean readonly; - - GMutex mutex; -} XdpFh; - -static GList *open_files = NULL; -G_LOCK_DEFINE(open_files); - -static XdpFh * -xdp_fh_ref (XdpFh *fh) -{ - g_atomic_int_inc (&fh->ref_count); - return fh; -} - -static void -xdp_fh_finalize (XdpFh *fh) -{ - if (fh->truncated) - { - fsync (fh->trunc_fd); - if (renameat (fh->dir_fd, fh->trunc_basename, - fh->dir_fd, fh->real_basename) != 0) - g_warning ("Unable to replace truncated document"); - } - else if (fh->trunc_basename) - unlinkat (fh->dir_fd, fh->trunc_basename, 0); - - if (fh->fd >= 0) - close (fh->fd); - - if (fh->trunc_fd >= 0) - close (fh->trunc_fd); - - if (fh->dir_fd >= 0) - close (fh->dir_fd); - - g_clear_pointer (&fh->trunc_basename, g_free); - g_clear_pointer (&fh->real_basename, g_free); - - g_free (fh); -} - -static void -xdp_fh_unref (XdpFh *fh) -{ - if (g_atomic_int_dec_and_test (&fh->ref_count)) - { - /* There is a tiny race here where fhs can be on the open_files list - with refcount 0, so make sure to skip such while under the open_files - lock */ - { - AUTOLOCK (open_files); - open_files = g_list_remove (open_files, fh); - } - - xdp_fh_finalize (fh); - } -} - -G_DEFINE_AUTOPTR_CLEANUP_FUNC(XdpFh, xdp_fh_unref) - -static void -xdp_fh_lock (XdpFh *fh) -{ - g_mutex_lock (&fh->mutex); -} - -static void -xdp_fh_unlock (XdpFh *fh) -{ - g_mutex_unlock (&fh->mutex); -} - -static inline void xdp_fh_auto_unlock_helper (XdpFh **fhp) -{ - if (*fhp) - xdp_fh_unlock (*fhp); -} - -static inline XdpFh *xdp_fh_auto_lock_helper (XdpFh *fh) -{ - if (fh) - xdp_fh_lock (fh); - return fh; -} - -#define XDP_FH_AUTOLOCK(_fh) G_GNUC_UNUSED __attribute__((cleanup(xdp_fh_auto_unlock_helper))) XdpFh * G_PASTE(xdp_fh_auto_unlock, __LINE__) = xdp_fh_auto_lock_helper (fh) - -static XdpFh * -xdp_fh_new (fuse_ino_t inode, - struct fuse_file_info *fi, - int fd, - XdpTmp *tmp) -{ - XdpFh *fh = g_new0 (XdpFh, 1); - fh->inode = inode; - fh->fd = fd; - if (tmp) - fh->tmp_id = tmp->tmp_id; - fh->dir_fd = -1; - fh->trunc_fd = -1; - fh->ref_count = 1; /* Owned by fuse_file_info fi */ - - fi->fh = (gsize)fh; - - AUTOLOCK (open_files); - - open_files = g_list_prepend (open_files, fh); - - return fh; -} - -static int -xdp_fh_get_fd_nolock (XdpFh *fh) -{ - if (fh->truncated) - return fh->trunc_fd; - else - return fh->fd; -} - -static int -xdp_fh_fstat (XdpFh *fh, - struct stat *stbuf) -{ - struct stat tmp_stbuf; - int fd; - - fd = xdp_fh_get_fd_nolock (fh); - if (fd < 0) - return -ENOSYS; - - if (fstat (fd, &tmp_stbuf) != 0) - return -errno; - - stbuf->st_nlink = DOC_FILE_NLINK; - stbuf->st_mode = S_IFREG | get_user_perms (&tmp_stbuf); - if (!fh->can_write) - stbuf->st_mode &= ~(0222); - stbuf->st_size = tmp_stbuf.st_size; - stbuf->st_uid = tmp_stbuf.st_uid; - stbuf->st_gid = tmp_stbuf.st_gid; - stbuf->st_blksize = tmp_stbuf.st_blksize; - stbuf->st_blocks = tmp_stbuf.st_blocks; - stbuf->st_atim = tmp_stbuf.st_atim; - stbuf->st_mtim = tmp_stbuf.st_mtim; - stbuf->st_ctim = tmp_stbuf.st_ctim; - - return 0; -} - -static int -xdp_fh_fstat_locked (XdpFh *fh, - struct stat *stbuf) -{ - XDP_FH_AUTOLOCK (fh); - - return xdp_fh_fstat (fh, stbuf); -} - -static int -xdp_fh_truncate_locked (XdpFh *fh, off_t size, struct stat *newattr) -{ - int fd; - - XDP_FH_AUTOLOCK (fh); - - if (fh->trunc_fd >= 0 && !fh->truncated) - { - if (size != 0) - return -EACCES; - - fh->truncated = TRUE; - fd = fh->trunc_fd; - } - else - { - fd = xdp_fh_get_fd_nolock (fh); - if (fd == -1) - return -EIO; - - if (ftruncate (fd, size) != 0) - return - errno; - } - - if (newattr) - { - int res = xdp_fh_fstat (fh, newattr); - if (res < 0) - return res; - } - - return 0; -} - -static void -mark_open_tmp_file_readonly (guint32 tmp_id) -{ - GList *found = NULL; - GList *l; - - { - AUTOLOCK (open_files); - - for (l = open_files; l != NULL; l = l->next) - { - XdpFh *fh = l->data; - /* See xdp_fh_unref for details of this ref_count check */ - if (g_atomic_int_get (&fh->ref_count) > 0 && - fh->tmp_id == tmp_id && fh->fd >= 0) - found = g_list_prepend (found, xdp_fh_ref (fh)); - } - } - - /* We do the actual updates outside of the open_files lock to avoid - potentially blocking for a long time with it held */ - - for (l = found; l != NULL; l = l->next) - { - XdpFh *fh = l->data; - XDP_FH_AUTOLOCK (fh); - fh->readonly = TRUE; - xdp_fh_unref (fh); - } - g_list_free (found); -} - - -static XdpFh * -find_open_fh (fuse_ino_t ino) -{ - GList *l; - - AUTOLOCK (open_files); - - for (l = open_files; l != NULL; l = l->next) - { - XdpFh *fh = l->data; - /* See xdp_fh_unref for details of this ref_count check */ - if (fh->inode == ino && - g_atomic_int_get (&fh->ref_count) > 0) - return xdp_fh_ref (fh); - } - - return NULL; -} - -/******************************* Main *******************************/ - -static XdpInodeClass -get_class (guint64 inode) -{ - return (inode >> (64-8)) & 0xff; -} - -static guint64 -get_class_ino (guint64 inode) -{ - return inode & ((1L << (64-8)) - 1); -} - -static guint32 -get_app_id_from_app_doc_ino (guint64 inode) -{ - return inode >> 32; -} - -static guint32 -get_doc_id_from_app_doc_ino (guint64 inode) -{ - return inode & 0xffffffff; -} - -static guint64 -make_inode (XdpInodeClass class, guint64 inode) -{ - return ((guint64)class) << (64-8) | (inode & 0xffffffffffffff); -} - -static guint64 -make_app_doc_dir_inode (guint32 app_id, guint32 doc_id) -{ - return make_inode (APP_DOC_DIR_INO_CLASS, - ((guint64)app_id << 32) | (guint64)doc_id); -} - -static guint64 -make_app_doc_file_inode (guint32 app_id, guint32 doc_id) -{ - return make_inode (APP_DOC_FILE_INO_CLASS, - ((guint64)app_id << 32) | (guint64)doc_id); -} - static gboolean -name_looks_like_id (const char *name) +app_can_write_doc (XdgAppDbEntry *entry, const char *app_id) { - int i; - - /* No zeros in front, we need canonical form */ - if (name[0] == '0') - return FALSE; - - for (i = 0; i < 8; i++) - { - char c = name[i]; - if (c == 0) - break; - - if (!g_ascii_isdigit(c) && - !(c >= 'a' && c <= 'f')) - return FALSE; - } - - if (name[i] != 0) - return FALSE; - - return TRUE; -} - -static guint32 -get_app_id_from_name (const char *name) -{ - guint32 id; - char *myname; - - AUTOLOCK(app_id); - - id = GPOINTER_TO_UINT (g_hash_table_lookup (app_name_to_id, name)); - - if (id != 0) - return id; - - id = next_app_id++; - - /* We rely this to not overwrap into the high byte in the inode */ - g_assert (id < 0x00ffffff); - - myname = g_strdup (name); - g_hash_table_insert (app_name_to_id, myname, GUINT_TO_POINTER (id)); - g_hash_table_insert (app_id_to_name, GUINT_TO_POINTER (id), myname); - return id; -} - -static const char * -get_app_name_from_id (guint32 id) -{ - AUTOLOCK(app_id); - return g_hash_table_lookup (app_id_to_name, GUINT_TO_POINTER (id)); -} - -static void -fill_app_name_hash (void) -{ - g_auto(GStrv) keys = NULL; - int i; - - keys = xdp_list_apps (); - for (i = 0; keys[i] != NULL; i++) - get_app_id_from_name (keys[i]); -} - -static gboolean -app_can_see_doc (XdgAppDbEntry *entry, guint32 app_id) -{ - const char *app_name = get_app_name_from_id (app_id); - - if (app_id == 0) + if (app_id == NULL) return TRUE; - if (app_name != NULL && - xdp_entry_has_permissions (entry, app_name, XDP_PERMISSION_FLAGS_READ)) + if (xdp_entry_has_permissions (entry, app_id, XDP_PERMISSION_FLAGS_WRITE)) return TRUE; return FALSE; } static gboolean -app_can_write_doc (XdgAppDbEntry *entry, guint32 app_id) +app_can_see_doc (XdgAppDbEntry *entry, const char *app_id) { - const char *app_name = get_app_name_from_id (app_id); - - if (app_id == 0) + if (app_id == NULL) return TRUE; - if (app_name != NULL && - xdp_entry_has_permissions (entry, app_name, XDP_PERMISSION_FLAGS_WRITE)) + if (xdp_entry_has_permissions (entry, app_id, XDP_PERMISSION_FLAGS_READ)) return TRUE; return FALSE; } - +/* Call with mutex held! */ static int -xdp_stat (fuse_ino_t ino, - struct stat *stbuf, - XdgAppDbEntry **entry_out) +xdp_inode_locked_get_fd (XdpInode *inode) { - XdpInodeClass class = get_class (ino); - guint64 class_ino = get_class_ino (ino); - g_autoptr (XdgAppDbEntry) entry = NULL; - struct stat tmp_stbuf; - g_autoptr(XdpTmp) tmp = NULL; - g_autofree char *backing_basename = NULL; + if (inode->truncated) + return inode->trunc_fd; - stbuf->st_ino = ino; + return inode->fd; +} - switch (class) +/* Call with mutex held! */ +static int +xdp_inode_locked_get_write_fd (XdpInode *inode) +{ + if (inode->is_doc) { - case STD_DIRS_INO_CLASS: - - switch (class_ino) + if (!inode->truncated) { - case FUSE_ROOT_ID: - stbuf->st_mode = S_IFDIR | NON_DOC_DIR_PERMS; - stbuf->st_nlink = 2; - break; - - case BY_APP_INO: - stbuf->st_mode = S_IFDIR | NON_DOC_DIR_PERMS; - stbuf->st_nlink = 2; - break; - - default: - return ENOENT; + errno = ENOSYS; + return -1; } - break; + return inode->trunc_fd; + } - case APP_DIR_INO_CLASS: - if (get_app_name_from_id (class_ino) == 0) - return ENOENT; + return inode->fd; +} +static int +xdp_inode_stat (XdpInode *inode, + struct stat *stbuf) +{ + stbuf->st_ino = inode->ino; + + switch (inode->type) + { + case XDP_INODE_ROOT: + case XDP_INODE_BY_APP: + case XDP_INODE_APP_DIR: stbuf->st_mode = S_IFDIR | NON_DOC_DIR_PERMS; stbuf->st_nlink = 2; break; - case APP_DOC_DIR_INO_CLASS: + case XDP_INODE_DOC_DIR: + case XDP_INODE_APP_DOC_DIR: + stbuf->st_mode = S_IFDIR | DOC_DIR_PERMS; + stbuf->st_nlink = 2; + break; + + case XDP_INODE_DOC_FILE: { - guint32 app_id = get_app_id_from_app_doc_ino (class_ino); - guint32 doc_id = get_doc_id_from_app_doc_ino (class_ino); + g_autoptr (XdgAppDbEntry) entry = NULL; + struct stat tmp_stbuf; + gboolean can_see, can_write; + int fd, res, errsv; - entry = xdp_lookup_doc (doc_id); - if (entry == NULL || !app_can_see_doc (entry, app_id)) - return ENOENT; - - stbuf->st_mode = S_IFDIR | DOC_DIR_PERMS; - stbuf->st_nlink = 2; - break; - } - - case APP_DOC_FILE_INO_CLASS: - { - guint32 app_id = get_app_id_from_app_doc_ino (class_ino); - guint32 doc_id = get_doc_id_from_app_doc_ino (class_ino); - gboolean can_write; - - entry = xdp_lookup_doc (doc_id); + entry = xdp_lookup_doc (inode->doc_id); if (entry == NULL) - return ENOENT; + { + errno = ENOENT; + return -1; + } - can_write = app_can_write_doc (entry, app_id); + can_see = app_can_see_doc (entry, inode->app_id); + can_write = app_can_write_doc (entry, inode->app_id); - stbuf->st_nlink = DOC_FILE_NLINK; + if (!can_see) + { + errno = ENOENT; + return -1; + } - if (xdp_entry_stat (entry, &tmp_stbuf, AT_SYMLINK_NOFOLLOW) != 0) - return ENOENT; + g_mutex_lock (&inode->mutex); + + fd = xdp_inode_locked_get_fd (inode); + if (fd != -1) + res = fstat (fd, &tmp_stbuf); + else + { + glnx_fd_close int dir_fd = xdp_inode_open_dir_fd (inode->parent); + + if (dir_fd == -1) + res = -1; + else + res = fstatat (dir_fd, inode->backing_filename, + &tmp_stbuf, AT_SYMLINK_NOFOLLOW); + } + errsv = errno; + + g_mutex_unlock (&inode->mutex); + + if (res != 0) + { + errno = errsv; + return -1; + } stbuf->st_mode = S_IFREG | get_user_perms (&tmp_stbuf); if (!can_write) @@ -799,239 +920,138 @@ xdp_stat (fuse_ino_t ino, stbuf->st_atim = tmp_stbuf.st_atim; stbuf->st_mtim = tmp_stbuf.st_mtim; stbuf->st_ctim = tmp_stbuf.st_ctim; - break; } - - case TMPFILE_INO_CLASS: - tmp = find_tmp_by_id (class_ino); - if (tmp == NULL) - return ENOENT; - - stbuf->st_mode = S_IFREG; - stbuf->st_nlink = DOC_FILE_NLINK; - - backing_basename = xdp_tmp_get_backing_basename (tmp); - - { - glnx_fd_close int dir_fd = xdp_entry_open_dir (tmp->entry); - - if (backing_basename == NULL || - dir_fd == -1 || - fstatat (dir_fd, backing_basename, &tmp_stbuf, 0) != 0) - return ENOENT; - } - - stbuf->st_mode = S_IFREG | get_user_perms (&tmp_stbuf); - stbuf->st_size = tmp_stbuf.st_size; - stbuf->st_uid = tmp_stbuf.st_uid; - stbuf->st_gid = tmp_stbuf.st_gid; - stbuf->st_blksize = tmp_stbuf.st_blksize; - stbuf->st_blocks = tmp_stbuf.st_blocks; - stbuf->st_atim = tmp_stbuf.st_atim; - stbuf->st_mtim = tmp_stbuf.st_mtim; - stbuf->st_ctim = tmp_stbuf.st_ctim; break; default: - return ENOENT; + g_assert_not_reached (); } - if (entry && entry_out) - *entry_out = g_steal_pointer (&entry); - return 0; } -static void -xdp_fuse_getattr (fuse_req_t req, - fuse_ino_t ino, - struct fuse_file_info *fi) -{ - struct stat stbuf = { 0 }; - g_autoptr(XdpFh) fh = NULL; - int res; - - g_debug ("xdp_fuse_getattr %lx (fi=%p)", ino, fi); - - /* Fuse passes fi in to verify EOF during read/write/seek, but not during fstat */ - if (fi != NULL) - { - XdpFh *fh = (gpointer)fi->fh; - - res = xdp_fh_fstat_locked (fh, &stbuf); - if (res == 0) - { - fuse_reply_attr (req, &stbuf, get_attr_cache_time (stbuf.st_mode)); - return; - } - } - - - fh = find_open_fh (ino); - if (fh) - { - res = xdp_fh_fstat_locked (fh, &stbuf); - if (res == 0) - { - fuse_reply_attr (req, &stbuf, get_attr_cache_time (stbuf.st_mode)); - return; - } - } - - if ((res = xdp_stat (ino, &stbuf, NULL)) != 0) - fuse_reply_err (req, res); - else - fuse_reply_attr (req, &stbuf, get_attr_cache_time (stbuf.st_mode)); -} - -static int -xdp_lookup (fuse_ino_t parent, - const char *name, - fuse_ino_t *inode, - struct stat *stbuf, - XdgAppDbEntry **entry_out, - XdpTmp **tmp_out) -{ - XdpInodeClass parent_class = get_class (parent); - guint64 parent_class_ino = get_class_ino (parent); - g_autoptr (XdgAppDbEntry) entry = NULL; - g_autoptr (XdpTmp) tmp = NULL; - - if (entry_out) - *entry_out = NULL; - if (tmp_out) - *tmp_out = NULL; - - switch (parent_class) - { - case STD_DIRS_INO_CLASS: - - switch (parent_class_ino) - { - case FUSE_ROOT_ID: - if (strcmp (name, BY_APP_NAME) == 0) - { - *inode = make_inode (STD_DIRS_INO_CLASS, BY_APP_INO); - if (xdp_stat (*inode, stbuf, NULL) == 0) - return 0; - } - else if (name_looks_like_id (name)) - { - *inode = make_app_doc_dir_inode (0, xdp_id_from_name (name)); - if (xdp_stat (*inode, stbuf, NULL) == 0) - return 0; - } - - break; - - case BY_APP_INO: - if (xdg_app_is_valid_name (name)) - { - guint32 app_id = get_app_id_from_name (name); - *inode = make_inode (APP_DIR_INO_CLASS, app_id); - if (xdp_stat (*inode, stbuf, NULL) == 0) - return 0; - } - - break; - - default: - break; - } - break; - - case APP_DIR_INO_CLASS: - { - if (name_looks_like_id (name)) - { - *inode = make_app_doc_dir_inode (parent_class_ino, - xdp_id_from_name (name)); - if (xdp_stat (*inode, stbuf, NULL) == 0) - return 0; - } - } - - break; - - case APP_DOC_DIR_INO_CLASS: - { - guint32 app_id = get_app_id_from_app_doc_ino (parent_class_ino); - guint32 doc_id = get_doc_id_from_app_doc_ino (parent_class_ino); - - entry = xdp_lookup_doc (doc_id); - if (entry != NULL) - { - g_autofree char *basename = xdp_entry_dup_basename (entry); - if (strcmp (name, basename) == 0) - { - *inode = make_app_doc_file_inode (app_id, doc_id); - if (xdp_stat (*inode, stbuf, NULL) == 0) - { - if (entry_out) - *entry_out = g_steal_pointer (&entry); - return 0; - } - - break; - } - } - - tmp = find_tmp_by_name (parent, name); - if (tmp != NULL) - { - *inode = make_inode (TMPFILE_INO_CLASS, tmp->tmp_id); - if (xdp_stat (*inode, stbuf, NULL) == 0) - { - if (entry_out) - *entry_out = g_steal_pointer (&entry); - if (tmp_out) - *tmp_out = g_steal_pointer (&tmp); - return 0; - } - - break; - } - - break; - } - - case TMPFILE_INO_CLASS: - case APP_DOC_FILE_INO_CLASS: - return ENOTDIR; - - default: - break; - } - - return ENOENT; -} - static void xdp_fuse_lookup (fuse_req_t req, fuse_ino_t parent, const char *name) { + g_autoptr(XdpInode) parent_inode = NULL; struct fuse_entry_param e = {0}; - int res; + g_autoptr(XdpInode) child_inode = NULL; + g_autoptr (XdgAppDbEntry) entry = NULL; g_debug ("xdp_fuse_lookup %lx/%s -> ", parent, name); - memset (&e, 0, sizeof(e)); - - res = xdp_lookup (parent, name, &e.ino, &e.attr, NULL, NULL); - - if (res == 0) + parent_inode = xdp_inode_lookup (parent); + if (parent_inode == NULL) { - g_debug ("xdp_fuse_lookup <- inode %lx", (long)e.ino); - e.attr_timeout = get_attr_cache_time (e.attr.st_mode); - e.entry_timeout = get_entry_cache_time (e.ino); - fuse_reply_entry (req, &e); + g_debug ("xdp_fuse_lookup <- error parent ENOENT"); + fuse_reply_err (req, ENOENT); + return; } + + /* Default */ + e.attr_timeout = ATTR_CACHE_TIME; + e.entry_timeout = ENTRY_CACHE_TIME; + + switch (parent_inode->type) + { + case XDP_INODE_ROOT: + if (strcmp (name, BY_APP_NAME) == 0) + child_inode = xdp_inode_ref (by_app_inode); + else + { + entry = xdp_lookup_doc (name); + if (entry != NULL) + child_inode = xdp_inode_get_dir (NULL, name, entry); + } + break; + + case XDP_INODE_BY_APP: + /* This lazily creates the app dir */ + if (xdg_app_is_valid_name (name)) + child_inode = xdp_inode_get_dir (name, NULL, NULL); + break; + + case XDP_INODE_APP_DIR: + entry = xdp_lookup_doc (name); + if (entry != NULL && + app_can_see_doc (entry, parent_inode->app_id)) + child_inode = xdp_inode_get_dir (parent_inode->app_id, name, entry); + break; + + case XDP_INODE_APP_DOC_DIR: + case XDP_INODE_DOC_DIR: + { + g_autoptr(XdpInode) doc_inode = NULL; + entry = xdp_lookup_doc (parent_inode->doc_id); + if (entry == NULL) + { + g_debug ("xdp_fuse_lookup <- error no parent entry ENOENT"); + fuse_reply_err (req, ENOENT); + return; + } + + /* Ensure it is alive at least during lookup_child () */ + doc_inode = xdp_inode_ensure_document_file (parent_inode, entry); + + child_inode = xdp_inode_lookup_child (parent_inode, name); + + /* We verify in the stat below if the backing file exists */ + + /* Files can be changed from outside the fuse fs, so don't cache any data */ + e.attr_timeout = 0; + e.entry_timeout = 0; + } + break; + + case XDP_INODE_DOC_FILE: + fuse_reply_err (req, ENOTDIR); + return; + + default: + break; + } + + if (child_inode == NULL) + { + g_debug ("xdp_fuse_lookup <- error child ENOENT"); + fuse_reply_err (req, ENOENT); + return; + } + + if (xdp_inode_stat (child_inode, &e.attr) != 0) + { + fuse_reply_err (req, errno); + return; + } + + e.ino = child_inode->ino; + + g_debug ("xdp_fuse_lookup <- inode %lx", (long)e.ino); + xdp_inode_ref (child_inode); /* Ref given to the kernel, returned in xdp_fuse_forget() */ + fuse_reply_entry (req, &e); +} + +void +xdp_fuse_forget (fuse_req_t req, fuse_ino_t ino, unsigned long nlookup) +{ + g_autoptr(XdpInode) inode = NULL; + g_debug ("xdp_fuse_forget %lx %ld -> ", ino, nlookup); + + inode = xdp_inode_lookup (ino); + if (inode == NULL) + g_warning ("xdp_fuse_forget, unknown inode"); else { - g_debug ("xdp_fuse_lookup <- error %s", strerror (res)); - fuse_reply_err (req, res); + while (nlookup > 0) + { + xdp_inode_unref (inode); + nlookup--; + } } + + fuse_reply_none (req); } struct dirbuf { @@ -1043,7 +1063,8 @@ static void dirbuf_add (fuse_req_t req, struct dirbuf *b, const char *name, - fuse_ino_t ino) + fuse_ino_t ino, + mode_t mode) { struct stat stbuf; @@ -1052,6 +1073,7 @@ dirbuf_add (fuse_req_t req, b->p = (char *) g_realloc (b->p, b->size); memset (&stbuf, 0, sizeof (stbuf)); stbuf.st_ino = ino; + stbuf.st_mode = mode; fuse_add_direntry (req, b->p + oldsize, b->size - oldsize, name, &stbuf, @@ -1061,15 +1083,14 @@ dirbuf_add (fuse_req_t req, static void dirbuf_add_docs (fuse_req_t req, struct dirbuf *b, - guint32 app_id) + const char *app_id) { - g_autofree guint32 *docs = NULL; - guint64 inode; + g_auto(GStrv) docs = NULL; + fuse_ino_t ino; int i; - g_autofree char *doc_name = NULL; docs = xdp_list_docs (); - for (i = 0; docs[i] != 0; i++) + for (i = 0; docs[i] != NULL; i++) { if (app_id) { @@ -1078,44 +1099,8 @@ dirbuf_add_docs (fuse_req_t req, !app_can_see_doc (entry, app_id)) continue; } - inode = make_app_doc_dir_inode (app_id, docs[i]); - doc_name = xdp_name_from_id (docs[i]); - dirbuf_add (req, b, doc_name, inode); - } -} - -static void -dirbuf_add_doc_file (fuse_req_t req, - struct dirbuf *b, - XdgAppDbEntry *entry, - guint32 doc_id, - guint32 app_id) -{ - struct stat tmp_stbuf; - guint64 inode; - g_autofree char *basename = xdp_entry_dup_basename (entry); - - inode = make_app_doc_file_inode (app_id, doc_id); - - if (xdp_entry_stat (entry, &tmp_stbuf, AT_SYMLINK_NOFOLLOW) == 0) - dirbuf_add (req, b, basename, inode); -} - -static void -dirbuf_add_tmp_files (fuse_req_t req, - struct dirbuf *b, - guint64 dir_inode) -{ - GList *l; - - AUTOLOCK(tmp_files); - - for (l = tmp_files; l != NULL; l = l->next) - { - XdpTmp *tmp = l->data; - if (tmp->parent_inode == dir_inode) - dirbuf_add (req, b, tmp->name, - make_inode (TMPFILE_INO_CLASS, tmp->tmp_id)); + ino = get_dir_inode_nr (app_id, docs[i]); + dirbuf_add (req, b, docs[i], ino, S_IFDIR); } } @@ -1147,107 +1132,101 @@ xdp_fuse_opendir (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { - struct stat stbuf = {0}; + g_autoptr(XdpInode) inode = NULL; struct dirbuf b = {0}; - XdpInodeClass class; - guint64 class_ino; g_autoptr (XdgAppDbEntry) entry = NULL; - int res; g_debug ("xdp_fuse_opendir %lx", ino); - if ((res = xdp_stat (ino, &stbuf, &entry)) != 0) + inode = xdp_inode_lookup (ino); + if (inode == NULL) { - fuse_reply_err (req, res); + g_debug ("xdp_fuse_opendir <- error ENOENT"); + fuse_reply_err (req, ENOENT); return; } - if ((stbuf.st_mode & S_IFMT) != S_IFDIR) + switch (inode->type) { - fuse_reply_err (req, ENOTDIR); - return; - } - - class = get_class (ino); - class_ino = get_class_ino (ino); - - switch (class) - { - case STD_DIRS_INO_CLASS: - switch (class_ino) - { - case FUSE_ROOT_ID: - dirbuf_add (req, &b, ".", FUSE_ROOT_ID); - dirbuf_add (req, &b, "..", FUSE_ROOT_ID); - dirbuf_add (req, &b, BY_APP_NAME, - make_inode (STD_DIRS_INO_CLASS, BY_APP_INO)); - dirbuf_add_docs (req, &b, 0); - break; - - case BY_APP_INO: - dirbuf_add (req, &b, ".", ino); - dirbuf_add (req, &b, "..", FUSE_ROOT_ID); - - /* Update for any possible new app */ - fill_app_name_hash (); - - { - GHashTableIter iter; - gpointer key, value; - - AUTOLOCK(app_id); - - g_hash_table_iter_init (&iter, app_name_to_id); - while (g_hash_table_iter_next (&iter, &key, &value)) - { - const char *name = key; - guint32 id = GPOINTER_TO_UINT(value); - - if (strlen (name) > 0) - dirbuf_add (req, &b, name, - make_inode (APP_DIR_INO_CLASS, id)); - } - } - break; - - default: - break; - } + case XDP_INODE_ROOT: + dirbuf_add (req, &b, ".", ROOT_INODE, S_IFDIR); + dirbuf_add (req, &b, "..", ROOT_INODE, S_IFDIR); + dirbuf_add (req, &b, BY_APP_NAME, BY_APP_INODE, S_IFDIR); + dirbuf_add_docs (req, &b, NULL); break; - case APP_DIR_INO_CLASS: + case XDP_INODE_BY_APP: { - dirbuf_add (req, &b, ".", ino); - dirbuf_add (req, &b, "..", make_inode (STD_DIRS_INO_CLASS, BY_APP_INO)); - dirbuf_add_docs (req, &b, class_ino); - break; + g_auto(GStrv) db_app_ids = NULL; + g_auto(GStrv) app_ids = NULL; + int i; + + dirbuf_add (req, &b, ".", BY_APP_INODE, S_IFDIR); + dirbuf_add (req, &b, "..", ROOT_INODE, S_IFDIR); + + /* Ensure that all apps from db are allocated */ + db_app_ids = xdp_list_apps (); + allocate_app_dir_inode_nr (db_app_ids); + + /* But return all allocated dirs. We might have app dirs + that have no permissions, and are thus not in the db */ + app_ids = get_allocated_app_dirs (); + for (i = 0; app_ids[i] != NULL; i++) + dirbuf_add (req, &b, app_ids[i], + get_dir_inode_nr (app_ids[i], NULL), S_IFDIR); } - break; - case APP_DOC_DIR_INO_CLASS: - dirbuf_add (req, &b, ".", ino); - if (get_app_id_from_app_doc_ino (class_ino) == 0) - dirbuf_add (req, &b, "..", FUSE_ROOT_ID); - else - dirbuf_add (req, &b, "..", make_inode (APP_DIR_INO_CLASS, - get_app_id_from_app_doc_ino (class_ino))); - dirbuf_add_doc_file (req, &b, entry, - get_doc_id_from_app_doc_ino (class_ino), - get_app_id_from_app_doc_ino (class_ino)); - dirbuf_add_tmp_files (req, &b, ino); + case XDP_INODE_APP_DIR: + dirbuf_add (req, &b, ".", inode->ino, S_IFDIR); + dirbuf_add (req, &b, "..", BY_APP_INODE, S_IFDIR); + dirbuf_add_docs (req, &b, inode->app_id); + break; + + case XDP_INODE_DOC_FILE: + fuse_reply_err (req, ENOTDIR); + break; + + case XDP_INODE_APP_DOC_DIR: + case XDP_INODE_DOC_DIR: + { + GList *children, *l; + g_autoptr(XdpInode) doc_inode = NULL; + g_autoptr (XdgAppDbEntry) entry = NULL; + + entry = xdp_lookup_doc (inode->doc_id); + if (entry == NULL) + { + fuse_reply_err (req, ENOENT); + break; + } + + dirbuf_add (req, &b, ".", inode->ino, S_IFDIR); + dirbuf_add (req, &b, "..", inode->parent->ino, S_IFDIR); + + /* Ensure it is alive at least during list_children () */ + doc_inode = xdp_inode_ensure_document_file (inode, entry); + + children = xdp_inode_list_children (inode); + + for (l = children; l != NULL; l = l->next) + { + struct stat stbuf; + XdpInode *child = l->data; + g_autofree char *filename = xdp_inode_get_filename (child); + if (filename != NULL && xdp_inode_stat (child, &stbuf) == 0) + dirbuf_add (req, &b, filename, child->ino, stbuf.st_mode); + xdp_inode_unref (child); + } + g_list_free (children); + } break; - case APP_DOC_FILE_INO_CLASS: - case TMPFILE_INO_CLASS: - /* These should have returned ENOTDIR above */ default: - break; + g_assert_not_reached (); } - if (b.p == NULL) - fuse_reply_err (req, EIO); - else + if (b.p != NULL) { fi->fh = (gsize)g_memdup (&b, sizeof (b)); if (fuse_reply_open (req, fi) == -ENOENT) @@ -1269,670 +1248,33 @@ xdp_fuse_releasedir (fuse_req_t req, fuse_reply_err (req, 0); } -static int -get_open_flags (struct fuse_file_info *fi) -{ - /* TODO: Maybe limit the flags set more */ - return fi->flags & ~(O_EXCL|O_CREAT); -} -static char * -create_tmp_for_doc (XdgAppDbEntry *entry, int dir_fd, int flags, int *fd_out) -{ - g_autofree char *basename = xdp_entry_dup_basename (entry); - g_autofree char *template = g_strconcat (".xdp_", basename, ".XXXXXX", NULL); - int fd; - - fd = xdg_app_mkstempat (dir_fd, template, flags|O_CLOEXEC, 0600); - if (fd == -1) - return NULL; - - *fd_out = fd; - return g_steal_pointer (&template); -} static void -xdp_fuse_open (fuse_req_t req, - fuse_ino_t ino, - struct fuse_file_info *fi) -{ - XdpInodeClass class = get_class (ino); - guint64 class_ino = get_class_ino (ino); - struct stat stbuf = {0}; - g_autoptr (XdgAppDbEntry) entry = NULL; - g_autoptr(XdpTmp) tmp = NULL; - glnx_fd_close int fd = -1; - int res; - XdpFh *fh = NULL; - - g_debug ("xdp_fuse_open %lx", ino); - - if ((res = xdp_stat (ino, &stbuf, &entry)) != 0) - { - fuse_reply_err (req, res); - return; - } - - if ((stbuf.st_mode & S_IFMT) != S_IFREG) - { - fuse_reply_err (req, EISDIR); - return; - } - - if (entry && class == APP_DOC_FILE_INO_CLASS) - { - g_autofree char *tmp_basename = NULL; - glnx_fd_close int write_fd = -1; - glnx_fd_close int dir_fd = -1; - g_autofree char *basename = xdp_entry_dup_basename (entry); - guint32 app_id = get_app_id_from_app_doc_ino (class_ino); - gboolean can_write; - - dir_fd = xdp_entry_open_dir (entry); - if (dir_fd == -1) - { - fuse_reply_err (req, errno); - return; - } - - can_write = app_can_write_doc (entry, app_id); - - if ((fi->flags & 3) != O_RDONLY) - { - if (!can_write) - { - fuse_reply_err (req, EACCES); - return; - } - - if (faccessat (dir_fd, basename, W_OK, 0) != 0) - { - fuse_reply_err (req, errno); - return; - } - - tmp_basename = create_tmp_for_doc (entry, dir_fd, O_RDWR, &write_fd); - if (tmp_basename == NULL) - { - fuse_reply_err (req, errno); - return; - } - } - - fd = openat (dir_fd, basename, O_RDONLY|O_NOFOLLOW|O_CLOEXEC); - if (fd < 0) - { - fuse_reply_err (req, errno); - return; - } - fh = xdp_fh_new (ino, fi, steal_fd(&fd), NULL); - fh->can_write = can_write; - fh->dir_fd = steal_fd (&dir_fd); - fh->trunc_fd = steal_fd (&write_fd); - fh->trunc_basename = g_steal_pointer (&tmp_basename); - fh->real_basename = g_strdup (basename); - if (fuse_reply_open (req, fi)) - xdp_fh_unref (fh); - } - else if (class == TMPFILE_INO_CLASS && - (tmp = find_tmp_by_id (class_ino))) - { - glnx_fd_close int dir_fd = xdp_entry_open_dir (tmp->entry); - g_autofree char *backing_basename = xdp_tmp_get_backing_basename (tmp); - if (dir_fd == -1 || backing_basename == NULL) - { - fuse_reply_err (req, ENOENT); - return; - } - - fd = openat (dir_fd, backing_basename, get_open_flags (fi)|O_NOFOLLOW|O_CLOEXEC); - if (fd < 0) - { - fuse_reply_err (req, errno); - return; - } - fh = xdp_fh_new (ino, fi, steal_fd (&fd), tmp); - fh->can_write = TRUE; - if (fuse_reply_open (req, fi)) - xdp_fh_unref (fh); - } - else - fuse_reply_err (req, EIO); -} - -static void -xdp_fuse_create (fuse_req_t req, - fuse_ino_t parent, - const char *name, - mode_t mode, - struct fuse_file_info *fi) -{ - struct fuse_entry_param e = {0}; - XdpInodeClass parent_class = get_class (parent); - guint64 parent_class_ino = get_class_ino (parent); - struct stat stbuf; - XdpFh *fh; - g_autoptr(XdgAppDbEntry) entry = NULL; - g_autofree char *basename = NULL; - glnx_fd_close int fd = -1; - gboolean can_write; - int res; - guint32 app_id = 0; - guint32 doc_id; - - g_debug ("xdp_fuse_create %lx/%s, flags %o", parent, name, fi->flags); - - if ((res = xdp_stat (parent, &stbuf, &entry)) != 0) - { - fuse_reply_err (req, res); - return; - } - - if ((stbuf.st_mode & S_IFMT) != S_IFDIR) - { - fuse_reply_err (req, ENOTDIR); - return; - } - - if (parent_class != APP_DOC_DIR_INO_CLASS) - { - fuse_reply_err (req, EACCES); - return; - } - - app_id = get_app_id_from_app_doc_ino (parent_class_ino); - doc_id = get_doc_id_from_app_doc_ino (parent_class_ino); - - can_write = app_can_write_doc (entry, app_id); - - basename = xdp_entry_dup_basename (entry); - if (strcmp (name, basename) == 0) - { - g_autofree char *tmp_basename = NULL; - glnx_fd_close int write_fd = -1; - glnx_fd_close int dir_fd = -1; - - dir_fd = xdp_entry_open_dir (entry); - if (dir_fd == -1) - { - fuse_reply_err (req, errno); - return; - } - - if (!can_write) - { - fuse_reply_err (req, EACCES); - return; - } - - tmp_basename = create_tmp_for_doc (entry, dir_fd, O_RDWR, &write_fd); - if (tmp_basename == NULL) - { - fuse_reply_err (req, errno); - return; - } - - fd = openat (dir_fd, basename, O_CREAT|O_EXCL|O_RDONLY|O_NOFOLLOW|O_CLOEXEC, mode & 0777); - if (fd < 0) - { - fuse_reply_err (req, errno); - return; - } - - e.ino = make_app_doc_file_inode (app_id, doc_id); - - fh = xdp_fh_new (e.ino, fi, steal_fd (&fd), NULL); - fh->can_write = TRUE; - fh->dir_fd = steal_fd (&dir_fd); - fh->truncated = TRUE; - fh->trunc_fd = steal_fd (&write_fd); - fh->trunc_basename = g_steal_pointer (&tmp_basename); - fh->real_basename = g_strdup (basename); - - if (xdp_fh_fstat_locked (fh, &e.attr) != 0) - { - xdp_fh_unref (fh); - fuse_reply_err (req, EIO); - return; - } - - e.attr_timeout = get_attr_cache_time (e.attr.st_mode); - e.entry_timeout = get_entry_cache_time (e.ino); - - if (fuse_reply_create (req, &e, fi)) - xdp_fh_unref (fh); - } - else - { - g_autoptr(XdpTmp) tmpfile = NULL; - - G_LOCK(tmp_files); - tmpfile = find_tmp_by_name_nolock (parent, name); - if (tmpfile != NULL && fi->flags & O_EXCL) - { - G_UNLOCK(tmp_files); - fuse_reply_err (req, EEXIST); - return; - } - - if (!can_write) - { - fuse_reply_err (req, EACCES); - return; - } - - if (tmpfile) - { - glnx_fd_close int dir_fd = xdp_entry_open_dir (tmpfile->entry); - g_autofree char *backing_basename = NULL; - - G_UNLOCK(tmp_files); - - backing_basename = xdp_tmp_get_backing_basename (tmpfile); - if (dir_fd == -1 || backing_basename == NULL) - { - fuse_reply_err (req, EINVAL); - return; - } - - fd = openat (dir_fd, backing_basename, get_open_flags (fi)|O_NOFOLLOW|O_CLOEXEC); - if (fd == -1) - { - fuse_reply_err (req, errno); - return; - } - } - else - { - int errsv; - g_autofree char *tmp_basename = NULL; - glnx_fd_close int dir_fd = -1; - - dir_fd = xdp_entry_open_dir (entry); - if (dir_fd == -1) - { - fuse_reply_err (req, errno); - return; - } - - tmp_basename = create_tmp_for_doc (entry, dir_fd, get_open_flags (fi), &fd); - if (tmp_basename == NULL) - return; - - tmpfile = xdp_tmp_new_nolock (parent, entry, name, tmp_basename); - errsv = errno; - G_UNLOCK(tmp_files); - - if (tmpfile == NULL) - { - fuse_reply_err (req, errsv); - return; - } - } - - e.ino = make_inode (TMPFILE_INO_CLASS, tmpfile->tmp_id); - if (xdp_stat (e.ino, &e.attr, NULL) != 0) - { - fuse_reply_err (req, EIO); - return; - } - e.attr_timeout = get_attr_cache_time (e.attr.st_mode); - e.entry_timeout = get_entry_cache_time (e.ino); - - fh = xdp_fh_new (e.ino, fi, steal_fd (&fd), tmpfile); - fh->can_write = TRUE; - if (fuse_reply_create (req, &e, fi)) - xdp_fh_unref (fh); - } -} - -static void -xdp_fuse_read (fuse_req_t req, - fuse_ino_t ino, - size_t size, - off_t off, - struct fuse_file_info *fi) -{ - XdpFh *fh = (gpointer)fi->fh; - struct fuse_bufvec bufv = FUSE_BUFVEC_INIT (size); - static char c = 'x'; - int fd; - - XDP_FH_AUTOLOCK (fh); - - fd = xdp_fh_get_fd_nolock (fh); - if (fd == -1) - { - bufv.buf[0].flags = 0; - bufv.buf[0].mem = &c; - bufv.buf[0].size = 0; - - fuse_reply_data (req, &bufv, FUSE_BUF_NO_SPLICE); - return; - } - - bufv.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; - bufv.buf[0].fd = fd; - bufv.buf[0].pos = off; - - fuse_reply_data (req, &bufv, FUSE_BUF_SPLICE_MOVE); -} - -static void -xdp_fuse_write (fuse_req_t req, - fuse_ino_t ino, - const char *buf, - size_t size, - off_t off, - struct fuse_file_info *fi) -{ - XdpFh *fh = (gpointer)fi->fh; - gssize res; - int fd; - - XDP_FH_AUTOLOCK (fh); - - if (fh->readonly) - { - fuse_reply_err (req, EACCES); - return; - } - - fd = xdp_fh_get_fd_nolock (fh); - if (fd == -1) - { - fuse_reply_err (req, EIO); - return; - } - - res = pwrite (fd, buf, size, off); - if (res < 0) - fuse_reply_err (req, errno); - else - fuse_reply_write (req, res); -} - -static void -xdp_fuse_write_buf (fuse_req_t req, - fuse_ino_t ino, - struct fuse_bufvec *bufv, - off_t off, - struct fuse_file_info *fi) -{ - XdpFh *fh = (gpointer)fi->fh; - struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(bufv)); - gssize res; - int fd; - - XDP_FH_AUTOLOCK (fh); - - if (fh->readonly) - { - fuse_reply_err (req, EACCES); - return; - } - - fd = xdp_fh_get_fd_nolock (fh); - if (fd == -1) - { - fuse_reply_err (req, EIO); - return; - } - - dst.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; - dst.buf[0].fd = fd; - dst.buf[0].pos = off; - - res = fuse_buf_copy (&dst, bufv, FUSE_BUF_SPLICE_NONBLOCK); - if (res < 0) - fuse_reply_err (req, -res); - else - fuse_reply_write (req, res); -} - -static void -xdp_fuse_release (fuse_req_t req, +xdp_fuse_getattr (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { - XdpFh *fh = (gpointer)fi->fh; + g_autoptr(XdpInode) inode = NULL; + struct stat stbuf = { 0 }; - g_debug ("xdp_fuse_release %lx (fi=%p, refcount: %d)", ino, fi, fh->ref_count); + g_debug ("xdp_fuse_getattr %lx (fi=%p)", ino, fi); - xdp_fh_unref (fh); - fuse_reply_err (req, 0); -} - -static void -xdp_fuse_rename (fuse_req_t req, - fuse_ino_t parent, - const char *name, - fuse_ino_t newparent, - const char *newname) -{ - XdpInodeClass parent_class = get_class (parent); - guint64 parent_class_ino = get_class_ino (parent); - g_autoptr (XdgAppDbEntry) entry = NULL; - int res; - fuse_ino_t inode; - struct stat stbuf = {0}; - g_autofree char *basename = NULL; - g_autoptr(XdpTmp) tmp = NULL; - g_autoptr(XdpTmp) other_tmp = NULL; - guint32 app_id = 0; - guint32 doc_id = 0; - gboolean can_write; - - g_debug ("xdp_fuse_rename %lx/%s -> %lx/%s", parent, name, newparent, newname); - - res = xdp_lookup (parent, name, &inode, &stbuf, &entry, &tmp); - if (res != 0) + inode = xdp_inode_lookup (ino); + if (inode == NULL) { - fuse_reply_err (req, res); + g_debug ("xdp_fuse_getattr <- error ENOENT"); + fuse_reply_err (req, ENOENT); return; } - if (parent_class != APP_DOC_DIR_INO_CLASS) + if (xdp_inode_stat (inode, &stbuf) != 0) { - /* Only allow renames in (app) doc dirs */ - fuse_reply_err (req, EACCES); + fuse_reply_err (req, errno); return; } - app_id = get_app_id_from_app_doc_ino (parent_class_ino); - doc_id = get_doc_id_from_app_doc_ino (parent_class_ino); - - can_write = app_can_write_doc (entry, app_id); - - /* Only allow renames inside the same dir */ - if (!can_write || - parent != newparent || - entry == NULL || - /* Also, don't allow renaming non-tmpfiles */ - tmp == NULL) - { - fuse_reply_err (req, EACCES); - return; - } - - basename = xdp_entry_dup_basename (entry); - - if (strcmp (newname, basename) == 0) - { - glnx_fd_close int dir_fd = -1; - g_autofree char *backing_basename = NULL; - /* Rename tmpfile to regular file */ - - dir_fd = xdp_entry_open_dir (entry); - if (dir_fd == -1) - { - fuse_reply_err (req, errno); - return; - } - - /* Steal backing path so we don't delete it when unlinking tmp */ - backing_basename = xdp_tmp_steal_backing_basename (tmp); - if (backing_basename == NULL) - { - fuse_reply_err (req, EINVAL); - return; - } - - /* Stop writes to all outstanding fds to the temp file */ - mark_open_tmp_file_readonly (tmp->tmp_id); - - if (renameat (dir_fd, backing_basename, - dir_fd, basename) != 0) - { - fuse_reply_err (req, errno); - return; - } - - AUTOLOCK(tmp_files); - - xdp_tmp_unlink_nolock (tmp); - - fuse_reply_err (req, 0); - - /* We actually turn the old inode to a different one after the rename, so - we need to invalidate the target entry */ - - fuse_lowlevel_notify_inval_entry (main_ch, make_app_doc_dir_inode (app_id, doc_id), - basename, strlen (basename)); - } - else - { - /* Rename tmpfile to other tmpfile name */ - - AUTOLOCK(tmp_files); - - other_tmp = find_tmp_by_name_nolock (newparent, newname); - if (other_tmp) - xdp_tmp_unlink_nolock (other_tmp); - - g_free (tmp->name); - tmp->name = g_strdup (newname); - fuse_reply_err (req, 0); - } -} - -static void -xdp_fuse_setattr (fuse_req_t req, - fuse_ino_t ino, - struct stat *attr, - int to_set, - struct fuse_file_info *fi) -{ - g_debug ("xdp_fuse_setattr %lx %x %p", ino, to_set, fi); - - if (to_set == FUSE_SET_ATTR_SIZE && fi != NULL) - { - XdpFh *fh = (gpointer)fi->fh; - int res; - struct stat newattr = {0}; - - /* ftruncate */ - - if (!fh->can_write) - { - fuse_reply_err (req, EACCES); - return; - } - - res = xdp_fh_truncate_locked (fh, attr->st_size, &newattr); - if (res < 0) - { - fuse_reply_err (req, res); - return; - } - - fuse_reply_attr (req, &newattr, get_attr_cache_time (newattr.st_mode)); - } - else if (to_set == FUSE_SET_ATTR_SIZE && fi == NULL) - { - int res = 0; - struct stat newattr = {0}; - struct stat *newattrp = &newattr; - g_autoptr(XdpFh) fh = NULL; - - /* truncate, truncate any open files (but EACCES if not open) */ - - fh = find_open_fh (ino); - if (fh) - { - if (!fh->can_write) - { - fuse_reply_err (req, EACCES); - return; - } - res = xdp_fh_truncate_locked (fh, attr->st_size, newattrp); - newattrp = NULL; - } - else - { - fuse_reply_err (req, EACCES); - return; - } - - if (res < 0) - { - fuse_reply_err (req, -res); - return; - } - - fuse_reply_attr (req, &newattr, get_attr_cache_time (newattr.st_mode)); - } - else if (to_set == FUSE_SET_ATTR_MODE) - { - gboolean found = FALSE; - int res, err = -1; - struct stat newattr = {0}; - XdpFh *fh; - - fh = find_open_fh (ino); - if (fh) - { - int fd, errsv; - - if (!fh->can_write) - { - fuse_reply_err (req, EACCES); - return; - } - - XDP_FH_AUTOLOCK (fh); - - fd = xdp_fh_get_fd_nolock (fh); - if (fd != -1) - { - res = fchmod (fd, get_user_perms (attr)); - errsv = errno; - - found = TRUE; - - if (res != 0) - err = -errsv; - else - err = xdp_fh_fstat (fh, &newattr); - } - } - - if (!found) - { - fuse_reply_err (req, EACCES); - return; - } - - if (err < 0) - { - fuse_reply_err (req, -err); - return; - } - - fuse_reply_attr (req, &newattr, get_attr_cache_time (newattr.st_mode)); - } - else - fuse_reply_err (req, ENOSYS); + fuse_reply_attr (req, &stbuf, ATTR_CACHE_TIME); } static void @@ -1941,16 +1283,22 @@ xdp_fuse_fsyncdir (fuse_req_t req, int datasync, struct fuse_file_info *fi) { - XdpInodeClass class = get_class (ino); - guint64 class_ino = get_class_ino (ino); - guint32 doc_id; + g_autoptr(XdpInode) inode = NULL; - if (class == APP_DOC_DIR_INO_CLASS) + g_debug ("xdp_fuse_fsyncdir %lx %p", ino, fi); + + inode = xdp_inode_lookup (ino); + if (inode == NULL) { - g_autoptr (XdgAppDbEntry) entry = NULL; - doc_id = get_doc_id_from_app_doc_ino (class_ino); + g_debug ("xdp_fuse_fsyncdir <- error ENOENT"); + fuse_reply_err (req, ENOENT); + return; + } - entry = xdp_lookup_doc (doc_id); + if (inode->type == XDP_INODE_APP_DOC_DIR || + inode->type == XDP_INODE_DOC_DIR) + { + g_autoptr (XdgAppDbEntry) entry = xdp_lookup_doc (inode->doc_id); if (entry != NULL) { g_autofree char *dirname = xdp_entry_dup_dirname (entry); @@ -1969,71 +1317,755 @@ xdp_fuse_fsyncdir (fuse_req_t req, fuse_reply_err (req, 0); } +static XdpFile * +xdp_file_new (XdpInode *inode, + int open_mode) +{ + XdpFile *file = g_new (XdpFile, 1); + file->inode = xdp_inode_ref (inode); + file->open_mode = open_mode; + + return file; +} + +/* Call with mutex held */ +static void +xdp_inode_locked_close_unneeded_fds (XdpInode *inode) +{ + gboolean has_open_for_write = FALSE; + GList *l; + + for (l = inode->open_files; l != NULL; l = l->next) + { + XdpFile *file = l->data; + + if (file->open_mode != O_RDONLY) + { + has_open_for_write = TRUE; + break; + } + } + + if (!has_open_for_write) + { + if (inode->truncated) + { + if (inode->open_files != NULL && inode->fd != -1) + { + /* We're not going to close the ->fd, so we repoint it to the trunc_fd, but reopened O_RDONLY */ + close (inode->fd); + inode->fd = reopen_fd (inode->trunc_fd, O_RDONLY); + } + + if (inode->filename != NULL) + { + /* not removed, replace original */ + fsync (inode->trunc_fd); + g_free (inode->backing_filename); + inode->backing_filename = g_strdup (inode->filename); + g_debug ("moving %s to %s", inode->trunc_filename, inode->backing_filename); + if (renameat (inode->dir_fd, inode->trunc_filename, + inode->dir_fd, inode->backing_filename) != 0) + g_warning ("Unable to replace truncated document: %s", strerror (errno)); + } + + inode->truncated = FALSE; + } + else if (inode->trunc_filename != NULL) + { + unlinkat (inode->dir_fd, inode->trunc_filename, 0); + g_debug ("unlinked truc_filename %s", inode->trunc_filename); + } + + if (inode->trunc_fd != -1) + { + close (inode->trunc_fd); + inode->trunc_fd = -1; + g_free (inode->trunc_filename); + inode->trunc_filename = NULL; + } + } + + if (inode->open_files == NULL) + { + if (inode->fd != -1) + { + close (inode->fd); + inode->fd = -1; + } + + if (inode->dir_fd != -1) + { + close (inode->dir_fd); + inode->dir_fd = -1; + } + } +} + +static void +xdp_file_free (XdpFile *file) +{ + XdpInode *inode = file->inode; + + g_mutex_lock (&inode->mutex); + inode->open_files = g_list_remove (inode->open_files, file); + + xdp_inode_locked_close_unneeded_fds (inode); + + g_mutex_unlock (&inode->mutex); + xdp_inode_unref (inode); + g_free (file); +} + +/* sets errno */ +static int +xdp_inode_locked_ensure_fd_open (XdpInode *inode, + XdgAppDbEntry *entry, + gboolean for_write) +{ + /* Ensure all fds are open */ + if (inode->dir_fd == -1) + { + inode->dir_fd = xdp_inode_open_dir_fd (inode->parent); + if (inode->dir_fd == -1) + return -1; + } + + if (for_write) + { + if (faccessat (inode->dir_fd, inode->backing_filename, W_OK, 0) != 0) + return -1; + } + + if (inode->fd == -1) + { + int mode = O_NOFOLLOW|O_CLOEXEC; + + if (inode->is_doc) + mode |= O_RDONLY; + else + mode |= O_RDWR; + + inode->fd = openat (inode->dir_fd, inode->backing_filename, mode); + if (inode->fd < 0) + return -1; + } + + if (inode->is_doc && for_write && inode->trunc_fd == -1) + { + struct stat st_buf; + mode_t mode = 0600; + + if (fstat (inode->fd, &st_buf) == 0) + mode = get_user_perms (&st_buf); + + g_assert (inode->trunc_filename == NULL); + inode->trunc_filename = create_tmp_for_doc (entry, inode->dir_fd, O_RDWR, mode, + &inode->trunc_fd); + if (inode->trunc_filename == NULL) + return -1; + } + + return 0; +} + +static void +xdp_fuse_open (fuse_req_t req, + fuse_ino_t ino, + struct fuse_file_info *fi) +{ + g_autoptr(XdpInode) inode = NULL; + g_autoptr(XdgAppDbEntry) entry = NULL; + gboolean can_write; + int open_mode; + XdpFile *file = NULL; + int errsv; + + g_debug ("xdp_fuse_open %lx flags %o", ino, fi->flags); + + inode = xdp_inode_lookup (ino); + if (inode == NULL) + { + g_debug ("xdp_fuse_open <- no inode error ENOENT"); + fuse_reply_err (req, ENOENT); + return; + } + + if (inode->type != XDP_INODE_DOC_FILE) + { + g_debug ("xdp_fuse_open <- error EISDIR"); + fuse_reply_err (req, EISDIR); + return; + } + + entry = xdp_lookup_doc (inode->doc_id); + if (entry == NULL || + !app_can_see_doc (entry, inode->app_id)) + { + g_debug ("xdp_fuse_open <- no entry error ENOENT"); + fuse_reply_err (req, ENOENT); + return; + } + + can_write = app_can_write_doc (entry, inode->app_id); + + open_mode = fi->flags & 3; + + if (open_mode != O_RDONLY && !can_write) + { + g_debug ("xdp_fuse_open <- no write EACCES"); + fuse_reply_err (req, EACCES); + return; + } + + g_mutex_lock (&inode->mutex); + + if (xdp_inode_locked_ensure_fd_open (inode, entry, + open_mode != O_RDONLY) == 0) + { + file = xdp_file_new (inode, open_mode); + inode->open_files = g_list_prepend (inode->open_files, file); + errsv = 0; + } + else + { + errsv = errno; + xdp_inode_locked_close_unneeded_fds (inode); + } + + g_mutex_unlock (&inode->mutex); + + if (file != NULL) + { + fi->fh = (gsize)file; + if (fuse_reply_open (req, fi)) + xdp_file_free (file); + } + else + fuse_reply_err (req, errsv); +} + + +static void +xdp_fuse_create (fuse_req_t req, + fuse_ino_t parent, + const char *filename, + mode_t mode, + struct fuse_file_info *fi) +{ + g_autoptr(XdpInode) parent_inode = NULL; + g_autoptr (XdgAppDbEntry) entry = NULL; + struct fuse_entry_param e = {0}; + gboolean can_see, can_write; + g_autofree char *tmpfile = NULL; + int open_mode; + XdpFile *file = NULL; + XdpInode *inode; + int errsv; + + g_debug ("xdp_fuse_create %lx/%s, flags %o", parent, filename, fi->flags); + + parent_inode = xdp_inode_lookup (parent); + if (parent_inode == NULL) + { + g_debug ("xdp_fuse_create <- error parent ENOENT"); + fuse_reply_err (req, ENOENT); + return; + } + + if (parent_inode->type == XDP_INODE_DOC_FILE) + { + g_debug ("xdp_fuse_create <- error parent ENOTDIR"); + fuse_reply_err (req, ENOTDIR); + return; + } + + if (parent_inode->type != XDP_INODE_APP_DOC_DIR && + parent_inode->type != XDP_INODE_DOC_DIR) + { + fuse_reply_err (req, EACCES); + return; + } + + entry = xdp_lookup_doc (parent_inode->doc_id); + if (entry == NULL) + { + fuse_reply_err (req, ENOENT); + return; + } + + can_see = app_can_see_doc (entry, parent_inode->app_id); + if (!can_see) + { + fuse_reply_err (req, ENOENT); + return; + } + + can_write = app_can_write_doc (entry, parent_inode->app_id); + if (!can_write) + { + fuse_reply_err (req, EACCES); + return; + } + + inode = xdp_inode_create_file (parent_inode, entry, filename, + mode, + (fi->flags | O_TRUNC) != 0, + (fi->flags | O_EXCL) != 0); + if (inode == NULL) + { + fuse_reply_err (req, errno); + return; + } + + g_mutex_lock (&inode->mutex); + + open_mode = fi->flags & 3; + + if (xdp_inode_locked_ensure_fd_open (inode, entry, + open_mode != O_RDONLY) == 0) + { + file = xdp_file_new (inode, open_mode); + inode->open_files = g_list_prepend (inode->open_files, file); + errsv = 0; + } + else + { + errsv = errno; + xdp_inode_locked_close_unneeded_fds (inode); + } + + g_mutex_unlock (&inode->mutex); + + if (file != NULL) + { + if (xdp_inode_stat (inode, &e.attr) != 0) + { + xdp_file_free (file); + fuse_reply_err (req, errno); + return; + } + + e.ino = inode->ino; + if (inode->is_doc) + { + e.attr_timeout = 0; + e.entry_timeout = 0; + } + else + { + e.attr_timeout = ATTR_CACHE_TIME; + e.entry_timeout = ENTRY_CACHE_TIME; + } + + xdp_inode_ref (inode); /* Ref given to the kernel, returned in xdp_fuse_forget() */ + + fi->fh = (gsize)file; + if (fuse_reply_create (req, &e, fi)) + { + xdp_file_free (file); + xdp_inode_unref (inode); + } + } + else + fuse_reply_err (req, errsv); +} + +static void +xdp_fuse_read (fuse_req_t req, + fuse_ino_t ino, + size_t size, + off_t off, + struct fuse_file_info *fi) +{ + XdpFile *file = (gpointer)fi->fh; + XdpInode *inode = file->inode; + struct fuse_bufvec bufv = FUSE_BUFVEC_INIT (size); + int fd; + + g_debug ("xdp_fuse_real %lx %ld %ld", ino, (long)size, (long)off); + + g_mutex_lock (&inode->mutex); + + fd = xdp_inode_locked_get_fd (inode); + if (fd == -1) + { + static char c = 'x'; + bufv.buf[0].flags = 0; + bufv.buf[0].mem = &c; + bufv.buf[0].size = 0; + + fuse_reply_data (req, &bufv, FUSE_BUF_NO_SPLICE); + } + else + { + bufv.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + bufv.buf[0].fd = fd; + bufv.buf[0].pos = off; + + fuse_reply_data (req, &bufv, FUSE_BUF_SPLICE_MOVE); + } + + g_mutex_unlock (&inode->mutex); +} + +static void +xdp_fuse_release (fuse_req_t req, + fuse_ino_t ino, + struct fuse_file_info *fi) +{ + XdpFile *file = (gpointer)fi->fh; + + g_debug ("xdp_fuse_release %lx (fi=%p)", ino, fi); + + xdp_file_free (file); + fuse_reply_err (req, 0); +} + +static int +truncateat (int dir_fd, const char *filename, int size) +{ + int fd; + int errsv, res; + + fd = openat (dir_fd, filename, O_RDWR); + if (fd != -1) + return -1; + + res = ftruncate (fd, size); + errsv = errno; + + close (fd); + + errno = errsv; + return res; +} + +static void +xdp_fuse_setattr (fuse_req_t req, + fuse_ino_t ino, + struct stat *attr, + int to_set, + struct fuse_file_info *fi) +{ + g_autoptr(XdpInode) inode = NULL; + g_autoptr(XdgAppDbEntry) entry = NULL; + double attr_cache_time = ATTR_CACHE_TIME; + struct stat newattr = {0}; + gboolean can_write; + int res = 0; + + g_debug ("xdp_fuse_setattr %lx %x %p", ino, to_set, fi); + + inode = xdp_inode_lookup (ino); + if (inode == NULL) + { + g_debug ("xdp_fuse_setattr <- error ENOENT"); + fuse_reply_err (req, ENOENT); + return; + } + + if (inode->type != XDP_INODE_DOC_FILE) + { + g_debug ("xdp_fuse_setattr <- not file ENOSYS"); + fuse_reply_err (req, ENOSYS); + return; + } + + entry = xdp_lookup_doc (inode->doc_id); + if (entry == NULL || + !app_can_see_doc (entry, inode->app_id)) + { + g_debug ("xdp_fuse_setattr <- no entry error ENOENT"); + fuse_reply_err (req, ENOENT); + return; + } + + can_write = app_can_write_doc (entry, inode->app_id); + + if (to_set == FUSE_SET_ATTR_SIZE) + { + g_mutex_lock (&inode->mutex); + + if (!can_write) + res = EACCES; + else if (inode->is_doc) + { + /* Only allow ftruncate with the file open for write. We could + * allow a truncate, but it would have to be implemented as + * an atomic-replace-with-empty-file to not affect other apps + * having the file open. + * Also, only support truncate-to-zero on first truncation, to + * avoid having to copy lots of data from the old file to the + * trunc_fd. + */ + if (inode->trunc_fd == -1) + res = EACCES; + else if (!inode->truncated && attr->st_size != 0) + res = ENOSYS; + else + { + if (ftruncate (inode->trunc_fd, attr->st_size) != 0) + res = errno; + else if (!inode->truncated) + { + inode->truncated = TRUE; + g_free (inode->backing_filename); + inode->backing_filename = g_strdup (inode->trunc_filename); + } + } + } + else + { + if (inode->fd) + { + if (ftruncate (inode->fd, attr->st_size) != 0) + res = errno; + } + else + { + glnx_fd_close int dir_fd = xdp_inode_open_dir_fd (inode->parent); + if (dir_fd == -1 || + truncateat (dir_fd, inode->backing_filename, attr->st_size) != 0) + res = errno; + } + } + g_mutex_unlock (&inode->mutex); + } + else if (to_set == FUSE_SET_ATTR_MODE) + { + if (!can_write) + res = EACCES; + else + { + int fd = xdp_inode_locked_get_write_fd (inode); + if (fd == -1 || + fchmod (fd, get_user_perms (attr)) != 0) + res = errno; + } + } + else + res = ENOSYS; + + if (res != 0) + fuse_reply_err (req, ENOSYS); + else + { + if (xdp_inode_stat (inode, &newattr) != 0) + fuse_reply_err (req, errno); + else + fuse_reply_attr (req, &newattr, attr_cache_time); + } +} + +static void +xdp_fuse_write (fuse_req_t req, + fuse_ino_t ino, + const char *buf, + size_t size, + off_t off, + struct fuse_file_info *fi) +{ + XdpFile *file = (gpointer)fi->fh; + XdpInode *inode = file->inode; + int fd; + int res; + + g_debug ("xdp_fuse_write %lx %ld %ld", ino, (long)size, (long)off); + + g_mutex_lock (&inode->mutex); + + fd = xdp_inode_locked_get_write_fd (inode); + if (fd < 0) + fuse_reply_err (req, errno); + else + { + res = pwrite (fd, buf, size, off); + if (res < 0) + fuse_reply_err (req, errno); + else + fuse_reply_write (req, res); + } + + g_mutex_unlock (&inode->mutex); +} + +static void +xdp_fuse_write_buf (fuse_req_t req, + fuse_ino_t ino, + struct fuse_bufvec *bufv, + off_t off, + struct fuse_file_info *fi) +{ + XdpFile *file = (gpointer)fi->fh; + struct fuse_bufvec dst = FUSE_BUFVEC_INIT(fuse_buf_size(bufv)); + XdpInode *inode = file->inode; + int fd; + int res; + + g_debug ("xdp_fuse_write_buf %lx %ld", ino, (long)off); + + g_mutex_lock (&inode->mutex); + + fd = xdp_inode_locked_get_write_fd (inode); + if (fd == -1) + fuse_reply_err (req, errno); + else + { + dst.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + dst.buf[0].fd = fd; + dst.buf[0].pos = off; + + res = fuse_buf_copy (&dst, bufv, FUSE_BUF_SPLICE_NONBLOCK); + if (res < 0) + fuse_reply_err (req, -res); + else + fuse_reply_write (req, res); + } + + g_mutex_unlock (&inode->mutex); +} + static void xdp_fuse_fsync (fuse_req_t req, fuse_ino_t ino, int datasync, struct fuse_file_info *fi) { - XdpInodeClass class = get_class (ino); + g_autoptr(XdpInode) inode = NULL; + int fd; + int res = 0; - if (class == APP_DOC_FILE_INO_CLASS || - class == TMPFILE_INO_CLASS) + g_debug ("xdp_fuse_fsync %lx", ino); + + inode = xdp_inode_lookup (ino); + if (inode == NULL) { - XdpFh *fh = (gpointer)fi->fh; + g_debug ("xdp_fuse_setattr <- error ENOENT"); + fuse_reply_err (req, ENOENT); + return; + } - XDP_FH_AUTOLOCK (fh); + if (inode->type == XDP_INODE_DOC_FILE) + { + g_mutex_lock (&inode->mutex); - if (fh->fd >= 0) - fsync (fh->fd); - if (fh->truncated && fh->trunc_fd >= 0) - fsync (fh->trunc_fd); + fd = xdp_inode_locked_get_write_fd (inode); + if (fd != -1 && fsync (fd) != 0) + res = errno; + + g_mutex_unlock (&inode->mutex); + } + + fuse_reply_err (req, res); +} + +static void +xdp_fuse_unlink (fuse_req_t req, + fuse_ino_t parent, + const char *filename) +{ + g_autoptr(XdpInode) parent_inode = NULL; + g_autoptr (XdgAppDbEntry) entry = NULL; + g_autoptr(XdpInode) child_inode = NULL; + + g_debug ("xdp_fuse_unlink %lx/%s", parent, filename); + + parent_inode = xdp_inode_lookup (parent); + if (parent_inode == NULL) + { + g_debug ("xdp_fuse_lookup <- error parent ENOENT"); + fuse_reply_err (req, ENOENT); + return; + } + + if (parent_inode->type == XDP_INODE_DOC_FILE) + { + fuse_reply_err (req, ENOTDIR); + return; + } + + if (parent_inode->type != XDP_INODE_APP_DOC_DIR && + parent_inode->type != XDP_INODE_DOC_DIR) + { + fuse_reply_err (req, EACCES); + return; + } + + child_inode = xdp_inode_unlink_child (parent_inode, filename); + if (child_inode == NULL) + { + fuse_reply_err (req, ENOENT); + return; } fuse_reply_err (req, 0); } static void -xdp_fuse_unlink (fuse_req_t req, +xdp_fuse_rename (fuse_req_t req, fuse_ino_t parent, - const char *name) + const char *name, + fuse_ino_t newparent, + const char *newname) { - XdpInodeClass parent_class = get_class (parent); - guint64 parent_class_ino = get_class_ino (parent); + g_autoptr(XdpInode) parent_inode = NULL; g_autoptr (XdgAppDbEntry) entry = NULL; - int res; - fuse_ino_t inode; - struct stat stbuf = {0}; g_autofree char *basename = NULL; - g_autoptr (XdpTmp) tmp = NULL; - guint32 app_id = 0; - gboolean can_write; + gboolean can_see, can_write; - g_debug ("xdp_fuse_unlink %lx/%s", parent, name); + g_debug ("xdp_fuse_rename %lx/%s -> %lx/%s", parent, name, newparent, newname); - res = xdp_lookup (parent, name, &inode, &stbuf, &entry, &tmp); - if (res != 0) + parent_inode = xdp_inode_lookup (parent); + if (parent_inode == NULL) { - fuse_reply_err (req, res); + g_debug ("xdp_fuse_rename <- error parent ENOENT"); + fuse_reply_err (req, ENOENT); return; } + if (parent_inode->type == XDP_INODE_DOC_FILE) + { + fuse_reply_err (req, ENOTDIR); + return; + } + + if (parent_inode->type != XDP_INODE_APP_DOC_DIR && + parent_inode->type != XDP_INODE_DOC_DIR) + { + fuse_reply_err (req, EACCES); + return; + } + + if (newparent != parent) + { + g_debug ("xdp_fuse_rename <- error different parents EACCES"); + fuse_reply_err (req, EACCES); + return; + } + + if (strcmp (name, newname) == 0) + { + fuse_reply_err (req, 0); + return; + } + + entry = xdp_lookup_doc (parent_inode->doc_id); if (entry == NULL) { - fuse_reply_err (req, EACCES); + fuse_reply_err (req, ENOENT); return; } - if (parent_class != APP_DOC_DIR_INO_CLASS) + can_see = app_can_see_doc (entry, parent_inode->app_id); + can_write = app_can_write_doc (entry, parent_inode->app_id); + + if (!can_see) { - /* Only allow unlink in (app) doc dirs */ - fuse_reply_err (req, EACCES); + fuse_reply_err (req, ENOENT); return; } - app_id = get_app_id_from_app_doc_ino (parent_class_ino); - - can_write = app_can_write_doc (entry, app_id); if (!can_write) { fuse_reply_err (req, EACCES); @@ -2041,110 +2073,85 @@ xdp_fuse_unlink (fuse_req_t req, } basename = xdp_entry_dup_basename (entry); - if (strcmp (name, basename) == 0) - { - glnx_fd_close int dir_fd = -1; - dir_fd = xdp_entry_open_dir (entry); - if (dir_fd == -1) - { - fuse_reply_err (req, errno); - return; - } - - if (unlinkat (dir_fd, basename, 0) != 0) - { - fuse_reply_err (req, errno); - return; - } - - fuse_reply_err (req, 0); - } + if (xdp_inode_rename_child (parent_inode, name, newname, basename) != 0) + fuse_reply_err (req, errno); else - { - AUTOLOCK(tmp_files); - xdp_tmp_unlink_nolock (tmp); - - fuse_reply_err (req, 0); - } + fuse_reply_err (req, 0); } - static struct fuse_lowlevel_ops xdp_fuse_oper = { .lookup = xdp_fuse_lookup, + .forget = xdp_fuse_forget, .getattr = xdp_fuse_getattr, .opendir = xdp_fuse_opendir, .readdir = xdp_fuse_readdir, .releasedir = xdp_fuse_releasedir, .fsyncdir = xdp_fuse_fsyncdir, .open = xdp_fuse_open, - .create = xdp_fuse_create, .read = xdp_fuse_read, + .release = xdp_fuse_release, + .setattr = xdp_fuse_setattr, .write = xdp_fuse_write, .write_buf = xdp_fuse_write_buf, - .release = xdp_fuse_release, - .rename = xdp_fuse_rename, - .setattr = xdp_fuse_setattr, .fsync = xdp_fuse_fsync, + .create = xdp_fuse_create, .unlink = xdp_fuse_unlink, + .rename = xdp_fuse_rename, }; -/* Called when a apps permissions to see a document is changed */ +/* Called when a apps permissions to see a document is changed, + and with null opt_app_id when the doc is created/removed */ void -xdp_fuse_invalidate_doc_app (const char *doc_id_s, - const char *app_id_s, +xdp_fuse_invalidate_doc_app (const char *doc_id, + const char *opt_app_id, XdgAppDbEntry *entry) { - guint32 app_id = get_app_id_from_name (app_id_s); - guint32 doc_id = xdp_id_from_name (doc_id_s); - g_autofree char *basename = xdp_entry_dup_basename (entry); - - g_debug ("invalidate %s/%s\n", doc_id_s, app_id_s); + g_autoptr(XdpInode) inode = NULL; + fuse_ino_t ino; + GList *l; /* This can happen if fuse is not initialized yet for the very first dbus message that activated the service */ if (main_ch == NULL) return; - fuse_lowlevel_notify_inval_inode (main_ch, make_app_doc_file_inode (app_id, doc_id), 0, 0); - fuse_lowlevel_notify_inval_entry (main_ch, make_app_doc_dir_inode (app_id, doc_id), - basename, strlen (basename)); - fuse_lowlevel_notify_inval_inode (main_ch, make_app_doc_dir_inode (app_id, doc_id), 0, 0); - fuse_lowlevel_notify_inval_entry (main_ch, make_inode (APP_DIR_INO_CLASS, app_id), - doc_id_s, strlen (doc_id_s)); + g_debug ("invalidate %s/%s", doc_id, opt_app_id ? opt_app_id : "*"); + + AUTOLOCK(inodes); + ino = get_dir_inode_nr_unlocked (opt_app_id, doc_id); + inode = xdp_inode_lookup_unlocked (ino); + if (inode != NULL) + { + fuse_lowlevel_notify_inval_inode (main_ch, inode->ino, 0, 0); + fuse_lowlevel_notify_inval_entry (main_ch, inode->parent->ino, + inode->filename, strlen (inode->filename)); + + for (l = inode->children; l != NULL; l = l->next) + { + XdpInode *child = l->data; + + fuse_lowlevel_notify_inval_inode (main_ch, child->ino, 0, 0); + if (child->filename != NULL) + fuse_lowlevel_notify_inval_entry (main_ch, inode->ino, + child->filename, strlen (child->filename)); + } + } } -/* Called when a document id is created/removed */ -void -xdp_fuse_invalidate_doc (const char *doc_id_s, - XdgAppDbEntry *entry) +char * +xdp_fuse_lookup_id_for_inode (ino_t ino) { - guint32 doc_id = xdp_id_from_name (doc_id_s); - g_autofree char *basename = xdp_entry_dup_basename (entry); + g_autoptr(XdpInode) inode = NULL; - g_debug ("invalidate %s\n", doc_id_s); + inode = xdp_inode_lookup (ino); + if (inode == NULL) + return NULL; - /* This can happen if fuse is not initialized yet for the very - first dbus message that activated the service */ - if (main_ch == NULL) - return; + if (inode->type != XDP_INODE_DOC_FILE || + !inode->is_doc) + return NULL; - fuse_lowlevel_notify_inval_inode (main_ch, make_app_doc_file_inode (0, doc_id), 0, 0); - fuse_lowlevel_notify_inval_entry (main_ch, make_app_doc_dir_inode (0, doc_id), - basename, strlen (basename)); - fuse_lowlevel_notify_inval_inode (main_ch, make_app_doc_dir_inode (0, doc_id), 0, 0); - fuse_lowlevel_notify_inval_entry (main_ch, FUSE_ROOT_ID, doc_id_s, strlen (doc_id_s)); -} - -guint32 -xdp_fuse_lookup_id_for_inode (ino_t inode) -{ - XdpInodeClass class = get_class (inode); - guint64 class_ino = get_class_ino (inode); - - if (class != APP_DOC_FILE_INO_CLASS) - return 0; - - return get_doc_id_from_app_doc_ino (class_ino); + return g_strdup (inode->doc_id); } const char * @@ -2189,10 +2196,12 @@ xdp_fuse_init (GError **error) struct stat st; const char *mount_path; - app_name_to_id = - g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); - app_id_to_name = + inodes = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL); + root_inode = xdp_inode_new (ROOT_INODE, XDP_INODE_ROOT, NULL, "/", NULL, NULL); + by_app_inode = xdp_inode_new (BY_APP_INODE, XDP_INODE_BY_APP, root_inode, BY_APP_NAME, NULL, NULL); + dir_to_inode_nr = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); mount_path = xdp_fuse_get_mountpoint (); @@ -2207,7 +2216,7 @@ xdp_fuse_init (GError **error) if (g_mkdir_with_parents (mount_path, 0700)) { g_set_error (error, XDG_APP_PORTAL_ERROR, XDG_APP_PORTAL_ERROR_FAILED, - "Unable to create dir %s\n", mount_path); + "Unable to create dir %s", mount_path); return FALSE; } diff --git a/document-portal/xdp-fuse.h b/document-portal/xdp-fuse.h index b1e6c82f..887db801 100644 --- a/document-portal/xdp-fuse.h +++ b/document-portal/xdp-fuse.h @@ -7,18 +7,16 @@ G_BEGIN_DECLS char ** xdp_list_apps (void); -guint32 * xdp_list_docs (void); -XdgAppDbEntry *xdp_lookup_doc (guint32 id); +char ** xdp_list_docs (void); +XdgAppDbEntry *xdp_lookup_doc (const char *doc_id); gboolean xdp_fuse_init (GError **error); void xdp_fuse_exit (void); const char *xdp_fuse_get_mountpoint (void); void xdp_fuse_invalidate_doc_app (const char *doc_id, - const char *app_id, + const char *opt_app_id, XdgAppDbEntry *entry); -void xdp_fuse_invalidate_doc (const char *doc_id, - XdgAppDbEntry *entry); -guint32 xdp_fuse_lookup_id_for_inode (ino_t inode); +char *xdp_fuse_lookup_id_for_inode (ino_t inode); G_END_DECLS diff --git a/document-portal/xdp-main.c b/document-portal/xdp-main.c index 5138c4c9..611526f8 100644 --- a/document-portal/xdp-main.c +++ b/document-portal/xdp-main.c @@ -51,37 +51,16 @@ xdp_list_apps (void) return xdg_app_db_list_apps (db); } -guint32 * +char ** xdp_list_docs (void) { - GArray *res; - g_auto(GStrv) ids = NULL; - guint32 id; - int i; - AUTOLOCK(db); - - res = g_array_new (TRUE, FALSE, sizeof (guint32)); - - ids = xdg_app_db_list_ids (db); - - for (i = 0; ids[i] != NULL; i++) - { - guint32 id = xdp_id_from_name (ids[i]); - g_array_append_val (res, id); - } - - id = 0; - g_array_append_val (res, id); - - return (guint32 *)g_array_free (res, FALSE); + return xdg_app_db_list_ids (db); } XdgAppDbEntry * -xdp_lookup_doc (guint32 id) +xdp_lookup_doc (const char *doc_id) { - g_autofree char *doc_id = xdp_name_from_id (id); - AUTOLOCK(db); return xdg_app_db_lookup (db, doc_id); } @@ -252,7 +231,7 @@ portal_delete (GDBusMethodInvocation *invocation, old_apps = xdg_app_db_entry_list_apps (entry); for (i = 0; old_apps[i] != NULL; i++) xdp_fuse_invalidate_doc_app (id, old_apps[i], entry); - xdp_fuse_invalidate_doc (id, entry); + xdp_fuse_invalidate_doc_app (id, NULL, entry); if (persist_entry (entry)) xdg_app_permission_store_call_delete (permission_store, TABLE_NAME, @@ -305,7 +284,7 @@ do_create_doc (struct stat *parent_st_buf, const char *path, gboolean reuse_exis entry = xdg_app_db_entry_new (data); xdg_app_db_set_entry (db, id, entry); - xdp_fuse_invalidate_doc (id, entry); + xdp_fuse_invalidate_doc_app (id, NULL, entry); if (persistent) xdg_app_permission_store_call_set (permission_store, @@ -402,12 +381,11 @@ portal_add (GDBusMethodInvocation *invocation, if (st_buf.st_dev == fuse_dev) { /* The passed in fd is on the fuse filesystem itself */ - guint32 old_id; g_autoptr(XdgAppDbEntry) old_entry = NULL; - old_id = xdp_fuse_lookup_id_for_inode (st_buf.st_ino); - g_debug ("path on fuse, id %x\n", old_id); - if (old_id == 0) + id = xdp_fuse_lookup_id_for_inode (st_buf.st_ino); + g_debug ("path on fuse, id %s\n", id); + if (id == NULL) { g_dbus_method_invocation_return_error (invocation, XDG_APP_PORTAL_ERROR, XDG_APP_PORTAL_ERROR_INVALID_ARGUMENT, @@ -415,8 +393,6 @@ portal_add (GDBusMethodInvocation *invocation, return; } - id = xdp_name_from_id (old_id); - /* If the entry doesn't exist anymore, fail. Also fail if not resuse_existing, because otherwise the user could use this to get a copy with permissions and thus escape later permission diff --git a/document-portal/xdp-util.c b/document-portal/xdp-util.c index 5370b1e4..6725f416 100644 --- a/document-portal/xdp-util.c +++ b/document-portal/xdp-util.c @@ -74,12 +74,6 @@ xdp_entry_has_permissions (XdgAppDbEntry *entry, return (current_perms & perms) == perms; } -guint32 -xdp_id_from_name (const char *name) -{ - return g_ascii_strtoull (name, NULL, 16); -} - char * xdp_name_from_id (guint32 doc_id) { @@ -133,50 +127,3 @@ xdp_entry_get_flags (XdgAppDbEntry *entry) g_autoptr(GVariant) c = g_variant_get_child_value (v, 3); return g_variant_get_uint32 (c); } - -int -xdp_entry_open_dir (XdgAppDbEntry *entry) -{ - g_autofree char *dirname = xdp_entry_dup_dirname (entry); - struct stat st_buf; - int fd; - - fd = open (dirname, O_CLOEXEC | O_PATH | O_DIRECTORY); - if (fd == -1) - return -1; - - if (fstat (fd, &st_buf) < 0) - { - close (fd); - errno = ENOENT; - return -1; - } - - if (st_buf.st_ino != xdp_entry_get_inode (entry) || - st_buf.st_dev != xdp_entry_get_device (entry)) - { - close (fd); - errno = ENOENT; - return -1; - } - - return fd; -} - -int -xdp_entry_stat (XdgAppDbEntry *entry, - struct stat *buf, - int flags) -{ - glnx_fd_close int fd = -1; - g_autofree char *basename = xdp_entry_dup_basename (entry); - - fd = xdp_entry_open_dir (entry); - if (fd < 0) - return -1; - - if (fstatat (fd, basename, buf, flags) != 0) - return -1; - - return 0; -} diff --git a/document-portal/xdp-util.h b/document-portal/xdp-util.h index bc1bfc68..77e0bae6 100644 --- a/document-portal/xdp-util.h +++ b/document-portal/xdp-util.h @@ -24,12 +24,7 @@ char * xdp_entry_dup_dirname (XdgAppDbEntry *entry); guint64 xdp_entry_get_device (XdgAppDbEntry *entry); guint64 xdp_entry_get_inode (XdgAppDbEntry *entry); guint32 xdp_entry_get_flags (XdgAppDbEntry *entry); -int xdp_entry_open_dir (XdgAppDbEntry *entry); -int xdp_entry_stat (XdgAppDbEntry *entry, - struct stat *buf, - int flags); -guint32 xdp_id_from_name (const char *name); char * xdp_name_from_id (guint32 doc_id);