diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c index 56b381d918..44ca8abf08 100644 --- a/src/libostree/ostree-core.c +++ b/src/libostree/ostree-core.c @@ -2014,7 +2014,7 @@ file_header_parse (GVariant *metadata, mode = GUINT32_FROM_BE (mode); g_autoptr(GFileInfo) ret_file_info = _ostree_mode_uidgid_to_gfileinfo (mode, uid, gid); - if (S_ISREG (mode)) + if (S_ISREG (mode) || S_ISCHR(mode)) { ; } @@ -2065,7 +2065,7 @@ zlib_file_header_parse (GVariant *metadata, g_autoptr(GFileInfo) ret_file_info = _ostree_mode_uidgid_to_gfileinfo (mode, uid, gid); g_file_info_set_size (ret_file_info, GUINT64_FROM_BE (size)); - if (S_ISREG (mode)) + if (S_ISREG (mode) || S_ISCHR (mode)) { ; } @@ -2368,7 +2368,7 @@ _ostree_validate_bareuseronly_mode (guint32 content_mode, return glnx_throw (error, "Content object %s: invalid mode 0%04o with bits 0%04o", checksum, content_mode, invalid_modebits); } - else if (S_ISLNK (content_mode)) + else if (S_ISLNK (content_mode) || S_ISCHR(content_mode)) ; /* Nothing */ else g_assert_not_reached (); @@ -2400,7 +2400,7 @@ gboolean ostree_validate_structureof_file_mode (guint32 mode, GError **error) { - if (!(S_ISREG (mode) || S_ISLNK (mode))) + if (!(S_ISREG (mode) || S_ISLNK (mode) || S_ISCHR(mode))) return glnx_throw (error, "Invalid file metadata mode %u; not a valid file type", mode); if (!validate_stat_mode_perms (mode, error)) diff --git a/src/libostree/ostree-repo-checkout.c b/src/libostree/ostree-repo-checkout.c index 663292a98f..ed271781e4 100644 --- a/src/libostree/ostree-repo-checkout.c +++ b/src/libostree/ostree-repo-checkout.c @@ -398,6 +398,27 @@ create_file_copy_from_input_at (OstreeRepo *repo, error)) return FALSE; } + else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_SPECIAL) + { + guint32 file_mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); + g_assert(S_ISCHR(file_mode)); + + if (mknodat(destination_dfd, destination_name, file_mode, (dev_t)0) < 0) + return glnx_throw_errno_prefix (error, "Creating whiteout char device"); + if (options->mode != OSTREE_REPO_CHECKOUT_MODE_USER) + { + if (xattrs != NULL && + !glnx_dfd_name_set_all_xattrs(destination_dfd, destination_name, xattrs, + cancellable, error)) + return glnx_throw_errno_prefix (error, "Setting xattrs for whiteout char device"); + + if (TEMP_FAILURE_RETRY(fchownat(destination_dfd, destination_name, + g_file_info_get_attribute_uint32 (file_info, "unix::uid"), + g_file_info_get_attribute_uint32 (file_info, "unix::gid"), + AT_SYMLINK_NOFOLLOW) < 0)) + return glnx_throw_errno_prefix (error, "fchownat"); + } + } else g_assert_not_reached (); diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index c22864cd06..1bcd6d3e5a 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -213,6 +213,37 @@ _ostree_repo_commit_tmpf_final (OstreeRepo *self, return TRUE; } +gboolean +_ostree_repo_commit_bare_whiteout (OstreeRepo *self, + const char *checksum, + guint32 uid, + guint32 gid, + GVariant *xattrs, + GCancellable *cancellable, + GError **error) +{ + g_assert(self->mode == OSTREE_REPO_MODE_BARE); + + char dest_filename[_OSTREE_LOOSE_PATH_MAX]; + _ostree_loose_path (dest_filename, checksum, OSTREE_OBJECT_TYPE_FILE, self->mode); + + int dest_dfd = commit_dest_dfd (self); + if (!_ostree_repo_ensure_loose_objdir_at (dest_dfd, dest_filename, cancellable, error)) + return FALSE; + + if (mknodat(dest_dfd, dest_filename, S_IFCHR, (dev_t)0) < 0) + return glnx_throw_errno_prefix (error, "Creating whiteout char device"); + + if (xattrs != NULL && + !glnx_dfd_name_set_all_xattrs(dest_dfd, dest_filename, xattrs, cancellable, error)) + return glnx_throw_errno_prefix (error, "Setting xattrs for whiteout char device"); + + if (TEMP_FAILURE_RETRY(fchownat(dest_dfd, dest_filename, uid, gid, AT_SYMLINK_NOFOLLOW) < 0)) + return glnx_throw_errno_prefix (error, "fchownat"); + + return TRUE; +} + /* Given a dfd+path combination (may be regular file or symlink), * rename it into place. */ @@ -301,7 +332,7 @@ commit_loose_regfile_object (OstreeRepo *self, return FALSE; } else - g_assert (S_ISLNK (mode)); + g_assert (S_ISLNK (mode) || S_ISCHR(mode)); } else if (self->mode == OSTREE_REPO_MODE_BARE_USER_ONLY) { @@ -966,7 +997,9 @@ write_content_object (OstreeRepo *self, else file_input = input; + const guint32 mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); gboolean phys_object_is_symlink = FALSE; + gboolean phys_object_is_whiteout = FALSE; switch (object_file_type) { case G_FILE_TYPE_REGULAR: @@ -975,6 +1008,19 @@ write_content_object (OstreeRepo *self, if (self->mode == OSTREE_REPO_MODE_BARE || self->mode == OSTREE_REPO_MODE_BARE_USER_ONLY) phys_object_is_symlink = TRUE; break; + case G_FILE_TYPE_SPECIAL: + /* Only overlayfs whiteout char 0:0 files are supported for G_FILE_TYPE_SPECIAL */ + if (S_ISCHR(mode)) + { + /* For bare mode repositories where no side file metadata is stored we want to + * avoid the creation of an empty tmp file and later setup of permissions because + * this won't result in a char device. + */ + if (self->mode == OSTREE_REPO_MODE_BARE) + phys_object_is_whiteout = TRUE; + break; + } + return glnx_throw (error, "Unsupported file type %u with mode 0%o", object_file_type, mode); default: return glnx_throw (error, "Unsupported file type %u", object_file_type); } @@ -1021,7 +1067,8 @@ write_content_object (OstreeRepo *self, * binary with trailing garbage, creating a window on the local * system where a malicious setuid binary exists. * - * We use GLnxTmpfile for regular files, and OtCleanupUnlinkat for symlinks. + * We use GLnxTmpfile for regular files, OtCleanupUnlinkat for symlinks, + * we use no temporary for whiteout char devices in bare mode. */ g_auto(OtCleanupUnlinkat) tmp_unlinker = { commit_tmp_dfd (self), NULL }; g_auto(GLnxTmpfile) tmpf = { 0, }; @@ -1037,6 +1084,8 @@ write_content_object (OstreeRepo *self, cancellable, error)) return FALSE; } + else if (phys_object_is_whiteout) + ; else if (repo_mode != OSTREE_REPO_MODE_ARCHIVE) { if (!create_regular_tmpfile_linkable_with_content (self, size, file_input, @@ -1079,11 +1128,16 @@ write_content_object (OstreeRepo *self, unpacked_size = g_file_info_get_size (file_info); } - else + else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK) { /* For a symlink, the size is the length of the target */ unpacked_size = strlen (g_file_info_get_symlink_target (file_info)); } + else + { + /* For char devices 0:0 whiteouts the content size is 0 */ + unpacked_size = 0; + } if (!g_output_stream_flush (temp_out, cancellable, error)) return FALSE; @@ -1151,7 +1205,6 @@ write_content_object (OstreeRepo *self, const guint32 uid = g_file_info_get_attribute_uint32 (file_info, "unix::uid"); const guint32 gid = g_file_info_get_attribute_uint32 (file_info, "unix::gid"); - const guint32 mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); /* Is it "physically" a symlink? */ if (phys_object_is_symlink) { @@ -1189,6 +1242,13 @@ write_content_object (OstreeRepo *self, &tmp_unlinker, cancellable, error)) return FALSE; } + else if (phys_object_is_whiteout) + { + if (!_ostree_repo_commit_bare_whiteout (self, actual_checksum, + uid, gid, xattrs, + cancellable, error)) + return FALSE; + } else { /* Check if a file with the same payload is present in the repository, @@ -3739,6 +3799,7 @@ write_content_to_mtree_internal (OstreeRepo *self, { case G_FILE_TYPE_SYMBOLIC_LINK: case G_FILE_TYPE_REGULAR: + case G_FILE_TYPE_SPECIAL: break; default: return glnx_throw (error, "Unsupported file type for file: '%s'", child_relpath); @@ -4090,7 +4151,8 @@ write_dfd_iter_to_mtree_internal (OstreeRepo *self, continue; } - if (S_ISREG (stbuf.st_mode)) + /* For regular files and whiteout char devices we continue */ + if (S_ISREG (stbuf.st_mode) || (S_ISCHR(stbuf.st_mode) && stbuf.st_rdev == 0)) ; else if (S_ISLNK (stbuf.st_mode)) { diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index 0d33f7c2d0..fad94ae4bd 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -414,6 +414,15 @@ _ostree_repo_commit_tmpf_final (OstreeRepo *self, GCancellable *cancellable, GError **error); +gboolean +_ostree_repo_commit_bare_whiteout (OstreeRepo *self, + const char *checksum, + guint32 uid, + guint32 gid, + GVariant *xattrs, + GCancellable *cancellable, + GError **error); + typedef struct { gboolean initialized; gpointer opaque0[10]; diff --git a/src/libostree/ostree-repo-static-delta-compilation.c b/src/libostree/ostree-repo-static-delta-compilation.c index 28b421395d..c352c20991 100644 --- a/src/libostree/ostree-repo-static-delta-compilation.c +++ b/src/libostree/ostree-repo-static-delta-compilation.c @@ -516,7 +516,7 @@ process_one_object (OstreeRepo *repo, } else { - g_assert (S_ISREG (mode)); + g_assert (S_ISREG (mode) || S_ISCHR(mode)); } content_offset = current_part->payload->len; diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 90cde65139..932eef4fac 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -4322,8 +4322,9 @@ _ostree_repo_load_file_bare (OstreeRepo *self, return glnx_throw_errno_prefix (error, "openat"); } - if (!(S_ISREG (stbuf.st_mode) || S_ISLNK (stbuf.st_mode))) - return glnx_throw (error, "Not a regular file or symlink"); + if (!(S_ISREG (stbuf.st_mode) || S_ISLNK (stbuf.st_mode) || + (S_ISCHR (stbuf.st_mode) && stbuf.st_rdev == 0))) + return glnx_throw (error, "Not a regular file, symlink or whiteout"); /* In the non-bare-user case, gather symlink info if requested */ if (self->mode != OSTREE_REPO_MODE_BARE_USER @@ -4474,7 +4475,7 @@ ostree_repo_load_file (OstreeRepo *self, if (S_ISLNK (stbuf.st_mode)) g_file_info_set_symlink_target (*out_file_info, symlink_target); else - g_assert (S_ISREG (stbuf.st_mode)); + g_assert (S_ISREG (stbuf.st_mode) || (S_ISCHR(stbuf.st_mode) && stbuf.st_rdev == 0)); } ot_transfer_out_value (out_xattrs, &ret_xattrs);