diff --git a/Makefile-man.am b/Makefile-man.am index e2f88a16..4d99cde1 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -26,6 +26,7 @@ ostree-admin-config-diff.1 ostree-admin-deploy.1 \ ostree-admin-init-fs.1 ostree-admin-instutil.1 ostree-admin-os-init.1 \ ostree-admin-status.1 ostree-admin-set-origin.1 ostree-admin-switch.1 \ ostree-admin-undeploy.1 ostree-admin-upgrade.1 ostree-admin-unlock.1 \ +ostree-admin-pin.1 \ ostree-admin.1 ostree-cat.1 ostree-checkout.1 ostree-checksum.1 \ ostree-commit.1 ostree-export.1 ostree-gpg-sign.1 ostree-config.1 \ ostree-diff.1 ostree-fsck.1 ostree-init.1 ostree-log.1 ostree-ls.1 \ diff --git a/Makefile-ostree.am b/Makefile-ostree.am index c366c84f..cccbe300 100644 --- a/Makefile-ostree.am +++ b/Makefile-ostree.am @@ -74,6 +74,7 @@ ostree_SOURCES += \ src/ostree/ot-admin-builtin-set-origin.c \ src/ostree/ot-admin-builtin-status.c \ src/ostree/ot-admin-builtin-switch.c \ + src/ostree/ot-admin-builtin-pin.c \ src/ostree/ot-admin-builtin-upgrade.c \ src/ostree/ot-admin-builtin-unlock.c \ src/ostree/ot-admin-builtins.h \ diff --git a/Makefile.in b/Makefile.in index 370b9222..db100456 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1007,6 +1007,7 @@ am__ostree_SOURCES_DIST = src/ostree/main.c \ src/ostree/ot-admin-builtin-set-origin.c \ src/ostree/ot-admin-builtin-status.c \ src/ostree/ot-admin-builtin-switch.c \ + src/ostree/ot-admin-builtin-pin.c \ src/ostree/ot-admin-builtin-upgrade.c \ src/ostree/ot-admin-builtin-unlock.c \ src/ostree/ot-admin-builtins.h \ @@ -1079,6 +1080,7 @@ am_ostree_OBJECTS = src/ostree/ostree-main.$(OBJEXT) \ src/ostree/ostree-ot-admin-builtin-set-origin.$(OBJEXT) \ src/ostree/ostree-ot-admin-builtin-status.$(OBJEXT) \ src/ostree/ostree-ot-admin-builtin-switch.$(OBJEXT) \ + src/ostree/ostree-ot-admin-builtin-pin.$(OBJEXT) \ src/ostree/ostree-ot-admin-builtin-upgrade.$(OBJEXT) \ src/ostree/ostree-ot-admin-builtin-unlock.$(OBJEXT) \ src/ostree/ostree-ot-admin-instutil-builtin-selinux-ensure-labeled.$(OBJEXT) \ @@ -2469,6 +2471,7 @@ ostree_SOURCES = src/ostree/main.c src/ostree/ot-builtin-admin.c \ src/ostree/ot-admin-builtin-set-origin.c \ src/ostree/ot-admin-builtin-status.c \ src/ostree/ot-admin-builtin-switch.c \ + src/ostree/ot-admin-builtin-pin.c \ src/ostree/ot-admin-builtin-upgrade.c \ src/ostree/ot-admin-builtin-unlock.c \ src/ostree/ot-admin-builtins.h \ @@ -2773,7 +2776,8 @@ tests_test_gpg_verify_result_LDADD = $(TESTS_LDADD) $(OT_INTERNAL_GPGME_LIBS) @ENABLE_MAN_TRUE@ ostree-admin-set-origin.1 \ @ENABLE_MAN_TRUE@ ostree-admin-switch.1 ostree-admin-undeploy.1 \ @ENABLE_MAN_TRUE@ ostree-admin-upgrade.1 ostree-admin-unlock.1 \ -@ENABLE_MAN_TRUE@ ostree-admin.1 ostree-cat.1 ostree-checkout.1 \ +@ENABLE_MAN_TRUE@ ostree-admin-pin.1 ostree-admin.1 \ +@ENABLE_MAN_TRUE@ ostree-cat.1 ostree-checkout.1 \ @ENABLE_MAN_TRUE@ ostree-checksum.1 ostree-commit.1 \ @ENABLE_MAN_TRUE@ ostree-export.1 ostree-gpg-sign.1 \ @ENABLE_MAN_TRUE@ ostree-config.1 ostree-diff.1 ostree-fsck.1 \ @@ -3768,6 +3772,9 @@ src/ostree/ostree-ot-admin-builtin-status.$(OBJEXT): \ src/ostree/ostree-ot-admin-builtin-switch.$(OBJEXT): \ src/ostree/$(am__dirstamp) \ src/ostree/$(DEPDIR)/$(am__dirstamp) +src/ostree/ostree-ot-admin-builtin-pin.$(OBJEXT): \ + src/ostree/$(am__dirstamp) \ + src/ostree/$(DEPDIR)/$(am__dirstamp) src/ostree/ostree-ot-admin-builtin-upgrade.$(OBJEXT): \ src/ostree/$(am__dirstamp) \ src/ostree/$(DEPDIR)/$(am__dirstamp) @@ -4419,6 +4426,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-init-fs.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-instutil.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-os-init.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-pin.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-set-origin.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-status.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-switch.Po@am__quote@ @@ -5681,6 +5689,20 @@ src/ostree/ostree-ot-admin-builtin-switch.obj: src/ostree/ot-admin-builtin-switc @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ostree_CFLAGS) $(CFLAGS) -c -o src/ostree/ostree-ot-admin-builtin-switch.obj `if test -f 'src/ostree/ot-admin-builtin-switch.c'; then $(CYGPATH_W) 'src/ostree/ot-admin-builtin-switch.c'; else $(CYGPATH_W) '$(srcdir)/src/ostree/ot-admin-builtin-switch.c'; fi` +src/ostree/ostree-ot-admin-builtin-pin.o: src/ostree/ot-admin-builtin-pin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ostree_CFLAGS) $(CFLAGS) -MT src/ostree/ostree-ot-admin-builtin-pin.o -MD -MP -MF src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-pin.Tpo -c -o src/ostree/ostree-ot-admin-builtin-pin.o `test -f 'src/ostree/ot-admin-builtin-pin.c' || echo '$(srcdir)/'`src/ostree/ot-admin-builtin-pin.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-pin.Tpo src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-pin.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/ostree/ot-admin-builtin-pin.c' object='src/ostree/ostree-ot-admin-builtin-pin.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ostree_CFLAGS) $(CFLAGS) -c -o src/ostree/ostree-ot-admin-builtin-pin.o `test -f 'src/ostree/ot-admin-builtin-pin.c' || echo '$(srcdir)/'`src/ostree/ot-admin-builtin-pin.c + +src/ostree/ostree-ot-admin-builtin-pin.obj: src/ostree/ot-admin-builtin-pin.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ostree_CFLAGS) $(CFLAGS) -MT src/ostree/ostree-ot-admin-builtin-pin.obj -MD -MP -MF src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-pin.Tpo -c -o src/ostree/ostree-ot-admin-builtin-pin.obj `if test -f 'src/ostree/ot-admin-builtin-pin.c'; then $(CYGPATH_W) 'src/ostree/ot-admin-builtin-pin.c'; else $(CYGPATH_W) '$(srcdir)/src/ostree/ot-admin-builtin-pin.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-pin.Tpo src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-pin.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/ostree/ot-admin-builtin-pin.c' object='src/ostree/ostree-ot-admin-builtin-pin.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ostree_CFLAGS) $(CFLAGS) -c -o src/ostree/ostree-ot-admin-builtin-pin.obj `if test -f 'src/ostree/ot-admin-builtin-pin.c'; then $(CYGPATH_W) 'src/ostree/ot-admin-builtin-pin.c'; else $(CYGPATH_W) '$(srcdir)/src/ostree/ot-admin-builtin-pin.c'; fi` + src/ostree/ostree-ot-admin-builtin-upgrade.o: src/ostree/ot-admin-builtin-upgrade.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ostree_CFLAGS) $(CFLAGS) -MT src/ostree/ostree-ot-admin-builtin-upgrade.o -MD -MP -MF src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-upgrade.Tpo -c -o src/ostree/ostree-ot-admin-builtin-upgrade.o `test -f 'src/ostree/ot-admin-builtin-upgrade.c' || echo '$(srcdir)/'`src/ostree/ot-admin-builtin-upgrade.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-upgrade.Tpo src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-upgrade.Po diff --git a/apidoc/html/index.html b/apidoc/html/index.html index 21de5d66..1007a895 100644 --- a/apidoc/html/index.html +++ b/apidoc/html/index.html @@ -14,7 +14,7 @@
-

for OSTree 2018.2

+

for OSTree 2018.4


diff --git a/apidoc/html/ostree-Core-repository-independent-functions.html b/apidoc/html/ostree-Core-repository-independent-functions.html index ec27659a..1b01255d 100644 --- a/apidoc/html/ostree-Core-repository-independent-functions.html +++ b/apidoc/html/ostree-Core-repository-independent-functions.html @@ -2488,6 +2488,13 @@ content, the other types are metadata.

  + +

OSTREE_OBJECT_TYPE_PAYLOAD_LINK

+ +

Symlink to a .file given its checksum on the payload only.

+ +  + @@ -2495,7 +2502,7 @@ content, the other types are metadata.


OSTREE_OBJECT_TYPE_LAST

-
#define OSTREE_OBJECT_TYPE_LAST OSTREE_OBJECT_TYPE_COMMIT_META
+
#define OSTREE_OBJECT_TYPE_LAST OSTREE_OBJECT_TYPE_PAYLOAD_LINK
 

Last valid object type; use this to validate ranges.

diff --git a/apidoc/html/ostree-OstreeRepo.html b/apidoc/html/ostree-OstreeRepo.html index 190f1505..80160bef 100644 --- a/apidoc/html/ostree-OstreeRepo.html +++ b/apidoc/html/ostree-OstreeRepo.html @@ -2285,6 +2285,7 @@ The following are currently defined:

Parameters

@@ -6934,6 +6935,7 @@ string to pull the latest commit for that ref

  • 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

  • Parameters

    diff --git a/apidoc/html/ostree-Root-partition-mount-point.html b/apidoc/html/ostree-Root-partition-mount-point.html index 37711249..d05195f3 100644 --- a/apidoc/html/ostree-Root-partition-mount-point.html +++ b/apidoc/html/ostree-Root-partition-mount-point.html @@ -268,6 +268,14 @@ gboolean +ostree_sysroot_deployment_set_pinned () + + + + +gboolean + + ostree_sysroot_write_deployments () @@ -1253,6 +1261,56 @@ across reboots.


    +

    ostree_sysroot_deployment_set_pinned ()

    +
    gboolean
    +ostree_sysroot_deployment_set_pinned (OstreeSysroot *self,
    +                                      OstreeDeployment *deployment,
    +                                      gboolean is_pinned,
    +                                      GError **error);
    +

    By default, deployments may be subject to garbage collection. Typical uses of +libostree only retain at most 2 deployments. If is_pinned + is TRUE, a +metadata bit will be set causing libostree to avoid automatic GC of the +deployment. However, this is really an "advisory" note; it's still possible +for e.g. older versions of libostree unaware of pinning to GC the deployment.

    +

    This function does nothing and returns successfully if the deployment +is already in the desired pinning state.

    +
    +

    Parameters

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

    self

    Sysroot

     

    deployment

    A deployment

     

    is_pinned

    Whether or not deployment will be automatically GC'd

     

    error

    Error

     
    +
    +

    Since: 2018.3

    +
    +
    +

    ostree_sysroot_write_deployments ()

    gboolean
     ostree_sysroot_write_deployments (OstreeSysroot *self,
    diff --git a/apidoc/html/ostree-ostree-deployment.html b/apidoc/html/ostree-ostree-deployment.html
    index 7f238112..bdf259fe 100644
    --- a/apidoc/html/ostree-ostree-deployment.html
    +++ b/apidoc/html/ostree-ostree-deployment.html
    @@ -142,6 +142,14 @@
     
     
     
    +gboolean
    +
    +
    +ostree_deployment_is_pinned ()
    +
    +
    +
    +
     void
     
     
    @@ -174,6 +182,14 @@
     
     
     
    +void
    +
    +
    +ostree_deployment_origin_remove_transient_state ()
    +
    +
    +
    +
     OstreeDeployment *
     
     
    @@ -382,6 +398,33 @@ ostree_deployment_get_unlocked (
     
    +

    ostree_deployment_is_pinned ()

    +
    gboolean
    +ostree_deployment_is_pinned (OstreeDeployment *self);
    +

    See ostree_sysroot_deployment_set_pinned().

    +
    +

    Parameters

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

    self

    Deployment

     
    +
    +
    +

    Returns

    +

    TRUE if deployment will not be subject to GC

    +
    +

    Since: 2018.3

    +
    +
    +

    ostree_deployment_set_index ()

    void
     ostree_deployment_set_index (OstreeDeployment *self,
    @@ -410,6 +453,41 @@ ostree_deployment_set_origin (
    +

    ostree_deployment_origin_remove_transient_state ()

    +
    void
    +ostree_deployment_origin_remove_transient_state
    +                               (GKeyFile *origin);
    +

    The intention of an origin file is primarily describe the "inputs" that +resulted in a deployment, and it's commonly used to derive the new state. For +example, a key value (in pure libostree mode) is the "refspec". However, +libostree (or other applications) may want to store "transient" state that +should not be carried across upgrades.

    +

    This function just removes all members of the libostree-transient group. +The name of that group is available to all libostree users; best practice +would be to prefix values underneath there with a short identifier for your +software.

    +

    Additionally, this function will remove the origin/unlocked and +origin/override-commit members; these should be considered transient state +that should have been under an explicit group.

    +
    +

    Parameters

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

    origin

    An origin

     
    +
    +

    Since: 2018.3

    +
    +
    +

    ostree_deployment_clone ()

    OstreeDeployment *
     ostree_deployment_clone (OstreeDeployment *self);
    diff --git a/apidoc/html/ostree.devhelp2 b/apidoc/html/ostree.devhelp2 index ba6c16e2..1c33dbda 100644 --- a/apidoc/html/ostree.devhelp2 +++ b/apidoc/html/ostree.devhelp2 @@ -275,6 +275,7 @@ + @@ -362,10 +363,12 @@ + + @@ -395,6 +398,7 @@ + diff --git a/apidoc/html/reference.html b/apidoc/html/reference.html index a45edd56..231b9288 100644 --- a/apidoc/html/reference.html +++ b/apidoc/html/reference.html @@ -359,10 +359,18 @@ OSTREE_CHECK_VERSION, macro in ostree-version
    +ostree_deployment_is_pinned, function in ostree-deployment +
    +
    +
    ostree_deployment_new, function in ostree-deployment
    +ostree_deployment_origin_remove_transient_state, function in ostree-deployment +
    +
    +
    ostree_deployment_set_bootconfig, function in ostree-deployment
    @@ -1343,6 +1351,10 @@ OSTREE_RELEASE_VERSION, macro in ostree-version
    +ostree_sysroot_deployment_set_pinned, function in Root partition mount point +
    +
    +
    ostree_sysroot_deployment_unlock, function in Root partition mount point
    diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index 4421ed10..55f2e7a9 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -169,10 +169,12 @@ ostree_deployment_get_bootconfig ostree_deployment_get_origin ostree_deployment_get_origin_relpath ostree_deployment_get_unlocked +ostree_deployment_is_pinned ostree_deployment_set_index ostree_deployment_set_bootserial ostree_deployment_set_bootconfig ostree_deployment_set_origin +ostree_deployment_origin_remove_transient_state ostree_deployment_clone ostree_deployment_unlocked_state_to_string @@ -508,6 +510,7 @@ ostree_sysroot_init_osname ostree_sysroot_deployment_set_kargs ostree_sysroot_deployment_set_mutable ostree_sysroot_deployment_unlock +ostree_sysroot_deployment_set_pinned ostree_sysroot_write_deployments ostree_sysroot_write_deployments_with_options ostree_sysroot_write_origin_file diff --git a/apidoc/version.xml b/apidoc/version.xml index 442b5276..ae804ea1 100644 --- a/apidoc/version.xml +++ b/apidoc/version.xml @@ -1 +1 @@ -2018.2 \ No newline at end of file +2018.4 \ No newline at end of file diff --git a/bash/ostree b/bash/ostree index fe8e3d2c..218e4254 100644 --- a/bash/ostree +++ b/bash/ostree @@ -180,472 +180,6 @@ _ostree_ostree() { return 0 } -_ostree_admin_cleanup() { - local boolean_options=" - $main_boolean_options - " - - local options_with_args=" - --sysroot - " - - local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) - - case "$prev" in - --sysroot) - __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_admin_config_diff() { - local boolean_options=" - $main_boolean_options - " - - local options_with_args=" - --os - --sysroot - " - - local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) - - case "$prev" in - --os) - __ostree_compreply_oses - return 0 - ;; - --sysroot) - __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_admin_deploy() { - local boolean_options=" - $main_boolean_options - --retain - --retain-pending - --retain-rollback - --not-as-default - --karg-proc-cmdline - " - - local options_with_args=" - --karg - --karg-append - --origin-file - --os - --sysroot - " - - local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) - - case "$prev" in - --origin-file) - __ostree_compreply_all_files - return 0 - ;; - --os) - __ostree_compreply_oses - return 0 - ;; - --sysroot) - __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_admin_init_fs() { - local boolean_options=" - $main_boolean_options - " - - local options_with_args=" - --sysroot - " - - local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) - - case "$prev" in - --sysroot) - __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_admin_instutil() { - local boolean_options=" - $main_boolean_options - " - - local options_with_args=" - --sysroot - " - - local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) - - case "$prev" in - --sysroot) - __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 - ;; - *) - local argpos=$( __ostree_pos_first_nonflag $( __ostree_to_alternatives "$options_with_args" ) ) - if [ $cword -eq $argpos ]; then - local instutil_commands=" - grub2-generate - selinux-ensure-labeled - set-kargs - " - COMPREPLY=( $( compgen -W "$instutil_commands" -- "$cur" ) ) - fi - ;; - esac - - return 0 -} - -_ostree_admin_os_init() { - local boolean_options=" - $main_boolean_options - " - - local options_with_args=" - --sysroot - " - - local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) - - case "$prev" in - --sysroot) - __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_admin_set_origin() { - local boolean_options=" - $main_boolean_options - " - - local options_with_args=" - --index - --set -s - --sysroot - " - - local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) - - case "$prev" in - --sysroot) - __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 - ;; - *) - local argpos=$( __ostree_pos_first_nonflag $( __ostree_to_alternatives "$options_with_args" ) ) - if [ $cword -eq $argpos ]; then - __ostree_compreply_remotes - fi - ;; - esac - - return 0 -} - -_ostree_admin_status() { - local boolean_options=" - $main_boolean_options - " - - local options_with_args=" - --sysroot - " - - local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) - - case "$prev" in - --sysroot) - __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_admin_switch() { - local boolean_options=" - $main_boolean_options - " - - local options_with_args=" - --os - --reboot -r - --sysroot - " - - local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) - - case "$prev" in - --os) - __ostree_compreply_oses - return 0 - ;; - --sysroot) - __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 - ;; - *) - local argpos=$( __ostree_pos_first_nonflag $( __ostree_to_alternatives "$options_with_args" ) ) - - if [ $cword -eq $argpos ]; then - __ostree_compreply_refs - fi - esac - - return 0 -} - -_ostree_admin_undeploy() { - local boolean_options=" - $main_boolean_options - " - - local options_with_args=" - --sysroot - " - - local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) - - case "$prev" in - --sysroot) - __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_admin_unlock() { - local boolean_options=" - $main_boolean_options - --hotfix - " - - local options_with_args=" - --sysroot - " - - local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) - - case "$prev" in - --sysroot) - __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_admin_upgrade() { - local boolean_options=" - $main_boolean_options - --allow-downgrade - --deploy-only - --pull-only - --reboot -r - " - - local options_with_args=" - --os - --override-commit - --sysroot - " - - local options_with_args_glob=$( __ostree_to_extglob "$options_with_args" ) - - case "$prev" in - --override-commit) - __ostree_compreply_commits - return 0 - ;; - --sysroot) - __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_admin() { - local subcommands=" - cleanup - config-diff - deploy - init-fs - instutil - os-init - set-origin - status - switch - undeploy - unlock - upgrade - " - - __ostree_subcommands "$subcommands" && return 0 - - case "$cur" in - -*) - COMPREPLY=( $( compgen -W "$main_boolean_options" -- "$cur" ) ) - ;; - *) - COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) ) - ;; - esac - - return 0 -} - _ostree_cat() { local boolean_options=" $main_boolean_options diff --git a/buildutil/libglnx.m4 b/buildutil/libglnx.m4 index d5bcc2f1..34caf204 100644 --- a/buildutil/libglnx.m4 +++ b/buildutil/libglnx.m4 @@ -12,6 +12,7 @@ AC_CHECK_DECLS([ #include #include #include +#include ]]) AC_ARG_ENABLE(otmpfile, diff --git a/config.h.in b/config.h.in index 9a98d7f3..4b2f3366 100644 --- a/config.h.in +++ b/config.h.in @@ -79,7 +79,7 @@ /* Define if we have libsoup client certs */ #undef HAVE_LIBSOUP_CLIENT_CERTS -/* Define if we have libsystemd */ +/* Define if we have libsystemd.pc */ #undef HAVE_LIBSYSTEMD /* Define to 1 if you have the header file. */ diff --git a/configure b/configure index 8d879ab3..0cb23000 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 2018.2. +# Generated by GNU Autoconf 2.69 for libostree 2018.4. # # Report bugs to . # @@ -590,8 +590,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='libostree' PACKAGE_TARNAME='libostree' -PACKAGE_VERSION='2018.2' -PACKAGE_STRING='libostree 2018.2' +PACKAGE_VERSION='2018.4' +PACKAGE_STRING='libostree 2018.4' PACKAGE_BUGREPORT='walters@verbum.org' PACKAGE_URL='' @@ -948,6 +948,7 @@ with_libmount enable_rofiles_fuse with_dracut with_mkinitcpio +with_libsystemd with_systemdsystemunitdir with_systemdsystemgeneratordir with_builtin_grub2_mkconfig @@ -1541,7 +1542,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 2018.2 to adapt to many kinds of systems. +\`configure' configures libostree 2018.4 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1611,7 +1612,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of libostree 2018.2:";; + short | recursive ) echo "Configuration of libostree 2018.4:";; esac cat <<\_ACEOF @@ -1689,6 +1690,7 @@ Optional Packages: --without-libmount Do not use libmount --with-dracut Install dracut module (default: no) --with-mkinitcpio Install mkinitcpio module (default: no) + --without-libsystemd Do not use libsystemd --with-systemdsystemunitdir=DIR Directory for systemd service files --with-systemdsystemgeneratordir=DIR @@ -1853,7 +1855,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -libostree configure 2018.2 +libostree configure 2018.4 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2325,7 +2327,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 2018.2, which was +It was created by libostree $as_me 2018.4, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -3193,7 +3195,7 @@ fi # Define the identity of the package. PACKAGE='libostree' - VERSION='2018.2' + VERSION='2018.4' # Some tools Automake needs. @@ -5927,9 +5929,9 @@ test -n "$YACC" || YACC="yacc" YEAR_VERSION=2018 -RELEASE_VERSION=2 +RELEASE_VERSION=4 -PACKAGE_VERSION=2018.2 +PACKAGE_VERSION=2018.4 if echo "$CFLAGS" | grep -q -E -e '-Werror($| )'; then : @@ -13781,6 +13783,7 @@ ac_fn_c_check_decl "$LINENO" "renameat2" "ac_cv_have_decl_renameat2" " #include #include #include +#include " if test "x$ac_cv_have_decl_renameat2" = xyes; then : @@ -13800,6 +13803,7 @@ ac_fn_c_check_decl "$LINENO" "memfd_create" "ac_cv_have_decl_memfd_create" " #include #include #include +#include " if test "x$ac_cv_have_decl_memfd_create" = xyes; then : @@ -13819,6 +13823,7 @@ ac_fn_c_check_decl "$LINENO" "copy_file_range" "ac_cv_have_decl_copy_file_range" #include #include #include +#include " if test "x$ac_cv_have_decl_copy_file_range" = xyes; then : @@ -17335,6 +17340,41 @@ fi +# Check whether --with-libsystemd was given. +if test "${with_libsystemd+set}" = set; then : + withval=$with_libsystemd; : +else + with_libsystemd=maybe +fi + + +if test x$with_libsystemd != xno ; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for libsystemd" >&5 +$as_echo_n "checking for libsystemd... " >&6; } + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"libsystemd\""; } >&5 + ($PKG_CONFIG --exists --print-errors "libsystemd") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + have_libsystemd=yes +else + have_libsystemd=no +fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $have_libsystemd" >&5 +$as_echo "$have_libsystemd" >&6; } + if test x$have_libsystemd = xno && test x$with_libsystemd != xmaybe ; then : + + as_fn_error $? "libsystemd is enabled but could not be found" "$LINENO" 5 + +fi + if test x$have_libsystemd = xyes; then : + + +$as_echo "#define HAVE_LIBSYSTEMD 1" >>confdefs.h + + pkg_failed=no { $as_echo "$as_me:${as_lineno-$LINENO}: checking for LIBSYSTEMD" >&5 $as_echo_n "checking for LIBSYSTEMD... " >&6; } @@ -17393,19 +17433,50 @@ fi # Put the nasty error message in config.log where it belongs echo "$LIBSYSTEMD_PKG_ERRORS" >&5 - have_libsystemd=no + as_fn_error $? "Package requirements (libsystemd) were not met: + +$LIBSYSTEMD_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +Alternatively, you may set the environment variables LIBSYSTEMD_CFLAGS +and LIBSYSTEMD_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details." "$LINENO" 5 elif test $pkg_failed = untried; then { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 $as_echo "no" >&6; } - have_libsystemd=no + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +Alternatively, you may set the environment variables LIBSYSTEMD_CFLAGS +and LIBSYSTEMD_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details. + +To get pkg-config, see . +See \`config.log' for more details" "$LINENO" 5; } else LIBSYSTEMD_CFLAGS=$pkg_cv_LIBSYSTEMD_CFLAGS LIBSYSTEMD_LIBS=$pkg_cv_LIBSYSTEMD_LIBS { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 $as_echo "yes" >&6; } - have_libsystemd=yes + fi - if test x$have_libsystemd = xyes; then + with_libsystemd=yes + +else + + with_libsystemd=no + +fi + +else + with_libsystemd=no +fi + if test $with_libsystemd != no; then BUILDOPT_LIBSYSTEMD_TRUE= BUILDOPT_LIBSYSTEMD_FALSE='#' else @@ -17413,11 +17484,6 @@ else BUILDOPT_LIBSYSTEMD_FALSE= fi -if test -z "$BUILDOPT_LIBSYSTEMD_TRUE"; then : - -$as_echo "#define HAVE_LIBSYSTEMD 1" >>confdefs.h - -fi if test "x$have_libsystemd" = "xyes"; then : @@ -18381,7 +18447,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 2018.2, which was +This file was extended by libostree $as_me 2018.4, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -18447,7 +18513,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 2018.2 +libostree config.status 2018.4 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 1b299d55..35962dfa 100644 --- a/configure.ac +++ b/configure.ac @@ -4,7 +4,7 @@ dnl update libostree-released.sym from libostree-devel.sym, and update the check dnl in test-symbols.sh, and also set is_release_build=yes below. Then make dnl another post-release commit to bump the version, and set is_release_build=no. m4_define([year_version], [2018]) -m4_define([release_version], [2]) +m4_define([release_version], [4]) m4_define([package_version], [year_version.release_version]) AC_INIT([libostree], [package_version], [walters@verbum.org]) is_release_build=yes @@ -469,10 +469,26 @@ AC_ARG_WITH(mkinitcpio, AM_CONDITIONAL(BUILDOPT_MKINITCPIO, test x$with_mkinitcpio = xyes) dnl We have separate checks for libsystemd and the unit dir for historical reasons -PKG_CHECK_MODULES([LIBSYSTEMD], [libsystemd], [have_libsystemd=yes], [have_libsystemd=no]) -AM_CONDITIONAL(BUILDOPT_LIBSYSTEMD, test x$have_libsystemd = xyes) -AM_COND_IF(BUILDOPT_LIBSYSTEMD, - AC_DEFINE([HAVE_LIBSYSTEMD], 1, [Define if we have libsystemd])) +AC_ARG_WITH(libsystemd, + AS_HELP_STRING([--without-libsystemd], [Do not use libsystemd]), + :, with_libsystemd=maybe) + +AS_IF([ test x$with_libsystemd != xno ], [ + AC_MSG_CHECKING([for libsystemd]) + PKG_CHECK_EXISTS(libsystemd, have_libsystemd=yes, have_libsystemd=no) + AC_MSG_RESULT([$have_libsystemd]) + AS_IF([ test x$have_libsystemd = xno && test x$with_libsystemd != xmaybe ], [ + AC_MSG_ERROR([libsystemd is enabled but could not be found]) + ]) + AS_IF([ test x$have_libsystemd = xyes], [ + AC_DEFINE([HAVE_LIBSYSTEMD], 1, [Define if we have libsystemd.pc]) + PKG_CHECK_MODULES([LIBSYSTEMD], [libsystemd]) + with_libsystemd=yes + ], [ + with_libsystemd=no + ]) +], [ with_libsystemd=no ]) +AM_CONDITIONAL(BUILDOPT_LIBSYSTEMD, test $with_libsystemd != no) AS_IF([test "x$have_libsystemd" = "xyes"], [ with_systemd=yes diff --git a/libglnx/glnx-console.c b/libglnx/glnx-console.c index 1cb3a497..88130745 100644 --- a/libglnx/glnx-console.c +++ b/libglnx/glnx-console.c @@ -46,8 +46,8 @@ static gboolean locked; static guint64 last_update_ms; /* monotonic time in millis we last updated */ -static gboolean -stdout_is_tty (void) +gboolean +glnx_stdout_is_tty (void) { static gsize initialized = 0; static gboolean stdout_is_tty_v; @@ -156,7 +156,7 @@ glnx_console_lock (GLnxConsoleRef *console) g_return_if_fail (!locked); g_return_if_fail (!console->locked); - console->is_tty = stdout_is_tty (); + console->is_tty = glnx_stdout_is_tty (); locked = console->locked = TRUE; @@ -199,7 +199,7 @@ text_percent_internal (const char *text, if (percentage != 100) { const guint64 diff_ms = current_ms - last_update_ms; - if (stdout_is_tty ()) + if (glnx_stdout_is_tty ()) { if (diff_ms < (1000/MAX_TTY_UPDATE_HZ)) return; @@ -224,7 +224,7 @@ text_percent_internal (const char *text, const guint input_textlen = text ? strlen (text) : 0; - if (!stdout_is_tty ()) + if (!glnx_stdout_is_tty ()) { if (text) fprintf (stdout, "%s", text); diff --git a/libglnx/glnx-console.h b/libglnx/glnx-console.h index 108dc407..d853a80c 100644 --- a/libglnx/glnx-console.h +++ b/libglnx/glnx-console.h @@ -31,6 +31,8 @@ struct GLnxConsoleRef { typedef struct GLnxConsoleRef GLnxConsoleRef; +gboolean glnx_stdout_is_tty (void); + void glnx_console_lock (GLnxConsoleRef *ref); void glnx_console_text (const char *text); diff --git a/libglnx/libglnx.m4 b/libglnx/libglnx.m4 index d5bcc2f1..34caf204 100644 --- a/libglnx/libglnx.m4 +++ b/libglnx/libglnx.m4 @@ -12,6 +12,7 @@ AC_CHECK_DECLS([ #include #include #include +#include ]]) AC_ARG_ENABLE(otmpfile, diff --git a/man/ostree-admin-pin.xml b/man/ostree-admin-pin.xml new file mode 100644 index 00000000..db0787ae --- /dev/null +++ b/man/ostree-admin-pin.xml @@ -0,0 +1,82 @@ + + + + + + + + + ostree admin pin + OSTree + + + + Developer + Colin + Walters + walters@verbum.org + + + + + + ostree admin pin + 1 + + + + ostree-admin-pin + Explicitly retain deployment at a given index + + + + + ostree admin pin INDEX + + + + + Description + + + Ensures the deployment at INDEX, will not be garbage + collected by default. This is termed "pinning". If the + -u option is provided, undoes a pinning operation. + + + + + Options + + + + , + + + Undoes a pinning operation. + + + + + + diff --git a/man/ostree-find-remotes.xml b/man/ostree-find-remotes.xml deleted file mode 100644 index f0208b7c..00000000 --- a/man/ostree-find-remotes.xml +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - - - - ostree find-remotes - OSTree - - - - Developer - Matthew - Leeds - matthew.leeds@endlessm.com - - - - - - ostree find-remotes - 1 - - - - ostree-find-remotes - Find remotes to serve the given refs - - - - - ostree find-remotes OPTIONS COLLECTION-ID REF COLLECTION-ID REF - - - - - Description - - - - OSTree has the ability do pulls not just from configured remote - servers but also from peer computers on the LAN and from mounted - filesystems such as USB drives. Currently this requires compiling - OSTree with experimental API enabled, and it requires the use of - collection IDs and GPG verification. - - - The find-remotes command searches for remotes - which claim to provide one or more of the given COLLECTION-ID REF - pairs and prints information about them, with remotes sorted by - latency (Mounts > LAN > Internet). By default, OSTree searches for - remotes in configuration files, on mounted filesystems (in a - well-known location), and on the LAN using Avahi. Searching for LAN - remotes requires OSTree to have been compiled with Avahi support, - and it requires an Avahi daemon to be running. You can override the - default set of finders (sources for remotes) using the - option documented below. - - - - - Options - - - - - - - Do not invoke fsync(). - - - - - - =FINDERS - - - Use the specified comma separated list of finders rather than - the default set. Possible values: config, - lan, and mount (or any - combination thereof). - - - - - - - - Pull the most recent commit found for each ref. - - - - - - - - Example - $ ostree find-remotes --finders=mount,lan com.exampleos.Os exampleos/x86_64/standard - -Result 0: http://10.0.64.202:43381/0 - - Finder: OstreeRepoFinderAvahi - - Keyring: exampleos.trustedkeys.gpg - - Priority: 60 - - Summary last modified: 2018-01-12T19:00:28Z - - Refs: - - (com.exampleos.Os, exampleos/x86_64/standard) = c91acd964b3fda561b87bfb7f7c80e36220d76b567f0ce90c0e60742ef33c360 - -1/1 refs were found. - - - diff --git a/man/ostree-prune.xml b/man/ostree-prune.xml index 641176ba..f1b517f3 100644 --- a/man/ostree-prune.xml +++ b/man/ostree-prune.xml @@ -114,8 +114,12 @@ Boston, MA 02111-1307, USA. =DEPTH - Change the behaviour of --keep-younger-than and --delete-commit to prune only - the static deltas files. + This option may currently only be used in combination with + . Previous versions of ostree silently accepted + the option without that, and ignored it. However, there are desired use + cases for pruning just static deltas (while retaining the commits), and it's + likely at some point this option will be supported for use cases outside of just + . diff --git a/man/ostree.repo-config.xml b/man/ostree.repo-config.xml index aa3abd54..cbc605f7 100644 --- a/man/ostree.repo-config.xml +++ b/man/ostree.repo-config.xml @@ -145,6 +145,13 @@ Boston, MA 02111-1307, USA. + + payload-link-threshold + An integer value that specifies a minimum file size for creating + a payload link. By default it is disabled. + + + diff --git a/src/libostree/libostree-devel.sym b/src/libostree/libostree-devel.sym index 46ad280c..3377ae12 100644 --- a/src/libostree/libostree-devel.sym +++ b/src/libostree/libostree-devel.sym @@ -18,6 +18,8 @@ ***/ /* Add new symbols here. Release commits should copy this section into -released.sym. */ +LIBOSTREE_2018.5 { +} LIBOSTREE_2018.3; /* Stub section for the stable release *after* this development one; don't * edit this other than to update the last number. This is just a copy/paste diff --git a/src/libostree/libostree-released.sym b/src/libostree/libostree-released.sym index 6f86ddcd..e9a95cc7 100644 --- a/src/libostree/libostree-released.sym +++ b/src/libostree/libostree-released.sym @@ -458,6 +458,12 @@ LIBOSTREE_2018.2 { ostree_commit_get_content_checksum; } LIBOSTREE_2018.1; +LIBOSTREE_2018.3 { + ostree_deployment_origin_remove_transient_state; + ostree_sysroot_deployment_set_pinned; + ostree_deployment_is_pinned; +} LIBOSTREE_2018.2; + /* NOTE: Only add more content here in release commits! See the * comments at the top of this file. */ diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c index ba790dc7..33d6a48b 100644 --- a/src/libostree/ostree-core.c +++ b/src/libostree/ostree-core.c @@ -1239,6 +1239,8 @@ ostree_object_type_to_string (OstreeObjectType objtype) return "tombstone-commit"; case OSTREE_OBJECT_TYPE_COMMIT_META: return "commitmeta"; + case OSTREE_OBJECT_TYPE_PAYLOAD_LINK: + return "payload-link"; default: g_assert_not_reached (); return NULL; @@ -1266,6 +1268,8 @@ ostree_object_type_from_string (const char *str) return OSTREE_OBJECT_TYPE_TOMBSTONE_COMMIT; else if (!strcmp (str, "commitmeta")) return OSTREE_OBJECT_TYPE_COMMIT_META; + else if (!strcmp (str, "payload-link")) + return OSTREE_OBJECT_TYPE_PAYLOAD_LINK; g_assert_not_reached (); return 0; } @@ -2122,6 +2126,7 @@ _ostree_validate_structureof_metadata (OstreeObjectType objtype, break; case OSTREE_OBJECT_TYPE_TOMBSTONE_COMMIT: case OSTREE_OBJECT_TYPE_COMMIT_META: + case OSTREE_OBJECT_TYPE_PAYLOAD_LINK: /* TODO */ break; case OSTREE_OBJECT_TYPE_FILE: diff --git a/src/libostree/ostree-core.h b/src/libostree/ostree-core.h index 018f5070..b65c9ba9 100644 --- a/src/libostree/ostree-core.h +++ b/src/libostree/ostree-core.h @@ -68,6 +68,7 @@ G_BEGIN_DECLS * @OSTREE_OBJECT_TYPE_COMMIT: Toplevel object, refers to tree and dirmeta for root * @OSTREE_OBJECT_TYPE_TOMBSTONE_COMMIT: Toplevel object, refers to a deleted commit * @OSTREE_OBJECT_TYPE_COMMIT_META: Detached metadata for a commit + * @OSTREE_OBJECT_TYPE_PAYLOAD_LINK: Symlink to a .file given its checksum on the payload only. * * Enumeration for core object types; %OSTREE_OBJECT_TYPE_FILE is for * content, the other types are metadata. @@ -79,6 +80,7 @@ typedef enum { OSTREE_OBJECT_TYPE_COMMIT = 4, /* .commit */ OSTREE_OBJECT_TYPE_TOMBSTONE_COMMIT = 5, /* .commit-tombstone */ OSTREE_OBJECT_TYPE_COMMIT_META = 6, /* .commitmeta */ + OSTREE_OBJECT_TYPE_PAYLOAD_LINK = 7, /* .payload-link */ } OstreeObjectType; /** @@ -94,7 +96,7 @@ typedef enum { * * Last valid object type; use this to validate ranges. */ -#define OSTREE_OBJECT_TYPE_LAST OSTREE_OBJECT_TYPE_COMMIT_META +#define OSTREE_OBJECT_TYPE_LAST OSTREE_OBJECT_TYPE_PAYLOAD_LINK /** * OSTREE_DIRMETA_GVARIANT_FORMAT: diff --git a/src/libostree/ostree-deployment.c b/src/libostree/ostree-deployment.c index 6431aa96..75a5bd1d 100644 --- a/src/libostree/ostree-deployment.c +++ b/src/libostree/ostree-deployment.c @@ -121,6 +121,35 @@ ostree_deployment_set_origin (OstreeDeployment *self, GKeyFile *origin) self->origin = g_key_file_ref (origin); } +/** + * ostree_deployment_origin_remove_transient_state: + * @origin: An origin + * + * The intention of an origin file is primarily describe the "inputs" that + * resulted in a deployment, and it's commonly used to derive the new state. For + * example, a key value (in pure libostree mode) is the "refspec". However, + * libostree (or other applications) may want to store "transient" state that + * should not be carried across upgrades. + * + * This function just removes all members of the `libostree-transient` group. + * The name of that group is available to all libostree users; best practice + * would be to prefix values underneath there with a short identifier for your + * software. + * + * Additionally, this function will remove the `origin/unlocked` and + * `origin/override-commit` members; these should be considered transient state + * that should have been under an explicit group. + * + * Since: 2018.3 + */ +void +ostree_deployment_origin_remove_transient_state (GKeyFile *origin) +{ + g_key_file_remove_group (origin, OSTREE_ORIGIN_TRANSIENT_GROUP, NULL); + g_key_file_remove_key (origin, "origin", "override-commit", NULL); + g_key_file_remove_key (origin, "origin", "unlocked", NULL); +} + void _ostree_deployment_set_bootcsum (OstreeDeployment *self, const char *bootcsum) @@ -293,3 +322,20 @@ ostree_deployment_get_unlocked (OstreeDeployment *self) { return self->unlocked; } + +/** + * ostree_deployment_is_pinned: + * @self: Deployment + * + * See ostree_sysroot_deployment_set_pinned(). + * + * Returns: `TRUE` if deployment will not be subject to GC + * Since: 2018.3 + */ +gboolean +ostree_deployment_is_pinned (OstreeDeployment *self) +{ + if (!self->origin) + return FALSE; + return g_key_file_get_boolean (self->origin, OSTREE_ORIGIN_TRANSIENT_GROUP, "pinned", NULL); +} diff --git a/src/libostree/ostree-deployment.h b/src/libostree/ostree-deployment.h index b4368f46..612222a2 100644 --- a/src/libostree/ostree-deployment.h +++ b/src/libostree/ostree-deployment.h @@ -27,6 +27,17 @@ G_BEGIN_DECLS #define OSTREE_DEPLOYMENT(inst) (G_TYPE_CHECK_INSTANCE_CAST ((inst), OSTREE_TYPE_DEPLOYMENT, OstreeDeployment)) #define OSTREE_IS_DEPLOYMENT(inst) (G_TYPE_CHECK_INSTANCE_TYPE ((inst), OSTREE_TYPE_DEPLOYMENT)) +/** + * OSTREE_ORIGIN_TRANSIENT_GROUP: + * + * The name of a `GKeyFile` group for data that should not + * be carried across upgrades. For more information, + * see ostree_deployment_origin_remove_transient_state(). + * + * Since: 2018.3 + */ +#define OSTREE_ORIGIN_TRANSIENT_GROUP "libostree-transient" + typedef struct _OstreeDeployment OstreeDeployment; _OSTREE_PUBLIC @@ -62,6 +73,10 @@ OstreeBootconfigParser *ostree_deployment_get_bootconfig (OstreeDeployment *self _OSTREE_PUBLIC GKeyFile *ostree_deployment_get_origin (OstreeDeployment *self); + +_OSTREE_PUBLIC +gboolean ostree_deployment_is_pinned (OstreeDeployment *self); + _OSTREE_PUBLIC void ostree_deployment_set_index (OstreeDeployment *self, int index); _OSTREE_PUBLIC @@ -71,6 +86,9 @@ void ostree_deployment_set_bootconfig (OstreeDeployment *self, OstreeBootconfigP _OSTREE_PUBLIC void ostree_deployment_set_origin (OstreeDeployment *self, GKeyFile *origin); +_OSTREE_PUBLIC +void ostree_deployment_origin_remove_transient_state (GKeyFile *origin); + _OSTREE_PUBLIC OstreeDeployment *ostree_deployment_clone (OstreeDeployment *self); diff --git a/src/libostree/ostree-fetcher-curl.c b/src/libostree/ostree-fetcher-curl.c index c4514251..c0f38131 100644 --- a/src/libostree/ostree-fetcher-curl.c +++ b/src/libostree/ostree-fetcher-curl.c @@ -77,6 +77,7 @@ struct OstreeFetcher char *proxy; struct curl_slist *extra_headers; int tmpdir_dfd; + char *custom_user_agent; GMainContext *mainctx; CURLM *multi; @@ -180,6 +181,7 @@ _ostree_fetcher_finalize (GObject *object) g_clear_pointer (&self->timer_event, (GDestroyNotify)destroy_and_unref_source); if (self->mainctx) g_main_context_unref (self->mainctx); + g_clear_pointer (&self->custom_user_agent, (GDestroyNotify)g_free); G_OBJECT_CLASS (_ostree_fetcher_parent_class)->finalize (object); } @@ -676,6 +678,18 @@ _ostree_fetcher_set_extra_headers (OstreeFetcher *self, } } +void +_ostree_fetcher_set_extra_user_agent (OstreeFetcher *self, + const char *extra_user_agent) +{ + g_clear_pointer (&self->custom_user_agent, (GDestroyNotify)g_free); + if (extra_user_agent) + { + self->custom_user_agent = + g_strdup_printf ("%s %s", OSTREE_FETCHER_USERAGENT_STRING, extra_user_agent); + } +} + /* Re-bind all of the outstanding curl items to our new main context */ static void adopt_steal_mainctx (OstreeFetcher *self, @@ -716,7 +730,8 @@ initiate_next_curl_request (FetcherRequest *req, curl_easy_setopt (req->easy, CURLOPT_URL, uri); } - curl_easy_setopt (req->easy, CURLOPT_USERAGENT, OSTREE_FETCHER_USERAGENT_STRING); + curl_easy_setopt (req->easy, CURLOPT_USERAGENT, + self->custom_user_agent ?: OSTREE_FETCHER_USERAGENT_STRING); if (self->extra_headers) curl_easy_setopt (req->easy, CURLOPT_HTTPHEADER, self->extra_headers); diff --git a/src/libostree/ostree-fetcher-soup.c b/src/libostree/ostree-fetcher-soup.c index 306e2534..08a0a700 100644 --- a/src/libostree/ostree-fetcher-soup.c +++ b/src/libostree/ostree-fetcher-soup.c @@ -374,6 +374,24 @@ session_thread_set_tls_database_cb (ThreadClosure *thread_closure, } } +static void +session_thread_set_extra_user_agent_cb (ThreadClosure *thread_closure, + gpointer data) +{ + const char *extra_user_agent = data; + if (extra_user_agent != NULL) + { + g_autofree char *ua = + g_strdup_printf ("%s %s", OSTREE_FETCHER_USERAGENT_STRING, extra_user_agent); + g_object_set (thread_closure->session, SOUP_SESSION_USER_AGENT, ua, NULL); + } + else + { + g_object_set (thread_closure->session, SOUP_SESSION_USER_AGENT, + OSTREE_FETCHER_USERAGENT_STRING, NULL); + } +} + static void on_request_sent (GObject *object, GAsyncResult *result, gpointer user_data); @@ -774,6 +792,16 @@ _ostree_fetcher_set_extra_headers (OstreeFetcher *self, (GDestroyNotify) g_variant_unref); } +void +_ostree_fetcher_set_extra_user_agent (OstreeFetcher *self, + const char *extra_user_agent) +{ + session_thread_idle_add (self->thread_closure, + session_thread_set_extra_user_agent_cb, + g_strdup (extra_user_agent), + (GDestroyNotify) g_free); +} + static gboolean finish_stream (OstreeFetcherPendingURI *pending, GCancellable *cancellable, diff --git a/src/libostree/ostree-fetcher.h b/src/libostree/ostree-fetcher.h index 7ac82170..32f3ea1b 100644 --- a/src/libostree/ostree-fetcher.h +++ b/src/libostree/ostree-fetcher.h @@ -114,6 +114,9 @@ void _ostree_fetcher_set_tls_database (OstreeFetcher *self, void _ostree_fetcher_set_extra_headers (OstreeFetcher *self, GVariant *extra_headers); +void _ostree_fetcher_set_extra_user_agent (OstreeFetcher *self, + const char *extra_user_agent); + guint64 _ostree_fetcher_bytes_transferred (OstreeFetcher *self); void _ostree_fetcher_request_to_tmpfile (OstreeFetcher *self, diff --git a/src/libostree/ostree-remote.c b/src/libostree/ostree-remote.c index da513ab4..da325d18 100644 --- a/src/libostree/ostree-remote.c +++ b/src/libostree/ostree-remote.c @@ -149,6 +149,7 @@ ostree_remote_unref (OstreeRemote *remote) if (g_atomic_int_dec_and_test (&remote->ref_count)) { g_clear_pointer (&remote->name, g_free); + g_clear_pointer (&remote->refspec_name, g_free); g_clear_pointer (&remote->group, g_free); g_clear_pointer (&remote->keyring, g_free); g_clear_object (&remote->file); diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index 70068138..16081a95 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include "otutil.h" #include "ostree.h" @@ -40,6 +42,12 @@ #include "ostree-checksum-input-stream.h" #include "ostree-varint.h" +/* The standardized version of BTRFS_IOC_CLONE */ +#ifndef FICLONE +#define FICLONE _IOW(0x94, 9, int) +#endif + + /* If fsync is enabled and we're in a txn, we write into a staging dir for * commit, but we also allow direct writes into objects/ for e.g. hardlink * imports. @@ -589,6 +597,192 @@ create_regular_tmpfile_linkable_with_content (OstreeRepo *self, return TRUE; } +static gboolean +_check_support_reflink (OstreeRepo *self, gboolean *supported, GError **error) +{ + /* We have not checked yet if the file system supports reflinks, do it here */ + if (g_atomic_int_get (&self->fs_support_reflink) == 0) + { + g_auto(GLnxTmpfile) src_tmpf = { 0, }; + g_auto(GLnxTmpfile) dest_tmpf = { 0, }; + + if (!glnx_open_tmpfile_linkable_at (commit_tmp_dfd (self), ".", O_RDWR|O_CLOEXEC, + &src_tmpf, error)) + return FALSE; + if (!glnx_open_tmpfile_linkable_at (commit_tmp_dfd (self), ".", O_WRONLY|O_CLOEXEC, + &dest_tmpf, error)) + return FALSE; + + if (ioctl (dest_tmpf.fd, FICLONE, src_tmpf.fd) == 0) + g_atomic_int_set (&self->fs_support_reflink, 1); + else if (errno == EOPNOTSUPP) /* Ignore other kind of errors as they might be temporary failures */ + g_atomic_int_set (&self->fs_support_reflink, -1); + } + *supported = g_atomic_int_get (&self->fs_support_reflink) >= 0; + return TRUE; +} + +static gboolean +_create_payload_link (OstreeRepo *self, + const char *checksum, + const char *payload_checksum, + GFileInfo *file_info, + GCancellable *cancellable, + GError **error) +{ + gboolean reflinks_supported = FALSE; + if (!_check_support_reflink (self, &reflinks_supported, error)) + return FALSE; + + if (!reflinks_supported) + return TRUE; + + if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_REGULAR + || !G_IN_SET(self->mode, OSTREE_REPO_MODE_BARE, OSTREE_REPO_MODE_BARE_USER, OSTREE_REPO_MODE_BARE_USER_ONLY)) + return TRUE; + + if (payload_checksum == NULL || g_file_info_get_size (file_info) < self->payload_link_threshold) + return TRUE; + + char target_buf[_OSTREE_LOOSE_PATH_MAX + _OSTREE_PAYLOAD_LINK_PREFIX_LEN]; + strcpy (target_buf, _OSTREE_PAYLOAD_LINK_PREFIX); + _ostree_loose_path (target_buf + _OSTREE_PAYLOAD_LINK_PREFIX_LEN, checksum, OSTREE_OBJECT_TYPE_FILE, self->mode); + + if (symlinkat (target_buf, commit_tmp_dfd (self), payload_checksum) < 0) + { + if (errno != EEXIST) + return glnx_throw_errno_prefix (error, "symlinkat"); + } + else + { + g_auto(OtCleanupUnlinkat) tmp_unlinker = { commit_tmp_dfd (self), g_strdup (payload_checksum) }; + if (!commit_path_final (self, payload_checksum, OSTREE_OBJECT_TYPE_PAYLOAD_LINK, &tmp_unlinker, cancellable, error)) + return FALSE; + } + + return TRUE; +} + +static gboolean +_import_payload_link (OstreeRepo *self, + OstreeRepo *source, + const char *checksum, + GCancellable *cancellable, + GError **error) +{ + gboolean reflinks_supported = FALSE; + g_autofree char *payload_checksum = NULL; + g_autoptr(GInputStream) is = NULL; + glnx_unref_object OtChecksumInstream *checksum_payload = NULL; + g_autoptr(GFileInfo) file_info = NULL; + + if (!_check_support_reflink (self, &reflinks_supported, error)) + return FALSE; + + if (!reflinks_supported) + return TRUE; + + if (!G_IN_SET(self->mode, OSTREE_REPO_MODE_BARE, OSTREE_REPO_MODE_BARE_USER, OSTREE_REPO_MODE_BARE_USER_ONLY)) + return TRUE; + + if (!ostree_repo_load_file (source, checksum, &is, &file_info, NULL, cancellable, error)) + return FALSE; + + if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_REGULAR + || g_file_info_get_size (file_info) < self->payload_link_threshold) + return TRUE; + + checksum_payload = ot_checksum_instream_new (is, G_CHECKSUM_SHA256); + + guint64 remaining = g_file_info_get_size (file_info); + while (remaining) + { + char buf[8192]; + gssize ret = g_input_stream_read ((GInputStream *) checksum_payload, buf, + MIN (sizeof (buf), remaining), cancellable, error); + if (ret < 0) + return FALSE; + remaining -= ret; + } + payload_checksum = ot_checksum_instream_get_string (checksum_payload); + + return _create_payload_link (self, checksum, payload_checksum, file_info, cancellable, error); +} + +static gboolean +_try_clone_from_payload_link (OstreeRepo *self, + const char *payload_checksum, + GFileInfo *file_info, + GLnxTmpfile *tmpf, + GCancellable *cancellable, + GError **error) +{ + gboolean reflinks_supported = FALSE; + int dfd_searches[] = { -1, self->objects_dir_fd }; + if (self->commit_stagedir.initialized) + dfd_searches[0] = self->commit_stagedir.fd; + + if (!_check_support_reflink (self, &reflinks_supported, error)) + return FALSE; + + if (!reflinks_supported) + return TRUE; + + for (guint i = 0; i < G_N_ELEMENTS (dfd_searches); i++) + { + glnx_autofd int fdf = -1; + char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; + char loose_path_target_buf[_OSTREE_LOOSE_PATH_MAX]; + char target_buf[_OSTREE_LOOSE_PATH_MAX + _OSTREE_PAYLOAD_LINK_PREFIX_LEN]; + char target_checksum[OSTREE_SHA256_STRING_LEN+1]; + int dfd = dfd_searches[i]; + ssize_t size; + if (dfd == -1) + continue; + + _ostree_loose_path (loose_path_buf, payload_checksum, OSTREE_OBJECT_TYPE_PAYLOAD_LINK, self->mode); + + size = TEMP_FAILURE_RETRY (readlinkat (dfd, loose_path_buf, target_buf, sizeof (target_buf))); + if (size < 0) + { + if (errno == ENOENT) + continue; + return glnx_throw_errno_prefix (error, "readlinkat"); + } + + if (size < OSTREE_SHA256_STRING_LEN + _OSTREE_PAYLOAD_LINK_PREFIX_LEN) + return glnx_throw (error, "invalid data size for %s", loose_path_buf); + + sprintf (target_checksum, "%.2s%.62s", target_buf + _OSTREE_PAYLOAD_LINK_PREFIX_LEN, target_buf + _OSTREE_PAYLOAD_LINK_PREFIX_LEN + 3); + + _ostree_loose_path (loose_path_target_buf, target_checksum, OSTREE_OBJECT_TYPE_FILE, self->mode); + if (!ot_openat_ignore_enoent (dfd, loose_path_target_buf, &fdf, error)) + return FALSE; + + if (fdf < 0) + { + /* If the link is referring to an object that doesn't exist anymore in the repository, just unlink it. */ + if (!glnx_unlinkat (dfd, loose_path_buf, 0, error)) + return FALSE; + } + else + { + /* This undoes all of the previous writes; we want to generate reflinked data. */ + if (ftruncate (tmpf->fd, 0) < 0) + return glnx_throw_errno_prefix (error, "ftruncate"); + + if (glnx_regfile_copy_bytes (fdf, tmpf->fd, -1) < 0) + return glnx_throw_errno_prefix (error, "regfile copy"); + + return TRUE; + } + } + if (self->parent_repo) + return _try_clone_from_payload_link (self->parent_repo, payload_checksum, file_info, tmpf, cancellable, error); + + return TRUE; +} + /* The main driver for writing a content (regfile or symlink) object. * There are a variety of tricky cases here; for example, bare-user * repos store symlinks as regular files. Computing checksums @@ -616,6 +810,8 @@ write_content_object (OstreeRepo *self, GInputStream *file_input; /* Unowned alias */ g_autoptr(GInputStream) file_input_owned = NULL; /* We need a temporary for bare-user symlinks */ glnx_unref_object OtChecksumInstream *checksum_input = NULL; + glnx_unref_object OtChecksumInstream *checksum_payload_input = NULL; + const GFileType object_file_type = g_file_info_get_file_type (file_info); if (out_csum) { /* Previously we checksummed the input verbatim; now @@ -624,6 +820,7 @@ write_content_object (OstreeRepo *self, * it's not that's not a serious problem because we're still computing a * checksum over the data we actually use. */ + gboolean reflinks_supported = FALSE; g_autoptr(GBytes) header = _ostree_file_header_new (file_info, xattrs); size_t len; const guint8 *buf = g_bytes_get_data (header, &len); @@ -633,13 +830,26 @@ write_content_object (OstreeRepo *self, null_input = input = g_memory_input_stream_new_from_data ("", 0, NULL); checksum_input = ot_checksum_instream_new_with_start (input, G_CHECKSUM_SHA256, buf, len); - file_input = (GInputStream*)checksum_input; + + if (!_check_support_reflink (self, &reflinks_supported, error)) + return FALSE; + + if (xattrs == NULL || !G_IN_SET(self->mode, OSTREE_REPO_MODE_BARE, OSTREE_REPO_MODE_BARE_USER, OSTREE_REPO_MODE_BARE_USER_ONLY) || object_file_type != G_FILE_TYPE_REGULAR || + !reflinks_supported) + file_input = (GInputStream*)checksum_input; + else + { + /* The payload checksum-input reads from the full object checksum-input; this + * means it skips the header. + */ + checksum_payload_input = ot_checksum_instream_new ((GInputStream*)checksum_input, G_CHECKSUM_SHA256); + file_input = (GInputStream*)checksum_payload_input; + } } else file_input = input; gboolean phys_object_is_symlink = FALSE; - const GFileType object_file_type = g_file_info_get_file_type (file_info); switch (object_file_type) { case G_FILE_TYPE_REGULAR: @@ -765,6 +975,7 @@ write_content_object (OstreeRepo *self, } const char *actual_checksum = NULL; + g_autofree char *actual_payload_checksum = NULL; g_autofree char *actual_checksum_owned = NULL; if (!checksum_input) actual_checksum = expected_checksum; @@ -777,6 +988,9 @@ write_content_object (OstreeRepo *self, error)) return FALSE; } + + if (checksum_payload_input) + actual_payload_checksum = ot_checksum_instream_get_string (checksum_payload_input); } g_assert (actual_checksum != NULL); /* Pacify static analysis */ @@ -794,6 +1008,10 @@ write_content_object (OstreeRepo *self, g_mutex_lock (&self->txn_lock); self->txn.stats.content_objects_total++; g_mutex_unlock (&self->txn_lock); + + if (!_create_payload_link (self, actual_checksum, actual_payload_checksum, file_info, cancellable, error)) + return FALSE; + if (out_csum) *out_csum = ostree_checksum_to_bytes (actual_checksum); /* Note early return */ @@ -853,12 +1071,20 @@ write_content_object (OstreeRepo *self, repo_store_size_entry (self, actual_checksum, unpacked_size, stbuf.st_size); } + /* Check if a file with the same payload is present in the repository, + and in case try to reflink it */ + if (actual_payload_checksum && !_try_clone_from_payload_link (self, actual_payload_checksum, file_info, &tmpf, cancellable, error)) + return FALSE; + /* This path is for regular files */ if (!commit_loose_regfile_object (self, actual_checksum, &tmpf, uid, gid, mode, xattrs, cancellable, error)) return FALSE; + + if (!_create_payload_link (self, actual_checksum, actual_payload_checksum, file_info, cancellable, error)) + return FALSE; } /* Update statistics */ @@ -1927,6 +2153,12 @@ ostree_repo_abort_transaction (OstreeRepo *self, GCancellable *cancellable, GError **error) { + /* Always ignore the cancellable to avoid the chance that, if it gets + * canceled, the transaction may not be fully cleaned up. + * See https://github.com/ostreedev/ostree/issues/1491 . + */ + cancellable = NULL; + /* Note early return */ if (!self->in_transaction) return TRUE; @@ -3999,7 +4231,11 @@ import_one_object_direct (OstreeRepo *dest_repo, if (!copy_detached_metadata (dest_repo, src_repo, checksum, cancellable, error)) return FALSE; } - + else if (objtype == OSTREE_OBJECT_TYPE_FILE) + { + if (!_import_payload_link (dest_repo, src_repo, checksum, cancellable, error)) + return FALSE; + } *out_was_supported = TRUE; return TRUE; } @@ -4092,7 +4328,14 @@ _ostree_repo_import_object (OstreeRepo *self, return FALSE; /* If we have it, we're done */ if (has_object) - return TRUE; + { + if (objtype == OSTREE_OBJECT_TYPE_FILE) + { + if (!_import_payload_link (self, source, checksum, cancellable, error)) + return FALSE; + } + return TRUE; + } if (OSTREE_OBJECT_TYPE_IS_META (objtype)) { diff --git a/src/libostree/ostree-repo-finder-config.c b/src/libostree/ostree-repo-finder-config.c index d6c8adda..76acb58e 100644 --- a/src/libostree/ostree-repo-finder-config.c +++ b/src/libostree/ostree-repo-finder-config.c @@ -30,6 +30,7 @@ #include #include +#include "ostree-autocleanups.h" #include "ostree-remote-private.h" #include "ostree-repo.h" #include "ostree-repo-private.h" @@ -176,7 +177,7 @@ ostree_repo_finder_config_resolve_async (OstreeRepoFinder *find while (g_hash_table_iter_next (&iter, (gpointer *) &remote_name, (gpointer *) &supported_ref_to_checksum)) { g_autoptr(GError) local_error = NULL; - OstreeRemote *remote; + g_autoptr(OstreeRemote) remote = NULL; /* We don’t know what last-modified timestamp the remote has without * making expensive HTTP queries, so leave that information blank. We diff --git a/src/libostree/ostree-repo-finder-mount.c b/src/libostree/ostree-repo-finder-mount.c index a7919fca..41a6bed2 100644 --- a/src/libostree/ostree-repo-finder-mount.c +++ b/src/libostree/ostree-repo-finder-mount.c @@ -52,7 +52,7 @@ * enumerated, and all OSTree repositories below it will be searched, in lexical * order, for the requested #OstreeCollectionRefs. The names of the directories * below `.ostree/repos.d` are irrelevant, apart from their lexical ordering. - * The directories `.ostree/repo`, `ostree/repo` and `var/lib/flatpak` + * The directories `.ostree/repo`, `ostree/repo` and `var/lib/flatpak/repo` * will be searched after the others, if they exist. * Non-removable volumes are ignored. * @@ -295,8 +295,8 @@ scan_and_add_repo (int dfd, }; g_array_append_val (inout_repos_refs, val); - g_debug ("%s: Adding repo ‘%s’ (%ssortable)", - G_STRFUNC, path, sortable ? "" : "not "); + g_debug ("%s: Adding repo ‘%s’ on mount ‘%s’ (%ssortable)", + G_STRFUNC, path, mount_name, sortable ? "" : "not "); } } @@ -439,7 +439,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS { ".ostree/repo", "ostree/repo", - "var/lib/flatpak", + "var/lib/flatpak/repo", }; for (i = 0; i < G_N_ELEMENTS (well_known_repos); i++) diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index a31d4e5e..3078a9e2 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -53,6 +53,9 @@ G_BEGIN_DECLS #define OSTREE_SUMMARY_COLLECTION_ID "ostree.summary.collection-id" #define OSTREE_SUMMARY_COLLECTION_MAP "ostree.summary.collection-map" +#define _OSTREE_PAYLOAD_LINK_PREFIX "../" +#define _OSTREE_PAYLOAD_LINK_PREFIX_LEN (sizeof (_OSTREE_PAYLOAD_LINK_PREFIX) - 1) + /* Well-known keys for the additional metadata field in a commit in a ref entry * in a summary file. */ #define OSTREE_COMMIT_TIMESTAMP "ostree.commit.timestamp" @@ -161,6 +164,8 @@ struct OstreeRepo { gchar *collection_id; gboolean add_remotes_config_dir; /* Add new remotes in remotes.d dir */ gint lock_timeout_seconds; + guint64 payload_link_threshold; + gint fs_support_reflink; /* The underlying filesystem has support for ioctl (FICLONE..) */ OstreeRepo *parent_repo; }; @@ -237,14 +242,6 @@ _ostree_repo_ensure_loose_objdir_at (int dfd, GCancellable *cancellable, GError **error); -gboolean -_ostree_repo_find_object (OstreeRepo *self, - OstreeObjectType objtype, - const char *checksum, - GFile **out_stored_path, - GCancellable *cancellable, - GError **error); - GFile * _ostree_repo_get_commit_metadata_loose_path (OstreeRepo *self, const char *checksum); diff --git a/src/libostree/ostree-repo-prune.c b/src/libostree/ostree-repo-prune.c index fc3cfa54..f0c0a974 100644 --- a/src/libostree/ostree-repo-prune.c +++ b/src/libostree/ostree-repo-prune.c @@ -46,40 +46,79 @@ maybe_prune_loose_object (OtPruneData *data, GCancellable *cancellable, GError **error) { + gboolean reachable = FALSE; g_autoptr(GVariant) key = NULL; key = ostree_object_name_serialize (checksum, objtype); - if (!g_hash_table_lookup_extended (data->reachable, key, NULL, NULL)) + if (g_hash_table_lookup_extended (data->reachable, key, NULL, NULL)) + reachable = TRUE; + else { + guint64 storage_size = 0; + g_debug ("Pruning unneeded object %s.%s", checksum, ostree_object_type_to_string (objtype)); + + if (!ostree_repo_query_object_storage_size (data->repo, objtype, checksum, + &storage_size, cancellable, error)) + return FALSE; + + data->freed_bytes += storage_size; + if (!(flags & OSTREE_REPO_PRUNE_FLAGS_NO_PRUNE)) { - guint64 storage_size = 0; + if (objtype == OSTREE_OBJECT_TYPE_PAYLOAD_LINK) + { + ssize_t size; + char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; + char target_checksum[OSTREE_SHA256_STRING_LEN+1]; + char target_buf[_OSTREE_LOOSE_PATH_MAX + _OSTREE_PAYLOAD_LINK_PREFIX_LEN]; - if (objtype == OSTREE_OBJECT_TYPE_COMMIT) + _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_PAYLOAD_LINK, data->repo->mode); + size = readlinkat (data->repo->objects_dir_fd, loose_path_buf, target_buf, sizeof (target_buf)); + if (size < 0) + return glnx_throw_errno_prefix (error, "readlinkat"); + + if (size < OSTREE_SHA256_STRING_LEN + _OSTREE_PAYLOAD_LINK_PREFIX_LEN) + return glnx_throw (error, "invalid data size for %s", loose_path_buf); + + sprintf (target_checksum, "%.2s%.62s", target_buf + _OSTREE_PAYLOAD_LINK_PREFIX_LEN, target_buf + _OSTREE_PAYLOAD_LINK_PREFIX_LEN + 3); + + g_autoptr(GVariant) target_key = ostree_object_name_serialize (target_checksum, OSTREE_OBJECT_TYPE_FILE); + + if (g_hash_table_lookup_extended (data->reachable, target_key, NULL, NULL)) + { + guint64 target_storage_size = 0; + if (!ostree_repo_query_object_storage_size (data->repo, OSTREE_OBJECT_TYPE_FILE, target_checksum, + &target_storage_size, cancellable, error)) + return FALSE; + + reachable = target_storage_size >= data->repo->payload_link_threshold; + if (reachable) + goto exit; + } + } + else if (objtype == OSTREE_OBJECT_TYPE_COMMIT) { if (!ostree_repo_mark_commit_partial (data->repo, checksum, FALSE, error)) return FALSE; } - if (!ostree_repo_query_object_storage_size (data->repo, objtype, checksum, - &storage_size, cancellable, error)) - return FALSE; - if (!ostree_repo_delete_object (data->repo, objtype, checksum, cancellable, error)) return FALSE; - data->freed_bytes += storage_size; } + if (OSTREE_OBJECT_TYPE_IS_META (objtype)) data->n_unreachable_meta++; else data->n_unreachable_content++; } - else + + exit: + if (reachable) { g_debug ("Keeping needed object %s.%s", checksum, ostree_object_type_to_string (objtype)); @@ -283,7 +322,7 @@ repo_prune_internal (OstreeRepo *self, * of traversing all commits, only refs will be used. Particularly * when combined with @depth, this is a convenient way to delete * history from the repository. - * + * * Use the %OSTREE_REPO_PRUNE_FLAGS_NO_PRUNE to just determine * statistics on objects that would be deleted, without actually * deleting them. diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index 999d7ecf..89c67c8e 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -87,6 +87,7 @@ typedef struct { OstreeAsyncProgress *progress; GVariant *extra_headers; + char *append_user_agent; gboolean dry_run; gboolean dry_run_emitted_progress; @@ -2922,11 +2923,13 @@ repo_remote_fetch_summary (OstreeRepo *self, const char *url_override = NULL; g_autoptr(GVariant) extra_headers = NULL; g_autoptr(GPtrArray) mirrorlist = NULL; + const char *append_user_agent = NULL; 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); } mainctx = g_main_context_new (); @@ -2939,6 +2942,9 @@ repo_remote_fetch_summary (OstreeRepo *self, 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) @@ -3055,6 +3061,9 @@ reinitialize_fetcher (OtPullData *pull_data, const char *remote_name, 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; } @@ -3240,6 +3249,7 @@ initiate_request (OtPullData *pull_data, * * 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 */ gboolean ostree_repo_pull_with_options (OstreeRepo *self, @@ -3311,6 +3321,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, (void) g_variant_lookup (options, "update-frequency", "u", &update_frequency); (void) g_variant_lookup (options, "localcache-repos", "^a&s", &opt_localcache_repos); (void) g_variant_lookup (options, "timestamp-check", "b", &pull_data->timestamp_check); + (void) g_variant_lookup (options, "append-user-agent", "s", &pull_data->append_user_agent); if (pull_data->remote_refspec_name != NULL) pull_data->remote_name = g_strdup (pull_data->remote_refspec_name); @@ -3365,7 +3376,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, (GDestroyNotify)g_free); pull_data->ref_original_commits = g_hash_table_new_full (ostree_collection_ref_hash, ostree_collection_ref_equal, (GDestroyNotify)NULL, - (GDestroyNotify)g_variant_unref); + (GDestroyNotify)g_free); pull_data->gpg_verified_commits = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)g_free, NULL); pull_data->scanned_metadata = g_hash_table_new_full (ostree_hash_object_name, g_variant_equal, @@ -4323,6 +4334,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, g_clear_pointer (&pull_data->localcache_repos, (GDestroyNotify)g_ptr_array_unref); g_clear_object (&pull_data->remote_repo_local); g_free (pull_data->remote_name); + g_free (pull_data->append_user_agent); g_clear_pointer (&pull_data->meta_mirrorlist, (GDestroyNotify) g_ptr_array_unref); g_clear_pointer (&pull_data->content_mirrorlist, (GDestroyNotify) g_ptr_array_unref); g_clear_pointer (&pull_data->summary_data, (GDestroyNotify) g_bytes_unref); @@ -5502,6 +5514,7 @@ ostree_repo_pull_from_remotes_async (OstreeRepo *self, copy_option (&options_dict, &local_options_dict, "http-headers", G_VARIANT_TYPE ("a(ss)")); copy_option (&options_dict, &local_options_dict, "subdirs", G_VARIANT_TYPE ("as")); copy_option (&options_dict, &local_options_dict, "update-frequency", G_VARIANT_TYPE ("u")); + copy_option (&options_dict, &local_options_dict, "append-user-agent", G_VARIANT_TYPE ("s")); local_options = g_variant_dict_end (&local_options_dict); @@ -5645,7 +5658,7 @@ ostree_repo_resolve_keyring_for_collection (OstreeRepo *self, { gsize i; g_auto(GStrv) remotes = NULL; - OstreeRemote *keyring_remote = NULL; + g_autoptr(OstreeRemote) keyring_remote = NULL; g_return_val_if_fail (OSTREE_IS_REPO (self), NULL); g_return_val_if_fail (ostree_validate_collection_id (collection_id, NULL), NULL); @@ -5680,6 +5693,7 @@ ostree_repo_resolve_keyring_for_collection (OstreeRepo *self, { g_debug ("%s: Ignoring remote ‘%s’ as it has no keyring configured.", G_STRFUNC, remotes[i]); + g_clear_object (&keyring_remote); continue; } @@ -5695,7 +5709,7 @@ ostree_repo_resolve_keyring_for_collection (OstreeRepo *self, } if (keyring_remote != NULL) - return ostree_remote_ref (keyring_remote); + return g_steal_pointer (&keyring_remote); else { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, @@ -5724,6 +5738,7 @@ ostree_repo_resolve_keyring_for_collection (OstreeRepo *self, * * - override-url (s): Fetch summary from this URL if remote specifies no metalink in options * - http-headers (a(ss)): Additional headers to add to all HTTP requests + * - append-user-agent (s): Additional string to append to the user agent * * Returns: %TRUE on success, %FALSE on failure */ diff --git a/src/libostree/ostree-repo-refs.c b/src/libostree/ostree-repo-refs.c index bb0e1c75..ad0fd57c 100644 --- a/src/libostree/ostree-repo-refs.c +++ b/src/libostree/ostree-repo-refs.c @@ -617,6 +617,8 @@ _ostree_repo_list_refs_internal (OstreeRepo *self, GCancellable *cancellable, GError **error) { + GLNX_AUTO_PREFIX_ERROR ("Listing refs", error); + g_autofree char *remote = NULL; g_autofree char *ref_prefix = NULL; @@ -627,8 +629,24 @@ _ostree_repo_list_refs_internal (OstreeRepo *self, const char *prefix_path; const char *path; - if (!ostree_parse_refspec (refspec_prefix, &remote, &ref_prefix, error)) - return FALSE; + /* special-case ":" and ":.", which ostree_parse_refspec won't like */ + if (g_str_has_suffix (refspec_prefix, ":") || + g_str_has_suffix (refspec_prefix, ":.")) + { + const char *colon = strrchr (refspec_prefix, ':'); + g_autofree char *r = g_strndup (refspec_prefix, colon - refspec_prefix); + if (ostree_validate_remote_name (r, NULL)) + { + remote = g_steal_pointer (&r); + ref_prefix = g_strdup ("."); + } + } + + if (!ref_prefix) + { + if (!ostree_parse_refspec (refspec_prefix, &remote, &ref_prefix, error)) + return FALSE; + } if (!(flags & OSTREE_REPO_LIST_REFS_EXT_EXCLUDE_REMOTES) && remote) { @@ -1029,10 +1047,7 @@ _ostree_repo_write_ref (OstreeRepo *self, { if (!glnx_opendirat (self->repo_dir_fd, "refs/heads", TRUE, &dfd, error)) - { - g_prefix_error (error, "Opening %s: ", "refs/heads"); - return FALSE; - } + return FALSE; } else if (remote == NULL && ref->collection_id != NULL) { @@ -1041,10 +1056,7 @@ _ostree_repo_write_ref (OstreeRepo *self, /* refs/mirrors might not exist in older repositories, so create it. */ if (!glnx_shutil_mkdir_p_at_open (self->repo_dir_fd, "refs/mirrors", 0777, &refs_mirrors_dfd, cancellable, error)) - { - g_prefix_error (error, "Opening %s: ", "refs/mirrors"); - return FALSE; - } + return FALSE; if (rev != NULL) { @@ -1063,10 +1075,7 @@ _ostree_repo_write_ref (OstreeRepo *self, if (!glnx_opendirat (self->repo_dir_fd, "refs/remotes", TRUE, &refs_remotes_dfd, error)) - { - g_prefix_error (error, "Opening %s: ", "refs/remotes"); - return FALSE; - } + return FALSE; if (rev != NULL) { @@ -1207,6 +1216,8 @@ ostree_repo_list_collection_refs (OstreeRepo *self, GCancellable *cancellable, GError **error) { + GLNX_AUTO_PREFIX_ERROR ("Listing refs", error); + g_return_val_if_fail (OSTREE_IS_REPO (self), FALSE); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); diff --git a/src/libostree/ostree-repo-static-delta-compilation.c b/src/libostree/ostree-repo-static-delta-compilation.c index 746a1a50..9084a72f 100644 --- a/src/libostree/ostree-repo-static-delta-compilation.c +++ b/src/libostree/ostree-repo-static-delta-compilation.c @@ -241,14 +241,8 @@ finish_part (OstreeStaticDeltaBuilder *builder, GError **error) g_variant_builder_add_value (&xattr_builder, part_builder->xattrs->pdata[j]); { - g_autoptr(GBytes) payload_b; - g_autoptr(GBytes) operations_b; - - payload_b = g_string_free_to_bytes (part_builder->payload); - part_builder->payload = NULL; - - operations_b = g_string_free_to_bytes (part_builder->operations); - part_builder->operations = NULL; + g_autoptr(GBytes) payload_b = g_string_free_to_bytes (g_steal_pointer (&part_builder->payload)); + g_autoptr(GBytes) operations_b = g_string_free_to_bytes (g_steal_pointer (&part_builder->operations)); delta_part_content = g_variant_new ("(a(uuu)aa(ayay)@ay@ay)", &mode_builder, &xattr_builder, diff --git a/src/libostree/ostree-repo-static-delta-core.c b/src/libostree/ostree-repo-static-delta-core.c index bca638b2..68b06b5c 100644 --- a/src/libostree/ostree-repo-static-delta-core.c +++ b/src/libostree/ostree-repo-static-delta-core.c @@ -38,10 +38,8 @@ _ostree_static_delta_parse_checksum_array (GVariant *array, guint *out_n_checksums, GError **error) { - gsize n = g_variant_n_children (array); - guint n_checksums; - - n_checksums = n / OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN; + const gsize n = g_variant_n_children (array); + const guint n_checksums = n / OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN; if (G_UNLIKELY(n > (G_MAXUINT32/OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN) || (n_checksums * OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN) != n)) @@ -166,8 +164,8 @@ _ostree_repo_static_delta_part_have_all_objects (OstreeRepo *repo, GCancellable *cancellable, GError **error) { - guint8 *checksums_data; - guint n_checksums; + guint8 *checksums_data = NULL; + guint n_checksums = 0; gboolean have_object = TRUE; if (!_ostree_static_delta_parse_checksum_array (checksum_array, diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 9242990c..8ff0d961 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -2827,6 +2827,15 @@ reload_core_config (OstreeRepo *self, return FALSE; } + { g_autofree char *payload_threshold = NULL; + + if (!ot_keyfile_get_value_with_default (self->config, "core", "payload-link-threshold", "-1", + &payload_threshold, error)) + return FALSE; + + self->payload_link_threshold = g_ascii_strtoull (payload_threshold, NULL, 10); + } + return TRUE; } @@ -3278,6 +3287,8 @@ list_loose_objects_at (OstreeRepo *self, objtype = OSTREE_OBJECT_TYPE_DIR_META; else if (strcmp (dot, ".commit") == 0) objtype = OSTREE_OBJECT_TYPE_COMMIT; + else if (strcmp (dot, ".payload-link") == 0) + objtype = OSTREE_OBJECT_TYPE_PAYLOAD_LINK; else continue; @@ -4800,17 +4811,16 @@ ostree_repo_add_gpg_signature_summary (OstreeRepo *self, /* Note that fd is reused below */ glnx_close_fd (&fd); - g_autoptr(GVariant) existing_signatures = NULL; + g_autoptr(GVariant) metadata = NULL; if (!ot_openat_ignore_enoent (self->repo_dir_fd, "summary.sig", &fd, error)) return FALSE; if (fd != -1) { if (!ot_variant_read_fd (fd, 0, G_VARIANT_TYPE (OSTREE_SUMMARY_SIG_GVARIANT_STRING), - FALSE, &existing_signatures, error)) + FALSE, &metadata, error)) return FALSE; } - g_autoptr(GVariant) new_metadata = NULL; for (guint i = 0; key_id[i]; i++) { g_autoptr(GBytes) signature_data = NULL; @@ -4819,10 +4829,11 @@ ostree_repo_add_gpg_signature_summary (OstreeRepo *self, cancellable, error)) return FALSE; - new_metadata = _ostree_detached_metadata_append_gpg_sig (existing_signatures, signature_data); + g_autoptr(GVariant) old_metadata = g_steal_pointer (&metadata); + metadata = _ostree_detached_metadata_append_gpg_sig (old_metadata, signature_data); } - g_autoptr(GVariant) normalized = g_variant_get_normal_form (new_metadata); + g_autoptr(GVariant) normalized = g_variant_get_normal_form (metadata); if (!_ostree_repo_file_replace_contents (self, self->repo_dir_fd, @@ -4914,7 +4925,7 @@ _ostree_repo_gpg_verify_data_internal (OstreeRepo *self, g_autofree char *gpgkeypath = NULL; /* Add the remote's keyring file if it exists. */ - OstreeRemote *remote; + g_autoptr(OstreeRemote) remote = NULL; remote = _ostree_repo_get_remote_inherited (self, remote_name, error); if (remote == NULL) @@ -4936,8 +4947,6 @@ _ostree_repo_gpg_verify_data_internal (OstreeRepo *self, if (gpgkeypath) _ostree_gpg_verifier_add_key_ascii_file (verifier, gpgkeypath); - - ostree_remote_unref (remote); } if (add_global_keyring_dir) diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 4284b5ae..93a29ed6 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -523,38 +523,33 @@ checkout_deployment_tree (OstreeSysroot *sysroot, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - OstreeRepoCheckoutAtOptions checkout_opts = { 0, }; - const char *csum = ostree_deployment_get_csum (deployment); - g_autofree char *checkout_target_name = NULL; - g_autofree char *osdeploy_path = NULL; - glnx_autofd int osdeploy_dfd = -1; - int ret_fd; - - osdeploy_path = g_strconcat ("ostree/deploy/", ostree_deployment_get_osname (deployment), "/deploy", NULL); - checkout_target_name = g_strdup_printf ("%s.%d", csum, ostree_deployment_get_deployserial (deployment)); - + GLNX_AUTO_PREFIX_ERROR ("Checking out deployment tree", error); + /* Find the directory with deployments for this stateroot */ + g_autofree char *osdeploy_path = + g_strconcat ("ostree/deploy/", ostree_deployment_get_osname (deployment), "/deploy", NULL); if (!glnx_shutil_mkdir_p_at (sysroot->sysroot_fd, osdeploy_path, 0775, cancellable, error)) - goto out; + return FALSE; + glnx_autofd int osdeploy_dfd = -1; if (!glnx_opendirat (sysroot->sysroot_fd, osdeploy_path, TRUE, &osdeploy_dfd, error)) - goto out; + return FALSE; + /* Clean up anything that was there before, from e.g. an interrupted checkout */ + const char *csum = ostree_deployment_get_csum (deployment); + g_autofree char *checkout_target_name = + g_strdup_printf ("%s.%d", csum, ostree_deployment_get_deployserial (deployment)); if (!glnx_shutil_rm_rf_at (osdeploy_dfd, checkout_target_name, cancellable, error)) - goto out; + return FALSE; + /* Generate hardlink farm, then opendir it */ + OstreeRepoCheckoutAtOptions checkout_opts = { 0, }; if (!ostree_repo_checkout_at (repo, &checkout_opts, osdeploy_dfd, checkout_target_name, csum, cancellable, error)) - goto out; + return FALSE; - if (!glnx_opendirat (osdeploy_dfd, checkout_target_name, TRUE, &ret_fd, error)) - goto out; - - ret = TRUE; - *out_deployment_dfd = ret_fd; - out: - return ret; + return glnx_opendirat (osdeploy_dfd, checkout_target_name, TRUE, out_deployment_dfd, + error); } static char * @@ -672,27 +667,21 @@ selinux_relabel_dir (OstreeSysroot *sysroot, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - g_autoptr(GPtrArray) path_parts = g_ptr_array_new (); - g_autoptr(GFileInfo) root_info = NULL; - root_info = g_file_query_info (dir, OSTREE_GIO_FAST_QUERYINFO, - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, error); + g_autoptr(GFileInfo) root_info = + g_file_query_info (dir, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); if (!root_info) - goto out; + return FALSE; + g_autoptr(GPtrArray) path_parts = g_ptr_array_new (); g_ptr_array_add (path_parts, (char*)prefix); if (!relabel_recursively (sysroot, sepolicy, dir, root_info, path_parts, cancellable, error)) - { - g_prefix_error (error, "Relabeling /%s: ", prefix); - goto out; - } + return glnx_prefix_error (error, "Relabeling /%s", prefix); - ret = TRUE; - out: - return ret; + return TRUE; } /* Handles SELinux labeling for /var; this is slated to be deleted. See @@ -767,16 +756,12 @@ merge_configuration (OstreeSysroot *sysroot, GCancellable *cancellable, GError **error) { + GLNX_AUTO_PREFIX_ERROR ("During /etc merge", error); g_autoptr(OstreeSePolicy) sepolicy = NULL; if (previous_deployment) { - g_autoptr(GFile) previous_path = NULL; - OstreeBootconfigParser *previous_bootconfig; - - previous_path = ostree_sysroot_get_deployment_directory (sysroot, previous_deployment); - - previous_bootconfig = ostree_deployment_get_bootconfig (previous_deployment); + OstreeBootconfigParser *previous_bootconfig = ostree_deployment_get_bootconfig (previous_deployment); if (previous_bootconfig) { const char *previous_options = ostree_bootconfig_parser_get (previous_bootconfig, "options"); @@ -847,30 +832,39 @@ merge_configuration (OstreeSysroot *sysroot, return TRUE; } -/* Write the origin file for a deployment. */ +/* Write the origin file for a deployment; this does not bump the mtime, under + * the assumption the caller may be writing multiple. + */ static gboolean write_origin_file_internal (OstreeSysroot *sysroot, + OstreeSePolicy *sepolicy, OstreeDeployment *deployment, GKeyFile *new_origin, GLnxFileReplaceFlags flags, GCancellable *cancellable, GError **error) { + GLNX_AUTO_PREFIX_ERROR ("Writing out origin file", error); GKeyFile *origin = new_origin ? new_origin : ostree_deployment_get_origin (deployment); if (origin) { - g_autofree char *origin_path = NULL; - g_autofree char *contents = NULL; + g_auto(OstreeSepolicyFsCreatecon) con = { 0, }; + if (!_ostree_sepolicy_preparefscreatecon (&con, sepolicy, + "/etc/ostree/remotes.d/dummy.conf", + 0644, error)) + return FALSE; + + g_autofree char *origin_path = + g_strdup_printf ("ostree/deploy/%s/deploy/%s.%d.origin", + ostree_deployment_get_osname (deployment), + ostree_deployment_get_csum (deployment), + ostree_deployment_get_deployserial (deployment)); + + gsize len; - - origin_path = g_strdup_printf ("ostree/deploy/%s/deploy/%s.%d.origin", - ostree_deployment_get_osname (deployment), - ostree_deployment_get_csum (deployment), - ostree_deployment_get_deployserial (deployment)); - - contents = g_key_file_to_data (origin, &len, error); + g_autofree char *contents = g_key_file_to_data (origin, &len, error); if (!contents) return FALSE; @@ -903,9 +897,20 @@ ostree_sysroot_write_origin_file (OstreeSysroot *sysroot, GCancellable *cancellable, GError **error) { - return write_origin_file_internal (sysroot, deployment, new_origin, - GLNX_FILE_REPLACE_DATASYNC_NEW, - cancellable, error); + g_autoptr(GFile) rootfs = g_file_new_for_path ("/"); + g_autoptr(OstreeSePolicy) sepolicy = ostree_sepolicy_new (rootfs, cancellable, error); + if (!sepolicy) + return FALSE; + + if (!write_origin_file_internal (sysroot, sepolicy, deployment, new_origin, + GLNX_FILE_REPLACE_DATASYNC_NEW, + cancellable, error)) + return FALSE; + + if (!_ostree_sysroot_bump_mtime (sysroot, error)) + return FALSE; + + return TRUE; } typedef struct { @@ -1497,6 +1502,7 @@ create_new_bootlinks (OstreeSysroot *self, GCancellable *cancellable, GError **error) { + GLNX_AUTO_PREFIX_ERROR ("Creating new current bootlinks", error); glnx_autofd int ostree_dfd = -1; if (!glnx_opendirat (self->sysroot_fd, "ostree", TRUE, &ostree_dfd, error)) return FALSE; @@ -1559,6 +1565,7 @@ swap_bootlinks (OstreeSysroot *self, GCancellable *cancellable, GError **error) { + GLNX_AUTO_PREFIX_ERROR ("Swapping new version bootlinks", error); glnx_autofd int ostree_dfd = -1; if (!glnx_opendirat (self->sysroot_fd, "ostree", TRUE, &ostree_dfd, error)) return FALSE; @@ -1628,6 +1635,7 @@ install_deployment_kernel (OstreeSysroot *sysroot, GError **error) { + GLNX_AUTO_PREFIX_ERROR ("Installing kernel", error); OstreeBootconfigParser *bootconfig = ostree_deployment_get_bootconfig (deployment); g_autofree char *deployment_dirpath = ostree_sysroot_get_deployment_dirpath (sysroot, deployment); glnx_autofd int deployment_dfd = -1; @@ -1936,23 +1944,18 @@ deployment_bootconfigs_equal (OstreeDeployment *a, return FALSE; { - OstreeBootconfigParser *a_bootconfig = ostree_deployment_get_bootconfig (a); - OstreeBootconfigParser *b_bootconfig = ostree_deployment_get_bootconfig (b); - const char *a_boot_options = ostree_bootconfig_parser_get (a_bootconfig, "options"); - const char *b_boot_options = ostree_bootconfig_parser_get (b_bootconfig, "options"); - g_autoptr(OstreeKernelArgs) a_kargs = NULL; - g_autoptr(OstreeKernelArgs) b_kargs = NULL; - g_autofree char *a_boot_options_without_ostree = NULL; - g_autofree char *b_boot_options_without_ostree = NULL; - /* We checksum the kernel arguments *except* ostree= */ - a_kargs = _ostree_kernel_args_from_string (a_boot_options); + OstreeBootconfigParser *a_bootconfig = ostree_deployment_get_bootconfig (a); + const char *a_boot_options = ostree_bootconfig_parser_get (a_bootconfig, "options"); + g_autoptr(OstreeKernelArgs) a_kargs = _ostree_kernel_args_from_string (a_boot_options); _ostree_kernel_args_replace (a_kargs, "ostree"); - a_boot_options_without_ostree = _ostree_kernel_args_to_string (a_kargs); + g_autofree char *a_boot_options_without_ostree = _ostree_kernel_args_to_string (a_kargs); - b_kargs = _ostree_kernel_args_from_string (b_boot_options); + OstreeBootconfigParser *b_bootconfig = ostree_deployment_get_bootconfig (b); + const char *b_boot_options = ostree_bootconfig_parser_get (b_bootconfig, "options"); + g_autoptr(OstreeKernelArgs) b_kargs = _ostree_kernel_args_from_string (b_boot_options); _ostree_kernel_args_replace (b_kargs, "ostree"); - b_boot_options_without_ostree = _ostree_kernel_args_to_string (b_kargs); + g_autofree char *b_boot_options_without_ostree = _ostree_kernel_args_to_string (b_kargs); if (strcmp (a_boot_options_without_ostree, b_boot_options_without_ostree) != 0) return FALSE; @@ -2083,12 +2086,7 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self, GError **error) { gboolean ret = FALSE; - guint i; - gboolean requires_new_bootversion = FALSE; - gboolean found_booted_deployment = FALSE; - gboolean bootloader_is_atomic = FALSE; gboolean boot_was_ro_mount = FALSE; - SyncStats syncstats = { 0, }; g_autoptr(OstreeBootloader) bootloader = NULL; g_assert (self->loaded); @@ -2102,11 +2100,12 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self, * matching bootloader configuration, then we can just swap the * subbootversion bootlinks. */ + gboolean requires_new_bootversion = FALSE; if (new_deployments->len != self->deployments->len) requires_new_bootversion = TRUE; else { - for (i = 0; i < new_deployments->len; i++) + for (guint i = 0; i < new_deployments->len; i++) { if (!deployment_bootconfigs_equal (new_deployments->pdata[i], self->deployments->pdata[i])) @@ -2117,7 +2116,8 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self, } } - for (i = 0; i < new_deployments->len; i++) + gboolean found_booted_deployment = FALSE; + for (guint i = 0; i < new_deployments->len; i++) { OstreeDeployment *deployment = new_deployments->pdata[i]; g_autoptr(GFile) deployment_root = NULL; @@ -2143,15 +2143,14 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self, goto out; } + gboolean bootloader_is_atomic = FALSE; + SyncStats syncstats = { 0, }; if (!requires_new_bootversion) { if (!create_new_bootlinks (self, self->bootversion, new_deployments, cancellable, error)) - { - g_prefix_error (error, "Creating new current bootlinks: "); - goto out; - } + goto out; if (!full_system_sync (self, &syncstats, cancellable, error)) { @@ -2162,10 +2161,7 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self, if (!swap_bootlinks (self, self->bootversion, new_deployments, cancellable, error)) - { - g_prefix_error (error, "Swapping current bootlinks: "); - goto out; - } + goto out; bootloader_is_atomic = TRUE; } @@ -2208,13 +2204,10 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self, /* Only show the osname in bootloader titles if there are multiple * osname's among the new deployments. Check for that here. */ - for (i = 1; i < new_deployments->len; i++) + for (guint i = 1; i < new_deployments->len; i++) { - const gchar *osname_0, *osname_i; - - osname_0 = ostree_deployment_get_osname (new_deployments->pdata[0]); - osname_i = ostree_deployment_get_osname (new_deployments->pdata[i]); - + const char *osname_0 = ostree_deployment_get_osname (new_deployments->pdata[0]); + const char *osname_i = ostree_deployment_get_osname (new_deployments->pdata[i]); if (!g_str_equal (osname_0, osname_i)) { show_osname = TRUE; @@ -2222,32 +2215,23 @@ ostree_sysroot_write_deployments_with_options (OstreeSysroot *self, } } - for (i = 0; i < new_deployments->len; i++) + for (guint i = 0; i < new_deployments->len; i++) { OstreeDeployment *deployment = new_deployments->pdata[i]; if (!install_deployment_kernel (self, repo, new_bootversion, deployment, new_deployments->len, show_osname, cancellable, error)) - { - g_prefix_error (error, "Installing kernel: "); - goto out; - } + goto out; } /* Create and swap bootlinks for *new* version */ if (!create_new_bootlinks (self, new_bootversion, new_deployments, cancellable, error)) - { - g_prefix_error (error, "Creating new version bootlinks: "); - goto out; - } + goto out; if (!swap_bootlinks (self, new_bootversion, new_deployments, cancellable, error)) - { - g_prefix_error (error, "Swapping new version bootlinks: "); - goto out; - } + goto out; g_debug ("Using bootloader: %s", bootloader ? g_type_name (G_TYPE_FROM_INSTANCE (bootloader)) : "(none)"); @@ -2438,10 +2422,7 @@ ostree_sysroot_deploy_tree (OstreeSysroot *self, glnx_autofd int deployment_dfd = -1; if (!checkout_deployment_tree (self, repo, new_deployment, &deployment_dfd, cancellable, error)) - { - g_prefix_error (error, "Checking out tree: "); - return FALSE; - } + return FALSE; g_autoptr(OstreeKernelLayout) kernel_layout = NULL; if (!get_kernel_from_tree (deployment_dfd, &kernel_layout, @@ -2461,10 +2442,7 @@ ostree_sysroot_deploy_tree (OstreeSysroot *self, deployment_dfd, &sepolicy, cancellable, error)) - { - g_prefix_error (error, "During /etc merge: "); - return FALSE; - } + return FALSE; if (!selinux_relabel_var_if_needed (self, sepolicy, os_deploy_dfd, cancellable, error)) @@ -2477,24 +2455,13 @@ ostree_sysroot_deploy_tree (OstreeSysroot *self, return FALSE; } - { g_auto(OstreeSepolicyFsCreatecon) con = { 0, }; - - if (!_ostree_sepolicy_preparefscreatecon (&con, sepolicy, - "/etc/ostree/remotes.d/dummy.conf", - 0644, error)) - return FALSE; - - /* Don't fsync here, as we assume that's all done in - * ostree_sysroot_write_deployments(). - */ - if (!write_origin_file_internal (self, new_deployment, NULL, - GLNX_FILE_REPLACE_NODATASYNC, - cancellable, error)) - { - g_prefix_error (error, "Writing out origin file: "); - return FALSE; - } - } + /* Don't fsync here, as we assume that's all done in + * ostree_sysroot_write_deployments(). + */ + if (!write_origin_file_internal (self, sepolicy, new_deployment, NULL, + GLNX_FILE_REPLACE_NODATASYNC, + cancellable, error)) + return FALSE; /* After this, install_deployment_kernel() will set the other boot * options and write it out to disk. diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h index 3ed6237b..b2776e8d 100644 --- a/src/libostree/ostree-sysroot-private.h +++ b/src/libostree/ostree-sysroot-private.h @@ -50,6 +50,11 @@ struct OstreeSysroot { GLnxLockFile lock; gboolean loaded; + gboolean ostree_booted; + gboolean root_is_sysroot; /* TRUE if sysroot_fd is pointed to rootfs "/" */ + /* The device/inode for /, used to detect booted deployment */ + dev_t root_device; + ino_t root_inode; gboolean is_physical; /* TRUE if we're pointed at physical storage root and not a deployment */ GPtrArray *deployments; diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c index 34799444..f77d7703 100644 --- a/src/libostree/ostree-sysroot.c +++ b/src/libostree/ostree-sysroot.c @@ -37,13 +37,6 @@ #include "ostree-bootloader-syslinux.h" #include "ostree-bootloader-grub2.h" -static gboolean -find_booted_deployment (OstreeSysroot *self, - GPtrArray *deployments, - OstreeDeployment **out_deployment, - GCancellable *cancellable, - GError **error); - /** * SECTION:ostree-sysroot * @title: Root partition mount point @@ -642,6 +635,24 @@ parse_deployment (OstreeSysroot *self, &deployment_dfd, error)) return FALSE; + /* See if this is the booted deployment */ + const gboolean looking_for_booted_deployment = + (self->ostree_booted && self->root_is_sysroot && + !self->booted_deployment); + gboolean is_booted_deployment = FALSE; + if (looking_for_booted_deployment) + { + struct stat stbuf; + if (!glnx_fstat (deployment_dfd, &stbuf, error)) + return FALSE; + /* A bit ugly, we're assigning to a sysroot-owned variable from deep in + * this parsing code. But eh, if something fails the sysroot state can't + * be relied on anyways. + */ + is_booted_deployment = (stbuf.st_dev == self->root_device && + stbuf.st_ino == self->root_inode); + } + g_autoptr(GKeyFile) origin = NULL; if (!parse_origin (self, deployment_dfd, deploy_basename, &origin, cancellable, error)) @@ -672,35 +683,32 @@ parse_deployment (OstreeSysroot *self, g_debug ("Deployment %s.%d unlocked=%d", treecsum, deployserial, ret_deployment->unlocked); + if (is_booted_deployment) + self->booted_deployment = g_object_ref (ret_deployment); if (out_deployment) *out_deployment = g_steal_pointer (&ret_deployment); return TRUE; } +/* Given a bootloader config, return the value part of the ostree= kernel + * argument. + */ static char * get_ostree_kernel_arg_from_config (OstreeBootconfigParser *config) { - const char *options; - char *ret = NULL; - char **opts, **iter; - - options = ostree_bootconfig_parser_get (config, "options"); + const char *options = ostree_bootconfig_parser_get (config, "options"); if (!options) return NULL; - opts = g_strsplit (options, " ", -1); - for (iter = opts; *iter; iter++) + g_auto(GStrv) opts = g_strsplit (options, " ", -1); + for (char **iter = opts; *iter; iter++) { const char *opt = *iter; if (g_str_has_prefix (opt, "ostree=")) - { - ret = g_strdup (opt + strlen ("ostree=")); - break; - } + return g_strdup (opt + strlen ("ostree=")); } - g_strfreev (opts); - return ret; + return NULL; } static gboolean @@ -797,6 +805,32 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self, if (!ensure_repo (self, error)) return FALSE; + /* Gather some global state; first if we have the global ostree-booted flag; + * we'll use it to sanity check that we found a booted deployment for example. + * Second, we also find out whether sysroot == /. + */ + if (!self->loaded) + { + if (!glnx_fstatat_allow_noent (AT_FDCWD, "/run/ostree-booted", NULL, 0, error)) + return FALSE; + self->ostree_booted = (errno == 0); + + { struct stat root_stbuf; + if (!glnx_fstatat (AT_FDCWD, "/", &root_stbuf, 0, error)) + return FALSE; + self->root_device = root_stbuf.st_dev; + self->root_inode = root_stbuf.st_ino; + } + + struct stat self_stbuf; + if (!glnx_fstat (self->sysroot_fd, &self_stbuf, error)) + return FALSE; + + self->root_is_sysroot = + (self->root_device == self_stbuf.st_dev && + self->root_inode == self_stbuf.st_ino); + } + int bootversion = 0; if (!read_current_bootversion (self, &bootversion, cancellable, error)) return FALSE; @@ -837,11 +871,19 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self, { OstreeBootconfigParser *config = boot_loader_configs->pdata[i]; + /* Note this also sets self->booted_deployment */ if (!list_deployments_process_one_boot_entry (self, config, deployments, cancellable, error)) - return FALSE; + { + g_clear_object (&self->booted_deployment); + return FALSE; + } } + if (self->ostree_booted && self->root_is_sysroot + && !self->booted_deployment) + return glnx_throw (error, "Unexpected state: /run/ostree-booted found and in / sysroot but not in a booted deployment"); + g_ptr_array_sort (deployments, compare_deployments_by_boot_loader_version_reversed); for (guint i = 0; i < deployments->len; i++) { @@ -849,10 +891,6 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self, ostree_deployment_set_index (deployment, i); } - if (!find_booted_deployment (self, deployments, &self->booted_deployment, - cancellable, error)) - return FALSE; - /* Determine whether we're "physical" or not, the first time we initialize */ if (!self->loaded) { @@ -920,13 +958,10 @@ ostree_sysroot_get_booted_deployment (OstreeSysroot *self) GPtrArray * ostree_sysroot_get_deployments (OstreeSysroot *self) { - GPtrArray *copy; - guint i; - g_return_val_if_fail (self->loaded, NULL); - copy = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); - for (i = 0; i < self->deployments->len; i++) + GPtrArray *copy = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); + for (guint i = 0; i < self->deployments->len; i++) g_ptr_array_add (copy, g_object_ref (self->deployments->pdata[i])); return copy; } @@ -1071,10 +1106,9 @@ char * _ostree_sysroot_join_lines (GPtrArray *lines) { GString *buf = g_string_new (""); - guint i; gboolean prev_was_empty = FALSE; - for (i = 0; i < lines->len; i++) + for (guint i = 0; i < lines->len; i++) { const char *line = lines->pdata[i]; /* Special bit to remove extraneous empty lines */ @@ -1091,84 +1125,6 @@ _ostree_sysroot_join_lines (GPtrArray *lines) return g_string_free (buf, FALSE); } -static gboolean -parse_kernel_commandline (OstreeKernelArgs **out_args, - GCancellable *cancellable, - GError **error) -{ - g_autoptr(GFile) proc_cmdline = g_file_new_for_path ("/proc/cmdline"); - g_autofree char *contents = NULL; - gsize len; - - if (!g_file_load_contents (proc_cmdline, cancellable, &contents, &len, NULL, - error)) - return FALSE; - - g_strchomp (contents); - *out_args = _ostree_kernel_args_from_string (contents); - return TRUE; -} - -static gboolean -find_booted_deployment (OstreeSysroot *self, - GPtrArray *deployments, - OstreeDeployment **out_deployment, - GCancellable *cancellable, - GError **error) -{ - struct stat root_stbuf; - struct stat self_stbuf; - g_autoptr(OstreeDeployment) ret_deployment = NULL; - - if (stat ("/", &root_stbuf) != 0) - return glnx_throw_errno_prefix (error, "stat /"); - - if (!ensure_sysroot_fd (self, error)) - return FALSE; - - if (fstat (self->sysroot_fd, &self_stbuf) != 0) - return glnx_throw_errno_prefix (error, "fstat"); - - if (root_stbuf.st_dev == self_stbuf.st_dev && - root_stbuf.st_ino == self_stbuf.st_ino) - { - g_autoptr(OstreeKernelArgs) kernel_args = NULL; - if (!parse_kernel_commandline (&kernel_args, cancellable, error)) - return FALSE; - - const char *bootlink_arg = _ostree_kernel_args_get_last_value (kernel_args, "ostree"); - if (bootlink_arg) - { - for (guint i = 0; i < deployments->len; i++) - { - OstreeDeployment *deployment = deployments->pdata[i]; - g_autofree char *deployment_path = ostree_sysroot_get_deployment_dirpath (self, deployment); - struct stat stbuf; - - if (fstatat (self->sysroot_fd, deployment_path, &stbuf, 0) != 0) - return glnx_throw_errno_prefix (error, "fstatat"); - - if (stbuf.st_dev == root_stbuf.st_dev && - stbuf.st_ino == root_stbuf.st_ino) - { - ret_deployment = g_object_ref (deployment); - break; - } - } - - if (ret_deployment == NULL) - return glnx_throw (error, "Unexpected state: ostree= kernel argument found, but / is not a deployment root"); - } - else - { - /* Not an ostree system */ - } - } - - ot_transfer_out_value (out_deployment, &ret_deployment); - return TRUE; -} - /** * ostree_sysroot_query_deployments_for: * @self: Sysroot @@ -1202,6 +1158,10 @@ ostree_sysroot_query_deployments_for (OstreeSysroot *self, { OstreeDeployment *deployment = self->deployments->pdata[i]; + /* Ignore deployments not for this osname */ + if (strcmp (ostree_deployment_get_osname (deployment), osname) != 0) + continue; + /* Is this deployment booted? If so, note we're past the booted */ if (self->booted_deployment != NULL && ostree_deployment_equal (deployment, self->booted_deployment)) @@ -1210,10 +1170,6 @@ ostree_sysroot_query_deployments_for (OstreeSysroot *self, continue; } - /* Ignore deployments not for this osname */ - if (strcmp (ostree_deployment_get_osname (deployment), osname) != 0) - continue; - if (!found_booted && !ret_pending) ret_pending = g_object_ref (deployment); else if (found_booted && !ret_rollback) @@ -1572,12 +1528,14 @@ ostree_sysroot_simple_write_deployment (OstreeSysroot *sysroot, /* Retain deployment if: * - we're explicitly asked to, or + * - it's pinned * - the deployment is for another osname, or * - we're keeping pending deployments and this is a pending deployment, or * - this is the merge or boot deployment, or * - we're keeping rollback deployments and this is a rollback deployment */ if (retain + || ostree_deployment_is_pinned (deployment) || !osname_matches || (retain_pending && !passed_crossover) || (is_booted || is_merge) @@ -1832,3 +1790,45 @@ ostree_sysroot_deployment_unlock (OstreeSysroot *self, return TRUE; } + +/** + * ostree_sysroot_deployment_set_pinned: + * @self: Sysroot + * @deployment: A deployment + * @is_pinned: Whether or not deployment will be automatically GC'd + * @error: Error + * + * By default, deployments may be subject to garbage collection. Typical uses of + * libostree only retain at most 2 deployments. If @is_pinned is `TRUE`, a + * metadata bit will be set causing libostree to avoid automatic GC of the + * deployment. However, this is really an "advisory" note; it's still possible + * for e.g. older versions of libostree unaware of pinning to GC the deployment. + * + * This function does nothing and returns successfully if the deployment + * is already in the desired pinning state. + * + * Since: 2018.3 + */ +gboolean +ostree_sysroot_deployment_set_pinned (OstreeSysroot *self, + OstreeDeployment *deployment, + gboolean is_pinned, + GError **error) +{ + const gboolean current_pin = ostree_deployment_is_pinned (deployment); + if (is_pinned == current_pin) + return TRUE; + + g_autoptr(OstreeDeployment) deployment_clone = ostree_deployment_clone (deployment); + GKeyFile *origin_clone = ostree_deployment_get_origin (deployment_clone); + + if (is_pinned) + g_key_file_set_boolean (origin_clone, OSTREE_ORIGIN_TRANSIENT_GROUP, "pinned", TRUE); + else + g_key_file_remove_key (origin_clone, OSTREE_ORIGIN_TRANSIENT_GROUP, "pinned", NULL); + + if (!ostree_sysroot_write_origin_file (self, deployment, origin_clone, NULL, error)) + return FALSE; + + return TRUE; +} diff --git a/src/libostree/ostree-sysroot.h b/src/libostree/ostree-sysroot.h index 830ed272..e4763d37 100644 --- a/src/libostree/ostree-sysroot.h +++ b/src/libostree/ostree-sysroot.h @@ -181,6 +181,12 @@ gboolean ostree_sysroot_deployment_set_mutable (OstreeSysroot *self, GCancellable *cancellable, GError **error); +_OSTREE_PUBLIC +gboolean ostree_sysroot_deployment_set_pinned (OstreeSysroot *self, + OstreeDeployment *deployment, + gboolean is_pinned, + GError **error); + _OSTREE_PUBLIC gboolean ostree_sysroot_deployment_unlock (OstreeSysroot *self, OstreeDeployment *deployment, diff --git a/src/libostree/ostree-version.h b/src/libostree/ostree-version.h index fb3ba5d7..1f843105 100644 --- a/src/libostree/ostree-version.h +++ b/src/libostree/ostree-version.h @@ -43,7 +43,7 @@ * * Since: 2017.4 */ -#define OSTREE_RELEASE_VERSION (2) +#define OSTREE_RELEASE_VERSION (4) /** * OSTREE_VERSION @@ -52,7 +52,7 @@ * * Since: 2017.4 */ -#define OSTREE_VERSION (2018.2) +#define OSTREE_VERSION (2018.4) /** * OSTREE_VERSION_S: @@ -62,7 +62,7 @@ * * Since: 2017.4 */ -#define OSTREE_VERSION_S "2018.2" +#define OSTREE_VERSION_S "2018.4" #define OSTREE_ENCODE_VERSION(year,release) \ ((year) << 16 | (release)) diff --git a/src/ostree/main.c b/src/ostree/main.c index ddf531d1..9ed0f880 100644 --- a/src/ostree/main.c +++ b/src/ostree/main.c @@ -141,15 +141,10 @@ main (int argc, if (error != NULL) { - int is_tty = isatty (1); - const char *prefix = ""; - const char *suffix = ""; - if (is_tty) - { - prefix = "\x1b[31m\x1b[1m"; /* red, bold */ - suffix = "\x1b[22m\x1b[0m"; /* bold off, color reset */ - } - g_printerr ("%serror: %s%s\n", prefix, suffix, error->message); + g_printerr ("%s%serror:%s%s %s\n", + ot_get_red_start (), ot_get_bold_start (), + ot_get_bold_end (), ot_get_red_end (), + error->message); } return ret; diff --git a/src/ostree/ostree-trivial-httpd.c b/src/ostree/ostree-trivial-httpd.c index 377de525..5da5a6ab 100644 --- a/src/ostree/ostree-trivial-httpd.c +++ b/src/ostree/ostree-trivial-httpd.c @@ -680,15 +680,10 @@ main (int argc, if (!run (argc, argv, cancellable, &error)) { - int is_tty = isatty (1); - const char *prefix = ""; - const char *suffix = ""; - if (is_tty) - { - prefix = "\x1b[31m\x1b[1m"; /* red, bold */ - suffix = "\x1b[22m\x1b[0m"; /* bold off, color reset */ - } - g_printerr ("%serror: %s%s\n", prefix, suffix, error->message); + g_printerr ("%s%serror:%s%s %s\n", + ot_get_red_start (), ot_get_bold_start (), + ot_get_bold_end (), ot_get_red_end (), + error->message); return 1; } diff --git a/src/ostree/ot-admin-builtin-cleanup.c b/src/ostree/ot-admin-builtin-cleanup.c index 4028931e..a4753030 100644 --- a/src/ostree/ot-admin-builtin-cleanup.c +++ b/src/ostree/ot-admin-builtin-cleanup.c @@ -30,11 +30,6 @@ #include -/* ATTENTION: - * Please remember to update the bash-completion script (bash/ostree) and - * man page (man/ostree-admin-cleanup.xml) when changing the option list. - */ - static GOptionEntry options[] = { { NULL } }; diff --git a/src/ostree/ot-admin-builtin-deploy.c b/src/ostree/ot-admin-builtin-deploy.c index ee7f3ccf..d9905212 100644 --- a/src/ostree/ot-admin-builtin-deploy.c +++ b/src/ostree/ot-admin-builtin-deploy.c @@ -45,11 +45,6 @@ static char *opt_osname; static char *opt_origin_path; static gboolean opt_kernel_arg_none; -/* ATTENTION: - * Please remember to update the bash-completion script (bash/ostree) and - * man page (man/ostree-admin-deploy.xml) when changing the option list. - */ - static GOptionEntry options[] = { { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Use a different operating system root than the current one", "OSNAME" }, { "origin-file", 0, 0, G_OPTION_ARG_FILENAME, &opt_origin_path, "Specify origin file", "FILENAME" }, diff --git a/src/ostree/ot-admin-builtin-diff.c b/src/ostree/ot-admin-builtin-diff.c index 6a44a72b..27855881 100644 --- a/src/ostree/ot-admin-builtin-diff.c +++ b/src/ostree/ot-admin-builtin-diff.c @@ -32,11 +32,6 @@ static char *opt_osname; -/* ATTENTION: - * Please remember to update the bash-completion script (bash/ostree) and - * man page (man/ostree-admin-config-diff.xml) when changing the option list. - */ - static GOptionEntry options[] = { { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Use a different operating system root than the current one", "OSNAME" }, { NULL } diff --git a/src/ostree/ot-admin-builtin-init-fs.c b/src/ostree/ot-admin-builtin-init-fs.c index 70348402..cca63a62 100644 --- a/src/ostree/ot-admin-builtin-init-fs.c +++ b/src/ostree/ot-admin-builtin-init-fs.c @@ -30,11 +30,6 @@ #include -/* ATTENTION: - * Please remember to update the bash-completion script (bash/ostree) and - * man page (man/ostree-admin-init-fs.xml) when changing the option list. - */ - static GOptionEntry options[] = { { NULL } }; diff --git a/src/ostree/ot-admin-builtin-os-init.c b/src/ostree/ot-admin-builtin-os-init.c index 411e415e..203f297b 100644 --- a/src/ostree/ot-admin-builtin-os-init.c +++ b/src/ostree/ot-admin-builtin-os-init.c @@ -30,11 +30,6 @@ #include -/* ATTENTION: - * Please remember to update the bash-completion script (bash/ostree) and - * man page (man/ostree-admin-os-init.xml) when changing the option list. - */ - static GOptionEntry options[] = { { NULL } }; diff --git a/src/ostree/ot-admin-builtin-pin.c b/src/ostree/ot-admin-builtin-pin.c new file mode 100644 index 00000000..f110983b --- /dev/null +++ b/src/ostree/ot-admin-builtin-pin.c @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2018 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. + */ + +#include "config.h" + +#include + +#include "ot-main.h" +#include "ot-admin-builtins.h" +#include "ot-admin-functions.h" +#include "ostree.h" +#include "otutil.h" + +static gboolean opt_unpin; + +static GOptionEntry options[] = { + { "unpin", 'u', 0, G_OPTION_ARG_NONE, &opt_unpin, "Unset pin", NULL }, + { NULL } +}; + +gboolean +ot_admin_builtin_pin (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) +{ + g_autoptr(GOptionContext) context = g_option_context_new ("INDEX"); + g_autoptr(OstreeSysroot) sysroot = NULL; + if (!ostree_admin_option_context_parse (context, options, &argc, &argv, + OSTREE_ADMIN_BUILTIN_FLAG_SUPERUSER, + invocation, &sysroot, cancellable, error)) + return FALSE; + + if (argc < 2) + { + ot_util_usage_error (context, "INDEX must be specified", error); + return FALSE; + } + + const char *deploy_index_str = argv[1]; + const int deploy_index = atoi (deploy_index_str); + + g_autoptr(OstreeDeployment) target_deployment = ot_admin_get_indexed_deployment (sysroot, deploy_index, error); + if (!target_deployment) + return FALSE; + + + gboolean current_pin = ostree_deployment_is_pinned (target_deployment); + const gboolean desired_pin = !opt_unpin; + if (current_pin == desired_pin) + g_print ("Deployment is already %s\n", current_pin ? "pinned" : "unpinned"); + else + { + if (!ostree_sysroot_deployment_set_pinned (sysroot, target_deployment, desired_pin, error)) + return FALSE; + g_print ("Deployment is now %s\n", desired_pin ? "pinned" : "unpinned"); + } + + return TRUE; +} diff --git a/src/ostree/ot-admin-builtin-set-origin.c b/src/ostree/ot-admin-builtin-set-origin.c index 77f14bf5..07453a87 100644 --- a/src/ostree/ot-admin-builtin-set-origin.c +++ b/src/ostree/ot-admin-builtin-set-origin.c @@ -35,11 +35,6 @@ static int opt_index = -1; static char **opt_set; -/* ATTENTION: - * Please remember to update the bash-completion script (bash/ostree) and - * man page (man/ostree-admin-set-origin.xml) when changing the option list. - */ - static GOptionEntry options[] = { { "set", 's', 0, G_OPTION_ARG_STRING_ARRAY, &opt_set, "Set config option KEY=VALUE for remote", "KEY=VALUE" }, { "index", 0, 0, G_OPTION_ARG_INT, &opt_index, "Operate on the deployment INDEX, starting from zero", "INDEX" }, diff --git a/src/ostree/ot-admin-builtin-status.c b/src/ostree/ot-admin-builtin-status.c index 0279c5af..096155c6 100644 --- a/src/ostree/ot-admin-builtin-status.c +++ b/src/ostree/ot-admin-builtin-status.c @@ -27,14 +27,10 @@ #include "ot-admin-builtins.h" #include "ot-admin-functions.h" #include "ostree.h" +#include "libglnx.h" #include -/* ATTENTION: - * Please remember to update the bash-completion script (bash/ostree) and - * man page (man/ostree-admin-status.xml) when changing the option list. - */ - static GOptionEntry options[] = { { NULL } }; @@ -66,13 +62,116 @@ deployment_get_gpg_verify (OstreeDeployment *deployment, return gpg_verify; } + +static gboolean +deployment_print_status (OstreeSysroot *sysroot, + OstreeRepo *repo, + OstreeDeployment *deployment, + gboolean is_booted, + gboolean is_pending, + gboolean is_rollback, + GCancellable *cancellable, + GError **error) +{ + const char *ref = ostree_deployment_get_csum (deployment); + + /* Load the backing commit; shouldn't normally fail, but if it does, + * we stumble on. + */ + g_autoptr(GVariant) commit = NULL; + (void)ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, ref, + &commit, NULL); + g_autoptr(GVariant) commit_metadata = NULL; + if (commit) + commit_metadata = g_variant_get_child_value (commit, 0); + + const char *version = NULL; + const char *source_title = NULL; + if (commit_metadata) + { + (void) g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_VERSION, "&s", &version); + (void) g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_SOURCE_TITLE, "&s", &source_title); + } + + GKeyFile *origin = ostree_deployment_get_origin (deployment); + + const char *deployment_status = ""; + if (is_pending) + deployment_status = " (pending)"; + else if (is_rollback) + deployment_status = " (rollback)"; + g_print ("%c %s %s.%d%s\n", + is_booted ? '*' : ' ', + ostree_deployment_get_osname (deployment), + ostree_deployment_get_csum (deployment), + ostree_deployment_get_deployserial (deployment), + deployment_status); + if (version) + g_print (" Version: %s\n", version); + + OstreeDeploymentUnlockedState unlocked = ostree_deployment_get_unlocked (deployment); + switch (unlocked) + { + case OSTREE_DEPLOYMENT_UNLOCKED_NONE: + break; + default: + g_print (" %s%sUnlocked: %s%s%s\n", ot_get_red_start (), ot_get_bold_start (), + ostree_deployment_unlocked_state_to_string (unlocked), + ot_get_bold_end (), ot_get_red_end ()); + } + if (ostree_deployment_is_pinned (deployment)) + g_print (" Pinned: yes\n"); + if (!origin) + g_print (" origin: none\n"); + else + { + g_autofree char *origin_refspec = g_key_file_get_string (origin, "origin", "refspec", NULL); + if (!origin_refspec) + g_print (" origin: \n"); + else + g_print (" origin refspec: %s\n", origin_refspec); + if (source_title) + g_print (" `- %s\n", source_title); + } + + if (deployment_get_gpg_verify (deployment, repo)) + { + g_autoptr(GString) output_buffer = g_string_sized_new (256); + /* Print any digital signatures on this commit. */ + + g_autoptr(GError) local_error = NULL; + g_autoptr(OstreeGpgVerifyResult) result = + ostree_repo_verify_commit_ext (repo, ref, NULL, NULL, + cancellable, &local_error); + + /* G_IO_ERROR_NOT_FOUND just means the commit is not signed. */ + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_clear_error (&local_error); + return TRUE; + } + else if (local_error != NULL) + { + g_propagate_error (error, g_steal_pointer (&local_error)); + return FALSE; + } + + const guint n_signatures = ostree_gpg_verify_result_count_all (result); + for (guint jj = 0; jj < n_signatures; jj++) + { + ostree_gpg_verify_result_describe (result, jj, output_buffer, " GPG: ", + OSTREE_GPG_SIGNATURE_FORMAT_DEFAULT); + } + + g_print ("%s", output_buffer->str); + } + + return TRUE; +} + gboolean ot_admin_builtin_status (int argc, char **argv, OstreeCommandInvocation *invocation, GCancellable *cancellable, GError **error) { - const int is_tty = isatty (1); - const char *red_bold_prefix = is_tty ? "\x1b[31m\x1b[1m" : ""; - const char *red_bold_suffix = is_tty ? "\x1b[22m\x1b[0m" : ""; - g_autoptr(GOptionContext) context = g_option_context_new (""); g_autoptr(OstreeSysroot) sysroot = NULL; @@ -103,96 +202,13 @@ ot_admin_builtin_status (int argc, char **argv, OstreeCommandInvocation *invocat for (guint i = 0; i < deployments->len; i++) { OstreeDeployment *deployment = deployments->pdata[i]; - const char *ref = ostree_deployment_get_csum (deployment); - - /* Load the backing commit; shouldn't normally fail, but if it does, - * we stumble on. - */ - g_autoptr(GVariant) commit = NULL; - (void)ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, ref, - &commit, NULL); - g_autoptr(GVariant) commit_metadata = NULL; - if (commit) - commit_metadata = g_variant_get_child_value (commit, 0); - - const char *version = NULL; - const char *source_title = NULL; - if (commit_metadata) - { - (void) g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_VERSION, "&s", &version); - (void) g_variant_lookup (commit_metadata, OSTREE_COMMIT_META_KEY_SOURCE_TITLE, "&s", &source_title); - } - - GKeyFile *origin = ostree_deployment_get_origin (deployment); - - const char *deployment_status = ""; - if (deployment == pending_deployment) - deployment_status = " (pending)"; - else if (deployment == rollback_deployment) - deployment_status = " (rollback)"; - g_print ("%c %s %s.%d%s\n", - deployment == booted_deployment ? '*' : ' ', - ostree_deployment_get_osname (deployment), - ostree_deployment_get_csum (deployment), - ostree_deployment_get_deployserial (deployment), - deployment_status); - if (version) - g_print (" Version: %s\n", version); - - OstreeDeploymentUnlockedState unlocked = ostree_deployment_get_unlocked (deployment); - switch (unlocked) - { - case OSTREE_DEPLOYMENT_UNLOCKED_NONE: - break; - default: - g_print (" %sUnlocked: %s%s\n", red_bold_prefix, - ostree_deployment_unlocked_state_to_string (unlocked), - red_bold_suffix); - } - if (!origin) - g_print (" origin: none\n"); - else - { - g_autofree char *origin_refspec = g_key_file_get_string (origin, "origin", "refspec", NULL); - if (!origin_refspec) - g_print (" origin: \n"); - else - g_print (" origin refspec: %s\n", origin_refspec); - if (source_title) - g_print (" `- %s\n", source_title); - } - - if (deployment_get_gpg_verify (deployment, repo)) - { - g_autoptr(GString) output_buffer = g_string_sized_new (256); - /* Print any digital signatures on this commit. */ - - g_autoptr(GError) local_error = NULL; - g_autoptr(OstreeGpgVerifyResult) result = - ostree_repo_verify_commit_ext (repo, ref, NULL, NULL, - cancellable, &local_error); - - /* G_IO_ERROR_NOT_FOUND just means the commit is not signed. */ - if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) - { - g_clear_error (&local_error); - continue; - } - else if (local_error != NULL) - { - g_propagate_error (error, g_steal_pointer (&local_error)); - return FALSE; - } - - const guint n_signatures = ostree_gpg_verify_result_count_all (result); - for (guint jj = 0; jj < n_signatures; jj++) - { - ostree_gpg_verify_result_describe (result, jj, output_buffer, " GPG: ", - OSTREE_GPG_SIGNATURE_FORMAT_DEFAULT); - } - - g_print ("%s", output_buffer->str); - } + if (!deployment_print_status (sysroot, repo, deployment, + deployment == booted_deployment, + deployment == pending_deployment, + deployment == rollback_deployment, + cancellable, + error)) + return FALSE; } } diff --git a/src/ostree/ot-admin-builtin-switch.c b/src/ostree/ot-admin-builtin-switch.c index 7609b25a..2f12ef1d 100644 --- a/src/ostree/ot-admin-builtin-switch.c +++ b/src/ostree/ot-admin-builtin-switch.c @@ -34,11 +34,6 @@ static gboolean opt_reboot; static char *opt_osname; -/* ATTENTION: - * Please remember to update the bash-completion script (bash/ostree) and - * man page (man/ostree-admin-switch.xml) when changing the option list. - */ - static GOptionEntry options[] = { { "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot, "Reboot after switching trees", NULL }, { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Use a different operating system root than the current one", "OSNAME" }, diff --git a/src/ostree/ot-admin-builtin-undeploy.c b/src/ostree/ot-admin-builtin-undeploy.c index 6f070524..0a50dc0d 100644 --- a/src/ostree/ot-admin-builtin-undeploy.c +++ b/src/ostree/ot-admin-builtin-undeploy.c @@ -29,11 +29,6 @@ #include "ostree.h" #include "otutil.h" -/* ATTENTION: - * Please remember to update the bash-completion script (bash/ostree) and - * man page (man/ostree-admin-undeploy.xml) when changing the option list. - */ - static GOptionEntry options[] = { { NULL } }; diff --git a/src/ostree/ot-admin-builtin-unlock.c b/src/ostree/ot-admin-builtin-unlock.c index 34ff08cd..f0efa44a 100644 --- a/src/ostree/ot-admin-builtin-unlock.c +++ b/src/ostree/ot-admin-builtin-unlock.c @@ -34,11 +34,6 @@ static gboolean opt_hotfix; -/* ATTENTION: - * Please remember to update the bash-completion script (bash/ostree) and - * man page (man/ostree-admin-unlock.xml) when changing the option list. - */ - static GOptionEntry options[] = { { "hotfix", 0, 0, G_OPTION_ARG_NONE, &opt_hotfix, "Retain changes across reboots", NULL }, { NULL } diff --git a/src/ostree/ot-admin-builtin-upgrade.c b/src/ostree/ot-admin-builtin-upgrade.c index e42ded6c..eafe8bce 100644 --- a/src/ostree/ot-admin-builtin-upgrade.c +++ b/src/ostree/ot-admin-builtin-upgrade.c @@ -40,11 +40,6 @@ static gboolean opt_deploy_only; static char *opt_osname; static char *opt_override_commit; -/* ATTENTION: - * Please remember to update the bash-completion script (bash/ostree) and - * man page (man/ostree-admin-upgrade.xml) when changing the option list. - */ - static GOptionEntry options[] = { { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Use a different operating system root than the current one", "OSNAME" }, { "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot, "Reboot after a successful upgrade", NULL }, @@ -88,33 +83,18 @@ ot_admin_builtin_upgrade (int argc, char **argv, OstreeCommandInvocation *invoca g_autoptr(GKeyFile) origin = ostree_sysroot_upgrader_dup_origin (upgrader); if (origin != NULL) { - gboolean origin_changed = FALSE; - + /* Should we consider requiring --discard-hotfix here? */ + ostree_deployment_origin_remove_transient_state (origin); if (opt_override_commit != NULL) { /* Override the commit to pull and deploy. */ g_key_file_set_string (origin, "origin", "override-commit", opt_override_commit); - origin_changed = TRUE; - } - else - { - /* Strip any override-commit from the origin file so - * we always upgrade to the latest available commit. */ - origin_changed = g_key_file_remove_key (origin, "origin", - "override-commit", NULL); } - /* Should we consider requiring --discard-hotfix here? */ - origin_changed |= g_key_file_remove_key (origin, "origin", "unlocked", NULL); - - if (origin_changed) - { - /* XXX GCancellable parameter is not used. */ - if (!ostree_sysroot_upgrader_set_origin (upgrader, origin, NULL, error)) - return FALSE; - } + if (!ostree_sysroot_upgrader_set_origin (upgrader, origin, NULL, error)) + return FALSE; } gboolean changed; diff --git a/src/ostree/ot-admin-builtins.h b/src/ostree/ot-admin-builtins.h index 39996243..a81f4d62 100644 --- a/src/ostree/ot-admin-builtins.h +++ b/src/ostree/ot-admin-builtins.h @@ -39,6 +39,7 @@ BUILTINPROTO(init_fs); BUILTINPROTO(undeploy); BUILTINPROTO(deploy); BUILTINPROTO(cleanup); +BUILTINPROTO(pin); BUILTINPROTO(unlock); BUILTINPROTO(status); BUILTINPROTO(set_origin); diff --git a/src/ostree/ot-admin-instutil-builtin-grub2-generate.c b/src/ostree/ot-admin-instutil-builtin-grub2-generate.c index df9d804b..3ab5c245 100644 --- a/src/ostree/ot-admin-instutil-builtin-grub2-generate.c +++ b/src/ostree/ot-admin-instutil-builtin-grub2-generate.c @@ -28,11 +28,6 @@ #include "otutil.h" -/* ATTENTION: - * Please remember to update the bash-completion script (bash/ostree) and - * man page (man/ostree-admin-instutil.xml) when changing the option list. - */ - static GOptionEntry options[] = { { NULL } }; diff --git a/src/ostree/ot-admin-instutil-builtin-selinux-ensure-labeled.c b/src/ostree/ot-admin-instutil-builtin-selinux-ensure-labeled.c index 3b546711..8bf75b31 100644 --- a/src/ostree/ot-admin-instutil-builtin-selinux-ensure-labeled.c +++ b/src/ostree/ot-admin-instutil-builtin-selinux-ensure-labeled.c @@ -173,11 +173,6 @@ selinux_relabel_dir (OstreeSePolicy *sepolicy, return ret; } -/* ATTENTION: - * Please remember to update the bash-completion script (bash/ostree) and - * man page (man/ostree-admin-instutil.xml) when changing the option list. - */ - static GOptionEntry options[] = { { NULL } }; diff --git a/src/ostree/ot-admin-instutil-builtin-set-kargs.c b/src/ostree/ot-admin-instutil-builtin-set-kargs.c index 1194f82d..666e5369 100644 --- a/src/ostree/ot-admin-instutil-builtin-set-kargs.c +++ b/src/ostree/ot-admin-instutil-builtin-set-kargs.c @@ -34,11 +34,6 @@ static gboolean opt_merge; static char **opt_replace; static char **opt_append; -/* ATTENTION: - * Please remember to update the bash-completion script (bash/ostree) and - * man page (man/ostree-admin-instutil.xml) when changing the option list. - */ - static GOptionEntry options[] = { { "import-proc-cmdline", 0, 0, G_OPTION_ARG_NONE, &opt_proc_cmdline, "Import current /proc/cmdline", NULL }, { "merge", 0, 0, G_OPTION_ARG_NONE, &opt_merge, "Merge with previous command line", NULL }, diff --git a/src/ostree/ot-builtin-admin.c b/src/ostree/ot-builtin-admin.c index f4e687e9..fd6d9a8f 100644 --- a/src/ostree/ot-builtin-admin.c +++ b/src/ostree/ot-builtin-admin.c @@ -54,6 +54,9 @@ static OstreeCommand admin_subcommands[] = { { "set-origin", OSTREE_BUILTIN_FLAG_NO_REPO, ot_admin_builtin_set_origin, "Set Origin and create a new origin file" }, + { "pin", OSTREE_BUILTIN_FLAG_NO_REPO, + ot_admin_builtin_pin, + "Change the \"pinning\" state of a deployment" }, { "status", OSTREE_BUILTIN_FLAG_NO_REPO, ot_admin_builtin_status, "List deployments" }, diff --git a/src/ostree/ot-builtin-prune.c b/src/ostree/ot-builtin-prune.c index 3f39dfe8..c34bbf4c 100644 --- a/src/ostree/ot-builtin-prune.c +++ b/src/ostree/ot-builtin-prune.c @@ -174,6 +174,15 @@ ostree_builtin_prune (int argc, char **argv, OstreeCommandInvocation *invocation else if (!delete_commit (repo, opt_delete_commit, cancellable, error)) return FALSE; } + else + { + /* In the future we should make this useful, but for now let's + * error out since what we were doing before was very misleading. + * https://github.com/ostreedev/ostree/issues/1479 + */ + if (opt_static_deltas_only) + return glnx_throw (error, "--static-deltas-only requires --delete-commit; see https://github.com/ostreedev/ostree/issues/1479"); + } OstreeRepoPruneFlags pruneflags = 0; if (opt_refs_only) diff --git a/src/ostree/ot-builtin-pull.c b/src/ostree/ot-builtin-pull.c index c8e12e2d..e2d1f09a 100644 --- a/src/ostree/ot-builtin-pull.c +++ b/src/ostree/ot-builtin-pull.c @@ -41,6 +41,7 @@ static gboolean opt_bareuseronly_files; static char** opt_subpaths; static char** opt_http_headers; static char* opt_cache_dir; +static char* opt_append_user_agent; static int opt_depth = 0; static int opt_frequency = 0; static char* opt_url; @@ -69,6 +70,8 @@ static GOptionEntry options[] = { { "update-frequency", 0, 0, G_OPTION_ARG_INT, &opt_frequency, "Sets the update frequency, in milliseconds (0=1000ms) (default: 0)", "FREQUENCY" }, { "localcache-repo", 'L', 0, G_OPTION_ARG_FILENAME_ARRAY, &opt_localcache_repos, "Add REPO as local cache source for objects during this pull", "REPO" }, { "timestamp-check", 'T', 0, G_OPTION_ARG_NONE, &opt_timestamp_check, "Require fetched commits to have newer timestamps", NULL }, + /* let's leave this hidden for now; we just need it for tests */ + { "append-user-agent", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &opt_append_user_agent, "Append string to user agent", NULL }, { NULL } }; @@ -333,6 +336,10 @@ ostree_builtin_pull (int argc, char **argv, OstreeCommandInvocation *invocation, g_variant_new_variant (g_variant_builder_end (&hdr_builder))); } + if (opt_append_user_agent) + g_variant_builder_add (&builder, "{s@v}", "append-user-agent", + g_variant_new_variant (g_variant_new_string (opt_append_user_agent))); + if (!opt_dry_run) { if (console.is_tty) diff --git a/src/ostree/ot-main.h b/src/ostree/ot-main.h index c5ffeb7f..ac75118c 100644 --- a/src/ostree/ot-main.h +++ b/src/ostree/ot-main.h @@ -91,3 +91,18 @@ gboolean ostree_ensure_repo_writable (OstreeRepo *repo, GError **error); void ostree_print_gpg_verify_result (OstreeGpgVerifyResult *result); gboolean ot_enable_tombstone_commits (OstreeRepo *repo, GError **error); + +/* Copied from rpm-ostree's rpmostree-libbuiltin.h */ +#define TERM_ESCAPE_SEQUENCE(type,seq) \ + static inline const char* ot_get_##type (void) { \ + if (glnx_stdout_is_tty ()) \ + return seq; \ + return ""; \ + } + +TERM_ESCAPE_SEQUENCE(red_start, "\x1b[31m") +TERM_ESCAPE_SEQUENCE(red_end, "\x1b[22m") +TERM_ESCAPE_SEQUENCE(bold_start, "\x1b[1m") +TERM_ESCAPE_SEQUENCE(bold_end, "\x1b[0m") + +#undef TERM_ESCAPE_SEQUENCE diff --git a/src/switchroot/ostree-mount-util.h b/src/switchroot/ostree-mount-util.h index 7cf7e09e..08e10f97 100644 --- a/src/switchroot/ostree-mount-util.h +++ b/src/switchroot/ostree-mount-util.h @@ -104,4 +104,19 @@ read_proc_cmdline_ostree (void) return ret; } +/* This is an API for other projects to determine whether or not the + * currently running system is ostree-controlled. + */ +static inline void +touch_run_ostree (void) +{ + int fd = open ("/run/ostree-booted", O_CREAT | O_WRONLY | O_NOCTTY | O_CLOEXEC, 0640); + /* We ignore failures here in case /run isn't mounted...not much we + * can do about that, but we don't want to fail. + */ + if (fd == -1) + return; + (void) close (fd); +} + #endif /* __OSTREE_MOUNT_UTIL_H_ */ diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c index 0b04189e..a5c3c785 100644 --- a/src/switchroot/ostree-prepare-root.c +++ b/src/switchroot/ostree-prepare-root.c @@ -48,23 +48,6 @@ #include "ostree-mount-util.h" -/* This is an API for other projects to determine whether or not the - * currently running system is ostree-controlled. - */ -static void -touch_run_ostree (void) -{ - int fd; - - fd = open ("/run/ostree-booted", O_CREAT | O_WRONLY | O_NOCTTY | O_CLOEXEC, 0640); - /* We ignore failures here in case /run isn't mounted...not much we - * can do about that, but we don't want to fail. - */ - if (fd == -1) - return; - (void) close (fd); -} - static char* resolve_deploy_path (const char * root_mountpoint) { @@ -205,7 +188,13 @@ main(int argc, char *argv[]) err (EXIT_FAILURE, "failed to bind mount (class:readonly) /usr"); } - touch_run_ostree (); + + /* We only stamp /run now if we're running in an initramfs, i.e. we're + * not pid 1. Otherwise it's handled later via ostree-remount.service. + * https://mail.gnome.org/archives/ostree-list/2018-March/msg00012.html + */ + if (getpid () != 1) + touch_run_ostree (); if (strcmp(root_mountpoint, "/") == 0) { diff --git a/src/switchroot/ostree-remount.c b/src/switchroot/ostree-remount.c index 3644a063..c3e39c0b 100644 --- a/src/switchroot/ostree-remount.c +++ b/src/switchroot/ostree-remount.c @@ -46,6 +46,16 @@ main(int argc, char *argv[]) struct stat stbuf; int i; + /* See comments in ostree-prepare-root.c for this. + * + * This service is triggered via + * ConditionKernelCommandLine=ostree + * but it's a lot easier for various bits of userspace to check for + * a file versus parsing the kernel cmdline. So let's ensure + * the stamp file is created here too. + */ + touch_run_ostree (); + /* The /sysroot mount needs to be private to avoid having a mount for e.g. /var/cache * also propagate to /sysroot/ostree/deploy/$stateroot/var/cache * diff --git a/tests/basic-test.sh b/tests/basic-test.sh index 3376ac58..b8e7eb07 100644 --- a/tests/basic-test.sh +++ b/tests/basic-test.sh @@ -21,7 +21,7 @@ set -euo pipefail -echo "1..$((82 + ${extra_basic_tests:-0}))" +echo "1..$((83 + ${extra_basic_tests:-0}))" CHECKOUT_U_ARG="" CHECKOUT_H_ARGS="-H" @@ -476,6 +476,17 @@ cd ${test_tmpdir} $OSTREE prune echo "ok prune didn't fail" +# https://github.com/ostreedev/ostree/issues/1467 +cd ${test_tmpdir} +mv repo/refs/remotes{,.orig} +if $OSTREE refs --list >/dev/null 2>err.txt; then + fatal "listed refs without remotes dir?" +fi +assert_file_has_content err.txt 'Listing refs.*opendir.*No such file or directory' +mv repo/refs/remotes{.orig,} +$OSTREE refs --list >/dev/null +echo "ok refs enoent error" + cd ${test_tmpdir} # Verify we can't cat dirs for path in / /baz; do diff --git a/tests/bootloader-entries-crosscheck.py b/tests/bootloader-entries-crosscheck.py index 38e8e451..41f6956e 100755 --- a/tests/bootloader-entries-crosscheck.py +++ b/tests/bootloader-entries-crosscheck.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 # # Copyright (C) 2015 Red Hat # @@ -38,8 +38,8 @@ def fatal(msg): sys.stderr.write('\n') sys.exit(1) -def compare_entries_descending(a, b): - return int(b['version']) - int(a['version']) +def entry_get_version(entry): + return int(entry['version']) def get_ostree_option(optionstring): for o in optionstring.split(): @@ -65,7 +65,7 @@ for fname in os.listdir(loaderpath): v = line[s+1:] entry[k] = v entries.append(entry) - entries.sort(compare_entries_descending) + entries.sort(key=entry_get_version, reverse=True) # Parse SYSLINUX config with open(syslinuxpath) as f: diff --git a/tests/libtest.sh b/tests/libtest.sh index 9591a88f..586bf9ef 100755 --- a/tests/libtest.sh +++ b/tests/libtest.sh @@ -234,10 +234,9 @@ ostree_repo_init() { # The original one; use setup_fake_remote_repo2 for newer code, # down the line we'll try to port tests. setup_fake_remote_repo1() { - mode=$1 - commit_opts=${2:-} - args=${3:-} - shift + mode=$1; shift + commit_opts=${1:-} + [ $# -eq 0 ] || shift oldpwd=`pwd` mkdir ostree-srv cd ostree-srv @@ -263,7 +262,7 @@ setup_fake_remote_repo1() { mkdir ${test_tmpdir}/httpd cd httpd ln -s ${test_tmpdir}/ostree-srv ostree - ${OSTREE_HTTPD} --autoexit --log-file $(pwd)/httpd.log --daemonize -p ${test_tmpdir}/httpd-port $args + ${OSTREE_HTTPD} --autoexit --log-file $(pwd)/httpd.log --daemonize -p ${test_tmpdir}/httpd-port "$@" port=$(cat ${test_tmpdir}/httpd-port) echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address cd ${oldpwd} diff --git a/tests/test-admin-deploy-2.sh b/tests/test-admin-deploy-2.sh index 4ecaf67a..eab0a3d3 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..3" +echo "1..6" ${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) @@ -63,3 +63,50 @@ assert_has_dir sysroot/boot/ostree/testos-${bootcsum} assert_file_has_content sysroot/ostree/deploy/testos/deploy/${newrev}.0/etc/os-release 'NAME=TestOS' echo "ok manual cleanup" + +assert_n_pinned() { + local n=$1 + ${CMD_PREFIX} ostree admin status > status.txt + local n_pinned="$(grep -F -c -e 'Pinned: yes' < status.txt)" + if test "${n_pinned}" '!=' "${n}"; then + cat status.txt + fatal "${n_pinned} != ${n}" + fi +} +assert_n_deployments() { + local n=$1 + ${CMD_PREFIX} ostree admin status > status.txt + local n_deployments="$(grep -F -c -e 'Version: ' < status.txt)" + if test "${n_deployments}" '!=' "${n}"; then + cat status.txt + fatal "${n_deployments} != ${n}" + fi +} +assert_n_pinned 0 +${CMD_PREFIX} ostree admin pin 0 +assert_n_pinned 1 +${CMD_PREFIX} ostree admin pin -u 0 +assert_n_pinned 0 +echo "ok pin unpin" + +${CMD_PREFIX} ostree admin pin 0 +${CMD_PREFIX} ostree admin pin 1 +assert_n_pinned 2 +assert_n_deployments 2 +os_repository_new_commit +${CMD_PREFIX} ostree admin upgrade --os=testos +assert_n_pinned 2 +assert_n_deployments 3 +echo "ok pin across upgrades" + +${CMD_PREFIX} ostree admin pin -u 1 +os_repository_new_commit +${CMD_PREFIX} ostree admin upgrade --os=testos +assert_n_pinned 1 +assert_n_deployments 3 +os_repository_new_commit +${CMD_PREFIX} ostree admin upgrade --os=testos +assert_n_pinned 1 +assert_n_deployments 3 + +echo "ok pinning" diff --git a/tests/test-basic-user-only.sh b/tests/test-basic-user-only.sh index bea7b77f..5f27014e 100755 --- a/tests/test-basic-user-only.sh +++ b/tests/test-basic-user-only.sh @@ -28,7 +28,7 @@ extra_basic_tests=5 . $(dirname $0)/basic-test.sh $CMD_PREFIX ostree --version > version.yaml -python -c 'import yaml; yaml.safe_load(open("version.yaml"))' +python3 -c 'import yaml; yaml.safe_load(open("version.yaml"))' echo "ok yaml version" # Reset things so we don't inherit a lot of state from earlier tests diff --git a/tests/test-concurrency.py b/tests/test-concurrency.py index bdcc1d91..3ec3681c 100755 --- a/tests/test-concurrency.py +++ b/tests/test-concurrency.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # # Copyright (C) 2017 Colin Walters # @@ -17,6 +17,7 @@ # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. +from __future__ import division from __future__ import print_function import os import sys @@ -33,8 +34,8 @@ def fatal(msg): # different files with different checksums. def mktree(dname, serial=0): print('Creating tree', dname, file=sys.stderr) - os.mkdir(dname, 0755) - for v in xrange(20): + os.mkdir(dname, 0o755) + for v in range(20): with open('{}/{}'.format(dname, v), 'w') as f: f.write('{} {} {}\n'.format(dname, serial, v)) @@ -78,13 +79,13 @@ def run(n_committers, n_pruners): pruners = set() print('n_committers', n_committers, 'n_pruners', n_pruners, file=sys.stderr) - n_trees = n_committers / 2 - for v in xrange(n_trees): + n_trees = n_committers // 2 + for v in range(n_trees): mktree('tree{}'.format(v)) - for v in xrange(n_committers): - committers.add(commit(v / 2)) - for v in xrange(n_pruners): + for v in range(n_committers): + committers.add(commit(v // 2)) + for v in range(n_pruners): pruners.add(prune()) failed = False @@ -97,12 +98,12 @@ def run(n_committers, n_pruners): if failed: fatal('A child process exited abnormally') - for v in xrange(n_trees): + for v in range(n_trees): shutil.rmtree('tree{}'.format(v)) # No concurrent pruning -run(cpu_count()/2 + 2, 0) +run(cpu_count() // 2 + 2, 0) print("ok no concurrent prunes") -run(cpu_count()/2 + 4, 3) +run(cpu_count() // 2 + 4, 3) print("ok concurrent prunes") diff --git a/tests/test-prune.sh b/tests/test-prune.sh index 0f083221..e8734801 100755 --- a/tests/test-prune.sh +++ b/tests/test-prune.sh @@ -52,6 +52,13 @@ assert_repo_has_n_commits() { assert_streq "$(find ${repo}/objects -name '*.commit' | wc -l)" "${count}" } +# Test --no-prune +objectcount_orig=$(find repo/objects | wc -l) +${CMD_PREFIX} ostree prune --repo=repo --refs-only --depth=0 --no-prune | tee noprune.txt +assert_file_has_content noprune.txt 'Would delete: [1-9][0-9]* objects, freeing [1-9][0-9]*' +objectcount_new=$(find repo/objects | wc -l) +assert_streq "${objectcount_orig}" "${objectcount_new}" + ${CMD_PREFIX} ostree prune --repo=repo --refs-only --depth=2 -v assert_repo_has_n_commits repo 3 find repo/objects -name '*.tombstone-commit' | wc -l > tombstonecommitcount @@ -134,9 +141,10 @@ assert_file_has_content deltascount "^1$" ${CMD_PREFIX} ostree --repo=repo static-delta generate test ${CMD_PREFIX} ostree --repo=repo static-delta list | wc -l > deltascount assert_file_has_content deltascount "^2$" -${CMD_PREFIX} ostree --repo=repo prune --static-deltas-only --keep-younger-than="October 20 2015" -${CMD_PREFIX} ostree --repo=repo static-delta list | wc -l > deltascount -assert_file_has_content deltascount "^1$" +if ${CMD_PREFIX} ostree --repo=repo prune --static-deltas-only --keep-younger-than="October 20 2015" 2>err.txt; then + fatal "pruned deltas only" +fi +assert_file_has_content_literal err.txt "--static-deltas-only requires --delete-commit" echo "ok prune" diff --git a/tests/test-refs.sh b/tests/test-refs.sh index 50862d76..f4fe1833 100755 --- a/tests/test-refs.sh +++ b/tests/test-refs.sh @@ -147,6 +147,13 @@ ${CMD_PREFIX} ostree --repo=repo refs local1 --create=origin:local1 ${CMD_PREFIX} ostree --repo=repo refs | wc -l > refscount.create6 assert_file_has_content refscount.create6 "^11$" +#Check that we can list just remote refs +${CMD_PREFIX} ostree --repo=repo refs origin: | wc -l > refscount.create7 +assert_file_has_content refscount.create7 "^2$" # origin:remote1 origin:local1 +#Also support :. for backcompat with flatpak +${CMD_PREFIX} ostree --repo=repo refs origin:. | wc -l > refscount.create8 +assert_file_has_content refscount.create8 "^2$" # origin:remote1 origin:local1 + echo "ok refs" # Test symlinking a ref diff --git a/tests/test-remote-cookies.sh b/tests/test-remote-cookies.sh index 74e30cb5..e94a70d1 100755 --- a/tests/test-remote-cookies.sh +++ b/tests/test-remote-cookies.sh @@ -27,7 +27,8 @@ echo '1..4' . $(dirname $0)/libtest.sh setup_fake_remote_repo1 "archive" "" \ - "--expected-cookies foo=bar --expected-cookies baz=badger" + --expected-cookies foo=bar \ + --expected-cookies baz=badger assert_fail (){ if $@; then diff --git a/tests/test-remote-headers.sh b/tests/test-remote-headers.sh index 6ba612c0..a4ee386f 100755 --- a/tests/test-remote-headers.sh +++ b/tests/test-remote-headers.sh @@ -25,8 +25,13 @@ echo '1..2' . $(dirname $0)/libtest.sh +V=$($CMD_PREFIX ostree --version | \ + python3 -c 'import sys, yaml; print(yaml.safe_load(sys.stdin)["libostree"]["Version"])') + setup_fake_remote_repo1 "archive" "" \ - "--expected-header foo=bar --expected-header baz=badger" + --expected-header foo=bar \ + --expected-header baz=badger \ + --expected-header "User-Agent=libostree/$V dodo/2.15" assert_fail (){ set +e @@ -46,9 +51,22 @@ ${CMD_PREFIX} ostree --repo=repo remote add --set=gpg-verify=false origin $(cat # Sanity check the setup, without headers the pull should fail assert_fail ${CMD_PREFIX} ostree --repo=repo pull origin main +# without proper User-Agent, the pull should fail +assert_fail ${CMD_PREFIX} ostree --repo=repo pull origin main \ + --http-header foo=bar \ + --http-header baz=badger +assert_fail ${CMD_PREFIX} ostree --repo=repo pull origin main \ + --http-header foo=bar \ + --http-header baz=badger \ + --append-user-agent bar/1.2 + echo "ok setup done" # Now pull should succeed now -${CMD_PREFIX} ostree --repo=repo pull --http-header foo=bar --http-header baz=badger origin main +${CMD_PREFIX} ostree --repo=repo pull \ + --http-header foo=bar \ + --http-header baz=badger \ + --append-user-agent dodo/2.15 \ + origin main echo "ok pull succeeded" diff --git a/tests/test-symbols.sh b/tests/test-symbols.sh index ffea2482..13b47afb 100755 --- a/tests/test-symbols.sh +++ b/tests/test-symbols.sh @@ -54,7 +54,7 @@ echo 'ok documented symbols' # ONLY update this checksum in release commits! cat > released-sha256.txt <