diff --git a/src/rofiles-fuse/main.c b/src/rofiles-fuse/main.c index dc98898756..795a48cede 100644 --- a/src/rofiles-fuse/main.c +++ b/src/rofiles-fuse/main.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -243,6 +244,9 @@ can_write_stbuf (const struct statx *stbuf) */ if (!(S_ISREG (stbuf->stx_mode) || S_ISLNK (stbuf->stx_mode))) return TRUE; + /* Can't write to fsverity files */ + if (stbuf->stx_attributes & STATX_ATTR_VERITY) + return FALSE; /* If the object isn't hardlinked, it's OK to write */ if (stbuf->stx_nlink <= 1) return TRUE; @@ -275,11 +279,86 @@ gioerror_to_errno (GIOErrorEnum e) } } +static inline void +statx_to_stat (const struct statx *stx, struct stat *stat) +{ + stat->st_dev = makedev (stx->stx_dev_major, stx->stx_dev_minor); + stat->st_ino = stx->stx_ino; + stat->st_mode = stx->stx_mode; + stat->st_nlink = stx->stx_nlink; + stat->st_uid = stx->stx_uid; + stat->st_gid = stx->stx_gid; + stat->st_rdev = makedev (stx->stx_rdev_major, stx->stx_rdev_minor); + stat->st_size = stx->stx_size; + stat->st_blksize = stx->stx_blksize; +/* define to avoid sc_prohibit_stat_st_blocks. */ +#define SC_ST_BLOCKS st_blocks + stat->SC_ST_BLOCKS = stx->stx_blocks; +} + +static gboolean +break_symhardlink (int dfd, const char *path, struct stat *stbuf, GLnxFileCopyFlags copyflags, + GCancellable *cancellable, GError **error) +{ + guint count; + gboolean copy_success = FALSE; + char *path_tmp = glnx_strjoina (path, ".XXXXXX"); + + for (count = 0; count < 100; count++) + { + g_autoptr (GError) tmp_error = NULL; + + glnx_gen_temp_name (path_tmp); + + if (!glnx_file_copy_at (dfd, path, stbuf, dfd, path_tmp, copyflags, cancellable, &tmp_error)) + { + if (g_error_matches (tmp_error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + continue; + g_propagate_error (error, g_steal_pointer (&tmp_error)); + return FALSE; + } + + copy_success = TRUE; + break; + } + + if (!copy_success) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS, + "Exceeded limit of %u file creation attempts", count); + return FALSE; + } + + if (!glnx_renameat (dfd, path_tmp, dfd, path, error)) + return FALSE; + + return TRUE; +} + +// A copy of ostree_break_hardlink but without the check for hardlinks +static gboolean +copyup (int dfd, const char *path, const struct statx *stxbuf, GError **error) +{ + struct stat stbuf; + statx_to_stat (stxbuf, &stbuf); + if (S_ISREG (stbuf.st_mode)) + /* Note it's now completely safe to copy a file to itself, + * as glnx_file_copy_at() is documented to do an O_TMPFILE + rename() + * with GLNX_FILE_COPY_OVERWRITE. + */ + return glnx_file_copy_at (dfd, path, &stbuf, dfd, path, GLNX_FILE_COPY_OVERWRITE, NULL, error); + else if (S_ISLNK (stbuf.st_mode)) + return break_symhardlink (dfd, path, &stbuf, 0, NULL, error); + else + return glnx_throw (error, "Unsupported type for entry '%s'", path); + + return TRUE; +} + static int verify_write_or_copyup (const char *path, const struct statx *stbuf, gboolean *out_did_copyup) { struct statx stbuf_local; - if (out_did_copyup) *out_did_copyup = FALSE; @@ -304,7 +383,7 @@ verify_write_or_copyup (const char *path, const struct statx *stbuf, gboolean *o if (opt_copyup) { g_autoptr (GError) tmp_error = NULL; - if (!ostree_break_hardlink (basefd, path, FALSE, NULL, &tmp_error)) + if (!copyup (basefd, path, stbuf, &tmp_error)) return -gioerror_to_errno ((GIOErrorEnum)tmp_error->code); if (out_did_copyup) *out_did_copyup = TRUE; @@ -417,53 +496,27 @@ do_open (const char *path, mode_t mode, struct fuse_file_info *finfo) else { /* Write */ - - /* We need to specially handle O_TRUNC */ - fd = openat (basefd, path, finfo->flags & ~O_TRUNC, mode); - if (fd == -1) - return -errno; - - if (statx (fd, "", AT_EMPTY_PATH, STATX_BASIC_STATS, &stbuf) == -1) + gboolean exists = FALSE; + if (statx (basefd, path, AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, STATX_BASIC_STATS, &stbuf) + == -1) { - (void)close (fd); - return -errno; - } - - gboolean did_copyup; - int r = verify_write_or_copyup (path, &stbuf, &did_copyup); - if (r != 0) - { - (void)close (fd); - return r; - } - - /* In the copyup case, we need to re-open */ - if (did_copyup) - { - (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 - * high performance anyways. - */ - fd = openat (basefd, path, finfo->flags & ~(O_EXCL | O_CREAT), mode); - if (fd == -1) + if (errno != ENOENT) return -errno; } else + exists = TRUE; + + if (exists) { - /* 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; - } - } + gboolean did_copyup; + int r = verify_write_or_copyup (path, &stbuf, &did_copyup); + if (r != 0) + return r; } + + fd = openat (basefd, path, finfo->flags, mode); + if (fd == -1) + return -errno; } finfo->fh = fd; diff --git a/tests/test-rofiles-fuse.sh b/tests/test-rofiles-fuse.sh index a56a76c6d3..ef6b090843 100755 --- a/tests/test-rofiles-fuse.sh +++ b/tests/test-rofiles-fuse.sh @@ -26,7 +26,7 @@ skip_without_user_xattrs setup_test_repository "bare" -echo "1..12" +echo "1..13" cd ${test_tmpdir} mkdir mnt @@ -192,3 +192,15 @@ sed -i -e s,first,second, mnt/firstfile assert_file_has_content_literal mnt/firstfile "second" echo "ok copyup" + +copyup_reset +echo nonhardlinked > checkout-test2/nonhardlinked +if fsverity enable checkout-test2/nonhardlinked 2>err.txt; then + orig_inode=$(stat -c %i checkout-test2/nonhardlinked) + echo "updated content" > mnt/nonhardlinked + new_inode=$(stat -c %i checkout-test2/nonhardlinked) + assert_not_streq "${orig_inode}" "${new_inode}" + echo "ok copyup fsverity" +else + skip "no fsverity support: $(cat err.txt)" +fi \ No newline at end of file