#include <string.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
+#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <unistd.h>
*/
if (!(S_ISREG (stbuf->stx_mode) || S_ISLNK (stbuf->stx_mode)))
return TRUE;
+#ifdef STATX_ATTR_VERITY
+ /* Can't write to fsverity files */
+ if (stbuf->stx_attributes & STATX_ATTR_VERITY)
+ return FALSE;
+#endif
/* If the object isn't hardlinked, it's OK to write */
if (stbuf->stx_nlink <= 1)
return TRUE;
}
}
+// The libglnx APIs take a stat buffer, so we need to be able to
+// convert from statx.
+static inline void
+statx_to_stat (const struct statx *stxbuf, struct stat *stbuf)
+{
+ stbuf->st_dev = makedev (stxbuf->stx_dev_major, stxbuf->stx_dev_minor);
+ stbuf->st_rdev = makedev (stxbuf->stx_rdev_major, stxbuf->stx_rdev_minor);
+ stbuf->st_ino = stxbuf->stx_ino;
+ stbuf->st_mode = stxbuf->stx_mode;
+ stbuf->st_nlink = stxbuf->stx_nlink;
+ stbuf->st_uid = stxbuf->stx_uid;
+ stbuf->st_gid = stxbuf->stx_gid;
+ stbuf->st_size = stxbuf->stx_size;
+ stbuf->st_blksize = stxbuf->stx_blksize;
+}
+
+// A copy of ostree_break_hardlink but without the check for hardlinks, which
+// is mainly relevant for regular files, where we need to handle verity.
+static gboolean
+copyup (int dfd, const char *path, const struct statx *stxbuf, GError **error)
+{
+ if (S_ISREG (stxbuf->stx_mode))
+ {
+ struct stat stbuf;
+ statx_to_stat (stxbuf, &stbuf);
+ // Note GLNX_FILE_COPY_OVERWRITE always uses O_TMPFILE+rename
+ return glnx_file_copy_at (dfd, path, &stbuf, dfd, path, GLNX_FILE_COPY_OVERWRITE, NULL,
+ error);
+ }
+ else
+ {
+ // For symlinks, we can just directly call the ostree API. This avoids
+ // more code duplication because atomically copying symlinks requires
+ // a temp-link dance.
+ return ostree_break_hardlink (dfd, path, FALSE, NULL, error);
+ }
+}
+
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;
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;
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)
- {
- (void)close (fd);
- return -errno;
- }
-
- gboolean did_copyup;
- int r = verify_write_or_copyup (path, &stbuf, &did_copyup);
- if (r != 0)
+ if (statx (basefd, path, AT_SYMLINK_NOFOLLOW | AT_NO_AUTOMOUNT, STATX_BASIC_STATS, &stbuf)
+ == -1)
{
- (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
{
- /* 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;
setup_test_repository "bare"
-echo "1..12"
+echo "1..13"
cd ${test_tmpdir}
mkdir mnt
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}"
+ # And via chmod
+ fsverity enable checkout-test2/nonhardlinked
+ orig_inode=$(stat -c %i checkout-test2/nonhardlinked)
+ chmod 0700 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