return g_strconcat ("state/", checksum, ".commitpartial", NULL);
}
+gboolean
+_ostree_validate_bareuseronly_mode (guint32 mode,
+ const char *checksum,
+ GError **error);
+static inline gboolean
+_ostree_validate_bareuseronly_mode_finfo (GFileInfo *finfo,
+ const char *checksum,
+ GError **error)
+{
+ const guint32 content_mode = g_file_info_get_attribute_uint32 (finfo, "unix::mode");
+ return _ostree_validate_bareuseronly_mode (content_mode, checksum, error);
+}
+
gboolean
_ostree_parse_delta_name (const char *delta_name,
char **out_from,
return TRUE;
}
+/* This bit mirrors similar code in commit_loose_regfile_object() for the
+ * bare-user-only mode. It's opt-in though for all pulls.
+ */
+gboolean
+_ostree_validate_bareuseronly_mode (guint32 content_mode,
+ const char *checksum,
+ GError **error)
+{
+ if (S_ISREG (content_mode))
+ {
+ const guint32 invalid_modebits = ((content_mode & ~S_IFMT) & ~0775);
+ if (invalid_modebits > 0)
+ 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))
+ ; /* Nothing */
+ else
+ g_assert_not_reached ();
+
+ return TRUE;
+}
+
static gboolean
validate_stat_mode_perms (guint32 mode,
GError **error)
}
else if (self->mode == OSTREE_REPO_MODE_BARE_USER_ONLY)
{
- guint32 invalid_modebits = (mode & ~S_IFMT) & ~0775;
- if (invalid_modebits > 0)
- return glnx_throw (error, "Invalid mode 0%04o with bits 0%04o in bare-user-only repository",
- mode, invalid_modebits);
+ if (!_ostree_validate_bareuseronly_mode (mode, checksum, error))
+ return FALSE;
if (!glnx_fchmod (tmpf->fd, mode, error))
return FALSE;
ostree_repo_commit_modifier_ref,
ostree_repo_commit_modifier_unref);
+/* Special case between bare-user and bare-user-only,
+ * mostly for https://github.com/flatpak/flatpak/issues/845
+ * see below for any more comments.
+ */
+static gboolean
+import_is_bareuser_only_conversion (OstreeRepo *src_repo,
+ OstreeRepo *dest_repo,
+ OstreeObjectType objtype)
+{
+ return src_repo->mode == OSTREE_REPO_MODE_BARE_USER
+ && dest_repo->mode == OSTREE_REPO_MODE_BARE_USER_ONLY
+ && objtype == OSTREE_OBJECT_TYPE_FILE;
+}
+
+/* Returns TRUE if we can potentially just call link() to copy an object. */
+static gboolean
+import_via_hardlink_is_possible (OstreeRepo *src_repo,
+ OstreeRepo *dest_repo,
+ OstreeObjectType objtype)
+{
+ /* We need the ability to make hardlinks */
+ if (src_repo->owner_uid != dest_repo->owner_uid)
+ return FALSE;
+ /* Equal modes are always compatible */
+ if (src_repo->mode == dest_repo->mode)
+ return TRUE;
+ /* Metadata is identical between all modes */
+ if (OSTREE_OBJECT_TYPE_IS_META (objtype))
+ return TRUE;
+ /* And now a special case between bare-user and bare-user-only,
+ * mostly for https://github.com/flatpak/flatpak/issues/845
+ */
+ if (import_is_bareuser_only_conversion (src_repo, dest_repo, objtype))
+ return TRUE;
+ return FALSE;
+}
+
+/* Copy the detached metadata for commit @checksum from @source repo
+ * to @self.
+ */
+static gboolean
+copy_detached_metadata (OstreeRepo *self,
+ OstreeRepo *source,
+ const char *checksum,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GVariant) detached_meta = NULL;
+ if (!ostree_repo_read_commit_detached_metadata (source,
+ checksum, &detached_meta,
+ cancellable, error))
+ return FALSE;
+
+ if (detached_meta)
+ {
+ if (!ostree_repo_write_commit_detached_metadata (self,
+ checksum, detached_meta,
+ cancellable, error))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Try to import an object by just calling linkat(); returns
+ * a value in @out_was_supported if we were able to do it or not.
+ */
+static gboolean
+import_one_object_link (OstreeRepo *self,
+ OstreeRepo *source,
+ const char *checksum,
+ OstreeObjectType objtype,
+ gboolean *out_was_supported,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const char *errprefix = glnx_strjoina ("Importing ", checksum, ".",
+ ostree_object_type_to_string (objtype));
+ GLNX_AUTO_PREFIX_ERROR (errprefix, error);
+ char loose_path_buf[_OSTREE_LOOSE_PATH_MAX];
+ _ostree_loose_path (loose_path_buf, checksum, objtype, self->mode);
+
+ /* Hardlinking between bare-user → bare-user-only is only possible for regular
+ * files, *not* symlinks, which in bare-user are stored as regular files. At
+ * this point we need to parse the file to see the difference.
+ */
+ if (import_is_bareuser_only_conversion (source, self, objtype))
+ {
+ struct stat stbuf;
+
+ if (!_ostree_repo_load_file_bare (source, checksum, NULL, &stbuf,
+ NULL, NULL, cancellable, error))
+ return FALSE;
+
+ if (S_ISREG (stbuf.st_mode))
+ {
+ /* This is OK, we'll drop through and try a hardlink */
+ }
+ else if (S_ISLNK (stbuf.st_mode))
+ {
+ /* NOTE early return */
+ *out_was_supported = FALSE;
+ return TRUE;
+ }
+ else
+ g_assert_not_reached ();
+ }
+
+ if (!_ostree_repo_ensure_loose_objdir_at (self->objects_dir_fd, loose_path_buf, cancellable, error))
+ return FALSE;
+
+ *out_was_supported = TRUE;
+ if (linkat (source->objects_dir_fd, loose_path_buf, self->objects_dir_fd, loose_path_buf, 0) != 0)
+ {
+ if (errno == EEXIST)
+ return TRUE;
+ else if (errno == EMLINK || errno == EXDEV || errno == EPERM)
+ {
+ /* EMLINK, EXDEV and EPERM shouldn't be fatal; we just can't do the
+ * optimization of hardlinking instead of copying.
+ */
+ *out_was_supported = FALSE;
+ return TRUE;
+ }
+ else
+ return glnx_throw_errno_prefix (error, "linkat");
+ }
+
+ if (objtype == OSTREE_OBJECT_TYPE_COMMIT)
+ {
+ if (!copy_detached_metadata (self, source, checksum, cancellable, error))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* A version of ostree_repo_import_object_from_with_trust()
+ * with flags; may make this public API later.
+ */
+gboolean
+_ostree_repo_import_object (OstreeRepo *self,
+ OstreeRepo *source,
+ OstreeObjectType objtype,
+ const char *checksum,
+ OstreeRepoImportFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const gboolean trusted = (flags & _OSTREE_REPO_IMPORT_FLAGS_TRUSTED) > 0;
+ /* Implements OSTREE_REPO_PULL_FLAGS_BAREUSERONLY_FILES which was designed for flatpak */
+ const gboolean verify_bareuseronly = (flags & _OSTREE_REPO_IMPORT_FLAGS_VERIFY_BAREUSERONLY) > 0;
+
+ /* If we need to do bareuseronly verification, let's dispense with that
+ * first so we don't complicate the rest of the code below.
+ */
+ if (verify_bareuseronly && !OSTREE_OBJECT_TYPE_IS_META (objtype))
+ {
+ g_autoptr(GFileInfo) src_finfo = NULL;
+ if (!ostree_repo_load_file (source, checksum,
+ NULL, &src_finfo, NULL,
+ cancellable, error))
+ return FALSE;
+
+ if (!_ostree_validate_bareuseronly_mode_finfo (src_finfo, checksum, error))
+ return FALSE;
+ }
+
+ /* We try to import via hardlink. If the remote is explicitly not trusted
+ * (i.e.) their checksums may be incorrect, we skip that. Also, we require the
+ * repository modes to match, as well as the owner uid (since we need to be
+ * able to make hardlinks).
+ */
+ if (trusted && import_via_hardlink_is_possible (source, self, objtype))
+ {
+ gboolean hardlink_was_supported = FALSE;
+
+ if (!import_one_object_link (self, source, checksum, objtype,
+ &hardlink_was_supported,
+ cancellable, error))
+ return FALSE;
+
+ /* If we hardlinked, we're done! */
+ if (hardlink_was_supported)
+ return TRUE;
+ }
+
+ /* The copy path */
+
+ /* First, do we have the object already? */
+ gboolean has_object;
+ if (!ostree_repo_has_object (self, objtype, checksum, &has_object,
+ cancellable, error))
+ return FALSE;
+ /* If we have it, we're done */
+ if (has_object)
+ return TRUE;
+
+ if (OSTREE_OBJECT_TYPE_IS_META (objtype))
+ {
+ /* Metadata object */
+ g_autoptr(GVariant) variant = NULL;
+
+ if (objtype == OSTREE_OBJECT_TYPE_COMMIT)
+ {
+ /* FIXME - cleanup detached metadata if copy below fails */
+ if (!copy_detached_metadata (self, source, checksum, cancellable, error))
+ return FALSE;
+ }
+
+ if (!ostree_repo_load_variant (source, objtype, checksum,
+ &variant, error))
+ return FALSE;
+
+ g_autofree guchar *real_csum = NULL;
+ if (!ostree_repo_write_metadata (self, objtype,
+ checksum, variant,
+ trusted ? NULL : &real_csum,
+ cancellable, error))
+ return FALSE;
+ }
+ else
+ {
+ /* Content object */
+ guint64 length;
+ g_autoptr(GInputStream) object_stream = NULL;
+
+ if (!ostree_repo_load_object_stream (source, objtype, checksum,
+ &object_stream, &length,
+ cancellable, error))
+ return FALSE;
+
+ g_autofree guchar *real_csum = NULL;
+ if (!ostree_repo_write_content (self, checksum,
+ object_stream, length,
+ trusted ? NULL : &real_csum,
+ cancellable, error))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
static OstreeRepoTransactionStats *
ostree_repo_transaction_stats_copy (OstreeRepoTransactionStats *stats)
{
GCancellable *cancellable,
GError **error);
+typedef enum {
+ _OSTREE_REPO_IMPORT_FLAGS_NONE,
+ _OSTREE_REPO_IMPORT_FLAGS_TRUSTED,
+ _OSTREE_REPO_IMPORT_FLAGS_VERIFY_BAREUSERONLY,
+} OstreeRepoImportFlags;
+
+gboolean
+_ostree_repo_import_object (OstreeRepo *self,
+ OstreeRepo *source,
+ OstreeObjectType objtype,
+ const char *checksum,
+ OstreeRepoImportFlags flags,
+ GCancellable *cancellable,
+ GError **error);
+
gboolean
_ostree_repo_commit_tmpf_final (OstreeRepo *self,
const char *checksum,
return FALSE;
}
-/* This bit mirrors similar code in commit_loose_content_object() for the
- * bare-user-only mode. It's opt-in though for all pulls.
- */
-static gboolean
-validate_bareuseronly_mode (OtPullData *pull_data,
- const char *checksum,
- guint32 content_mode,
- GError **error)
-{
- if (!pull_data->is_bareuseronly_files)
- return TRUE;
-
- if (S_ISREG (content_mode))
- {
- const guint32 invalid_modebits = ((content_mode & ~S_IFMT) & ~0775);
- if (invalid_modebits > 0)
- return glnx_throw (error, "object %s.file: invalid mode 0%04o with bits 0%04o",
- checksum, content_mode, invalid_modebits);
- }
- else if (S_ISLNK (content_mode))
- ; /* Nothing */
- else
- g_assert_not_reached ();
-
- return TRUE;
-}
-
/* Synchronously import a single content object; this is used async for content,
* or synchronously for metadata. @src_repo is either
* pull_data->remote_repo_local or one of pull_data->localcache_repos.
GCancellable *cancellable,
GError **error)
{
- const gboolean trusted = !pull_data->is_untrusted;
- if (trusted && !pull_data->is_bareuseronly_files)
- {
- if (!ostree_repo_import_object_from_with_trust (pull_data->repo, src_repo,
- OSTREE_OBJECT_TYPE_FILE, checksum,
- trusted,
- cancellable, error))
- return FALSE;
- }
- else
- {
- /* In this case we either need to validate the checksum
- * or the file mode.
- */
- g_autoptr(GInputStream) content_input = NULL;
- g_autoptr(GFileInfo) content_finfo = NULL;
- g_autoptr(GVariant) content_xattrs = NULL;
-
- if (!ostree_repo_load_file (src_repo, checksum,
- &content_input, &content_finfo, &content_xattrs,
- cancellable, error))
- return FALSE;
-
- if (!validate_bareuseronly_mode (pull_data, checksum,
- g_file_info_get_attribute_uint32 (content_finfo, "unix::mode"),
- error))
- return FALSE;
-
- /* Now that we've potentially validated it, convert to object stream */
- guint64 length;
- g_autoptr(GInputStream) object_stream = NULL;
- if (!ostree_raw_file_to_content_stream (content_input, content_finfo,
- content_xattrs, &object_stream,
- &length, cancellable, error))
- return FALSE;
-
- g_autofree guchar *real_csum = NULL;
- if (!ostree_repo_write_content (pull_data->repo, checksum,
- object_stream, length,
- &real_csum,
- cancellable, error))
- return FALSE;
- }
-
- return TRUE;
+ OstreeRepoImportFlags flags = _OSTREE_REPO_IMPORT_FLAGS_NONE;
+ if (!pull_data->is_untrusted)
+ flags |= _OSTREE_REPO_IMPORT_FLAGS_TRUSTED;
+ if (pull_data->is_bareuseronly_files)
+ flags |= _OSTREE_REPO_IMPORT_FLAGS_VERIFY_BAREUSERONLY;
+ return _ostree_repo_import_object (pull_data->repo, src_repo,
+ OSTREE_OBJECT_TYPE_FILE, checksum,
+ flags, cancellable, error);
}
typedef struct {
*/
ot_cleanup_unlinkat (&tmp_unlinker);
- if (!validate_bareuseronly_mode (pull_data,
- checksum,
- g_file_info_get_attribute_uint32 (file_info, "unix::mode"),
- error))
- goto out;
+ if (pull_data->is_bareuseronly_files)
+ {
+ if (!_ostree_validate_bareuseronly_mode_finfo (file_info, checksum, error))
+ goto out;
+ }
if (!ostree_raw_file_to_content_stream (file_in, file_info, xattrs,
&object_input, &length,
return TRUE;
}
-static gboolean
-copy_detached_metadata (OstreeRepo *self,
- OstreeRepo *source,
- const char *checksum,
- GCancellable *cancellable,
- GError **error)
-{
- g_autoptr(GVariant) detached_meta = NULL;
- if (!ostree_repo_read_commit_detached_metadata (source,
- checksum, &detached_meta,
- cancellable, error))
- return FALSE;
-
- if (detached_meta)
- {
- if (!ostree_repo_write_commit_detached_metadata (self,
- checksum, detached_meta,
- cancellable, error))
- return FALSE;
- }
-
- return TRUE;
-}
-
-/* Special case between bare-user and bare-user-only,
- * mostly for https://github.com/flatpak/flatpak/issues/845
- * see below for any more comments.
- */
-static gboolean
-import_is_bareuser_only_conversion (OstreeRepo *src_repo,
- OstreeRepo *dest_repo,
- OstreeObjectType objtype)
-{
- return src_repo->mode == OSTREE_REPO_MODE_BARE_USER
- && dest_repo->mode == OSTREE_REPO_MODE_BARE_USER_ONLY
- && objtype == OSTREE_OBJECT_TYPE_FILE;
-}
-
-static gboolean
-import_one_object_link (OstreeRepo *self,
- OstreeRepo *source,
- const char *checksum,
- OstreeObjectType objtype,
- gboolean *out_was_supported,
- GCancellable *cancellable,
- GError **error)
-{
- const char *errprefix = glnx_strjoina ("Importing ", checksum, ".",
- ostree_object_type_to_string (objtype));
- GLNX_AUTO_PREFIX_ERROR (errprefix, error);
- char loose_path_buf[_OSTREE_LOOSE_PATH_MAX];
- _ostree_loose_path (loose_path_buf, checksum, objtype, self->mode);
-
- /* Hardlinking between bare-user → bare-user-only is only possible for regular
- * files, *not* symlinks, which in bare-user are stored as regular files. At
- * this point we need to parse the file to see the difference.
- */
- if (import_is_bareuser_only_conversion (source, self, objtype))
- {
- struct stat stbuf;
-
- if (!_ostree_repo_load_file_bare (source, checksum, NULL, &stbuf,
- NULL, NULL, cancellable, error))
- return FALSE;
-
- if (S_ISREG (stbuf.st_mode))
- {
- /* This is OK, we'll drop through and try a hardlink */
- }
- else if (S_ISLNK (stbuf.st_mode))
- {
- /* NOTE early return */
- *out_was_supported = FALSE;
- return TRUE;
- }
- else
- g_assert_not_reached ();
- }
-
- if (!_ostree_repo_ensure_loose_objdir_at (self->objects_dir_fd, loose_path_buf, cancellable, error))
- return FALSE;
-
- *out_was_supported = TRUE;
- if (linkat (source->objects_dir_fd, loose_path_buf, self->objects_dir_fd, loose_path_buf, 0) != 0)
- {
- if (errno == EEXIST)
- return TRUE;
- else if (errno == EMLINK || errno == EXDEV || errno == EPERM)
- {
- /* EMLINK, EXDEV and EPERM shouldn't be fatal; we just can't do the
- * optimization of hardlinking instead of copying.
- */
- *out_was_supported = FALSE;
- return TRUE;
- }
- else
- return glnx_throw_errno_prefix (error, "linkat");
- }
-
- if (objtype == OSTREE_OBJECT_TYPE_COMMIT)
- {
- if (!copy_detached_metadata (self, source, checksum, cancellable, error))
- return FALSE;
- }
-
- return TRUE;
-}
-
/**
* ostree_repo_import_object_from:
* @self: Destination repo
checksum, TRUE, cancellable, error);
}
-static gboolean
-import_via_hardlink_is_possible (OstreeRepo *src_repo,
- OstreeRepo *dest_repo,
- OstreeObjectType objtype)
-{
- /* We need the ability to make hardlinks */
- if (src_repo->owner_uid != dest_repo->owner_uid)
- return FALSE;
- /* Equal modes are always compatible */
- if (src_repo->mode == dest_repo->mode)
- return TRUE;
- /* Metadata is identical between all modes */
- if (OSTREE_OBJECT_TYPE_IS_META (objtype))
- return TRUE;
- /* And now a special case between bare-user and bare-user-only,
- * mostly for https://github.com/flatpak/flatpak/issues/845
- */
- if (import_is_bareuser_only_conversion (src_repo, dest_repo, objtype))
- return TRUE;
- return FALSE;
-}
-
/**
* ostree_repo_import_object_from_with_trust:
* @self: Destination repo
GCancellable *cancellable,
GError **error)
{
- /* We try to import via hardlink. If the remote is explicitly not trusted
- * (i.e.) their checksums may be incorrect, we skip that. Also, we require the
- * repository modes to match, as well as the owner uid (since we need to be
- * able to make hardlinks).
- */
- if (trusted && import_via_hardlink_is_possible (source, self, objtype))
- {
- gboolean hardlink_was_supported = FALSE;
-
- if (!import_one_object_link (self, source, checksum, objtype,
- &hardlink_was_supported,
- cancellable, error))
- return FALSE;
-
- /* If we hardlinked, we're done! */
- if (hardlink_was_supported)
- return TRUE;
- }
-
- /* The copy path */
-
- /* First, do we have the object already? */
- gboolean has_object;
- if (!ostree_repo_has_object (self, objtype, checksum, &has_object,
- cancellable, error))
- return FALSE;
- /* If we have it, we're done */
- if (has_object)
- return TRUE;
-
- if (OSTREE_OBJECT_TYPE_IS_META (objtype))
- {
- /* Metadata object */
- g_autoptr(GVariant) variant = NULL;
-
- if (objtype == OSTREE_OBJECT_TYPE_COMMIT)
- {
- /* FIXME - cleanup detached metadata if copy below fails */
- if (!copy_detached_metadata (self, source, checksum, cancellable, error))
- return FALSE;
- }
-
- if (!ostree_repo_load_variant (source, objtype, checksum,
- &variant, error))
- return FALSE;
-
- g_autofree guchar *real_csum = NULL;
- if (!ostree_repo_write_metadata (self, objtype,
- checksum, variant,
- trusted ? NULL : &real_csum,
- cancellable, error))
- return FALSE;
- }
- else
- {
- /* Content object */
- guint64 length;
- g_autoptr(GInputStream) object_stream = NULL;
-
- if (!ostree_repo_load_object_stream (source, objtype, checksum,
- &object_stream, &length,
- cancellable, error))
- return FALSE;
-
- g_autofree guchar *real_csum = NULL;
- if (!ostree_repo_write_content (self, checksum,
- object_stream, length,
- trusted ? NULL : &real_csum,
- cancellable, error))
- return FALSE;
- }
-
- return TRUE;
+ /* This just wraps a currently internal API, may make it public later */
+ OstreeRepoImportFlags flags = trusted ? _OSTREE_REPO_IMPORT_FLAGS_TRUSTED : 0;
+ return _ostree_repo_import_object (self, source, objtype, checksum,
+ flags, cancellable, error);
}
-
/**
* ostree_repo_query_object_storage_size:
* @self: Repo
if $CMD_PREFIX ostree pull-local --repo=repo --bareuseronly-files repo-input content-with-suid 2>err.txt; then
assert_not_reached "copying suid file with --bareuseronly-files worked?"
fi
-assert_file_has_content err.txt 'object.*\.file: invalid mode.*with bits 040.*'
+assert_file_has_content err.txt 'Content object.*: invalid mode.*with bits 040.*'
echo "ok pull-local (bareuseronly files)"
if ! skip_one_without_user_xattrs; then
if ${CMD_PREFIX} ostree --repo=mirrorrepo pull ${flag} --bareuseronly-files origin content-with-suid 2>err.txt; then
assert_not_reached "pulled unsafe bareuseronly"
fi
- assert_file_has_content err.txt 'object.*\.file: invalid mode.*with bits 040.*'
+ assert_file_has_content err.txt 'Content object.*: invalid mode.*with bits 040.*'
done
echo "ok pull (bareuseronly, unsafe)"
if $CMD_PREFIX ostree pull-local --repo=repo repo-input 2>err.txt; then
assert_not_reached "copying suid file into bare-user worked?"
fi
-assert_file_has_content err.txt "Invalid mode.*with bits 040.*in bare-user-only"
+assert_file_has_content err.txt "Content object.*invalid mode.*with bits 040.*"
echo "ok failed to commit suid"
cd ${test_tmpdir}