New upstream version 2020.2
This commit is contained in:
commit
08d4ffe725
71 files changed
+2382
-331
No files matched your search
@@ -22,12 +22,12 @@ DefaultDependencies=no
|
||||
ConditionKernelCommandLine=ostree
|
||||
OnFailure=emergency.target
|
||||
Conflicts=umount.target
|
||||
After=-.mount
|
||||
# Run after core mounts
|
||||
After=-.mount var.mount
|
||||
After=systemd-remount-fs.service
|
||||
# But we run *before* most other core bootup services that need write access to /etc and /var
|
||||
Before=local-fs.target umount.target
|
||||
# Other early boot units that need to write to /var
|
||||
Before=systemd-random-seed.service plymouth-read-write.service systemd-journal-flush.service
|
||||
# tmpfiles.d usually needs write access to a few places
|
||||
Before=systemd-tmpfiles-setup.service
|
||||
|
||||
[Service]
|
||||
|
||||
@@ -18,15 +18,17 @@
|
||||
***/
|
||||
|
||||
/* Add new symbols here. Release commits should copy this section into -released.sym. */
|
||||
LIBOSTREE_2019.7 {
|
||||
} LIBOSTREE_2019.6;
|
||||
LIBOSTREE_2020.2 {
|
||||
global:
|
||||
someostree_symbol_deleteme;
|
||||
} LIBOSTREE_2020.1;
|
||||
|
||||
/* Stub section for the stable release *after* this development one; don't
|
||||
* edit this other than to update the year. This is just a copy/paste
|
||||
* source. Replace $LASTSTABLE with the last stable version, and $NEWVERSION
|
||||
* with whatever the next version with new symbols will be.
|
||||
LIBOSTREE_2019.$NEWVERSION {
|
||||
LIBOSTREE_2020.$NEWVERSION {
|
||||
global:
|
||||
someostree_symbol_deleteme;
|
||||
} LIBOSTREE_2019.$LASTSTABLE;
|
||||
} LIBOSTREE_2020.$LASTSTABLE;
|
||||
*/
|
||||
@@ -581,6 +581,20 @@ LIBOSTREE_2019.6 {
|
||||
ostree_async_progress_copy_state;
|
||||
} LIBOSTREE_2019.4;
|
||||
|
||||
LIBOSTREE_2020.1 {
|
||||
global:
|
||||
ostree_commit_get_object_sizes;
|
||||
ostree_commit_sizes_entry_copy;
|
||||
ostree_commit_sizes_entry_free;
|
||||
ostree_commit_sizes_entry_get_type;
|
||||
ostree_commit_sizes_entry_new;
|
||||
ostree_sysroot_initialize;
|
||||
ostree_sysroot_is_booted;
|
||||
ostree_sysroot_set_mount_namespace_in_use;
|
||||
} LIBOSTREE_2019.6;
|
||||
|
||||
/* No new symbols in 2020.2 */
|
||||
|
||||
/* NOTE: Only add more content here in release commits! See the
|
||||
* comments at the top of this file.
|
||||
*/
|
||||
@@ -49,6 +49,7 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoDevInoCache, ostree_repo_devino_cache_u
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeAsyncProgress, g_object_unref)
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeBootconfigParser, g_object_unref)
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeCommitSizesEntry, ostree_commit_sizes_entry_free)
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeDeployment, g_object_unref)
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeGpgVerifyResult, g_object_unref)
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeKernelArgs, ostree_kernel_args_free)
|
||||
|
||||
@@ -30,6 +30,12 @@ G_BEGIN_DECLS
|
||||
/* It's what gzip does, 9 is too slow */
|
||||
#define OSTREE_ARCHIVE_DEFAULT_COMPRESSION_LEVEL (6)
|
||||
|
||||
/* Note the permissive group bits. We want to be liberal here and let individual machines
|
||||
* narrow permissions as needed via umask. This is important in setups where group ownership
|
||||
* can matter for repo management (like OpenShift). */
|
||||
#define DEFAULT_DIRECTORY_MODE 0775
|
||||
#define DEFAULT_REGFILE_MODE 0660
|
||||
|
||||
/* This file contains private implementation data format definitions
|
||||
* read by multiple implementation .c files.
|
||||
*/
|
||||
@@ -102,6 +108,9 @@ _ostree_checksum_inplace_from_bytes_v (GVariant *csum_v, char *buf)
|
||||
*/
|
||||
#define _OSTREE_LOOSE_PATH_MAX (256)
|
||||
|
||||
/* GVariant format for ostree.sizes metadata entries. */
|
||||
#define _OSTREE_OBJECT_SIZES_ENTRY_SIGNATURE "ay"
|
||||
|
||||
char *
|
||||
_ostree_get_relative_object_path (const char *checksum,
|
||||
OstreeObjectType type,
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "ostree.h"
|
||||
#include "ostree-core-private.h"
|
||||
#include "ostree-chain-input-stream.h"
|
||||
#include "ostree-varint.h"
|
||||
#include "otutil.h"
|
||||
|
||||
/* Generic ABI checks */
|
||||
@@ -2430,6 +2431,187 @@ ostree_commit_get_content_checksum (GVariant *commit_variant)
|
||||
return g_strdup (hexdigest);
|
||||
}
|
||||
|
||||
G_DEFINE_BOXED_TYPE (OstreeCommitSizesEntry, ostree_commit_sizes_entry,
|
||||
ostree_commit_sizes_entry_copy, ostree_commit_sizes_entry_free)
|
||||
|
||||
/**
|
||||
* ostree_commit_sizes_entry_new:
|
||||
* @checksum: (not nullable): object checksum
|
||||
* @objtype: object type
|
||||
* @unpacked: unpacked object size
|
||||
* @archived: compressed object size
|
||||
*
|
||||
* Create a new #OstreeCommitSizesEntry for representing an object in a
|
||||
* commit's "ostree.sizes" metadata.
|
||||
*
|
||||
* Returns: (transfer full) (nullable): a new #OstreeCommitSizesEntry
|
||||
* Since: 2020.1
|
||||
*/
|
||||
OstreeCommitSizesEntry *
|
||||
ostree_commit_sizes_entry_new (const gchar *checksum,
|
||||
OstreeObjectType objtype,
|
||||
guint64 unpacked,
|
||||
guint64 archived)
|
||||
{
|
||||
g_return_val_if_fail (checksum == NULL || ostree_validate_checksum_string (checksum, NULL), NULL);
|
||||
|
||||
g_autoptr(OstreeCommitSizesEntry) entry = g_new0 (OstreeCommitSizesEntry, 1);
|
||||
entry->checksum = g_strdup (checksum);
|
||||
entry->objtype = objtype;
|
||||
entry->unpacked = unpacked;
|
||||
entry->archived = archived;
|
||||
|
||||
return g_steal_pointer (&entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_commit_sizes_entry_copy:
|
||||
* @entry: (not nullable): an #OstreeCommitSizesEntry
|
||||
*
|
||||
* Create a copy of the given @entry.
|
||||
*
|
||||
* Returns: (transfer full) (nullable): a new copy of @entry
|
||||
* Since: 2020.1
|
||||
*/
|
||||
OstreeCommitSizesEntry *
|
||||
ostree_commit_sizes_entry_copy (const OstreeCommitSizesEntry *entry)
|
||||
{
|
||||
g_return_val_if_fail (entry != NULL, NULL);
|
||||
|
||||
return ostree_commit_sizes_entry_new (entry->checksum,
|
||||
entry->objtype,
|
||||
entry->unpacked,
|
||||
entry->archived);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_commit_sizes_entry_free:
|
||||
* @entry: (transfer full): an #OstreeCommitSizesEntry
|
||||
*
|
||||
* Free given @entry.
|
||||
*
|
||||
* Since: 2020.1
|
||||
*/
|
||||
void
|
||||
ostree_commit_sizes_entry_free (OstreeCommitSizesEntry *entry)
|
||||
{
|
||||
g_return_if_fail (entry != NULL);
|
||||
|
||||
g_free (entry->checksum);
|
||||
g_free (entry);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
read_sizes_entry (GVariant *entry,
|
||||
OstreeCommitSizesEntry **out_sizes,
|
||||
GError **error)
|
||||
{
|
||||
gsize entry_size = g_variant_get_size (entry);
|
||||
g_return_val_if_fail (entry_size >= OSTREE_SHA256_DIGEST_LEN + 2, FALSE);
|
||||
|
||||
const guchar *buffer = g_variant_get_data (entry);
|
||||
if (buffer == NULL)
|
||||
return glnx_throw (error, "Could not read ostree.sizes metadata entry");
|
||||
|
||||
char checksum[OSTREE_SHA256_STRING_LEN + 1];
|
||||
ostree_checksum_inplace_from_bytes (buffer, checksum);
|
||||
buffer += OSTREE_SHA256_DIGEST_LEN;
|
||||
entry_size -= OSTREE_SHA256_DIGEST_LEN;
|
||||
|
||||
gsize bytes_read = 0;
|
||||
guint64 archived = 0;
|
||||
if (!_ostree_read_varuint64 (buffer, entry_size, &archived, &bytes_read))
|
||||
return glnx_throw (error, "Unexpected EOF reading ostree.sizes varint");
|
||||
buffer += bytes_read;
|
||||
entry_size -= bytes_read;
|
||||
|
||||
guint64 unpacked = 0;
|
||||
if (!_ostree_read_varuint64 (buffer, entry_size, &unpacked, &bytes_read))
|
||||
return glnx_throw (error, "Unexpected EOF reading ostree.sizes varint");
|
||||
buffer += bytes_read;
|
||||
entry_size -= bytes_read;
|
||||
|
||||
/* On newer commits, an additional byte is used for the object type. */
|
||||
OstreeObjectType objtype;
|
||||
if (entry_size > 0)
|
||||
{
|
||||
objtype = *buffer;
|
||||
if (objtype < OSTREE_OBJECT_TYPE_FILE || objtype > OSTREE_OBJECT_TYPE_LAST)
|
||||
return glnx_throw (error, "Unexpected ostree.sizes object type %u",
|
||||
objtype);
|
||||
buffer++;
|
||||
entry_size--;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Assume the object is a file. */
|
||||
objtype = OSTREE_OBJECT_TYPE_FILE;
|
||||
}
|
||||
|
||||
g_autoptr(OstreeCommitSizesEntry) sizes = ostree_commit_sizes_entry_new (checksum,
|
||||
objtype,
|
||||
unpacked,
|
||||
archived);
|
||||
|
||||
if (out_sizes != NULL)
|
||||
*out_sizes = g_steal_pointer (&sizes);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_commit_get_object_sizes:
|
||||
* @commit_variant: (not nullable): variant of type %OSTREE_OBJECT_TYPE_COMMIT
|
||||
* @out_sizes_entries: (out) (element-type OstreeCommitSizesEntry) (transfer container) (optional):
|
||||
* return location for an array of object size entries
|
||||
* @error: Error
|
||||
*
|
||||
* Reads a commit's "ostree.sizes" metadata and returns an array of
|
||||
* #OstreeCommitSizesEntry in @out_sizes_entries. Each element
|
||||
* represents an object in the commit. If the commit does not contain
|
||||
* the "ostree.sizes" metadata, a %G_IO_ERROR_NOT_FOUND error will be
|
||||
* returned.
|
||||
*
|
||||
* Since: 2020.1
|
||||
*/
|
||||
gboolean
|
||||
ostree_commit_get_object_sizes (GVariant *commit_variant,
|
||||
GPtrArray **out_sizes_entries,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (commit_variant != NULL, FALSE);
|
||||
|
||||
g_autoptr(GVariant) metadata = g_variant_get_child_value (commit_variant, 0);
|
||||
g_autoptr(GVariant) sizes_variant =
|
||||
g_variant_lookup_value (metadata, "ostree.sizes",
|
||||
G_VARIANT_TYPE ("a" _OSTREE_OBJECT_SIZES_ENTRY_SIGNATURE));
|
||||
if (sizes_variant == NULL)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND,
|
||||
"No metadata key ostree.sizes in commit");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_autoptr(GPtrArray) sizes_entries =
|
||||
g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_commit_sizes_entry_free);
|
||||
g_autoptr(GVariant) entry = NULL;
|
||||
GVariantIter entry_iter;
|
||||
g_variant_iter_init (&entry_iter, sizes_variant);
|
||||
while ((entry = g_variant_iter_next_value (&entry_iter)))
|
||||
{
|
||||
OstreeCommitSizesEntry *sizes_entry = NULL;
|
||||
if (!read_sizes_entry (entry, &sizes_entry, error))
|
||||
return FALSE;
|
||||
g_clear_pointer (&entry, g_variant_unref);
|
||||
g_ptr_array_add (sizes_entries, sizes_entry);
|
||||
}
|
||||
|
||||
if (out_sizes_entries != NULL)
|
||||
*out_sizes_entries = g_steal_pointer (&sizes_entries);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Used in pull/deploy to validate we're not being downgraded */
|
||||
gboolean
|
||||
_ostree_compare_timestamps (const char *current_rev,
|
||||
|
||||
@@ -521,6 +521,43 @@ guint64 ostree_commit_get_timestamp (GVariant *commit_variant);
|
||||
_OSTREE_PUBLIC
|
||||
gchar * ostree_commit_get_content_checksum (GVariant *commit_variant);
|
||||
|
||||
/**
|
||||
* OstreeCommitSizesEntry:
|
||||
* @checksum: (not nullable): object checksum
|
||||
* @objtype: object type
|
||||
* @unpacked: unpacked object size
|
||||
* @archived: compressed object size
|
||||
*
|
||||
* Structure representing an entry in the "ostree.sizes" commit metadata. Each
|
||||
* entry corresponds to an object in the associated commit.
|
||||
*
|
||||
* Since: 2020.1
|
||||
*/
|
||||
typedef struct {
|
||||
gchar *checksum;
|
||||
OstreeObjectType objtype;
|
||||
guint64 unpacked;
|
||||
guint64 archived;
|
||||
} OstreeCommitSizesEntry;
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
GType ostree_commit_sizes_entry_get_type (void);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
OstreeCommitSizesEntry *ostree_commit_sizes_entry_new (const gchar *checksum,
|
||||
OstreeObjectType objtype,
|
||||
guint64 unpacked,
|
||||
guint64 archived);
|
||||
_OSTREE_PUBLIC
|
||||
OstreeCommitSizesEntry *ostree_commit_sizes_entry_copy (const OstreeCommitSizesEntry *entry);
|
||||
_OSTREE_PUBLIC
|
||||
void ostree_commit_sizes_entry_free (OstreeCommitSizesEntry *entry);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_commit_get_object_sizes (GVariant *commit_variant,
|
||||
GPtrArray **out_sizes_entries,
|
||||
GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_check_version (guint required_year, guint required_release);
|
||||
|
||||
|
||||
@@ -548,14 +548,10 @@ append_expire_info (GString *output_buffer,
|
||||
gint64 exp_timestamp,
|
||||
gboolean expired)
|
||||
{
|
||||
g_autoptr(GDateTime) date_time_utc = NULL;
|
||||
g_autoptr(GDateTime) date_time_local = NULL;
|
||||
g_autofree char *formatted_date_time = NULL;
|
||||
|
||||
if (line_prefix != NULL)
|
||||
g_string_append (output_buffer, line_prefix);
|
||||
|
||||
date_time_utc = g_date_time_new_from_unix_utc (exp_timestamp);
|
||||
g_autoptr(GDateTime) date_time_utc = g_date_time_new_from_unix_utc (exp_timestamp);
|
||||
if (date_time_utc == NULL)
|
||||
{
|
||||
g_string_append_printf (output_buffer,
|
||||
@@ -565,8 +561,8 @@ append_expire_info (GString *output_buffer,
|
||||
return;
|
||||
}
|
||||
|
||||
date_time_local = g_date_time_to_local (date_time_utc);
|
||||
formatted_date_time = g_date_time_format (date_time_local, "%c");
|
||||
g_autoptr(GDateTime) date_time_local = g_date_time_to_local (date_time_utc);
|
||||
g_autofree char *formatted_date_time = g_date_time_format (date_time_local, "%c");
|
||||
|
||||
if (expired)
|
||||
{
|
||||
@@ -773,8 +769,58 @@ ostree_gpg_verify_result_require_valid_signature (OstreeGpgVerifyResult *result,
|
||||
|
||||
if (ostree_gpg_verify_result_count_valid (result) == 0)
|
||||
{
|
||||
g_set_error (error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_MISSING_KEY,
|
||||
"GPG signatures found, but none are in trusted keyring");
|
||||
/*
|
||||
* Join the description of each failed signature for the error message.
|
||||
* Only one error code can be returned, so if there was more than one
|
||||
* signature, use the error of the last one under the assumption that
|
||||
* it's the most recent and hopefully most likely to be made with a
|
||||
* valid key.
|
||||
*/
|
||||
gint code = OSTREE_GPG_ERROR_NO_SIGNATURE;
|
||||
g_autoptr(GString) buffer = g_string_sized_new (256);
|
||||
guint nsigs = ostree_gpg_verify_result_count_all (result);
|
||||
|
||||
if (nsigs == 0)
|
||||
/* In case an empty result was passed in */
|
||||
g_string_append (buffer, "No GPG signatures found");
|
||||
else
|
||||
{
|
||||
for (int i = nsigs - 1; i >= 0; i--)
|
||||
{
|
||||
g_autoptr(GVariant) info = ostree_gpg_verify_result_get_all (result, i);
|
||||
ostree_gpg_verify_result_describe_variant (info, buffer, "",
|
||||
OSTREE_GPG_SIGNATURE_FORMAT_DEFAULT);
|
||||
|
||||
if (i == nsigs - 1)
|
||||
{
|
||||
gboolean key_missing, key_revoked, key_expired, sig_expired;
|
||||
g_variant_get_child (info, OSTREE_GPG_SIGNATURE_ATTR_KEY_MISSING,
|
||||
"b", &key_missing);
|
||||
g_variant_get_child (info, OSTREE_GPG_SIGNATURE_ATTR_KEY_REVOKED,
|
||||
"b", &key_revoked);
|
||||
g_variant_get_child (info, OSTREE_GPG_SIGNATURE_ATTR_KEY_EXPIRED,
|
||||
"b", &key_expired);
|
||||
g_variant_get_child (info, OSTREE_GPG_SIGNATURE_ATTR_SIG_EXPIRED,
|
||||
"b", &sig_expired);
|
||||
|
||||
if (key_missing)
|
||||
code = OSTREE_GPG_ERROR_MISSING_KEY;
|
||||
else if (key_revoked)
|
||||
code = OSTREE_GPG_ERROR_REVOKED_KEY;
|
||||
else if (key_expired)
|
||||
code = OSTREE_GPG_ERROR_EXPIRED_KEY;
|
||||
else if (sig_expired)
|
||||
code = OSTREE_GPG_ERROR_EXPIRED_SIGNATURE;
|
||||
else
|
||||
/* Assume any other issue is a bad signature */
|
||||
code = OSTREE_GPG_ERROR_INVALID_SIGNATURE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Strip any trailing newlines */
|
||||
g_strchomp (buffer->str);
|
||||
g_set_error_literal (error, OSTREE_GPG_ERROR, code, buffer->str);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
@@ -159,6 +159,11 @@ gboolean ostree_gpg_verify_result_require_valid_signature (OstreeGpgVerifyResult
|
||||
* @OSTREE_GPG_ERROR_NO_SIGNATURE: A signature was expected, but not found.
|
||||
* @OSTREE_GPG_ERROR_INVALID_SIGNATURE: A signature was malformed.
|
||||
* @OSTREE_GPG_ERROR_MISSING_KEY: A signature was found, but was created with a key not in the configured keyrings.
|
||||
* @OSTREE_GPG_ERROR_EXPIRED_SIGNATURE: A signature was expired. Since: 2020.1.
|
||||
* @OSTREE_GPG_ERROR_EXPIRED_KEY: A signature was found, but the key used to
|
||||
* sign it has expired. Since: 2020.1.
|
||||
* @OSTREE_GPG_ERROR_REVOKED_KEY: A signature was found, but the key used to
|
||||
* sign it has been revoked. Since: 2020.1.
|
||||
*
|
||||
* Errors returned by signature creation and verification operations in OSTree.
|
||||
* These may be returned by any API which creates or verifies signatures.
|
||||
@@ -169,6 +174,9 @@ typedef enum {
|
||||
OSTREE_GPG_ERROR_NO_SIGNATURE = 0,
|
||||
OSTREE_GPG_ERROR_INVALID_SIGNATURE,
|
||||
OSTREE_GPG_ERROR_MISSING_KEY,
|
||||
OSTREE_GPG_ERROR_EXPIRED_SIGNATURE,
|
||||
OSTREE_GPG_ERROR_EXPIRED_KEY,
|
||||
OSTREE_GPG_ERROR_REVOKED_KEY,
|
||||
} OstreeGpgError;
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#ifdef HAVE_LIBMOUNT
|
||||
#include <libmount.h>
|
||||
#endif
|
||||
#include <sys/statvfs.h>
|
||||
#include <stdbool.h>
|
||||
#include "otutil.h"
|
||||
|
||||
@@ -189,8 +190,6 @@ _ostree_impl_system_generator (const char *ostree_cmdline,
|
||||
"[Unit]\n"
|
||||
"Documentation=man:ostree(1)\n"
|
||||
"ConditionKernelCommandLine=!systemd.volatile\n"
|
||||
/* We need /sysroot mounted writable first */
|
||||
"After=ostree-remount.service\n"
|
||||
"Before=local-fs.target\n"
|
||||
"\n"
|
||||
"[Mount]\n"
|
||||
|
||||
@@ -68,6 +68,10 @@ _ostree_linuxfs_fd_alter_immutable_flag (int fd,
|
||||
}
|
||||
else
|
||||
{
|
||||
gboolean prev_immutable_state = (flags & EXT2_IMMUTABLE_FL) > 0;
|
||||
if (prev_immutable_state == new_immutable_state)
|
||||
return TRUE; /* Nothing to do */
|
||||
|
||||
if (new_immutable_state)
|
||||
flags |= EXT2_IMMUTABLE_FL;
|
||||
else
|
||||
|
||||
@@ -92,8 +92,8 @@ checkout_object_for_uncompressed_cache (OstreeRepo *self,
|
||||
|
||||
if (self->uncompressed_objects_dir_fd == -1)
|
||||
{
|
||||
if (!glnx_shutil_mkdir_p_at (self->repo_dir_fd, "uncompressed-objects-cache", 0755,
|
||||
cancellable, error))
|
||||
if (!glnx_shutil_mkdir_p_at (self->repo_dir_fd, "uncompressed-objects-cache",
|
||||
DEFAULT_DIRECTORY_MODE, cancellable, error))
|
||||
return FALSE;
|
||||
if (!glnx_opendirat (self->repo_dir_fd, "uncompressed-objects-cache", TRUE,
|
||||
&self->uncompressed_objects_dir_fd,
|
||||
|
||||
@@ -32,6 +32,10 @@
|
||||
#include <glib/gprintf.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/fs.h>
|
||||
#include <ext2fs/ext2_fs.h>
|
||||
#ifdef HAVE_LINUX_FSVERITY_H
|
||||
#include <linux/fsverity.h>
|
||||
#endif
|
||||
|
||||
#include "otutil.h"
|
||||
#include "ostree.h"
|
||||
@@ -168,6 +172,113 @@ ot_security_smack_reset_fd (int fd)
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Wrapper around the fsverity ioctl, compressing the result to
|
||||
* "success, unsupported or error". This is used for /boot where
|
||||
* we enable verity if supported.
|
||||
* */
|
||||
gboolean
|
||||
_ostree_tmpf_fsverity_core (GLnxTmpfile *tmpf,
|
||||
_OstreeFeatureSupport fsverity_requested,
|
||||
gboolean *supported,
|
||||
GError **error)
|
||||
{
|
||||
/* Set this by default to simplify the code below */
|
||||
if (supported)
|
||||
*supported = FALSE;
|
||||
|
||||
if (fsverity_requested == _OSTREE_FEATURE_NO)
|
||||
return TRUE;
|
||||
|
||||
#ifdef HAVE_LINUX_FSVERITY_H
|
||||
GLNX_AUTO_PREFIX_ERROR ("fsverity", error);
|
||||
|
||||
/* fs-verity requires a read-only file descriptor */
|
||||
if (!glnx_tmpfile_reopen_rdonly (tmpf, error))
|
||||
return FALSE;
|
||||
|
||||
struct fsverity_enable_arg arg = { 0, };
|
||||
arg.version = 1;
|
||||
arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256; /* TODO configurable? */
|
||||
arg.block_size = 4096; /* FIXME query */
|
||||
arg.salt_size = 0; /* TODO store salt in ostree repo config */
|
||||
arg.salt_ptr = 0;
|
||||
arg.sig_size = 0; /* We don't currently expect use of in-kernel signature verification */
|
||||
arg.sig_ptr = 0;
|
||||
|
||||
if (ioctl (tmpf->fd, FS_IOC_ENABLE_VERITY, &arg) < 0)
|
||||
{
|
||||
switch (errno)
|
||||
{
|
||||
case ENOTTY:
|
||||
case EOPNOTSUPP:
|
||||
return TRUE;
|
||||
default:
|
||||
return glnx_throw_errno_prefix (error, "ioctl(FS_IOC_ENABLE_VERITY)");
|
||||
}
|
||||
}
|
||||
|
||||
if (supported)
|
||||
*supported = TRUE;
|
||||
#endif
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Enable verity on a file, respecting the "wanted" and "supported" states.
|
||||
* The main idea here is to optimize out pointlessly calling the ioctl()
|
||||
* over and over in cases where it's not supported for the repo's filesystem,
|
||||
* as well as to support "opportunistic" use (requested and if filesystem supports).
|
||||
* */
|
||||
gboolean
|
||||
_ostree_tmpf_fsverity (OstreeRepo *self,
|
||||
GLnxTmpfile *tmpf,
|
||||
GError **error)
|
||||
{
|
||||
#ifdef HAVE_LINUX_FSVERITY_H
|
||||
g_mutex_lock (&self->txn_lock);
|
||||
_OstreeFeatureSupport fsverity_wanted = self->fs_verity_wanted;
|
||||
_OstreeFeatureSupport fsverity_supported = self->fs_verity_supported;
|
||||
g_mutex_unlock (&self->txn_lock);
|
||||
|
||||
switch (fsverity_wanted)
|
||||
{
|
||||
case _OSTREE_FEATURE_YES:
|
||||
{
|
||||
if (fsverity_supported == _OSTREE_FEATURE_NO)
|
||||
return glnx_throw (error, "fsverity required but filesystem does not support it");
|
||||
}
|
||||
break;
|
||||
case _OSTREE_FEATURE_MAYBE:
|
||||
break;
|
||||
case _OSTREE_FEATURE_NO:
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean supported = FALSE;
|
||||
if (!_ostree_tmpf_fsverity_core (tmpf, fsverity_wanted, &supported, error))
|
||||
return FALSE;
|
||||
|
||||
if (!supported)
|
||||
{
|
||||
if (G_UNLIKELY (fsverity_wanted == _OSTREE_FEATURE_YES))
|
||||
return glnx_throw (error, "fsverity required but filesystem does not support it");
|
||||
|
||||
/* If we got here, we must be trying "opportunistic" use of fs-verity */
|
||||
g_assert_cmpint (fsverity_wanted, ==, _OSTREE_FEATURE_MAYBE);
|
||||
g_mutex_lock (&self->txn_lock);
|
||||
self->fs_verity_supported = _OSTREE_FEATURE_NO;
|
||||
g_mutex_unlock (&self->txn_lock);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
g_mutex_lock (&self->txn_lock);
|
||||
self->fs_verity_supported = _OSTREE_FEATURE_YES;
|
||||
g_mutex_unlock (&self->txn_lock);
|
||||
#else
|
||||
g_assert_cmpint (self->fs_verity_wanted, !=, _OSTREE_FEATURE_YES);
|
||||
#endif
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Given an O_TMPFILE regular file, link it into place. */
|
||||
gboolean
|
||||
_ostree_repo_commit_tmpf_final (OstreeRepo *self,
|
||||
@@ -185,6 +296,9 @@ _ostree_repo_commit_tmpf_final (OstreeRepo *self,
|
||||
cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
if (!_ostree_tmpf_fsverity (self, tmpf, error))
|
||||
return FALSE;
|
||||
|
||||
if (!glnx_link_tmpfile_at (tmpf, GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST,
|
||||
dest_dfd, tmpbuf, error))
|
||||
return FALSE;
|
||||
@@ -322,16 +436,19 @@ commit_loose_regfile_object (OstreeRepo *self,
|
||||
/* This is used by OSTREE_REPO_COMMIT_MODIFIER_FLAGS_GENERATE_SIZES */
|
||||
typedef struct
|
||||
{
|
||||
OstreeObjectType objtype;
|
||||
goffset unpacked;
|
||||
goffset archived;
|
||||
} OstreeContentSizeCacheEntry;
|
||||
|
||||
static OstreeContentSizeCacheEntry *
|
||||
content_size_cache_entry_new (goffset unpacked,
|
||||
goffset archived)
|
||||
content_size_cache_entry_new (OstreeObjectType objtype,
|
||||
goffset unpacked,
|
||||
goffset archived)
|
||||
{
|
||||
OstreeContentSizeCacheEntry *entry = g_slice_new0 (OstreeContentSizeCacheEntry);
|
||||
|
||||
entry->objtype = objtype;
|
||||
entry->unpacked = unpacked;
|
||||
entry->archived = archived;
|
||||
|
||||
@@ -345,19 +462,61 @@ content_size_cache_entry_free (gpointer entry)
|
||||
g_slice_free (OstreeContentSizeCacheEntry, entry);
|
||||
}
|
||||
|
||||
void
|
||||
_ostree_repo_setup_generate_sizes (OstreeRepo *self,
|
||||
OstreeRepoCommitModifier *modifier)
|
||||
{
|
||||
if (modifier && modifier->flags & OSTREE_REPO_COMMIT_MODIFIER_FLAGS_GENERATE_SIZES)
|
||||
{
|
||||
if (ostree_repo_get_mode (self) == OSTREE_REPO_MODE_ARCHIVE)
|
||||
{
|
||||
self->generate_sizes = TRUE;
|
||||
|
||||
/* Clear any stale data in the object sizes hash table */
|
||||
if (self->object_sizes != NULL)
|
||||
g_hash_table_remove_all (self->object_sizes);
|
||||
}
|
||||
else
|
||||
g_debug ("Not generating sizes for non-archive repo");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
repo_store_size_entry (OstreeRepo *self,
|
||||
const gchar *checksum,
|
||||
goffset unpacked,
|
||||
goffset archived)
|
||||
repo_ensure_size_entries (OstreeRepo *self)
|
||||
{
|
||||
if (G_UNLIKELY (self->object_sizes == NULL))
|
||||
self->object_sizes = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
g_free, content_size_cache_entry_free);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
repo_has_size_entry (OstreeRepo *self,
|
||||
OstreeObjectType objtype,
|
||||
const gchar *checksum)
|
||||
{
|
||||
/* Only file, dirtree and dirmeta objects appropriate for size metadata */
|
||||
if (objtype > OSTREE_OBJECT_TYPE_DIR_META)
|
||||
return TRUE;
|
||||
|
||||
repo_ensure_size_entries (self);
|
||||
return (g_hash_table_lookup (self->object_sizes, checksum) != NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
repo_store_size_entry (OstreeRepo *self,
|
||||
OstreeObjectType objtype,
|
||||
const gchar *checksum,
|
||||
goffset unpacked,
|
||||
goffset archived)
|
||||
{
|
||||
/* Only file, dirtree and dirmeta objects appropriate for size metadata */
|
||||
if (objtype > OSTREE_OBJECT_TYPE_DIR_META)
|
||||
return;
|
||||
|
||||
repo_ensure_size_entries (self);
|
||||
g_hash_table_replace (self->object_sizes,
|
||||
g_strdup (checksum),
|
||||
content_size_cache_entry_new (unpacked, archived));
|
||||
content_size_cache_entry_new (objtype, unpacked, archived));
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -408,6 +567,7 @@ add_size_index_to_metadata (OstreeRepo *self,
|
||||
g_hash_table_lookup (self->object_sizes, e_checksum);
|
||||
_ostree_write_varuint64 (buffer, e_size->archived);
|
||||
_ostree_write_varuint64 (buffer, e_size->unpacked);
|
||||
g_string_append_c (buffer, (gchar) e_size->objtype);
|
||||
|
||||
g_variant_builder_add (&index_builder, "@ay",
|
||||
ot_gvariant_new_bytearray ((guint8*)buffer->str, buffer->len));
|
||||
@@ -415,6 +575,9 @@ add_size_index_to_metadata (OstreeRepo *self,
|
||||
|
||||
g_variant_builder_add (builder, "{sv}", "ostree.sizes",
|
||||
g_variant_builder_end (&index_builder));
|
||||
|
||||
/* Clear the object sizes hash table for a subsequent commit. */
|
||||
g_hash_table_remove_all (self->object_sizes);
|
||||
}
|
||||
|
||||
return g_variant_ref_sink (g_variant_builder_end (builder));
|
||||
@@ -956,7 +1119,6 @@ write_content_object (OstreeRepo *self,
|
||||
g_auto(OtCleanupUnlinkat) tmp_unlinker = { commit_tmp_dfd (self), NULL };
|
||||
g_auto(GLnxTmpfile) tmpf = { 0, };
|
||||
goffset unpacked_size = 0;
|
||||
gboolean indexable = FALSE;
|
||||
/* Is it a symlink physically? */
|
||||
if (phys_object_is_symlink)
|
||||
{
|
||||
@@ -982,9 +1144,6 @@ write_content_object (OstreeRepo *self,
|
||||
|
||||
g_assert (repo_mode == OSTREE_REPO_MODE_ARCHIVE);
|
||||
|
||||
if (self->generate_sizes)
|
||||
indexable = TRUE;
|
||||
|
||||
if (!glnx_open_tmpfile_linkable_at (commit_tmp_dfd (self), ".", O_WRONLY|O_CLOEXEC,
|
||||
&tmpf, error))
|
||||
return FALSE;
|
||||
@@ -1013,6 +1172,11 @@ write_content_object (OstreeRepo *self,
|
||||
|
||||
unpacked_size = g_file_info_get_size (file_info);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* For a symlink, the size is the length of the target */
|
||||
unpacked_size = strlen (g_file_info_get_symlink_target (file_info));
|
||||
}
|
||||
|
||||
if (!g_output_stream_flush (temp_out, cancellable, error))
|
||||
return FALSE;
|
||||
@@ -1042,6 +1206,19 @@ write_content_object (OstreeRepo *self,
|
||||
|
||||
g_assert (actual_checksum != NULL); /* Pacify static analysis */
|
||||
|
||||
/* Update size metadata if configured and entry missing */
|
||||
if (self->generate_sizes &&
|
||||
!repo_has_size_entry (self, OSTREE_OBJECT_TYPE_FILE, actual_checksum))
|
||||
{
|
||||
struct stat stbuf;
|
||||
|
||||
if (!glnx_fstat (tmpf.fd, &stbuf, error))
|
||||
return FALSE;
|
||||
|
||||
repo_store_size_entry (self, OSTREE_OBJECT_TYPE_FILE, actual_checksum,
|
||||
unpacked_size, stbuf.st_size);
|
||||
}
|
||||
|
||||
/* See whether or not we have the object, now that we know the
|
||||
* checksum.
|
||||
*/
|
||||
@@ -1107,17 +1284,6 @@ write_content_object (OstreeRepo *self,
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Update size metadata if configured */
|
||||
if (indexable && object_file_type == G_FILE_TYPE_REGULAR)
|
||||
{
|
||||
struct stat stbuf;
|
||||
|
||||
if (!glnx_fstat (tmpf.fd, &stbuf, error))
|
||||
return FALSE;
|
||||
|
||||
repo_store_size_entry (self, actual_checksum, unpacked_size, stbuf.st_size);
|
||||
}
|
||||
|
||||
/* Check if a file with the same payload is present in the repository,
|
||||
and in case try to reflink it */
|
||||
if (actual_payload_checksum && !_try_clone_from_payload_link (self, self, actual_payload_checksum, file_info, &tmpf, cancellable, error))
|
||||
@@ -1309,6 +1475,11 @@ write_metadata_object (OstreeRepo *self,
|
||||
*/
|
||||
if (have_obj)
|
||||
{
|
||||
/* Update size metadata if needed */
|
||||
if (self->generate_sizes &&
|
||||
!repo_has_size_entry (self, objtype, actual_checksum))
|
||||
repo_store_size_entry (self, objtype, actual_checksum, len, len);
|
||||
|
||||
g_mutex_lock (&self->txn_lock);
|
||||
self->txn.stats.metadata_objects_total++;
|
||||
g_mutex_unlock (&self->txn_lock);
|
||||
@@ -1330,6 +1501,11 @@ write_metadata_object (OstreeRepo *self,
|
||||
gsize len;
|
||||
const guint8 *bufp = g_bytes_get_data (buf, &len);
|
||||
|
||||
/* Update size metadata if needed */
|
||||
if (self->generate_sizes &&
|
||||
!repo_has_size_entry (self, objtype, actual_checksum))
|
||||
repo_store_size_entry (self, objtype, actual_checksum, len, len);
|
||||
|
||||
/* Write the metadata to a temporary file */
|
||||
g_auto(GLnxTmpfile) tmpf = { 0, };
|
||||
if (!glnx_open_tmpfile_linkable_at (commit_tmp_dfd (self), ".", O_WRONLY|O_CLOEXEC,
|
||||
@@ -2345,6 +2521,16 @@ ostree_repo_write_metadata (OstreeRepo *self,
|
||||
return FALSE;
|
||||
if (have_obj)
|
||||
{
|
||||
/* Update size metadata if needed */
|
||||
if (self->generate_sizes &&
|
||||
!repo_has_size_entry (self, objtype, expected_checksum))
|
||||
{
|
||||
/* Make sure we have a fully serialized object */
|
||||
g_autoptr(GVariant) trusted = g_variant_get_normal_form (object);
|
||||
gsize size = g_variant_get_size (trusted);
|
||||
repo_store_size_entry (self, objtype, expected_checksum, size, size);
|
||||
}
|
||||
|
||||
if (out_csum)
|
||||
*out_csum = ostree_checksum_to_bytes (expected_checksum);
|
||||
return TRUE;
|
||||
@@ -2620,8 +2806,11 @@ ostree_repo_write_content (OstreeRepo *self,
|
||||
{
|
||||
/* First, if we have an expected checksum, see if we already have this
|
||||
* object. This mirrors the same logic in ostree_repo_write_metadata().
|
||||
*
|
||||
* If size metadata is needed, fall through to write_content_object()
|
||||
* where the entries are made.
|
||||
*/
|
||||
if (expected_checksum)
|
||||
if (expected_checksum && !self->generate_sizes)
|
||||
{
|
||||
gboolean have_obj;
|
||||
if (!_ostree_repo_has_loose_object (self, expected_checksum,
|
||||
@@ -3848,8 +4037,7 @@ ostree_repo_write_directory_to_mtree (OstreeRepo *self,
|
||||
}
|
||||
else
|
||||
{
|
||||
if (modifier && modifier->flags & OSTREE_REPO_COMMIT_MODIFIER_FLAGS_GENERATE_SIZES)
|
||||
self->generate_sizes = TRUE;
|
||||
_ostree_repo_setup_generate_sizes (self, modifier);
|
||||
|
||||
g_autoptr(GPtrArray) path = g_ptr_array_new ();
|
||||
if (!write_directory_to_mtree_internal (self, dir, mtree, modifier, path,
|
||||
@@ -3883,8 +4071,7 @@ ostree_repo_write_dfd_to_mtree (OstreeRepo *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
if (modifier && modifier->flags & OSTREE_REPO_COMMIT_MODIFIER_FLAGS_GENERATE_SIZES)
|
||||
self->generate_sizes = TRUE;
|
||||
_ostree_repo_setup_generate_sizes (self, modifier);
|
||||
|
||||
g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
|
||||
if (!glnx_dirfd_iterator_init_at (dfd, path, FALSE, &dfd_iter, error))
|
||||
|
||||
@@ -844,6 +844,8 @@ ostree_repo_import_archive_to_mtree (OstreeRepo *self,
|
||||
.modifier = modifier
|
||||
};
|
||||
|
||||
_ostree_repo_setup_generate_sizes (self, modifier);
|
||||
|
||||
while (TRUE)
|
||||
{
|
||||
int r = archive_read_next_header (a, &aictx.entry);
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <sys/statvfs.h>
|
||||
#include "config.h"
|
||||
#include "otutil.h"
|
||||
#include "ostree-ref.h"
|
||||
#include "ostree-repo.h"
|
||||
@@ -31,8 +32,6 @@ G_BEGIN_DECLS
|
||||
|
||||
#define OSTREE_DELTAPART_VERSION (0)
|
||||
|
||||
#define _OSTREE_OBJECT_SIZES_ENTRY_SIGNATURE "ay"
|
||||
|
||||
#define _OSTREE_SUMMARY_CACHE_DIR "summaries"
|
||||
#define _OSTREE_CACHE_DIR "cache"
|
||||
|
||||
@@ -97,6 +96,12 @@ typedef struct {
|
||||
fsblkcnt_t max_blocks;
|
||||
} OstreeRepoTxn;
|
||||
|
||||
typedef enum {
|
||||
_OSTREE_FEATURE_NO,
|
||||
_OSTREE_FEATURE_MAYBE,
|
||||
_OSTREE_FEATURE_YES,
|
||||
} _OstreeFeatureSupport;
|
||||
|
||||
/**
|
||||
* OstreeRepo:
|
||||
*
|
||||
@@ -127,6 +132,8 @@ struct OstreeRepo {
|
||||
GMutex txn_lock;
|
||||
OstreeRepoTxn txn;
|
||||
gboolean txn_locked;
|
||||
_OstreeFeatureSupport fs_verity_wanted;
|
||||
_OstreeFeatureSupport fs_verity_supported;
|
||||
|
||||
GMutex cache_lock;
|
||||
guint dirmeta_cache_refcount;
|
||||
@@ -143,6 +150,14 @@ struct OstreeRepo {
|
||||
guint zlib_compression_level;
|
||||
GHashTable *loose_object_devino_hash;
|
||||
GHashTable *updated_uncompressed_dirs;
|
||||
|
||||
/* FIXME: The object sizes hash table is really per-commit state, not repo
|
||||
* state. Using a single table for the repo means that commits cannot be
|
||||
* built simultaneously if they're adding size information. This data should
|
||||
* probably be in OstreeMutableTree, but that's gone by the time the actual
|
||||
* commit is constructed. At that point the only commit state is in the root
|
||||
* OstreeRepoFile.
|
||||
*/
|
||||
GHashTable *object_sizes;
|
||||
|
||||
/* Cache the repo's device/inode to use for comparisons elsewhere */
|
||||
@@ -329,6 +344,10 @@ _ostree_repo_commit_modifier_apply (OstreeRepo *self,
|
||||
GFileInfo *file_info,
|
||||
GFileInfo **out_modified_info);
|
||||
|
||||
void
|
||||
_ostree_repo_setup_generate_sizes (OstreeRepo *self,
|
||||
OstreeRepoCommitModifier *modifier);
|
||||
|
||||
gboolean
|
||||
_ostree_repo_remote_name_is_file (const char *remote_name);
|
||||
|
||||
@@ -471,4 +490,15 @@ OstreeRepoAutoLock * _ostree_repo_auto_lock_push (OstreeRepo *self,
|
||||
void _ostree_repo_auto_lock_cleanup (OstreeRepoAutoLock *lock);
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoAutoLock, _ostree_repo_auto_lock_cleanup)
|
||||
|
||||
gboolean
|
||||
_ostree_tmpf_fsverity_core (GLnxTmpfile *tmpf,
|
||||
_OstreeFeatureSupport fsverity_requested,
|
||||
gboolean *supported,
|
||||
GError **error);
|
||||
|
||||
gboolean
|
||||
_ostree_tmpf_fsverity (OstreeRepo *self,
|
||||
GLnxTmpfile *tmpf,
|
||||
GError **error);
|
||||
|
||||
G_END_DECLS
|
||||
@@ -2925,7 +2925,7 @@ _ostree_repo_cache_summary (OstreeRepo *self,
|
||||
if (self->cache_dir_fd == -1)
|
||||
return TRUE;
|
||||
|
||||
if (!glnx_shutil_mkdir_p_at (self->cache_dir_fd, _OSTREE_SUMMARY_CACHE_DIR, 0775, cancellable, error))
|
||||
if (!glnx_shutil_mkdir_p_at (self->cache_dir_fd, _OSTREE_SUMMARY_CACHE_DIR, DEFAULT_DIRECTORY_MODE, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
const char *summary_cache_file = glnx_strjoina (_OSTREE_SUMMARY_CACHE_DIR, "/", remote);
|
||||
|
||||
@@ -1181,7 +1181,7 @@ _ostree_repo_write_ref (OstreeRepo *self,
|
||||
char *parent = strdupa (ref->ref_name);
|
||||
parent[lastslash - ref->ref_name] = '\0';
|
||||
|
||||
if (!glnx_shutil_mkdir_p_at (dfd, parent, 0755, cancellable, error))
|
||||
if (!glnx_shutil_mkdir_p_at (dfd, parent, DEFAULT_DIRECTORY_MODE, cancellable, error))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
@@ -1427,7 +1427,7 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
|
||||
g_autofree char *dnbuf = g_strdup (descriptor_relpath);
|
||||
const char *dn = dirname (dnbuf);
|
||||
|
||||
if (!glnx_shutil_mkdir_p_at (self->repo_dir_fd, dn, 0755, cancellable, error))
|
||||
if (!glnx_shutil_mkdir_p_at (self->repo_dir_fd, dn, DEFAULT_DIRECTORY_MODE, cancellable, error))
|
||||
goto out;
|
||||
if (!glnx_opendirat (self->repo_dir_fd, dn, TRUE, &descriptor_dfd, error))
|
||||
goto out;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "libglnx.h"
|
||||
#include "otutil.h"
|
||||
#include <glnx-console.h>
|
||||
#include <linux/magic.h>
|
||||
|
||||
#include "ostree-core-private.h"
|
||||
#include "ostree-sysroot-private.h"
|
||||
@@ -47,6 +48,7 @@
|
||||
#include <glib/gstdio.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <sys/statfs.h>
|
||||
|
||||
#define REPO_LOCK_DISABLED (-2)
|
||||
#define REPO_LOCK_BLOCKING (-1)
|
||||
@@ -357,7 +359,7 @@ push_repo_lock (OstreeRepo *self,
|
||||
g_debug ("Opening repo lock file");
|
||||
lock->fd = TEMP_FAILURE_RETRY (openat (self->repo_dir_fd, ".lock",
|
||||
O_CREAT | O_RDWR | O_CLOEXEC,
|
||||
0600));
|
||||
DEFAULT_REGFILE_MODE));
|
||||
if (lock->fd < 0)
|
||||
{
|
||||
free_repo_lock (lock);
|
||||
@@ -2489,7 +2491,7 @@ repo_create_at_internal (int dfd,
|
||||
}
|
||||
}
|
||||
|
||||
if (mkdirat (dfd, path, 0755) != 0)
|
||||
if (mkdirat (dfd, path, DEFAULT_DIRECTORY_MODE) != 0)
|
||||
{
|
||||
if (G_UNLIKELY (errno != EEXIST))
|
||||
return glnx_throw_errno_prefix (error, "mkdirat");
|
||||
@@ -2527,7 +2529,7 @@ repo_create_at_internal (int dfd,
|
||||
for (guint i = 0; i < G_N_ELEMENTS (state_dirs); i++)
|
||||
{
|
||||
const char *elt = state_dirs[i];
|
||||
if (mkdirat (repo_dfd, elt, 0755) == -1)
|
||||
if (mkdirat (repo_dfd, elt, DEFAULT_DIRECTORY_MODE) == -1)
|
||||
{
|
||||
if (G_UNLIKELY (errno != EEXIST))
|
||||
return glnx_throw_errno_prefix (error, "mkdirat");
|
||||
@@ -3033,6 +3035,34 @@ reload_core_config (OstreeRepo *self,
|
||||
}
|
||||
}
|
||||
|
||||
/* Currently experimental */
|
||||
static const char fsverity_key[] = "ex-fsverity";
|
||||
self->fs_verity_wanted = _OSTREE_FEATURE_NO;
|
||||
#ifdef HAVE_LINUX_FSVERITY_H
|
||||
self->fs_verity_supported = _OSTREE_FEATURE_MAYBE;
|
||||
#else
|
||||
self->fs_verity_supported = _OSTREE_FEATURE_NO;
|
||||
#endif
|
||||
gboolean fsverity_required = FALSE;
|
||||
if (!ot_keyfile_get_boolean_with_default (self->config, fsverity_key, "required",
|
||||
FALSE, &fsverity_required, error))
|
||||
return FALSE;
|
||||
if (fsverity_required)
|
||||
{
|
||||
self->fs_verity_wanted = _OSTREE_FEATURE_YES;
|
||||
if (self->fs_verity_supported == _OSTREE_FEATURE_NO)
|
||||
return glnx_throw (error, "fsverity required, but libostree compiled without support");
|
||||
}
|
||||
else
|
||||
{
|
||||
gboolean fsverity_opportunistic = FALSE;
|
||||
if (!ot_keyfile_get_boolean_with_default (self->config, fsverity_key, "opportunistic",
|
||||
FALSE, &fsverity_opportunistic, error))
|
||||
return FALSE;
|
||||
if (fsverity_opportunistic)
|
||||
self->fs_verity_wanted = _OSTREE_FEATURE_MAYBE;
|
||||
}
|
||||
|
||||
{
|
||||
g_clear_pointer (&self->collection_id, g_free);
|
||||
if (!ot_keyfile_get_value_with_default (self->config, "core", "collection-id",
|
||||
@@ -3295,7 +3325,7 @@ ostree_repo_open (OstreeRepo *self,
|
||||
*
|
||||
* https://github.com/ostreedev/ostree/issues/1018
|
||||
*/
|
||||
if (mkdirat (self->repo_dir_fd, "tmp", 0755) == -1)
|
||||
if (mkdirat (self->repo_dir_fd, "tmp", DEFAULT_DIRECTORY_MODE) == -1)
|
||||
{
|
||||
if (G_UNLIKELY (errno != EEXIST))
|
||||
return glnx_throw_errno_prefix (error, "mkdir(tmp)");
|
||||
@@ -3307,7 +3337,7 @@ ostree_repo_open (OstreeRepo *self,
|
||||
|
||||
if (self->writable)
|
||||
{
|
||||
if (!glnx_shutil_mkdir_p_at (self->tmp_dir_fd, _OSTREE_CACHE_DIR, 0775, cancellable, error))
|
||||
if (!glnx_shutil_mkdir_p_at (self->tmp_dir_fd, _OSTREE_CACHE_DIR, DEFAULT_DIRECTORY_MODE, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
if (!glnx_opendirat (self->tmp_dir_fd, _OSTREE_CACHE_DIR, TRUE, &self->cache_dir_fd, error))
|
||||
@@ -6099,7 +6129,7 @@ _ostree_repo_allocate_tmpdir (int tmpdir_dfd,
|
||||
{
|
||||
g_auto(GLnxTmpDir) new_tmpdir = { 0, };
|
||||
/* No existing tmpdir found, create a new */
|
||||
if (!glnx_mkdtempat (tmpdir_dfd, tmpdir_name_template, 0755,
|
||||
if (!glnx_mkdtempat (tmpdir_dfd, tmpdir_name_template, DEFAULT_DIRECTORY_MODE,
|
||||
&new_tmpdir, error))
|
||||
return FALSE;
|
||||
|
||||
|
||||
@@ -455,6 +455,9 @@ ostree_sysroot_cleanup_prune_repo (OstreeSysroot *sysroot,
|
||||
OstreeRepo *repo = ostree_sysroot_repo (sysroot);
|
||||
const guint depth = 0; /* Historical default */
|
||||
|
||||
if (!_ostree_sysroot_ensure_writable (sysroot, error))
|
||||
return FALSE;
|
||||
|
||||
/* Hold an exclusive lock by default across gathering refs and doing
|
||||
* the prune.
|
||||
*/
|
||||
@@ -535,7 +538,10 @@ _ostree_sysroot_cleanup_internal (OstreeSysroot *self,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (OSTREE_IS_SYSROOT (self), FALSE);
|
||||
g_return_val_if_fail (self->loaded, FALSE);
|
||||
g_return_val_if_fail (self->loadstate == OSTREE_SYSROOT_LOAD_STATE_LOADED, FALSE);
|
||||
|
||||
if (!_ostree_sysroot_ensure_writable (self, error))
|
||||
return FALSE;
|
||||
|
||||
if (!cleanup_other_bootversions (self, cancellable, error))
|
||||
return glnx_prefix_error (error, "Cleaning bootversions");
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
|
||||
#include "otutil.h"
|
||||
#include "ostree.h"
|
||||
#include "ostree-repo-private.h"
|
||||
#include "ostree-sysroot-private.h"
|
||||
#include "ostree-sepolicy-private.h"
|
||||
#include "ostree-bootloader-zipl.h"
|
||||
@@ -56,6 +57,9 @@
|
||||
#define OSTREE_DEPLOYMENT_FINALIZING_ID SD_ID128_MAKE(e8,64,6c,d6,3d,ff,46,25,b7,79,09,a8,e7,a4,09,94)
|
||||
#endif
|
||||
|
||||
static gboolean
|
||||
is_ro_mount (const char *path);
|
||||
|
||||
/*
|
||||
* Like symlinkat() but overwrites (atomically) an existing
|
||||
* symlink.
|
||||
@@ -101,7 +105,8 @@ sysroot_flags_to_copy_flags (GLnxFileCopyFlags defaults,
|
||||
* hardlink if we're on the same partition.
|
||||
*/
|
||||
static gboolean
|
||||
install_into_boot (OstreeSePolicy *sepolicy,
|
||||
install_into_boot (OstreeRepo *repo,
|
||||
OstreeSePolicy *sepolicy,
|
||||
int src_dfd,
|
||||
const char *src_subpath,
|
||||
int dest_dfd,
|
||||
@@ -110,32 +115,67 @@ install_into_boot (OstreeSePolicy *sepolicy,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
if (linkat (src_dfd, src_subpath, dest_dfd, dest_subpath, 0) != 0)
|
||||
{
|
||||
if (G_IN_SET (errno, EMLINK, EXDEV))
|
||||
{
|
||||
/* Be sure we relabel when copying the kernel, as in current
|
||||
* e.g. Fedora it might be labeled module_object_t or usr_t,
|
||||
* but policy may not allow other processes to read from that
|
||||
* like kdump.
|
||||
* See also https://github.com/fedora-selinux/selinux-policy/commit/747f4e6775d773ab74efae5aa37f3e5e7f0d4aca
|
||||
* This means we also drop xattrs but...I doubt anyone uses
|
||||
* non-SELinux xattrs for the kernel anyways aside from perhaps
|
||||
* IMA but that's its own story.
|
||||
*/
|
||||
g_auto(OstreeSepolicyFsCreatecon) fscreatecon = { 0, };
|
||||
const char *boot_path = glnx_strjoina ("/boot/", glnx_basename (dest_subpath));
|
||||
if (!_ostree_sepolicy_preparefscreatecon (&fscreatecon, sepolicy,
|
||||
boot_path, S_IFREG | 0644,
|
||||
error))
|
||||
return FALSE;
|
||||
return glnx_file_copy_at (src_dfd, src_subpath, NULL, dest_dfd, dest_subpath,
|
||||
GLNX_FILE_COPY_NOXATTRS | GLNX_FILE_COPY_DATASYNC,
|
||||
cancellable, error);
|
||||
}
|
||||
else
|
||||
return glnx_throw_errno_prefix (error, "linkat(%s)", dest_subpath);
|
||||
}
|
||||
if (linkat (src_dfd, src_subpath, dest_dfd, dest_subpath, 0) == 0)
|
||||
return TRUE; /* Note early return */
|
||||
if (!G_IN_SET (errno, EMLINK, EXDEV))
|
||||
return glnx_throw_errno_prefix (error, "linkat(%s)", dest_subpath);
|
||||
|
||||
/* Otherwise, copy */
|
||||
struct stat src_stbuf;
|
||||
if (!glnx_fstatat (src_dfd, src_subpath, &src_stbuf, AT_SYMLINK_NOFOLLOW, error))
|
||||
return FALSE;
|
||||
|
||||
glnx_autofd int src_fd = -1;
|
||||
if (!glnx_openat_rdonly (src_dfd, src_subpath, FALSE, &src_fd, error))
|
||||
return FALSE;
|
||||
|
||||
/* Be sure we relabel when copying the kernel, as in current
|
||||
* e.g. Fedora it might be labeled module_object_t or usr_t,
|
||||
* but policy may not allow other processes to read from that
|
||||
* like kdump.
|
||||
* See also https://github.com/fedora-selinux/selinux-policy/commit/747f4e6775d773ab74efae5aa37f3e5e7f0d4aca
|
||||
* This means we also drop xattrs but...I doubt anyone uses
|
||||
* non-SELinux xattrs for the kernel anyways aside from perhaps
|
||||
* IMA but that's its own story.
|
||||
*/
|
||||
g_auto(OstreeSepolicyFsCreatecon) fscreatecon = { 0, };
|
||||
const char *boot_path = glnx_strjoina ("/boot/", glnx_basename (dest_subpath));
|
||||
if (!_ostree_sepolicy_preparefscreatecon (&fscreatecon, sepolicy,
|
||||
boot_path, S_IFREG | 0644,
|
||||
error))
|
||||
return FALSE;
|
||||
|
||||
g_auto(GLnxTmpfile) tmp_dest = { 0, };
|
||||
if (!glnx_open_tmpfile_linkable_at (dest_dfd, ".", O_WRONLY | O_CLOEXEC,
|
||||
&tmp_dest, error))
|
||||
return FALSE;
|
||||
|
||||
if (glnx_regfile_copy_bytes (src_fd, tmp_dest.fd, (off_t) -1) < 0)
|
||||
return glnx_throw_errno_prefix (error, "regfile copy");
|
||||
|
||||
/* Kernel data should always be root-owned */
|
||||
if (fchown (tmp_dest.fd, src_stbuf.st_uid, src_stbuf.st_gid) != 0)
|
||||
return glnx_throw_errno_prefix (error, "fchown");
|
||||
|
||||
if (fchmod (tmp_dest.fd, src_stbuf.st_mode & 07777) != 0)
|
||||
return glnx_throw_errno_prefix (error, "fchmod");
|
||||
|
||||
if (fdatasync (tmp_dest.fd) < 0)
|
||||
return glnx_throw_errno_prefix (error, "fdatasync");
|
||||
|
||||
/* Today we don't have a config flag to *require* verity on /boot,
|
||||
* and at least for Fedora CoreOS we're not likely to do fsverity on
|
||||
* /boot soon due to wanting to support mounting it from old Linux
|
||||
* kernels. So change "required" to "maybe".
|
||||
*/
|
||||
_OstreeFeatureSupport boot_verity = _OSTREE_FEATURE_NO;
|
||||
if (repo->fs_verity_wanted != _OSTREE_FEATURE_NO)
|
||||
boot_verity = _OSTREE_FEATURE_MAYBE;
|
||||
if (!_ostree_tmpf_fsverity_core (&tmp_dest, boot_verity, NULL, error))
|
||||
return FALSE;
|
||||
|
||||
if (!glnx_link_tmpfile_at (&tmp_dest, GLNX_LINK_TMPFILE_NOREPLACE, dest_dfd, dest_subpath, error))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
@@ -806,6 +846,9 @@ write_origin_file_internal (OstreeSysroot *sysroot,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
if (!_ostree_sysroot_ensure_writable (sysroot, error))
|
||||
return FALSE;
|
||||
|
||||
GLNX_AUTO_PREFIX_ERROR ("Writing out origin file", error);
|
||||
GKeyFile *origin =
|
||||
new_origin ? new_origin : ostree_deployment_get_origin (deployment);
|
||||
@@ -1660,7 +1703,7 @@ install_deployment_kernel (OstreeSysroot *sysroot,
|
||||
return FALSE;
|
||||
if (errno == ENOENT)
|
||||
{
|
||||
if (!install_into_boot (sepolicy, kernel_layout->boot_dfd, kernel_layout->kernel_srcpath,
|
||||
if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->kernel_srcpath,
|
||||
bootcsum_dfd, kernel_layout->kernel_namever,
|
||||
sysroot->debug_flags,
|
||||
cancellable, error))
|
||||
@@ -1677,7 +1720,7 @@ install_deployment_kernel (OstreeSysroot *sysroot,
|
||||
return FALSE;
|
||||
if (errno == ENOENT)
|
||||
{
|
||||
if (!install_into_boot (sepolicy, kernel_layout->boot_dfd, kernel_layout->initramfs_srcpath,
|
||||
if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->initramfs_srcpath,
|
||||
bootcsum_dfd, kernel_layout->initramfs_namever,
|
||||
sysroot->debug_flags,
|
||||
cancellable, error))
|
||||
@@ -1692,7 +1735,7 @@ install_deployment_kernel (OstreeSysroot *sysroot,
|
||||
return FALSE;
|
||||
if (errno == ENOENT)
|
||||
{
|
||||
if (!install_into_boot (sepolicy, kernel_layout->boot_dfd, kernel_layout->devicetree_srcpath,
|
||||
if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->devicetree_srcpath,
|
||||
bootcsum_dfd, kernel_layout->devicetree_namever,
|
||||
sysroot->debug_flags,
|
||||
cancellable, error))
|
||||
@@ -1706,7 +1749,7 @@ install_deployment_kernel (OstreeSysroot *sysroot,
|
||||
return FALSE;
|
||||
if (errno == ENOENT)
|
||||
{
|
||||
if (!install_into_boot (sepolicy, kernel_layout->boot_dfd, kernel_layout->kernel_hmac_srcpath,
|
||||
if (!install_into_boot (repo, sepolicy, kernel_layout->boot_dfd, kernel_layout->kernel_hmac_srcpath,
|
||||
bootcsum_dfd, kernel_layout->kernel_hmac_namever,
|
||||
sysroot->debug_flags,
|
||||
cancellable, error))
|
||||
@@ -2217,7 +2260,10 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
g_assert (self->loaded);
|
||||
g_assert (self->loadstate == OSTREE_SYSROOT_LOAD_STATE_LOADED);
|
||||
|
||||
if (!_ostree_sysroot_ensure_writable (self, error))
|
||||
return FALSE;
|
||||
|
||||
/* Dealing with the staged deployment is quite tricky here. This function is
|
||||
* primarily concerned with writing out "finalized" deployments which have
|
||||
@@ -2374,7 +2420,6 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self,
|
||||
|
||||
if (boot_was_ro_mount)
|
||||
{
|
||||
/* TODO: Use new mount namespace. https://github.com/ostreedev/ostree/issues/1265 */
|
||||
if (mount ("/boot", "/boot", NULL, MS_REMOUNT | MS_SILENT, NULL) < 0)
|
||||
return glnx_throw_errno_prefix (error, "Remounting /boot read-write");
|
||||
}
|
||||
@@ -2408,8 +2453,10 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self,
|
||||
/* Note equivalent of try/finally here */
|
||||
gboolean success = write_deployments_bootswap (self, new_deployments, opts, bootloader,
|
||||
&syncstats, cancellable, error);
|
||||
/* Below here don't set GError until the if (!success) check */
|
||||
if (boot_was_ro_mount)
|
||||
/* Below here don't set GError until the if (!success) check.
|
||||
* Note we only bother remounting if a mount namespace isn't in use.
|
||||
* */
|
||||
if (boot_was_ro_mount && !self->mount_namespace_in_use)
|
||||
{
|
||||
if (mount ("/boot", "/boot", NULL, MS_REMOUNT | MS_RDONLY | MS_SILENT, NULL) < 0)
|
||||
{
|
||||
@@ -2716,6 +2763,9 @@ ostree_sysroot_deploy_tree (OstreeSysroot *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
if (!_ostree_sysroot_ensure_writable (self, error))
|
||||
return FALSE;
|
||||
|
||||
g_autoptr(OstreeDeployment) deployment = NULL;
|
||||
if (!sysroot_initialize_deployment (self, osname, revision, origin, override_kernel_argv,
|
||||
&deployment, cancellable, error))
|
||||
@@ -2817,6 +2867,9 @@ ostree_sysroot_stage_tree (OstreeSysroot *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
if (!_ostree_sysroot_ensure_writable (self, error))
|
||||
return FALSE;
|
||||
|
||||
OstreeDeployment *booted_deployment = ostree_sysroot_get_booted_deployment (self);
|
||||
if (booted_deployment == NULL)
|
||||
return glnx_throw (error, "Cannot stage a deployment when not currently booted into an OSTree system");
|
||||
@@ -3043,6 +3096,9 @@ ostree_sysroot_deployment_set_kargs (OstreeSysroot *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
if (!_ostree_sysroot_ensure_writable (self, error))
|
||||
return FALSE;
|
||||
|
||||
/* For now; instead of this do a redeployment */
|
||||
g_assert (!ostree_deployment_is_staged (deployment));
|
||||
|
||||
@@ -3090,6 +3146,8 @@ ostree_sysroot_deployment_set_mutable (OstreeSysroot *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
if (!_ostree_sysroot_ensure_writable (self, error))
|
||||
return FALSE;
|
||||
|
||||
if (g_cancellable_set_error_if_cancelled (cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
@@ -40,6 +40,12 @@ typedef enum {
|
||||
OSTREE_SYSROOT_DEBUG_TEST_STAGED_PATH = 1 << 3,
|
||||
} OstreeSysrootDebugFlags;
|
||||
|
||||
typedef enum {
|
||||
OSTREE_SYSROOT_LOAD_STATE_NONE, /* ostree_sysroot_new() was called */
|
||||
OSTREE_SYSROOT_LOAD_STATE_INIT, /* We've loaded basic sysroot state and have an fd */
|
||||
OSTREE_SYSROOT_LOAD_STATE_LOADED, /* We've loaded all of the deployments */
|
||||
} OstreeSysrootLoadState;
|
||||
|
||||
/**
|
||||
* OstreeSysroot:
|
||||
* Internal struct
|
||||
@@ -51,7 +57,8 @@ struct OstreeSysroot {
|
||||
int sysroot_fd;
|
||||
GLnxLockFile lock;
|
||||
|
||||
gboolean loaded;
|
||||
OstreeSysrootLoadState loadstate;
|
||||
gboolean mount_namespace_in_use; /* TRUE if caller has told us they used CLONE_NEWNS */
|
||||
gboolean root_is_ostree_booted; /* TRUE if sysroot is / and we are booted via ostree */
|
||||
/* The device/inode for /, used to detect booted deployment */
|
||||
dev_t root_device;
|
||||
@@ -79,6 +86,10 @@ struct OstreeSysroot {
|
||||
#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_DIR "/run/ostree/deployment-state/"
|
||||
#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT "unlocked-development"
|
||||
|
||||
gboolean
|
||||
_ostree_sysroot_ensure_writable (OstreeSysroot *self,
|
||||
GError **error);
|
||||
|
||||
void
|
||||
_ostree_sysroot_emit_journal_msg (OstreeSysroot *self,
|
||||
const char *msg);
|
||||
|
||||
+156
-44
@@ -226,6 +226,33 @@ ostree_sysroot_new_default (void)
|
||||
return ostree_sysroot_new (NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sysroot_set_mount_namespace_in_use:
|
||||
*
|
||||
* If this function is invoked, then libostree will assume that
|
||||
* a private Linux mount namespace has been created by the process.
|
||||
* The primary use case for this is to have e.g. /sysroot mounted
|
||||
* read-only by default.
|
||||
*
|
||||
* If this function has been called, then when a function which requires
|
||||
* writable access is invoked, libostree will automatically remount as writable
|
||||
* any mount points on which it operates. This currently is just `/sysroot` and
|
||||
* `/boot`.
|
||||
*
|
||||
* If you invoke this function, it must be before ostree_sysroot_load(); it may
|
||||
* be invoked before or after ostree_sysroot_initialize().
|
||||
*
|
||||
* Since: 2020.1
|
||||
*/
|
||||
void
|
||||
ostree_sysroot_set_mount_namespace_in_use (OstreeSysroot *self)
|
||||
{
|
||||
/* Must be before we're loaded, as otherwise we'd have to close/reopen all our
|
||||
fds, e.g. the repo */
|
||||
g_return_if_fail (self->loadstate < OSTREE_SYSROOT_LOAD_STATE_LOADED);
|
||||
self->mount_namespace_in_use = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sysroot_get_path:
|
||||
* @self:
|
||||
@@ -238,6 +265,7 @@ ostree_sysroot_get_path (OstreeSysroot *self)
|
||||
return self->path;
|
||||
}
|
||||
|
||||
/* Open a directory file descriptor for the sysroot if we haven't yet */
|
||||
static gboolean
|
||||
ensure_sysroot_fd (OstreeSysroot *self,
|
||||
GError **error)
|
||||
@@ -251,13 +279,51 @@ ensure_sysroot_fd (OstreeSysroot *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Remount /sysroot read-write if necessary */
|
||||
gboolean
|
||||
_ostree_sysroot_ensure_writable (OstreeSysroot *self,
|
||||
GError **error)
|
||||
{
|
||||
/* Do nothing if no mount namespace is in use */
|
||||
if (!self->mount_namespace_in_use)
|
||||
return TRUE;
|
||||
|
||||
/* If a mount namespace is in use, ensure we're initialized */
|
||||
if (!ostree_sysroot_initialize (self, error))
|
||||
return FALSE;
|
||||
|
||||
/* If we aren't operating on a booted system, then we don't
|
||||
* do anything with mounts.
|
||||
*/
|
||||
if (!self->root_is_ostree_booted)
|
||||
return TRUE;
|
||||
|
||||
/* Check if /sysroot is a read-only mountpoint */
|
||||
struct statvfs stvfsbuf;
|
||||
if (statvfs ("/sysroot", &stvfsbuf) < 0)
|
||||
return glnx_throw_errno_prefix (error, "fstatvfs(/sysroot)");
|
||||
if ((stvfsbuf.f_flag & ST_RDONLY) == 0)
|
||||
return TRUE;
|
||||
|
||||
/* OK, let's remount writable. */
|
||||
if (mount ("/sysroot", "/sysroot", NULL, MS_REMOUNT | MS_RELATIME, "") < 0)
|
||||
return glnx_throw_errno_prefix (error, "Remounting /sysroot read-write");
|
||||
|
||||
/* Reopen our fd */
|
||||
glnx_close_fd (&self->sysroot_fd);
|
||||
if (!ensure_sysroot_fd (self, error))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sysroot_get_fd:
|
||||
* @self: Sysroot
|
||||
*
|
||||
* Access a file descriptor that refers to the root directory of this
|
||||
* sysroot. ostree_sysroot_load() must have been invoked prior to
|
||||
* calling this function.
|
||||
* Access a file descriptor that refers to the root directory of this sysroot.
|
||||
* ostree_sysroot_initialize() (or ostree_sysroot_load()) must have been invoked
|
||||
* prior to calling this function.
|
||||
*
|
||||
* Returns: A file descriptor valid for the lifetime of @self
|
||||
*/
|
||||
@@ -268,6 +334,22 @@ ostree_sysroot_get_fd (OstreeSysroot *self)
|
||||
return self->sysroot_fd;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sysroot_is_booted:
|
||||
* @self: Sysroot
|
||||
*
|
||||
* Can only be invoked after `ostree_sysroot_initialize()`.
|
||||
*
|
||||
* Returns: %TRUE iff the sysroot points to a booted deployment
|
||||
* Since: 2020.1
|
||||
*/
|
||||
gboolean
|
||||
ostree_sysroot_is_booted (OstreeSysroot *self)
|
||||
{
|
||||
g_return_val_if_fail (self->loadstate >= OSTREE_SYSROOT_LOAD_STATE_INIT, FALSE);
|
||||
return self->root_is_ostree_booted;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_ostree_sysroot_bump_mtime (OstreeSysroot *self,
|
||||
GError **error)
|
||||
@@ -798,6 +880,57 @@ ensure_repo (OstreeSysroot *self,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* ostree_sysroot_initialize:
|
||||
* @self: sysroot
|
||||
*
|
||||
* Subset of ostree_sysroot_load(); performs basic initialization. Notably, one
|
||||
* can invoke `ostree_sysroot_get_fd()` after calling this function.
|
||||
*
|
||||
* It is not necessary to call this function if ostree_sysroot_load() is
|
||||
* invoked.
|
||||
*
|
||||
* Since: 2020.1
|
||||
*/
|
||||
gboolean
|
||||
ostree_sysroot_initialize (OstreeSysroot *self,
|
||||
GError **error)
|
||||
{
|
||||
if (!ensure_sysroot_fd (self, error))
|
||||
return FALSE;
|
||||
|
||||
if (self->loadstate < OSTREE_SYSROOT_LOAD_STATE_INIT)
|
||||
{
|
||||
/* Gather some global state; first if we have the global ostree-booted flag;
|
||||
* we'll use it to sanity check that we found a booted deployment for example.
|
||||
* Second, we also find out whether sysroot == /.
|
||||
*/
|
||||
if (!glnx_fstatat_allow_noent (AT_FDCWD, "/run/ostree-booted", NULL, 0, error))
|
||||
return FALSE;
|
||||
const gboolean ostree_booted = (errno == 0);
|
||||
|
||||
{ struct stat root_stbuf;
|
||||
if (!glnx_fstatat (AT_FDCWD, "/", &root_stbuf, 0, error))
|
||||
return FALSE;
|
||||
self->root_device = root_stbuf.st_dev;
|
||||
self->root_inode = root_stbuf.st_ino;
|
||||
}
|
||||
|
||||
struct stat self_stbuf;
|
||||
if (!glnx_fstatat (AT_FDCWD, gs_file_get_path_cached (self->path), &self_stbuf, 0, error))
|
||||
return FALSE;
|
||||
|
||||
const gboolean root_is_sysroot =
|
||||
(self->root_device == self_stbuf.st_dev &&
|
||||
self->root_inode == self_stbuf.st_ino);
|
||||
|
||||
self->root_is_ostree_booted = (ostree_booted && root_is_sysroot);
|
||||
self->loadstate = OSTREE_SYSROOT_LOAD_STATE_INIT;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Reload the staged deployment from the file in /run */
|
||||
gboolean
|
||||
_ostree_sysroot_reload_staged (OstreeSysroot *self,
|
||||
@@ -870,7 +1003,7 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
if (!ensure_sysroot_fd (self, error))
|
||||
if (!ostree_sysroot_initialize (self, error))
|
||||
return FALSE;
|
||||
|
||||
/* Here we also lazily initialize the repository. We didn't do this
|
||||
@@ -880,34 +1013,6 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self,
|
||||
if (!ensure_repo (self, error))
|
||||
return FALSE;
|
||||
|
||||
/* Gather some global state; first if we have the global ostree-booted flag;
|
||||
* we'll use it to sanity check that we found a booted deployment for example.
|
||||
* Second, we also find out whether sysroot == /.
|
||||
*/
|
||||
if (!self->loaded)
|
||||
{
|
||||
if (!glnx_fstatat_allow_noent (AT_FDCWD, "/run/ostree-booted", NULL, 0, error))
|
||||
return FALSE;
|
||||
const gboolean ostree_booted = (errno == 0);
|
||||
|
||||
{ struct stat root_stbuf;
|
||||
if (!glnx_fstatat (AT_FDCWD, "/", &root_stbuf, 0, error))
|
||||
return FALSE;
|
||||
self->root_device = root_stbuf.st_dev;
|
||||
self->root_inode = root_stbuf.st_ino;
|
||||
}
|
||||
|
||||
struct stat self_stbuf;
|
||||
if (!glnx_fstat (self->sysroot_fd, &self_stbuf, error))
|
||||
return FALSE;
|
||||
|
||||
const gboolean root_is_sysroot =
|
||||
(self->root_device == self_stbuf.st_dev &&
|
||||
self->root_inode == self_stbuf.st_ino);
|
||||
|
||||
self->root_is_ostree_booted = (ostree_booted && root_is_sysroot);
|
||||
}
|
||||
|
||||
int bootversion = 0;
|
||||
if (!read_current_bootversion (self, &bootversion, cancellable, error))
|
||||
return FALSE;
|
||||
@@ -990,8 +1095,8 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self,
|
||||
ostree_deployment_set_index (deployment, i);
|
||||
}
|
||||
|
||||
/* Determine whether we're "physical" or not, the first time we initialize */
|
||||
if (!self->loaded)
|
||||
/* Determine whether we're "physical" or not, the first time we load deployments */
|
||||
if (self->loadstate < OSTREE_SYSROOT_LOAD_STATE_LOADED)
|
||||
{
|
||||
/* If we have a booted deployment, the sysroot is / and we're definitely
|
||||
* not physical.
|
||||
@@ -1009,13 +1114,14 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self,
|
||||
self->is_physical = TRUE;
|
||||
}
|
||||
/* Otherwise, the default is FALSE */
|
||||
|
||||
self->loadstate = OSTREE_SYSROOT_LOAD_STATE_LOADED;
|
||||
}
|
||||
|
||||
self->bootversion = bootversion;
|
||||
self->subbootversion = subbootversion;
|
||||
self->deployments = deployments;
|
||||
deployments = NULL; /* Transfer ownership */
|
||||
self->loaded = TRUE;
|
||||
self->loaded_ts = stbuf.st_mtim;
|
||||
|
||||
if (out_changed)
|
||||
@@ -1044,7 +1150,7 @@ ostree_sysroot_get_subbootversion (OstreeSysroot *self)
|
||||
OstreeDeployment *
|
||||
ostree_sysroot_get_booted_deployment (OstreeSysroot *self)
|
||||
{
|
||||
g_return_val_if_fail (self->loaded, NULL);
|
||||
g_return_val_if_fail (self->loadstate == OSTREE_SYSROOT_LOAD_STATE_LOADED, NULL);
|
||||
|
||||
return self->booted_deployment;
|
||||
}
|
||||
@@ -1060,7 +1166,7 @@ ostree_sysroot_get_booted_deployment (OstreeSysroot *self)
|
||||
OstreeDeployment *
|
||||
ostree_sysroot_get_staged_deployment (OstreeSysroot *self)
|
||||
{
|
||||
g_return_val_if_fail (self->loaded, NULL);
|
||||
g_return_val_if_fail (self->loadstate == OSTREE_SYSROOT_LOAD_STATE_LOADED, NULL);
|
||||
|
||||
return self->staged_deployment;
|
||||
}
|
||||
@@ -1074,7 +1180,7 @@ ostree_sysroot_get_staged_deployment (OstreeSysroot *self)
|
||||
GPtrArray *
|
||||
ostree_sysroot_get_deployments (OstreeSysroot *self)
|
||||
{
|
||||
g_return_val_if_fail (self->loaded, NULL);
|
||||
g_return_val_if_fail (self->loadstate == OSTREE_SYSROOT_LOAD_STATE_LOADED, NULL);
|
||||
|
||||
GPtrArray *copy = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref);
|
||||
for (guint i = 0; i < self->deployments->len; i++)
|
||||
@@ -1163,8 +1269,8 @@ ostree_sysroot_get_repo (OstreeSysroot *self,
|
||||
* @self: Sysroot
|
||||
*
|
||||
* This function is a variant of ostree_sysroot_get_repo() that cannot fail, and
|
||||
* returns a cached repository. Can only be called after ostree_sysroot_load()
|
||||
* has been invoked successfully.
|
||||
* returns a cached repository. Can only be called after ostree_sysroot_initialize()
|
||||
* or ostree_sysroot_load() has been invoked successfully.
|
||||
*
|
||||
* Returns: (transfer none): The OSTree repository in sysroot @self.
|
||||
*
|
||||
@@ -1173,7 +1279,7 @@ ostree_sysroot_get_repo (OstreeSysroot *self,
|
||||
OstreeRepo *
|
||||
ostree_sysroot_repo (OstreeSysroot *self)
|
||||
{
|
||||
g_return_val_if_fail (self->loaded, NULL);
|
||||
g_return_val_if_fail (self->loadstate >= OSTREE_SYSROOT_LOAD_STATE_LOADED, NULL);
|
||||
g_assert (self->repo);
|
||||
return self->repo;
|
||||
}
|
||||
@@ -1368,6 +1474,10 @@ ostree_sysroot_lock (OstreeSysroot *self,
|
||||
{
|
||||
if (!ensure_sysroot_fd (self, error))
|
||||
return FALSE;
|
||||
|
||||
if (!_ostree_sysroot_ensure_writable (self, error))
|
||||
return FALSE;
|
||||
|
||||
return glnx_make_lock_file (self->sysroot_fd, OSTREE_SYSROOT_LOCKFILE,
|
||||
LOCK_EX, &self->lock, error);
|
||||
}
|
||||
@@ -1391,12 +1501,14 @@ ostree_sysroot_try_lock (OstreeSysroot *self,
|
||||
gboolean *out_acquired,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(GError) local_error = NULL;
|
||||
|
||||
if (!ensure_sysroot_fd (self, error))
|
||||
return FALSE;
|
||||
|
||||
if (!_ostree_sysroot_ensure_writable (self, error))
|
||||
return FALSE;
|
||||
|
||||
/* Note use of LOCK_NB */
|
||||
g_autoptr(GError) local_error = NULL;
|
||||
if (!glnx_make_lock_file (self->sysroot_fd, OSTREE_SYSROOT_LOCKFILE,
|
||||
LOCK_EX | LOCK_NB, &self->lock, &local_error))
|
||||
{
|
||||
@@ -1509,7 +1621,7 @@ ostree_sysroot_init_osname (OstreeSysroot *self,
|
||||
GCancellable *cancellable,
|
||||
GError **error)
|
||||
{
|
||||
if (!ensure_sysroot_fd (self, error))
|
||||
if (!_ostree_sysroot_ensure_writable (self, error))
|
||||
return FALSE;
|
||||
|
||||
const char *deploydir = glnx_strjoina ("ostree/deploy/", osname);
|
||||
|
||||
@@ -41,12 +41,22 @@ OstreeSysroot* ostree_sysroot_new (GFile *path);
|
||||
_OSTREE_PUBLIC
|
||||
OstreeSysroot* ostree_sysroot_new_default (void);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
void ostree_sysroot_set_mount_namespace_in_use (OstreeSysroot *self);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
GFile *ostree_sysroot_get_path (OstreeSysroot *self);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sysroot_is_booted (OstreeSysroot *self);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
int ostree_sysroot_get_fd (OstreeSysroot *self);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sysroot_initialize (OstreeSysroot *self,
|
||||
GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sysroot_load (OstreeSysroot *self,
|
||||
GCancellable *cancellable,
|
||||
@@ -90,6 +100,10 @@ GFile * ostree_sysroot_get_deployment_origin_path (GFile *deployment_path);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sysroot_lock (OstreeSysroot *self, GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sysroot_lock_with_mount_namespace (OstreeSysroot *self, GError **error);
|
||||
|
||||
_OSTREE_PUBLIC
|
||||
gboolean ostree_sysroot_try_lock (OstreeSysroot *self,
|
||||
gboolean *out_acquired,
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
*
|
||||
* Since: 2017.4
|
||||
*/
|
||||
#define OSTREE_YEAR_VERSION (2019)
|
||||
#define OSTREE_YEAR_VERSION (2020)
|
||||
|
||||
/**
|
||||
* OSTREE_RELEASE_VERSION:
|
||||
@@ -43,7 +43,7 @@
|
||||
*
|
||||
* Since: 2017.4
|
||||
*/
|
||||
#define OSTREE_RELEASE_VERSION (6)
|
||||
#define OSTREE_RELEASE_VERSION (2)
|
||||
|
||||
/**
|
||||
* OSTREE_VERSION
|
||||
@@ -52,7 +52,7 @@
|
||||
*
|
||||
* Since: 2017.4
|
||||
*/
|
||||
#define OSTREE_VERSION (2019.6)
|
||||
#define OSTREE_VERSION (2020.2)
|
||||
|
||||
/**
|
||||
* OSTREE_VERSION_S:
|
||||
@@ -62,7 +62,7 @@
|
||||
*
|
||||
* Since: 2017.4
|
||||
*/
|
||||
#define OSTREE_VERSION_S "2019.6"
|
||||
#define OSTREE_VERSION_S "2020.2"
|
||||
|
||||
#define OSTREE_ENCODE_VERSION(year,release) \
|
||||
((year) << 16 | (release))
|
||||
@@ -100,4 +100,4 @@
|
||||
*
|
||||
* Since: 2019.3
|
||||
*/
|
||||
#define OSTREE_BUILT_FEATURES "libcurl libsoup gpgme libarchive selinux openssl libmount release p2p"
|
||||
#define OSTREE_BUILT_FEATURES "libcurl libsoup gpgme ex-fsverity libarchive selinux openssl libmount systemd release p2p"
|
||||
@@ -494,6 +494,7 @@ on_dir_changed (GFileMonitor *mon,
|
||||
|
||||
if (event == G_FILE_MONITOR_EVENT_DELETED)
|
||||
{
|
||||
httpd_log (self, "root directory removed, exiting\n");
|
||||
self->running = FALSE;
|
||||
g_main_context_wakeup (NULL);
|
||||
}
|
||||
@@ -507,6 +508,7 @@ run (int argc, char **argv, GCancellable *cancellable, GError **error)
|
||||
const char *dirpath;
|
||||
OtTrivialHttpd appstruct = { 0, };
|
||||
OtTrivialHttpd *app = &appstruct;
|
||||
int pipefd[2] = { -1, -1 };
|
||||
glnx_unref_object SoupServer *server = NULL;
|
||||
g_autoptr(GFileMonitor) dirmon = NULL;
|
||||
|
||||
@@ -540,17 +542,86 @@ run (int argc, char **argv, GCancellable *cancellable, GError **error)
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (opt_daemonize && (g_strcmp0 (opt_log, "-") == 0))
|
||||
{
|
||||
ot_util_usage_error (context, "Cannot use --log-file=- and --daemonize at the same time", error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Fork early before glib sets up its worker context and thread since they'll
|
||||
* be gone once the parent exits. The parent waits on a pipe with the child to
|
||||
* handle setup errors. The child writes a 0 when setup is successful and a 1
|
||||
* otherwise.
|
||||
*/
|
||||
if (opt_daemonize)
|
||||
{
|
||||
if (pipe (pipefd) == -1)
|
||||
{
|
||||
glnx_set_error_from_errno (error);
|
||||
goto out;
|
||||
}
|
||||
|
||||
pid_t pid = fork();
|
||||
if (pid == -1)
|
||||
{
|
||||
glnx_set_error_from_errno (error);
|
||||
goto out;
|
||||
}
|
||||
else if (pid > 0)
|
||||
{
|
||||
/* Parent, read the child status from the pipe */
|
||||
glnx_close_fd (&pipefd[1]);
|
||||
guint8 buf;
|
||||
int res = TEMP_FAILURE_RETRY (read (pipefd[0], &buf, 1));
|
||||
if (res < 0)
|
||||
{
|
||||
glnx_set_error_from_errno (error);
|
||||
goto out;
|
||||
}
|
||||
else if (res == 0)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Child process closed pipe without writing status");
|
||||
goto out;
|
||||
}
|
||||
|
||||
g_debug ("Read %u from child", buf);
|
||||
if (buf > 0)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Child process failed during setup");
|
||||
goto out;
|
||||
}
|
||||
glnx_close_fd (&pipefd[0]);
|
||||
|
||||
ret = TRUE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Child, continue */
|
||||
glnx_close_fd (&pipefd[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Since we're used for testing purposes, let's just do this by
|
||||
* default. This ensures we exit when our parent does.
|
||||
*/
|
||||
if (prctl (PR_SET_PDEATHSIG, SIGTERM) != 0)
|
||||
{
|
||||
if (errno != ENOSYS)
|
||||
{
|
||||
glnx_set_error_from_errno (error);
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (opt_log)
|
||||
{
|
||||
GOutputStream *stream = NULL;
|
||||
|
||||
if (g_strcmp0 (opt_log, "-") == 0)
|
||||
{
|
||||
if (opt_daemonize)
|
||||
{
|
||||
ot_util_usage_error (context, "Cannot use --log-file=- and --daemonize at the same time", error);
|
||||
goto out;
|
||||
}
|
||||
stream = G_OUTPUT_STREAM (g_unix_output_stream_new (STDOUT_FILENO, FALSE));
|
||||
}
|
||||
else
|
||||
@@ -625,45 +696,39 @@ run (int argc, char **argv, GCancellable *cancellable, GError **error)
|
||||
#if !SOUP_CHECK_VERSION(2, 48, 0)
|
||||
soup_server_run_async (server);
|
||||
#endif
|
||||
|
||||
|
||||
if (opt_daemonize)
|
||||
{
|
||||
pid_t pid = fork();
|
||||
if (pid == -1)
|
||||
/* Write back a 0 to the pipe to indicate setup was successful. */
|
||||
guint8 buf = 0;
|
||||
g_debug ("Writing %u to parent", buf);
|
||||
if (TEMP_FAILURE_RETRY (write (pipefd[1], &buf, 1)) == -1)
|
||||
{
|
||||
int errsv = errno;
|
||||
g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
|
||||
g_strerror (errsv));
|
||||
glnx_set_error_from_errno (error);
|
||||
goto out;
|
||||
}
|
||||
else if (pid > 0)
|
||||
{
|
||||
ret = TRUE;
|
||||
goto out;
|
||||
}
|
||||
/* Child, continue */
|
||||
glnx_close_fd (&pipefd[1]);
|
||||
|
||||
if (setsid () < 0)
|
||||
err (1, "setsid");
|
||||
{
|
||||
glnx_set_prefix_error_from_errno (error, "%s", "setsid: ");
|
||||
goto out;
|
||||
}
|
||||
/* Daemonising: close stdout/stderr so $() et al work on us */
|
||||
if (freopen("/dev/null", "r", stdin) == NULL)
|
||||
err (1, "freopen");
|
||||
if (freopen("/dev/null", "w", stdout) == NULL)
|
||||
err (1, "freopen");
|
||||
if (freopen("/dev/null", "w", stderr) == NULL)
|
||||
err (1, "freopen");
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Since we're used for testing purposes, let's just do this by
|
||||
* default. This ensures we exit when our parent does.
|
||||
*/
|
||||
if (prctl (PR_SET_PDEATHSIG, SIGTERM) != 0)
|
||||
{
|
||||
if (errno != ENOSYS)
|
||||
{
|
||||
glnx_set_error_from_errno (error);
|
||||
goto out;
|
||||
}
|
||||
glnx_set_prefix_error_from_errno (error, "%s", "freopen: ");
|
||||
goto out;
|
||||
}
|
||||
if (freopen("/dev/null", "w", stdout) == NULL)
|
||||
{
|
||||
glnx_set_prefix_error_from_errno (error, "%s", "freopen: ");
|
||||
goto out;
|
||||
}
|
||||
if (freopen("/dev/null", "w", stderr) == NULL)
|
||||
{
|
||||
glnx_set_prefix_error_from_errno (error, "%s", "freopen: ");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -699,6 +764,21 @@ run (int argc, char **argv, GCancellable *cancellable, GError **error)
|
||||
|
||||
ret = TRUE;
|
||||
out:
|
||||
if (pipefd[0] >= 0)
|
||||
{
|
||||
/* Read end in the parent. This should only be open on errors. */
|
||||
g_assert_false (ret);
|
||||
glnx_close_fd (&pipefd[0]);
|
||||
}
|
||||
if (pipefd[1] >= 0)
|
||||
{
|
||||
/* Write end in the child. This should only be open on errors. */
|
||||
g_assert_false (ret);
|
||||
guint8 buf = 1;
|
||||
g_debug ("Writing %u to parent", buf);
|
||||
(void) TEMP_FAILURE_RETRY (write (pipefd[1], &buf, 1));
|
||||
glnx_close_fd (&pipefd[1]);
|
||||
}
|
||||
if (app->root_dfd != -1)
|
||||
(void) close (app->root_dfd);
|
||||
g_clear_object (&app->log);
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
#include "ostree-cmdprivate.h"
|
||||
#include "ostree.h"
|
||||
|
||||
static GOptionEntry options[] = {
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
/* Called by ostree-finalize-staged.service, and in turn
|
||||
* invokes a cmdprivate function inside the shared library.
|
||||
*/
|
||||
@@ -46,11 +50,13 @@ ot_admin_builtin_finalize_staged (int argc, char **argv, OstreeCommandInvocation
|
||||
if (fstatat (AT_FDCWD, "/run/ostree-booted", &stbuf, 0) < 0)
|
||||
return TRUE;
|
||||
|
||||
g_autoptr(GFile) sysroot_file = g_file_new_for_path ("/");
|
||||
g_autoptr(OstreeSysroot) sysroot = ostree_sysroot_new (sysroot_file);
|
||||
|
||||
if (!ostree_sysroot_load (sysroot, cancellable, error))
|
||||
g_autoptr(GOptionContext) context = g_option_context_new ("");
|
||||
g_autoptr(OstreeSysroot) sysroot = NULL;
|
||||
if (!ostree_admin_option_context_parse (context, options, &argc, &argv,
|
||||
OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER,
|
||||
invocation, &sysroot, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
if (!ostree_cmd__private__()->ostree_finalize_staged (sysroot, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ static gboolean opt_print_related;
|
||||
static char* opt_print_variant_type;
|
||||
static char* opt_print_metadata_key;
|
||||
static char* opt_print_detached_metadata_key;
|
||||
static gboolean opt_print_sizes;
|
||||
static gboolean opt_raw;
|
||||
static gboolean opt_no_byteswap;
|
||||
static char *opt_gpg_homedir;
|
||||
@@ -48,6 +49,7 @@ static GOptionEntry options[] = {
|
||||
{ "print-variant-type", 0, 0, G_OPTION_ARG_STRING, &opt_print_variant_type, "Memory map OBJECT (in this case a filename) to the GVariant type string", "TYPE" },
|
||||
{ "print-metadata-key", 0, 0, G_OPTION_ARG_STRING, &opt_print_metadata_key, "Print string value of metadata key", "KEY" },
|
||||
{ "print-detached-metadata-key", 0, 0, G_OPTION_ARG_STRING, &opt_print_detached_metadata_key, "Print string value of detached metadata key", "KEY" },
|
||||
{ "print-sizes", 0, 0, G_OPTION_ARG_NONE, &opt_print_sizes, "Show the commit size metadata", NULL },
|
||||
{ "raw", 0, 0, G_OPTION_ARG_NONE, &opt_raw, "Show raw variant data" },
|
||||
{ "no-byteswap", 'B', 0, G_OPTION_ARG_NONE, &opt_no_byteswap, "Do not automatically convert variant data from big endian" },
|
||||
{ "gpg-homedir", 0, 0, G_OPTION_ARG_FILENAME, &opt_gpg_homedir, "GPG Homedir to use when looking for keyrings", "HOMEDIR"},
|
||||
@@ -146,6 +148,65 @@ do_print_metadata_key (OstreeRepo *repo,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
do_print_sizes (OstreeRepo *repo,
|
||||
const char *rev,
|
||||
GError **error)
|
||||
{
|
||||
g_autoptr(GVariant) commit = NULL;
|
||||
if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, rev,
|
||||
&commit, error))
|
||||
{
|
||||
g_prefix_error (error, "Failed to read commit: ");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_autoptr(GPtrArray) sizes = NULL;
|
||||
if (!ostree_commit_get_object_sizes (commit, &sizes, error))
|
||||
return FALSE;
|
||||
|
||||
gint64 new_archived = 0;
|
||||
gint64 new_unpacked = 0;
|
||||
gsize new_objects = 0;
|
||||
gint64 archived = 0;
|
||||
gint64 unpacked = 0;
|
||||
gsize objects = 0;
|
||||
for (guint i = 0; i < sizes->len; i++)
|
||||
{
|
||||
OstreeCommitSizesEntry *entry = sizes->pdata[i];
|
||||
|
||||
archived += entry->archived;
|
||||
unpacked += entry->unpacked;
|
||||
objects++;
|
||||
|
||||
gboolean exists;
|
||||
if (!ostree_repo_has_object (repo, entry->objtype, entry->checksum,
|
||||
&exists, NULL, error))
|
||||
return FALSE;
|
||||
|
||||
if (!exists)
|
||||
{
|
||||
/* Object not in local repo */
|
||||
new_archived += entry->archived;
|
||||
new_unpacked += entry->unpacked;
|
||||
new_objects++;
|
||||
}
|
||||
}
|
||||
|
||||
g_autofree char *new_archived_str = g_format_size (new_archived);
|
||||
g_autofree char *archived_str = g_format_size (archived);
|
||||
g_autofree char *new_unpacked_str = g_format_size (new_unpacked);
|
||||
g_autofree char *unpacked_str = g_format_size (unpacked);
|
||||
g_print ("Compressed size (needed/total): %s/%s\n"
|
||||
"Unpacked size (needed/total): %s/%s\n"
|
||||
"Number of objects (needed/total): %" G_GSIZE_FORMAT "/%" G_GSIZE_FORMAT "\n",
|
||||
new_archived_str, archived_str,
|
||||
new_unpacked_str, unpacked_str,
|
||||
new_objects, objects);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
print_object (OstreeRepo *repo,
|
||||
OstreeObjectType objtype,
|
||||
@@ -279,6 +340,14 @@ ostree_builtin_show (int argc, char **argv, OstreeCommandInvocation *invocation,
|
||||
if (!do_print_variant_generic (G_VARIANT_TYPE (opt_print_variant_type), rev, error))
|
||||
return FALSE;
|
||||
}
|
||||
else if (opt_print_sizes)
|
||||
{
|
||||
if (!ostree_repo_resolve_rev (repo, rev, FALSE, &resolved_rev, error))
|
||||
return FALSE;
|
||||
|
||||
if (!do_print_sizes (repo, resolved_rev, error))
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
gboolean found = FALSE;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/statvfs.h>
|
||||
|
||||
#include "ot-main.h"
|
||||
#include "ostree.h"
|
||||
@@ -434,10 +435,46 @@ ostree_admin_option_context_parse (GOptionContext *context,
|
||||
sysroot_path = g_file_new_for_path (opt_sysroot);
|
||||
|
||||
g_autoptr(OstreeSysroot) sysroot = ostree_sysroot_new (sysroot_path);
|
||||
if (!ostree_sysroot_initialize (sysroot, error))
|
||||
return FALSE;
|
||||
g_signal_connect (sysroot, "journal-msg", G_CALLBACK (on_sysroot_journal_msg), NULL);
|
||||
|
||||
if ((flags & OSTREE_ADMIN_BUILTIN_FLAG_UNLOCKED) == 0)
|
||||
{
|
||||
/* If we're requested to lock the sysroot, first check if we're operating
|
||||
* on a booted (not physical) sysroot. Then find out if the /sysroot
|
||||
* subdir is a read-only mount point, and if so, create a new mount
|
||||
* namespace and tell the sysroot that we've done so. See the docs for
|
||||
* ostree_sysroot_set_mount_namespace_in_use().
|
||||
*
|
||||
* This is a conservative approach; we could just always
|
||||
* unshare() too.
|
||||
*/
|
||||
if (ostree_sysroot_is_booted (sysroot))
|
||||
{
|
||||
int sysroot_fd = ostree_sysroot_get_fd (sysroot);
|
||||
g_assert_cmpint (sysroot_fd, !=, -1);
|
||||
|
||||
glnx_autofd int sysroot_subdir_fd = glnx_opendirat_with_errno (sysroot_fd, "sysroot", TRUE);
|
||||
if (sysroot_subdir_fd < 0)
|
||||
{
|
||||
if (errno != ENOENT)
|
||||
return glnx_throw_errno_prefix (error, "opendirat");
|
||||
}
|
||||
else if (getuid () == 0)
|
||||
{
|
||||
struct statvfs stvfs;
|
||||
if (fstatvfs (sysroot_subdir_fd, &stvfs) < 0)
|
||||
return glnx_throw_errno_prefix (error, "fstatvfs");
|
||||
if (stvfs.f_flag & ST_RDONLY)
|
||||
{
|
||||
if (unshare (CLONE_NEWNS) < 0)
|
||||
return glnx_throw_errno_prefix (error, "preparing writable sysroot: unshare (CLONE_NEWNS)");
|
||||
ostree_sysroot_set_mount_namespace_in_use (sysroot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Released when sysroot is finalized, or on process exit */
|
||||
if (!ot_admin_sysroot_lock (sysroot, error))
|
||||
return FALSE;
|
||||
|
||||
@@ -29,6 +29,28 @@
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
/* The high level goal of ostree-prepare-root.service is to run inside
|
||||
* the initial ram disk (if one is in use) and set up the `/` mountpoint
|
||||
* to be the deployment root, using the ostree= kernel commandline
|
||||
* argument to find the target deployment root.
|
||||
*
|
||||
* It's really the heart of how ostree works - basically multiple
|
||||
* hardlinked chroot() targets are maintained, this one does the equivalent
|
||||
* of chroot().
|
||||
*
|
||||
* If using systemd, an excellent reference is `man bootup`. This
|
||||
* service runs Before=initrd-root-fs.target. At this point it's
|
||||
* assumed that the block storage and root filesystem are mounted at
|
||||
* /sysroot - i.e. /sysroot points to the *physical* root before
|
||||
* this service runs. After, `/` is the deployment root.
|
||||
*
|
||||
* There is also a secondary mode for this service when an initrd isn't
|
||||
* used - instead the binary must be statically linked (and the kernel
|
||||
* must have mounted the rootfs itself) - then we set things up and
|
||||
* exec the real init directly. This can be popular in embedded
|
||||
* systems to increase bootup speed.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <sys/mount.h>
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/mount.h>
|
||||
@@ -37,10 +38,14 @@
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include "ostree-mount-util.h"
|
||||
#include "glnx-backport-autocleanups.h"
|
||||
|
||||
static void
|
||||
do_remount (const char *target)
|
||||
do_remount (const char *target,
|
||||
bool writable)
|
||||
{
|
||||
struct stat stbuf;
|
||||
if (lstat (target, &stbuf) < 0)
|
||||
@@ -54,20 +59,41 @@ do_remount (const char *target)
|
||||
struct statvfs stvfsbuf;
|
||||
if (statvfs (target, &stvfsbuf) == -1)
|
||||
return;
|
||||
/* If no read-only flag, skip it */
|
||||
if ((stvfsbuf.f_flag & ST_RDONLY) == 0)
|
||||
|
||||
const bool currently_writable = ((stvfsbuf.f_flag & ST_RDONLY) == 0);
|
||||
if (writable == currently_writable)
|
||||
return;
|
||||
/* It's a mounted, read-only fs; remount it */
|
||||
if (mount (target, target, NULL, MS_REMOUNT | MS_SILENT, NULL) < 0)
|
||||
|
||||
int mnt_flags = MS_REMOUNT | MS_SILENT;
|
||||
if (!writable)
|
||||
mnt_flags |= MS_RDONLY;
|
||||
if (mount (target, target, NULL, mnt_flags, NULL) < 0)
|
||||
{
|
||||
/* Also ignore EINVAL - if the target isn't a mountpoint
|
||||
* already, then assume things are OK.
|
||||
*/
|
||||
if (errno != EINVAL)
|
||||
err (EXIT_FAILURE, "failed to remount %s", target);
|
||||
if (errno != EINVAL)
|
||||
err (EXIT_FAILURE, "failed to remount(%s) %s", writable ? "rw" : "ro", target);
|
||||
else
|
||||
return;
|
||||
}
|
||||
else
|
||||
printf ("Remounted: %s\n", target);
|
||||
|
||||
printf ("Remounted %s: %s\n", writable ? "rw" : "ro", target);
|
||||
}
|
||||
|
||||
static bool
|
||||
sysroot_is_configured_ro (void)
|
||||
{
|
||||
struct stat stbuf;
|
||||
static const char config_path[] = "/ostree/repo/config";
|
||||
if (stat (config_path, &stbuf) != 0)
|
||||
return false;
|
||||
|
||||
g_autoptr(GKeyFile) keyfile = g_key_file_new ();
|
||||
if (!g_key_file_load_from_file (keyfile, config_path, 0, NULL))
|
||||
return false;
|
||||
|
||||
return g_key_file_get_boolean (keyfile, "sysroot", "readonly", NULL);
|
||||
}
|
||||
|
||||
int
|
||||
@@ -95,8 +121,38 @@ main(int argc, char *argv[])
|
||||
exit (EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
do_remount ("/sysroot");
|
||||
do_remount ("/var");
|
||||
/* Query the repository configuration - this is an operating system builder
|
||||
* choice.
|
||||
* */
|
||||
const bool sysroot_readonly = sysroot_is_configured_ro ();
|
||||
|
||||
/* Mount the sysroot read-only if we're configured to do so.
|
||||
* Note we only get here if / is already writable.
|
||||
*/
|
||||
do_remount ("/sysroot", !sysroot_readonly);
|
||||
|
||||
if (sysroot_readonly)
|
||||
{
|
||||
/* Now, /etc is not normally a bind mount, but remounting the
|
||||
* sysroot above made it read-only since it's on the same filesystem.
|
||||
* Make it a self-bind mount, so we can then mount it read-write.
|
||||
*/
|
||||
if (mount ("/etc", "/etc", NULL, MS_BIND, NULL) < 0)
|
||||
err (EXIT_FAILURE, "failed to make /etc a bind mount");
|
||||
do_remount ("/etc", true);
|
||||
}
|
||||
|
||||
/* If /var was created as as an OSTree default bind mount (instead of being a separate filesystem)
|
||||
* then remounting the root mount read-only also remounted it.
|
||||
* So just like /etc, we need to make it read-write by default.
|
||||
* If it was a separate filesystem, we expect it to be writable anyways,
|
||||
* so it doesn't hurt to remount it if so.
|
||||
*
|
||||
* And if we started out with a writable system root, then we need
|
||||
* to ensure that the /var bind mount created by the systemd generator
|
||||
* is writable too.
|
||||
*/
|
||||
do_remount ("/var", true);
|
||||
|
||||
exit (EXIT_SUCCESS);
|
||||
}
|
||||
Reference in new issue
Block a user