Skip to content

Commit

Permalink
lib: Move bareuseronly verification into commit/core
Browse files Browse the repository at this point in the history
Conceptually `ostree-repo-pull.c` should be be written using
just public APIs; we theoretically support building without HTTP
for people who just want to use the object store portion and
do their own fetching.

We have some nontrivial behaviors in the pull layer though; one
of those is the "bareuseronly" verification.  Make a new internal
API that accepts flags, move it into `commit.c`.  This
is prep for further work in changing object import to support
reflinks.

Closes: #1193
Approved by: jlebon
  • Loading branch information
cgwalters authored and rh-atomic-bot committed Sep 21, 2017
1 parent d4c7093 commit 160864d
Show file tree
Hide file tree
Showing 9 changed files with 316 additions and 288 deletions.
13 changes: 13 additions & 0 deletions src/libostree/ostree-core-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,19 @@ static inline char * _ostree_get_commitpartial_path (const char *checksum)
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,
Expand Down
23 changes: 23 additions & 0 deletions src/libostree/ostree-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -1998,6 +1998,29 @@ ostree_validate_structureof_dirtree (GVariant *dirtree,
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)
Expand Down
249 changes: 245 additions & 4 deletions src/libostree/ostree-repo-commit.c
Original file line number Diff line number Diff line change
Expand Up @@ -262,10 +262,8 @@ commit_loose_regfile_object (OstreeRepo *self,
}
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;
Expand Down Expand Up @@ -3171,6 +3169,249 @@ G_DEFINE_BOXED_TYPE(OstreeRepoCommitModifier, ostree_repo_commit_modifier,
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)
{
Expand Down
15 changes: 15 additions & 0 deletions src/libostree/ostree-repo-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,21 @@ _ostree_repo_verify_commit_internal (OstreeRepo *self,
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,
Expand Down
Loading

0 comments on commit 160864d

Please sign in to comment.