From b7285311257738f6062f3d55ac84a2ae0a5fc714 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 14 Feb 2024 16:43:29 +0100 Subject: [PATCH] rofiles-fuse: Support breaking hardlinks to fs-verity files In case fs-verity is in used for the repo objects, and something like "rpm-ostree apply-live" uses rofiles-fuse with --copyup, then writing to a hard-linked file fails to copy up, like this: echo foo > /a/rofile-mnt/a-file /a/rofile-mnt/a-file: Operation not permitted The reason for this is that do_write() starts by opening the file non-truncating for writing, stat:ing it and then calling verify_write_or_copyup(). It is expecting the the open(write) to succeed, however, in the fs-verity case any open with write fails with EPERM. We fix this by delaying the EPERM failure, only reporting it when the file descriptor needs to be used. In the case this triggered a copyup the file descriptor will be reopened, and in this case we will not get the EPERM anymore. To simplify this code the fd variable now uses glnx_autofd. This fixes https://github.com/coreos/rpm-ostree/issues/4827 --- src/rofiles-fuse/main.c | 42 +++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/rofiles-fuse/main.c b/src/rofiles-fuse/main.c index 937ee4a669..a667f18410 100644 --- a/src/rofiles-fuse/main.c +++ b/src/rofiles-fuse/main.c @@ -400,7 +400,7 @@ callback_utimens (const char *path, const struct timespec tv[2]) static int do_open (const char *path, mode_t mode, struct fuse_file_info *finfo) { - int fd; + glnx_autofd int fd = -1; struct stat stbuf; path = ENSURE_RELPATH (path); @@ -416,29 +416,35 @@ do_open (const char *path, mode_t mode, struct fuse_file_info *finfo) { /* Write */ - /* We need to specially handle O_TRUNC */ + /* We need to specially handle O_TRUNC. + * Also, to files using fs-verity we special case EPERM (which is + * reported when opening such files for writing), and delay reporting + * the error until after trying to copy-up. + */ + int delayed_errno = 0; 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 (errno == EPERM) + delayed_errno = errno; + else + return -errno; } + if (fd != -1 && fstat (fd, &stbuf) == -1) + return -errno; + gboolean did_copyup; - int r = verify_write_or_copyup (path, &stbuf, &did_copyup); + int r = verify_write_or_copyup (path, fd != -1 ? &stbuf : NULL, &did_copyup); if (r != 0) - { - (void)close (fd); - return r; - } + return r; /* In the copyup case, we need to re-open */ if (did_copyup) { - (void)close (fd); + if (fd != -1) + (void)close (fd); + /* Note that unlike the initial open, we will pass through * O_TRUNC. More ideally in this copyup case we'd avoid copying * the whole file in the first place, but eh. It's not like we're @@ -450,21 +456,21 @@ do_open (const char *path, mode_t mode, struct fuse_file_info *finfo) } else { + if (delayed_errno) + return -delayed_errno; + /* In the non-copyup case we handle O_TRUNC here, after we've verified * the hardlink state above with verify_write_or_copyup(). */ if (finfo->flags & O_TRUNC) { if (ftruncate (fd, 0) == -1) - { - (void)close (fd); - return -errno; - } + return -errno; } } } - finfo->fh = fd; + finfo->fh = g_steal_fd (&fd); return 0; }