#include "config.h" #define FUSE_USE_VERSION 26 #include #include #include #include #include #include #include #include #include #include #include #include "xdg-app-portal-error.h" #include "xdp-fuse.h" #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 /* 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. */ #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; #define BY_APP_NAME "by-app" static GHashTable *app_name_to_id; static GHashTable *app_id_to_name; static guint32 next_app_id = 1; G_LOCK_DEFINE(app_id); static GThread *fuse_thread = NULL; static struct fuse_session *session = NULL; static struct fuse_chan *main_ch = NULL; static char *mount_path = NULL; static pthread_t fuse_pthread = 0; static int steal_fd (int *fdp) { int fd = *fdp; *fdp = -1; return fd; } static int get_user_perms (const struct stat *stbuf) { /* Strip out exec and setuid bits */ 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) { 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) return TRUE; if (app_name != NULL && xdp_entry_has_permissions (entry, app_name, XDP_PERMISSION_FLAGS_READ)) return TRUE; return FALSE; } static gboolean app_can_write_doc (XdgAppDbEntry *entry, guint32 app_id) { const char *app_name = get_app_name_from_id (app_id); if (app_id == 0) return TRUE; if (app_name != NULL && xdp_entry_has_permissions (entry, app_name, XDP_PERMISSION_FLAGS_WRITE)) return TRUE; return FALSE; } static int xdp_stat (fuse_ino_t ino, struct stat *stbuf, XdgAppDbEntry **entry_out) { 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; stbuf->st_ino = ino; switch (class) { case STD_DIRS_INO_CLASS: switch (class_ino) { 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; } break; case APP_DIR_INO_CLASS: if (get_app_name_from_id (class_ino) == 0) return ENOENT; stbuf->st_mode = S_IFDIR | NON_DOC_DIR_PERMS; stbuf->st_nlink = 2; break; case APP_DOC_DIR_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); 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); if (entry == NULL) return ENOENT; can_write = app_can_write_doc (entry, app_id); stbuf->st_nlink = DOC_FILE_NLINK; if (xdp_entry_stat (entry, &tmp_stbuf, AT_SYMLINK_NOFOLLOW) != 0) return ENOENT; stbuf->st_mode = S_IFREG | get_user_perms (&tmp_stbuf); if (!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; 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; } 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) { struct fuse_entry_param e = {0}; int res; 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) { 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); } else { g_debug ("xdp_fuse_lookup <- error %s", strerror (res)); fuse_reply_err (req, res); } } struct dirbuf { char *p; size_t size; }; static void dirbuf_add (fuse_req_t req, struct dirbuf *b, const char *name, fuse_ino_t ino) { struct stat stbuf; size_t oldsize = b->size; b->size += fuse_add_direntry (req, NULL, 0, name, NULL, 0); b->p = (char *) g_realloc (b->p, b->size); memset (&stbuf, 0, sizeof (stbuf)); stbuf.st_ino = ino; fuse_add_direntry (req, b->p + oldsize, b->size - oldsize, name, &stbuf, b->size); } static void dirbuf_add_docs (fuse_req_t req, struct dirbuf *b, guint32 app_id) { g_autofree guint32 *docs = NULL; guint64 inode; int i; g_autofree char *doc_name = NULL; docs = xdp_list_docs (); for (i = 0; docs[i] != 0; i++) { if (app_id) { g_autoptr(XdgAppDbEntry) entry = xdp_lookup_doc (docs[i]); if (entry == NULL || !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)); } } static int reply_buf_limited (fuse_req_t req, const char *buf, size_t bufsize, off_t off, size_t maxsize) { if (off < bufsize) return fuse_reply_buf (req, buf + off, MIN (bufsize - off, maxsize)); else return fuse_reply_buf (req, NULL, 0); } static void xdp_fuse_readdir (fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, struct fuse_file_info *fi) { struct dirbuf *b = (struct dirbuf *)(fi->fh); reply_buf_limited (req, b->p, b->size, off, size); } static void xdp_fuse_opendir (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { struct stat stbuf = {0}; 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) { fuse_reply_err (req, res); return; } if ((stbuf.st_mode & S_IFMT) != S_IFDIR) { 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; } break; 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; } 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); break; case APP_DOC_FILE_INO_CLASS: case TMPFILE_INO_CLASS: /* These should have returned ENOTDIR above */ default: break; } if (b.p == NULL) fuse_reply_err (req, EIO); else { fi->fh = (gsize)g_memdup (&b, sizeof (b)); if (fuse_reply_open (req, fi) == -ENOENT) { g_free (b.p); g_free ((gpointer)(fi->fh)); } } } static void xdp_fuse_releasedir (fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi) { struct dirbuf *b = (struct dirbuf *)(fi->fh); g_free (b->p); g_free (b); 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, 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, 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) { fuse_reply_err (req, res); return; } if (parent_class != APP_DOC_DIR_INO_CLASS) { /* 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); 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); } static void 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); guint32 doc_id; if (class == APP_DOC_DIR_INO_CLASS) { g_autoptr (XdgAppDbEntry) entry = NULL; doc_id = get_doc_id_from_app_doc_ino (class_ino); 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); } } } fuse_reply_err (req, 0); } 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); if (class == APP_DOC_FILE_INO_CLASS || class == TMPFILE_INO_CLASS) { XdpFh *fh = (gpointer)fi->fh; XDP_FH_AUTOLOCK (fh); if (fh->fd >= 0) fsync (fh->fd); if (fh->truncated && fh->trunc_fd >= 0) fsync (fh->trunc_fd); } fuse_reply_err (req, 0); } static void xdp_fuse_unlink (fuse_req_t req, fuse_ino_t parent, const char *name) { 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; guint32 app_id = 0; gboolean can_write; g_debug ("xdp_fuse_unlink %lx/%s", parent, name); res = xdp_lookup (parent, name, &inode, &stbuf, &entry, &tmp); if (res != 0) { fuse_reply_err (req, res); return; } if (entry == NULL) { fuse_reply_err (req, EACCES); return; } if (parent_class != APP_DOC_DIR_INO_CLASS) { /* 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); can_write = app_can_write_doc (entry, app_id); if (!can_write) { fuse_reply_err (req, EACCES); return; } 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); } else { AUTOLOCK(tmp_files); xdp_tmp_unlink_nolock (tmp); fuse_reply_err (req, 0); } } static struct fuse_lowlevel_ops xdp_fuse_oper = { .lookup = xdp_fuse_lookup, .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, .fsync = xdp_fuse_fsync, .unlink = xdp_fuse_unlink, }; /* Called when a apps permissions to see a document is changed */ void xdp_fuse_invalidate_doc_app (const char *doc_id_s, const char *app_id_s, 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); /* 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\n", doc_id_s); /* 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 (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); } const char * xdp_fuse_get_mountpoint (void) { if (mount_path == NULL) mount_path = g_build_filename (g_get_user_runtime_dir(), "doc", NULL); return mount_path; } void xdp_fuse_exit (void) { if (session) fuse_session_exit (session); if (fuse_pthread) pthread_kill (fuse_pthread, SIGHUP); if (fuse_thread) g_thread_join (fuse_thread); } static gpointer xdp_fuse_mainloop (gpointer data) { fuse_pthread = pthread_self (); fuse_session_loop_mt (session); fuse_session_remove_chan(main_ch); fuse_session_destroy (session); fuse_unmount (mount_path, main_ch); return NULL; } gboolean xdp_fuse_init (GError **error) { char *argv[] = { "xdp-fuse", "-osplice_write,splice_move,splice_read" }; struct fuse_args args = FUSE_ARGS_INIT(G_N_ELEMENTS(argv), argv); 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 = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL); mount_path = xdp_fuse_get_mountpoint (); if (stat (mount_path, &st) == -1 && errno == ENOTCONN) { char *argv[] = { "fusermount", "-u", (char *)mount_path, NULL }; g_spawn_sync (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL, NULL, NULL); } 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); return FALSE; } main_ch = fuse_mount (mount_path, &args); if (main_ch == NULL) { g_set_error (error, XDG_APP_PORTAL_ERROR, XDG_APP_PORTAL_ERROR_FAILED, "Can't mount fuse fs"); return FALSE; } session = fuse_lowlevel_new (&args, &xdp_fuse_oper, sizeof (xdp_fuse_oper), NULL); if (session == NULL) { g_set_error (error, XDG_APP_PORTAL_ERROR, XDG_APP_PORTAL_ERROR_FAILED, "Can't create fuse session"); return FALSE; } fuse_session_add_chan (session, main_ch); fuse_thread = g_thread_new ("fuse mainloop", xdp_fuse_mainloop, session); return TRUE; }