diff --git a/Makefile-tests.am b/Makefile-tests.am index a417937..3fbc94b 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -105,6 +105,7 @@ _installed_or_uninstalled_test_scripts = \ tests/test-admin-deploy-nomerge.sh \ tests/test-admin-deploy-none.sh \ tests/test-admin-deploy-bootid-gc.sh \ + tests/test-osupdate-dtb.sh \ tests/test-admin-instutil-set-kargs.sh \ tests/test-admin-upgrade-not-backwards.sh \ tests/test-admin-pull-deploy-commit.sh \ @@ -114,6 +115,8 @@ _installed_or_uninstalled_test_scripts = \ tests/test-reset-nonlinear.sh \ tests/test-oldstyle-partial.sh \ tests/test-delta.sh \ + tests/test-delta-sign.sh \ + tests/test-delta-ed25519.sh \ tests/test-xattrs.sh \ tests/test-auto-summary.sh \ tests/test-prune.sh \ diff --git a/Makefile.am b/Makefile.am index cd04a05..87a705c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -43,6 +43,9 @@ AM_DISTCHECK_CONFIGURE_FLAGS += \ GITIGNOREFILES = aclocal.m4 build-aux/ buildutil/*.m4 config.h.in gtk-doc.make +# Generated by coreos-assembler build-fast and kola +GITIGNOREFILES += fastbuild-*.qcow2 _kola_temp/ + SUBDIRS += . if ENABLE_GTK_DOC diff --git a/Makefile.in b/Makefile.in index 541e5d4..e90c219 100644 --- a/Makefile.in +++ b/Makefile.in @@ -2056,13 +2056,15 @@ am__EXEEXT_25 = tests/test-basic.sh tests/test-basic-user.sh \ tests/test-admin-deploy-nomerge.sh \ tests/test-admin-deploy-none.sh \ tests/test-admin-deploy-bootid-gc.sh \ + tests/test-osupdate-dtb.sh \ tests/test-admin-instutil-set-kargs.sh \ tests/test-admin-upgrade-not-backwards.sh \ tests/test-admin-pull-deploy-commit.sh \ tests/test-admin-pull-deploy-split.sh \ tests/test-admin-locking.sh tests/test-admin-deploy-clean.sh \ tests/test-reset-nonlinear.sh tests/test-oldstyle-partial.sh \ - tests/test-delta.sh tests/test-xattrs.sh \ + tests/test-delta.sh tests/test-delta-sign.sh \ + tests/test-delta-ed25519.sh tests/test-xattrs.sh \ tests/test-auto-summary.sh tests/test-prune.sh \ tests/test-concurrency.py tests/test-refs.sh \ tests/test-demo-buildsystem.sh tests/test-switchroot.sh \ @@ -2504,8 +2506,10 @@ ALL_LOCAL_RULES = tests/libreaddir-rand.so shortened_sysconfdir = $$(echo "$(sysconfdir)" | sed -e 's|^$(prefix)||' -e 's|^/||') OSTREE_GITREV = $(shell cd $(srcdir) && if command -v git >/dev/null 2>&1 && test -d .git; then git describe --abbrev=42 --tags --always HEAD; fi) ACLOCAL_AMFLAGS = -I buildutil -I libglnx ${ACLOCAL_FLAGS} + +# Generated by coreos-assembler build-fast and kola GITIGNOREFILES = aclocal.m4 build-aux/ buildutil/*.m4 config.h.in \ - gtk-doc.make $(am__append_72) + gtk-doc.make fastbuild-*.qcow2 _kola_temp/ $(am__append_72) OT_INTERNAL_GIO_UNIX_CFLAGS = $(OT_DEP_GIO_UNIX_CFLAGS) OT_INTERNAL_GIO_UNIX_LIBS = $(OT_DEP_GIO_UNIX_LIBS) OT_INTERNAL_SOUP_CFLAGS = $(OT_DEP_SOUP_CFLAGS) @@ -2909,13 +2913,15 @@ _installed_or_uninstalled_test_scripts = tests/test-basic.sh \ tests/test-admin-deploy-nomerge.sh \ tests/test-admin-deploy-none.sh \ tests/test-admin-deploy-bootid-gc.sh \ + tests/test-osupdate-dtb.sh \ tests/test-admin-instutil-set-kargs.sh \ tests/test-admin-upgrade-not-backwards.sh \ tests/test-admin-pull-deploy-commit.sh \ tests/test-admin-pull-deploy-split.sh \ tests/test-admin-locking.sh tests/test-admin-deploy-clean.sh \ tests/test-reset-nonlinear.sh tests/test-oldstyle-partial.sh \ - tests/test-delta.sh tests/test-xattrs.sh \ + tests/test-delta.sh tests/test-delta-sign.sh \ + tests/test-delta-ed25519.sh tests/test-xattrs.sh \ tests/test-auto-summary.sh tests/test-prune.sh \ tests/test-concurrency.py tests/test-refs.sh \ tests/test-demo-buildsystem.sh tests/test-switchroot.sh \ @@ -8357,6 +8363,13 @@ tests/test-admin-deploy-bootid-gc.sh.log: tests/test-admin-deploy-bootid-gc.sh --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) +tests/test-osupdate-dtb.sh.log: tests/test-osupdate-dtb.sh + @p='tests/test-osupdate-dtb.sh'; \ + b='tests/test-osupdate-dtb.sh'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) tests/test-admin-instutil-set-kargs.sh.log: tests/test-admin-instutil-set-kargs.sh @p='tests/test-admin-instutil-set-kargs.sh'; \ b='tests/test-admin-instutil-set-kargs.sh'; \ @@ -8420,6 +8433,20 @@ tests/test-delta.sh.log: tests/test-delta.sh --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) +tests/test-delta-sign.sh.log: tests/test-delta-sign.sh + @p='tests/test-delta-sign.sh'; \ + b='tests/test-delta-sign.sh'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +tests/test-delta-ed25519.sh.log: tests/test-delta-ed25519.sh + @p='tests/test-delta-ed25519.sh'; \ + b='tests/test-delta-ed25519.sh'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) tests/test-xattrs.sh.log: tests/test-xattrs.sh @p='tests/test-xattrs.sh'; \ b='tests/test-xattrs.sh'; \ diff --git a/README.md b/README.md index 1ef8e30..be6f645 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,4 @@ -libostree ---------- - -New! See the docs online at [Read The Docs (OSTree)](https://ostree.readthedocs.org/en/latest/ ) - ------ +# libostree This project is now known as "libostree", though it is still appropriate to use the previous name: "OSTree" (or "ostree"). The focus is on projects which use @@ -36,8 +31,12 @@ version of projects like [flatpak](https://github.com/flatpak/flatpak) which use libostree for applications, rather than hosts. -Operating systems and distributions using OSTree ---------------------- +## Documentation + +For more information, see the [project documentation](docs/index.md) or the +[project documentation website](https://ostreedev.github.io/ostree). + +## Operating systems and distributions using OSTree [Endless OS](https://endlessos.com/) uses libostree for their host system as well as flatpak. See @@ -63,8 +62,7 @@ system for GNOME. [Liri OS](https://liri.io/download/silverblue/) has the option to install their distribution using ostree. -Distribution build tools ------------------------- +## Distribution build tools [meta-updater](https://github.com/advancedtelematic/meta-updater) is a layer available for [OpenEmbedded](http://www.openembedded.org/wiki/Main_Page) @@ -79,8 +77,7 @@ integration tool supports importing and exporting from libostree repos. Fedora [coreos-assembler](https://github.com/coreos/coreos-assembler) is the build tool used to generate Fedora CoreOS derivatives. -Projects linking to libostree ------------------------------ +## Projects linking to libostree [rpm-ostree](https://github.com/projectatomic/rpm-ostree) is used by the Fedora-derived operating systems listed above. It is a full hybrid @@ -98,8 +95,7 @@ use the "libostree host system" aspects (e.g. bootloader management), just the "git-like hardlink dedup". For example, flatpak supports a per-user OSTree repository. -Language bindings ----- +## Language bindings libostree is accessible via [GObject Introspection](https://gi.readthedocs.io/en/latest/); any language which has implemented the GI binding model should work. @@ -114,8 +110,7 @@ for statically compiled languages. Here's a list of such bindings: - [ostree-go](https://github.com/ostreedev/ostree-go/) - [ostree-rs](https://gitlab.com/fkrull/ostree-rs/) -Building --------- +## Building Releases are available as GPG signed git tags, and most recent versions support extended validation using @@ -139,19 +134,11 @@ make make install DESTDIR=/path/to/dest ``` -More documentation ------------------- - -New! See the docs online at [Read The Docs (OSTree)](https://ostree.readthedocs.org/en/latest/ ) - -Contributing ------------- +## Contributing See [Contributing](docs/CONTRIBUTING.md). - -Licensing -------- +## Licensing The licensing for the *code* of libostree can be canonically found in the individual files; and the overall status in the [COPYING](https://github.com/ostreedev/ostree/blob/master/COPYING) diff --git a/apidoc/html/ostree-OstreeRepo.html b/apidoc/html/ostree-OstreeRepo.html index c9a2783..efccacd 100644 --- a/apidoc/html/ostree-OstreeRepo.html +++ b/apidoc/html/ostree-OstreeRepo.html @@ -914,11 +914,27 @@ gboolean +ostree_repo_static_delta_execute_offline_with_signature () + + + + +gboolean + + ostree_repo_static_delta_execute_offline () +gboolean + + +ostree_repo_static_delta_verify_signature () + + + + GHashTable * @@ -5279,6 +5295,7 @@ one to easily set up SELinux labeling from a base commit.

+

Since: 2020.4


@@ -6471,6 +6488,8 @@ for input files

  • verbose: b: Print diagnostic messages. Default FALSE.

  • endianness: b: Deltas use host byte order by default; this option allows choosing (G_BIG_ENDIAN or G_LITTLE_ENDIAN)

  • filename: ay: Save delta superblock to this filename, and parts in the same directory. Default saves to repository.

  • +
  • sign-name: ay: Signature type to use.

  • +
  • sign-key-ids: as: Array of keys used to sign delta superblock.

  • Parameters

    @@ -6527,6 +6546,69 @@ for input files


    +

    ostree_repo_static_delta_execute_offline_with_signature ()

    +
    gboolean
    +ostree_repo_static_delta_execute_offline_with_signature
    +                               (OstreeRepo *self,
    +                                GFile *dir_or_file,
    +                                OstreeSign *sign,
    +                                gboolean skip_validation,
    +                                GCancellable *cancellable,
    +                                GError **error);
    +

    Given a directory representing an already-downloaded static delta +on disk, apply it, generating a new commit. +If sign is passed, the static delta signature is verified. +If sign-verify-deltas configuration option is set and static delta is signed, +signature verification will be mandatory before apply the static delta. +The directory must be named with the form "FROM-TO", where both are +checksums, and it must contain a file named "superblock", along with at least +one part.

    +
    +

    Parameters

    +
    +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    self

    Repo

     

    dir_or_file

    Path to a directory containing static delta data, or directly to the superblock

     

    sign

    Signature engine used to check superblock

     

    skip_validation

    If TRUE, assume data integrity

     

    cancellable

    Cancellable

     

    error

    Error

     
    +
    +

    Since: 2020.7

    +
    +
    +

    ostree_repo_static_delta_execute_offline ()

    gboolean
     ostree_repo_static_delta_execute_offline
    @@ -6579,6 +6661,61 @@ must contain a file named "superblock", along with at least one part.


    +

    ostree_repo_static_delta_verify_signature ()

    +
    gboolean
    +ostree_repo_static_delta_verify_signature
    +                               (OstreeRepo *self,
    +                                const char *delta_id,
    +                                OstreeSign *sign,
    +                                char **out_success_message,
    +                                GError **error);
    +

    Verify static delta file signature.

    +
    +

    Parameters

    +
    +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    self

    Repo

     

    delta_id

    delta path

     

    sign

    Signature engine used to check superblock

     

    out_success_message

    success message

     

    error

    Error

     
    +
    +
    +

    Returns

    +

    TRUE if the signature of static delta file is valid using the +signature engine provided, FALSE otherwise.

    +
    +

    Since: 2020.7

    +
    +
    +

    ostree_repo_traverse_new_reachable ()

    GHashTable *
     ostree_repo_traverse_new_reachable (void);
    @@ -7455,37 +7592,37 @@ ostree_repo_pull_with_options (Like ostree_repo_pull(), but supports an extensible set of flags. The following are currently defined:

      -
    • refs (as): Array of string refs

    • -
    • collection-refs (a(sss)): Array of (collection ID, ref name, checksum) tuples to pull; +

    • refs (as): Array of string refs

    • +
    • collection-refs (a(sss)): Array of (collection ID, ref name, checksum) tuples to pull; mutually exclusive with refs and override-commit-ids. Checksums may be the empty string to pull the latest commit for that ref

    • -
    • flags (i): An instance of OstreeRepoPullFlags

    • -
    • subdir (s): Pull just this subdirectory

    • -
    • subdirs (as): Pull just these subdirectories

    • -
    • override-remote-name (s): If local, add this remote to refspec

    • -
    • gpg-verify (b): GPG verify commits

    • -
    • gpg-verify-summary (b): GPG verify summary

    • -
    • disable-sign-verify (b): Disable signapi verification of commits

    • -
    • disable-sign-verify-summary (b): Disable signapi verification of the summary

    • -
    • depth (i): How far in the history to traverse; default is 0, -1 means infinite

    • -
    • per-object-fsync (b): Perform disk writes more slowly, avoiding a single large I/O sync

    • -
    • disable-static-deltas (b): Do not use static deltas

    • -
    • require-static-deltas (b): Require static deltas

    • -
    • override-commit-ids (as): Array of specific commit IDs to fetch for refs

    • -
    • timestamp-check (b): Verify commit timestamps are newer than current (when pulling via ref); Since: 2017.11

    • -
    • timestamp-check-from-rev (s): Verify that all fetched commit timestamps are newer than timestamp of given rev; Since: 2020.4

    • -
    • metadata-size-restriction (t): Restrict metadata objects to a maximum number of bytes; 0 to disable. Since: 2018.9

    • -
    • dry-run (b): Only print information on what will be downloaded (requires static deltas)

    • -
    • override-url (s): Fetch objects from this URL if remote specifies no metalink in options

    • -
    • inherit-transaction (b): Don't initiate, finish or abort a transaction, useful to do multiple pulls in one transaction.

    • -
    • http-headers (a(ss)): Additional headers to add to all HTTP requests

    • -
    • update-frequency (u): Frequency to call the async progress callback in milliseconds, if any; only values higher than 0 are valid

    • -
    • localcache-repos (as): File paths for local repos to use as caches when doing remote fetches

    • -
    • append-user-agent (s): Additional string to append to the user agent

    • -
    • n-network-retries (u): Number of times to retry each download on receiving +

    • flags (i): An instance of OstreeRepoPullFlags

    • +
    • subdir (s): Pull just this subdirectory

    • +
    • subdirs (as): Pull just these subdirectories

    • +
    • override-remote-name (s): If local, add this remote to refspec

    • +
    • gpg-verify (b): GPG verify commits

    • +
    • gpg-verify-summary (b): GPG verify summary

    • +
    • disable-sign-verify (b): Disable signapi verification of commits

    • +
    • disable-sign-verify-summary (b): Disable signapi verification of the summary

    • +
    • depth (i): How far in the history to traverse; default is 0, -1 means infinite

    • +
    • per-object-fsync (b): Perform disk writes more slowly, avoiding a single large I/O sync

    • +
    • disable-static-deltas (b): Do not use static deltas

    • +
    • require-static-deltas (b): Require static deltas

    • +
    • override-commit-ids (as): Array of specific commit IDs to fetch for refs

    • +
    • timestamp-check (b): Verify commit timestamps are newer than current (when pulling via ref); Since: 2017.11

    • +
    • timestamp-check-from-rev (s): Verify that all fetched commit timestamps are newer than timestamp of given rev; Since: 2020.4

    • +
    • metadata-size-restriction (t): Restrict metadata objects to a maximum number of bytes; 0 to disable. Since: 2018.9

    • +
    • dry-run (b): Only print information on what will be downloaded (requires static deltas)

    • +
    • override-url (s): Fetch objects from this URL if remote specifies no metalink in options

    • +
    • inherit-transaction (b): Don't initiate, finish or abort a transaction, useful to do multiple pulls in one transaction.

    • +
    • http-headers (a(ss)): Additional headers to add to all HTTP requests

    • +
    • update-frequency (u): Frequency to call the async progress callback in milliseconds, if any; only values higher than 0 are valid

    • +
    • localcache-repos (as): File paths for local repos to use as caches when doing remote fetches

    • +
    • append-user-agent (s): Additional string to append to the user agent

    • +
    • n-network-retries (u): Number of times to retry each download on receiving a transient network error, such as a socket timeout; default is 5, 0 means return errors without retrying. Since: 2018.6

    • -
    • ref-keyring-map (a(sss)): Array of (collection ID, ref name, keyring +

    • ref-keyring-map (a(sss)): Array of (collection ID, ref name, keyring remote name) tuples specifying which remote's keyring should be used when doing GPG verification of each collection-ref. This is useful to prevent a remote from serving malicious updates to refs which did not originate from diff --git a/apidoc/html/ostree-Root-partition-mount-point.html b/apidoc/html/ostree-Root-partition-mount-point.html index 3b1b106..91c0d6b 100644 --- a/apidoc/html/ostree-Root-partition-mount-point.html +++ b/apidoc/html/ostree-Root-partition-mount-point.html @@ -348,11 +348,35 @@ gboolean +ostree_sysroot_stage_tree_with_options () + + + + +gboolean + + +ostree_sysroot_stage_overlay_initrd () + + + + +gboolean + + ostree_sysroot_deploy_tree () +gboolean + + +ostree_sysroot_deploy_tree_with_options () + + + + OstreeDeployment * @@ -1728,8 +1752,7 @@ ostree_sysroot_stage_tree (OstreeDeployment **out_new_deployment, GCancellable *cancellable, GError **error); -

      Like ostree_sysroot_deploy_tree(), but "finalization" only occurs at OS -shutdown time.

      +

      Older version of ostree_sysroot_stage_tree_with_options().

      Parameters

      @@ -1791,6 +1814,132 @@ shutdown time.


      +

      ostree_sysroot_stage_tree_with_options ()

      +
      gboolean
      +ostree_sysroot_stage_tree_with_options
      +                               (OstreeSysroot *self,
      +                                const char *osname,
      +                                const char *revision,
      +                                GKeyFile *origin,
      +                                OstreeDeployment *merge_deployment,
      +                                OstreeSysrootDeployTreeOpts *opts,
      +                                OstreeDeployment **out_new_deployment,
      +                                GCancellable *cancellable,
      +                                GError **error);
      +

      Like ostree_sysroot_deploy_tree(), but "finalization" only occurs at OS +shutdown time.

      +
      +

      Parameters

      +
      +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

      self

      Sysroot

       

      osname

      osname to use for merge deployment.

      [allow-none]

      revision

      Checksum to add

       

      origin

      Origin to use for upgrades.

      [allow-none]

      merge_deployment

      Use this deployment for merge path.

      [allow-none]

      opts

      Options

       

      out_new_deployment

      The new deployment path.

      [out]

      cancellable

      Cancellable

       

      error

      Error

       
      +
      +

      Since: 2020.7

      +
    +
    +
    +

    ostree_sysroot_stage_overlay_initrd ()

    +
    gboolean
    +ostree_sysroot_stage_overlay_initrd (OstreeSysroot *self,
    +                                     int fd,
    +                                     char **out_checksum,
    +                                     GCancellable *cancellable,
    +                                     GError **error);
    +

    Stage an overlay initrd to be used in an upcoming deployment. Returns a checksum which +can be passed to ostree_sysroot_deploy_tree_with_options() or +ostree_sysroot_stage_tree_with_options() via the overlay_initrds array option.

    +
    +

    Parameters

    +
    +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    self

    Sysroot

     

    fd

    File descriptor to overlay initrd.

    [transfer none]

    out_checksum

    Overlay initrd checksum.

    [out][transfer full]

    cancellable

    Cancellable

     

    error

    Error

     
    +
    +

    Since: 2020.7

    +
    +
    +

    ostree_sysroot_deploy_tree ()

    gboolean
     ostree_sysroot_deploy_tree (OstreeSysroot *self,
    @@ -1802,12 +1951,7 @@ ostree_sysroot_deploy_tree (OstreeDeployment **out_new_deployment,
                                 GCancellable *cancellable,
                                 GError **error);
    -

    Check out deployment tree with revision revision -, performing a 3 -way merge with provided_merge_deployment - for configuration.

    -

    While this API is not deprecated, you most likely want to use the -ostree_sysroot_stage_tree() API.

    +

    Older version of ostree_sysroot_stage_tree_with_options().

    Parameters

    @@ -1865,6 +2009,86 @@ way merge with provided_merge_deployment
    +

    Since: 2018.5

    +
    +
    +
    +

    ostree_sysroot_deploy_tree_with_options ()

    +
    gboolean
    +ostree_sysroot_deploy_tree_with_options
    +                               (OstreeSysroot *self,
    +                                const char *osname,
    +                                const char *revision,
    +                                GKeyFile *origin,
    +                                OstreeDeployment *provided_merge_deployment,
    +                                OstreeSysrootDeployTreeOpts *opts,
    +                                OstreeDeployment **out_new_deployment,
    +                                GCancellable *cancellable,
    +                                GError **error);
    +

    Check out deployment tree with revision revision +, performing a 3 +way merge with provided_merge_deployment + for configuration.

    +

    When booted into the sysroot, you should use the +ostree_sysroot_stage_tree() API instead.

    +
    +

    Parameters

    +
    +++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    self

    Sysroot

     

    osname

    osname to use for merge deployment.

    [allow-none]

    revision

    Checksum to add

     

    origin

    Origin to use for upgrades.

    [allow-none]

    provided_merge_deployment

    Use this deployment for merge path.

    [allow-none]

    opts

    Options.

    [allow-none]

    out_new_deployment

    The new deployment path.

    [out]

    cancellable

    Cancellable

     

    error

    Error

     
    +
    +

    Since: 2020.7


    diff --git a/apidoc/html/ostree-Signature-management.html b/apidoc/html/ostree-Signature-management.html index dcd6075..6ab8d79 100644 --- a/apidoc/html/ostree-Signature-management.html +++ b/apidoc/html/ostree-Signature-management.html @@ -290,6 +290,11 @@ the public key(s) for verification with   +

    out_success_message

    +

    success message returned by the signing engine.

    +[
    out][nullable][optional] + +

    cancellable

    A GCancellable

      @@ -346,8 +351,8 @@ the secret key with

    signature

    -

    in case of success will contain signature

    -  +

    in case of success will contain signature.

    +[
    out]

    cancellable

    @@ -411,6 +416,11 @@ or   +

    out_success_message

    +

    success message returned by the signing engine.

    +[
    out][nullable][optional] + +

    error

    a GError

      @@ -849,6 +859,7 @@ Based on ostree_repo_add_gpg_signature_summary implementation.

    TRUE if summary file has been signed with all provided keys

    +

    Since: 2020.2

    diff --git a/apidoc/html/ostree-ostree-bootconfig-parser.html b/apidoc/html/ostree-ostree-bootconfig-parser.html index c9fdc37..b5b6a77 100644 --- a/apidoc/html/ostree-ostree-bootconfig-parser.html +++ b/apidoc/html/ostree-ostree-bootconfig-parser.html @@ -102,6 +102,22 @@ ostree_bootconfig_parser_get () + + +void + + +ostree_bootconfig_parser_set_overlay_initrds () + + + + +char ** + + +ostree_bootconfig_parser_get_overlay_initrds () + +
    @@ -246,6 +262,69 @@ ostree_bootconfig_parser_set (OstreeBootconfigParser *self, const char *key); +
    +
    +

    ostree_bootconfig_parser_set_overlay_initrds ()

    +
    void
    +ostree_bootconfig_parser_set_overlay_initrds
    +                               (OstreeBootconfigParser *self,
    +                                char **initrds);
    +

    These are rendered as additional initrd keys in the final bootloader configs. The +base initrd is part of the primary keys.

    +
    +

    Parameters

    +
    +++++ + + + + + + + + + + + + +

    self

    Parser

     

    initrds

    Array of overlay +initrds or NULL to unset.

    [array zero-terminated=1][transfer none][allow-none]
    +
    +

    Since: 2020.7

    +
    +
    +
    +

    ostree_bootconfig_parser_get_overlay_initrds ()

    +
    char **
    +ostree_bootconfig_parser_get_overlay_initrds
    +                               (OstreeBootconfigParser *self);
    +
    +

    Parameters

    +
    +++++ + + + + + +

    self

    Parser

     
    +
    +
    +

    Returns

    +

    Array of initrds or NULL +if none are set.

    +

    [array zero-terminated=1][transfer none][nullable]

    +
    +

    Since: 2020.7

    +

    Types and Values

    diff --git a/apidoc/html/ostree-ostree-deployment.html b/apidoc/html/ostree-ostree-deployment.html index bcac84d..a206f46 100644 --- a/apidoc/html/ostree-ostree-deployment.html +++ b/apidoc/html/ostree-ostree-deployment.html @@ -574,6 +574,8 @@ ostree_deployment_unlocked_state_to_string GKeyFile *origin; OstreeDeploymentUnlockedState unlocked; gboolean staged; + char **overlay_initrds; + char *overlay_initrds_id; } OstreeDeployment;
    @@ -635,6 +637,16 @@ ostree_deployment_unlocked_state_to_string

    TRUE iff this deployment is staged

      + +

    char **overlay_initrds;

    +

    Checksums of staged additional initrds for this deployment

    +  + + +

    char *overlay_initrds_id;

    +

    Unique ID generated from initrd checksums; used to compare deployments

    +  +
    diff --git a/apidoc/html/ostree.devhelp2 b/apidoc/html/ostree.devhelp2 index ad15fe0..89cd25b 100644 --- a/apidoc/html/ostree.devhelp2 +++ b/apidoc/html/ostree.devhelp2 @@ -172,7 +172,7 @@ - + @@ -199,7 +199,9 @@ + + @@ -305,7 +307,10 @@ - + + + + @@ -378,7 +383,7 @@ - + @@ -388,6 +393,8 @@ + + @@ -559,5 +566,7 @@ + + diff --git a/apidoc/html/reference.html b/apidoc/html/reference.html index 272075d..0bf7c27 100644 --- a/apidoc/html/reference.html +++ b/apidoc/html/reference.html @@ -148,6 +148,10 @@
    +ostree_bootconfig_parser_get_overlay_initrds, function in ostree-bootconfig-parser +
    +
    +
    ostree_bootconfig_parser_new, function in ostree-bootconfig-parser
    @@ -164,6 +168,10 @@
    +ostree_bootconfig_parser_set_overlay_initrds, function in ostree-bootconfig-parser +
    +
    +
    ostree_bootconfig_parser_write, function in ostree-bootconfig-parser
    @@ -1462,10 +1470,18 @@ ostree_repo_set_collection_ref_immediate, function in ostree-misc-experimental
    +ostree_repo_static_delta_execute_offline_with_signature, function in OstreeRepo +
    +
    +
    ostree_repo_static_delta_generate, function in OstreeRepo
    +ostree_repo_static_delta_verify_signature, function in OstreeRepo +
    +
    +
    ostree_repo_transaction_set_collection_ref, function in ostree-misc-experimental
    @@ -1763,6 +1779,10 @@ ostree_repo_transaction_set_collection_ref, function in ostree-misc-experimental
    +ostree_sysroot_deploy_tree_with_options, function in Root partition mount point +
    +
    +
    ostree_sysroot_ensure_initialized, function in Root partition mount point
    @@ -1879,10 +1899,18 @@ ostree_repo_transaction_set_collection_ref, function in ostree-misc-experimental
    +ostree_sysroot_stage_overlay_initrd, function in Root partition mount point +
    +
    +
    ostree_sysroot_stage_tree, function in Root partition mount point
    +ostree_sysroot_stage_tree_with_options, function in Root partition mount point +
    +
    +
    ostree_sysroot_try_lock, function in Root partition mount point
    diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index 979c8e9..c1d6b35 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -37,6 +37,8 @@ ostree_bootconfig_parser_write ostree_bootconfig_parser_write_at ostree_bootconfig_parser_set ostree_bootconfig_parser_get +ostree_bootconfig_parser_set_overlay_initrds +ostree_bootconfig_parser_get_overlay_initrds OSTREE_BOOTCONFIG_PARSER OSTREE_IS_BOOTCONFIG_PARSER @@ -412,7 +414,9 @@ ostree_repo_list_commit_objects_starting_with ostree_repo_list_static_delta_names OstreeStaticDeltaGenerateOpt ostree_repo_static_delta_generate +ostree_repo_static_delta_execute_offline_with_signature ostree_repo_static_delta_execute_offline +ostree_repo_static_delta_verify_signature ostree_repo_traverse_new_reachable ostree_repo_traverse_new_parents ostree_repo_traverse_parents_get_commits @@ -543,7 +547,10 @@ ostree_sysroot_write_deployments ostree_sysroot_write_deployments_with_options ostree_sysroot_write_origin_file ostree_sysroot_stage_tree +ostree_sysroot_stage_tree_with_options +ostree_sysroot_stage_overlay_initrd ostree_sysroot_deploy_tree +ostree_sysroot_deploy_tree_with_options ostree_sysroot_get_merge_deployment ostree_sysroot_query_deployments_for ostree_sysroot_origin_new_from_refspec diff --git a/bash/ostree b/bash/ostree index 7256e40..d00695e 100644 --- a/bash/ostree +++ b/bash/ostree @@ -1532,6 +1532,9 @@ _ostree_static_delta_apply_offline() { " local options_with_args=" + --sign-type + --keys-file + --keys-dir --repo " @@ -1613,6 +1616,8 @@ _ostree_static_delta_generate() { --repo --set-endianness --to + --sign + --sign-type " local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) @@ -1630,6 +1635,9 @@ _ostree_static_delta_generate() { COMPREPLY=( $( compgen -W "l B" -- "$cur" ) ) return 0 ;; + $options_with_args_glob ) + return 0 + ;; esac case "$cur" in @@ -1704,6 +1712,40 @@ _ostree_static_delta_show() { return 0 } +_ostree_static_delta_verify() { + local boolean_options=" + $main_boolean_options + " + + local options_with_args=" + --sign-type + --keys-file + --keys-dir + --repo + " + + local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) + + case "$prev" in + --keys-file|--keys-dir|--repo) + __ostree_compreply_dirs_only + return 0 + ;; + $options_with_args_glob ) + return 0 + ;; + esac + + case "$cur" in + -*) + local all_options="$boolean_options $options_with_args" + __ostree_compreply_all_options + ;; + esac + + return 0 +} + _ostree_static_delta() { local subcommands=" apply-offline diff --git a/configure b/configure index 127efaa..6f5d091 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for libostree 2020.4. +# Generated by GNU Autoconf 2.69 for libostree 2020.7. # # Report bugs to . # @@ -590,8 +590,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='libostree' PACKAGE_TARNAME='libostree' -PACKAGE_VERSION='2020.4' -PACKAGE_STRING='libostree 2020.4' +PACKAGE_VERSION='2020.7' +PACKAGE_STRING='libostree 2020.7' PACKAGE_BUGREPORT='walters@verbum.org' PACKAGE_URL='' @@ -1561,7 +1561,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures libostree 2020.4 to adapt to many kinds of systems. +\`configure' configures libostree 2020.7 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1631,7 +1631,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of libostree 2020.4:";; + short | recursive ) echo "Configuration of libostree 2020.7:";; esac cat <<\_ACEOF @@ -1896,7 +1896,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -libostree configure 2020.4 +libostree configure 2020.7 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2368,7 +2368,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by libostree $as_me 2020.4, which was +It was created by libostree $as_me 2020.7, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -3236,7 +3236,7 @@ fi # Define the identity of the package. PACKAGE='libostree' - VERSION='2020.4' + VERSION='2020.7' # Some tools Automake needs. @@ -5970,9 +5970,9 @@ test -n "$YACC" || YACC="yacc" YEAR_VERSION=2020 -RELEASE_VERSION=4 +RELEASE_VERSION=7 -PACKAGE_VERSION=2020.4 +PACKAGE_VERSION=2020.7 if echo "$CFLAGS" | grep -q -E -e '-Werror($| )'; then : @@ -5983,6 +5983,7 @@ else for flag in \ -pipe \ -Wall \ + -Werror=shadow \ -Werror=empty-body \ -Werror=strict-prototypes \ -Werror=missing-prototypes \ @@ -19025,7 +19026,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by libostree $as_me 2020.4, which was +This file was extended by libostree $as_me 2020.7, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -19091,7 +19092,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -libostree config.status 2020.4 +libostree config.status 2020.7 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 855528c..13f5bef 100644 --- a/configure.ac +++ b/configure.ac @@ -7,7 +7,7 @@ dnl Seed the release notes with `git-shortlog-with-prs ..`. Th dnl `git-evtag` to create the tag and push it. Finally, create a GitHub release and attach dnl the tarball from `make dist`. m4_define([year_version], [2020]) -m4_define([release_version], [4]) +m4_define([release_version], [7]) m4_define([package_version], [year_version.release_version]) AC_INIT([libostree], [package_version], [walters@verbum.org]) is_release_build=yes @@ -35,6 +35,7 @@ AS_IF([echo "$CFLAGS" | grep -q -E -e '-Werror($| )'], [], [ CC_CHECK_FLAGS_APPEND([WARN_CFLAGS], [CFLAGS], [\ -pipe \ -Wall \ + -Werror=shadow \ -Werror=empty-body \ -Werror=strict-prototypes \ -Werror=missing-prototypes \ diff --git a/libglnx/glnx-fdio.c b/libglnx/glnx-fdio.c index e537a9b..422bc2d 100644 --- a/libglnx/glnx-fdio.c +++ b/libglnx/glnx-fdio.c @@ -303,8 +303,8 @@ glnx_open_anonymous_tmpfile_full (int flags, } /* A variant of `glnx_open_tmpfile_linkable_at()` which doesn't support linking. - * Useful for true temporary storage. The fd will be allocated in /var/tmp to - * ensure maximum storage space. + * Useful for true temporary storage. The fd will be allocated in `$TMPDIR` if + * set or `/var/tmp` otherwise. * * If you need the file on a specific filesystem use glnx_open_anonymous_tmpfile_full() * which lets you pass a directory. @@ -314,7 +314,10 @@ glnx_open_anonymous_tmpfile (int flags, GLnxTmpfile *out_tmpf, GError **error) { - return glnx_open_anonymous_tmpfile_full (flags, "/var/tmp", out_tmpf, error); + return glnx_open_anonymous_tmpfile_full (flags, + getenv("TMPDIR") ?: "/var/tmp", + out_tmpf, + error); } /* Use this after calling glnx_open_tmpfile_linkable_at() to give diff --git a/libglnx/glnx-xattrs.c b/libglnx/glnx-xattrs.c index 79a14cd..892d534 100644 --- a/libglnx/glnx-xattrs.c +++ b/libglnx/glnx-xattrs.c @@ -283,7 +283,7 @@ set_all_xattrs_for_path (const char *path, const guint8* value_data = g_variant_get_fixed_array (value, &value_len, 1); if (lsetxattr (path, (char*)name, (char*)value_data, value_len, 0) < 0) - return glnx_throw_errno_prefix (error, "lsetxattr"); + return glnx_throw_errno_prefix (error, "lsetxattr(%s)", name); } return TRUE; @@ -351,7 +351,7 @@ glnx_fd_set_all_xattrs (int fd, const guint8* value_data = g_variant_get_fixed_array (value, &value_len, 1); if (TEMP_FAILURE_RETRY (fsetxattr (fd, (char*)name, (char*)value_data, value_len, 0)) < 0) - return glnx_throw_errno_prefix (error, "fsetxattr"); + return glnx_throw_errno_prefix (error, "Setting xattrs: fsetxattr(%s)", name); } return TRUE; @@ -378,11 +378,11 @@ glnx_lgetxattrat (int dfd, ssize_t bytes_read, real_size; if (TEMP_FAILURE_RETRY (bytes_read = lgetxattr (pathbuf, attribute, NULL, 0)) < 0) - return glnx_null_throw_errno_prefix (error, "lgetxattr"); + return glnx_null_throw_errno_prefix (error, "lgetxattr(%s)", attribute); g_autofree guint8 *buf = g_malloc (bytes_read); if (TEMP_FAILURE_RETRY (real_size = lgetxattr (pathbuf, attribute, buf, bytes_read)) < 0) - return glnx_null_throw_errno_prefix (error, "lgetxattr"); + return glnx_null_throw_errno_prefix (error, "lgetxattr(%s)", attribute); return g_bytes_new_take (g_steal_pointer (&buf), real_size); } @@ -403,11 +403,11 @@ glnx_fgetxattr_bytes (int fd, ssize_t bytes_read, real_size; if (TEMP_FAILURE_RETRY (bytes_read = fgetxattr (fd, attribute, NULL, 0)) < 0) - return glnx_null_throw_errno_prefix (error, "fgetxattr"); + return glnx_null_throw_errno_prefix (error, "fgetxattr(%s)", attribute); g_autofree guint8 *buf = g_malloc (bytes_read); if (TEMP_FAILURE_RETRY (real_size = fgetxattr (fd, attribute, buf, bytes_read)) < 0) - return glnx_null_throw_errno_prefix (error, "fgetxattr"); + return glnx_null_throw_errno_prefix (error, "fgetxattr(%s)", attribute); return g_bytes_new_take (g_steal_pointer (&buf), real_size); } @@ -437,7 +437,7 @@ glnx_lsetxattrat (int dfd, snprintf (pathbuf, sizeof (pathbuf), "/proc/self/fd/%d/%s", dfd, subpath); if (TEMP_FAILURE_RETRY (lsetxattr (subpath, attribute, value, len, flags)) < 0) - return glnx_throw_errno_prefix (error, "lsetxattr"); + return glnx_throw_errno_prefix (error, "lsetxattr(%s)", attribute); return TRUE; } diff --git a/man/ostree-checkout.xml b/man/ostree-checkout.xml index 3956e34..dfa2ce1 100644 --- a/man/ostree-checkout.xml +++ b/man/ostree-checkout.xml @@ -163,7 +163,7 @@ Boston, MA 02111-1307, USA. - Do not hardlink zero-sized files. + This option does nothing; the functionality is now always on by default. diff --git a/man/ostree-commit.xml b/man/ostree-commit.xml index b0c5b33..ab5d341 100644 --- a/man/ostree-commit.xml +++ b/man/ostree-commit.xml @@ -83,6 +83,14 @@ Boston, MA 02111-1307, USA. + , ="FILE" + + + Full commit description from a file. (optional) + + + + , @@ -99,6 +107,14 @@ Boston, MA 02111-1307, USA. + ="COMMIT" + + + Parent checksum or "none" to explicitly use no parent. If not specified, BRANCH is used as parent (no parent in case BRANCH does not exist). + + + + ="dir=PATH" or "tar=TARFILE" or "ref=COMMIT" @@ -119,7 +135,23 @@ Boston, MA 02111-1307, USA. ="KEY=VALUE" - Add a key/value pair to metadata. + Add a key/value pair to metadata. Can be specified multiple times. + + + + + ="KEY=VALUE" + + + Add a key/value pair to metadata, where the KEY is a string, and VALUE is g_variant_parse() formatted. Can be specified multiple times. + + + + + ="KEY" + + + Keep metadata KEY and its associated VALUE from parent. Can be specified multiple times. diff --git a/man/ostree-static-delta.xml b/man/ostree-static-delta.xml index dfeef28..440ada4 100644 --- a/man/ostree-static-delta.xml +++ b/man/ostree-static-delta.xml @@ -63,7 +63,10 @@ Boston, MA 02111-1307, USA. ostree static-delta generate --to=REV OPTIONS - ostree static-delta apply-offline PATH + ostree static-delta apply-offline OPTIONS PATH KEY-ID + + + ostree static-delta verify OPTIONS STATIC-DELTA KEY-ID @@ -113,6 +116,159 @@ Boston, MA 02111-1307, USA. + + =ENGINE + + + Use particular signature engine. Currently + available ed25519 and dummy + signature types. + + The default is ed25519. + + + + + ="KEY-ID" + + There KEY-ID is: + + + + + base64-encoded secret key for signing. + + + + + + + ASCII-string used as secret key. + + + + + + + + + + 'Apply-offline' Options + + + + + + + + + + + base64-encoded public key for verifying. + + + + + + + ASCII-string used as public key. + + + + + + + + =ENGINE + + + Use particular signature engine. Currently + available ed25519 and dummy + signature types. + + + + + + + Read key(s) from file filename. + + + + Valid for ed25519 signature type. + For ed25519 this file must contain base64-encoded + public key(s) per line for verifying. + + + + + + + Redefine the system path, where to search files and subdirectories with + well-known and revoked keys. + + + + + + + 'Verify' Options + + + + + + + + + + + base64-encoded public key for verifying. + + + + + + + ASCII-string used as public key. + + + + + + + + =ENGINE + + + Use particular signature engine. Currently + available ed25519 and dummy + signature types. + + The default is ed25519. + + + + + + + Read key(s) from file filename. + + + + Valid for ed25519 signature type. + For ed25519 this file must contain base64-encoded + public key(s) per line for verifying. + + + + + + + Redefine the system path, where to search files and subdirectories with + well-known and revoked keys. + + diff --git a/man/ostree.xml b/man/ostree.xml index 8c08bae..e280a02 100644 --- a/man/ostree.xml +++ b/man/ostree.xml @@ -484,6 +484,70 @@ Boston, MA 02111-1307, USA. + Terminology + + The following terms are commonly used throughout the man pages. Terms in upper case letters + are literals used in command line arguments. + + + BRANCH + + + Branch name. Part of a REF. + + + + CHECKSUM + + + A SHA256 hash of a object stored in the OSTree repository. This can be a content, + a dirtree, a dirmeta or a commit object. If the SHA256 hash of a commit object is + meant, the term COMMIT is used. + + + + COMMIT + + + A SHA256 hash of a commit object. + + + + REF + + + A reference to a particular commit. References are text files stored in + refs/ that name (refer to) a particular commit. A + reference can only be the branch name part, in which case a local reference + is used (e.g. mybranch/stable). If a remote branch + is referred to, the remote name followed by a colon and the branch name + needs to be used (e.g. myremote:mybranch/stable). + + + + REV REFSPEC + + + A specific revision, a commit. This can be anything which can be resolved to a + commit, e.g. a REF or a + COMMIT. + + + + SHA256 + + + A cryptographic hash function used to store objects in the OSTree + repository. The hashes have a length of 256 bites and are typically + shown and passed to ostree in its 64 ASCII character long hexadecimal + representation + (e.g. 0fc70ed33cfd7d26fe99ae29afb7682ddd0e2157a4898bd8cfcdc8a03565b870). + + + + + + See Also ostree.repo5 diff --git a/src/boot/dracut/ostree.conf b/src/boot/dracut/ostree.conf index 612bb43..ac70494 100755 --- a/src/boot/dracut/ostree.conf +++ b/src/boot/dracut/ostree.conf @@ -16,3 +16,4 @@ # Boston, MA 02111-1307, USA. add_dracutmodules+=" ostree systemd " +reproducible=yes diff --git a/src/boot/mkinitcpio/ostree b/src/boot/mkinitcpio/ostree index 7f21cac..3aa0659 100644 --- a/src/boot/mkinitcpio/ostree +++ b/src/boot/mkinitcpio/ostree @@ -5,6 +5,6 @@ build() { add_binary /usr/lib/ostree/ostree-remount add_file /usr/lib/systemd/system/ostree-prepare-root.service - add_symlink /usr/lib/systemd/system/initrd-switch-root.target.wants/ostree-prepare-root.service \ + add_symlink /usr/lib/systemd/system/initrd-root-fs.target.wants/ostree-prepare-root.service \ /usr/lib/systemd/system/ostree-prepare-root.service } diff --git a/src/boot/ostree-prepare-root.service b/src/boot/ostree-prepare-root.service index 9169220..250ffe7 100644 --- a/src/boot/ostree-prepare-root.service +++ b/src/boot/ostree-prepare-root.service @@ -30,6 +30,6 @@ Before=initrd-root-fs.target Type=oneshot ExecStart=/usr/lib/ostree/ostree-prepare-root /sysroot StandardInput=null -StandardOutput=syslog -StandardError=syslog+console +StandardOutput=journal +StandardError=journal+console RemainAfterExit=yes diff --git a/src/boot/ostree-remount.service b/src/boot/ostree-remount.service index 4c3ed94..af40453 100644 --- a/src/boot/ostree-remount.service +++ b/src/boot/ostree-remount.service @@ -35,8 +35,8 @@ Type=oneshot RemainAfterExit=yes ExecStart=/usr/lib/ostree/ostree-remount StandardInput=null -StandardOutput=syslog -StandardError=syslog+console +StandardOutput=journal +StandardError=journal+console [Install] WantedBy=local-fs.target diff --git a/src/libostree/libostree-released.sym b/src/libostree/libostree-released.sym index 5c63f78..c154d8c 100644 --- a/src/libostree/libostree-released.sym +++ b/src/libostree/libostree-released.sym @@ -593,6 +593,9 @@ global: ostree_sysroot_set_mount_namespace_in_use; } LIBOSTREE_2019.6; +/* No new symbols in 2020.2 */ +/* No new symbols in 2020.3 */ + /* Add new symbols here. Release commits should copy this section into -released.sym. */ LIBOSTREE_2020.4 { global: @@ -615,7 +618,23 @@ global: ostree_sign_summary; } LIBOSTREE_2020.1; -/* No new symbols in 2020.2 */ +/* No new symbols in 2020.5 */ + +/* No new symbols in 2020.6 */ + +LIBOSTREE_2020.7 { +global: + /* Add symbols here, and uncomment the bits in + * Makefile-libostree.am to enable this too. + */ + ostree_repo_static_delta_execute_offline_with_signature; + ostree_repo_static_delta_verify_signature; + ostree_bootconfig_parser_get_overlay_initrds; + ostree_bootconfig_parser_set_overlay_initrds; + ostree_sysroot_deploy_tree_with_options; + ostree_sysroot_stage_tree_with_options; + ostree_sysroot_stage_overlay_initrd; +} LIBOSTREE_2020.4; /* NOTE: Only add more content here in release commits! See the * comments at the top of this file. diff --git a/src/libostree/ostree-bootconfig-parser.c b/src/libostree/ostree-bootconfig-parser.c index 67f9fb5..a36a411 100644 --- a/src/libostree/ostree-bootconfig-parser.c +++ b/src/libostree/ostree-bootconfig-parser.c @@ -30,6 +30,9 @@ struct _OstreeBootconfigParser const char *separators; GHashTable *options; + + /* Additional initrds; the primary initrd is in options. */ + char **overlay_initrds; }; typedef GObjectClass OstreeBootconfigParserClass; @@ -50,6 +53,8 @@ ostree_bootconfig_parser_clone (OstreeBootconfigParser *self) GLNX_HASH_TABLE_FOREACH_KV (self->options, const char*, k, const char*, v) g_hash_table_replace (parser->options, g_strdup (k), g_strdup (v)); + parser->overlay_initrds = g_strdupv (self->overlay_initrds); + return parser; } @@ -76,6 +81,8 @@ ostree_bootconfig_parser_parse_at (OstreeBootconfigParser *self, if (!contents) return FALSE; + g_autoptr(GPtrArray) overlay_initrds = NULL; + g_auto(GStrv) lines = g_strsplit (contents, "\n", -1); for (char **iter = lines; *iter; iter++) { @@ -87,8 +94,19 @@ ostree_bootconfig_parser_parse_at (OstreeBootconfigParser *self, items = g_strsplit_set (line, self->separators, 2); if (g_strv_length (items) == 2 && items[0][0] != '\0') { - g_hash_table_insert (self->options, items[0], items[1]); - g_free (items); /* Transfer ownership */ + if (g_str_equal (items[0], "initrd") && + g_hash_table_contains (self->options, "initrd")) + { + if (!overlay_initrds) + overlay_initrds = g_ptr_array_new_with_free_func (g_free); + g_ptr_array_add (overlay_initrds, items[1]); + g_free (items[0]); + } + else + { + g_hash_table_insert (self->options, items[0], items[1]); + } + g_free (items); /* Free container; we stole the elements */ } else { @@ -97,6 +115,12 @@ ostree_bootconfig_parser_parse_at (OstreeBootconfigParser *self, } } + if (overlay_initrds) + { + g_ptr_array_add (overlay_initrds, NULL); + self->overlay_initrds = (char**)g_ptr_array_free (g_steal_pointer (&overlay_initrds), FALSE); + } + self->parsed = TRUE; return TRUE; @@ -127,6 +151,41 @@ ostree_bootconfig_parser_get (OstreeBootconfigParser *self, return g_hash_table_lookup (self->options, key); } +/** + * ostree_bootconfig_parser_set_overlay_initrds: + * @self: Parser + * @initrds: (array zero-terminated=1) (transfer none) (allow-none): Array of overlay + * initrds or %NULL to unset. + * + * These are rendered as additional `initrd` keys in the final bootloader configs. The + * base initrd is part of the primary keys. + * + * Since: 2020.7 + */ +void +ostree_bootconfig_parser_set_overlay_initrds (OstreeBootconfigParser *self, + char **initrds) +{ + g_assert (g_hash_table_contains (self->options, "initrd")); + g_strfreev (self->overlay_initrds); + self->overlay_initrds = g_strdupv (initrds); +} + +/** + * ostree_bootconfig_parser_get_overlay_initrds: + * @self: Parser + * + * Returns: (array zero-terminated=1) (transfer none) (nullable): Array of initrds or %NULL + * if none are set. + * + * Since: 2020.7 + */ +char** +ostree_bootconfig_parser_get_overlay_initrds (OstreeBootconfigParser *self) +{ + return self->overlay_initrds; +} + static void write_key (OstreeBootconfigParser *self, GString *buf, @@ -165,6 +224,15 @@ ostree_bootconfig_parser_write_at (OstreeBootconfigParser *self, } } + /* Write overlay initrds */ + if (self->overlay_initrds && (g_strv_length (self->overlay_initrds) > 0)) + { + /* we should've written the primary initrd already */ + g_assert (g_hash_table_contains (keys_written, "initrd")); + for (char **it = self->overlay_initrds; it && *it; it++) + write_key (self, buf, "initrd", *it); + } + /* Write unknown fields */ GLNX_HASH_TABLE_FOREACH_KV (self->options, const char*, k, const char*, v) { @@ -197,6 +265,7 @@ ostree_bootconfig_parser_finalize (GObject *object) { OstreeBootconfigParser *self = OSTREE_BOOTCONFIG_PARSER (object); + g_strfreev (self->overlay_initrds); g_hash_table_unref (self->options); G_OBJECT_CLASS (ostree_bootconfig_parser_parent_class)->finalize (object); diff --git a/src/libostree/ostree-bootconfig-parser.h b/src/libostree/ostree-bootconfig-parser.h index aec0753..d03c931 100644 --- a/src/libostree/ostree-bootconfig-parser.h +++ b/src/libostree/ostree-bootconfig-parser.h @@ -73,5 +73,11 @@ _OSTREE_PUBLIC const char *ostree_bootconfig_parser_get (OstreeBootconfigParser *self, const char *key); +_OSTREE_PUBLIC +void ostree_bootconfig_parser_set_overlay_initrds (OstreeBootconfigParser *self, + char **initrds); + +_OSTREE_PUBLIC +char** ostree_bootconfig_parser_get_overlay_initrds (OstreeBootconfigParser *self); G_END_DECLS diff --git a/src/libostree/ostree-bootloader-syslinux.c b/src/libostree/ostree-bootloader-syslinux.c index 5fb8a1d..0055896 100644 --- a/src/libostree/ostree-bootloader-syslinux.c +++ b/src/libostree/ostree-bootloader-syslinux.c @@ -89,15 +89,15 @@ append_config_from_loader_entries (OstreeBootloaderSyslinux *self, val = ostree_bootconfig_parser_get (config, "linux"); if (!val) return glnx_throw (error, "No \"linux\" key in bootloader config"); - g_ptr_array_add (new_lines, g_strdup_printf ("\tKERNEL %s", val)); + g_ptr_array_add (new_lines, g_strdup_printf ("\tKERNEL /boot%s", val)); val = ostree_bootconfig_parser_get (config, "initrd"); if (val) - g_ptr_array_add (new_lines, g_strdup_printf ("\tINITRD %s", val)); + g_ptr_array_add (new_lines, g_strdup_printf ("\tINITRD /boot%s", val)); val = ostree_bootconfig_parser_get (config, "devicetree"); if (val) - g_ptr_array_add (new_lines, g_strdup_printf ("\tDEVICETREE %s", val)); + g_ptr_array_add (new_lines, g_strdup_printf ("\tDEVICETREE /boot%s", val)); val = ostree_bootconfig_parser_get (config, "options"); if (val) @@ -150,10 +150,13 @@ _ostree_bootloader_syslinux_write_config (OstreeBootloader *bootloader, if (kernel_arg == NULL) return glnx_throw (error, "No KERNEL argument found after LABEL"); - /* If this is a non-ostree kernel, just emit the lines - * we saw. + /* If this is a non-ostree kernel, just emit the lines we saw. + * + * We check for /ostree (without /boot prefix) as well to support + * upgrading ostree from len; i++) { diff --git a/src/libostree/ostree-bootloader-uboot.c b/src/libostree/ostree-bootloader-uboot.c index 1e1f037..7e23001 100644 --- a/src/libostree/ostree-bootloader-uboot.c +++ b/src/libostree/ostree-bootloader-uboot.c @@ -134,19 +134,19 @@ create_config_from_boot_loader_entries (OstreeBootloaderUboot *self, "No \"linux\" key in bootloader config"); return FALSE; } - g_ptr_array_add (new_lines, g_strdup_printf ("kernel_image%s=%s", index_suffix, val)); + g_ptr_array_add (new_lines, g_strdup_printf ("kernel_image%s=/boot%s", index_suffix, val)); val = ostree_bootconfig_parser_get (config, "initrd"); if (val) - g_ptr_array_add (new_lines, g_strdup_printf ("ramdisk_image%s=%s", index_suffix, val)); + g_ptr_array_add (new_lines, g_strdup_printf ("ramdisk_image%s=/boot%s", index_suffix, val)); val = ostree_bootconfig_parser_get (config, "devicetree"); if (val) - g_ptr_array_add (new_lines, g_strdup_printf ("fdt_file%s=%s", index_suffix, val)); + g_ptr_array_add (new_lines, g_strdup_printf ("fdt_file%s=/boot%s", index_suffix, val)); val = ostree_bootconfig_parser_get (config, "fdtdir"); if (val) - g_ptr_array_add (new_lines, g_strdup_printf ("fdtdir%s=%s", index_suffix, val)); + g_ptr_array_add (new_lines, g_strdup_printf ("fdtdir%s=/boot%s", index_suffix, val)); val = ostree_bootconfig_parser_get (config, "options"); if (val) diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c index 523f57c..29528fa 100644 --- a/src/libostree/ostree-core.c +++ b/src/libostree/ostree-core.c @@ -2675,7 +2675,7 @@ _ostree_detached_metadata_append_gpg_sig (GVariant *existing_metadata, _OSTREE_METADATA_GPGSIGS_NAME, g_variant_builder_end (signature_builder)); - return g_variant_dict_end (&metadata_dict); + return g_variant_ref_sink (g_variant_dict_end (&metadata_dict)); } #endif /* OSTREE_DISABLE_GPGME */ diff --git a/src/libostree/ostree-deployment-private.h b/src/libostree/ostree-deployment-private.h index ad77317..b339ae2 100644 --- a/src/libostree/ostree-deployment-private.h +++ b/src/libostree/ostree-deployment-private.h @@ -37,6 +37,8 @@ G_BEGIN_DECLS * @origin: How to construct an upgraded version of this tree * @unlocked: The unlocked state * @staged: TRUE iff this deployment is staged + * @overlay_initrds: Checksums of staged additional initrds for this deployment + * @overlay_initrds_id: Unique ID generated from initrd checksums; used to compare deployments */ struct _OstreeDeployment { @@ -52,8 +54,15 @@ struct _OstreeDeployment GKeyFile *origin; OstreeDeploymentUnlockedState unlocked; gboolean staged; + char **overlay_initrds; + char *overlay_initrds_id; }; void _ostree_deployment_set_bootcsum (OstreeDeployment *self, const char *bootcsum); +void _ostree_deployment_set_overlay_initrds (OstreeDeployment *self, + char **overlay_initrds); + +char** _ostree_deployment_get_overlay_initrds (OstreeDeployment *self); + G_END_DECLS diff --git a/src/libostree/ostree-deployment.c b/src/libostree/ostree-deployment.c index 6532a97..182bcee 100644 --- a/src/libostree/ostree-deployment.c +++ b/src/libostree/ostree-deployment.c @@ -158,6 +158,34 @@ _ostree_deployment_set_bootcsum (OstreeDeployment *self, self->bootcsum = g_strdup (bootcsum); } +void +_ostree_deployment_set_overlay_initrds (OstreeDeployment *self, + char **overlay_initrds) +{ + g_clear_pointer (&self->overlay_initrds, g_strfreev); + g_clear_pointer (&self->overlay_initrds_id, g_free); + + if (!overlay_initrds || g_strv_length (overlay_initrds) == 0) + return; + + /* Generate a unique ID representing this combination of overlay initrds. This is so that + * ostree_sysroot_write_deployments_with_options() can easily compare initrds when + * comparing deployments for whether a bootswap is necessary. We could be fancier here but + * meh... this works. */ + g_autoptr(GString) id = g_string_new (NULL); + for (char **it = overlay_initrds; it && *it; it++) + g_string_append (id, *it); + + self->overlay_initrds = g_strdupv (overlay_initrds); + self->overlay_initrds_id = g_string_free (g_steal_pointer (&id), FALSE); +} + +char** +_ostree_deployment_get_overlay_initrds (OstreeDeployment *self) +{ + return self->overlay_initrds; +} + /** * ostree_deployment_clone: * @self: Deployment @@ -175,6 +203,8 @@ ostree_deployment_clone (OstreeDeployment *self) new_bootconfig = ostree_bootconfig_parser_clone (self->bootconfig); ostree_deployment_set_bootconfig (ret, new_bootconfig); + _ostree_deployment_set_overlay_initrds (ret, self->overlay_initrds); + if (self->origin) { g_autoptr(GKeyFile) new_origin = NULL; @@ -238,6 +268,8 @@ ostree_deployment_finalize (GObject *object) g_free (self->bootcsum); g_clear_object (&self->bootconfig); g_clear_pointer (&self->origin, g_key_file_unref); + g_strfreev (self->overlay_initrds); + g_free (self->overlay_initrds_id); G_OBJECT_CLASS (ostree_deployment_parent_class)->finalize (object); } @@ -318,6 +350,8 @@ ostree_deployment_unlocked_state_to_string (OstreeDeploymentUnlockedState state) return "hotfix"; case OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT: return "development"; + case OSTREE_DEPLOYMENT_UNLOCKED_TRANSIENT: + return "transient"; } g_assert_not_reached (); } diff --git a/src/libostree/ostree-deployment.h b/src/libostree/ostree-deployment.h index 756e39d..dcfa25e 100644 --- a/src/libostree/ostree-deployment.h +++ b/src/libostree/ostree-deployment.h @@ -99,7 +99,8 @@ char *ostree_deployment_get_origin_relpath (OstreeDeployment *self); typedef enum { OSTREE_DEPLOYMENT_UNLOCKED_NONE, OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT, - OSTREE_DEPLOYMENT_UNLOCKED_HOTFIX + OSTREE_DEPLOYMENT_UNLOCKED_HOTFIX, + OSTREE_DEPLOYMENT_UNLOCKED_TRANSIENT, } OstreeDeploymentUnlockedState; _OSTREE_PUBLIC diff --git a/src/libostree/ostree-fetcher-curl.c b/src/libostree/ostree-fetcher-curl.c index fdf8a2e..0ce3ff0 100644 --- a/src/libostree/ostree-fetcher-curl.c +++ b/src/libostree/ostree-fetcher-curl.c @@ -341,14 +341,14 @@ check_multi_info (OstreeFetcher *fetcher) if (req->idx + 1 == req->mirrorlist->len) { - g_autofree char *msg = g_strdup_printf ("Server returned HTTP %lu", response); + g_autofree char *response_msg = g_strdup_printf ("Server returned HTTP %lu", response); g_task_return_new_error (task, G_IO_ERROR, giocode, - "%s", msg); + "%s", response_msg); if (req->fetcher->remote_name && !((req->flags & OSTREE_FETCHER_REQUEST_OPTIONAL_CONTENT) > 0 && giocode == G_IO_ERROR_NOT_FOUND)) _ostree_fetcher_journal_failure (req->fetcher->remote_name, - eff_url, msg); + eff_url, response_msg); } else diff --git a/src/libostree/ostree-linuxfsutil.c b/src/libostree/ostree-linuxfsutil.c index 231ecf7..cb778de 100644 --- a/src/libostree/ostree-linuxfsutil.c +++ b/src/libostree/ostree-linuxfsutil.c @@ -55,7 +55,7 @@ _ostree_linuxfs_fd_alter_immutable_flag (int fd, if (g_atomic_int_get (&no_alter_immutable)) return TRUE; - unsigned long flags; + int flags = 0; int r = ioctl (fd, EXT2_IOC_GETFLAGS, &flags); if (r == -1) { diff --git a/src/libostree/ostree-repo-checkout.c b/src/libostree/ostree-repo-checkout.c index dc36370..00c6a77 100644 --- a/src/libostree/ostree-repo-checkout.c +++ b/src/libostree/ostree-repo-checkout.c @@ -613,9 +613,12 @@ checkout_one_file_at (OstreeRepo *repo, } const gboolean is_symlink = (g_file_info_get_file_type (source_info) == G_FILE_TYPE_SYMBOLIC_LINK); + const guint32 source_mode = g_file_info_get_attribute_uint32 (source_info, "unix::mode"); + const gboolean is_unreadable = (!is_symlink && (source_mode & S_IRUSR) == 0); const gboolean is_whiteout = (!is_symlink && options->process_whiteouts && g_str_has_prefix (destination_name, WHITEOUT_PREFIX)); const gboolean is_reg_zerosized = (!is_symlink && g_file_info_get_size (source_info) == 0); + const gboolean override_user_unreadable = (options->mode == OSTREE_REPO_CHECKOUT_MODE_USER && is_unreadable); /* First, see if it's a Docker whiteout, * https://github.com/docker/docker/blob/1a714e76a2cb9008cd19609059e9988ff1660b78/pkg/archive/whiteouts.go @@ -634,8 +637,12 @@ checkout_one_file_at (OstreeRepo *repo, need_copy = FALSE; } - else if (options->force_copy_zerosized && is_reg_zerosized) + else if (is_reg_zerosized || override_user_unreadable) { + /* In https://github.com/ostreedev/ostree/commit/673cacd633f9d6b653cdea530657d3e780a41bbd we + * made this an option, but in order to avoid hitting EMLINK, we now force copy zerosized + * files unconditionally. + */ need_copy = TRUE; } else if (!options->force_copy) @@ -735,7 +742,7 @@ checkout_one_file_at (OstreeRepo *repo, if (can_cache && !is_whiteout && !is_symlink - && !is_reg_zerosized + && !(is_reg_zerosized || override_user_unreadable) && need_copy && repo->mode == OSTREE_REPO_MODE_ARCHIVE && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER) @@ -799,12 +806,21 @@ checkout_one_file_at (OstreeRepo *repo, * succeeded at hardlinking above. */ if (options->no_copy_fallback) - g_assert (is_bare_user_symlink || is_reg_zerosized); + g_assert (is_bare_user_symlink || is_reg_zerosized || override_user_unreadable); if (!ostree_repo_load_file (repo, checksum, &input, NULL, &xattrs, cancellable, error)) return FALSE; - if (!create_file_copy_from_input_at (repo, options, state, checksum, source_info, xattrs, input, + GFileInfo *copy_source_info = source_info; + g_autoptr(GFileInfo) modified_info = NULL; + if (override_user_unreadable) + { + modified_info = g_file_info_dup (source_info); + g_file_info_set_attribute_uint32 (modified_info, "unix::mode", (source_mode | S_IRUSR)); + copy_source_info = modified_info; + } + + if (!create_file_copy_from_input_at (repo, options, state, checksum, copy_source_info, xattrs, input, destination_dfd, destination_name, cancellable, error)) return glnx_prefix_error (error, "Copy checkout of %s to %s", checksum, destination_name); diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index 0c9de23..690075e 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -3943,11 +3943,9 @@ write_dfd_iter_to_mtree_internal (OstreeRepo *self, GCancellable *cancellable, GError **error) { - g_autoptr(GFileInfo) child_info = NULL; g_autoptr(GFileInfo) modified_info = NULL; g_autoptr(GVariant) xattrs = NULL; g_autofree guchar *child_file_csum = NULL; - g_autofree char *tmp_checksum = NULL; g_autofree char *relpath = NULL; OstreeRepoCommitFilterResult filter_result; struct stat dir_stbuf; @@ -3955,19 +3953,19 @@ write_dfd_iter_to_mtree_internal (OstreeRepo *self, if (!glnx_fstat (src_dfd_iter->fd, &dir_stbuf, error)) return FALSE; - child_info = _ostree_stbuf_to_gfileinfo (&dir_stbuf); - - if (modifier != NULL) - { - relpath = ptrarray_path_join (path); - - filter_result = _ostree_repo_commit_modifier_apply (self, modifier, relpath, child_info, &modified_info); - } - else - { - filter_result = OSTREE_REPO_COMMIT_FILTER_ALLOW; - modified_info = g_object_ref (child_info); - } + { + g_autoptr(GFileInfo) child_info = _ostree_stbuf_to_gfileinfo (&dir_stbuf); + if (modifier != NULL) + { + relpath = ptrarray_path_join (path); + filter_result = _ostree_repo_commit_modifier_apply (self, modifier, relpath, child_info, &modified_info); + } + else + { + filter_result = OSTREE_REPO_COMMIT_FILTER_ALLOW; + modified_info = g_object_ref (child_info); + } + } if (filter_result == OSTREE_REPO_COMMIT_FILTER_ALLOW) { @@ -3979,8 +3977,7 @@ write_dfd_iter_to_mtree_internal (OstreeRepo *self, cancellable, error)) return FALSE; - g_free (tmp_checksum); - tmp_checksum = ostree_checksum_from_bytes (child_file_csum); + g_autofree char *tmp_checksum = ostree_checksum_from_bytes (child_file_csum); ostree_mutable_tree_set_metadata_checksum (mtree, tmp_checksum); } @@ -4332,6 +4329,8 @@ ostree_repo_commit_modifier_set_sepolicy (OstreeRepoCommitModifier * In many cases, one wants to create a "derived" commit from base commit. * SELinux policy labels are part of that base commit. This API allows * one to easily set up SELinux labeling from a base commit. + * + * Since: 2020.4 */ gboolean ostree_repo_commit_modifier_set_sepolicy_from_commit (OstreeRepoCommitModifier *modifier, diff --git a/src/libostree/ostree-repo-finder-avahi-parser.c b/src/libostree/ostree-repo-finder-avahi-parser.c index afc9790..1cf4a11 100644 --- a/src/libostree/ostree-repo-finder-avahi-parser.c +++ b/src/libostree/ostree-repo-finder-avahi-parser.c @@ -91,14 +91,14 @@ parse_txt_record (const guint8 *txt, /* TODO: Docs. Return value is only valid as long as @txt is. Reference: RFC 6763, §6. */ GHashTable * -_ostree_txt_records_parse (AvahiStringList *txt) +_ostree_txt_records_parse (AvahiStringList *txt_list) { AvahiStringList *l; g_autoptr(GHashTable) out = NULL; out = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_bytes_unref); - for (l = txt; l != NULL; l = avahi_string_list_get_next (l)) + for (l = txt_list; l != NULL; l = avahi_string_list_get_next (l)) { const guint8 *txt; gsize txt_len; diff --git a/src/libostree/ostree-repo-finder-mount.c b/src/libostree/ostree-repo-finder-mount.c index c259f3e..5c8ab1f 100644 --- a/src/libostree/ostree-repo-finder-mount.c +++ b/src/libostree/ostree-repo-finder-mount.c @@ -336,7 +336,6 @@ ostree_repo_finder_mount_resolve_async (OstreeRepoFinder *finde g_autoptr(GHashTable) repo_to_refs = NULL; /* (element-type UriAndKeyring GHashTable) */ GHashTable *supported_ref_to_checksum; /* (element-type OstreeCollectionRef utf8) */ GHashTableIter iter; - UriAndKeyring *repo; g_autoptr(GError) local_error = NULL; mount_name = g_mount_get_name (mount); @@ -525,6 +524,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS /* Aggregate the results. */ g_hash_table_iter_init (&iter, repo_to_refs); + UriAndKeyring *repo; while (g_hash_table_iter_next (&iter, (gpointer *) &repo, (gpointer *) &supported_ref_to_checksum)) { g_autoptr(OstreeRemote) remote = NULL; diff --git a/src/libostree/ostree-repo-finder-override.c b/src/libostree/ostree-repo-finder-override.c index 3219954..d6fb5a9 100644 --- a/src/libostree/ostree-repo-finder-override.c +++ b/src/libostree/ostree-repo-finder-override.c @@ -151,7 +151,6 @@ ostree_repo_finder_override_resolve_async (OstreeRepoFinder *fi GHashTable *supported_ref_to_checksum; /* (element-type OstreeCollectionRef utf8) */ GHashTableIter iter; const gchar *remote_uri; - OstreeRemote *remote; task = g_task_new (finder, cancellable, callback, user_data); g_task_set_source_tag (task, ostree_repo_finder_override_resolve_async); @@ -242,6 +241,7 @@ ostree_repo_finder_override_resolve_async (OstreeRepoFinder *fi /* Aggregate the results. */ g_hash_table_iter_init (&iter, repo_remote_to_refs); + OstreeRemote *remote; while (g_hash_table_iter_next (&iter, (gpointer *) &remote, (gpointer *) &supported_ref_to_checksum)) g_ptr_array_add (results, ostree_repo_finder_result_new (remote, finder, priority, supported_ref_to_checksum, NULL, 0)); diff --git a/src/libostree/ostree-repo-libarchive.c b/src/libostree/ostree-repo-libarchive.c index d55459f..ef7252e 100644 --- a/src/libostree/ostree-repo-libarchive.c +++ b/src/libostree/ostree-repo-libarchive.c @@ -1161,16 +1161,16 @@ write_directory_to_libarchive_recurse (OstreeRepo *self, { guint8 buf[8192]; g_autoptr(GInputStream) file_in = NULL; - g_autoptr(GFileInfo) file_info = NULL; + g_autoptr(GFileInfo) regular_file_info = NULL; const char *checksum; checksum = ostree_repo_file_get_checksum ((OstreeRepoFile*)path); - if (!ostree_repo_load_file (self, checksum, &file_in, &file_info, NULL, + if (!ostree_repo_load_file (self, checksum, &file_in, ®ular_file_info, NULL, cancellable, error)) goto out; - archive_entry_set_size (entry, g_file_info_get_size (file_info)); + archive_entry_set_size (entry, g_file_info_get_size (regular_file_info)); if (archive_write_header (a, entry) != ARCHIVE_OK) { diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index 8c1f507..a48feca 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -55,6 +55,8 @@ G_BEGIN_DECLS #define OSTREE_SUMMARY_EXPIRES "ostree.summary.expires" #define OSTREE_SUMMARY_COLLECTION_ID "ostree.summary.collection-id" #define OSTREE_SUMMARY_COLLECTION_MAP "ostree.summary.collection-map" +#define OSTREE_SUMMARY_MODE "ostree.summary.mode" +#define OSTREE_SUMMARY_TOMBSTONE_COMMITS "ostree.summary.tombstone-commits" #define _OSTREE_PAYLOAD_LINK_PREFIX "../" #define _OSTREE_PAYLOAD_LINK_PREFIX_LEN (sizeof (_OSTREE_PAYLOAD_LINK_PREFIX) - 1) diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index 5fdbeab..58c8054 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -436,8 +436,9 @@ ensure_idle_queued (OtPullData *pull_data) idle_src = g_idle_source_new (); g_source_set_callback (idle_src, idle_worker, pull_data, NULL); g_source_attach (idle_src, pull_data->main_context); - g_source_unref (idle_src); pull_data->idle_src = idle_src; + /* Ownership is transferred to pull_data */ + g_source_unref (idle_src); } typedef struct { @@ -1624,13 +1625,13 @@ scan_commit_object (OtPullData *pull_data, } if (pull_data->timestamp_check_from_rev) { - g_autoptr(GVariant) commit = NULL; + g_autoptr(GVariant) timestamp_commit = NULL; if (!ostree_repo_load_commit (pull_data->repo, pull_data->timestamp_check_from_rev, - &commit, NULL, error)) + ×tamp_commit, NULL, error)) return glnx_prefix_error (error, "Reading %s for timestamp-check-from-rev", pull_data->timestamp_check_from_rev); - guint64 ts = ostree_commit_get_timestamp (commit); + guint64 ts = ostree_commit_get_timestamp (timestamp_commit); if (!_ostree_compare_timestamps (pull_data->timestamp_check_from_rev, ts, checksum, new_ts, error)) return FALSE; } @@ -2000,6 +2001,8 @@ start_fetch (OtPullData *pull_data, is_meta ? meta_fetch_on_complete : content_fetch_on_complete, fetch); } +/* Deprecated: code should load options from the `summary` file rather than + * downloading the remote’s `config` file, to save on network round trips. */ static gboolean load_remote_repo_config (OtPullData *pull_data, GKeyFile **out_keyfile, @@ -2512,10 +2515,7 @@ on_superblock_fetched (GObject *src, const guchar *expected_summary_digest = g_hash_table_lookup (pull_data->summary_deltas_checksums, delta); guint8 actual_summary_digest[OSTREE_SHA256_DIGEST_LEN]; - g_auto(OtChecksum) hasher = { 0, }; - ot_checksum_init (&hasher); - ot_checksum_update_bytes (&hasher, delta_superblock_data); - ot_checksum_get_digest (&hasher, actual_summary_digest, sizeof (actual_summary_digest)); + ot_checksum_bytes (delta_superblock_data, actual_summary_digest); #ifndef OSTREE_DISABLE_GPGME /* At this point we've GPG verified the data, so in theory @@ -2625,88 +2625,198 @@ validate_variant_is_csum (GVariant *csum, return ostree_validate_structureof_csum_v (csum, error); } +static gboolean +_ostree_repo_verify_summary (OstreeRepo *self, + const char *name, + gboolean gpg_verify_summary, + GPtrArray *signapi_summary_verifiers, + GBytes *summary, + GBytes *signatures, + GCancellable *cancellable, + GError **error) +{ + if (gpg_verify_summary) + { + if (summary == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "GPG verification enabled, but no summary found (check that the configured URL in remote config is correct)"); + return FALSE; + } + + if (signatures == NULL) + { + g_set_error (error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_NO_SIGNATURE, + "GPG verification enabled, but no summary signatures found (use gpg-verify-summary=false in remote config to disable)"); + return FALSE; + } + + /* Verify any summary signatures. */ + if (summary != NULL && signatures != NULL) + { + g_autoptr(OstreeGpgVerifyResult) result = NULL; + + result = ostree_repo_verify_summary (self, + name, + summary, + signatures, + cancellable, + error); + if (!ostree_gpg_verify_result_require_valid_signature (result, error)) + return FALSE; + } + } + + if (signapi_summary_verifiers) + { + if (summary == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Signature verification enabled, but no summary found (check that the configured URL in remote config is correct)"); + return FALSE; + } + + if (signatures == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Signature verification enabled, but no summary signatures found (use sign-verify-summary=false in remote config to disable)"); + return FALSE; + } + + /* Verify any summary signatures. */ + if (summary != NULL && signatures != NULL) + { + g_autoptr(GVariant) sig_variant = NULL; + + sig_variant = g_variant_new_from_bytes (OSTREE_SUMMARY_SIG_GVARIANT_FORMAT, + signatures, FALSE); + + if (!_sign_verify_for_remote (signapi_summary_verifiers, summary, sig_variant, NULL, error)) + return FALSE; + } + } + + return TRUE; +} + +static gboolean +_ostree_repo_load_cache_summary_file (OstreeRepo *self, + const char *filename, + const char *extension, + GBytes **out_data, + GCancellable *cancellable, + GError **error) +{ + const char *file = glnx_strjoina (_OSTREE_SUMMARY_CACHE_DIR, "/", filename, extension); + glnx_autofd int fd = -1; + g_autoptr(GBytes) data = NULL; + + *out_data = NULL; + + if (self->cache_dir_fd == -1) + return TRUE; + + fd = openat (self->cache_dir_fd, file, O_CLOEXEC | O_RDONLY); + if (fd < 0) + { + if (errno == ENOENT) + return TRUE; + return glnx_throw_errno_prefix (error, "openat(%s)", file); + } + + data = ot_fd_readall_or_mmap (fd, 0, error); + if (!data) + return FALSE; + + *out_data =g_steal_pointer (&data); + return TRUE; +} + /* Load the summary from the cache if the provided .sig file is the same as the cached version. */ static gboolean _ostree_repo_load_cache_summary_if_same_sig (OstreeRepo *self, const char *remote, GBytes *summary_sig, - GBytes **summary, + GBytes **out_summary, GCancellable *cancellable, GError **error) { - if (self->cache_dir_fd == -1) - return TRUE; + g_autoptr(GBytes) old_sig_contents = NULL; - const char *summary_cache_sig_file = glnx_strjoina (_OSTREE_SUMMARY_CACHE_DIR, "/", remote, ".sig"); - glnx_autofd int prev_fd = -1; - if (!ot_openat_ignore_enoent (self->cache_dir_fd, summary_cache_sig_file, &prev_fd, error)) - return FALSE; - if (prev_fd < 0) - return TRUE; /* Note early return */ + *out_summary = NULL; - g_autoptr(GBytes) old_sig_contents = ot_fd_readall_or_mmap (prev_fd, 0, error); - if (!old_sig_contents) + if (!_ostree_repo_load_cache_summary_file (self, remote, ".sig", + &old_sig_contents, + cancellable, error)) return FALSE; - if (g_bytes_compare (old_sig_contents, summary_sig) == 0) + if (old_sig_contents != NULL && + g_bytes_compare (old_sig_contents, summary_sig) == 0) { - const char *summary_cache_file = glnx_strjoina (_OSTREE_SUMMARY_CACHE_DIR, "/", remote); - glnx_autofd int summary_fd = -1; - GBytes *summary_data; + g_autoptr(GBytes) summary_data = NULL; + if (!_ostree_repo_load_cache_summary_file (self, remote, NULL, + &summary_data, + cancellable, error)) + return FALSE; - summary_fd = openat (self->cache_dir_fd, summary_cache_file, O_CLOEXEC | O_RDONLY); - if (summary_fd < 0) + if (summary_data == NULL) { - if (errno == ENOENT) - { - (void) unlinkat (self->cache_dir_fd, summary_cache_sig_file, 0); - return TRUE; /* Note early return */ - } - - return glnx_throw_errno_prefix (error, "openat(%s)", summary_cache_file); + /* Cached signature without cached summary, remove the signature */ + const char *summary_cache_sig_file = glnx_strjoina (_OSTREE_SUMMARY_CACHE_DIR, "/", remote, ".sig"); + (void) unlinkat (self->cache_dir_fd, summary_cache_sig_file, 0); } - - summary_data = glnx_fd_readall_bytes (summary_fd, cancellable, error); - if (!summary_data) - return FALSE; - *summary = summary_data; + else + *out_summary = g_steal_pointer (&summary_data); } + return TRUE; } -/* Replace the current summary+signature with new versions */ static gboolean -_ostree_repo_cache_summary (OstreeRepo *self, - const char *remote, - GBytes *summary, - GBytes *summary_sig, - GCancellable *cancellable, - GError **error) +_ostree_repo_save_cache_summary_file (OstreeRepo *self, + const char *filename, + const char *extension, + GBytes *data, + GCancellable *cancellable, + GError **error) { + const char *file = glnx_strjoina (_OSTREE_SUMMARY_CACHE_DIR, "/", filename, extension); + glnx_autofd int fd = -1; + if (self->cache_dir_fd == -1) return TRUE; 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); if (!glnx_file_replace_contents_at (self->cache_dir_fd, - summary_cache_file, - g_bytes_get_data (summary, NULL), - g_bytes_get_size (summary), + file, + g_bytes_get_data (data, NULL), + g_bytes_get_size (data), self->disable_fsync ? GLNX_FILE_REPLACE_NODATASYNC : GLNX_FILE_REPLACE_DATASYNC_NEW, cancellable, error)) return FALSE; - const char *summary_cache_sig_file = glnx_strjoina (_OSTREE_SUMMARY_CACHE_DIR, "/", remote, ".sig"); - if (!glnx_file_replace_contents_at (self->cache_dir_fd, - summary_cache_sig_file, - g_bytes_get_data (summary_sig, NULL), - g_bytes_get_size (summary_sig), - self->disable_fsync ? GLNX_FILE_REPLACE_NODATASYNC : GLNX_FILE_REPLACE_DATASYNC_NEW, - cancellable, error)) + return TRUE; +} + +/* Replace the current summary+signature with new versions */ +static gboolean +_ostree_repo_cache_summary (OstreeRepo *self, + const char *remote, + GBytes *summary, + GBytes *summary_sig, + GCancellable *cancellable, + GError **error) +{ + if (!_ostree_repo_save_cache_summary_file (self, remote, NULL, + summary, cancellable, error)) + return FALSE; + + if (!_ostree_repo_save_cache_summary_file (self, remote, ".sig", + summary_sig, cancellable, error)) return FALSE; return TRUE; @@ -2716,6 +2826,8 @@ static OstreeFetcher * _ostree_repo_remote_new_fetcher (OstreeRepo *self, const char *remote_name, gboolean gzip, + GVariant *extra_headers, + const char *append_user_agent, OstreeFetcherSecurityState *out_state, GError **error) { @@ -2829,6 +2941,12 @@ _ostree_repo_remote_new_fetcher (OstreeRepo *self, _ostree_fetcher_set_cookie_jar (fetcher, jar_path); } + if (extra_headers) + _ostree_fetcher_set_extra_headers (fetcher, extra_headers); + + if (append_user_agent) + _ostree_fetcher_set_extra_user_agent (fetcher, append_user_agent); + success = TRUE; out: @@ -2976,127 +3094,43 @@ fetch_mirrorlist (OstreeFetcher *fetcher, } static gboolean -repo_remote_fetch_summary (OstreeRepo *self, - const char *name, - const char *metalink_url_string, - GVariant *options, - GBytes **out_summary, - GBytes **out_signatures, - gboolean *out_from_cache, - GCancellable *cancellable, - GError **error) +compute_effective_mirrorlist (OstreeRepo *self, + const char *remote_name_or_baseurl, + const char *url_override, + OstreeFetcher *fetcher, + guint n_network_retries, + GPtrArray **out_mirrorlist, + GCancellable *cancellable, + GError **error) { - g_autoptr(OstreeFetcher) fetcher = NULL; - g_autoptr(GMainContext) mainctx = NULL; - gboolean ret = FALSE; - gboolean from_cache = FALSE; - const char *url_override = NULL; - g_autoptr(GVariant) extra_headers = NULL; - g_autoptr(GPtrArray) mirrorlist = NULL; - const char *append_user_agent = NULL; - guint n_network_retries = DEFAULT_N_NETWORK_RETRIES; - - if (options) - { - (void) g_variant_lookup (options, "override-url", "&s", &url_override); - (void) g_variant_lookup (options, "http-headers", "@a(ss)", &extra_headers); - (void) g_variant_lookup (options, "append-user-agent", "&s", &append_user_agent); - (void) g_variant_lookup (options, "n-network-retries", "&u", &n_network_retries); - } - - mainctx = g_main_context_new (); - g_main_context_push_thread_default (mainctx); - - fetcher = _ostree_repo_remote_new_fetcher (self, name, TRUE, NULL, error); - if (fetcher == NULL) - goto out; - - if (extra_headers) - _ostree_fetcher_set_extra_headers (fetcher, extra_headers); - - if (append_user_agent) - _ostree_fetcher_set_extra_user_agent (fetcher, append_user_agent); - - { - g_autofree char *url_string = NULL; - if (metalink_url_string) - url_string = g_strdup (metalink_url_string); - else if (url_override) - url_string = g_strdup (url_override); - else if (!ostree_repo_remote_get_url (self, name, &url_string, error)) - goto out; - - if (metalink_url_string == NULL && - g_str_has_prefix (url_string, "mirrorlist=")) - { - if (!fetch_mirrorlist (fetcher, url_string + strlen ("mirrorlist="), - n_network_retries, &mirrorlist, cancellable, error)) - goto out; - } - else - { - g_autoptr(OstreeFetcherURI) uri = _ostree_fetcher_uri_parse (url_string, error); - - if (!uri) - goto out; - - mirrorlist = - g_ptr_array_new_with_free_func ((GDestroyNotify) _ostree_fetcher_uri_free); - g_ptr_array_add (mirrorlist, g_steal_pointer (&uri)); - } - } + g_autofree char *baseurl = NULL; - /* FIXME: Send the ETag from the cache with the request for summary.sig to - * avoid downloading summary.sig unnecessarily. This won’t normally provide - * any benefits (but won’t do any harm) since summary.sig is typically 500B - * in size. But if a repository has multiple keys, the signature file will - * grow and this optimisation may be useful. */ - if (!_ostree_preload_metadata_file (self, - fetcher, - mirrorlist, - "summary.sig", - metalink_url_string ? TRUE : FALSE, - n_network_retries, - out_signatures, - cancellable, - error)) - goto out; + if (url_override != NULL) + baseurl = g_strdup (url_override); + else if (!ostree_repo_remote_get_url (self, remote_name_or_baseurl, &baseurl, error)) + return FALSE; - if (*out_signatures) + if (g_str_has_prefix (baseurl, "mirrorlist=")) { - if (!_ostree_repo_load_cache_summary_if_same_sig (self, - name, - *out_signatures, - out_summary, - cancellable, - error)) - goto out; + if (!fetch_mirrorlist (fetcher, + baseurl + strlen ("mirrorlist="), + n_network_retries, + out_mirrorlist, + cancellable, error)) + return FALSE; } - - if (*out_summary) - from_cache = TRUE; else { - if (!_ostree_preload_metadata_file (self, - fetcher, - mirrorlist, - "summary", - metalink_url_string ? TRUE : FALSE, - n_network_retries, - out_summary, - cancellable, - error)) - goto out; - } - - ret = TRUE; + g_autoptr(OstreeFetcherURI) baseuri = _ostree_fetcher_uri_parse (baseurl, error); - out: - if (mainctx) - g_main_context_pop_thread_default (mainctx); + if (!baseuri) + return FALSE; - *out_from_cache = from_cache; - return ret; + *out_mirrorlist = + g_ptr_array_new_with_free_func ((GDestroyNotify) _ostree_fetcher_uri_free); + g_ptr_array_add (*out_mirrorlist, g_steal_pointer (&baseuri)); + } + return TRUE; } /* Create the fetcher by unioning options from the remote config, plus @@ -3108,17 +3142,13 @@ reinitialize_fetcher (OtPullData *pull_data, const char *remote_name, { g_clear_object (&pull_data->fetcher); pull_data->fetcher = _ostree_repo_remote_new_fetcher (pull_data->repo, remote_name, FALSE, + pull_data->extra_headers, + pull_data->append_user_agent, &pull_data->fetcher_security_state, error); if (pull_data->fetcher == NULL) return FALSE; - if (pull_data->extra_headers) - _ostree_fetcher_set_extra_headers (pull_data->fetcher, pull_data->extra_headers); - - if (pull_data->append_user_agent) - _ostree_fetcher_set_extra_user_agent (pull_data->fetcher, pull_data->append_user_agent); - return TRUE; } @@ -3273,37 +3303,37 @@ initiate_request (OtPullData *pull_data, * Like ostree_repo_pull(), but supports an extensible set of flags. * The following are currently defined: * - * * refs (as): Array of string refs - * * collection-refs (a(sss)): Array of (collection ID, ref name, checksum) tuples to pull; + * * `refs` (`as`): Array of string refs + * * `collection-refs` (`a(sss)`): Array of (collection ID, ref name, checksum) tuples to pull; * mutually exclusive with `refs` and `override-commit-ids`. Checksums may be the empty * string to pull the latest commit for that ref - * * flags (i): An instance of #OstreeRepoPullFlags - * * subdir (s): Pull just this subdirectory - * * subdirs (as): Pull just these subdirectories - * * override-remote-name (s): If local, add this remote to refspec - * * gpg-verify (b): GPG verify commits - * * gpg-verify-summary (b): GPG verify summary - * * disable-sign-verify (b): Disable signapi verification of commits - * * disable-sign-verify-summary (b): Disable signapi verification of the summary - * * depth (i): How far in the history to traverse; default is 0, -1 means infinite - * * per-object-fsync (b): Perform disk writes more slowly, avoiding a single large I/O sync - * * disable-static-deltas (b): Do not use static deltas - * * require-static-deltas (b): Require static deltas - * * override-commit-ids (as): Array of specific commit IDs to fetch for refs - * * timestamp-check (b): Verify commit timestamps are newer than current (when pulling via ref); Since: 2017.11 - * * timestamp-check-from-rev (s): Verify that all fetched commit timestamps are newer than timestamp of given rev; Since: 2020.4 - * * metadata-size-restriction (t): Restrict metadata objects to a maximum number of bytes; 0 to disable. Since: 2018.9 - * * dry-run (b): Only print information on what will be downloaded (requires static deltas) - * * override-url (s): Fetch objects from this URL if remote specifies no metalink in options - * * inherit-transaction (b): Don't initiate, finish or abort a transaction, useful to do multiple pulls in one transaction. - * * http-headers (a(ss)): Additional headers to add to all HTTP requests - * * update-frequency (u): Frequency to call the async progress callback in milliseconds, if any; only values higher than 0 are valid - * * localcache-repos (as): File paths for local repos to use as caches when doing remote fetches - * * append-user-agent (s): Additional string to append to the user agent - * * n-network-retries (u): Number of times to retry each download on receiving + * * `flags` (`i`): An instance of #OstreeRepoPullFlags + * * `subdir` (`s`): Pull just this subdirectory + * * `subdirs` (`as`): Pull just these subdirectories + * * `override-remote-name` (`s`): If local, add this remote to refspec + * * `gpg-verify` (`b`): GPG verify commits + * * `gpg-verify-summary` (`b`): GPG verify summary + * * `disable-sign-verify` (`b`): Disable signapi verification of commits + * * `disable-sign-verify-summary` (`b`): Disable signapi verification of the summary + * * `depth` (`i`): How far in the history to traverse; default is 0, -1 means infinite + * * `per-object-fsync` (`b`): Perform disk writes more slowly, avoiding a single large I/O sync + * * `disable-static-deltas` (`b`): Do not use static deltas + * * `require-static-deltas` (`b`): Require static deltas + * * `override-commit-ids` (`as`): Array of specific commit IDs to fetch for refs + * * `timestamp-check` (`b`): Verify commit timestamps are newer than current (when pulling via ref); Since: 2017.11 + * * `timestamp-check-from-rev` (`s`): Verify that all fetched commit timestamps are newer than timestamp of given rev; Since: 2020.4 + * * `metadata-size-restriction` (`t`): Restrict metadata objects to a maximum number of bytes; 0 to disable. Since: 2018.9 + * * `dry-run` (`b`): Only print information on what will be downloaded (requires static deltas) + * * `override-url` (`s`): Fetch objects from this URL if remote specifies no metalink in options + * * `inherit-transaction` (`b`): Don't initiate, finish or abort a transaction, useful to do multiple pulls in one transaction. + * * `http-headers` (`a(ss)`): Additional headers to add to all HTTP requests + * * `update-frequency` (`u`): Frequency to call the async progress callback in milliseconds, if any; only values higher than 0 are valid + * * `localcache-repos` (`as`): File paths for local repos to use as caches when doing remote fetches + * * `append-user-agent` (`s`): Additional string to append to the user agent + * * `n-network-retries` (`u`): Number of times to retry each download on receiving * a transient network error, such as a socket timeout; default is 5, 0 * means return errors without retrying. Since: 2018.6 - * * ref-keyring-map (a(sss)): Array of (collection ID, ref name, keyring + * * `ref-keyring-map` (`a(sss)`): Array of (collection ID, ref name, keyring * remote name) tuples specifying which remote's keyring should be used when * doing GPG verification of each collection-ref. This is useful to prevent a * remote from serving malicious updates to refs which did not originate from @@ -3311,6 +3341,14 @@ initiate_request (OtPullData *pull_data, * not being pulled will be ignored and any ref without a keyring remote * will be verified with the keyring of the remote being pulled from. * Since: 2019.2 + * * `summary-bytes` (`ay'): Contents of the `summary` file to use. If this is + * specified, `summary-sig-bytes` must also be specified. This is + * useful if doing multiple pull operations in a transaction, using + * ostree_repo_remote_fetch_summary_with_options() beforehand to download + * the `summary` and `summary.sig` once for the entire transaction. If not + * specified, the `summary` will be downloaded from the remote. Since: 2020.5 + * * `summary-sig-bytes` (`ay`): Contents of the `summary.sig` file. If this + * is specified, `summary-bytes` must also be specified. Since: 2020.5 */ gboolean ostree_repo_pull_with_options (OstreeRepo *self, @@ -3353,9 +3391,11 @@ ostree_repo_pull_with_options (OstreeRepo *self, const char *url_override = NULL; gboolean inherit_transaction = FALSE; g_autoptr(GHashTable) updated_requested_refs_to_fetch = NULL; /* (element-type OstreeCollectionRef utf8) */ - int i; + gsize i; g_autofree char **opt_localcache_repos = NULL; g_autoptr(GVariantIter) ref_keyring_map_iter = NULL; + g_autoptr(GVariant) summary_bytes_v = NULL; + g_autoptr(GVariant) summary_sig_bytes_v = NULL; /* If refs or collection-refs has exactly one value, this will point to that * value, otherwise NULL. Used for logging. */ @@ -3402,6 +3442,8 @@ ostree_repo_pull_with_options (OstreeRepo *self, g_variant_lookup (options, "n-network-retries", "u", &pull_data->n_network_retries); opt_ref_keyring_map_set = g_variant_lookup (options, "ref-keyring-map", "a(sss)", &ref_keyring_map_iter); + (void) g_variant_lookup (options, "summary-bytes", "@ay", &summary_bytes_v); + (void) g_variant_lookup (options, "summary-sig-bytes", "@ay", &summary_sig_bytes_v); if (pull_data->remote_refspec_name != NULL) pull_data->remote_name = g_strdup (pull_data->remote_refspec_name); @@ -3439,6 +3481,10 @@ ostree_repo_pull_with_options (OstreeRepo *self, */ g_return_val_if_fail (!pull_data->dry_run || pull_data->require_static_deltas, FALSE); + /* summary-bytes and summary-sig-bytes must both be specified, or neither be + * specified, so we know they’re consistent */ + g_return_val_if_fail ((summary_bytes_v == NULL) == (summary_sig_bytes_v == NULL), FALSE); + pull_data->is_mirror = (flags & OSTREE_REPO_PULL_FLAGS_MIRROR) > 0; pull_data->is_commit_only = (flags & OSTREE_REPO_PULL_FLAGS_COMMIT_ONLY) > 0; /* See our processing of OSTREE_REPO_PULL_FLAGS_UNTRUSTED below */ @@ -3614,33 +3660,13 @@ ostree_repo_pull_with_options (OstreeRepo *self, if (!metalink_url_str) { - g_autofree char *baseurl = NULL; - - if (url_override != NULL) - baseurl = g_strdup (url_override); - else if (!ostree_repo_remote_get_url (self, remote_name_or_baseurl, &baseurl, error)) + if (!compute_effective_mirrorlist (self, remote_name_or_baseurl, + url_override, + pull_data->fetcher, + pull_data->n_network_retries, + &pull_data->meta_mirrorlist, + cancellable, error)) goto out; - - if (g_str_has_prefix (baseurl, "mirrorlist=")) - { - if (!fetch_mirrorlist (pull_data->fetcher, - baseurl + strlen ("mirrorlist="), - pull_data->n_network_retries, - &pull_data->meta_mirrorlist, - cancellable, error)) - goto out; - } - else - { - g_autoptr(OstreeFetcherURI) baseuri = _ostree_fetcher_uri_parse (baseurl, error); - - if (!baseuri) - goto out; - - pull_data->meta_mirrorlist = - g_ptr_array_new_with_free_func ((GDestroyNotify) _ostree_fetcher_uri_free); - g_ptr_array_add (pull_data->meta_mirrorlist, g_steal_pointer (&baseuri)); - } } else { @@ -3651,6 +3677,10 @@ ostree_repo_pull_with_options (OstreeRepo *self, if (!metalink_uri) goto out; + /* FIXME: Use summary_bytes_v/summary_sig_bytes_v to avoid unnecessary + * re-downloads here. Would require additional support for caching the + * metalink file or mirror list. */ + metalink = _ostree_metalink_new (pull_data->fetcher, "summary", OSTREE_MAX_METADATA_SIZE, metalink_uri, pull_data->n_network_retries); @@ -3695,27 +3725,13 @@ ostree_repo_pull_with_options (OstreeRepo *self, } else { - if (g_str_has_prefix (contenturl, "mirrorlist=")) - { - if (!fetch_mirrorlist (pull_data->fetcher, - contenturl + strlen ("mirrorlist="), - pull_data->n_network_retries, - &pull_data->content_mirrorlist, - cancellable, error)) - goto out; - } - else - { - g_autoptr(OstreeFetcherURI) contenturi = _ostree_fetcher_uri_parse (contenturl, error); - - if (!contenturi) - goto out; - - pull_data->content_mirrorlist = - g_ptr_array_new_with_free_func ((GDestroyNotify) _ostree_fetcher_uri_free); - g_ptr_array_add (pull_data->content_mirrorlist, - g_steal_pointer (&contenturi)); - } + if (!compute_effective_mirrorlist (self, remote_name_or_baseurl, + contenturl, + pull_data->fetcher, + pull_data->n_network_retries, + &pull_data->content_mirrorlist, + cancellable, error)) + goto out; } } @@ -3743,30 +3759,6 @@ ostree_repo_pull_with_options (OstreeRepo *self, if (!ostree_repo_open (pull_data->remote_repo_local, cancellable, error)) goto out; } - else - { - if (!load_remote_repo_config (pull_data, &remote_config, cancellable, error)) - goto out; - - if (!ot_keyfile_get_value_with_default (remote_config, "core", "mode", "bare", - &remote_mode_str, error)) - goto out; - - if (!ostree_repo_mode_from_string (remote_mode_str, &pull_data->remote_mode, error)) - goto out; - - if (!ot_keyfile_get_boolean_with_default (remote_config, "core", "tombstone-commits", FALSE, - &pull_data->has_tombstone_commits, error)) - goto out; - - if (pull_data->remote_mode != OSTREE_REPO_MODE_ARCHIVE) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Can't pull from archives with mode \"%s\"", - remote_mode_str); - goto out; - } - } } /* Change some option defaults if we're actually pulling from a local @@ -3835,13 +3827,33 @@ ostree_repo_pull_with_options (OstreeRepo *self, { g_autoptr(GBytes) bytes_sig = NULL; - gsize i, n; + gsize n; g_autoptr(GVariant) refs = NULL; g_autoptr(GVariant) deltas = NULL; g_autoptr(GVariant) additional_metadata = NULL; gboolean summary_from_cache = FALSE; + gboolean remote_mode_loaded = FALSE; + gboolean tombstone_commits = FALSE; - if (!pull_data->summary_data_sig) + if (summary_sig_bytes_v) + { + /* Must both be specified */ + g_assert (summary_bytes_v); + + bytes_sig = g_variant_get_data_as_bytes (summary_sig_bytes_v); + bytes_summary = g_variant_get_data_as_bytes (summary_bytes_v); + + if (!bytes_sig || !bytes_summary) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "summary-bytes or summary-sig-bytes set to invalid value"); + goto out; + } + + g_debug ("Loaded %s summary from options", remote_name_or_baseurl); + } + + if (!bytes_sig) { if (!_ostree_fetcher_mirrored_request_to_membuf (pull_data->fetcher, pull_data->meta_mirrorlist, @@ -3854,6 +3866,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, } if (bytes_sig && + !bytes_summary && !pull_data->remote_repo_local && !_ostree_repo_load_cache_summary_if_same_sig (self, remote_name_or_baseurl, @@ -3863,7 +3876,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, error)) goto out; - if (bytes_summary) + if (bytes_summary && !summary_bytes_v) { g_debug ("Loaded %s summary from cache", remote_name_or_baseurl); summary_from_cache = TRUE; @@ -4134,6 +4147,46 @@ ostree_repo_pull_with_options (OstreeRepo *self, csum_data); } } + + if (pull_data->summary && + g_variant_lookup (additional_metadata, OSTREE_SUMMARY_MODE, "s", &remote_mode_str) && + g_variant_lookup (additional_metadata, OSTREE_SUMMARY_TOMBSTONE_COMMITS, "b", &tombstone_commits)) + { + if (!ostree_repo_mode_from_string (remote_mode_str, &pull_data->remote_mode, error)) + goto out; + pull_data->has_tombstone_commits = tombstone_commits; + remote_mode_loaded = TRUE; + } + else if (pull_data->remote_repo_local == NULL) + { + /* Fall-back path which loads the necessary config from the remote’s + * `config` file. Doing so is deprecated since it means an + * additional round trip to the remote for each pull. No need to do + * it for local pulls. */ + if (!load_remote_repo_config (pull_data, &remote_config, cancellable, error)) + goto out; + + if (!ot_keyfile_get_value_with_default (remote_config, "core", "mode", "bare", + &remote_mode_str, error)) + goto out; + + if (!ostree_repo_mode_from_string (remote_mode_str, &pull_data->remote_mode, error)) + goto out; + + if (!ot_keyfile_get_boolean_with_default (remote_config, "core", "tombstone-commits", FALSE, + &pull_data->has_tombstone_commits, error)) + goto out; + + remote_mode_loaded = TRUE; + } + + if (remote_mode_loaded && pull_data->remote_repo_local == NULL && pull_data->remote_mode != OSTREE_REPO_MODE_ARCHIVE) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Can't pull from archives with mode \"%s\"", + remote_mode_str); + goto out; + } } if (pull_data->is_mirror && !refs_to_fetch && !opt_collection_refs_set && !configured_branches) @@ -5194,7 +5247,7 @@ find_remotes_process_refs (OstreeRepo *self, static void find_remotes_cb (GObject *obj, - GAsyncResult *result, + GAsyncResult *async_result, gpointer user_data) { OstreeRepo *self; @@ -5226,7 +5279,7 @@ find_remotes_cb (GObject *obj, /* progress = data->progress; */ /* Finish finding the remotes. */ - results = ostree_repo_finder_resolve_all_finish (result, &error); + results = ostree_repo_finder_resolve_all_finish (async_result, &error); if (results == NULL) { @@ -5319,8 +5372,8 @@ find_remotes_cb (GObject *obj, /* Check the metadata in the summary file, especially whether it contains * all the @refs we are interested in. */ - summary_v = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, - summary_bytes, FALSE); + summary_v = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, + summary_bytes, FALSE)); /* Check the summary’s additional metadata and set up @commit_metadata * and @refs_and_remotes_table with the refs listed in the summary file, @@ -5431,7 +5484,7 @@ find_remotes_cb (GObject *obj, goto error; fetcher = _ostree_repo_remote_new_fetcher (self, result->remote->name, - TRUE, NULL, &error); + TRUE, NULL, NULL, NULL, &error); if (fetcher == NULL) goto error; @@ -6053,96 +6106,108 @@ ostree_repo_remote_fetch_summary_with_options (OstreeRepo *self, g_autoptr(GBytes) signatures = NULL; gboolean gpg_verify_summary; g_autoptr(GPtrArray) signapi_summary_verifiers = NULL; - gboolean ret = FALSE; - gboolean summary_is_from_cache; + gboolean summary_is_from_cache = FALSE; + g_autoptr(OstreeFetcher) fetcher = NULL; + g_autoptr(GMainContextPopDefault) mainctx = NULL; + const char *url_override = NULL; + g_autoptr(GVariant) extra_headers = NULL; + g_autoptr(GPtrArray) mirrorlist = NULL; + const char *append_user_agent = NULL; + guint n_network_retries = DEFAULT_N_NETWORK_RETRIES; g_return_val_if_fail (OSTREE_REPO (self), FALSE); g_return_val_if_fail (name != NULL, FALSE); if (!ostree_repo_get_remote_option (self, name, "metalink", NULL, &metalink_url_string, error)) - goto out; - - if (!repo_remote_fetch_summary (self, - name, - metalink_url_string, - options, - &summary, - &signatures, - &summary_is_from_cache, - cancellable, - error)) - goto out; - - if (!ostree_repo_remote_get_gpg_verify_summary (self, name, &gpg_verify_summary, error)) - goto out; + return FALSE; - if (gpg_verify_summary) + if (options) { - if (summary == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "GPG verification enabled, but no summary found (check that the configured URL in remote config is correct)"); - goto out; - } - - if (signatures == NULL) - { - g_set_error (error, OSTREE_GPG_ERROR, OSTREE_GPG_ERROR_NO_SIGNATURE, - "GPG verification enabled, but no summary signatures found (use gpg-verify-summary=false in remote config to disable)"); - goto out; - } - - /* Verify any summary signatures. */ - if (summary != NULL && signatures != NULL) - { - g_autoptr(OstreeGpgVerifyResult) result = NULL; - - result = ostree_repo_verify_summary (self, - name, - summary, - signatures, - cancellable, - error); - if (!ostree_gpg_verify_result_require_valid_signature (result, error)) - goto out; - } + (void) g_variant_lookup (options, "override-url", "&s", &url_override); + (void) g_variant_lookup (options, "http-headers", "@a(ss)", &extra_headers); + (void) g_variant_lookup (options, "append-user-agent", "&s", &append_user_agent); + (void) g_variant_lookup (options, "n-network-retries", "u", &n_network_retries); } + if (!ostree_repo_remote_get_gpg_verify_summary (self, name, &gpg_verify_summary, error)) + return FALSE; + if (!_signapi_init_for_remote (self, name, NULL, &signapi_summary_verifiers, error)) - goto out; + return FALSE; - if (signapi_summary_verifiers) + mainctx = _ostree_main_context_new_default (); + + fetcher = _ostree_repo_remote_new_fetcher (self, name, TRUE, extra_headers, append_user_agent, NULL, error); + if (fetcher == NULL) + return FALSE; + + if (metalink_url_string) { - if (summary == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "Signature verification enabled, but no summary found (check that the configured URL in remote config is correct)"); - goto out; - } + g_autoptr(OstreeFetcherURI) uri = _ostree_fetcher_uri_parse (metalink_url_string, error); + if (!uri) + return FALSE; - if (signatures == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "Signature verification enabled, but no summary signatures found (use sign-verify-summary=false in remote config to disable)"); - goto out; - } + mirrorlist = + g_ptr_array_new_with_free_func ((GDestroyNotify) _ostree_fetcher_uri_free); + g_ptr_array_add (mirrorlist, g_steal_pointer (&uri)); + } + else if (!compute_effective_mirrorlist (self, name, url_override, + fetcher, n_network_retries, + &mirrorlist, cancellable, error)) + return FALSE; - /* Verify any summary signatures. */ - if (summary != NULL && signatures != NULL) - { - g_autoptr(GVariant) sig_variant = NULL; + /* FIXME: Send the ETag from the cache with the request for summary.sig to + * avoid downloading summary.sig unnecessarily. This won’t normally provide + * any benefits (but won’t do any harm) since summary.sig is typically 500B + * in size. But if a repository has multiple keys, the signature file will + * grow and this optimisation may be useful. */ + if (!_ostree_preload_metadata_file (self, + fetcher, + mirrorlist, + "summary.sig", + metalink_url_string ? TRUE : FALSE, + n_network_retries, + &signatures, + cancellable, + error)) + return FALSE; - sig_variant = g_variant_new_from_bytes (OSTREE_SUMMARY_SIG_GVARIANT_FORMAT, - signatures, FALSE); + if (signatures) + { + if (!_ostree_repo_load_cache_summary_if_same_sig (self, + name, + signatures, + &summary, + cancellable, + error)) + return FALSE; + } - if (!_sign_verify_for_remote (signapi_summary_verifiers, summary, sig_variant, NULL, error)) - goto out; - } + if (summary) + summary_is_from_cache = TRUE; + else + { + if (!_ostree_preload_metadata_file (self, + fetcher, + mirrorlist, + "summary", + metalink_url_string ? TRUE : FALSE, + n_network_retries, + &summary, + cancellable, + error)) + return FALSE; } + if (!_ostree_repo_verify_summary (self, name, + gpg_verify_summary, signapi_summary_verifiers, + summary, signatures, + cancellable, error)) + return FALSE; + if (!summary_is_from_cache && summary && signatures) { g_autoptr(GError) temp_error = NULL; @@ -6159,7 +6224,7 @@ ostree_repo_remote_fetch_summary_with_options (OstreeRepo *self, else { g_propagate_error (error, g_steal_pointer (&temp_error)); - goto out; + return FALSE; } } } @@ -6170,10 +6235,7 @@ ostree_repo_remote_fetch_summary_with_options (OstreeRepo *self, if (out_signatures != NULL) *out_signatures = g_steal_pointer (&signatures); - ret = TRUE; - -out: - return ret; + return TRUE; } #else /* HAVE_LIBCURL_OR_LIBSOUP */ diff --git a/src/libostree/ostree-repo-static-delta-compilation.c b/src/libostree/ostree-repo-static-delta-compilation.c index 3e9a8e3..753f8ae 100644 --- a/src/libostree/ostree-repo-static-delta-compilation.c +++ b/src/libostree/ostree-repo-static-delta-compilation.c @@ -36,6 +36,8 @@ #include "libglnx.h" #include "ostree-varint.h" #include "bsdiff/bsdiff.h" +#include "ostree-autocleanups.h" +#include "ostree-sign.h" #define CONTENT_SIZE_SIMILARITY_THRESHOLD_PERCENT (30) @@ -1335,6 +1337,8 @@ get_fallback_headers (OstreeRepo *self, * - verbose: b: Print diagnostic messages. Default FALSE. * - endianness: b: Deltas use host byte order by default; this option allows choosing (G_BIG_ENDIAN or G_LITTLE_ENDIAN) * - filename: ay: Save delta superblock to this filename, and parts in the same directory. Default saves to repository. + * - sign-name: ay: Signature type to use. + * - sign-key-ids: as: Array of keys used to sign delta superblock. */ gboolean ostree_repo_static_delta_generate (OstreeRepo *self, @@ -1368,6 +1372,8 @@ ostree_repo_static_delta_generate (OstreeRepo *self, g_autoptr(GPtrArray) builder_fallback_objects = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref); g_auto(GLnxTmpfile) descriptor_tmpf = { 0, }; g_autoptr(OtVariantBuilder) descriptor_builder = NULL; + const char *opt_sign_name; + const char **opt_key_ids; if (!g_variant_lookup (params, "min-fallback-size", "u", &min_fallback_size)) min_fallback_size = 4; @@ -1407,6 +1413,12 @@ ostree_repo_static_delta_generate (OstreeRepo *self, if (!g_variant_lookup (params, "filename", "^&ay", &opt_filename)) opt_filename = NULL; + if (!g_variant_lookup (params, "sign-name", "^&ay", &opt_sign_name)) + opt_sign_name = NULL; + + if (!g_variant_lookup (params, "sign-key-ids", "^a&s", &opt_key_ids)) + opt_key_ids = NULL; + if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_COMMIT, to, &to_commit, error)) return FALSE; @@ -1442,7 +1454,7 @@ ostree_repo_static_delta_generate (OstreeRepo *self, cancellable, error)) return FALSE; - if (!glnx_open_tmpfile_linkable_at (descriptor_dfd, ".", O_WRONLY | O_CLOEXEC, + if (!glnx_open_tmpfile_linkable_at (descriptor_dfd, ".", O_RDWR | O_CLOEXEC, &descriptor_tmpf, error)) return FALSE; @@ -1586,12 +1598,85 @@ ostree_repo_static_delta_generate (OstreeRepo *self, g_printerr ("bsdiff=%u objects\n", builder.n_bsdiff); } - if (fchmod (descriptor_tmpf.fd, 0644) < 0) - return glnx_throw_errno_prefix (error, "fchmod"); + if (opt_sign_name != NULL && opt_key_ids != NULL) + { + g_autoptr(GBytes) tmpdata = NULL; + g_autoptr(OstreeSign) sign = NULL; + const gchar *signature_key = NULL; + g_autoptr(GVariantBuilder) signature_builder = NULL; + g_auto(GLnxTmpfile) descriptor_sign_tmpf = { 0, }; + g_autoptr(OtVariantBuilder) descriptor_sign_builder = NULL; + + lseek (descriptor_tmpf.fd, 0, SEEK_SET); + tmpdata = glnx_fd_readall_bytes (descriptor_tmpf.fd, cancellable, error); + if (!tmpdata) + return FALSE; - if (!glnx_link_tmpfile_at (&descriptor_tmpf, GLNX_LINK_TMPFILE_REPLACE, - descriptor_dfd, descriptor_name, error)) - return FALSE; + sign = ostree_sign_get_by_name (opt_sign_name, error); + if (sign == NULL) + return FALSE; + + signature_key = ostree_sign_metadata_key (sign); + const gchar *signature_format = ostree_sign_metadata_format (sign); + + signature_builder = g_variant_builder_new (G_VARIANT_TYPE (signature_format)); + + for (const char **iter = opt_key_ids; iter && *iter; iter++) + { + const char *keyid = *iter; + g_autoptr(GVariant) secret_key = NULL; + g_autoptr(GBytes) signature_bytes = NULL; + + secret_key = g_variant_new_string (keyid); + if (!ostree_sign_set_sk (sign, secret_key, error)) + return FALSE; + + if (!ostree_sign_data (sign, tmpdata, &signature_bytes, + NULL, error)) + return FALSE; + + g_variant_builder_add (signature_builder, "@ay", ot_gvariant_new_ay_bytes (signature_bytes)); + } + + if (!glnx_open_tmpfile_linkable_at (descriptor_dfd, ".", O_WRONLY | O_CLOEXEC, + &descriptor_sign_tmpf, error)) + return FALSE; + + descriptor_sign_builder = ot_variant_builder_new (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SIGNED_FORMAT), + descriptor_sign_tmpf.fd); + + if (!ot_variant_builder_add (descriptor_sign_builder, error, "t", + GUINT64_TO_BE (OSTREE_STATIC_DELTA_SIGNED_MAGIC))) + return FALSE; + if (!ot_variant_builder_add (descriptor_sign_builder, error, "@ay", ot_gvariant_new_ay_bytes (tmpdata))) + return FALSE; + if (!ot_variant_builder_open (descriptor_sign_builder, G_VARIANT_TYPE ("a{sv}"), error)) + return FALSE; + if (!ot_variant_builder_add (descriptor_sign_builder, error, "{sv}", + signature_key, g_variant_builder_end(signature_builder))) + return FALSE; + if (!ot_variant_builder_close (descriptor_sign_builder, error)) + return FALSE; + + if (!ot_variant_builder_end (descriptor_sign_builder, error)) + return FALSE; + + if (fchmod (descriptor_sign_tmpf.fd, 0644) < 0) + return glnx_throw_errno_prefix (error, "fchmod"); + + if (!glnx_link_tmpfile_at (&descriptor_sign_tmpf, GLNX_LINK_TMPFILE_REPLACE, + descriptor_dfd, descriptor_name, error)) + return FALSE; + } + else + { + if (fchmod (descriptor_tmpf.fd, 0644) < 0) + return glnx_throw_errno_prefix (error, "fchmod"); + + if (!glnx_link_tmpfile_at (&descriptor_tmpf, GLNX_LINK_TMPFILE_REPLACE, + descriptor_dfd, descriptor_name, error)) + return FALSE; + } return TRUE; } diff --git a/src/libostree/ostree-repo-static-delta-core.c b/src/libostree/ostree-repo-static-delta-core.c index ade4e9d..d83ec8c 100644 --- a/src/libostree/ostree-repo-static-delta-core.c +++ b/src/libostree/ostree-repo-static-delta-core.c @@ -54,6 +54,28 @@ _ostree_static_delta_parse_checksum_array (GVariant *array, return TRUE; } +GVariant * +_ostree_repo_static_delta_superblock_digest (OstreeRepo *repo, + const char *from, + const char *to, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *superblock = _ostree_get_relative_static_delta_superblock_path ((from && from[0]) ? from : NULL, to); + glnx_autofd int superblock_file_fd = -1; + guint8 digest[OSTREE_SHA256_DIGEST_LEN]; + + if (!glnx_openat_rdonly (repo->repo_dir_fd, superblock, TRUE, &superblock_file_fd, error)) + return NULL; + + g_autoptr(GBytes) superblock_content = ot_fd_readall_or_mmap (superblock_file_fd, 0, error); + if (!superblock_content) + return NULL; + + ot_checksum_bytes (superblock_content, digest); + + return ot_gvariant_new_bytearray (digest, sizeof (digest)); +} /** * ostree_repo_list_static_delta_names: @@ -109,7 +131,7 @@ ostree_repo_list_static_delta_names (OstreeRepo *self, return FALSE; if (sub_dent == NULL) break; - if (dent->d_type != DT_DIR) + if (sub_dent->d_type != DT_DIR) continue; const char *name1 = dent->d_name; @@ -188,27 +210,126 @@ _ostree_repo_static_delta_part_have_all_objects (OstreeRepo *repo, return TRUE; } +static gboolean +_ostree_repo_static_delta_is_signed (OstreeRepo *self, + int fd, + GPtrArray **out_value, + GError **error) +{ + g_autoptr(GVariant) delta = NULL; + g_autoptr(GVariant) delta_sign_magic = NULL; + g_autoptr(GVariant) delta_sign = NULL; + GVariantIter iter; + GVariant *item; + g_autoptr(GPtrArray) signatures = NULL; + gboolean ret = FALSE; + + if (out_value) + *out_value = NULL; + + if (!ot_variant_read_fd (fd, 0, (GVariantType*)OSTREE_STATIC_DELTA_SIGNED_FORMAT, TRUE, &delta, error)) + return FALSE; + + delta_sign_magic = g_variant_get_child_value (delta, 0); + if (delta_sign_magic == NULL) + return glnx_throw (error, "no signatures in static-delta"); + + if (GUINT64_FROM_BE (g_variant_get_uint64 (delta_sign_magic)) != OSTREE_STATIC_DELTA_SIGNED_MAGIC) + return glnx_throw (error, "no signatures in static-delta"); + + delta_sign = g_variant_get_child_value (delta, 2); + if (delta_sign == NULL) + return glnx_throw (error, "no signatures in static-delta"); + + if (out_value) + signatures = g_ptr_array_new_with_free_func (g_free); + + /* Check if there are signatures in the superblock */ + g_variant_iter_init (&iter, delta_sign); + while ((item = g_variant_iter_next_value (&iter))) + { + g_autoptr(GVariant) key_v = g_variant_get_child_value (item, 0); + const char *str = g_variant_get_string (key_v, NULL); + if (g_str_has_prefix (str, "ostree.sign.")) + { + ret = TRUE; + if (signatures) + g_ptr_array_add (signatures, g_strdup (str + strlen ("ostree.sign."))); + } + g_variant_unref (item); + } + + if (out_value && ret) + ot_transfer_out_value (out_value, &signatures); + + return ret; +} + +static gboolean +_ostree_repo_static_delta_verify_signature (OstreeRepo *self, + int fd, + OstreeSign *sign, + char **out_success_message, + GError **error) +{ + g_autoptr(GVariant) delta = NULL; + + if (!ot_variant_read_fd (fd, 0, + (GVariantType*)OSTREE_STATIC_DELTA_SIGNED_FORMAT, + TRUE, &delta, error)) + return FALSE; + + /* Check if there are signatures for signature engine */ + const gchar *signature_key = ostree_sign_metadata_key(sign); + GVariantType *signature_format = (GVariantType *) ostree_sign_metadata_format(sign); + g_autoptr(GVariant) delta_meta = g_variant_get_child_value (delta, 2); + if (delta_meta == NULL) + return glnx_throw (error, "no metadata in static-delta superblock"); + g_autoptr(GVariant) signatures = g_variant_lookup_value (delta_meta, + signature_key, + signature_format); + if (!signatures) + return glnx_throw (error, "no signature for '%s' in static-delta superblock", signature_key); + + /* Get static delta superblock */ + g_autoptr(GVariant) child = g_variant_get_child_value (delta, 1); + if (child == NULL) + return glnx_throw (error, "no metadata in static-delta superblock"); + g_autoptr(GBytes) signed_data = g_variant_get_data_as_bytes(child); + + return ostree_sign_data_verify (sign, signed_data, signatures, out_success_message, error); +} + /** - * ostree_repo_static_delta_execute_offline: + * ostree_repo_static_delta_execute_offline_with_signature: * @self: Repo * @dir_or_file: Path to a directory containing static delta data, or directly to the superblock + * @sign: Signature engine used to check superblock * @skip_validation: If %TRUE, assume data integrity * @cancellable: Cancellable * @error: Error * * Given a directory representing an already-downloaded static delta - * on disk, apply it, generating a new commit. The directory must be - * named with the form "FROM-TO", where both are checksums, and it - * must contain a file named "superblock", along with at least one part. + * on disk, apply it, generating a new commit. + * If sign is passed, the static delta signature is verified. + * If sign-verify-deltas configuration option is set and static delta is signed, + * signature verification will be mandatory before apply the static delta. + * The directory must be named with the form "FROM-TO", where both are + * checksums, and it must contain a file named "superblock", along with at least + * one part. + * + * Since: 2020.7 */ gboolean -ostree_repo_static_delta_execute_offline (OstreeRepo *self, - GFile *dir_or_file, - gboolean skip_validation, - GCancellable *cancellable, - GError **error) +ostree_repo_static_delta_execute_offline_with_signature (OstreeRepo *self, + GFile *dir_or_file, + OstreeSign *sign, + gboolean skip_validation, + GCancellable *cancellable, + GError **error) { g_autofree char *basename = NULL; + g_autoptr(GVariant) meta = NULL; const char *dir_or_file_path = gs_file_get_path_cached (dir_or_file); @@ -234,10 +355,44 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self, if (meta_fd < 0) return glnx_throw_errno_prefix (error, "openat(%s)", basename); - g_autoptr(GVariant) meta = NULL; - if (!ot_variant_read_fd (meta_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), - FALSE, &meta, error)) - return FALSE; + gboolean is_signed = _ostree_repo_static_delta_is_signed (self, meta_fd, NULL, NULL); + if (is_signed) + { + gboolean verify_deltas; + gboolean verified; + + if (!ot_keyfile_get_boolean_with_default (self->config, "core", "sign-verify-deltas", + FALSE, &verify_deltas, error)) + return FALSE; + + if (verify_deltas && !sign) + return glnx_throw (error, "Key is mandatory to check delta signature"); + + if (sign) + { + verified = _ostree_repo_static_delta_verify_signature (self, meta_fd, sign, NULL, error); + if (*error) + return FALSE; + if (!verified) + return glnx_throw (error, "Delta signature verification failed"); + } + + g_autoptr(GVariant) delta = NULL; + if (!ot_variant_read_fd (meta_fd, 0, (GVariantType*)OSTREE_STATIC_DELTA_SIGNED_FORMAT, + TRUE, &delta, error)) + return FALSE; + + g_autoptr(GVariant) child = g_variant_get_child_value (delta, 1); + g_autoptr(GBytes) bytes = g_variant_get_data_as_bytes (child); + meta = g_variant_new_from_bytes ((GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT, + bytes, FALSE); + } + else + { + if (!ot_variant_read_fd (meta_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), + FALSE, &meta, error)) + return FALSE; + } /* Parsing OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT */ @@ -280,9 +435,8 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self, if (!have_to_commit) { g_autofree char *detached_path = _ostree_get_relative_static_delta_path (from_checksum, to_checksum, "commitmeta"); - g_autoptr(GVariant) detached_data = NULL; - - detached_data = g_variant_lookup_value (metadata, detached_path, G_VARIANT_TYPE("a{sv}")); + g_autoptr(GVariant) detached_data = + g_variant_lookup_value (metadata, detached_path, G_VARIANT_TYPE("a{sv}")); if (detached_data && !ostree_repo_write_commit_detached_metadata (self, to_checksum, detached_data, @@ -386,6 +540,32 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self, return TRUE; } +/** + * ostree_repo_static_delta_execute_offline: + * @self: Repo + * @dir_or_file: Path to a directory containing static delta data, or directly to the superblock + * @skip_validation: If %TRUE, assume data integrity + * @cancellable: Cancellable + * @error: Error + * + * Given a directory representing an already-downloaded static delta + * on disk, apply it, generating a new commit. The directory must be + * named with the form "FROM-TO", where both are checksums, and it + * must contain a file named "superblock", along with at least one part. + */ +gboolean +ostree_repo_static_delta_execute_offline (OstreeRepo *self, + GFile *dir_or_file, + gboolean skip_validation, + GCancellable *cancellable, + GError **error) +{ + return ostree_repo_static_delta_execute_offline_with_signature(self, dir_or_file, NULL, + skip_validation, + cancellable, + error); +} + gboolean _ostree_static_delta_part_open (GInputStream *part_in, GBytes *inline_part_bytes, @@ -726,6 +906,8 @@ _ostree_repo_static_delta_dump (OstreeRepo *self, GError **error) { glnx_autofd int superblock_fd = -1; + g_autoptr(GVariant) delta = NULL; + g_autoptr(GVariant) delta_superblock = NULL; if (strchr (delta_id, '/')) { @@ -744,13 +926,28 @@ _ostree_repo_static_delta_dump (OstreeRepo *self, return FALSE; } - g_autoptr(GVariant) delta_superblock = NULL; - if (!ot_variant_read_fd (superblock_fd, 0, - (GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT, - TRUE, &delta_superblock, error)) - return FALSE; + gboolean is_signed = _ostree_repo_static_delta_is_signed(self, superblock_fd, NULL, NULL); + if (is_signed) + { + if (!ot_variant_read_fd (superblock_fd, 0, (GVariantType*)OSTREE_STATIC_DELTA_SIGNED_FORMAT, + TRUE, &delta, error)) + return FALSE; + + g_autoptr(GVariant) child = g_variant_get_child_value (delta, 1); + g_autoptr(GBytes) bytes = g_variant_get_data_as_bytes(child); + delta_superblock = g_variant_new_from_bytes ((GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT, + bytes, FALSE); + } + else + { + if (!ot_variant_read_fd (superblock_fd, 0, + (GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT, + TRUE, &delta_superblock, error)) + return FALSE; + } g_print ("Delta: %s\n", delta_id); + g_print ("Signed: %s\n", is_signed ? "yes" : "no"); g_autoptr(GVariant) from_commit_v = NULL; g_variant_get_child (delta_superblock, 2, "@ay", &from_commit_v); g_autofree char *from_commit = NULL; @@ -873,3 +1070,51 @@ _ostree_repo_static_delta_dump (OstreeRepo *self, return TRUE; } + +/** + * ostree_repo_static_delta_verify_signature: + * @self: Repo + * @delta_id: delta path + * @sign: Signature engine used to check superblock + * @out_success_message: success message + * @error: Error + * + * Verify static delta file signature. + * + * Returns: TRUE if the signature of static delta file is valid using the + * signature engine provided, FALSE otherwise. + * + * Since: 2020.7 + */ +gboolean +ostree_repo_static_delta_verify_signature (OstreeRepo *self, + const char *delta_id, + OstreeSign *sign, + char **out_success_message, + GError **error) +{ + g_autoptr(GVariant) delta_meta = NULL; + glnx_autofd int delta_fd = -1; + + if (strchr (delta_id, '/')) + { + if (!glnx_openat_rdonly (AT_FDCWD, delta_id, TRUE, &delta_fd, error)) + return FALSE; + } + else + { + g_autofree char *from = NULL; + g_autofree char *to = NULL; + if (!_ostree_parse_delta_name (delta_id, &from, &to, error)) + return FALSE; + + g_autofree char *delta_path = _ostree_get_relative_static_delta_superblock_path (from, to); + if (!glnx_openat_rdonly (self->repo_dir_fd, delta_path, TRUE, &delta_fd, error)) + return FALSE; + } + + if (!_ostree_repo_static_delta_is_signed (self, delta_fd, NULL, error)) + return FALSE; + + return _ostree_repo_static_delta_verify_signature (self, delta_fd, sign, out_success_message, error); +} diff --git a/src/libostree/ostree-repo-static-delta-private.h b/src/libostree/ostree-repo-static-delta-private.h index 155acd5..5a2e687 100644 --- a/src/libostree/ostree-repo-static-delta-private.h +++ b/src/libostree/ostree-repo-static-delta-private.h @@ -104,6 +104,25 @@ G_BEGIN_DECLS */ #define OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT "(a{sv}tayay" OSTREE_COMMIT_GVARIANT_STRING "aya" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT ")" +/** + * OSTREE_STATIC_DELTA_SIGNED_FORMAT + * + * magic: t magic number, 8 bytes for alignment + * superblock: ay delta supeblock variant + * signatures: a{sv} + * + * The signed static delta starts with the 'OSTSGNDT' magic number followed by + * the array of bytes containing the superblock used for the signature. + * + * Then, the signatures array contains the signatures of the superblock. A + * signature has the following form: + * type: signature key + * signature: variant depending on type used + */ +#define OSTREE_STATIC_DELTA_SIGNED_FORMAT "(taya{sv})" + +#define OSTREE_STATIC_DELTA_SIGNED_MAGIC 0x4F535453474E4454 /* OSTSGNDT */ + typedef enum { OSTREE_STATIC_DELTA_OPEN_FLAGS_NONE = 0, OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM = (1 << 0), @@ -190,6 +209,12 @@ _ostree_repo_static_delta_query_exists (OstreeRepo *repo, gboolean *out_exists, GCancellable *cancellable, GError **error); +GVariant * +_ostree_repo_static_delta_superblock_digest (OstreeRepo *repo, + const char *from, + const char *to, + GCancellable *cancellable, + GError **error); gboolean _ostree_repo_static_delta_dump (OstreeRepo *repo, diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 95eb0ef..ba3e877 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -3782,14 +3782,14 @@ load_metadata_internal (OstreeRepo *self, g_autofree char *commitpartial_path = _ostree_get_commitpartial_path (sha256); *out_state = 0; - glnx_autofd int fd = -1; - if (!ot_openat_ignore_enoent (self->repo_dir_fd, commitpartial_path, &fd, error)) + glnx_autofd int commitpartial_fd = -1; + if (!ot_openat_ignore_enoent (self->repo_dir_fd, commitpartial_path, &commitpartial_fd, error)) return FALSE; - if (fd != -1) + if (commitpartial_fd != -1) { *out_state |= OSTREE_REPO_COMMIT_STATE_PARTIAL; char reason; - if (read (fd, &reason, 1) == 1) + if (read (commitpartial_fd, &reason, 1) == 1) { if (reason == 'f') *out_state |= OSTREE_REPO_COMMIT_STATE_FSCK_PARTIAL; @@ -5793,25 +5793,18 @@ ostree_repo_regenerate_summary (OstreeRepo *self, { g_autofree char *from = NULL; g_autofree char *to = NULL; - if (!_ostree_parse_delta_name (delta_names->pdata[i], &from, &to, error)) - return FALSE; - - g_autofree char *superblock = _ostree_get_relative_static_delta_superblock_path ((from && from[0]) ? from : NULL, to); - glnx_autofd int superblock_file_fd = -1; + GVariant *digest; - if (!glnx_openat_rdonly (self->repo_dir_fd, superblock, TRUE, &superblock_file_fd, error)) + if (!_ostree_parse_delta_name (delta_names->pdata[i], &from, &to, error)) return FALSE; - g_autoptr(GBytes) superblock_content = ot_fd_readall_or_mmap (superblock_file_fd, 0, error); - if (!superblock_content) + digest = _ostree_repo_static_delta_superblock_digest (self, + (from && from[0]) ? from : NULL, + to, cancellable, error); + if (digest == NULL) return FALSE; - g_auto(OtChecksum) hasher = { 0, }; - ot_checksum_init (&hasher); - ot_checksum_update_bytes (&hasher, superblock_content); - guint8 digest[OSTREE_SHA256_DIGEST_LEN]; - ot_checksum_get_digest (&hasher, digest, sizeof (digest)); - g_variant_dict_insert_value (&deltas_builder, delta_names->pdata[i], ot_gvariant_new_bytearray (digest, sizeof (digest))); + g_variant_dict_insert_value (&deltas_builder, delta_names->pdata[i], digest); } if (delta_names->len > 0) @@ -5823,6 +5816,24 @@ ostree_repo_regenerate_summary (OstreeRepo *self, g_variant_new_uint64 (GUINT64_TO_BE (g_get_real_time () / G_USEC_PER_SEC))); } + { + g_autofree char *remote_mode_str = NULL; + if (!ot_keyfile_get_value_with_default (self->config, "core", "mode", "bare", + &remote_mode_str, error)) + return FALSE; + g_variant_dict_insert_value (&additional_metadata_builder, OSTREE_SUMMARY_MODE, + g_variant_new_string (remote_mode_str)); + } + + { + gboolean tombstone_commits = FALSE; + if (!ot_keyfile_get_boolean_with_default (self->config, "core", "tombstone-commits", FALSE, + &tombstone_commits, error)) + return FALSE; + g_variant_dict_insert_value (&additional_metadata_builder, OSTREE_SUMMARY_TOMBSTONE_COMMITS, + g_variant_new_boolean (tombstone_commits)); + } + /* Add refs which have a collection specified, which could be in refs/mirrors, * refs/heads, and/or refs/remotes. */ { @@ -5838,19 +5849,19 @@ ostree_repo_regenerate_summary (OstreeRepo *self, collection_map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) g_hash_table_unref); - const OstreeCollectionRef *ref; + const OstreeCollectionRef *c_ref; const char *checksum; - while (g_hash_table_iter_next (&iter, (gpointer *) &ref, (gpointer *) &checksum)) + while (g_hash_table_iter_next (&iter, (gpointer *) &c_ref, (gpointer *) &checksum)) { - GHashTable *ref_map = g_hash_table_lookup (collection_map, ref->collection_id); + GHashTable *ref_map = g_hash_table_lookup (collection_map, c_ref->collection_id); if (ref_map == NULL) { ref_map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); - g_hash_table_insert (collection_map, ref->collection_id, ref_map); + g_hash_table_insert (collection_map, c_ref->collection_id, ref_map); } - g_hash_table_insert (ref_map, ref->ref_name, (gpointer) checksum); + g_hash_table_insert (ref_map, c_ref->ref_name, (gpointer) checksum); } g_autoptr(GVariantBuilder) collection_refs_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sa(s(taya{sv}))}")); diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index e28af29..d52fc9c 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -32,6 +32,7 @@ #include "ostree-repo-finder.h" #include "ostree-sepolicy.h" #include "ostree-gpg-verify-result.h" +#include "ostree-sign.h" G_BEGIN_DECLS @@ -1068,6 +1069,14 @@ gboolean ostree_repo_static_delta_generate (OstreeRepo *self, GError **error); _OSTREE_PUBLIC +gboolean ostree_repo_static_delta_execute_offline_with_signature (OstreeRepo *self, + GFile *dir_or_file, + OstreeSign *sign, + gboolean skip_validation, + GCancellable *cancellable, + GError **error); + +_OSTREE_PUBLIC gboolean ostree_repo_static_delta_execute_offline (OstreeRepo *self, GFile *dir_or_file, gboolean skip_validation, @@ -1075,6 +1084,13 @@ gboolean ostree_repo_static_delta_execute_offline (OstreeRepo GError **error); _OSTREE_PUBLIC +gboolean ostree_repo_static_delta_verify_signature (OstreeRepo *self, + const char *delta_id, + OstreeSign *sign, + char **out_success_message, + GError **error); + +_OSTREE_PUBLIC GHashTable *ostree_repo_traverse_new_reachable (void); _OSTREE_PUBLIC diff --git a/src/libostree/ostree-sign.c b/src/libostree/ostree-sign.c index bcb5d0a..eeef96d 100644 --- a/src/libostree/ostree-sign.c +++ b/src/libostree/ostree-sign.c @@ -271,7 +271,7 @@ ostree_sign_load_pk (OstreeSign *self, * ostree_sign_data: * @self: an #OstreeSign object * @data: the raw data to be signed with pre-loaded secret key - * @signature: in case of success will contain signature + * @signature: (out): in case of success will contain signature * @cancellable: A #GCancellable * @error: a #GError * @@ -305,6 +305,7 @@ ostree_sign_data (OstreeSign *self, * @self: an #OstreeSign object * @data: the raw data to check * @signatures: the signatures to be checked + * @out_success_message: (out) (nullable) (optional): success message returned by the signing engine * @error: a #GError * * Verify given data against signatures with pre-loaded public keys. @@ -364,7 +365,7 @@ _sign_detached_metadata_append (OstreeSign *self, signature_key, g_variant_builder_end (signature_builder)); - return g_variant_dict_end (&metadata_dict); + return g_variant_ref_sink (g_variant_dict_end (&metadata_dict)); } /** @@ -372,6 +373,7 @@ _sign_detached_metadata_append (OstreeSign *self, * @self: an #OstreeSign object * @repo: an #OsreeRepo object * @commit_checksum: SHA256 of given commit to verify + * @out_success_message: (out) (nullable) (optional): success message returned by the signing engine * @cancellable: A #GCancellable * @error: a #GError * @@ -593,6 +595,8 @@ ostree_sign_get_by_name (const gchar *name, GError **error) * Based on ostree_repo_add_gpg_signature_summary implementation. * * Returns: @TRUE if summary file has been signed with all provided keys + * + * Since: 2020.2 */ gboolean ostree_sign_summary (OstreeSign *self, diff --git a/src/libostree/ostree-sign.h b/src/libostree/ostree-sign.h index 0d06905..75dd483 100644 --- a/src/libostree/ostree-sign.h +++ b/src/libostree/ostree-sign.h @@ -52,6 +52,8 @@ G_GNUC_END_IGNORE_DEPRECATIONS /** * OSTREE_SIGN_NAME_ED25519: * The name of the default ed25519 signing type. + * + * Since: 2020.4 */ #define OSTREE_SIGN_NAME_ED25519 "ed25519" diff --git a/src/libostree/ostree-soup-uri.c b/src/libostree/ostree-soup-uri.c index a3fa2ac..ba14e5c 100644 --- a/src/libostree/ostree-soup-uri.c +++ b/src/libostree/ostree-soup-uri.c @@ -348,7 +348,7 @@ soup_uri_new_with_base (SoupURI *base, const char *uri_string) { SoupURI *uri, fixed_base; const char *end, *hash, *colon, *at, *path, *question; - const char *p, *hostend; + const char *c, *hostend; gboolean remove_dot_segments = TRUE; int len; @@ -402,17 +402,17 @@ soup_uri_new_with_base (SoupURI *base, const char *uri_string) } /* Find scheme */ - p = uri_string; - while (p < end && (g_ascii_isalpha (*p) || - (p > uri_string && (g_ascii_isdigit (*p) || - *p == '.' || - *p == '+' || - *p == '-')))) - p++; - - if (p > uri_string && *p == ':') { - uri->scheme = soup_uri_parse_scheme (uri_string, p - uri_string); - uri_string = p + 1; + c = uri_string; + while (c < end && (g_ascii_isalpha (*c) || + (c > uri_string && (g_ascii_isdigit (*c) || + *c == '.' || + *c == '+' || + *c == '-')))) + c++; + + if (c > uri_string && *c == ':') { + uri->scheme = soup_uri_parse_scheme (uri_string, c - uri_string); + uri_string = c + 1; } if (uri_string == end && !base && !uri->fragment) { diff --git a/src/libostree/ostree-sysroot-cleanup.c b/src/libostree/ostree-sysroot-cleanup.c index 71d978e..2712283 100644 --- a/src/libostree/ostree-sysroot-cleanup.c +++ b/src/libostree/ostree-sysroot-cleanup.c @@ -295,9 +295,11 @@ cleanup_old_deployments (OstreeSysroot *self, /* Load all active deployments referenced by bootloader configuration. */ g_autoptr(GHashTable) active_deployment_dirs = - g_hash_table_new_full (g_str_hash, (GEqualFunc)g_str_equal, g_free, NULL); + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); g_autoptr(GHashTable) active_boot_checksums = - g_hash_table_new_full (g_str_hash, (GEqualFunc)g_str_equal, g_free, NULL); + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + g_autoptr(GHashTable) active_overlay_initrds = + g_hash_table_new (g_str_hash, g_str_equal); /* borrows from deployment's bootconfig */ for (guint i = 0; i < self->deployments->len; i++) { OstreeDeployment *deployment = self->deployments->pdata[i]; @@ -306,6 +308,11 @@ cleanup_old_deployments (OstreeSysroot *self, /* Transfer ownership */ g_hash_table_replace (active_deployment_dirs, deployment_path, deployment_path); g_hash_table_replace (active_boot_checksums, bootcsum, bootcsum); + + OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment); + char **initrds = ostree_bootconfig_parser_get_overlay_initrds (bootconfig); + for (char **it = initrds; it && *it; it++) + g_hash_table_add (active_overlay_initrds, (char*)glnx_basename (*it)); } /* Find all deployment directories, both active and inactive */ @@ -349,6 +356,42 @@ cleanup_old_deployments (OstreeSysroot *self, return FALSE; } + /* Clean up overlay initrds */ + glnx_autofd int overlays_dfd = + glnx_opendirat_with_errno (self->sysroot_fd, _OSTREE_SYSROOT_INITRAMFS_OVERLAYS, FALSE); + if (overlays_dfd < 0) + { + if (errno != ENOENT) + return glnx_throw_errno_prefix (error, "open(initrd_overlays)"); + } + else + { + g_autoptr(GPtrArray) initrds_to_delete = g_ptr_array_new_with_free_func (g_free); + g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; + if (!glnx_dirfd_iterator_init_at (overlays_dfd, ".", TRUE, &dfd_iter, error)) + return FALSE; + while (TRUE) + { + struct dirent *dent; + if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error)) + return FALSE; + if (dent == NULL) + break; + + /* there shouldn't be other file types there, but let's be conservative */ + if (dent->d_type != DT_REG) + continue; + + if (!g_hash_table_lookup (active_overlay_initrds, dent->d_name)) + g_ptr_array_add (initrds_to_delete, g_strdup (dent->d_name)); + } + for (guint i = 0; i < initrds_to_delete->len; i++) + { + if (!ot_ensure_unlinked_at (overlays_dfd, initrds_to_delete->pdata[i], error)) + return FALSE; + } + } + return TRUE; } diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index cb59302..7b7ba5e 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -111,7 +111,6 @@ install_into_boot (OstreeRepo *repo, const char *src_subpath, int dest_dfd, const char *dest_subpath, - OstreeSysrootDebugFlags flags, GCancellable *cancellable, GError **error) { @@ -273,13 +272,13 @@ checksum_dir_recurse (int dfd, } else { - int fd; + glnx_autofd int fd = -1; if (!ot_openat_ignore_enoent (dfditer.fd, d_name, &fd, error)) return FALSE; if (fd != -1) { - g_autoptr(GInputStream) in = g_unix_input_stream_new (fd, FALSE); + g_autoptr(GInputStream) in = g_unix_input_stream_new (glnx_steal_fd (&fd), TRUE); if (!ot_gio_splice_update_checksum (NULL, in, checksum, cancellable, error)) return FALSE; } @@ -314,7 +313,7 @@ copy_dir_recurse (int src_parent_dfd, if (!dirfd_copy_attributes_and_xattrs (src_parent_dfd, name, src_dfd_iter.fd, dest_dfd, flags, cancellable, error)) - return FALSE; + return glnx_prefix_error (error, "Copying attributes of %s", name); while (TRUE) { @@ -341,7 +340,7 @@ copy_dir_recurse (int src_parent_dfd, dest_dfd, dent->d_name, sysroot_flags_to_copy_flags (GLNX_FILE_COPY_OVERWRITE, flags), cancellable, error)) - return FALSE; + return glnx_prefix_error (error, "Copying %s", dent->d_name); } } @@ -489,7 +488,7 @@ copy_modified_config_file (int orig_etc_fd, new_etc_fd, path, sysroot_flags_to_copy_flags (GLNX_FILE_COPY_OVERWRITE, flags), cancellable, error)) - return FALSE; + return glnx_prefix_error (error, "Copying %s", path); } else { @@ -1029,7 +1028,8 @@ _ostree_kernel_layout_new (void) /* See get_kernel_from_tree() below */ static gboolean -get_kernel_from_tree_usrlib_modules (int deployment_dfd, +get_kernel_from_tree_usrlib_modules (OstreeSysroot *sysroot, + int deployment_dfd, OstreeKernelLayout **out_layout, GCancellable *cancellable, GError **error) @@ -1138,37 +1138,41 @@ get_kernel_from_tree_usrlib_modules (int deployment_dfd, g_clear_object (&in); glnx_close_fd (&fd); - /* Check for /usr/lib/modules/$kver/devicetree first, if it does not - * exist check for /usr/lib/modules/$kver/dtb/ directory. - */ - if (!ot_openat_ignore_enoent (ret_layout->boot_dfd, "devicetree", &fd, error)) - return FALSE; - if (fd != -1) - { - ret_layout->devicetree_srcpath = g_strdup ("devicetree"); - ret_layout->devicetree_namever = g_strdup_printf ("devicetree-%s", kver); - in = g_unix_input_stream_new (fd, FALSE); - if (!ot_gio_splice_update_checksum (NULL, in, &checksum, cancellable, error)) - return FALSE; - } - else + /* Testing aid for https://github.com/ostreedev/ostree/issues/2154 */ + const gboolean no_dtb = (sysroot->debug_flags & OSTREE_SYSROOT_DEBUG_TEST_NO_DTB) > 0; + if (!no_dtb) { - struct stat stbuf; - /* Check for dtb directory */ - if (!glnx_fstatat_allow_noent (ret_layout->boot_dfd, "dtb", &stbuf, 0, error)) + /* Check for /usr/lib/modules/$kver/devicetree first, if it does not + * exist check for /usr/lib/modules/$kver/dtb/ directory. + */ + if (!ot_openat_ignore_enoent (ret_layout->boot_dfd, "devicetree", &fd, error)) return FALSE; - - if (errno == 0 && S_ISDIR (stbuf.st_mode)) + if (fd != -1) { - /* devicetree_namever set to NULL indicates a complete directory */ - ret_layout->devicetree_srcpath = g_strdup ("dtb"); - ret_layout->devicetree_namever = NULL; - - if (!checksum_dir_recurse(ret_layout->boot_dfd, "dtb", &checksum, cancellable, error)) + ret_layout->devicetree_srcpath = g_strdup ("devicetree"); + ret_layout->devicetree_namever = g_strdup_printf ("devicetree-%s", kver); + in = g_unix_input_stream_new (fd, FALSE); + if (!ot_gio_splice_update_checksum (NULL, in, &checksum, cancellable, error)) return FALSE; } - } + else + { + struct stat stbuf; + /* Check for dtb directory */ + if (!glnx_fstatat_allow_noent (ret_layout->boot_dfd, "dtb", &stbuf, 0, error)) + return FALSE; + if (errno == 0 && S_ISDIR (stbuf.st_mode)) + { + /* devicetree_namever set to NULL indicates a complete directory */ + ret_layout->devicetree_srcpath = g_strdup ("dtb"); + ret_layout->devicetree_namever = NULL; + + if (!checksum_dir_recurse(ret_layout->boot_dfd, "dtb", &checksum, cancellable, error)) + return FALSE; + } + } + } g_clear_object (&in); glnx_close_fd (&fd); @@ -1337,7 +1341,8 @@ get_kernel_from_tree_legacy_layouts (int deployment_dfd, * initramfs there, so we need to look in /usr/lib/ostree-boot first. */ static gboolean -get_kernel_from_tree (int deployment_dfd, +get_kernel_from_tree (OstreeSysroot *sysroot, + int deployment_dfd, OstreeKernelLayout **out_layout, GCancellable *cancellable, GError **error) @@ -1346,7 +1351,7 @@ get_kernel_from_tree (int deployment_dfd, g_autoptr(OstreeKernelLayout) legacy_layout = NULL; /* First, gather from usr/lib/modules/$kver if it exists */ - if (!get_kernel_from_tree_usrlib_modules (deployment_dfd, &usrlib_modules_layout, cancellable, error)) + if (!get_kernel_from_tree_usrlib_modules (sysroot, deployment_dfd, &usrlib_modules_layout, cancellable, error)) return FALSE; /* Gather the legacy layout */ @@ -1762,7 +1767,7 @@ install_deployment_kernel (OstreeSysroot *sysroot, /* Find the kernel/initramfs/devicetree in the tree */ g_autoptr(OstreeKernelLayout) kernel_layout = NULL; - if (!get_kernel_from_tree (deployment_dfd, &kernel_layout, + if (!get_kernel_from_tree (sysroot, deployment_dfd, &kernel_layout, cancellable, error)) return FALSE; @@ -1772,7 +1777,6 @@ install_deployment_kernel (OstreeSysroot *sysroot, const char *osname = ostree_deployment_get_osname (deployment); const char *bootcsum = ostree_deployment_get_bootcsum (deployment); - g_assert_cmpstr (kernel_layout->bootcsum, ==, bootcsum); g_autofree char *bootcsumdir = g_strdup_printf ("ostree/%s-%s", osname, bootcsum); g_autofree char *bootconfdir = g_strdup_printf ("loader.%d/entries", new_bootversion); g_autofree char *bootconf_name = g_strdup_printf ("ostree-%d-%s.conf", @@ -1798,7 +1802,6 @@ install_deployment_kernel (OstreeSysroot *sysroot, { 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)) return FALSE; } @@ -1815,7 +1818,6 @@ install_deployment_kernel (OstreeSysroot *sysroot, { 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)) return FALSE; } @@ -1832,7 +1834,6 @@ install_deployment_kernel (OstreeSysroot *sysroot, { 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)) return FALSE; } @@ -1853,12 +1854,52 @@ install_deployment_kernel (OstreeSysroot *sysroot, { 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)) return FALSE; } } + g_autoptr(GPtrArray) overlay_initrds = NULL; + for (char **it = _ostree_deployment_get_overlay_initrds (deployment); it && *it; it++) + { + char *checksum = *it; + + /* Overlay initrds are not part of the bootcsum dir; they're not part of the tree + * proper. Instead they're in /boot/ostree/initramfs-overlays/ named by their csum. + * Doing it this way allows sharing the same bootcsum dir for multiple deployments + * with the only change being in overlay initrds (or conversely, the same overlay + * across different boocsums). Eventually, it'd be nice to have an OSTree repo in + * /boot itself and drop the boocsum dir concept entirely. */ + + g_autofree char *destpath = + g_strdup_printf ("/" _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS "/%s.img", checksum); + const char *rel_destpath = destpath + 1; + + /* lazily allocate array and create dir so we don't pollute /boot if not needed */ + if (overlay_initrds == NULL) + { + overlay_initrds = g_ptr_array_new_with_free_func (g_free); + + if (!glnx_shutil_mkdir_p_at (boot_dfd, _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS, + 0755, cancellable, error)) + return FALSE; + } + + if (!glnx_fstatat_allow_noent (boot_dfd, rel_destpath, NULL, 0, error)) + return FALSE; + if (errno == ENOENT) + { + g_autofree char *srcpath = + g_strdup_printf (_OSTREE_SYSROOT_RUNSTATE_STAGED_INITRDS_DIR "/%s", checksum); + if (!install_into_boot (repo, sepolicy, AT_FDCWD, srcpath, boot_dfd, rel_destpath, + cancellable, error)) + return FALSE; + } + + /* these are used lower down to populate the bootconfig */ + g_ptr_array_add (overlay_initrds, g_steal_pointer (&destpath)); + } + g_autofree char *contents = NULL; if (!glnx_fstatat_allow_noent (deployment_dfd, "usr/lib/os-release", &stbuf, 0, error)) return FALSE; @@ -1935,8 +1976,15 @@ install_deployment_kernel (OstreeSysroot *sysroot, if (kernel_layout->initramfs_namever) { - g_autofree char * boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->initramfs_namever, NULL); - ostree_bootconfig_parser_set (bootconfig, "initrd", boot_relpath); + g_autofree char * initrd_boot_relpath = + g_strconcat ("/", bootcsumdir, "/", kernel_layout->initramfs_namever, NULL); + ostree_bootconfig_parser_set (bootconfig, "initrd", initrd_boot_relpath); + + if (overlay_initrds) + { + g_ptr_array_add (overlay_initrds, NULL); + ostree_bootconfig_parser_set_overlay_initrds (bootconfig, (char**)overlay_initrds->pdata); + } } else { @@ -1949,8 +1997,8 @@ install_deployment_kernel (OstreeSysroot *sysroot, if (kernel_layout->devicetree_namever) { - g_autofree char * boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->devicetree_namever, NULL); - ostree_bootconfig_parser_set (bootconfig, "devicetree", boot_relpath); + g_autofree char * dt_boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->devicetree_namever, NULL); + ostree_bootconfig_parser_set (bootconfig, "devicetree", dt_boot_relpath); } else if (kernel_layout->devicetree_srcpath) { @@ -1958,8 +2006,8 @@ install_deployment_kernel (OstreeSysroot *sysroot, * want to point to a whole directory of device trees. * See: https://github.com/ostreedev/ostree/issues/1900 */ - g_autofree char * boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->devicetree_srcpath, NULL); - ostree_bootconfig_parser_set (bootconfig, "fdtdir", boot_relpath); + g_autofree char * dt_boot_relpath = g_strconcat ("/", bootcsumdir, "/", kernel_layout->devicetree_srcpath, NULL); + ostree_bootconfig_parser_set (bootconfig, "fdtdir", dt_boot_relpath); } /* Note this is parsed in ostree-impl-system-generator.c */ @@ -1998,6 +2046,12 @@ prepare_new_bootloader_link (OstreeSysroot *sysroot, g_assert ((current_bootversion == 0 && new_bootversion == 1) || (current_bootversion == 1 && new_bootversion == 0)); + /* This allows us to support both /boot on a seperate filesystem to / as well + * as on the same filesystem. */ + if (TEMP_FAILURE_RETRY (symlinkat (".", sysroot->sysroot_fd, "boot/boot")) < 0) + if (errno != EEXIST) + return glnx_throw_errno_prefix (error, "symlinkat"); + g_autofree char *new_target = g_strdup_printf ("loader.%d", new_bootversion); /* We shouldn't actually need to replace but it's easier to reuse @@ -2128,6 +2182,10 @@ deployment_bootconfigs_equal (OstreeRepo *repo, if (strcmp (a_bootcsum, b_bootcsum) != 0) return FALSE; + /* same initrd overlays? */ + if (g_strcmp0 (a->overlay_initrds_id, b->overlay_initrds_id) != 0) + return FALSE; + /* same kargs? */ g_autofree char *a_boot_options_without_ostree = get_deployment_nonostree_kargs (a); g_autofree char *b_boot_options_without_ostree = get_deployment_nonostree_kargs (b); @@ -2681,7 +2739,7 @@ sysroot_initialize_deployment (OstreeSysroot *self, const char *osname, const char *revision, GKeyFile *origin, - char **override_kernel_argv, + OstreeSysrootDeployTreeOpts *opts, OstreeDeployment **out_new_deployment, GCancellable *cancellable, GError **error) @@ -2698,10 +2756,8 @@ sysroot_initialize_deployment (OstreeSysroot *self, cancellable, error)) return FALSE; - g_autofree char *new_bootcsum = NULL; g_autoptr(OstreeDeployment) new_deployment = - ostree_deployment_new (0, osname, revision, new_deployserial, - new_bootcsum, -1); + ostree_deployment_new (0, osname, revision, new_deployserial, NULL, -1); ostree_deployment_set_origin (new_deployment, origin); /* Check out the userspace tree onto the filesystem */ @@ -2711,12 +2767,13 @@ sysroot_initialize_deployment (OstreeSysroot *self, return FALSE; g_autoptr(OstreeKernelLayout) kernel_layout = NULL; - if (!get_kernel_from_tree (deployment_dfd, &kernel_layout, + if (!get_kernel_from_tree (self, deployment_dfd, &kernel_layout, cancellable, error)) return FALSE; _ostree_deployment_set_bootcsum (new_deployment, kernel_layout->bootcsum); - _ostree_deployment_set_bootconfig_from_kargs (new_deployment, override_kernel_argv); + _ostree_deployment_set_bootconfig_from_kargs (new_deployment, opts ? opts->override_kernel_argv : NULL); + _ostree_deployment_set_overlay_initrds (new_deployment, opts ? opts->overlay_initrds : NULL); if (!prepare_deployment_etc (self, repo, new_deployment, deployment_dfd, cancellable, error)) @@ -2770,7 +2827,6 @@ get_var_dfd (OstreeSysroot *self, static gboolean sysroot_finalize_deployment (OstreeSysroot *self, OstreeDeployment *deployment, - char **override_kernel_argv, OstreeDeployment *merge_deployment, GCancellable *cancellable, GError **error) @@ -2780,15 +2836,18 @@ sysroot_finalize_deployment (OstreeSysroot *self, if (!glnx_opendirat (self->sysroot_fd, deployment_path, TRUE, &deployment_dfd, error)) return FALSE; - /* Only use the merge if we didn't get an override */ - if (!override_kernel_argv && merge_deployment) + OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment); + + /* If the kargs weren't set yet, then just pick it up from the merge deployment. In the + * deploy path, overrides are set as part of sysroot_initialize_deployment(). In the + * finalize-staged path, they're set by OstreeSysroot when reading the staged GVariant. */ + if (merge_deployment && ostree_bootconfig_parser_get (bootconfig, "options") == NULL) { - /* Override the bootloader arguments */ OstreeBootconfigParser *merge_bootconfig = ostree_deployment_get_bootconfig (merge_deployment); if (merge_bootconfig) { - const char *opts = ostree_bootconfig_parser_get (merge_bootconfig, "options"); - ostree_bootconfig_parser_set (ostree_deployment_get_bootconfig (deployment), "options", opts); + const char *kargs = ostree_bootconfig_parser_get (merge_bootconfig, "options"); + ostree_bootconfig_parser_set (bootconfig, "options", kargs); } } @@ -2846,13 +2905,13 @@ sysroot_finalize_deployment (OstreeSysroot *self, } /** - * ostree_sysroot_deploy_tree: + * ostree_sysroot_deploy_tree_with_options: * @self: Sysroot * @osname: (allow-none): osname to use for merge deployment * @revision: Checksum to add * @origin: (allow-none): Origin to use for upgrades * @provided_merge_deployment: (allow-none): Use this deployment for merge path - * @override_kernel_argv: (allow-none) (array zero-terminated=1) (element-type utf8): Use these as kernel arguments; if %NULL, inherit options from provided_merge_deployment + * @opts: (allow-none): Options * @out_new_deployment: (out): The new deployment path * @cancellable: Cancellable * @error: Error @@ -2860,30 +2919,31 @@ sysroot_finalize_deployment (OstreeSysroot *self, * Check out deployment tree with revision @revision, performing a 3 * way merge with @provided_merge_deployment for configuration. * - * While this API is not deprecated, you most likely want to use the - * ostree_sysroot_stage_tree() API. + * When booted into the sysroot, you should use the + * ostree_sysroot_stage_tree() API instead. + * + * Since: 2020.7 */ gboolean -ostree_sysroot_deploy_tree (OstreeSysroot *self, - const char *osname, - const char *revision, - GKeyFile *origin, - OstreeDeployment *provided_merge_deployment, - char **override_kernel_argv, - OstreeDeployment **out_new_deployment, - GCancellable *cancellable, - GError **error) +ostree_sysroot_deploy_tree_with_options (OstreeSysroot *self, + const char *osname, + const char *revision, + GKeyFile *origin, + OstreeDeployment *provided_merge_deployment, + OstreeSysrootDeployTreeOpts *opts, + OstreeDeployment **out_new_deployment, + 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, + if (!sysroot_initialize_deployment (self, osname, revision, origin, opts, &deployment, cancellable, error)) return FALSE; - if (!sysroot_finalize_deployment (self, deployment, override_kernel_argv, - provided_merge_deployment, + if (!sysroot_finalize_deployment (self, deployment, provided_merge_deployment, cancellable, error)) return FALSE; @@ -2891,6 +2951,39 @@ ostree_sysroot_deploy_tree (OstreeSysroot *self, return TRUE; } +/** + * ostree_sysroot_deploy_tree: + * @self: Sysroot + * @osname: (allow-none): osname to use for merge deployment + * @revision: Checksum to add + * @origin: (allow-none): Origin to use for upgrades + * @provided_merge_deployment: (allow-none): Use this deployment for merge path + * @override_kernel_argv: (allow-none) (array zero-terminated=1) (element-type utf8): Use these as kernel arguments; if %NULL, inherit options from provided_merge_deployment + * @out_new_deployment: (out): The new deployment path + * @cancellable: Cancellable + * @error: Error + * + * Older version of ostree_sysroot_stage_tree_with_options(). + * + * Since: 2018.5 + */ +gboolean +ostree_sysroot_deploy_tree (OstreeSysroot *self, + const char *osname, + const char *revision, + GKeyFile *origin, + OstreeDeployment *provided_merge_deployment, + char **override_kernel_argv, + OstreeDeployment **out_new_deployment, + GCancellable *cancellable, + GError **error) +{ + OstreeSysrootDeployTreeOpts opts = { .override_kernel_argv = override_kernel_argv }; + return ostree_sysroot_deploy_tree_with_options (self, osname, revision, origin, + provided_merge_deployment, &opts, + out_new_deployment, cancellable, error); +} + /* Serialize information about a deployment to a variant, used by the staging * code. */ @@ -2951,6 +3044,63 @@ _ostree_sysroot_deserialize_deployment_from_variant (GVariant *v, /** + * ostree_sysroot_stage_overlay_initrd: + * @self: Sysroot + * @fd: (transfer none): File descriptor to overlay initrd + * @out_checksum: (out) (transfer full): Overlay initrd checksum + * @cancellable: Cancellable + * @error: Error + * + * Stage an overlay initrd to be used in an upcoming deployment. Returns a checksum which + * can be passed to ostree_sysroot_deploy_tree_with_options() or + * ostree_sysroot_stage_tree_with_options() via the `overlay_initrds` array option. + * + * Since: 2020.7 + */ +gboolean +ostree_sysroot_stage_overlay_initrd (OstreeSysroot *self, + int fd, + char **out_checksum, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (fd != -1, FALSE); + g_return_val_if_fail (out_checksum != NULL, FALSE); + + if (!glnx_shutil_mkdir_p_at (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED_INITRDS_DIR, + 0755, cancellable, error)) + return FALSE; + + glnx_autofd int staged_initrds_dfd = -1; + if (!glnx_opendirat (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED_INITRDS_DIR, FALSE, + &staged_initrds_dfd, error)) + return FALSE; + + g_auto(GLnxTmpfile) overlay_initrd = { 0, }; + if (!glnx_open_tmpfile_linkable_at (staged_initrds_dfd, ".", O_WRONLY | O_CLOEXEC, + &overlay_initrd, error)) + return FALSE; + + char checksum[_OSTREE_SHA256_STRING_LEN+1]; + { + g_autoptr(GOutputStream) output = g_unix_output_stream_new (overlay_initrd.fd, FALSE); + g_autoptr(GInputStream) input = g_unix_input_stream_new (fd, FALSE); + g_autofree guchar *digest = NULL; + if (!ot_gio_splice_get_checksum (output, input, &digest, cancellable, error)) + return FALSE; + ot_bin2hex (checksum, (guint8*)digest, _OSTREE_SHA256_DIGEST_LEN); + } + + if (!glnx_link_tmpfile_at (&overlay_initrd, GLNX_LINK_TMPFILE_REPLACE, + staged_initrds_dfd, checksum, error)) + return FALSE; + + *out_checksum = g_strdup (checksum); + return TRUE; +} + + +/** * ostree_sysroot_stage_tree: * @self: Sysroot * @osname: (allow-none): osname to use for merge deployment @@ -2962,8 +3112,7 @@ _ostree_sysroot_deserialize_deployment_from_variant (GVariant *v, * @cancellable: Cancellable * @error: Error * - * Like ostree_sysroot_deploy_tree(), but "finalization" only occurs at OS - * shutdown time. + * Older version of ostree_sysroot_stage_tree_with_options(). * * Since: 2018.5 */ @@ -2978,6 +3127,41 @@ ostree_sysroot_stage_tree (OstreeSysroot *self, GCancellable *cancellable, GError **error) { + OstreeSysrootDeployTreeOpts opts = { .override_kernel_argv = override_kernel_argv }; + return ostree_sysroot_stage_tree_with_options (self, osname, revision, origin, + merge_deployment, &opts, + out_new_deployment, cancellable, error); +} + + +/** + * ostree_sysroot_stage_tree_with_options: + * @self: Sysroot + * @osname: (allow-none): osname to use for merge deployment + * @revision: Checksum to add + * @origin: (allow-none): Origin to use for upgrades + * @merge_deployment: (allow-none): Use this deployment for merge path + * @opts: Options + * @out_new_deployment: (out): The new deployment path + * @cancellable: Cancellable + * @error: Error + * + * Like ostree_sysroot_deploy_tree(), but "finalization" only occurs at OS + * shutdown time. + * + * Since: 2020.7 + */ +gboolean +ostree_sysroot_stage_tree_with_options (OstreeSysroot *self, + const char *osname, + const char *revision, + GKeyFile *origin, + OstreeDeployment *merge_deployment, + OstreeSysrootDeployTreeOpts *opts, + OstreeDeployment **out_new_deployment, + GCancellable *cancellable, + GError **error) +{ if (!_ostree_sysroot_ensure_writable (self, error)) return FALSE; @@ -3008,8 +3192,8 @@ ostree_sysroot_stage_tree (OstreeSysroot *self, } /* OSTREE_SYSROOT_DEBUG_TEST_STAGED_PATH */ g_autoptr(OstreeDeployment) deployment = NULL; - if (!sysroot_initialize_deployment (self, osname, revision, origin, override_kernel_argv, - &deployment, cancellable, error)) + if (!sysroot_initialize_deployment (self, osname, revision, origin, opts, &deployment, + cancellable, error)) return FALSE; /* Write out the origin file using the sepolicy from the non-merged root for @@ -3044,9 +3228,12 @@ ostree_sysroot_stage_tree (OstreeSysroot *self, g_variant_builder_add (builder, "{sv}", "merge-deployment", serialize_deployment_to_variant (merge_deployment)); - if (override_kernel_argv) + if (opts && opts->override_kernel_argv) g_variant_builder_add (builder, "{sv}", "kargs", - g_variant_new_strv ((const char *const*)override_kernel_argv, -1)); + g_variant_new_strv ((const char *const*)opts->override_kernel_argv, -1)); + if (opts && opts->overlay_initrds) + g_variant_builder_add (builder, "{sv}", "overlay-initrds", + g_variant_new_strv ((const char *const*)opts->overlay_initrds, -1)); const char *parent = dirname (strdupa (_OSTREE_SYSROOT_RUNSTATE_STAGED)); if (!glnx_shutil_mkdir_p_at (AT_FDCWD, parent, 0755, cancellable, error)) @@ -3149,8 +3336,6 @@ _ostree_sysroot_finalize_staged (OstreeSysroot *self, ostree_deployment_get_csum (merge_deployment_stub), ostree_deployment_get_deployserial (merge_deployment_stub)); } - g_autofree char **kargs = NULL; - g_variant_lookup (self->staged_deployment_data, "kargs", "^a&s", &kargs); /* Unlink the staged state now; if we're interrupted in the middle, * we don't want e.g. deal with the partially written /etc merge. @@ -3158,7 +3343,7 @@ _ostree_sysroot_finalize_staged (OstreeSysroot *self, if (!glnx_unlinkat (AT_FDCWD, _OSTREE_SYSROOT_RUNSTATE_STAGED, 0, error)) return FALSE; - if (!sysroot_finalize_deployment (self, self->staged_deployment, kargs, merge_deployment, + if (!sysroot_finalize_deployment (self, self->staged_deployment, merge_deployment, cancellable, error)) return FALSE; diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h index 96670b1..318b0b1 100644 --- a/src/libostree/ostree-sysroot-private.h +++ b/src/libostree/ostree-sysroot-private.h @@ -38,6 +38,7 @@ typedef enum { /* This is a temporary flag until we fully drop the explicit `systemctl start * ostree-finalize-staged.service` so that tests can exercise the new path unit. */ OSTREE_SYSROOT_DEBUG_TEST_STAGED_PATH = 1 << 3, + OSTREE_SYSROOT_DEBUG_TEST_NO_DTB = 1 << 4, /* https://github.com/ostreedev/ostree/issues/2154 */ } OstreeSysrootDebugFlags; typedef enum { @@ -83,8 +84,13 @@ struct OstreeSysroot { /* We keep some transient state in /run */ #define _OSTREE_SYSROOT_RUNSTATE_STAGED "/run/ostree/staged-deployment" #define _OSTREE_SYSROOT_RUNSTATE_STAGED_LOCKED "/run/ostree/staged-deployment-locked" +#define _OSTREE_SYSROOT_RUNSTATE_STAGED_INITRDS_DIR "/run/ostree/staged-initrds/" #define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_DIR "/run/ostree/deployment-state/" #define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT "unlocked-development" +#define _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_TRANSIENT "unlocked-transient" + +#define _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS "ostree/initramfs-overlays" +#define _OSTREE_SYSROOT_INITRAMFS_OVERLAYS "boot/" _OSTREE_SYSROOT_BOOT_INITRAMFS_OVERLAYS gboolean _ostree_sysroot_ensure_writable (OstreeSysroot *self, diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c index 0dc7a39..e0813b5 100644 --- a/src/libostree/ostree-sysroot.c +++ b/src/libostree/ostree-sysroot.c @@ -190,6 +190,7 @@ ostree_sysroot_init (OstreeSysroot *self) { "test-fifreeze", OSTREE_SYSROOT_DEBUG_TEST_FIFREEZE }, { "no-xattrs", OSTREE_SYSROOT_DEBUG_NO_XATTRS }, { "test-staged-path", OSTREE_SYSROOT_DEBUG_TEST_STAGED_PATH }, + { "no-dtb", OSTREE_SYSROOT_DEBUG_TEST_NO_DTB }, }; self->debug_flags = g_parse_debug_string (g_getenv ("OSTREE_SYSROOT_DEBUG"), @@ -747,9 +748,13 @@ parse_deployment (OstreeSysroot *self, ret_deployment->unlocked = OSTREE_DEPLOYMENT_UNLOCKED_NONE; g_autofree char *unlocked_development_path = _ostree_sysroot_get_runstate_path (ret_deployment, _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT); + g_autofree char *unlocked_transient_path = + _ostree_sysroot_get_runstate_path (ret_deployment, _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_TRANSIENT); struct stat stbuf; if (lstat (unlocked_development_path, &stbuf) == 0) ret_deployment->unlocked = OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT; + else if (lstat (unlocked_transient_path, &stbuf) == 0) + ret_deployment->unlocked = OSTREE_DEPLOYMENT_UNLOCKED_TRANSIENT; else { GKeyFile *origin = ostree_deployment_get_origin (ret_deployment); @@ -810,6 +815,24 @@ list_deployments_process_one_boot_entry (OstreeSysroot *self, return FALSE; ostree_deployment_set_bootconfig (deployment, config); + char **overlay_initrds = ostree_bootconfig_parser_get_overlay_initrds (config); + g_autoptr(GPtrArray) initrds_chksums = NULL; + for (char **it = overlay_initrds; it && *it; it++) + { + const char *basename = glnx_basename (*it); + if (strlen (basename) != (_OSTREE_SHA256_STRING_LEN + strlen (".img"))) + return glnx_throw (error, "Malformed overlay initrd filename: %s", basename); + + if (!initrds_chksums) /* lazy init */ + initrds_chksums = g_ptr_array_new_full (g_strv_length (overlay_initrds), g_free); + g_ptr_array_add (initrds_chksums, g_strndup (basename, _OSTREE_SHA256_STRING_LEN)); + } + + if (initrds_chksums) + { + g_ptr_array_add (initrds_chksums, NULL); + _ostree_deployment_set_overlay_initrds (deployment, (char**)initrds_chksums->pdata); + } g_ptr_array_add (inout_deployments, g_object_ref (deployment)); return TRUE; @@ -962,8 +985,10 @@ _ostree_sysroot_reload_staged (OstreeSysroot *self, /* Parse it */ g_autoptr(GVariant) target = NULL; g_autofree char **kargs = NULL; + g_autofree char **overlay_initrds = NULL; g_variant_dict_lookup (staged_deployment_dict, "target", "@a{sv}", &target); g_variant_dict_lookup (staged_deployment_dict, "kargs", "^a&s", &kargs); + g_variant_dict_lookup (staged_deployment_dict, "overlay-initrds", "^a&s", &overlay_initrds); if (target) { g_autoptr(OstreeDeployment) staged = @@ -975,6 +1000,8 @@ _ostree_sysroot_reload_staged (OstreeSysroot *self, if (!load_origin (self, staged, NULL, error)) return FALSE; + _ostree_deployment_set_overlay_initrds (staged, overlay_initrds); + self->staged_deployment = g_steal_pointer (&staged); self->staged_deployment_data = g_steal_pointer (&staged_deployment_data); /* We set this flag for ostree_deployment_is_staged() because that API @@ -1932,6 +1959,8 @@ ostree_sysroot_deployment_unlock (OstreeSysroot *self, const char *ovl_options = NULL; static const char hotfix_ovl_options[] = "lowerdir=usr,upperdir=.usr-ovl-upper,workdir=.usr-ovl-work"; + g_autofree char *unlock_ovldir = NULL; + switch (unlocked_state) { case OSTREE_DEPLOYMENT_UNLOCKED_NONE: @@ -1951,11 +1980,12 @@ ostree_sysroot_deployment_unlock (OstreeSysroot *self, } break; case OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT: + case OSTREE_DEPLOYMENT_UNLOCKED_TRANSIENT: { + unlock_ovldir = g_strdup ("/var/tmp/ostree-unlock-ovl.XXXXXX"); /* We're just doing transient development/hacking? Okay, * stick the overlayfs bits in /var/tmp. */ - char *development_ovldir = strdupa ("/var/tmp/ostree-unlock-ovl.XXXXXX"); const char *development_ovl_upper; const char *development_ovl_work; @@ -1966,14 +1996,14 @@ ostree_sysroot_deployment_unlock (OstreeSysroot *self, "/usr", usr_mode, error)) return FALSE; - if (g_mkdtemp_full (development_ovldir, 0755) == NULL) + if (g_mkdtemp_full (unlock_ovldir, 0755) == NULL) return glnx_throw_errno_prefix (error, "mkdtemp"); } - development_ovl_upper = glnx_strjoina (development_ovldir, "/upper"); + development_ovl_upper = glnx_strjoina (unlock_ovldir, "/upper"); if (!mkdir_unmasked (AT_FDCWD, development_ovl_upper, usr_mode, cancellable, error)) return FALSE; - development_ovl_work = glnx_strjoina (development_ovldir, "/work"); + development_ovl_work = glnx_strjoina (unlock_ovldir, "/work"); if (!mkdir_unmasked (AT_FDCWD, development_ovl_work, usr_mode, cancellable, error)) return FALSE; ovl_options = glnx_strjoina ("lowerdir=usr,upperdir=", development_ovl_upper, @@ -1996,6 +2026,9 @@ ostree_sysroot_deployment_unlock (OstreeSysroot *self, return glnx_throw_errno_prefix (error, "fork"); else if (mount_child == 0) { + int mountflags = 0; + if (unlocked_state == OSTREE_DEPLOYMENT_UNLOCKED_TRANSIENT) + mountflags |= MS_RDONLY; /* Child process. Do NOT use any GLib API here; it's not generally fork() safe. * * TODO: report errors across a pipe (or use the journal?) rather than @@ -2003,7 +2036,7 @@ ostree_sysroot_deployment_unlock (OstreeSysroot *self, */ if (fchdir (deployment_dfd) < 0) err (1, "fchdir"); - if (mount ("overlay", "/usr", "overlay", 0, ovl_options) < 0) + if (mount ("overlay", "/usr", "overlay", mountflags, ovl_options) < 0) err (1, "mount"); exit (EXIT_SUCCESS); } @@ -2036,15 +2069,19 @@ ostree_sysroot_deployment_unlock (OstreeSysroot *self, return FALSE; break; case OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT: + case OSTREE_DEPLOYMENT_UNLOCKED_TRANSIENT: { g_autofree char *devpath = - _ostree_sysroot_get_runstate_path (deployment, _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT); + unlocked_state == OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT ? + _ostree_sysroot_get_runstate_path (deployment, _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_DEVELOPMENT) + : + _ostree_sysroot_get_runstate_path (deployment, _OSTREE_SYSROOT_DEPLOYMENT_RUNSTATE_FLAG_TRANSIENT); g_autofree char *devpath_parent = dirname (g_strdup (devpath)); if (!glnx_shutil_mkdir_p_at (AT_FDCWD, devpath_parent, 0755, cancellable, error)) return FALSE; - if (!g_file_set_contents (devpath, "", 0, error)) + if (!g_file_set_contents (devpath, unlock_ovldir, -1, error)) return FALSE; } } diff --git a/src/libostree/ostree-sysroot.h b/src/libostree/ostree-sysroot.h index d9f5a54..3a3b6a7 100644 --- a/src/libostree/ostree-sysroot.h +++ b/src/libostree/ostree-sysroot.h @@ -187,6 +187,21 @@ gboolean ostree_sysroot_write_deployments_with_options (OstreeSysroot *self, GError **error); _OSTREE_PUBLIC +gboolean ostree_sysroot_stage_overlay_initrd (OstreeSysroot *self, + int fd, + char **out_checksum, + GCancellable *cancellable, + GError **error); + +typedef struct { + gboolean unused_bools[8]; + int unused_ints[8]; + char **override_kernel_argv; + char **overlay_initrds; + gpointer unused_ptrs[6]; +} OstreeSysrootDeployTreeOpts; + +_OSTREE_PUBLIC gboolean ostree_sysroot_deploy_tree (OstreeSysroot *self, const char *osname, const char *revision, @@ -198,6 +213,17 @@ gboolean ostree_sysroot_deploy_tree (OstreeSysroot *self, GError **error); _OSTREE_PUBLIC +gboolean ostree_sysroot_deploy_tree_with_options (OstreeSysroot *self, + const char *osname, + const char *revision, + GKeyFile *origin, + OstreeDeployment *provided_merge_deployment, + OstreeSysrootDeployTreeOpts *opts, + OstreeDeployment **out_new_deployment, + GCancellable *cancellable, + GError **error); + +_OSTREE_PUBLIC gboolean ostree_sysroot_stage_tree (OstreeSysroot *self, const char *osname, const char *revision, @@ -209,6 +235,18 @@ gboolean ostree_sysroot_stage_tree (OstreeSysroot *self, GError **error); _OSTREE_PUBLIC +gboolean ostree_sysroot_stage_tree_with_options (OstreeSysroot *self, + const char *osname, + const char *revision, + GKeyFile *origin, + OstreeDeployment *merge_deployment, + OstreeSysrootDeployTreeOpts *opts, + OstreeDeployment **out_new_deployment, + GCancellable *cancellable, + GError **error); + + +_OSTREE_PUBLIC gboolean ostree_sysroot_deployment_set_mutable (OstreeSysroot *self, OstreeDeployment *deployment, gboolean is_mutable, diff --git a/src/libostree/ostree-version.h b/src/libostree/ostree-version.h index f9c398e..90e96ea 100644 --- a/src/libostree/ostree-version.h +++ b/src/libostree/ostree-version.h @@ -43,7 +43,7 @@ * * Since: 2017.4 */ -#define OSTREE_RELEASE_VERSION (4) +#define OSTREE_RELEASE_VERSION (7) /** * OSTREE_VERSION @@ -52,7 +52,7 @@ * * Since: 2017.4 */ -#define OSTREE_VERSION (2020.4) +#define OSTREE_VERSION (2020.7) /** * OSTREE_VERSION_S: @@ -62,7 +62,7 @@ * * Since: 2017.4 */ -#define OSTREE_VERSION_S "2020.4" +#define OSTREE_VERSION_S "2020.7" #define OSTREE_ENCODE_VERSION(year,release) \ ((year) << 16 | (release)) diff --git a/src/libotutil/ot-checksum-utils.c b/src/libotutil/ot-checksum-utils.c index 6676736..26e0280 100644 --- a/src/libotutil/ot-checksum-utils.c +++ b/src/libotutil/ot-checksum-utils.c @@ -275,3 +275,13 @@ ot_checksum_file_at (int dfd, ot_checksum_get_hexdigest (&checksum, hexdigest, sizeof (hexdigest)); return g_strdup (hexdigest); } + +void +ot_checksum_bytes (GBytes *data, + guint8 out_digest[_OSTREE_SHA256_DIGEST_LEN]) +{ + g_auto(OtChecksum) hasher = { 0, }; + ot_checksum_init (&hasher); + ot_checksum_update_bytes (&hasher, data); + ot_checksum_get_digest (&hasher, out_digest, _OSTREE_SHA256_DIGEST_LEN); +} diff --git a/src/libotutil/ot-checksum-utils.h b/src/libotutil/ot-checksum-utils.h index 411ef25..5432c81 100644 --- a/src/libotutil/ot-checksum-utils.h +++ b/src/libotutil/ot-checksum-utils.h @@ -96,4 +96,7 @@ char * ot_checksum_file_at (int dfd, GCancellable *cancellable, GError **error); +void ot_checksum_bytes (GBytes *data, + guint8 out_digest[_OSTREE_SHA256_DIGEST_LEN]); + G_END_DECLS diff --git a/src/libotutil/otutil.h b/src/libotutil/otutil.h index cd31236..7db7270 100644 --- a/src/libotutil/otutil.h +++ b/src/libotutil/otutil.h @@ -52,6 +52,31 @@ #define ot_journal_print(...) {} #endif +typedef GMainContext GMainContextPopDefault; +static inline void +_ostree_main_context_pop_default_destroy (void *p) +{ + GMainContext *main_context = p; + + if (main_context) + { + g_main_context_pop_thread_default (main_context); + g_main_context_unref (main_context); + } +} + +static inline GMainContextPopDefault * +_ostree_main_context_new_default (void) +{ + GMainContext *main_context = g_main_context_new (); + + g_main_context_push_thread_default (main_context); + return main_context; +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GMainContextPopDefault, _ostree_main_context_pop_default_destroy) + + #include #include #include diff --git a/src/ostree/ot-admin-builtin-deploy.c b/src/ostree/ot-admin-builtin-deploy.c index bcece3f..8156cc1 100644 --- a/src/ostree/ot-admin-builtin-deploy.c +++ b/src/ostree/ot-admin-builtin-deploy.c @@ -44,6 +44,7 @@ static gboolean opt_kernel_proc_cmdline; static char *opt_osname; static char *opt_origin_path; static gboolean opt_kernel_arg_none; +static char **opt_overlay_initrds; static GOptionEntry options[] = { { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Use a different operating system root than the current one", "OSNAME" }, @@ -59,6 +60,7 @@ static GOptionEntry options[] = { { "karg", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_kernel_argv, "Set kernel argument, like root=/dev/sda1; this overrides any earlier argument with the same name", "NAME=VALUE" }, { "karg-append", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_kernel_argv_append, "Append kernel argument; useful with e.g. console= that can be used multiple times", "NAME=VALUE" }, { "karg-none", 0, 0, G_OPTION_ARG_NONE, &opt_kernel_arg_none, "Do not import kernel arguments", NULL }, + { "overlay-initrd", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_overlay_initrds, "Overlay iniramfs file", "FILE" }, { NULL } }; @@ -167,24 +169,76 @@ ot_admin_builtin_deploy (int argc, char **argv, OstreeCommandInvocation *invocat ostree_kernel_args_append_argv (kargs, opt_kernel_argv_append); } - g_autoptr(OstreeDeployment) new_deployment = NULL; + g_autoptr(GPtrArray) overlay_initrd_chksums = NULL; + for (char **it = opt_overlay_initrds; it && *it; it++) + { + const char *path = *it; + + glnx_autofd int fd = -1; + if (!glnx_openat_rdonly (AT_FDCWD, path, TRUE, &fd, error)) + return FALSE; + + g_autofree char *chksum = NULL; + if (!ostree_sysroot_stage_overlay_initrd (sysroot, fd, &chksum, cancellable, error)) + return FALSE; + + if (!overlay_initrd_chksums) + overlay_initrd_chksums = g_ptr_array_new_full (g_strv_length (opt_overlay_initrds), g_free); + g_ptr_array_add (overlay_initrd_chksums, g_steal_pointer (&chksum)); + } + + if (overlay_initrd_chksums) + g_ptr_array_add (overlay_initrd_chksums, NULL); + g_auto(GStrv) kargs_strv = kargs ? ostree_kernel_args_to_strv (kargs) : NULL; + + OstreeSysrootDeployTreeOpts opts = { + .override_kernel_argv = kargs_strv, + .overlay_initrds = overlay_initrd_chksums ? (char**)overlay_initrd_chksums->pdata : NULL, + }; + + g_autoptr(OstreeDeployment) new_deployment = NULL; if (opt_stage) { if (opt_retain_pending || opt_retain_rollback) return glnx_throw (error, "--stage cannot currently be combined with --retain arguments"); if (opt_not_as_default) return glnx_throw (error, "--stage cannot currently be combined with --not-as-default"); - if (!ostree_sysroot_stage_tree (sysroot, opt_osname, revision, origin, merge_deployment, - kargs_strv, &new_deployment, cancellable, error)) - return FALSE; + /* use old API if we can to exercise it in CI */ + if (!overlay_initrd_chksums) + { + if (!ostree_sysroot_stage_tree (sysroot, opt_osname, revision, origin, + merge_deployment, kargs_strv, &new_deployment, + cancellable, error)) + return FALSE; + } + else + { + if (!ostree_sysroot_stage_tree_with_options (sysroot, opt_osname, revision, + origin, merge_deployment, &opts, + &new_deployment, cancellable, error)) + return FALSE; + } g_assert (new_deployment); } else { - if (!ostree_sysroot_deploy_tree (sysroot, opt_osname, revision, origin, merge_deployment, - kargs_strv, &new_deployment, cancellable, error)) - return FALSE; + /* use old API if we can to exercise it in CI */ + if (!overlay_initrd_chksums) + { + if (!ostree_sysroot_deploy_tree (sysroot, opt_osname, revision, origin, + merge_deployment, kargs_strv, &new_deployment, + cancellable, error)) + return FALSE; + } + else + { + if (!ostree_sysroot_deploy_tree_with_options (sysroot, opt_osname, revision, + origin, merge_deployment, &opts, + &new_deployment, cancellable, + error)) + return FALSE; + } g_assert (new_deployment); OstreeSysrootSimpleWriteDeploymentFlags flags = OSTREE_SYSROOT_SIMPLE_WRITE_DEPLOYMENT_FLAGS_NO_CLEAN; diff --git a/src/ostree/ot-admin-builtin-pin.c b/src/ostree/ot-admin-builtin-pin.c index d4337e3..5269dd8 100644 --- a/src/ostree/ot-admin-builtin-pin.c +++ b/src/ostree/ot-admin-builtin-pin.c @@ -55,7 +55,14 @@ ot_admin_builtin_pin (int argc, char **argv, OstreeCommandInvocation *invocation for (unsigned int i = 1; i < argc; i++) { const char *deploy_index_str = argv[i]; - const int deploy_index = atoi (deploy_index_str); + char *endptr = NULL; + + errno = 0; + const guint64 deploy_index = g_ascii_strtoull (deploy_index_str, &endptr, 10); + if (*endptr != '\0') + return glnx_throw (error, "Invalid index: %s", deploy_index_str); + if (errno == ERANGE) + return glnx_throw (error, "Index too large: %s", deploy_index_str); g_autoptr(OstreeDeployment) target_deployment = ot_admin_get_indexed_deployment (sysroot, deploy_index, error); if (!target_deployment) diff --git a/src/ostree/ot-admin-builtin-set-origin.c b/src/ostree/ot-admin-builtin-set-origin.c index 9d96512..4dc68a0 100644 --- a/src/ostree/ot-admin-builtin-set-origin.c +++ b/src/ostree/ot-admin-builtin-set-origin.c @@ -95,7 +95,7 @@ ot_admin_builtin_set_origin (int argc, char **argv, OstreeCommandInvocation *inv { char **iter; g_autoptr(GVariantBuilder) optbuilder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); - g_autoptr(GVariant) options = NULL; + g_autoptr(GVariant) remote_options = NULL; for (iter = opt_set; iter && *iter; iter++) { @@ -110,12 +110,12 @@ ot_admin_builtin_set_origin (int argc, char **argv, OstreeCommandInvocation *inv subkey, g_variant_new_variant (g_variant_new_string (subvalue))); } - options = g_variant_ref_sink (g_variant_builder_end (optbuilder)); + remote_options = g_variant_ref_sink (g_variant_builder_end (optbuilder)); if (!ostree_repo_remote_change (repo, NULL, OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS, remotename, url, - options, + remote_options, cancellable, error)) goto out; } diff --git a/src/ostree/ot-admin-builtin-switch.c b/src/ostree/ot-admin-builtin-switch.c index 2f12ef1..b94be76 100644 --- a/src/ostree/ot-admin-builtin-switch.c +++ b/src/ostree/ot-admin-builtin-switch.c @@ -44,7 +44,7 @@ gboolean ot_admin_builtin_switch (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { g_autoptr(GOptionContext) context = - g_option_context_new ("REF"); + g_option_context_new ("REFSPEC"); g_autoptr(OstreeSysroot) sysroot = NULL; if (!ostree_admin_option_context_parse (context, options, &argc, &argv, OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER, @@ -53,7 +53,7 @@ ot_admin_builtin_switch (int argc, char **argv, OstreeCommandInvocation *invocat if (argc < 2) { - ot_util_usage_error (context, "REF must be specified", error); + ot_util_usage_error (context, "REFSPEC must be specified", error); return FALSE; } diff --git a/src/ostree/ot-admin-builtin-unlock.c b/src/ostree/ot-admin-builtin-unlock.c index cd46618..6c265f5 100644 --- a/src/ostree/ot-admin-builtin-unlock.c +++ b/src/ostree/ot-admin-builtin-unlock.c @@ -31,9 +31,11 @@ #include static gboolean opt_hotfix; +static gboolean opt_transient; static GOptionEntry options[] = { { "hotfix", 0, 0, G_OPTION_ARG_NONE, &opt_hotfix, "Retain changes across reboots", NULL }, + { "transient", 0, 0, G_OPTION_ARG_NONE, &opt_transient, "Mount overlayfs read-only by default", NULL }, { NULL } }; @@ -67,7 +69,17 @@ ot_admin_builtin_unlock (int argc, char **argv, OstreeCommandInvocation *invocat goto out; } - target_state = opt_hotfix ? OSTREE_DEPLOYMENT_UNLOCKED_HOTFIX : OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT; + if (opt_hotfix && opt_transient) + { + glnx_throw (error, "Cannot specify both --hotfix and --transient"); + goto out; + } + else if (opt_hotfix) + target_state = OSTREE_DEPLOYMENT_UNLOCKED_HOTFIX; + else if (opt_transient) + target_state = OSTREE_DEPLOYMENT_UNLOCKED_TRANSIENT; + else + target_state = OSTREE_DEPLOYMENT_UNLOCKED_DEVELOPMENT; if (!ostree_sysroot_deployment_unlock (sysroot, booted_deployment, target_state, cancellable, error)) @@ -87,6 +99,10 @@ ot_admin_builtin_unlock (int argc, char **argv, OstreeCommandInvocation *invocat g_print ("Development mode enabled. A writable overlayfs is now mounted on /usr.\n" "All changes there will be discarded on reboot.\n"); break; + case OSTREE_DEPLOYMENT_UNLOCKED_TRANSIENT: + g_print ("A writable overlayfs is prepared for /usr, but is mounted read-only by default.\n" + "All changes there will be discarded on reboot.\n"); + break; } ret = TRUE; diff --git a/src/ostree/ot-builtin-admin.c b/src/ostree/ot-builtin-admin.c index 9f1a615..834a271 100644 --- a/src/ostree/ot-builtin-admin.c +++ b/src/ostree/ot-builtin-admin.c @@ -65,7 +65,7 @@ static OstreeCommand admin_subcommands[] = { "List deployments" }, { "switch", OSTREE_BUILTIN_FLAG_NO_REPO, ot_admin_builtin_switch, - "Construct new tree from REF and deploy it" }, + "Construct new tree from REFSPEC and deploy it" }, { "undeploy", OSTREE_BUILTIN_FLAG_NO_REPO, ot_admin_builtin_undeploy, "Delete deployment INDEX" }, diff --git a/src/ostree/ot-builtin-checkout.c b/src/ostree/ot-builtin-checkout.c index 813dbb9..fe9558c 100644 --- a/src/ostree/ot-builtin-checkout.c +++ b/src/ostree/ot-builtin-checkout.c @@ -84,7 +84,7 @@ static GOptionEntry options[] = { { "from-file", 0, 0, G_OPTION_ARG_STRING, &opt_from_file, "Process many checkouts from input file", "FILE" }, { "fsync", 0, 0, G_OPTION_ARG_CALLBACK, parse_fsync_cb, "Specify how to invoke fsync()", "POLICY" }, { "require-hardlinks", 'H', 0, G_OPTION_ARG_NONE, &opt_require_hardlinks, "Do not fall back to full copies if hardlinking fails", NULL }, - { "force-copy-zerosized", 'z', 0, G_OPTION_ARG_NONE, &opt_force_copy_zerosized, "Do not hardlink zero-sized files", NULL }, + { "force-copy-zerosized", 'z', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &opt_force_copy_zerosized, "Do not hardlink zero-sized files", NULL }, { "force-copy", 'C', 0, G_OPTION_ARG_NONE, &opt_force_copy, "Never hardlink (but may reflink if available)", NULL }, { "bareuseronly-dirs", 'M', 0, G_OPTION_ARG_NONE, &opt_bareuseronly_dirs, "Suppress mode bits outside of 0775 for directories (suid, world writable, etc.)", NULL }, { "skip-list", 0, 0, G_OPTION_ARG_FILENAME, &opt_skiplist_file, "File containing list of files to skip", "FILE" }, @@ -135,14 +135,14 @@ process_one_checkout (OstreeRepo *repo, opt_bareuseronly_dirs || opt_union_identical || opt_skiplist_file || opt_selinux_policy || opt_selinux_prefix) { - OstreeRepoCheckoutAtOptions options = { 0, }; + OstreeRepoCheckoutAtOptions checkout_options = { 0, }; /* do this early so option checking also catches force copy conflicts */ if (opt_selinux_policy) opt_force_copy = TRUE; if (opt_user_mode) - options.mode = OSTREE_REPO_CHECKOUT_MODE_USER; + checkout_options.mode = OSTREE_REPO_CHECKOUT_MODE_USER; /* Can't union these */ if (opt_union && opt_union_add) { @@ -173,9 +173,9 @@ process_one_checkout (OstreeRepo *repo, goto out; } else if (opt_union) - options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES; + checkout_options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES; else if (opt_union_add) - options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES; + checkout_options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_ADD_FILES; else if (opt_union_identical) { if (!opt_require_hardlinks) @@ -184,12 +184,12 @@ process_one_checkout (OstreeRepo *repo, "--union-identical requires --require-hardlinks"); goto out; } - options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_IDENTICAL; + checkout_options.overwrite_mode = OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_IDENTICAL; } if (opt_whiteouts) - options.process_whiteouts = TRUE; + checkout_options.process_whiteouts = TRUE; if (subpath) - options.subpath = subpath; + checkout_options.subpath = subpath; g_autoptr(OstreeSePolicy) policy = NULL; if (opt_selinux_policy) @@ -203,8 +203,8 @@ process_one_checkout (OstreeRepo *repo, policy = ostree_sepolicy_new_at (rootfs_dfd, cancellable, error); if (!policy) goto out; - options.sepolicy = policy; - options.sepolicy_prefix = opt_selinux_prefix; + checkout_options.sepolicy = policy; + checkout_options.sepolicy_prefix = opt_selinux_prefix; } g_autoptr(GHashTable) skip_list = @@ -214,16 +214,16 @@ process_one_checkout (OstreeRepo *repo, if (!ot_parse_file_by_line (opt_skiplist_file, handle_skiplist_line, skip_list, cancellable, error)) goto out; - options.filter = checkout_filter; - options.filter_user_data = skip_list; + checkout_options.filter = checkout_filter; + checkout_options.filter_user_data = skip_list; } - options.no_copy_fallback = opt_require_hardlinks; - options.force_copy = opt_force_copy; - options.force_copy_zerosized = opt_force_copy_zerosized; - options.bareuseronly_dirs = opt_bareuseronly_dirs; + checkout_options.no_copy_fallback = opt_require_hardlinks; + checkout_options.force_copy = opt_force_copy; + checkout_options.force_copy_zerosized = opt_force_copy_zerosized; + checkout_options.bareuseronly_dirs = opt_bareuseronly_dirs; - if (!ostree_repo_checkout_at (repo, &options, + if (!ostree_repo_checkout_at (repo, &checkout_options, AT_FDCWD, destination, resolved_commit, cancellable, error)) diff --git a/src/ostree/ot-builtin-commit.c b/src/ostree/ot-builtin-commit.c index 20409fc..48fa292 100644 --- a/src/ostree/ot-builtin-commit.c +++ b/src/ostree/ot-builtin-commit.c @@ -94,7 +94,7 @@ parse_fsync_cb (const char *option_name, */ static GOptionEntry options[] = { - { "parent", 0, 0, G_OPTION_ARG_STRING, &opt_parent, "Parent ref, or \"none\"", "REF" }, + { "parent", 0, 0, G_OPTION_ARG_STRING, &opt_parent, "Parent commit checksum, or \"none\"", "COMMIT" }, { "subject", 's', 0, G_OPTION_ARG_STRING, &opt_subject, "One line subject", "SUBJECT" }, { "body", 'm', 0, G_OPTION_ARG_STRING, &opt_body, "Full description", "BODY" }, { "body-file", 'F', 0, G_OPTION_ARG_FILENAME, &opt_body_file, "Commit message from FILE path", "FILE" }, @@ -103,7 +103,7 @@ static GOptionEntry options[] = { { "orphan", 0, 0, G_OPTION_ARG_NONE, &opt_orphan, "Create a commit without writing a ref", NULL }, { "no-bindings", 0, 0, G_OPTION_ARG_NONE, &opt_no_bindings, "Do not write any ref bindings", NULL }, { "bind-ref", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_bind_refs, "Add a ref to ref binding commit metadata", "BRANCH" }, - { "base", 0, 0, G_OPTION_ARG_STRING, &opt_base, "Start from the given commit as a base (no modifiers apply)", "REF" }, + { "base", 0, 0, G_OPTION_ARG_STRING, &opt_base, "Start from the given commit as a base (no modifiers apply)", "REV" }, { "tree", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_trees, "Overlay the given argument as a tree", "dir=PATH or tar=TARFILE or ref=COMMIT" }, { "add-metadata-string", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_metadata_strings, "Add a key/value pair to metadata", "KEY=VALUE" }, { "add-metadata", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_metadata_variants, "Add a key/value pair to metadata, where the KEY is a string, an VALUE is g_variant_parse() formatted", "KEY=VALUE" }, @@ -614,10 +614,10 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio if (opt_base) { g_autofree char *base_commit = NULL; - g_autoptr(GFile) root = NULL; - if (!ostree_repo_read_commit (repo, opt_base, &root, &base_commit, cancellable, error)) + g_autoptr(GFile) base_root = NULL; + if (!ostree_repo_read_commit (repo, opt_base, &base_root, &base_commit, cancellable, error)) goto out; - OstreeRepoFile *rootf = (OstreeRepoFile*) root; + OstreeRepoFile *rootf = (OstreeRepoFile*) base_root; mtree = ostree_mutable_tree_new_from_checksum (repo, ostree_repo_file_tree_get_contents_checksum (rootf), diff --git a/src/ostree/ot-builtin-config.c b/src/ostree/ot-builtin-config.c index 811a838..64e434a 100644 --- a/src/ostree/ot-builtin-config.c +++ b/src/ostree/ot-builtin-config.c @@ -134,7 +134,7 @@ ostree_builtin_config (int argc, char **argv, OstreeCommandInvocation *invocatio else if (!strcmp (op, "get")) { GKeyFile *readonly_config = NULL; - g_autofree char *value = NULL; + g_autofree char *read_value = NULL; if (opt_group) { if (argc < 3) @@ -160,11 +160,11 @@ ostree_builtin_config (int argc, char **argv, OstreeCommandInvocation *invocatio } readonly_config = ostree_repo_get_config (repo); - value = g_key_file_get_string (readonly_config, section, key, error); - if (value == NULL) + read_value = g_key_file_get_string (readonly_config, section, key, error); + if (read_value == NULL) return FALSE; - g_print ("%s\n", value); + g_print ("%s\n", read_value); } else if (!strcmp (op, "unset")) { diff --git a/src/ostree/ot-builtin-gpg-sign.c b/src/ostree/ot-builtin-gpg-sign.c index 6babbf2..bde9180 100644 --- a/src/ostree/ot-builtin-gpg-sign.c +++ b/src/ostree/ot-builtin-gpg-sign.c @@ -171,9 +171,8 @@ delete_signatures (OstreeRepo *repo, while (!g_queue_is_empty (&signatures)) { - GVariant *child = g_queue_pop_head (&signatures); - g_variant_builder_add_value (&signature_builder, child); - g_variant_unref (child); + g_autoptr(GVariant) sigchild = g_queue_pop_head (&signatures); + g_variant_builder_add_value (&signature_builder, sigchild); } g_variant_dict_insert_value (&metadata_dict, diff --git a/src/ostree/ot-builtin-log.c b/src/ostree/ot-builtin-log.c index 306f177..0a1d408 100644 --- a/src/ostree/ot-builtin-log.c +++ b/src/ostree/ot-builtin-log.c @@ -95,7 +95,7 @@ ostree_builtin_log (int argc, g_autofree char *checksum = NULL; OstreeDumpFlags flags = OSTREE_DUMP_NONE; - context = g_option_context_new ("REF"); + context = g_option_context_new ("REV"); if (!ostree_option_context_parse (context, options, &argc, &argv, invocation, &repo, cancellable, error)) goto out; @@ -105,7 +105,7 @@ ostree_builtin_log (int argc, if (argc <= 1) { - ot_util_usage_error (context, "A ref argument is required", error); + ot_util_usage_error (context, "A rev argument is required", error); goto out; } rev = argv[1]; diff --git a/src/ostree/ot-builtin-pull.c b/src/ostree/ot-builtin-pull.c index e69d62e..ed0ec55 100644 --- a/src/ostree/ot-builtin-pull.c +++ b/src/ostree/ot-builtin-pull.c @@ -271,7 +271,7 @@ ostree_builtin_pull (int argc, char **argv, OstreeCommandInvocation *invocation, { GVariantBuilder builder; - g_autoptr(GVariant) options = NULL; + g_autoptr(GVariant) pull_options = NULL; g_auto(GLnxConsoleRef) console = { 0, }; g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); @@ -378,9 +378,9 @@ ostree_builtin_pull (int argc, char **argv, OstreeCommandInvocation *invocation, #endif /* OSTREE_DISABLE_GPGME */ } - options = g_variant_ref_sink (g_variant_builder_end (&builder)); + pull_options = g_variant_ref_sink (g_variant_builder_end (&builder)); - if (!ostree_repo_pull_with_options (repo, remote, options, + if (!ostree_repo_pull_with_options (repo, remote, pull_options, progress, cancellable, error)) goto out; diff --git a/src/ostree/ot-builtin-sign.c b/src/ostree/ot-builtin-sign.c index c777748..2f90acd 100644 --- a/src/ostree/ot-builtin-sign.c +++ b/src/ostree/ot-builtin-sign.c @@ -167,7 +167,7 @@ ostree_builtin_sign (int argc, char **argv, OstreeCommandInvocation *invocation, if ((n_key_ids == 0) || opt_filename) { g_autoptr (GVariantBuilder) builder = NULL; - g_autoptr (GVariant) options = NULL; + g_autoptr (GVariant) sign_options = NULL; builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); /* Use custom directory with public and revoked keys instead of system-wide directories */ @@ -176,9 +176,9 @@ ostree_builtin_sign (int argc, char **argv, OstreeCommandInvocation *invocation, /* The last chance for verification source -- system files */ if (opt_filename) g_variant_builder_add (builder, "{sv}", "filename", g_variant_new_string (opt_filename)); - options = g_variant_builder_end (builder); + sign_options = g_variant_builder_end (builder); - if (!ostree_sign_load_pk (sign, options, error)) + if (!ostree_sign_load_pk (sign, sign_options, error)) goto out; if (ostree_sign_commit_verify (sign, diff --git a/src/ostree/ot-builtin-static-delta.c b/src/ostree/ot-builtin-static-delta.c index 4f9ff2b..3e0af5b 100644 --- a/src/ostree/ot-builtin-static-delta.c +++ b/src/ostree/ot-builtin-static-delta.c @@ -40,6 +40,10 @@ static gboolean opt_swap_endianness; static gboolean opt_inline; static gboolean opt_disable_bsdiff; static gboolean opt_if_not_exists; +static char **opt_key_ids; +static char *opt_sign_name; +static char *opt_keysfilename; +static char *opt_keysdir; #define BUILTINPROTO(name) static gboolean ot_static_delta_builtin_ ## name (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) @@ -48,6 +52,7 @@ BUILTINPROTO(show); BUILTINPROTO(delete); BUILTINPROTO(generate); BUILTINPROTO(apply_offline); +BUILTINPROTO(verify); #undef BUILTINPROTO @@ -67,6 +72,9 @@ static OstreeCommand static_delta_subcommands[] = { { "apply-offline", OSTREE_BUILTIN_FLAG_NONE, ot_static_delta_builtin_apply_offline, "Apply static delta file" }, + { "verify", OSTREE_BUILTIN_FLAG_NONE, + ot_static_delta_builtin_verify, + "Verify static delta signatures" }, { NULL, 0, NULL, NULL } }; @@ -88,10 +96,20 @@ static GOptionEntry generate_options[] = { { "max-bsdiff-size", 0, 0, G_OPTION_ARG_STRING, &opt_max_bsdiff_size, "Maximum size in megabytes to consider bsdiff compression for input files", NULL}, { "max-chunk-size", 0, 0, G_OPTION_ARG_STRING, &opt_max_chunk_size, "Maximum size of delta chunks in megabytes", NULL}, { "filename", 0, 0, G_OPTION_ARG_FILENAME, &opt_filename, "Write the delta content to PATH (a directory). If not specified, the OSTree repository is used", "PATH"}, + { "sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_key_ids, "Sign the delta with", "KEY_ID"}, + { "sign-type", 0, 0, G_OPTION_ARG_STRING, &opt_sign_name, "Signature type to use (defaults to 'ed25519')", "NAME"}, +#if defined(HAVE_LIBSODIUM) + { "keys-file", 0, 0, G_OPTION_ARG_STRING, &opt_keysfilename, "Read key(s) from file", "NAME"}, +#endif { NULL } }; static GOptionEntry apply_offline_options[] = { + { "sign-type", 0, 0, G_OPTION_ARG_STRING, &opt_sign_name, "Signature type to use (defaults to 'ed25519')", "NAME"}, +#if defined(HAVE_LIBSODIUM) + { "keys-file", 0, 0, G_OPTION_ARG_STRING, &opt_keysfilename, "Read key(s) from file", "NAME"}, + { "keys-dir", 0, 0, G_OPTION_ARG_STRING, &opt_keysdir, "Redefine system-wide directories with public and revoked keys for verification", "NAME"}, +#endif { NULL } }; @@ -99,6 +117,15 @@ static GOptionEntry list_options[] = { { NULL } }; +static GOptionEntry verify_options[] = { + { "sign-type", 0, 0, G_OPTION_ARG_STRING, &opt_sign_name, "Signature type to use (defaults to 'ed25519')", "NAME"}, +#if defined(HAVE_LIBSODIUM) + { "keys-file", 0, 0, G_OPTION_ARG_STRING, &opt_keysfilename, "Read key(s) from file", "NAME"}, + { "keys-dir", 0, 0, G_OPTION_ARG_STRING, &opt_keysdir, "Redefine system-wide directories with public and revoked keys for verification", "NAME"}, +#endif + { NULL } +}; + static void static_delta_usage (char **argv, gboolean is_error) @@ -326,6 +353,60 @@ ot_static_delta_builtin_generate (int argc, char **argv, OstreeCommandInvocation if (opt_endianness || opt_swap_endianness) g_variant_builder_add (parambuilder, "{sv}", "endianness", g_variant_new_uint32 (endianness)); + if (opt_key_ids || opt_keysfilename) + { + g_autoptr(GPtrArray) key_ids = g_ptr_array_new (); + + for (char **iter = opt_key_ids; iter != NULL && *iter != NULL; ++iter) + g_ptr_array_add (key_ids, *iter); + + if (opt_keysfilename) + { + g_autoptr (GFile) keyfile = NULL; + g_autoptr (GFileInputStream) key_stream_in = NULL; + g_autoptr (GDataInputStream) key_data_in = NULL; + + if (!g_file_test (opt_keysfilename, G_FILE_TEST_IS_REGULAR)) + { + g_warning ("Can't open file '%s' with keys", opt_keysfilename); + return glnx_throw (error, "File object '%s' is not a regular file", opt_keysfilename); + } + + keyfile = g_file_new_for_path (opt_keysfilename); + key_stream_in = g_file_read (keyfile, NULL, error); + if (key_stream_in == NULL) + return FALSE; + + key_data_in = g_data_input_stream_new (G_INPUT_STREAM(key_stream_in)); + g_assert (key_data_in != NULL); + + /* Use simple file format with just a list of base64 public keys per line */ + while (TRUE) + { + gsize len = 0; + g_autofree char *line = g_data_input_stream_read_line (key_data_in, &len, NULL, error); + g_autoptr (GVariant) sk = NULL; + + if (*error != NULL) + return FALSE; + + if (line == NULL) + break; + + // Pass the key as a string + g_ptr_array_add (key_ids, g_strdup (line)); + } + } + + g_autoptr(GVariant) key_ids_v = g_variant_new_strv ((const char *const *)key_ids->pdata, + key_ids->len); + g_variant_builder_add (parambuilder, "{s@v}", "sign-key-ids", + g_variant_new_variant (g_steal_pointer (&key_ids_v))); + } + opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519; + g_variant_builder_add (parambuilder, "{sv}", "sign-name", + g_variant_new_bytestring (opt_sign_name)); + g_print ("Generating static delta:\n"); g_print (" From: %s\n", from_resolved ? from_resolved : "empty"); g_print (" To: %s\n", to_resolved); @@ -347,6 +428,9 @@ ot_static_delta_builtin_apply_offline (int argc, char **argv, OstreeCommandInvoc { g_autoptr(GOptionContext) context = NULL; g_autoptr(OstreeRepo) repo = NULL; + g_autoptr (OstreeSign) sign = NULL; + char **key_ids; + int n_key_ids; context = g_option_context_new (""); if (!ostree_option_context_parse (context, apply_offline_options, &argc, &argv, invocation, &repo, cancellable, error)) @@ -362,13 +446,59 @@ ot_static_delta_builtin_apply_offline (int argc, char **argv, OstreeCommandInvoc return FALSE; } +#if defined(HAVE_LIBSODIUM) + /* Initialize crypto system */ + opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519; +#endif + + if (opt_sign_name) + { + sign = ostree_sign_get_by_name (opt_sign_name, error); + if (!sign) + return glnx_throw (error, "Signing type %s is not supported", opt_sign_name); + + key_ids = argv + 3; + n_key_ids = argc - 3; + for (int i = 0; i < n_key_ids; i++) + { + g_autoptr (GVariant) pk = g_variant_new_string(key_ids[i]); + if (!ostree_sign_add_pk(sign, pk, error)) + return FALSE; + } + if ((n_key_ids == 0) || opt_keysfilename) + { + g_autoptr (GVariantBuilder) builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + g_autoptr (GVariant) options = NULL; + + /* Use custom directory with public and revoked keys instead of system-wide directories */ + if (opt_keysdir) + g_variant_builder_add (builder, "{sv}", "basedir", g_variant_new_string (opt_keysdir)); + /* The last chance for verification source -- system files */ + if (opt_keysfilename) + g_variant_builder_add (builder, "{sv}", "filename", g_variant_new_string (opt_keysfilename)); + options = g_variant_builder_end (builder); + + if (!ostree_sign_load_pk (sign, options, error)) + { + /* If it fails to load system default public keys, consider there no signature engine */ + if (!opt_keysdir && !opt_keysfilename) + { + g_clear_error(error); + g_clear_object(&sign); + } + else + return FALSE; + } + } + } + const char *patharg = argv[2]; g_autoptr(GFile) path = g_file_new_for_path (patharg); if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) return FALSE; - if (!ostree_repo_static_delta_execute_offline (repo, path, FALSE, cancellable, error)) + if (!ostree_repo_static_delta_execute_offline_with_signature (repo, path, sign, FALSE, cancellable, error)) return FALSE; if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error)) @@ -377,6 +507,68 @@ ot_static_delta_builtin_apply_offline (int argc, char **argv, OstreeCommandInvoc return TRUE; } +static gboolean +ot_static_delta_builtin_verify (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) +{ + g_autoptr (GOptionContext) context = g_option_context_new ("STATIC-DELTA-FILE [KEY-ID...]"); + g_autoptr (OstreeRepo) repo = NULL; + gboolean verified; + char **key_ids; + int n_key_ids; + + if (!ostree_option_context_parse (context, verify_options, &argc, &argv, invocation, &repo, cancellable, error)) + return FALSE; + + if (argc < 3) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "DELTA must be specified"); + return FALSE; + } + + opt_sign_name = opt_sign_name ?: OSTREE_SIGN_NAME_ED25519; + + const char *delta_id = argv[2]; + + g_autoptr (OstreeSign) sign = ostree_sign_get_by_name (opt_sign_name, error); + if (!sign) + { + g_print("Sign-type not supported\n"); + return FALSE; + } + + key_ids = argv + 3; + n_key_ids = argc - 3; + for (int i = 0; i < n_key_ids; i++) + { + g_autoptr (GVariant) pk = g_variant_new_string(key_ids[i]); + if (!ostree_sign_add_pk(sign, pk, error)) + return FALSE; + } + if ((n_key_ids == 0) || opt_keysfilename) + { + g_autoptr (GVariantBuilder) builder = NULL; + g_autoptr (GVariant) options = NULL; + + builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + /* Use custom directory with public and revoked keys instead of system-wide directories */ + if (opt_keysdir) + g_variant_builder_add (builder, "{sv}", "basedir", g_variant_new_string (opt_keysdir)); + /* The last chance for verification source -- system files */ + if (opt_keysfilename) + g_variant_builder_add (builder, "{sv}", "filename", g_variant_new_string (opt_keysfilename)); + options = g_variant_builder_end (builder); + + if (!ostree_sign_load_pk (sign, options, error)) + return FALSE; + } + + verified = ostree_repo_static_delta_verify_signature (repo, delta_id, sign, NULL, error); + g_print ("Verification %s\n", verified ? "OK" : "fails"); + + return verified; +} + gboolean ostree_builtin_static_delta (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { diff --git a/src/ostree/ot-dump.c b/src/ostree/ot-dump.c index 38f3730..a8ed54a 100644 --- a/src/ostree/ot-dump.c +++ b/src/ostree/ot-dump.c @@ -114,6 +114,7 @@ dump_commit (GVariant *variant, const gchar *subject; const gchar *body; guint64 timestamp; + g_autofree char *parent = NULL; g_autofree char *str = NULL; g_autofree char *version = NULL; g_autoptr(GError) local_error = NULL; @@ -129,6 +130,12 @@ dump_commit (GVariant *variant, g_assert (local_error); /* Pacify static analysis */ errx (1, "Failed to read commit: %s", local_error->message); } + + if ((parent = ostree_commit_get_parent(variant))) + { + g_print ("Parent: %s\n", parent); + } + g_autofree char *contents = ostree_commit_get_content_checksum (variant) ?: ""; g_print ("ContentChecksum: %s\n", contents); g_print ("Date: %s\n", str); @@ -315,10 +322,11 @@ ot_dump_summary_bytes (GBytes *summary_bytes, collection_map = g_variant_lookup_value (exts, OSTREE_SUMMARY_COLLECTION_MAP, G_VARIANT_TYPE ("a{sa(s(taya{sv}))}")); if (collection_map != NULL) { + g_autoptr(GVariant) collection_refs = NULL; g_variant_iter_init (&iter, collection_map); - while (g_variant_iter_loop (&iter, "{&s@a(s(taya{sv}))}", &collection_id, &refs)) - dump_summary_refs (collection_id, refs); + while (g_variant_iter_loop (&iter, "{&s@a(s(taya{sv}))}", &collection_id, &collection_refs)) + dump_summary_refs (collection_id, collection_refs); } /* Print out the additional metadata. */ @@ -354,6 +362,22 @@ ot_dump_summary_bytes (GBytes *summary_bytes, pretty_key = "Collection Map"; value_str = g_strdup ("(printed above)"); } + else if (g_strcmp0 (key, OSTREE_SUMMARY_MODE) == 0) + { + OstreeRepoMode repo_mode; + const char *repo_mode_str = g_variant_get_string (value, NULL); + + pretty_key = "Repository Mode"; + if (!ostree_repo_mode_from_string (repo_mode_str, &repo_mode, NULL)) + value_str = g_strdup_printf ("Invalid (‘%s’)", repo_mode_str); + else + value_str = g_strdup (repo_mode_str); + } + else if (g_strcmp0 (key, OSTREE_SUMMARY_TOMBSTONE_COMMITS) == 0) + { + pretty_key = "Has Tombstone Commits"; + value_str = g_strdup (g_variant_get_boolean (value) ? "Yes" : "No"); + } else { value_str = g_variant_print (value, FALSE); diff --git a/src/ostree/ot-remote-builtin-add.c b/src/ostree/ot-remote-builtin-add.c index 172625d..61539ec 100644 --- a/src/ostree/ot-remote-builtin-add.c +++ b/src/ostree/ot-remote-builtin-add.c @@ -104,7 +104,6 @@ ot_remote_builtin_add (int argc, char **argv, OstreeCommandInvocation *invocatio g_autoptr(GString) sign_verify = NULL; const char *remote_name; const char *remote_url; - char **iter; g_autoptr(GVariantBuilder) optbuilder = NULL; g_autoptr(GVariant) options = NULL; gboolean ret = FALSE; @@ -161,7 +160,7 @@ ot_remote_builtin_add (int argc, char **argv, OstreeCommandInvocation *invocatio g_variant_builder_add (optbuilder, "{s@v}", "contenturl", g_variant_new_variant (g_variant_new_string (opt_contenturl))); - for (iter = opt_set; iter && *iter; iter++) + for (char **iter = opt_set; iter && *iter; iter++) { const char *keyvalue = *iter; g_autofree char *subkey = NULL; diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c index 8a68e1f..6351bab 100644 --- a/src/switchroot/ostree-prepare-root.c +++ b/src/switchroot/ostree-prepare-root.c @@ -101,10 +101,9 @@ sysroot_is_configured_ro (const char *sysroot) bool ret = false; char *line = NULL; size_t len = 0; - ssize_t nread; /* Note getline() will reuse the previous buffer */ bool in_sysroot = false; - while ((nread = getline (&line, &len, f)) != -1) + while (getline (&line, &len, f) != -1) { /* This is an awful hack to avoid depending on GLib in the * initramfs right now. @@ -252,7 +251,7 @@ main(int argc, char *argv[]) * sysroot, we still need a writable /etc. And to avoid race conditions * we ensure it's writable in the initramfs, before we switchroot at all. */ - if (mount ("/etc", "/etc", NULL, MS_BIND, NULL) < 0) + if (mount ("etc", "etc", NULL, MS_BIND, NULL) < 0) err (EXIT_FAILURE, "failed to make /etc a bind mount"); /* Pass on the fact that we discovered a readonly sysroot to ostree-remount.service */ int fd = open (_OSTREE_SYSROOT_READONLY_STAMP, O_WRONLY | O_CREAT | O_CLOEXEC, 0644); diff --git a/src/switchroot/ostree-remount.c b/src/switchroot/ostree-remount.c index 5c313c8..3981682 100644 --- a/src/switchroot/ostree-remount.c +++ b/src/switchroot/ostree-remount.c @@ -106,11 +106,16 @@ main(int argc, char *argv[]) exit (EXIT_SUCCESS); } - /* Handle remounting /sysroot read-only now */ - if (unlink (_OSTREE_SYSROOT_READONLY_STAMP) == 0) - { - do_remount ("/sysroot", false); - } + /* Handle remounting /sysroot; if it's explicitly marked as read-only (opt in) + * then ensure it's readonly, otherwise mount writable, the same as / + */ + bool sysroot_configured_readonly = unlink (_OSTREE_SYSROOT_READONLY_STAMP) == 0; + do_remount ("/sysroot", !sysroot_configured_readonly); + + /* And also make sure to make /etc rw again. We make this conditional on + * sysroot_configured_readonly because only in that case is it a bind-mount. */ + if (sysroot_configured_readonly) + 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. diff --git a/tests/basic-test.sh b/tests/basic-test.sh index fc193f4..9227b0c 100644 --- a/tests/basic-test.sh +++ b/tests/basic-test.sh @@ -750,15 +750,17 @@ rm files -rf && mkdir files touch files/anemptyfile touch files/anotheremptyfile $CMD_PREFIX ostree --repo=repo commit --consume -b tree-with-empty-files --tree=dir=files -$CMD_PREFIX ostree --repo=repo checkout ${CHECKOUT_H_ARGS} -z tree-with-empty-files tree-with-empty-files +$CMD_PREFIX ostree --repo=repo checkout ${CHECKOUT_H_ARGS} tree-with-empty-files tree-with-empty-files if files_are_hardlinked tree-with-empty-files/an{,other}emptyfile; then fatal "--force-copy-zerosized failed" fi +# And pass the now-defunct -z option to validate it does nothing rm tree-with-empty-files -rf -$CMD_PREFIX ostree --repo=repo checkout ${CHECKOUT_H_ARGS} tree-with-empty-files tree-with-empty-files -assert_files_hardlinked tree-with-empty-files/an{,other}emptyfile -rm tree-with-empty-files -rf -echo "ok checkout --force-copy-zerosized" +$CMD_PREFIX ostree --repo=repo checkout ${CHECKOUT_H_ARGS} -z tree-with-empty-files tree-with-empty-files +if files_are_hardlinked tree-with-empty-files/an{,other}emptyfile; then + fatal "--force-copy-zerosized failed" +fi +echo "ok checkout zero sized files are not hardlinked" # These should merge, they're identical $CMD_PREFIX ostree --repo=repo checkout ${CHECKOUT_H_ARGS} --union-identical -z tree-with-empty-files tree-with-empty-files diff --git a/tests/bootloader-entries-crosscheck.py b/tests/bootloader-entries-crosscheck.py index 41f6956..605bd08 100755 --- a/tests/bootloader-entries-crosscheck.py +++ b/tests/bootloader-entries-crosscheck.py @@ -73,36 +73,56 @@ with open(syslinuxpath) as f: syslinux_entry = None syslinux_default = None for line in f: - line = line.strip() - if line.startswith('DEFAULT '): + try: + k, v = line.strip().split(" ", 1) + except ValueError: + continue + if k == 'DEFAULT': if syslinux_entry is not None: - syslinux_default = line.split(' ', 1)[1] - elif line.startswith('LABEL '): + syslinux_default = v + elif k == 'LABEL': if syslinux_entry is not None: syslinux_entries.append(syslinux_entry) syslinux_entry = {} - syslinux_entry['title'] = line.split(' ', 1)[1] - elif line.startswith('KERNEL '): - syslinux_entry['linux'] = line.split(' ', 1)[1] - elif line.startswith('INITRD '): - syslinux_entry['initrd'] = line.split(' ', 1)[1] - elif line.startswith('APPEND '): - syslinux_entry['options'] = line.split(' ', 1)[1] + syslinux_entry['title'] = v + elif k == 'KERNEL': + syslinux_entry['linux'] = v + elif k == 'INITRD': + syslinux_entry['initrd'] = v + elif k == 'APPEND': + syslinux_entry['options'] = v if syslinux_entry is not None: syslinux_entries.append(syslinux_entry) if len(entries) != len(syslinux_entries): fatal("Found {0} loader entries, but {1} SYSLINUX entries\n".format(len(entries), len(syslinux_entries))) -def assert_matches_key(a, b, key): + +def assert_eq(a, b): + assert a == b, "%r == %r" % (a, b) + + +def assert_key_same_file(a, b, key): aval = a[key] bval = b[key] - if aval != bval: - fatal("Mismatch on {0}: {1} != {2}".format(key, aval, bval)) + sys.stderr.write("aval: %r\nbval: %r\n" % (aval, bval)) + + # Paths in entries are always relative to /boot + entry = os.stat(sysroot + "/boot" + aval) + + # Syslinux entries can be relative to /boot (if it's on another filesystem) + # or relative to / if /boot is on /. + s1 = os.stat(sysroot + bval) + s2 = os.stat(sysroot + "/boot" + bval) + + # A symlink ensures that no matter what they point at the same file + assert_eq(entry, s1) + assert_eq(entry, s2) + for i,(entry,syslinuxentry) in enumerate(zip(entries, syslinux_entries)): - assert_matches_key(entry, syslinuxentry, 'linux') - assert_matches_key(entry, syslinuxentry, 'initrd') + assert_key_same_file(entry, syslinuxentry, 'linux') + assert_key_same_file(entry, syslinuxentry, 'initrd') entry_ostree = get_ostree_option(entry['options']) syslinux_ostree = get_ostree_option(syslinuxentry['options']) if entry_ostree != syslinux_ostree: diff --git a/tests/libtest.sh b/tests/libtest.sh index ca457fa..7c66a5c 100755 --- a/tests/libtest.sh +++ b/tests/libtest.sh @@ -700,6 +700,12 @@ has_sign_ed25519 () { return ${ret} } +skip_without_sign_ed25519() { + if ! has_sign_ed25519; then + skip "no ed25519 support compiled in" + fi +} + # Keys for ed25519 signing tests ED25519PUBLIC= ED25519SEED= diff --git a/tests/repo-finder-mount.c b/tests/repo-finder-mount.c index 2cb1d23..3d068af 100644 --- a/tests/repo-finder-mount.c +++ b/tests/repo-finder-mount.c @@ -93,16 +93,16 @@ main (int argc, char **argv) g_ptr_array_add (refs, NULL); /* NULL terminator */ - g_autoptr(GAsyncResult) result = NULL; + g_autoptr(GAsyncResult) async_result = NULL; ostree_repo_finder_resolve_async (OSTREE_REPO_FINDER (finder), (const OstreeCollectionRef * const *) refs->pdata, - parent_repo, NULL, result_cb, &result); + parent_repo, NULL, result_cb, &async_result); - while (result == NULL) + while (async_result == NULL) g_main_context_iteration (context, TRUE); g_autoptr(GPtrArray) results = ostree_repo_finder_resolve_finish (OSTREE_REPO_FINDER (finder), - result, &error); + async_result, &error); g_assert_no_error (error); /* Check that the results are correct: the invalid refs should have been diff --git a/tests/test-admin-deploy-2.sh b/tests/test-admin-deploy-2.sh index 0fa2df9..6df4877 100755 --- a/tests/test-admin-deploy-2.sh +++ b/tests/test-admin-deploy-2.sh @@ -26,7 +26,7 @@ set -euo pipefail # Exports OSTREE_SYSROOT so --sysroot not needed. setup_os_repository "archive" "syslinux" -echo "1..7" +echo "1..8" ${CMD_PREFIX} ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmaster/x86_64-runtime rev=$(${CMD_PREFIX} ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime) @@ -102,6 +102,13 @@ ${CMD_PREFIX} ostree admin pin -u 0 assert_n_pinned 0 echo "ok pin unpin" +for p in medal 0medal '' 5000 9999999999999999999999999999999999999; do + if ${CMD_PREFIX} ostree admin pin ${p}; then + fatal "created invalid pin ${p}" + fi +done +echo "ok invalid pin" + ${CMD_PREFIX} ostree admin pin 0 1 assert_n_pinned 2 assert_n_deployments 2 diff --git a/tests/test-basic-user.sh b/tests/test-basic-user.sh index e56f828..fa17bee 100755 --- a/tests/test-basic-user.sh +++ b/tests/test-basic-user.sh @@ -75,6 +75,8 @@ $OSTREE fsck rm test2-checkout -rf $OSTREE checkout -U -H test2-unreadable test2-checkout assert_file_has_mode test2-checkout/unreadable 400 +# Should not be hardlinked +assert_streq $(stat -c "%h" test2-checkout/unreadable) 1 echo "ok bare-user handled unreadable file" cd ${test_tmpdir} diff --git a/tests/test-delta-ed25519.sh b/tests/test-delta-ed25519.sh new file mode 100755 index 0000000..ef732cf --- /dev/null +++ b/tests/test-delta-ed25519.sh @@ -0,0 +1,322 @@ +#!/bin/bash +# +# Copyright (C) 2011,2013 Colin Walters +# +# SPDX-License-Identifier: LGPL-2.0+ +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +set -euo pipefail + +. $(dirname $0)/libtest.sh + +skip_without_user_xattrs + +skip_without_sign_ed25519 + +bindatafiles="bash true ostree" + +echo '1..12' + +mkdir repo +ostree_repo_init repo --mode=archive + +mkdir files +for bin in ${bindatafiles}; do + cp $(which ${bin}) files +done + +${CMD_PREFIX} ostree --repo=repo commit -b test -s test --tree=dir=files + +function permuteFile() { + permutation=$(($1 % 2)) + output=$2 + case $permutation in + 0) dd if=/dev/zero count=40 bs=1 >> $output;; + 1) echo aheader | cat - $output >> $output.new && mv $output.new $output;; + esac +} + +function permuteDirectory() { + permutation=$1 + dir=$2 + for x in ${dir}/*; do + for z in $(seq ${permutation}); do + permuteFile ${z} ${x} + done + done +} + +get_assert_one_direntry_matching() { + local path=$1 + local r=$2 + local child="" + local bn + for p in ${path}/*; do + bn=$(basename $p) + if ! echo ${bn} | grep -q "$r"; then + continue + fi + if test -z "${child}"; then + child=${bn} + else + assert_not_reached "Expected only one child matching ${r} in ${path}"; + fi + done + if test -z "${child}"; then + assert_not_reached "Failed to find child matching ${r}" + fi + echo ${child} +} + +origrev=$(${CMD_PREFIX} ostree --repo=repo rev-parse test) + +permuteDirectory 1 files +${CMD_PREFIX} ostree --repo=repo commit -b test -s test --tree=dir=files + +newrev=$(${CMD_PREFIX} ostree --repo=repo rev-parse test) + +# Test ostree sign with 'ed25519' module +gen_ed25519_keys +PUBLIC=${ED25519PUBLIC} +SEED=${ED25519SEED} +SECRET=${ED25519SECRET} +WRONG_PUBLIC="$(gen_ed25519_random_public)" + +SECRETKEYS="$(mktemp -p ${test_tmpdir} ed25519_XXXXXX.ed25519)" +echo ${SECRET} > ${SECRETKEYS} + +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-key-signed-1.txt +assert_file_has_content show-ed25519-key-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" "${WRONG_PUBLIC}" > show-ed25519-key-signed-2.txt +assert_file_has_content show-ed25519-key-signed-2.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" "${PUBLIC}" > show-ed25519-key-signed-3.txt +assert_file_has_content show-ed25519-key-signed-3.txt "Verification OK" + +deltaprefix=$(get_assert_one_direntry_matching repo/deltas '.') +deltadir=$(get_assert_one_direntry_matching repo/deltas/${deltaprefix} '-') + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-key-inline-signed-1.txt +assert_file_has_content show-ed25519-key-inline-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" "${WRONG_PUBLIC}" > show-ed25519-key-inline-signed-2.txt +assert_file_has_content show-ed25519-key-inline-signed-2.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" "${PUBLIC}" > show-ed25519-key-inline-signed-3.txt +assert_file_has_content show-ed25519-key-inline-signed-3.txt "Verification OK" + +echo 'ok verified with ed25519 (sign - key)' + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-keyfile-signed-1.txt +assert_file_has_content show-ed25519-keyfile-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" "${WRONG_PUBLIC}" > show-ed25519-keyfile-signed-2.txt +assert_file_has_content show-ed25519-keyfile-signed-2.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" "${PUBLIC}" > show-ed25519-keyfile-signed-3.txt +assert_file_has_content show-ed25519-keyfile-signed-3.txt "Verification OK" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-keyfile-inline-signed-1.txt +assert_file_has_content show-ed25519-keyfile-inline-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" "${WRONG_PUBLIC}" > show-ed25519-keyfile-inline-signed-2.txt +assert_file_has_content show-ed25519-keyfile-inline-signed-2.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" "${PUBLIC}" > show-ed25519-keyfile-inline-signed-3.txt +assert_file_has_content show-ed25519-keyfile-inline-signed-3.txt "Verification OK" + +echo 'ok verified with ed25519 (keyfile - key)' + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-key-bad-signed.txt && exit 1 +assert_file_has_content show-ed25519-key-bad-signed.txt "Verification fails" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-key-bad-inline-signed.txt && exit 1 +assert_file_has_content show-ed25519-key-bad-inline-signed.txt "Verification fails" + +echo 'ok Verification fails with ed25519 (sign - bad key)' + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-keyfile-bad-signed.txt && exit 1 +assert_file_has_content show-ed25519-keyfile-bad-signed.txt "Verification fails" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-keyfile-bad-inline-signed.txt && exit 1 +assert_file_has_content show-ed25519-keyfile-bad-inline-signed.txt "Verification fails" + +echo 'ok Verification fails with ed25519 (keyfile - bad key)' + +# Prepare files with public ed25519 signatures +PUBKEYS="$(mktemp -p ${test_tmpdir} ed25519_XXXXXX.ed25519)" +for((i=0;i<100;i++)); do + # Generate a list with some public signatures + gen_ed25519_random_public +done > ${PUBKEYS} + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-bad-signed-1.txt && exit 1 +assert_file_has_content show-ed25519-file-bad-signed-1.txt "Verification fails" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-bad-signed-2.txt && exit 1 +assert_file_has_content show-ed25519-file-bad-signed-2.txt "Verification fails" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-inline-bad-signed-1.txt && exit 1 +assert_file_has_content show-ed25519-file-inline-bad-signed-1.txt "Verification fails" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-inline-bad-signed-2.txt && exit 1 +assert_file_has_content show-ed25519-file-inline-bad-signed-2.txt "Verification fails" + +echo 'ok Verification fails with ed25519 (sign - bad keys)' + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-bad-signed-3.txt && exit 1 +assert_file_has_content show-ed25519-file-bad-signed-3.txt "Verification fails" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-bad-signed-4.txt && exit 1 +assert_file_has_content show-ed25519-file-bad-signed-4.txt "Verification fails" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-inline-bad-signed-3.txt && exit 1 +assert_file_has_content show-ed25519-file-inline-bad-signed-3.txt "Verification fails" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-inline-bad-signed-4.txt && exit 1 +assert_file_has_content show-ed25519-file-inline-bad-signed-4.txt "Verification fails" + +echo 'ok Verification fails with ed25519 (keyfile - bad keys)' + +# Add correct key into the list +echo ${PUBLIC} >> ${PUBKEYS} + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-signed-1.txt +assert_file_has_content show-ed25519-file-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-signed-2.txt +assert_file_has_content show-ed25519-file-signed-2.txt "Verification OK" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --sign=${SECRET} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-inline-signed-1.txt +assert_file_has_content show-ed25519-file-inline-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-inline-signed-2.txt +assert_file_has_content show-ed25519-file-inline-signed-2.txt "Verification OK" + +echo 'ok verified with ed25519 (sign - file)' + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-signed-3.txt +assert_file_has_content show-ed25519-file-signed-3.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-signed-4.txt +assert_file_has_content show-ed25519-file-signed-4.txt "Verification OK" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-file-inline-signed-3.txt +assert_file_has_content show-ed25519-file-inline-signed-3.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-file-inline-signed-4.txt +assert_file_has_content show-ed25519-file-inline-signed-4.txt "Verification OK" + +echo 'ok verified with ed25519 (keyfile - file)' + +# Test ostree sign with multiple 'ed25519' keys +gen_ed25519_keys +PUBLIC2=${ED25519PUBLIC} +SEED2=${ED25519SEED} +SECRET2=${ED25519SECRET} + +echo ${SECRET2} >> ${SECRETKEYS} +echo ${PUBLIC2} >> ${PUBKEYS} + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-multiplekeys-signed-1.txt +assert_file_has_content show-ed25519-multiplekeys-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC2}" > show-ed25519-multiplekeys-signed-2.txt +assert_file_has_content show-ed25519-multiplekeys-signed-2.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-multiplekeys-bad-signed.txt && exit 1 +assert_file_has_content show-ed25519-multiplekeys-bad-signed.txt "Verification fails" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-multiplekeys-signed-3.txt +assert_file_has_content show-ed25519-multiplekeys-signed-3.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-multiplekeys-signed-4.txt +assert_file_has_content show-ed25519-multiplekeys-signed-4.txt "Verification OK" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC}" > show-ed25519-multiplekeys-inline-signed-1.txt +assert_file_has_content show-ed25519-multiplekeys-inline-signed-1.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${PUBLIC2}" > show-ed25519-multiplekeys-inline-signed-2.txt +assert_file_has_content show-ed25519-multiplekeys-inline-signed-2.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} "${WRONG_PUBLIC}" > show-ed25519-multiplekeys-bad-inline-signed.txt && exit 1 +assert_file_has_content show-ed25519-multiplekeys-bad-inline-signed.txt "Verification fails" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=ed25519 --keys-file=${SECRETKEYS} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} > show-ed25519-multiplekeys-inline-signed-3.txt +assert_file_has_content show-ed25519-multiplekeys-inline-signed-3.txt "Verification OK" +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=ed25519 ${origrev}-${newrev} --keys-file=${PUBKEYS} "${WRONG_PUBLIC}" > show-ed25519-multiplekeys-inline-signed-4.txt +assert_file_has_content show-ed25519-multiplekeys-inline-signed-4.txt "Verification OK" + +echo 'ok verified with ed25519 (multiple keys)' + +rm -rf repo2 +ostree_repo_init repo2 --mode=bare-user + +${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev} +${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null +${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline --sign-type=ed25519 --keys-file=${PUBKEYS} repo/deltas/${deltaprefix}/${deltadir} +${CMD_PREFIX} ostree --repo=repo2 fsck +${CMD_PREFIX} ostree --repo=repo2 ls ${newrev} >/dev/null + +echo 'ok apply offline with ed25519 (keyfile)' + +mkdir -p ${test_tmpdir}/{trusted,revoked}.ed25519.d + +rm -rf repo2 +ostree_repo_init repo2 --mode=bare-user + +echo ${PUBLIC} > ${test_tmpdir}/trusted.ed25519.d/correct +${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev} +${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null +${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline --keys-dir=${test_tmpdir} repo/deltas/${deltaprefix}/${deltadir} +${CMD_PREFIX} ostree --repo=repo2 fsck +${CMD_PREFIX} ostree --repo=repo2 ls ${newrev} >/dev/null + +echo 'ok apply offline with ed25519 (keydir)' + +rm -rf repo2 +ostree_repo_init repo2 --mode=bare-user + +echo ${PUBLIC} > ${test_tmpdir}/revoked.ed25519.d/correct +${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev} +${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null +if ${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline --keys-dir=${test_tmpdir} repo/deltas/${deltaprefix}/${deltadir}; then + exit 1 +fi + +rm -rf ${test_tmpdir}/{trusted,revoked}.ed25519.d + +echo 'ok apply offline with ed25519 revoking key mechanism (keydir)' diff --git a/tests/test-delta-sign.sh b/tests/test-delta-sign.sh new file mode 100755 index 0000000..86f12f9 --- /dev/null +++ b/tests/test-delta-sign.sh @@ -0,0 +1,174 @@ +#!/bin/bash +# +# Copyright (C) 2011,2013 Colin Walters +# +# SPDX-License-Identifier: LGPL-2.0+ +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +set -euo pipefail + +. $(dirname $0)/libtest.sh + +skip_without_user_xattrs + +bindatafiles="bash true ostree" + +echo '1..7' + +# This is explicitly opt in for testing +export OSTREE_DUMMY_SIGN_ENABLED=1 + +mkdir repo +ostree_repo_init repo --mode=archive + +mkdir files +for bin in ${bindatafiles}; do + cp $(which ${bin}) files +done + +${CMD_PREFIX} ostree --repo=repo commit -b test -s test --tree=dir=files + +function permuteFile() { + permutation=$(($1 % 2)) + output=$2 + case $permutation in + 0) dd if=/dev/zero count=40 bs=1 >> $output;; + 1) echo aheader | cat - $output >> $output.new && mv $output.new $output;; + esac +} + +function permuteDirectory() { + permutation=$1 + dir=$2 + for x in ${dir}/*; do + for z in $(seq ${permutation}); do + permuteFile ${z} ${x} + done + done +} + +get_assert_one_direntry_matching() { + local path=$1 + local r=$2 + local child="" + local bn + for p in ${path}/*; do + bn=$(basename $p) + if ! echo ${bn} | grep -q "$r"; then + continue + fi + if test -z "${child}"; then + child=${bn} + else + assert_not_reached "Expected only one child matching ${r} in ${path}"; + fi + done + if test -z "${child}"; then + assert_not_reached "Failed to find child matching ${r}" + fi + echo ${child} +} + +origrev=$(${CMD_PREFIX} ostree --repo=repo rev-parse test) + +permuteDirectory 1 files +${CMD_PREFIX} ostree --repo=repo commit -b test -s test --tree=dir=files + +newrev=$(${CMD_PREFIX} ostree --repo=repo rev-parse test) + +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} dummysign > show-not-signed.txt 2>&1 && exit 1 +assert_file_has_content show-not-signed.txt "Verification fails" +assert_file_has_content show-not-signed.txt "no signatures in static-delta" + +deltaprefix=$(get_assert_one_direntry_matching repo/deltas '.') +deltadir=$(get_assert_one_direntry_matching repo/deltas/${deltaprefix} '-') + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} dummysign > show-inline-not-signed.txt 2>&1 && exit 1 +assert_file_has_content show-not-signed.txt "Verification fails" +assert_file_has_content show-not-signed.txt "no signatures in static-delta" + +echo 'ok verify ok with unsigned deltas' + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=dummy --sign=dummysign +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} dummysign > show-dummy-signed.txt +assert_file_has_content show-dummy-signed.txt "Verification OK" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=dummy --sign=dummysign +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} dummysign > show-dummy-inline-signed.txt +assert_file_has_content show-dummy-inline-signed.txt "Verification OK" + +echo 'ok verified with dummy' + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --sign-type=dummy --sign=dummysign +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} badsign > show-dummy-bad-signed.txt && exit 1 +assert_file_has_content show-dummy-bad-signed.txt "Verification fails" + +rm -rf repo/deltas/${deltaprefix}/${deltadir}/* +${CMD_PREFIX} ostree --repo=repo static-delta generate --from=${origrev} --to=${newrev} --inline --sign-type=dummy --sign=dummysign +${CMD_PREFIX} ostree --repo=repo static-delta verify --sign-type=dummy ${origrev}-${newrev} badsign > show-dummy-bad-inline-signed.txt && exit 1 +assert_file_has_content show-dummy-bad-inline-signed.txt "Verification fails" + +echo 'ok verification failed with dummy and bad key' + +rm -rf repo2 +ostree_repo_init repo2 --mode=bare-user + +${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev} +${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null +${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline repo/deltas/${deltaprefix}/${deltadir} +${CMD_PREFIX} ostree --repo=repo2 fsck +${CMD_PREFIX} ostree --repo=repo2 ls ${newrev} >/dev/null + +echo 'ok apply offline with no signature verification and no key' + +rm -rf repo2 +ostree_repo_init repo2 --mode=bare-user + +${CMD_PREFIX} ostree --repo=repo2 config set core.sign-verify-deltas true +${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev} +${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null +${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline repo/deltas/${deltaprefix}/${deltadir} 2> apply-offline-verification-no-key.txt && exit 1 +assert_file_has_content apply-offline-verification-no-key.txt "Key is mandatory to check delta signature" + +echo 'ok apply offline failed with signature verification forced and no key' + +rm -rf repo2 +ostree_repo_init repo2 --mode=bare-user + +${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev} +${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null +${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline --sign-type=dummy repo/deltas/${deltaprefix}/${deltadir} dummysign +${CMD_PREFIX} ostree --repo=repo2 fsck +${CMD_PREFIX} ostree --repo=repo2 ls ${newrev} >/dev/null + +echo 'ok apply offline with dummy' + +rm -rf repo2 +ostree_repo_init repo2 --mode=bare-user + +${CMD_PREFIX} ostree --repo=repo2 pull-local repo ${origrev} +${CMD_PREFIX} ostree --repo=repo2 ls ${origrev} >/dev/null +${CMD_PREFIX} ostree --repo=repo2 static-delta apply-offline --sign-type=dummy repo/deltas/${deltaprefix}/${deltadir} badsign 2> apply-offline-bad-key.txt && exit 1 +assert_file_has_content apply-offline-bad-key.txt "signature: dummy: incorrect signature" + +echo 'ok apply offline failed with dummy and bad key' diff --git a/tests/test-libarchive.sh b/tests/test-libarchive.sh index 174be80..73a58dd 100755 --- a/tests/test-libarchive.sh +++ b/tests/test-libarchive.sh @@ -37,7 +37,7 @@ mkdir foo cd foo mkdir -p usr/bin usr/lib echo contents > usr/bin/foo -touch usr/bin/foo0 +echo foo0 > usr/bin/foo0 ln usr/bin/foo usr/bin/bar ln usr/bin/foo0 usr/bin/bar0 ln -s foo usr/bin/sl @@ -45,8 +45,8 @@ mkdir -p usr/local/bin ln usr/bin/foo usr/local/bin/baz ln usr/bin/foo0 usr/local/bin/baz0 ln usr/bin/sl usr/local/bin/slhl -touch usr/bin/setuidme -touch usr/bin/skipme +echo setuidme > usr/bin/setuidme +echo skipme > usr/bin/skipme echo "a library" > usr/lib/libfoo.so echo "another library" > usr/lib/libbar.so @@ -102,9 +102,9 @@ assert_valid_content () { assert_file_has_content usr/bin/foo contents assert_file_has_content usr/bin/bar contents assert_file_has_content usr/local/bin/baz contents - assert_file_empty usr/bin/foo0 - assert_file_empty usr/bin/bar0 - assert_file_empty usr/local/bin/baz0 + assert_file_has_content usr/bin/foo0 foo0 + assert_file_has_content usr/bin/bar0 foo0 + assert_file_has_content usr/local/bin/baz0 foo0 assert_file_has_content usr/lib/libfoo.so 'a library' assert_file_has_content usr/lib/libbar.so 'another library' @@ -244,7 +244,7 @@ ${CMD_PREFIX} ostree --repo=repo2 commit \ --generate-sizes \ --tree=tar=foo.tar.gz ${CMD_PREFIX} ostree --repo=repo2 show --print-sizes test-tar > sizes.txt -assert_file_has_content sizes.txt 'Compressed size (needed/total): 0[  ]bytes/1.1[  ]kB' -assert_file_has_content sizes.txt 'Unpacked size (needed/total): 0[  ]bytes/900[  ]bytes' -assert_file_has_content sizes.txt 'Number of objects (needed/total): 0/12' +assert_file_has_content sizes.txt 'Compressed size (needed/total): 0[  ]bytes/1.2[  ]kB' +assert_file_has_content sizes.txt 'Unpacked size (needed/total): 0[  ]bytes/921[  ]bytes' +assert_file_has_content sizes.txt 'Number of objects (needed/total): 0/14' echo "ok tar sizes metadata" diff --git a/tests/test-osupdate-dtb.sh b/tests/test-osupdate-dtb.sh new file mode 100755 index 0000000..9e0c468 --- /dev/null +++ b/tests/test-osupdate-dtb.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# +# Copyright (C) 2020 Red Hat, Inc. +# +# SPDX-License-Identifier: LGPL-2.0+ +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +set -euo pipefail + +. $(dirname $0)/libtest.sh + +echo "1..1" + +# Exports OSTREE_SYSROOT so --sysroot not needed. +kver="3.6.0" +modulesdir="usr/lib/modules/${kver}" +setup_os_repository "archive" "syslinux" ${modulesdir} + +cd ${test_tmpdir} +os_repository_new_commit "test" "test with device tree directory" + +devicetree_path=osdata/${modulesdir}/dtb/asoc-board.dtb +devicetree_overlay_path=osdata/${modulesdir}/dtb/overlays/overlay.dtbo + +mkdir -p osdata/${modulesdir}/dtb +echo "a device tree" > ${devicetree_path} +mkdir -p osdata/${modulesdir}/dtb/overlays +echo "a device tree overlay" > ${devicetree_overlay_path} + +${CMD_PREFIX} ostree --repo=testos-repo commit --tree=dir=osdata/ -b testos/buildmaster/x86_64-runtime +${CMD_PREFIX} ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmaster/x86_64-runtime +${CMD_PREFIX} ostree --repo=sysroot/ostree/repo remote add --set=gpg-verify=false testos file://$(pwd)/testos-repo testos/buildmaster/x86_64-runtime +${CMD_PREFIX} env OSTREE_SYSROOT_DEBUG=${OSTREE_SYSROOT_DEBUG},no-dtb ostree admin deploy --os=testos testos:testos/buildmaster/x86_64-runtime +assert_has_file sysroot/boot/ostree/testos-${bootcsum}/vmlinuz-3.6.0 +assert_not_has_file sysroot/boot/ostree/testos-${bootcsum}/dtb/asoc-board.dtb 'a device tree' +assert_streq $(ls sysroot/boot/ostree | wc -l) 1 +assert_streq $(find sysroot/boot/ostree -name '*.dtb' | wc -l) 0 +${CMD_PREFIX} ostree --repo=testos-repo commit --tree=dir=osdata/ -b testos/buildmaster/x86_64-runtime +env OSTREE_SYSROOT_DEBUG=${OSTREE_SYSROOT_DEBUG},no-dtb ${CMD_PREFIX} ostree admin upgrade --os=testos +${CMD_PREFIX} ostree --repo=testos-repo commit --tree=dir=osdata/ -b testos/buildmaster/x86_64-runtime +${CMD_PREFIX} ostree admin upgrade --os=testos +assert_streq $(ls sysroot/boot/ostree | wc -l) 2 +# Note that the bootcsum computed by the test suite doesn't include devicetree +# And currently we end up installing the dtb for the *previous* deployment +# too which is a bug - in the future this should be fixed to assert 1. +assert_streq $(find sysroot/boot/ostree -name '*.dtb' | wc -l) 2 + +echo "ok update with no dtb to dtb" diff --git a/tests/test-repo-finder-config.c b/tests/test-repo-finder-config.c index 0a2e9e6..b3e8a26 100644 --- a/tests/test-repo-finder-config.c +++ b/tests/test-repo-finder-config.c @@ -173,6 +173,9 @@ assert_create_remote (Fixture *fixture, glnx_shutil_mkdir_p_at (fixture->tmpdir.fd, repo_name, 0700, NULL, &error); g_assert_no_error (error); + glnx_shutil_mkdir_p_at (fixture->tmpdir.fd, "empty", 0700, NULL, &error); + g_assert_no_error (error); + g_autoptr(GFile) repo_path = g_file_get_child (fixture->working_dir, repo_name); g_autoptr(OstreeRepo) repo = ostree_repo_new (repo_path); ostree_repo_set_collection_id (repo, collection_id, &error); @@ -193,7 +196,7 @@ assert_create_remote (Fixture *fixture, g_autoptr(OstreeRepoFile) repo_file = NULL; mtree = ostree_mutable_tree_new (); - ostree_repo_write_dfd_to_mtree (repo, AT_FDCWD, ".", mtree, NULL, NULL, &error); + ostree_repo_write_dfd_to_mtree (repo, fixture->tmpdir.fd, "empty", mtree, NULL, NULL, &error); g_assert_no_error (error); ostree_repo_write_mtree (repo, mtree, (GFile **) &repo_file, NULL, &error); g_assert_no_error (error); @@ -227,7 +230,7 @@ test_repo_finder_config_mixed_configs (Fixture *fixture, { g_autoptr(OstreeRepoFinderConfig) finder = NULL; g_autoptr(GMainContext) context = NULL; - g_autoptr(GAsyncResult) result = NULL; + g_autoptr(GAsyncResult) async_result = NULL; g_autoptr(GPtrArray) results = NULL; /* (element-type OstreeRepoFinderResult) */ g_autoptr(GError) error = NULL; gsize i; @@ -263,13 +266,13 @@ test_repo_finder_config_mixed_configs (Fixture *fixture, /* Resolve the refs. */ ostree_repo_finder_resolve_async (OSTREE_REPO_FINDER (finder), refs, - fixture->parent_repo, NULL, result_cb, &result); + fixture->parent_repo, NULL, result_cb, &async_result); - while (result == NULL) + while (async_result == NULL) g_main_context_iteration (context, TRUE); results = ostree_repo_finder_resolve_finish (OSTREE_REPO_FINDER (finder), - result, &error); + async_result, &error); g_assert_no_error (error); g_assert_nonnull (results); g_assert_cmpuint (results->len, ==, 3); diff --git a/tests/test-repo-finder-mount.c b/tests/test-repo-finder-mount.c index af2f5e0..45e58fa 100644 --- a/tests/test-repo-finder-mount.c +++ b/tests/test-repo-finder-mount.c @@ -190,6 +190,9 @@ assert_create_remote_va (Fixture *fixture, ostree_repo_create (repo, OSTREE_REPO_MODE_ARCHIVE, NULL, &error); g_assert_no_error (error); + glnx_shutil_mkdir_p_at (fixture->tmpdir.fd, "empty", 0700, NULL, &error); + g_assert_no_error (error); + /* Set up the refs from @.... */ for (const OstreeCollectionRef *ref = va_arg (args, const OstreeCollectionRef *); ref != NULL; @@ -201,7 +204,7 @@ assert_create_remote_va (Fixture *fixture, gchar **out_checksum = va_arg (args, gchar **); mtree = ostree_mutable_tree_new (); - ostree_repo_write_dfd_to_mtree (repo, AT_FDCWD, ".", mtree, NULL, NULL, &error); + ostree_repo_write_dfd_to_mtree (repo, fixture->tmpdir.fd, "empty", mtree, NULL, NULL, &error); g_assert_no_error (error); ostree_repo_write_mtree (repo, mtree, (GFile **) &repo_file, NULL, &error); g_assert_no_error (error); @@ -314,7 +317,7 @@ test_repo_finder_mount_mixed_mounts (Fixture *fixture, g_autoptr(OstreeRepoFinderMount) finder = NULL; g_autoptr(GVolumeMonitor) monitor = NULL; g_autoptr(GMainContext) context = NULL; - g_autoptr(GAsyncResult) result = NULL; + g_autoptr(GAsyncResult) async_result = NULL; g_autoptr(GPtrArray) results = NULL; /* (element-type OstreeRepoFinderResult) */ g_autoptr(GError) error = NULL; g_autoptr(GList) mounts = NULL; /* (element-type OstreeMockMount) */ @@ -392,13 +395,13 @@ test_repo_finder_mount_mixed_mounts (Fixture *fixture, /* Resolve the refs. */ ostree_repo_finder_resolve_async (OSTREE_REPO_FINDER (finder), refs, fixture->parent_repo, - NULL, result_cb, &result); + NULL, result_cb, &async_result); - while (result == NULL) + while (async_result == NULL) g_main_context_iteration (context, TRUE); results = ostree_repo_finder_resolve_finish (OSTREE_REPO_FINDER (finder), - result, &error); + async_result, &error); g_assert_no_error (error); g_assert_nonnull (results); g_assert_cmpuint (results->len, ==, 4); @@ -463,7 +466,7 @@ test_repo_finder_mount_well_known (Fixture *fixture, g_autoptr(OstreeRepoFinderMount) finder = NULL; g_autoptr(GVolumeMonitor) monitor = NULL; g_autoptr(GMainContext) context = NULL; - g_autoptr(GAsyncResult) result = NULL; + g_autoptr(GAsyncResult) async_result = NULL; g_autoptr(GPtrArray) results = NULL; /* (element-type OstreeRepoFinderResult) */ g_autoptr(GError) error = NULL; g_autoptr(GList) mounts = NULL; /* (element-type OstreeMockMount) */ @@ -504,13 +507,13 @@ test_repo_finder_mount_well_known (Fixture *fixture, /* Resolve the refs. */ ostree_repo_finder_resolve_async (OSTREE_REPO_FINDER (finder), refs, fixture->parent_repo, - NULL, result_cb, &result); + NULL, result_cb, &async_result); - while (result == NULL) + while (async_result == NULL) g_main_context_iteration (context, TRUE); results = ostree_repo_finder_resolve_finish (OSTREE_REPO_FINDER (finder), - result, &error); + async_result, &error); g_assert_no_error (error); g_assert_nonnull (results); g_assert_cmpuint (results->len, ==, 2); diff --git a/tests/test-summary-view.sh b/tests/test-summary-view.sh index 14de029..f6278a8 100755 --- a/tests/test-summary-view.sh +++ b/tests/test-summary-view.sh @@ -64,5 +64,5 @@ echo "ok view summary" ${OSTREE} summary --raw > raw-summary.txt assert_file_has_content_literal raw-summary.txt "('main', (" assert_file_has_content_literal raw-summary.txt "('other', (" -assert_file_has_content_literal raw-summary.txt "{'ostree.summary.last-modified': released-sha256.txt <