diff options
-rw-r--r-- | document-portal/xdp-fuse.c | 2939 | ||||
-rw-r--r-- | document-portal/xdp-fuse.h | 10 | ||||
-rw-r--r-- | document-portal/xdp-main.c | 40 | ||||
-rw-r--r-- | document-portal/xdp-util.c | 53 | ||||
-rw-r--r-- | document-portal/xdp-util.h | 5 |
5 files changed, 1486 insertions, 1561 deletions
diff --git a/document-portal/xdp-fuse.c b/document-portal/xdp-fuse.c index 1bd6a9e..c20618a 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) - <same as DOC_DIR> - "$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,724 +103,811 @@ 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); } -static int -get_user_perms (const struct stat *stbuf) +/* Call with inodes lock held */ +static fuse_ino_t +allocate_inode_unlocked (void) { - /* Strip out exec and setuid bits */ - return stbuf->st_mode & 0666; -} + fuse_ino_t next = next_inode_nr++; -static double -get_attr_cache_time (int st_mode) -{ - if (S_ISDIR (st_mode)) - return DIRS_ATTR_CACHE_TIME; - return 0.0; -} + /* Bail out on overflow, to avoid reuse */ + if (next <= 0) + g_assert_not_reached (); -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; + return next; } -/******************************* 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 +static fuse_ino_t +get_dir_inode_nr_unlocked (const char *app_id, const char *doc_id) { - volatile gint ref_count; - - /* These are immutable, no lock needed */ - guint64 parent_inode; - guint32 tmp_id; - XdgAppDbEntry *entry; + gpointer res; + fuse_ino_t allocated; + g_autofree char *dir = NULL; - /* Changes always done under tmp_files lock */ - char *name; - - GMutex mutex; + 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); + } - /* protected by mutex */ - char *backing_basename; -} XdpTmp; + res = g_hash_table_lookup (dir_to_inode_nr, dir); + if (res != NULL) + return (fuse_ino_t)(gsize)res; -/* Owns a ref to the files */ -static GList *tmp_files = NULL; -G_LOCK_DEFINE(tmp_files); + allocated = allocate_inode_unlocked (); + g_hash_table_insert (dir_to_inode_nr, g_strdup (dir), (gpointer)allocated); + return allocated; +} -static XdpTmp * -xdp_tmp_ref (XdpTmp *tmp) +static fuse_ino_t +get_dir_inode_nr (const char *app_id, const char *doc_id) { - g_atomic_int_inc (&tmp->ref_count); - return tmp; + AUTOLOCK(inodes); + return get_dir_inode_nr_unlocked (app_id, doc_id); } static void -xdp_tmp_unref (XdpTmp *tmp) +allocate_app_dir_inode_nr (char **app_ids) { - 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); - } + int i; + AUTOLOCK(inodes); + for (i = 0; app_ids[i] != NULL; i++) + get_dir_inode_nr_unlocked (app_ids[i], NULL); } -char * -xdp_tmp_get_backing_basename (XdpTmp *tmp) +static char ** +get_allocated_app_dirs (void) { - char *res; - g_mutex_lock (&tmp->mutex); - res = g_strdup (tmp->backing_basename); - g_mutex_unlock (&tmp->mutex); + GHashTableIter iter; + gpointer key, value; + GPtrArray *array = g_ptr_array_new (); - return res; + 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); } -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); +static void xdp_inode_unref_internal (XdpInode *inode, gboolean locked); +static void xdp_inode_unref (XdpInode *inode); - return res; +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); } -G_DEFINE_AUTOPTR_CLEANUP_FUNC(XdpTmp, xdp_tmp_unref) +static XdpInode * +xdp_inode_ref (XdpInode *inode) +{ + if (inode) + g_atomic_int_inc (&inode->ref_count); + return inode; +} -/* Must first take tmp_files lock */ -static XdpTmp * -find_tmp_by_name_nolock (guint64 parent_inode, - const char *name) +static void +xdp_inode_unref_internal (XdpInode *inode, gboolean locked) { - GList *l; + gint old_ref; + + if (inode == NULL) + return; - for (l = tmp_files; l != NULL; l = l->next) + /* 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) { - XdpTmp *tmp = l->data; - if (tmp->parent_inode == parent_inode && - strcmp (tmp->name, name) == 0) - return xdp_tmp_ref (tmp); + 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; + } - return NULL; + 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); + } } -/* Takes tmp_files lock */ -static XdpTmp * -find_tmp_by_name (guint64 parent_inode, - const char *name) +static void +xdp_inode_unref (XdpInode *inode) { - AUTOLOCK(tmp_files); - return find_tmp_by_name_nolock (parent_inode, name); + return xdp_inode_unref_internal (inode, FALSE); } -/* Takes tmp_files lock */ -static XdpTmp * -find_tmp_by_id (guint32 tmp_id) +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) { - GList *l; + XdpInode *inode; - AUTOLOCK(tmp_files); + 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; - for (l = tmp_files; l != NULL; l = l->next) - { - XdpTmp *tmp = l->data; - if (tmp->tmp_id == tmp_id) - return xdp_tmp_ref (tmp); - } + if (parent) + parent->children = g_list_prepend (parent->children, inode); + g_hash_table_insert (inodes, (gpointer)ino, inode); - return NULL; + return inode; } -/* 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) +static XdpInode * +xdp_inode_new (fuse_ino_t ino, + XdpInodeType type, + XdpInode *parent, + const char *filename, + const char *app_id, + const char *doc_id) { - 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); + AUTOLOCK(inodes); + return xdp_inode_new_unlocked (ino, type, parent, filename, app_id, doc_id); +} - tmp_files = g_list_prepend (tmp_files, tmp); +static XdpInode * +xdp_inode_lookup_unlocked (fuse_ino_t inode_nr) +{ + XdpInode *inode; - return tmp; + inode = g_hash_table_lookup (inodes, (gpointer)inode_nr); + if (inode != NULL) + return xdp_inode_ref (inode); + return NULL; } -/* Caller must own tmp_files lock */ -static void -xdp_tmp_unlink_nolock (XdpTmp *tmp) +static GList * +xdp_inode_list_children (XdpInode *inode) { + GList *list = NULL, *l; - g_autofree char *backing_basename = NULL; - - backing_basename = xdp_tmp_steal_backing_basename (tmp); - if (backing_basename) + AUTOLOCK(inodes); + for (l = inode->children; l != NULL; l = l->next) { - glnx_fd_close int dir_fd = xdp_entry_open_dir (tmp->entry); - if (dir_fd) - unlinkat (dir_fd, backing_basename, 0); + XdpInode *child = l->data; + + list = g_list_prepend (list, xdp_inode_ref (child)); } - tmp_files = g_list_remove (tmp_files, tmp); - xdp_tmp_unref (tmp); + return g_list_reverse (list); } -/******************************* 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 +static XdpInode * +xdp_inode_lookup_child_unlocked (XdpInode *inode, const char *filename) { - 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; + GList *l; - GMutex mutex; -} XdpFh; + 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); + } -static GList *open_files = NULL; -G_LOCK_DEFINE(open_files); + return NULL; +} -static XdpFh * -xdp_fh_ref (XdpFh *fh) +static XdpInode * +xdp_inode_lookup_child (XdpInode *inode, const char *filename) { - g_atomic_int_inc (&fh->ref_count); - return fh; + AUTOLOCK(inodes); + return xdp_inode_lookup_child_unlocked (inode, filename); } -static void -xdp_fh_finalize (XdpFh *fh) +static int +xdp_inode_open_dir_fd (XdpInode *dir) { - 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); + struct stat st_buf; + glnx_fd_close int fd = -1; - if (fh->fd >= 0) - close (fh->fd); + g_assert (dir->dirname != NULL); - if (fh->trunc_fd >= 0) - close (fh->trunc_fd); + fd = open (dir->dirname, O_CLOEXEC | O_PATH | O_DIRECTORY); + if (fd == -1) + return -1; - if (fh->dir_fd >= 0) - close (fh->dir_fd); + if (fstat (fd, &st_buf) < 0) + { + errno = ENOENT; + return -1; + } - g_clear_pointer (&fh->trunc_basename, g_free); - g_clear_pointer (&fh->real_basename, g_free); + if (st_buf.st_ino != dir->dir_ino || + st_buf.st_dev != dir->dir_dev) + { + errno = ENOENT; + return -1; + } - g_free (fh); + return glnx_steal_fd (&fd); } static void -xdp_fh_unref (XdpFh *fh) +xdp_inode_unlink_backing_files (XdpInode *child_inode, int dir_fd) { - if (g_atomic_int_dec_and_test (&fh->ref_count)) + if (dir_fd == -1) { - /* 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); - } + g_debug ("Can't unlink child inode due to no dir_fd"); + return; + } - xdp_fh_finalize (fh); + 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); } } -G_DEFINE_AUTOPTR_CLEANUP_FUNC(XdpFh, xdp_fh_unref) - static void -xdp_fh_lock (XdpFh *fh) +xdp_inode_do_unlink (XdpInode *child_inode, int dir_fd, gboolean unlink_backing) { - g_mutex_lock (&fh->mutex); -} + if (unlink_backing) + xdp_inode_unlink_backing_files (child_inode, dir_fd); -static void -xdp_fh_unlock (XdpFh *fh) -{ - g_mutex_unlock (&fh->mutex); -} + /* Zero out filename to mark it deleted */ + g_free (child_inode->filename); + child_inode->filename = NULL; -static inline void xdp_fh_auto_unlock_helper (XdpFh **fhp) -{ - if (*fhp) - xdp_fh_unlock (*fhp); + /* Drop keep-alive-until-unlink ref */ + if (!child_inode->is_doc) + xdp_inode_unref (child_inode); } -static inline XdpFh *xdp_fh_auto_lock_helper (XdpFh *fh) +static XdpInode * +xdp_inode_unlink_child (XdpInode *dir, const char *filename) { - if (fh) - xdp_fh_lock (fh); - return fh; -} + XdpInode *child_inode; + glnx_fd_close int dir_fd = -1; -#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) + AUTOLOCK(inodes); + child_inode = xdp_inode_lookup_child_unlocked (dir, filename); + if (child_inode == NULL) + return NULL; -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 */ + g_assert (child_inode->type == XDP_INODE_DOC_FILE); + g_assert (child_inode->filename != NULL); - fi->fh = (gsize)fh; + /* 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); - AUTOLOCK (open_files); + dir_fd = xdp_inode_open_dir_fd (dir); - open_files = g_list_prepend (open_files, fh); + xdp_inode_do_unlink (child_inode, dir_fd, TRUE); - return fh; -} + g_mutex_unlock (&child_inode->mutex); -static int -xdp_fh_get_fd_nolock (XdpFh *fh) -{ - if (fh->truncated) - return fh->trunc_fd; - else - return fh->fd; + return child_inode; } +/* Sets errno */ 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; +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; - return 0; -} + AUTOLOCK(inodes); + src_inode = xdp_inode_lookup_child_unlocked (dir, src_filename); + if (src_inode == NULL) + { + errno = ENOENT; + return -1; + } -static int -xdp_fh_fstat_locked (XdpFh *fh, - struct stat *stbuf) -{ - XDP_FH_AUTOLOCK (fh); + g_assert (src_inode->type == XDP_INODE_DOC_FILE); + g_assert (src_inode->filename != NULL); - return xdp_fh_fstat (fh, stbuf); -} + 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); + } -static int -xdp_fh_truncate_locked (XdpFh *fh, off_t size, struct stat *newattr) -{ - int fd; + /* 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); - XDP_FH_AUTOLOCK (fh); + dir_fd = xdp_inode_open_dir_fd (dir); + res = 0; - if (fh->trunc_fd >= 0 && !fh->truncated) + if (src_inode->is_doc) { - if (size != 0) - return -EACCES; - - fh->truncated = TRUE; - fd = fh->trunc_fd; + /* 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 + else if (strcmp (dst_filename, doc_basename) != 0) { - fd = xdp_fh_get_fd_nolock (fh); - if (fd == -1) - return -EIO; + /* tmp -> tmp */ - if (ftruncate (fd, size) != 0) - return - errno; - } + if (dst_inode) + xdp_inode_do_unlink (dst_inode, dir_fd, TRUE); - if (newattr) - { - int res = xdp_fh_fstat (fh, newattr); - if (res < 0) - return res; + g_free (src_inode->filename); + src_inode->filename = g_strdup (dst_filename); } + else + { + /* tmp -> doc */ - return 0; -} - -static void -mark_open_tmp_file_readonly (guint32 tmp_id) -{ - GList *found = NULL; - GList *l; + 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); - { - AUTOLOCK (open_files); + /* However, unlink trunc_file if its there */ + if (dst_inode->trunc_filename) + unlinkat (dir_fd, dst_inode->trunc_filename, 0); + } - 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)); - } - } + 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); - /* We do the actual updates outside of the open_files lock to avoid - potentially blocking for a long time with it held */ + /* 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; + } - for (l = found; l != NULL; l = l->next) - { - XdpFh *fh = l->data; - XDP_FH_AUTOLOCK (fh); - fh->readonly = TRUE; - xdp_fh_unref (fh); + /* 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_list_free (found); + + 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 XdpFh * -find_open_fh (fuse_ino_t ino) +static XdpInode * +xdp_inode_ensure_document_file (XdpInode *dir, + XdgAppDbEntry *entry) { - GList *l; + 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 (open_files); + AUTOLOCK(inodes); - for (l = open_files; l != NULL; l = l->next) + inode = xdp_inode_lookup_child_unlocked (dir, basename); + if (inode == NULL) { - 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); + 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 NULL; + return inode; } -/******************************* Main *******************************/ - -static XdpInodeClass -get_class (guint64 inode) +static char * +create_tmp_for_doc (XdgAppDbEntry *entry, int dir_fd, int flags, mode_t mode, int *fd_out) { - return (inode >> (64-8)) & 0xff; -} + g_autofree char *basename = xdp_entry_dup_basename (entry); + g_autofree char *template = g_strconcat (".xdp_", basename, ".XXXXXX", NULL); + int fd; -static guint64 -get_class_ino (guint64 inode) -{ - return inode & ((1L << (64-8)) - 1); -} + fd = xdg_app_mkstempat (dir_fd, template, flags|O_CLOEXEC, mode); + if (fd == -1) + return NULL; -static guint32 -get_app_id_from_app_doc_ino (guint64 inode) -{ - return inode >> 32; + g_debug ("Created temp file %s", template); + *fd_out = fd; + return g_steal_pointer (&template); } -static guint32 -get_doc_id_from_app_doc_ino (guint64 inode) +/* sets errno */ +static XdpInode * +xdp_inode_create_file (XdpInode *dir, + XdgAppDbEntry *entry, + const char *filename, + mode_t mode, + gboolean truncate, + gboolean exclusive) { - return inode & 0xffffffff; -} + 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; -static guint64 -make_inode (XdpInodeClass class, guint64 inode) -{ - return ((guint64)class) << (64-8) | (inode & 0xffffffffffffff); -} + g_assert (dir->type == XDP_INODE_APP_DOC_DIR || dir->type == XDP_INODE_DOC_DIR); -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); -} + AUTOLOCK(inodes); -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); -} + inode = xdp_inode_lookup_child_unlocked (dir, filename); + if (inode != NULL) + { + if (exclusive) + { + xdp_inode_unref (inode); + errno = EEXIST; + return NULL; + } -static gboolean -name_looks_like_id (const char *name) -{ - int i; + if (truncate) + { + /* TODO: Handle extra truncate for existing file */ + errno = ENOSYS; + return NULL; + } + + return inode; + } - /* No zeros in front, we need canonical form */ - if (name[0] == '0') - return FALSE; + dir_fd = xdp_inode_open_dir_fd (dir); + if (dir_fd == -1) + return NULL; - for (i = 0; i < 8; i++) + 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 { - char c = name[i]; - if (c == 0) - break; + backing_filename = create_tmp_for_doc (entry, dir_fd, O_RDWR, mode & 0777, &fd); + if (backing_filename == NULL) + return NULL; + } - if (!g_ascii_isdigit(c) && - !(c >= 'a' && c <= 'f')) - return FALSE; + 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); } - if (name[i] != 0) - return FALSE; + /* We add an extra ref for tmp files to keep them alive until unlink */ + if (!is_doc) + xdp_inode_ref (inode); - return TRUE; + return inode; } -static guint32 -get_app_id_from_name (const char *name) +static XdpInode * +xdp_inode_lookup (fuse_ino_t inode_nr) { - guint32 id; - char *myname; + 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; - AUTOLOCK(app_id); + ino = get_dir_inode_nr_unlocked (app_id, doc_id); - id = GPOINTER_TO_UINT (g_hash_table_lookup (app_name_to_id, name)); + inode = xdp_inode_lookup_unlocked (ino); + if (inode) + return inode; - if (id != 0) - return id; + 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; + } + } - id = next_app_id++; + inode = xdp_inode_new_unlocked (ino, type, parent, filename, app_id, doc_id); + xdp_inode_unref_internal (parent, TRUE); - /* We rely this to not overwrap into the high byte in the inode */ - g_assert (id < 0x00ffffff); + 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); + } - 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; + return inode; } -static const char * -get_app_name_from_id (guint32 id) +static XdpInode * +xdp_inode_get_dir (const char *app_id, const char *doc_id, XdgAppDbEntry *entry) { - AUTOLOCK(app_id); - return g_hash_table_lookup (app_id_to_name, GUINT_TO_POINTER (id)); + AUTOLOCK(inodes); + return xdp_inode_get_dir_unlocked (app_id, doc_id, entry); } -static void -fill_app_name_hash (void) -{ - g_auto(GStrv) keys = NULL; - int i; +/********************************************************************** \ + * FUSE Implementation +\***********************************************************************/ - keys = xdp_list_apps (); - for (i = 0; keys[i] != NULL; i++) - get_app_id_from_name (keys[i]); +static int +get_user_perms (const struct stat *stbuf) +{ + /* Strip out exec and setuid bits */ + return stbuf->st_mode & 0666; } static gboolean -app_can_see_doc (XdgAppDbEntry *entry, guint32 app_id) +app_can_write_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_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; + entry = xdp_lookup_doc (inode->doc_id); + if (entry == NULL) + { + errno = ENOENT; + return -1; + } - stbuf->st_mode = S_IFDIR | DOC_DIR_PERMS; - stbuf->st_nlink = 2; - break; - } + can_see = app_can_see_doc (entry, inode->app_id); + can_write = app_can_write_doc (entry, inode->app_id); - 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; + if (!can_see) + { + errno = ENOENT; + return -1; + } - entry = xdp_lookup_doc (doc_id); - if (entry == NULL) - return ENOENT; + g_mutex_lock (&inode->mutex); - can_write = app_can_write_doc (entry, app_id); + 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; - stbuf->st_nlink = DOC_FILE_NLINK; + g_mutex_unlock (&inode->mutex); - if (xdp_entry_stat (entry, &tmp_stbuf, AT_SYMLINK_NOFOLLOW) != 0) - return ENOENT; + 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) +xdp_fuse_lookup (fuse_req_t req, + fuse_ino_t parent, + const char *name) { - 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; - } - } + g_autoptr(XdpInode) parent_inode = NULL; + struct fuse_entry_param e = {0}; + g_autoptr(XdpInode) child_inode = NULL; + g_autoptr (XdgAppDbEntry) entry = NULL; + g_debug ("xdp_fuse_lookup %lx/%s -> ", parent, name); - fh = find_open_fh (ino); - if (fh) + parent_inode = xdp_inode_lookup (parent); + if (parent_inode == NULL) { - res = xdp_fh_fstat_locked (fh, &stbuf); - if (res == 0) - { - fuse_reply_attr (req, &stbuf, get_attr_cache_time (stbuf.st_mode)); - return; - } + g_debug ("xdp_fuse_lookup <- error parent ENOENT"); + fuse_reply_err (req, ENOENT); + 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; + /* Default */ + e.attr_timeout = ATTR_CACHE_TIME; + e.entry_timeout = ENTRY_CACHE_TIME; - switch (parent_class) + switch (parent_inode->type) { - case STD_DIRS_INO_CLASS: - - switch (parent_class_ino) + case XDP_INODE_ROOT: + if (strcmp (name, BY_APP_NAME) == 0) + child_inode = xdp_inode_ref (by_app_inode); + else { - 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; + entry = xdp_lookup_doc (name); + if (entry != NULL) + child_inode = xdp_inode_get_dir (NULL, name, entry); } 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; - } - } + 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 APP_DOC_DIR_INO_CLASS: + case XDP_INODE_APP_DOC_DIR: + case XDP_INODE_DOC_DIR: { - 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_autoptr(XdpInode) doc_inode = NULL; + entry = xdp_lookup_doc (parent_inode->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; - } + g_debug ("xdp_fuse_lookup <- error no parent entry ENOENT"); + fuse_reply_err (req, ENOENT); + return; } - 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; - } + /* Ensure it is alive at least during lookup_child () */ + doc_inode = xdp_inode_ensure_document_file (parent_inode, entry); - break; - } + child_inode = xdp_inode_lookup_child (parent_inode, name); + + /* We verify in the stat below if the backing file exists */ - break; + /* 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 TMPFILE_INO_CLASS: - case APP_DOC_FILE_INO_CLASS: - return ENOTDIR; + case XDP_INODE_DOC_FILE: + fuse_reply_err (req, ENOTDIR); + return; default: break; } - return ENOENT; -} + if (child_inode == NULL) + { + g_debug ("xdp_fuse_lookup <- error child ENOENT"); + fuse_reply_err (req, ENOENT); + return; + } -static void -xdp_fuse_lookup (fuse_req_t req, - fuse_ino_t parent, - const char *name) -{ - struct fuse_entry_param e = {0}; - int res; + if (xdp_inode_stat (child_inode, &e.attr) != 0) + { + fuse_reply_err (req, errno); + return; + } - g_debug ("xdp_fuse_lookup %lx/%s -> ", parent, name); + e.ino = child_inode->ino; - memset (&e, 0, sizeof(e)); + 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); +} - res = xdp_lookup (parent, name, &e.ino, &e.attr, NULL, NULL); +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); - if (res == 0) - { - 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); - } + 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; - } + 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; - class = get_class (ino); - class_ino = get_class_ino (ino); + case XDP_INODE_BY_APP: + { + 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; - 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 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 BY_APP_INO: - dirbuf_add (req, &b, ".", ino); - dirbuf_add (req, &b, "..", FUSE_ROOT_ID); + case XDP_INODE_DOC_FILE: + fuse_reply_err (req, ENOTDIR); + break; - /* Update for any possible new app */ - fill_app_name_hash (); + 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) { - 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)); - } + fuse_reply_err (req, ENOENT); + break; } - break; - default: - break; - } - break; + dirbuf_add (req, &b, ".", inode->ino, S_IFDIR); + dirbuf_add (req, &b, "..", inode->parent->ino, S_IFDIR); - case APP_DIR_INO_CLASS: - { - 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; - } + /* Ensure it is alive at least during list_children () */ + doc_inode = xdp_inode_ensure_document_file (inode, entry); - break; + children = xdp_inode_list_children (inode); - 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); + 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,551 +1248,497 @@ 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) + +static void +xdp_fuse_getattr (fuse_req_t req, + fuse_ino_t ino, + struct fuse_file_info *fi) { - g_autofree char *basename = xdp_entry_dup_basename (entry); - g_autofree char *template = g_strconcat (".xdp_", basename, ".XXXXXX", NULL); - int fd; + g_autoptr(XdpInode) inode = NULL; + struct stat stbuf = { 0 }; - fd = xdg_app_mkstempat (dir_fd, template, flags|O_CLOEXEC, 0600); - if (fd == -1) - return NULL; + g_debug ("xdp_fuse_getattr %lx (fi=%p)", ino, fi); - *fd_out = fd; - return g_steal_pointer (&template); + inode = xdp_inode_lookup (ino); + if (inode == NULL) + { + g_debug ("xdp_fuse_getattr <- error ENOENT"); + fuse_reply_err (req, ENOENT); + return; + } + + if (xdp_inode_stat (inode, &stbuf) != 0) + { + fuse_reply_err (req, errno); + return; + } + + fuse_reply_attr (req, &stbuf, ATTR_CACHE_TIME); } static void -xdp_fuse_open (fuse_req_t req, - fuse_ino_t ino, - struct fuse_file_info *fi) +xdp_fuse_fsyncdir (fuse_req_t req, + fuse_ino_t ino, + int datasync, + 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_autoptr(XdpInode) inode = NULL; - g_debug ("xdp_fuse_open %lx", ino); + g_debug ("xdp_fuse_fsyncdir %lx %p", ino, fi); - 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_fsyncdir <- error ENOENT"); + fuse_reply_err (req, ENOENT); return; } - if ((stbuf.st_mode & S_IFMT) != S_IFREG) + if (inode->type == XDP_INODE_APP_DOC_DIR || + inode->type == XDP_INODE_DOC_DIR) { - fuse_reply_err (req, EISDIR); - return; + g_autoptr (XdgAppDbEntry) entry = xdp_lookup_doc (inode->doc_id); + if (entry != NULL) + { + g_autofree char *dirname = xdp_entry_dup_dirname (entry); + int fd = open (dirname, O_DIRECTORY|O_RDONLY); + if (fd >= 0) + { + if (datasync) + fdatasync (fd); + else + fsync (fd); + close (fd); + } + } } - if (entry && class == APP_DOC_FILE_INO_CLASS) + 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) { - 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; + XdpFile *file = l->data; - dir_fd = xdp_entry_open_dir (entry); - if (dir_fd == -1) + if (file->open_mode != O_RDONLY) { - fuse_reply_err (req, errno); - return; + has_open_for_write = TRUE; + break; } + } - can_write = app_can_write_doc (entry, app_id); - - if ((fi->flags & 3) != O_RDONLY) + if (!has_open_for_write) + { + if (inode->truncated) { - if (!can_write) + if (inode->open_files != NULL && inode->fd != -1) { - fuse_reply_err (req, EACCES); - return; + /* 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 (faccessat (dir_fd, basename, W_OK, 0) != 0) + if (inode->filename != NULL) { - fuse_reply_err (req, errno); - return; + /* 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)); } - tmp_basename = create_tmp_for_doc (entry, dir_fd, O_RDWR, &write_fd); - if (tmp_basename == NULL) - { - fuse_reply_err (req, errno); - return; - } + 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); } - fd = openat (dir_fd, basename, O_RDONLY|O_NOFOLLOW|O_CLOEXEC); - if (fd < 0) + if (inode->trunc_fd != -1) { - fuse_reply_err (req, errno); - return; + close (inode->trunc_fd); + inode->trunc_fd = -1; + g_free (inode->trunc_filename); + inode->trunc_filename = NULL; } - 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))) + + if (inode->open_files == NULL) { - 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) + if (inode->fd != -1) { - fuse_reply_err (req, ENOENT); - return; + close (inode->fd); + inode->fd = -1; } - fd = openat (dir_fd, backing_basename, get_open_flags (fi)|O_NOFOLLOW|O_CLOEXEC); - if (fd < 0) + if (inode->dir_fd != -1) { - fuse_reply_err (req, errno); - return; + close (inode->dir_fd); + inode->dir_fd = -1; } - 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) +xdp_file_free (XdpFile *file) { - 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; + XdpInode *inode = file->inode; - g_debug ("xdp_fuse_create %lx/%s, flags %o", parent, name, fi->flags); + g_mutex_lock (&inode->mutex); + inode->open_files = g_list_remove (inode->open_files, file); - if ((res = xdp_stat (parent, &stbuf, &entry)) != 0) - { - fuse_reply_err (req, res); - return; - } + xdp_inode_locked_close_unneeded_fds (inode); - if ((stbuf.st_mode & S_IFMT) != S_IFDIR) - { - fuse_reply_err (req, ENOTDIR); - return; - } + g_mutex_unlock (&inode->mutex); + xdp_inode_unref (inode); + g_free (file); +} - if (parent_class != APP_DOC_DIR_INO_CLASS) +/* 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) { - fuse_reply_err (req, EACCES); - return; + inode->dir_fd = xdp_inode_open_dir_fd (inode->parent); + if (inode->dir_fd == -1) + return -1; } - 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) + if (for_write) { - 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); + if (faccessat (inode->dir_fd, inode->backing_filename, W_OK, 0) != 0) + return -1; } - 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; - } + if (inode->fd == -1) + { + int mode = O_NOFOLLOW|O_CLOEXEC; - fd = openat (dir_fd, backing_basename, get_open_flags (fi)|O_NOFOLLOW|O_CLOEXEC); - if (fd == -1) - { - fuse_reply_err (req, errno); - return; - } - } + if (inode->is_doc) + mode |= O_RDONLY; else - { - int errsv; - g_autofree char *tmp_basename = NULL; - glnx_fd_close int dir_fd = -1; + mode |= O_RDWR; - 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); + inode->fd = openat (inode->dir_fd, inode->backing_filename, mode); + if (inode->fd < 0) + return -1; + } - if (tmpfile == NULL) - { - fuse_reply_err (req, errsv); - return; - } - } + if (inode->is_doc && for_write && inode->trunc_fd == -1) + { + struct stat st_buf; + mode_t mode = 0600; - 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); + if (fstat (inode->fd, &st_buf) == 0) + mode = get_user_perms (&st_buf); - 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); + 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_read (fuse_req_t req, +xdp_fuse_open (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; + g_autoptr(XdpInode) inode = NULL; + g_autoptr(XdgAppDbEntry) entry = NULL; + gboolean can_write; + int open_mode; + XdpFile *file = NULL; + int errsv; - XDP_FH_AUTOLOCK (fh); + g_debug ("xdp_fuse_open %lx flags %o", ino, fi->flags); - fd = xdp_fh_get_fd_nolock (fh); - if (fd == -1) + inode = xdp_inode_lookup (ino); + if (inode == NULL) { - bufv.buf[0].flags = 0; - bufv.buf[0].mem = &c; - bufv.buf[0].size = 0; - - fuse_reply_data (req, &bufv, FUSE_BUF_NO_SPLICE); + g_debug ("xdp_fuse_open <- no inode error ENOENT"); + fuse_reply_err (req, ENOENT); 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) + if (inode->type != XDP_INODE_DOC_FILE) { - fuse_reply_err (req, EACCES); + g_debug ("xdp_fuse_open <- error EISDIR"); + fuse_reply_err (req, EISDIR); return; } - fd = xdp_fh_get_fd_nolock (fh); - if (fd == -1) + entry = xdp_lookup_doc (inode->doc_id); + if (entry == NULL || + !app_can_see_doc (entry, inode->app_id)) { - fuse_reply_err (req, EIO); + g_debug ("xdp_fuse_open <- no entry error ENOENT"); + fuse_reply_err (req, ENOENT); 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; + can_write = app_can_write_doc (entry, inode->app_id); - XDP_FH_AUTOLOCK (fh); + open_mode = fi->flags & 3; - if (fh->readonly) + if (open_mode != O_RDONLY && !can_write) { + g_debug ("xdp_fuse_open <- no write EACCES"); fuse_reply_err (req, EACCES); return; } - fd = xdp_fh_get_fd_nolock (fh); - if (fd == -1) + g_mutex_lock (&inode->mutex); + + if (xdp_inode_locked_ensure_fd_open (inode, entry, + open_mode != O_RDONLY) == 0) { - fuse_reply_err (req, EIO); - return; + 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); } - dst.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; - dst.buf[0].fd = fd; - dst.buf[0].pos = off; + g_mutex_unlock (&inode->mutex); - res = fuse_buf_copy (&dst, bufv, FUSE_BUF_SPLICE_NONBLOCK); - if (res < 0) - fuse_reply_err (req, -res); + if (file != NULL) + { + fi->fh = (gsize)file; + if (fuse_reply_open (req, fi)) + xdp_file_free (file); + } else - fuse_reply_write (req, res); + fuse_reply_err (req, errsv); } -static void -xdp_fuse_release (fuse_req_t req, - fuse_ino_t ino, - struct fuse_file_info *fi) -{ - XdpFh *fh = (gpointer)fi->fh; - - g_debug ("xdp_fuse_release %lx (fi=%p, refcount: %d)", ino, fi, fh->ref_count); - - xdp_fh_unref (fh); - fuse_reply_err (req, 0); -} static void -xdp_fuse_rename (fuse_req_t req, +xdp_fuse_create (fuse_req_t req, fuse_ino_t parent, - const char *name, - fuse_ino_t newparent, - const char *newname) + const char *filename, + mode_t mode, + struct fuse_file_info *fi) { - 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; - g_autoptr(XdpTmp) other_tmp = NULL; - guint32 app_id = 0; - guint32 doc_id = 0; - gboolean can_write; + 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_rename %lx/%s -> %lx/%s", parent, name, newparent, newname); + g_debug ("xdp_fuse_create %lx/%s, flags %o", parent, filename, fi->flags); - 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_create <- error parent ENOENT"); + fuse_reply_err (req, ENOENT); return; } - if (parent_class != APP_DOC_DIR_INO_CLASS) + 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) { - /* Only allow renames in (app) doc dirs */ 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); + entry = xdp_lookup_doc (parent_inode->doc_id); + if (entry == NULL) + { + fuse_reply_err (req, ENOENT); + return; + } - can_write = app_can_write_doc (entry, app_id); + can_see = app_can_see_doc (entry, parent_inode->app_id); + if (!can_see) + { + fuse_reply_err (req, ENOENT); + return; + } - /* Only allow renames inside the same dir */ - if (!can_write || - parent != newparent || - entry == NULL || - /* Also, don't allow renaming non-tmpfiles */ - tmp == NULL) + can_write = app_can_write_doc (entry, parent_inode->app_id); + if (!can_write) { fuse_reply_err (req, EACCES); return; } - basename = xdp_entry_dup_basename (entry); + 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); - if (strcmp (newname, basename) == 0) + open_mode = fi->flags & 3; + + if (xdp_inode_locked_ensure_fd_open (inode, entry, + open_mode != O_RDONLY) == 0) { - glnx_fd_close int dir_fd = -1; - g_autofree char *backing_basename = NULL; - /* Rename tmpfile to regular file */ + 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); + } - dir_fd = xdp_entry_open_dir (entry); - if (dir_fd == -1) + 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; } - /* Steal backing path so we don't delete it when unlinking tmp */ - backing_basename = xdp_tmp_steal_backing_basename (tmp); - if (backing_basename == NULL) + e.ino = inode->ino; + if (inode->is_doc) { - fuse_reply_err (req, EINVAL); - return; + e.attr_timeout = 0; + e.entry_timeout = 0; + } + else + { + e.attr_timeout = ATTR_CACHE_TIME; + e.entry_timeout = ENTRY_CACHE_TIME; } - /* Stop writes to all outstanding fds to the temp file */ - mark_open_tmp_file_readonly (tmp->tmp_id); + xdp_inode_ref (inode); /* Ref given to the kernel, returned in xdp_fuse_forget() */ - if (renameat (dir_fd, backing_basename, - dir_fd, basename) != 0) + fi->fh = (gsize)file; + if (fuse_reply_create (req, &e, fi)) { - fuse_reply_err (req, errno); - return; + xdp_file_free (file); + xdp_inode_unref (inode); } + } + else + fuse_reply_err (req, errsv); +} - AUTOLOCK(tmp_files); +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; - xdp_tmp_unlink_nolock (tmp); + g_debug ("xdp_fuse_real %lx %ld %ld", ino, (long)size, (long)off); - fuse_reply_err (req, 0); + g_mutex_lock (&inode->mutex); - /* We actually turn the old inode to a different one after the rename, so - we need to invalidate the target entry */ + 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_lowlevel_notify_inval_entry (main_ch, make_app_doc_dir_inode (app_id, doc_id), - basename, strlen (basename)); + fuse_reply_data (req, &bufv, FUSE_BUF_NO_SPLICE); } else { - /* Rename tmpfile to other tmpfile name */ + bufv.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + bufv.buf[0].fd = fd; + bufv.buf[0].pos = off; - AUTOLOCK(tmp_files); + fuse_reply_data (req, &bufv, FUSE_BUF_SPLICE_MOVE); + } - other_tmp = find_tmp_by_name_nolock (newparent, newname); - if (other_tmp) - xdp_tmp_unlink_nolock (other_tmp); + g_mutex_unlock (&inode->mutex); +} - g_free (tmp->name); - tmp->name = g_strdup (newname); - fuse_reply_err (req, 0); - } +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 @@ -1823,150 +1748,182 @@ xdp_fuse_setattr (fuse_req_t req, 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); - if (to_set == FUSE_SET_ATTR_SIZE && fi != NULL) + inode = xdp_inode_lookup (ino); + if (inode == NULL) { - XdpFh *fh = (gpointer)fi->fh; - int res; - struct stat newattr = {0}; + g_debug ("xdp_fuse_setattr <- error ENOENT"); + fuse_reply_err (req, ENOENT); + return; + } - /* ftruncate */ + if (inode->type != XDP_INODE_DOC_FILE) + { + g_debug ("xdp_fuse_setattr <- not file ENOSYS"); + fuse_reply_err (req, ENOSYS); + return; + } - if (!fh->can_write) - { - fuse_reply_err (req, EACCES); - 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; + } - res = xdp_fh_truncate_locked (fh, attr->st_size, &newattr); - if (res < 0) - { - fuse_reply_err (req, res); - return; - } + can_write = app_can_write_doc (entry, inode->app_id); - fuse_reply_attr (req, &newattr, get_attr_cache_time (newattr.st_mode)); - } - else if (to_set == FUSE_SET_ATTR_SIZE && fi == NULL) + if (to_set == FUSE_SET_ATTR_SIZE) { - int res = 0; - struct stat newattr = {0}; - struct stat *newattrp = &newattr; - g_autoptr(XdpFh) fh = NULL; + g_mutex_lock (&inode->mutex); - /* truncate, truncate any open files (but EACCES if not open) */ - - fh = find_open_fh (ino); - if (fh) + if (!can_write) + res = EACCES; + else if (inode->is_doc) { - if (!fh->can_write) + /* 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 { - fuse_reply_err (req, EACCES); - return; + 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); + } } - 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; + 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; + } } - - fuse_reply_attr (req, &newattr, get_attr_cache_time (newattr.st_mode)); + g_mutex_unlock (&inode->mutex); } 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) + if (!can_write) + res = EACCES; + else { - 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; + int fd = xdp_inode_locked_get_write_fd (inode); + if (fd == -1 || + fchmod (fd, get_user_perms (attr)) != 0) + res = errno; + } + } + else + res = ENOSYS; - found = TRUE; + 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); + } +} - if (res != 0) - err = -errsv; - else - err = xdp_fh_fstat (fh, &newattr); - } - } +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; - if (!found) - { - fuse_reply_err (req, EACCES); - return; - } + g_debug ("xdp_fuse_write %lx %ld %ld", ino, (long)size, (long)off); - if (err < 0) - { - fuse_reply_err (req, -err); - return; - } + g_mutex_lock (&inode->mutex); - fuse_reply_attr (req, &newattr, get_attr_cache_time (newattr.st_mode)); - } + fd = xdp_inode_locked_get_write_fd (inode); + if (fd < 0) + fuse_reply_err (req, errno); else - fuse_reply_err (req, ENOSYS); + { + 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_fsyncdir (fuse_req_t req, - fuse_ino_t ino, - int datasync, - struct fuse_file_info *fi) +xdp_fuse_write_buf (fuse_req_t req, + fuse_ino_t ino, + struct fuse_bufvec *bufv, + off_t off, + struct fuse_file_info *fi) { - XdpInodeClass class = get_class (ino); - guint64 class_ino = get_class_ino (ino); - guint32 doc_id; + 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); - if (class == APP_DOC_DIR_INO_CLASS) + g_mutex_lock (&inode->mutex); + + fd = xdp_inode_locked_get_write_fd (inode); + if (fd == -1) + fuse_reply_err (req, errno); + else { - g_autoptr (XdgAppDbEntry) entry = NULL; - doc_id = get_doc_id_from_app_doc_ino (class_ino); + dst.buf[0].flags = FUSE_BUF_IS_FD | FUSE_BUF_FD_SEEK; + dst.buf[0].fd = fd; + dst.buf[0].pos = off; - entry = xdp_lookup_doc (doc_id); - if (entry != NULL) - { - g_autofree char *dirname = xdp_entry_dup_dirname (entry); - int fd = open (dirname, O_DIRECTORY|O_RDONLY); - if (fd >= 0) - { - if (datasync) - fdatasync (fd); - else - fsync (fd); - close (fd); - } - } + res = fuse_buf_copy (&dst, bufv, FUSE_BUF_SPLICE_NONBLOCK); + if (res < 0) + fuse_reply_err (req, -res); + else + fuse_reply_write (req, res); } - fuse_reply_err (req, 0); + g_mutex_unlock (&inode->mutex); } static void @@ -1975,176 +1932,226 @@ xdp_fuse_fsync (fuse_req_t req, int datasync, struct fuse_file_info *fi) { - XdpInodeClass class = get_class (ino); + g_autoptr(XdpInode) inode = NULL; + int fd; + int res = 0; + + g_debug ("xdp_fuse_fsync %lx", ino); + + inode = xdp_inode_lookup (ino); + if (inode == NULL) + { + g_debug ("xdp_fuse_setattr <- error ENOENT"); + fuse_reply_err (req, ENOENT); + return; + } - if (class == APP_DOC_FILE_INO_CLASS || - class == TMPFILE_INO_CLASS) + if (inode->type == XDP_INODE_DOC_FILE) { - XdpFh *fh = (gpointer)fi->fh; + g_mutex_lock (&inode->mutex); - XDP_FH_AUTOLOCK (fh); + fd = xdp_inode_locked_get_write_fd (inode); + if (fd != -1 && fsync (fd) != 0) + res = errno; - if (fh->fd >= 0) - fsync (fh->fd); - if (fh->truncated && fh->trunc_fd >= 0) - fsync (fh->trunc_fd); + g_mutex_unlock (&inode->mutex); } - fuse_reply_err (req, 0); + fuse_reply_err (req, res); } static void xdp_fuse_unlink (fuse_req_t req, fuse_ino_t parent, - const char *name) + const char *filename) { - 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; + g_autoptr(XdpInode) child_inode = NULL; - g_debug ("xdp_fuse_unlink %lx/%s", parent, name); + g_debug ("xdp_fuse_unlink %lx/%s", parent, filename); - 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_lookup <- error parent ENOENT"); + fuse_reply_err (req, ENOENT); return; } - if (entry == NULL) + if (parent_inode->type == XDP_INODE_DOC_FILE) { - fuse_reply_err (req, EACCES); + fuse_reply_err (req, ENOTDIR); return; } - if (parent_class != APP_DOC_DIR_INO_CLASS) + if (parent_inode->type != XDP_INODE_APP_DOC_DIR && + parent_inode->type != XDP_INODE_DOC_DIR) { - /* Only allow unlink in (app) doc dirs */ fuse_reply_err (req, EACCES); return; } - app_id = get_app_id_from_app_doc_ino (parent_class_ino); + child_inode = xdp_inode_unlink_child (parent_inode, filename); + if (child_inode == NULL) + { + fuse_reply_err (req, ENOENT); + return; + } - can_write = app_can_write_doc (entry, app_id); - if (!can_write) + 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) +{ + g_autoptr(XdpInode) parent_inode = NULL; + g_autoptr (XdgAppDbEntry) entry = NULL; + g_autofree char *basename = NULL; + gboolean can_see, can_write; + + g_debug ("xdp_fuse_rename %lx/%s -> %lx/%s", parent, name, newparent, newname); + + parent_inode = xdp_inode_lookup (parent); + if (parent_inode == NULL) { - fuse_reply_err (req, EACCES); + g_debug ("xdp_fuse_rename <- error parent ENOENT"); + fuse_reply_err (req, ENOENT); return; } - basename = xdp_entry_dup_basename (entry); - if (strcmp (name, basename) == 0) + if (parent_inode->type == XDP_INODE_DOC_FILE) { - glnx_fd_close int dir_fd = -1; + fuse_reply_err (req, ENOTDIR); + return; + } - dir_fd = xdp_entry_open_dir (entry); - if (dir_fd == -1) - { - fuse_reply_err (req, errno); - return; - } + if (parent_inode->type != XDP_INODE_APP_DOC_DIR && + parent_inode->type != XDP_INODE_DOC_DIR) + { + fuse_reply_err (req, EACCES); + return; + } - if (unlinkat (dir_fd, basename, 0) != 0) - { - fuse_reply_err (req, errno); - 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; } - else + + entry = xdp_lookup_doc (parent_inode->doc_id); + if (entry == NULL) { - AUTOLOCK(tmp_files); - xdp_tmp_unlink_nolock (tmp); + fuse_reply_err (req, ENOENT); + return; + } - fuse_reply_err (req, 0); - } -} + 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) + { + fuse_reply_err (req, ENOENT); + return; + } + + if (!can_write) + { + fuse_reply_err (req, EACCES); + return; + } + + basename = xdp_entry_dup_basename (entry); + + if (xdp_inode_rename_child (parent_inode, name, newname, basename) != 0) + fuse_reply_err (req, errno); + else + 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, - .write = xdp_fuse_write, - .write_buf = xdp_fuse_write_buf, .release = xdp_fuse_release, - .rename = xdp_fuse_rename, .setattr = xdp_fuse_setattr, + .write = xdp_fuse_write, + .write_buf = xdp_fuse_write_buf, .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)); -} - -/* Called when a document id is created/removed */ -void -xdp_fuse_invalidate_doc (const char *doc_id_s, - XdgAppDbEntry *entry) -{ - guint32 doc_id = xdp_id_from_name (doc_id_s); - g_autofree char *basename = xdp_entry_dup_basename (entry); + g_debug ("invalidate %s/%s", doc_id, opt_app_id ? opt_app_id : "*"); - g_debug ("invalidate %s\n", doc_id_s); + 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)); - /* This can happen if fuse is not initialized yet for the very - first dbus message that activated the service */ - if (main_ch == NULL) - return; + for (l = inode->children; l != NULL; l = l->next) + { + XdpInode *child = l->data; - 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)); + 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)); + } + } } -guint32 -xdp_fuse_lookup_id_for_inode (ino_t inode) +char * +xdp_fuse_lookup_id_for_inode (ino_t ino) { - XdpInodeClass class = get_class (inode); - guint64 class_ino = get_class_ino (inode); + g_autoptr(XdpInode) inode = NULL; - if (class != APP_DOC_FILE_INO_CLASS) - return 0; + inode = xdp_inode_lookup (ino); + if (inode == NULL) + return NULL; - return get_doc_id_from_app_doc_ino (class_ino); + if (inode->type != XDP_INODE_DOC_FILE || + !inode->is_doc) + return NULL; + + 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 b1e6c82..887db80 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 5138c4c..611526f 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 5370b1e..6725f41 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 bc1bfc6..77e0bae 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); |