From 77af6844d8330b31d58080076afb31e08974ce09 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 16 Aug 2016 09:26:16 -0400 Subject: [PATCH] rofiles-fuse: Rework to be based on nlink Programs like `useradd` try to `open(/etc/passwd, O_RDWR)` to append, which didn't work with rofiles-fuse. Thinking about this, I realized that there's a simpler algorithm for "can we write to this file" which is "does it have a hardlink count <= 1"? Switching to this both drops complexity (we no longer need to keep a hash table of files we created), and also lets useradd work. Closes: #462 Approved by: jlebon --- src/rofiles-fuse/main.c | 131 +++++++++++++--------------------------- 1 file changed, 43 insertions(+), 88 deletions(-) diff --git a/src/rofiles-fuse/main.c b/src/rofiles-fuse/main.c index 8468276d..ac44a438 100644 --- a/src/rofiles-fuse/main.c +++ b/src/rofiles-fuse/main.c @@ -39,7 +39,6 @@ // Global to store our read-write path static int basefd = -1; -static GHashTable *created_devino_hash = NULL; static inline const char * ENSURE_RELPATH (const char *path) @@ -50,51 +49,6 @@ ENSURE_RELPATH (const char *path) return path; } -typedef struct { - dev_t dev; - ino_t ino; -} DevIno; - -static guint -devino_hash (gconstpointer a) -{ - DevIno *a_i = (gpointer)a; - return (guint) (a_i->dev + a_i->ino); -} - -static int -devino_equal (gconstpointer a, - gconstpointer b) -{ - DevIno *a_i = (gpointer)a; - DevIno *b_i = (gpointer)b; - return a_i->dev == b_i->dev - && a_i->ino == b_i->ino; -} - -static gboolean -devino_set_contains (dev_t dev, ino_t ino) -{ - DevIno devino = { dev, ino }; - return g_hash_table_contains (created_devino_hash, &devino); -} - -static gboolean -devino_set_insert (dev_t dev, ino_t ino) -{ - DevIno *devino = g_new (DevIno, 1); - devino->dev = dev; - devino->ino = ino; - return g_hash_table_add (created_devino_hash, devino); -} - -static gboolean -devino_set_remove (dev_t dev, ino_t ino) -{ - DevIno devino = { dev, ino }; - return g_hash_table_remove (created_devino_hash, &devino); -} - static int callback_getattr (const char *path, struct stat *st_data) { @@ -188,15 +142,7 @@ callback_mkdir (const char *path, mode_t mode) static int callback_unlink (const char *path) { - struct stat stbuf; path = ENSURE_RELPATH (path); - - if (fstatat (basefd, path, &stbuf, AT_SYMLINK_NOFOLLOW) == 0) - { - if (!S_ISDIR (stbuf.st_mode)) - devino_set_remove (stbuf.st_dev, stbuf.st_ino); - } - if (unlinkat (basefd, path, 0) == -1) return -errno; return 0; @@ -250,6 +196,12 @@ callback_link (const char *from, const char *to) return 0; } +static gboolean +stbuf_is_regfile_hardlinked (struct stat *stbuf) +{ + return S_ISREG (stbuf->st_mode) && stbuf->st_nlink > 1; +} + static int can_write (const char *path) { @@ -261,11 +213,8 @@ can_write (const char *path) else return -errno; } - if (!S_ISDIR (stbuf.st_mode)) - { - if (!devino_set_contains (stbuf.st_dev, stbuf.st_ino)) - return -EROFS; - } + if (stbuf_is_regfile_hardlinked (&stbuf)) + return -EROFS; return 0; } @@ -334,41 +283,49 @@ callback_utime (const char *path, struct utimbuf *buf) static int do_open (const char *path, mode_t mode, struct fuse_file_info *finfo) { - const int flags = finfo->flags & O_ACCMODE; int fd; struct stat stbuf; - /* Support read only opens */ - G_STATIC_ASSERT (O_RDONLY == 0); - path = ENSURE_RELPATH (path); - if (flags == 0) - fd = openat (basefd, path, flags); + if ((finfo->flags & O_ACCMODE) == O_RDONLY) + { + /* Read */ + fd = openat (basefd, path, finfo->flags); + if (fd == -1) + return -errno; + } else { - const int forced_excl_flags = flags | O_CREAT | O_EXCL; - /* Do an exclusive open, don't allow writable fds for existing - files */ - fd = openat (basefd, path, forced_excl_flags, mode); - /* If they didn't specify O_EXCL, give them EROFS if the file - * exists. - */ - if (fd == -1 && (flags & O_EXCL) == 0) - { - if (errno == EEXIST) - errno = EROFS; - } - else if (fd != -1) - { - if (fstat (fd, &stbuf) == -1) - return -errno; - devino_set_insert (stbuf.st_dev, stbuf.st_ino); - } - } + /* Write */ - if (fd == -1) - return -errno; + /* We need to specially handle O_TRUNC */ + fd = openat (basefd, path, finfo->flags & ~O_TRUNC, mode); + if (fd == -1) + return -errno; + + if (fstat (fd, &stbuf) == -1) + { + (void) close (fd); + return -errno; + } + + if (stbuf_is_regfile_hardlinked (&stbuf)) + { + (void) close (fd); + return -EROFS; + } + + /* Handle O_TRUNC here only after verifying hardlink state */ + if (finfo->flags & O_TRUNC) + { + if (ftruncate (fd, 0) == -1) + { + (void) close (fd); + return -errno; + } + } + } finfo->fh = fd; @@ -594,8 +551,6 @@ main (int argc, char *argv[]) exit (EXIT_FAILURE); } - created_devino_hash = g_hash_table_new_full (devino_hash, devino_equal, g_free, NULL); - fuse_main (args.argc, args.argv, &callback_oper, NULL); return 0;