diff --git a/Makefile-libostree-defines.am b/Makefile-libostree-defines.am index aff7e52b..6fc4a18a 100644 --- a/Makefile-libostree-defines.am +++ b/Makefile-libostree-defines.am @@ -40,7 +40,12 @@ libostree_public_headers = \ if ENABLE_EXPERIMENTAL_API libostree_public_headers += \ + src/libostree/ostree-ref.h \ src/libostree/ostree-remote.h \ + src/libostree/ostree-repo-finder.h \ + src/libostree/ostree-repo-finder-avahi.h \ + src/libostree/ostree-repo-finder-config.h \ + src/libostree/ostree-repo-finder-mount.h \ $(NULL) endif diff --git a/Makefile-libostree.am b/Makefile-libostree.am index 61ad1f4a..b589f1b2 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -94,6 +94,7 @@ libostree_1_la_SOURCES = \ src/libostree/ostree-linuxfsutil.c \ src/libostree/ostree-diff.c \ src/libostree/ostree-mutable-tree.c \ + src/libostree/ostree-ref.c \ src/libostree/ostree-remote.c \ src/libostree/ostree-remote-private.h \ src/libostree/ostree-repo.c \ @@ -149,10 +150,32 @@ libostree_1_la_SOURCES += \ src/libostree/ostree-tls-cert-interaction.h \ $(NULL) endif -if !ENABLE_EXPERIMENTAL_API -libostree_1_la_SOURCES += \ +libostree_experimental_headers = \ + src/libostree/ostree-ref.h \ src/libostree/ostree-remote.h \ + src/libostree/ostree-repo-finder.h \ + src/libostree/ostree-repo-finder-avahi.h \ + src/libostree/ostree-repo-finder-config.h \ + src/libostree/ostree-repo-finder-mount.h \ $(NULL) +if !ENABLE_EXPERIMENTAL_API +libostree_1_la_SOURCES += $(libostree_experimental_headers) +else # if ENABLE_EXPERIMENTAL_API +libostree_1_la_SOURCES += \ + src/libostree/ostree-bloom.c \ + src/libostree/ostree-bloom-private.h \ + src/libostree/ostree-repo-finder.c \ + src/libostree/ostree-repo-finder-avahi.c \ + src/libostree/ostree-repo-finder-config.c \ + src/libostree/ostree-repo-finder-mount.c \ + $(NULL) + +if USE_AVAHI +libostree_1_la_SOURCES += \ + src/libostree/ostree-repo-finder-avahi-parser.c \ + src/libostree/ostree-repo-finder-avahi-private.h \ + $(NULL) +endif # USE_AVAHI endif symbol_files = $(top_srcdir)/src/libostree/libostree-released.sym @@ -180,6 +203,13 @@ libostree_1_la_CFLAGS += $(OT_DEP_LIBARCHIVE_CFLAGS) libostree_1_la_LIBADD += $(OT_DEP_LIBARCHIVE_LIBS) endif +if ENABLE_EXPERIMENTAL_API +if USE_AVAHI +libostree_1_la_CFLAGS += $(OT_DEP_AVAHI_CFLAGS) +libostree_1_la_LIBADD += $(OT_DEP_AVAHI_LIBS) +endif +endif + if BUILDOPT_LIBSYSTEMD libostree_1_la_CFLAGS += $(LIBSYSTEMD_CFLAGS) libostree_1_la_LIBADD += $(LIBSYSTEMD_LIBS) @@ -227,8 +257,8 @@ OSTree_1_0_gir_EXPORT_PACKAGES = ostree-1 OSTree_1_0_gir_INCLUDES = Gio-2.0 OSTree_1_0_gir_CFLAGS = $(libostree_1_la_CFLAGS) OSTree_1_0_gir_LIBS = libostree-1.la -OSTree_1_0_gir_SCANNERFLAGS = --warn-all --identifier-prefix=Ostree --symbol-prefix=ostree -OSTree_1_0_gir_FILES = $(libostreeinclude_HEADERS) $(filter-out %-private.h %/ostree-soup-uri.h,$(libostree_1_la_SOURCES)) +OSTree_1_0_gir_SCANNERFLAGS = --warn-all --identifier-prefix=Ostree --symbol-prefix=ostree $(GI_SCANNERFLAGS) +OSTree_1_0_gir_FILES = $(libostreeinclude_HEADERS) $(filter-out %-private.h %/ostree-soup-uri.h $(libostree_experimental_headers),$(libostree_1_la_SOURCES)) INTROSPECTION_GIRS += OSTree-1.0.gir gir_DATA += OSTree-1.0.gir typelib_DATA += OSTree-1.0.typelib diff --git a/Makefile-man.am b/Makefile-man.am index 7996d2d5..93779509 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -32,6 +32,9 @@ ostree-remote.1 ostree-reset.1 ostree-rev-parse.1 ostree-show.1 \ ostree-summary.1 ostree-static-delta.1 if BUILDOPT_TRIVIAL_HTTPD man1_files += ostree-trivial-httpd.1 +else +# We still want to distribute the source, even if we are not building it +EXTRA_DIST += man/ostree-trivial-httpd.xml endif if BUILDOPT_FUSE diff --git a/Makefile-ostree.am b/Makefile-ostree.am index 4bd623d7..6414233f 100644 --- a/Makefile-ostree.am +++ b/Makefile-ostree.am @@ -53,6 +53,10 @@ ostree_SOURCES = src/ostree/main.c \ src/ostree/parse-datetime.c \ $(NULL) +if ENABLE_EXPERIMENTAL_API +ostree_SOURCES += src/ostree/ot-builtin-find-remotes.c +endif + # Admin subcommand ostree_SOURCES += \ src/ostree/ot-admin-builtin-init-fs.c \ diff --git a/Makefile-tests.am b/Makefile-tests.am index 4261fa7c..444b6636 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -67,7 +67,7 @@ _installed_or_uninstalled_test_scripts = \ tests/test-help.sh \ tests/test-libarchive.sh \ tests/test-parent.sh \ - tests/test-pull-archive-z.sh \ + tests/test-pull-archive.sh \ tests/test-pull-commit-only.sh \ tests/test-pull-depth.sh \ tests/test-pull-mirror-summary.sh \ @@ -78,6 +78,7 @@ _installed_or_uninstalled_test_scripts = \ tests/test-pull-repeated.sh \ tests/test-pull-untrusted.sh \ tests/test-pull-override-url.sh \ + tests/test-pull-localcache.sh \ tests/test-local-pull.sh \ tests/test-local-pull-depth.sh \ tests/test-gpg-signed-commit.sh \ @@ -108,9 +109,22 @@ _installed_or_uninstalled_test_scripts = \ tests/test-switchroot.sh \ tests/test-pull-contenturl.sh \ tests/test-pull-mirrorlist.sh \ + tests/test-summary-update.sh \ tests/test-summary-view.sh \ $(NULL) +if ENABLE_EXPERIMENTAL_API +_installed_or_uninstalled_test_scripts += \ + tests/test-find-remotes.sh \ + tests/test-fsck-collections.sh \ + tests/test-init-collections.sh \ + tests/test-prune-collections.sh \ + tests/test-refs-collections.sh \ + tests/test-remote-add-collections.sh \ + tests/test-summary-collections.sh \ + $(NULL) +endif + if BUILDOPT_FUSE _installed_or_uninstalled_test_scripts += tests/test-rofiles-fuse.sh else @@ -193,6 +207,18 @@ _installed_or_uninstalled_test_programs = tests/test-varint tests/test-ot-unix-u tests/test-gpg-verify-result tests/test-checksum tests/test-lzma tests/test-rollsum \ tests/test-basic-c tests/test-sysroot-c tests/test-pull-c +if ENABLE_EXPERIMENTAL_API +test_programs += \ + tests/test-bloom \ + tests/test-repo-finder-config \ + tests/test-repo-finder-mount \ + $(NULL) + +if USE_AVAHI +test_programs += tests/test-repo-finder-avahi +endif +endif + # An interactive tool noinst_PROGRAMS += tests/test-rollsum-cli @@ -204,7 +230,7 @@ common_tests_cflags = $(ostree_bin_shared_cflags) $(OT_INTERNAL_GIO_UNIX_CFLAGS) common_tests_ldadd = $(ostree_bin_shared_ldadd) $(OT_INTERNAL_GIO_UNIX_LIBS) noinst_LTLIBRARIES += libostreetest.la -libostreetest_la_SOURCES = tests/libostreetest.c +libostreetest_la_SOURCES = tests/libostreetest.c tests/test-mock-gio.c tests/test-mock-gio.h libostreetest_la_CFLAGS = $(common_tests_cflags) -I $(srcdir)/tests libostreetest_la_LIBADD = $(common_tests_ldadd) @@ -219,6 +245,24 @@ tests_test_rollsum_SOURCES = src/libostree/ostree-rollsum.c tests/test-rollsum.c tests_test_rollsum_CFLAGS = $(TESTS_CFLAGS) $(OT_DEP_ZLIB_CFLAGS) tests_test_rollsum_LDADD = $(bupsplitpath) $(TESTS_LDADD) $(OT_DEP_ZLIB_LIBS) +tests_test_bloom_SOURCES = src/libostree/ostree-bloom.c tests/test-bloom.c +tests_test_bloom_CFLAGS = $(TESTS_CFLAGS) +tests_test_bloom_LDADD = $(TESTS_LDADD) + +if USE_AVAHI +tests_test_repo_finder_avahi_SOURCES = src/libostree/ostree-repo-finder-avahi-parser.c tests/test-repo-finder-avahi.c +tests_test_repo_finder_avahi_CFLAGS = $(TESTS_CFLAGS) +tests_test_repo_finder_avahi_LDADD = $(TESTS_LDADD) +endif + +tests_test_repo_finder_config_SOURCES = tests/test-repo-finder-config.c +tests_test_repo_finder_config_CFLAGS = $(TESTS_CFLAGS) +tests_test_repo_finder_config_LDADD = $(TESTS_LDADD) + +tests_test_repo_finder_mount_SOURCES = tests/test-repo-finder-mount.c +tests_test_repo_finder_mount_CFLAGS = $(TESTS_CFLAGS) +tests_test_repo_finder_mount_LDADD = $(TESTS_LDADD) + tests_test_mutable_tree_CFLAGS = $(TESTS_CFLAGS) tests_test_mutable_tree_LDADD = $(TESTS_LDADD) diff --git a/Makefile.am b/Makefile.am index 53b505e3..0939d4b7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -29,7 +29,7 @@ AM_CPPFLAGS += -DDATADIR='"$(datadir)"' -DLIBEXECDIR='"$(libexecdir)"' \ -DOSTREE_COMPILATION \ -DG_LOG_DOMAIN=\"OSTree\" \ -DOSTREE_GITREV='"$(OSTREE_GITREV)"' \ - -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_40 -DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_40 \ + -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_40 -DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_50 \ -DSOUP_VERSION_MIN_REQUIRED=SOUP_VERSION_2_40 -DSOUP_VERSION_MAX_ALLOWED=SOUP_VERSION_2_48 AM_CFLAGS += -std=gnu99 $(WARN_CFLAGS) AM_DISTCHECK_CONFIGURE_FLAGS += \ diff --git a/Makefile.in b/Makefile.in index b93745c3..6a92ccf9 100644 --- a/Makefile.in +++ b/Makefile.in @@ -340,13 +340,13 @@ host_triplet = @host@ bin_PROGRAMS = ostree$(EXEEXT) $(am__EXEEXT_1) sbin_PROGRAMS = libexec_PROGRAMS = -pkglibexec_PROGRAMS = $(am__EXEEXT_16) -noinst_PROGRAMS = $(am__EXEEXT_13) tests/test-rollsum-cli$(EXEEXT) -ostree_boot_PROGRAMS = $(am__EXEEXT_14) $(am__EXEEXT_15) -TESTS = $(am__EXEEXT_6) $(am__EXEEXT_19) \ - $(dist_uninstalled_test_scripts) $(am__EXEEXT_10) -installed_test_PROGRAMS = $(am__EXEEXT_12) -check_PROGRAMS = $(am__EXEEXT_9) $(am__EXEEXT_10) $(am__EXEEXT_11) +pkglibexec_PROGRAMS = $(am__EXEEXT_18) +noinst_PROGRAMS = $(am__EXEEXT_15) tests/test-rollsum-cli$(EXEEXT) +ostree_boot_PROGRAMS = $(am__EXEEXT_16) $(am__EXEEXT_17) +TESTS = $(am__EXEEXT_8) $(am__EXEEXT_22) \ + $(dist_uninstalled_test_scripts) $(am__EXEEXT_12) +installed_test_PROGRAMS = $(am__EXEEXT_14) +check_PROGRAMS = $(am__EXEEXT_11) $(am__EXEEXT_12) $(am__EXEEXT_13) @ENABLE_ALWAYS_BUILD_TESTS_TRUE@am__append_1 = $(all_test_ltlibs) @ENABLE_ALWAYS_BUILD_TESTS_TRUE@am__append_2 = $(all_test_programs) @ENABLE_ALWAYS_BUILD_TESTS_TRUE@am__append_3 = $(all_test_scripts) @@ -375,7 +375,12 @@ check_PROGRAMS = $(am__EXEEXT_9) $(am__EXEEXT_10) $(am__EXEEXT_11) @ENABLE_GTK_DOC_TRUE@am__append_14 = apidoc @ENABLE_RUST_TRUE@am__append_15 = $(srcdir)/rust/Cargo.toml $(srcdir)/rust/cargo-vendor-config @ENABLE_EXPERIMENTAL_API_TRUE@am__append_16 = \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-ref.h \ @ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-remote.h \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-repo-finder.h \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-repo-finder-avahi.h \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-repo-finder-config.h \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-repo-finder-mount.h \ @ENABLE_EXPERIMENTAL_API_TRUE@ $(NULL) @ENABLE_RUST_TRUE@am__append_17 = $(BUPSPLIT_RUST_SRCS) @@ -390,17 +395,30 @@ check_PROGRAMS = $(am__EXEEXT_9) $(am__EXEEXT_10) $(am__EXEEXT_11) @HAVE_LIBSOUP_CLIENT_CERTS_TRUE@ src/libostree/ostree-tls-cert-interaction.h \ @HAVE_LIBSOUP_CLIENT_CERTS_TRUE@ $(NULL) -@ENABLE_EXPERIMENTAL_API_FALSE@am__append_21 = \ -@ENABLE_EXPERIMENTAL_API_FALSE@ src/libostree/ostree-remote.h \ -@ENABLE_EXPERIMENTAL_API_FALSE@ $(NULL) +@ENABLE_EXPERIMENTAL_API_FALSE@am__append_21 = $(libostree_experimental_headers) +@ENABLE_EXPERIMENTAL_API_TRUE@am__append_22 = \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-bloom.c \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-bloom-private.h \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-repo-finder.c \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-repo-finder-avahi.c \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-repo-finder-config.c \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-repo-finder-mount.c \ +@ENABLE_EXPERIMENTAL_API_TRUE@ $(NULL) -@BUILDOPT_IS_DEVEL_BUILD_TRUE@am__append_22 = $(top_srcdir)/src/libostree/libostree-devel.sym -@ENABLE_EXPERIMENTAL_API_TRUE@am__append_23 = $(top_srcdir)/src/libostree/libostree-experimental.sym -@USE_LIBARCHIVE_TRUE@am__append_24 = $(OT_DEP_LIBARCHIVE_CFLAGS) -@USE_LIBARCHIVE_TRUE@am__append_25 = $(OT_DEP_LIBARCHIVE_LIBS) -@BUILDOPT_LIBSYSTEMD_TRUE@am__append_26 = $(LIBSYSTEMD_CFLAGS) -@BUILDOPT_LIBSYSTEMD_TRUE@am__append_27 = $(LIBSYSTEMD_LIBS) -@USE_CURL_OR_SOUP_TRUE@am__append_28 = \ +@ENABLE_EXPERIMENTAL_API_TRUE@@USE_AVAHI_TRUE@am__append_23 = \ +@ENABLE_EXPERIMENTAL_API_TRUE@@USE_AVAHI_TRUE@ src/libostree/ostree-repo-finder-avahi-parser.c \ +@ENABLE_EXPERIMENTAL_API_TRUE@@USE_AVAHI_TRUE@ src/libostree/ostree-repo-finder-avahi-private.h \ +@ENABLE_EXPERIMENTAL_API_TRUE@@USE_AVAHI_TRUE@ $(NULL) + +@BUILDOPT_IS_DEVEL_BUILD_TRUE@am__append_24 = $(top_srcdir)/src/libostree/libostree-devel.sym +@ENABLE_EXPERIMENTAL_API_TRUE@am__append_25 = $(top_srcdir)/src/libostree/libostree-experimental.sym +@USE_LIBARCHIVE_TRUE@am__append_26 = $(OT_DEP_LIBARCHIVE_CFLAGS) +@USE_LIBARCHIVE_TRUE@am__append_27 = $(OT_DEP_LIBARCHIVE_LIBS) +@ENABLE_EXPERIMENTAL_API_TRUE@@USE_AVAHI_TRUE@am__append_28 = $(OT_DEP_AVAHI_CFLAGS) +@ENABLE_EXPERIMENTAL_API_TRUE@@USE_AVAHI_TRUE@am__append_29 = $(OT_DEP_AVAHI_LIBS) +@BUILDOPT_LIBSYSTEMD_TRUE@am__append_30 = $(LIBSYSTEMD_CFLAGS) +@BUILDOPT_LIBSYSTEMD_TRUE@am__append_31 = $(LIBSYSTEMD_LIBS) +@USE_CURL_OR_SOUP_TRUE@am__append_32 = \ @USE_CURL_OR_SOUP_TRUE@ src/libostree/ostree-fetcher.h \ @USE_CURL_OR_SOUP_TRUE@ src/libostree/ostree-fetcher-util.h \ @USE_CURL_OR_SOUP_TRUE@ src/libostree/ostree-fetcher-util.c \ @@ -409,25 +427,26 @@ check_PROGRAMS = $(am__EXEEXT_9) $(am__EXEEXT_10) $(am__EXEEXT_11) @USE_CURL_OR_SOUP_TRUE@ src/libostree/ostree-metalink.c \ @USE_CURL_OR_SOUP_TRUE@ $(NULL) -@USE_CURL_TRUE@am__append_29 = src/libostree/ostree-fetcher-curl.c \ +@USE_CURL_TRUE@am__append_33 = src/libostree/ostree-fetcher-curl.c \ @USE_CURL_TRUE@ src/libostree/ostree-soup-uri.h src/libostree/ostree-soup-uri.c \ @USE_CURL_TRUE@ src/libostree/ostree-soup-form.c \ @USE_CURL_TRUE@ $(NULL) -@USE_CURL_TRUE@am__append_30 = $(OT_DEP_CURL_CFLAGS) -@USE_CURL_TRUE@am__append_31 = $(OT_DEP_CURL_LIBS) -@USE_CURL_FALSE@@USE_LIBSOUP_TRUE@am__append_32 = src/libostree/ostree-fetcher-soup.c -@USE_CURL_FALSE@@USE_LIBSOUP_TRUE@am__append_33 = $(OT_INTERNAL_SOUP_CFLAGS) -@USE_CURL_FALSE@@USE_LIBSOUP_TRUE@am__append_34 = $(OT_INTERNAL_SOUP_LIBS) -@USE_LIBMOUNT_TRUE@am__append_35 = $(OT_DEP_LIBMOUNT_CFLAGS) -@USE_LIBMOUNT_TRUE@am__append_36 = $(OT_DEP_LIBMOUNT_LIBS) -@USE_SELINUX_TRUE@am__append_37 = $(OT_DEP_SELINUX_CFLAGS) -@USE_SELINUX_TRUE@am__append_38 = $(OT_DEP_SELINUX_LIBS) -@BUILDOPT_INTROSPECTION_TRUE@am__append_39 = OSTree-1.0.gir -@BUILDOPT_INTROSPECTION_TRUE@am__append_40 = OSTree-1.0.gir -@BUILDOPT_INTROSPECTION_TRUE@am__append_41 = OSTree-1.0.typelib -@BUILDOPT_INTROSPECTION_TRUE@am__append_42 = $(gir_DATA) $(typelib_DATA) -@USE_CURL_OR_SOUP_TRUE@am__append_43 = src/ostree/ot-remote-builtin-add-cookie.c \ +@USE_CURL_TRUE@am__append_34 = $(OT_DEP_CURL_CFLAGS) +@USE_CURL_TRUE@am__append_35 = $(OT_DEP_CURL_LIBS) +@USE_CURL_FALSE@@USE_LIBSOUP_TRUE@am__append_36 = src/libostree/ostree-fetcher-soup.c +@USE_CURL_FALSE@@USE_LIBSOUP_TRUE@am__append_37 = $(OT_INTERNAL_SOUP_CFLAGS) +@USE_CURL_FALSE@@USE_LIBSOUP_TRUE@am__append_38 = $(OT_INTERNAL_SOUP_LIBS) +@USE_LIBMOUNT_TRUE@am__append_39 = $(OT_DEP_LIBMOUNT_CFLAGS) +@USE_LIBMOUNT_TRUE@am__append_40 = $(OT_DEP_LIBMOUNT_LIBS) +@USE_SELINUX_TRUE@am__append_41 = $(OT_DEP_SELINUX_CFLAGS) +@USE_SELINUX_TRUE@am__append_42 = $(OT_DEP_SELINUX_LIBS) +@BUILDOPT_INTROSPECTION_TRUE@am__append_43 = OSTree-1.0.gir +@BUILDOPT_INTROSPECTION_TRUE@am__append_44 = OSTree-1.0.gir +@BUILDOPT_INTROSPECTION_TRUE@am__append_45 = OSTree-1.0.typelib +@BUILDOPT_INTROSPECTION_TRUE@am__append_46 = $(gir_DATA) $(typelib_DATA) +@ENABLE_EXPERIMENTAL_API_TRUE@am__append_47 = src/ostree/ot-builtin-find-remotes.c +@USE_CURL_OR_SOUP_TRUE@am__append_48 = src/ostree/ot-remote-builtin-add-cookie.c \ @USE_CURL_OR_SOUP_TRUE@ src/ostree/ot-remote-builtin-delete-cookie.c \ @USE_CURL_OR_SOUP_TRUE@ src/ostree/ot-remote-builtin-list-cookies.c \ @USE_CURL_OR_SOUP_TRUE@ src/ostree/ot-remote-cookie-util.h \ @@ -435,50 +454,69 @@ check_PROGRAMS = $(am__EXEEXT_9) $(am__EXEEXT_10) $(am__EXEEXT_11) @USE_CURL_OR_SOUP_TRUE@ $(NULL) src/ostree/ot-builtin-pull.c # Eventually once we stop things from using this, we should support disabling this -@USE_LIBSOUP_TRUE@am__append_44 = src/ostree/ot-builtin-trivial-httpd.c -@USE_LIBSOUP_TRUE@am__append_45 = ostree-trivial-httpd +@USE_LIBSOUP_TRUE@am__append_49 = src/ostree/ot-builtin-trivial-httpd.c +@USE_LIBSOUP_TRUE@am__append_50 = ostree-trivial-httpd # This is necessary for the cookie jar bits -@USE_CURL_FALSE@@USE_LIBSOUP_TRUE@am__append_46 = $(OT_INTERNAL_SOUP_CFLAGS) -@USE_CURL_FALSE@@USE_LIBSOUP_TRUE@am__append_47 = $(OT_INTERNAL_SOUP_LIBS) -@USE_LIBARCHIVE_TRUE@am__append_48 = $(OT_DEP_LIBARCHIVE_CFLAGS) -@USE_LIBARCHIVE_TRUE@am__append_49 = $(OT_DEP_LIBARCHIVE_LIBS) -@BUILDOPT_SYSTEMD_TRUE@am__append_50 = ostree-remount +@USE_CURL_FALSE@@USE_LIBSOUP_TRUE@am__append_51 = $(OT_INTERNAL_SOUP_CFLAGS) +@USE_CURL_FALSE@@USE_LIBSOUP_TRUE@am__append_52 = $(OT_INTERNAL_SOUP_LIBS) +@USE_LIBARCHIVE_TRUE@am__append_53 = $(OT_DEP_LIBARCHIVE_CFLAGS) +@USE_LIBARCHIVE_TRUE@am__append_54 = $(OT_DEP_LIBARCHIVE_LIBS) +@BUILDOPT_SYSTEMD_TRUE@am__append_55 = ostree-remount # It is built anyway as a side-effect of having the symlink in tests/, # and if we declare it here, it gets cleaned up properly -@BUILDOPT_SYSTEMD_FALSE@am__append_51 = ostree-remount -@BUILDOPT_USE_STATIC_COMPILER_FALSE@am__append_52 = ostree-prepare-root +@BUILDOPT_SYSTEMD_FALSE@am__append_56 = ostree-remount +@BUILDOPT_USE_STATIC_COMPILER_FALSE@am__append_57 = ostree-prepare-root # This is the "new mode" of using a generator for /var; see # https://github.com/ostreedev/ostree/issues/855 -@BUILDOPT_SYSTEMD_AND_LIBMOUNT_TRUE@am__append_53 = -DHAVE_SYSTEMD_AND_LIBMOUNT=1 -@BUILDOPT_SYSTEMD_AND_LIBMOUNT_TRUE@am__append_54 = -DHAVE_SYSTEMD_AND_LIBMOUNT=1 +@BUILDOPT_SYSTEMD_AND_LIBMOUNT_TRUE@am__append_58 = -DHAVE_SYSTEMD_AND_LIBMOUNT=1 +@BUILDOPT_SYSTEMD_AND_LIBMOUNT_TRUE@am__append_59 = -DHAVE_SYSTEMD_AND_LIBMOUNT=1 @BUILDOPT_SYSTEMD_AND_LIBMOUNT_TRUE@systemdsystemgenerator_PROGRAMS = ostree-system-generator$(EXEEXT) -@BUILDOPT_SYSTEMD_AND_LIBMOUNT_TRUE@am__append_55 = $(systemdsystemgenerator_PROGRAMS) -@BUILDOPT_FUSE_TRUE@am__append_56 = rofiles-fuse -@BUILDOPT_ASAN_TRUE@am__append_57 = OT_SKIP_READDIR_RAND=1 G_SLICE=always-malloc -@BUILDOPT_FUSE_TRUE@am__append_58 = tests/test-rofiles-fuse.sh -@BUILDOPT_FUSE_FALSE@am__append_59 = tests/test-rofiles-fuse.sh -@USE_LIBSOUP_TRUE@am__append_60 = tests/test-remote-cookies.sh -@BUILDOPT_GJS_TRUE@am__append_61 = $(js_tests) -@BUILDOPT_GJS_FALSE@am__append_62 = $(js_tests) -@BUILDOPT_GJS_FALSE@am__append_63 = $(js_installed_tests) -@ENABLE_INSTALLED_TESTS_FALSE@am__append_64 = -rpath $(abs_builddir) -@USE_LIBARCHIVE_TRUE@am__append_65 = tests/test-libarchive-import -@ENABLE_INSTALLED_TESTS_EXCLUSIVE_FALSE@am__append_66 = $(_installed_or_uninstalled_test_scripts) -@ENABLE_INSTALLED_TESTS_EXCLUSIVE_FALSE@am__append_67 = $(_installed_or_uninstalled_test_programs) -@ENABLE_INSTALLED_TESTS_TRUE@am__append_68 = install-installed-tests-extra +@BUILDOPT_SYSTEMD_AND_LIBMOUNT_TRUE@am__append_60 = $(systemdsystemgenerator_PROGRAMS) +@BUILDOPT_FUSE_TRUE@am__append_61 = rofiles-fuse +@BUILDOPT_ASAN_TRUE@am__append_62 = OT_SKIP_READDIR_RAND=1 G_SLICE=always-malloc +@ENABLE_EXPERIMENTAL_API_TRUE@am__append_63 = \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-find-remotes.sh \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-fsck-collections.sh \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-init-collections.sh \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-prune-collections.sh \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-refs-collections.sh \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-remote-add-collections.sh \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-summary-collections.sh \ +@ENABLE_EXPERIMENTAL_API_TRUE@ $(NULL) + +@BUILDOPT_FUSE_TRUE@am__append_64 = tests/test-rofiles-fuse.sh +@BUILDOPT_FUSE_FALSE@am__append_65 = tests/test-rofiles-fuse.sh +@USE_LIBSOUP_TRUE@am__append_66 = tests/test-remote-cookies.sh +@BUILDOPT_GJS_TRUE@am__append_67 = $(js_tests) +@BUILDOPT_GJS_FALSE@am__append_68 = $(js_tests) +@BUILDOPT_GJS_FALSE@am__append_69 = $(js_installed_tests) +@ENABLE_INSTALLED_TESTS_FALSE@am__append_70 = -rpath $(abs_builddir) +@ENABLE_EXPERIMENTAL_API_TRUE@am__append_71 = \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-bloom \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-repo-finder-config \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-repo-finder-mount \ +@ENABLE_EXPERIMENTAL_API_TRUE@ $(NULL) + +@ENABLE_EXPERIMENTAL_API_TRUE@@USE_AVAHI_TRUE@am__append_72 = tests/test-repo-finder-avahi +@USE_LIBARCHIVE_TRUE@am__append_73 = tests/test-libarchive-import +@ENABLE_INSTALLED_TESTS_EXCLUSIVE_FALSE@am__append_74 = $(_installed_or_uninstalled_test_scripts) +@ENABLE_INSTALLED_TESTS_EXCLUSIVE_FALSE@am__append_75 = $(_installed_or_uninstalled_test_programs) +@ENABLE_INSTALLED_TESTS_TRUE@am__append_76 = install-installed-tests-extra # Allow the distcheck install under $prefix test to pass -@BUILDOPT_SYSTEMD_TRUE@am__append_69 = --with-systemdsystemunitdir='$${libdir}/systemd/system' +@BUILDOPT_SYSTEMD_TRUE@am__append_77 = --with-systemdsystemunitdir='$${libdir}/systemd/system' # We're using the system grub2-mkconfig generator -@BUILDOPT_BUILTIN_GRUB2_MKCONFIG_FALSE@am__append_70 = src/boot/grub2/grub2-15_ostree -@BUILDOPT_BUILTIN_GRUB2_MKCONFIG_FALSE@am__append_71 = install-grub2-config-hook -@BUILDOPT_TRIVIAL_HTTPD_TRUE@@ENABLE_MAN_TRUE@am__append_72 = ostree-trivial-httpd.1 -@BUILDOPT_FUSE_TRUE@@ENABLE_MAN_TRUE@am__append_73 = rofiles-fuse.1 -@ENABLE_MAN_TRUE@am__append_74 = $(man1_MANS) $(man5_MANS) $(man1_MANS:.1=.xml) $(man5_MANS:.5=.xml) -@ENABLE_MAN_TRUE@am__append_75 = \ +@BUILDOPT_BUILTIN_GRUB2_MKCONFIG_FALSE@am__append_78 = src/boot/grub2/grub2-15_ostree +@BUILDOPT_BUILTIN_GRUB2_MKCONFIG_FALSE@am__append_79 = install-grub2-config-hook +@BUILDOPT_TRIVIAL_HTTPD_TRUE@@ENABLE_MAN_TRUE@am__append_80 = ostree-trivial-httpd.1 +# We still want to distribute the source, even if we are not building it +@BUILDOPT_TRIVIAL_HTTPD_FALSE@@ENABLE_MAN_TRUE@am__append_81 = man/ostree-trivial-httpd.xml +@BUILDOPT_FUSE_TRUE@@ENABLE_MAN_TRUE@am__append_82 = rofiles-fuse.1 +@ENABLE_MAN_TRUE@am__append_83 = $(man1_MANS) $(man5_MANS) $(man1_MANS:.1=.xml) $(man5_MANS:.5=.xml) +@ENABLE_MAN_TRUE@am__append_84 = \ @ENABLE_MAN_TRUE@ $(man1_MANS) \ @ENABLE_MAN_TRUE@ $(man5_MANS) \ @ENABLE_MAN_TRUE@ $(NULL) @@ -596,19 +634,21 @@ libglnx_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(CFLAGS) $(libglnx_la_LDFLAGS) $(LDFLAGS) -o $@ am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) @USE_LIBARCHIVE_TRUE@am__DEPENDENCIES_4 = $(am__DEPENDENCIES_1) -@BUILDOPT_LIBSYSTEMD_TRUE@am__DEPENDENCIES_5 = $(am__DEPENDENCIES_1) -@USE_CURL_TRUE@am__DEPENDENCIES_6 = $(am__DEPENDENCIES_1) -@USE_CURL_FALSE@@USE_LIBSOUP_TRUE@am__DEPENDENCIES_7 = \ +@ENABLE_EXPERIMENTAL_API_TRUE@@USE_AVAHI_TRUE@am__DEPENDENCIES_5 = $(am__DEPENDENCIES_1) +@BUILDOPT_LIBSYSTEMD_TRUE@am__DEPENDENCIES_6 = $(am__DEPENDENCIES_1) +@USE_CURL_TRUE@am__DEPENDENCIES_7 = $(am__DEPENDENCIES_1) +@USE_CURL_FALSE@@USE_LIBSOUP_TRUE@am__DEPENDENCIES_8 = \ @USE_CURL_FALSE@@USE_LIBSOUP_TRUE@ $(am__DEPENDENCIES_2) -@USE_LIBMOUNT_TRUE@am__DEPENDENCIES_8 = $(am__DEPENDENCIES_1) -@USE_SELINUX_TRUE@am__DEPENDENCIES_9 = $(am__DEPENDENCIES_1) +@USE_LIBMOUNT_TRUE@am__DEPENDENCIES_9 = $(am__DEPENDENCIES_1) +@USE_SELINUX_TRUE@am__DEPENDENCIES_10 = $(am__DEPENDENCIES_1) libostree_1_la_DEPENDENCIES = libotutil.la libglnx.la libbsdiff.la \ libostree-kernel-args.la $(am__DEPENDENCIES_2) \ $(am__DEPENDENCIES_3) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) $(bupsplitpath) \ $(am__DEPENDENCIES_4) $(am__DEPENDENCIES_5) \ $(am__DEPENDENCIES_6) $(am__DEPENDENCIES_7) \ - $(am__DEPENDENCIES_8) $(am__DEPENDENCIES_9) + $(am__DEPENDENCIES_8) $(am__DEPENDENCIES_9) \ + $(am__DEPENDENCIES_10) am__libostree_1_la_SOURCES_DIST = \ src/libostree/ostree-async-progress.c \ src/libostree/ostree-cmdprivate.h \ @@ -630,7 +670,7 @@ am__libostree_1_la_SOURCES_DIST = \ src/libostree/ostree-varint.h src/libostree/ostree-varint.c \ src/libostree/ostree-linuxfsutil.h \ src/libostree/ostree-linuxfsutil.c src/libostree/ostree-diff.c \ - src/libostree/ostree-mutable-tree.c \ + src/libostree/ostree-mutable-tree.c src/libostree/ostree-ref.c \ src/libostree/ostree-remote.c \ src/libostree/ostree-remote-private.h \ src/libostree/ostree-repo.c \ @@ -678,7 +718,20 @@ am__libostree_1_la_SOURCES_DIST = \ src/libostree/ostree-libarchive-private.h \ src/libostree/ostree-tls-cert-interaction.c \ src/libostree/ostree-tls-cert-interaction.h \ - src/libostree/ostree-remote.h src/libostree/ostree-fetcher.h \ + src/libostree/ostree-ref.h src/libostree/ostree-remote.h \ + src/libostree/ostree-repo-finder.h \ + src/libostree/ostree-repo-finder-avahi.h \ + src/libostree/ostree-repo-finder-config.h \ + src/libostree/ostree-repo-finder-mount.h \ + src/libostree/ostree-bloom.c \ + src/libostree/ostree-bloom-private.h \ + src/libostree/ostree-repo-finder.c \ + src/libostree/ostree-repo-finder-avahi.c \ + src/libostree/ostree-repo-finder-config.c \ + src/libostree/ostree-repo-finder-mount.c \ + src/libostree/ostree-repo-finder-avahi-parser.c \ + src/libostree/ostree-repo-finder-avahi-private.h \ + src/libostree/ostree-fetcher.h \ src/libostree/ostree-fetcher-util.h \ src/libostree/ostree-fetcher-util.c \ src/libostree/ostree-fetcher-uri.c \ @@ -693,16 +746,25 @@ am__libostree_1_la_SOURCES_DIST = \ @USE_LIBARCHIVE_TRUE@ $(am__objects_1) @HAVE_LIBSOUP_CLIENT_CERTS_TRUE@am__objects_3 = src/libostree/libostree_1_la-ostree-tls-cert-interaction.lo \ @HAVE_LIBSOUP_CLIENT_CERTS_TRUE@ $(am__objects_1) -@ENABLE_EXPERIMENTAL_API_FALSE@am__objects_4 = $(am__objects_1) -@USE_CURL_OR_SOUP_TRUE@am__objects_5 = src/libostree/libostree_1_la-ostree-fetcher-util.lo \ +am__objects_4 = $(am__objects_1) +@ENABLE_EXPERIMENTAL_API_FALSE@am__objects_5 = $(am__objects_4) +@ENABLE_EXPERIMENTAL_API_TRUE@am__objects_6 = src/libostree/libostree_1_la-ostree-bloom.lo \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/libostree_1_la-ostree-repo-finder.lo \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/libostree_1_la-ostree-repo-finder-avahi.lo \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/libostree_1_la-ostree-repo-finder-config.lo \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/libostree_1_la-ostree-repo-finder-mount.lo \ +@ENABLE_EXPERIMENTAL_API_TRUE@ $(am__objects_1) +@ENABLE_EXPERIMENTAL_API_TRUE@@USE_AVAHI_TRUE@am__objects_7 = src/libostree/libostree_1_la-ostree-repo-finder-avahi-parser.lo \ +@ENABLE_EXPERIMENTAL_API_TRUE@@USE_AVAHI_TRUE@ $(am__objects_1) +@USE_CURL_OR_SOUP_TRUE@am__objects_8 = src/libostree/libostree_1_la-ostree-fetcher-util.lo \ @USE_CURL_OR_SOUP_TRUE@ src/libostree/libostree_1_la-ostree-fetcher-uri.lo \ @USE_CURL_OR_SOUP_TRUE@ src/libostree/libostree_1_la-ostree-metalink.lo \ @USE_CURL_OR_SOUP_TRUE@ $(am__objects_1) -@USE_CURL_TRUE@am__objects_6 = src/libostree/libostree_1_la-ostree-fetcher-curl.lo \ +@USE_CURL_TRUE@am__objects_9 = src/libostree/libostree_1_la-ostree-fetcher-curl.lo \ @USE_CURL_TRUE@ src/libostree/libostree_1_la-ostree-soup-uri.lo \ @USE_CURL_TRUE@ src/libostree/libostree_1_la-ostree-soup-form.lo \ @USE_CURL_TRUE@ $(am__objects_1) -@USE_CURL_FALSE@@USE_LIBSOUP_TRUE@am__objects_7 = src/libostree/libostree_1_la-ostree-fetcher-soup.lo +@USE_CURL_FALSE@@USE_LIBSOUP_TRUE@am__objects_10 = src/libostree/libostree_1_la-ostree-fetcher-soup.lo am_libostree_1_la_OBJECTS = \ src/libostree/libostree_1_la-ostree-async-progress.lo \ src/libostree/libostree_1_la-ostree-cmdprivate.lo \ @@ -718,6 +780,7 @@ am_libostree_1_la_OBJECTS = \ src/libostree/libostree_1_la-ostree-linuxfsutil.lo \ src/libostree/libostree_1_la-ostree-diff.lo \ src/libostree/libostree_1_la-ostree-mutable-tree.lo \ + src/libostree/libostree_1_la-ostree-ref.lo \ src/libostree/libostree_1_la-ostree-remote.lo \ src/libostree/libostree_1_la-ostree-repo.lo \ src/libostree/libostree_1_la-ostree-repo-checkout.lo \ @@ -748,8 +811,8 @@ am_libostree_1_la_OBJECTS = \ src/libostree/libostree_1_la-ostree-gpg-verifier.lo \ src/libostree/libostree_1_la-ostree-gpg-verify-result.lo \ $(am__objects_1) $(am__objects_2) $(am__objects_3) \ - $(am__objects_4) $(am__objects_5) $(am__objects_6) \ - $(am__objects_7) + $(am__objects_5) $(am__objects_6) $(am__objects_7) \ + $(am__objects_8) $(am__objects_9) $(am__objects_10) nodist_libostree_1_la_OBJECTS = \ src/libostree/libostree_1_la-ostree-enumtypes.lo \ $(am__objects_1) @@ -769,11 +832,12 @@ libostree_kernel_args_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(libostree_kernel_args_la_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ -am__DEPENDENCIES_10 = libglnx.la libotutil.la libostree-1.la \ +am__DEPENDENCIES_11 = libglnx.la libotutil.la libostree-1.la \ $(am__DEPENDENCIES_2) -am__DEPENDENCIES_11 = $(am__DEPENDENCIES_10) $(am__DEPENDENCIES_2) -libostreetest_la_DEPENDENCIES = $(am__DEPENDENCIES_11) -am_libostreetest_la_OBJECTS = tests/libostreetest_la-libostreetest.lo +am__DEPENDENCIES_12 = $(am__DEPENDENCIES_11) $(am__DEPENDENCIES_2) +libostreetest_la_DEPENDENCIES = $(am__DEPENDENCIES_12) +am_libostreetest_la_OBJECTS = tests/libostreetest_la-libostreetest.lo \ + tests/libostreetest_la-test-mock-gio.lo libostreetest_la_OBJECTS = $(am_libostreetest_la_OBJECTS) libostreetest_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ @@ -813,9 +877,15 @@ libreaddir_rand_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ @ENABLE_INSTALLED_TESTS_TRUE@ $(installed_testdir) @BUILDOPT_FUSE_TRUE@am__EXEEXT_1 = rofiles-fuse$(EXEEXT) am__EXEEXT_2 = -@USE_LIBARCHIVE_TRUE@am__EXEEXT_3 = \ +@ENABLE_EXPERIMENTAL_API_TRUE@am__EXEEXT_3 = \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-bloom$(EXEEXT) \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-repo-finder-config$(EXEEXT) \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-repo-finder-mount$(EXEEXT) \ +@ENABLE_EXPERIMENTAL_API_TRUE@ $(am__EXEEXT_2) +@ENABLE_EXPERIMENTAL_API_TRUE@@USE_AVAHI_TRUE@am__EXEEXT_4 = tests/test-repo-finder-avahi$(EXEEXT) +@USE_LIBARCHIVE_TRUE@am__EXEEXT_5 = \ @USE_LIBARCHIVE_TRUE@ tests/test-libarchive-import$(EXEEXT) -am__EXEEXT_4 = tests/test-varint$(EXEEXT) \ +am__EXEEXT_6 = tests/test-varint$(EXEEXT) \ tests/test-ot-unix-utils$(EXEEXT) tests/test-bsdiff$(EXEEXT) \ tests/test-mutable-tree$(EXEEXT) \ tests/test-keyfile-utils$(EXEEXT) \ @@ -825,22 +895,24 @@ am__EXEEXT_4 = tests/test-varint$(EXEEXT) \ tests/test-checksum$(EXEEXT) tests/test-lzma$(EXEEXT) \ tests/test-rollsum$(EXEEXT) tests/test-basic-c$(EXEEXT) \ tests/test-sysroot-c$(EXEEXT) tests/test-pull-c$(EXEEXT) \ - $(am__EXEEXT_3) -@ENABLE_INSTALLED_TESTS_EXCLUSIVE_FALSE@am__EXEEXT_5 = \ -@ENABLE_INSTALLED_TESTS_EXCLUSIVE_FALSE@ $(am__EXEEXT_4) -am__EXEEXT_6 = $(am__EXEEXT_2) $(am__EXEEXT_5) -@ENABLE_INSTALLED_TESTS_EXCLUSIVE_TRUE@am__EXEEXT_7 = $(am__EXEEXT_4) -am__EXEEXT_8 = $(am__EXEEXT_6) $(am__EXEEXT_7) -@ENABLE_ALWAYS_BUILD_TESTS_FALSE@am__EXEEXT_9 = $(am__EXEEXT_8) -am__EXEEXT_10 = test-libglnx-xattrs$(EXEEXT) \ - test-libglnx-fdio$(EXEEXT) test-libglnx-errors$(EXEEXT) -@BUILDOPT_SYSTEMD_FALSE@am__EXEEXT_11 = ostree-remount$(EXEEXT) -@ENABLE_INSTALLED_TESTS_TRUE@am__EXEEXT_12 = $(am__EXEEXT_6) \ -@ENABLE_INSTALLED_TESTS_TRUE@ $(am__EXEEXT_7) -@ENABLE_ALWAYS_BUILD_TESTS_TRUE@am__EXEEXT_13 = $(am__EXEEXT_8) -@BUILDOPT_SYSTEMD_TRUE@am__EXEEXT_14 = ostree-remount$(EXEEXT) -@BUILDOPT_USE_STATIC_COMPILER_FALSE@am__EXEEXT_15 = ostree-prepare-root$(EXEEXT) -@USE_LIBSOUP_TRUE@am__EXEEXT_16 = ostree-trivial-httpd$(EXEEXT) + $(am__EXEEXT_5) +@ENABLE_INSTALLED_TESTS_EXCLUSIVE_FALSE@am__EXEEXT_7 = \ +@ENABLE_INSTALLED_TESTS_EXCLUSIVE_FALSE@ $(am__EXEEXT_6) +am__EXEEXT_8 = $(am__EXEEXT_2) $(am__EXEEXT_3) $(am__EXEEXT_4) \ + $(am__EXEEXT_7) +@ENABLE_INSTALLED_TESTS_EXCLUSIVE_TRUE@am__EXEEXT_9 = $(am__EXEEXT_6) +am__EXEEXT_10 = $(am__EXEEXT_8) $(am__EXEEXT_9) +@ENABLE_ALWAYS_BUILD_TESTS_FALSE@am__EXEEXT_11 = $(am__EXEEXT_10) +am__EXEEXT_12 = test-libglnx-xattrs$(EXEEXT) \ + test-libglnx-fdio$(EXEEXT) test-libglnx-errors$(EXEEXT) \ + test-libglnx-macros$(EXEEXT) +@BUILDOPT_SYSTEMD_FALSE@am__EXEEXT_13 = ostree-remount$(EXEEXT) +@ENABLE_INSTALLED_TESTS_TRUE@am__EXEEXT_14 = $(am__EXEEXT_8) \ +@ENABLE_INSTALLED_TESTS_TRUE@ $(am__EXEEXT_9) +@ENABLE_ALWAYS_BUILD_TESTS_TRUE@am__EXEEXT_15 = $(am__EXEEXT_10) +@BUILDOPT_SYSTEMD_TRUE@am__EXEEXT_16 = ostree-remount$(EXEEXT) +@BUILDOPT_USE_STATIC_COMPILER_FALSE@am__EXEEXT_17 = ostree-prepare-root$(EXEEXT) +@USE_LIBSOUP_TRUE@am__EXEEXT_18 = ostree-trivial-httpd$(EXEEXT) PROGRAMS = $(bin_PROGRAMS) $(installed_test_PROGRAMS) \ $(libexec_PROGRAMS) $(noinst_PROGRAMS) $(ostree_boot_PROGRAMS) \ $(pkglibexec_PROGRAMS) $(sbin_PROGRAMS) \ @@ -863,6 +935,7 @@ am__ostree_SOURCES_DIST = src/ostree/main.c \ src/ostree/ot-main.c src/ostree/ot-dump.h src/ostree/ot-dump.c \ src/ostree/ot-editor.c src/ostree/ot-editor.h \ src/ostree/parse-datetime.h src/ostree/parse-datetime.c \ + src/ostree/ot-builtin-find-remotes.c \ src/ostree/ot-admin-builtin-init-fs.c \ src/ostree/ot-admin-builtin-diff.c \ src/ostree/ot-admin-builtin-deploy.c \ @@ -897,13 +970,14 @@ am__ostree_SOURCES_DIST = src/ostree/main.c \ src/ostree/ot-remote-cookie-util.c \ src/ostree/ot-builtin-pull.c \ src/ostree/ot-builtin-trivial-httpd.c -@USE_CURL_OR_SOUP_TRUE@am__objects_8 = src/ostree/ostree-ot-remote-builtin-add-cookie.$(OBJEXT) \ +@ENABLE_EXPERIMENTAL_API_TRUE@am__objects_11 = src/ostree/ostree-ot-builtin-find-remotes.$(OBJEXT) +@USE_CURL_OR_SOUP_TRUE@am__objects_12 = src/ostree/ostree-ot-remote-builtin-add-cookie.$(OBJEXT) \ @USE_CURL_OR_SOUP_TRUE@ src/ostree/ostree-ot-remote-builtin-delete-cookie.$(OBJEXT) \ @USE_CURL_OR_SOUP_TRUE@ src/ostree/ostree-ot-remote-builtin-list-cookies.$(OBJEXT) \ @USE_CURL_OR_SOUP_TRUE@ src/ostree/ostree-ot-remote-cookie-util.$(OBJEXT) \ @USE_CURL_OR_SOUP_TRUE@ $(am__objects_1) \ @USE_CURL_OR_SOUP_TRUE@ src/ostree/ostree-ot-builtin-pull.$(OBJEXT) -@USE_LIBSOUP_TRUE@am__objects_9 = src/ostree/ostree-ot-builtin-trivial-httpd.$(OBJEXT) +@USE_LIBSOUP_TRUE@am__objects_13 = src/ostree/ostree-ot-builtin-trivial-httpd.$(OBJEXT) am_ostree_OBJECTS = src/ostree/ostree-main.$(OBJEXT) \ src/ostree/ostree-ot-builtin-admin.$(OBJEXT) \ src/ostree/ostree-ot-builtin-cat.$(OBJEXT) \ @@ -931,6 +1005,7 @@ am_ostree_OBJECTS = src/ostree/ostree-main.$(OBJEXT) \ src/ostree/ostree-ot-dump.$(OBJEXT) \ src/ostree/ostree-ot-editor.$(OBJEXT) \ src/ostree/ostree-parse-datetime.$(OBJEXT) $(am__objects_1) \ + $(am__objects_11) \ src/ostree/ostree-ot-admin-builtin-init-fs.$(OBJEXT) \ src/ostree/ostree-ot-admin-builtin-diff.$(OBJEXT) \ src/ostree/ostree-ot-admin-builtin-deploy.$(OBJEXT) \ @@ -955,11 +1030,11 @@ am_ostree_OBJECTS = src/ostree/ostree-main.$(OBJEXT) \ src/ostree/ostree-ot-remote-builtin-show-url.$(OBJEXT) \ src/ostree/ostree-ot-remote-builtin-refs.$(OBJEXT) \ src/ostree/ostree-ot-remote-builtin-summary.$(OBJEXT) \ - $(am__objects_1) $(am__objects_8) $(am__objects_9) + $(am__objects_1) $(am__objects_12) $(am__objects_13) ostree_OBJECTS = $(am_ostree_OBJECTS) -ostree_DEPENDENCIES = $(am__DEPENDENCIES_10) libbsdiff.la \ +ostree_DEPENDENCIES = $(am__DEPENDENCIES_11) libbsdiff.la \ libostree-kernel-args.la $(am__DEPENDENCIES_1) \ - $(am__DEPENDENCIES_7) $(am__DEPENDENCIES_4) + $(am__DEPENDENCIES_8) $(am__DEPENDENCIES_4) ostree_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(ostree_CFLAGS) $(CFLAGS) \ $(AM_LDFLAGS) $(LDFLAGS) -o $@ @@ -994,7 +1069,7 @@ am__ostree_trivial_httpd_SOURCES_DIST = \ @USE_LIBSOUP_TRUE@am_ostree_trivial_httpd_OBJECTS = src/ostree/ostree_trivial_httpd-ostree-trivial-httpd.$(OBJEXT) ostree_trivial_httpd_OBJECTS = $(am_ostree_trivial_httpd_OBJECTS) @USE_LIBSOUP_TRUE@ostree_trivial_httpd_DEPENDENCIES = \ -@USE_LIBSOUP_TRUE@ $(am__DEPENDENCIES_10) $(am__DEPENDENCIES_2) +@USE_LIBSOUP_TRUE@ $(am__DEPENDENCIES_11) $(am__DEPENDENCIES_2) ostree_trivial_httpd_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(ostree_trivial_httpd_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ @@ -1023,6 +1098,13 @@ test_libglnx_fdio_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(test_libglnx_fdio_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ -o $@ +am_test_libglnx_macros_OBJECTS = libglnx/tests/test_libglnx_macros-test-libglnx-macros.$(OBJEXT) +test_libglnx_macros_OBJECTS = $(am_test_libglnx_macros_OBJECTS) +test_libglnx_macros_DEPENDENCIES = $(am__DEPENDENCIES_2) libglnx.la +test_libglnx_macros_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(test_libglnx_macros_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ am_test_libglnx_xattrs_OBJECTS = libglnx/tests/test_libglnx_xattrs-test-libglnx-xattrs.$(OBJEXT) test_libglnx_xattrs_OBJECTS = $(am_test_libglnx_xattrs_OBJECTS) test_libglnx_xattrs_DEPENDENCIES = $(am__DEPENDENCIES_2) libglnx.la @@ -1033,16 +1115,25 @@ test_libglnx_xattrs_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ tests_test_basic_c_SOURCES = tests/test-basic-c.c tests_test_basic_c_OBJECTS = \ tests/tests_test_basic_c-test-basic-c.$(OBJEXT) -am__DEPENDENCIES_12 = $(am__DEPENDENCIES_11) libostreetest.la -tests_test_basic_c_DEPENDENCIES = $(am__DEPENDENCIES_12) +am__DEPENDENCIES_13 = $(am__DEPENDENCIES_12) libostreetest.la +tests_test_basic_c_DEPENDENCIES = $(am__DEPENDENCIES_13) tests_test_basic_c_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_test_basic_c_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ +am_tests_test_bloom_OBJECTS = \ + src/libostree/tests_test_bloom-ostree-bloom.$(OBJEXT) \ + tests/tests_test_bloom-test-bloom.$(OBJEXT) +tests_test_bloom_OBJECTS = $(am_tests_test_bloom_OBJECTS) +tests_test_bloom_DEPENDENCIES = $(am__DEPENDENCIES_13) +tests_test_bloom_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(tests_test_bloom_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ + -o $@ tests_test_bsdiff_SOURCES = tests/test-bsdiff.c tests_test_bsdiff_OBJECTS = \ tests/tests_test_bsdiff-test-bsdiff.$(OBJEXT) -tests_test_bsdiff_DEPENDENCIES = libbsdiff.la $(am__DEPENDENCIES_12) +tests_test_bsdiff_DEPENDENCIES = libbsdiff.la $(am__DEPENDENCIES_13) tests_test_bsdiff_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_test_bsdiff_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ @@ -1051,7 +1142,7 @@ am_tests_test_checksum_OBJECTS = \ src/libostree/tests_test_checksum-ostree-core.$(OBJEXT) \ tests/tests_test_checksum-test-checksum.$(OBJEXT) tests_test_checksum_OBJECTS = $(am_tests_test_checksum_OBJECTS) -tests_test_checksum_DEPENDENCIES = $(am__DEPENDENCIES_12) +tests_test_checksum_DEPENDENCIES = $(am__DEPENDENCIES_13) tests_test_checksum_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_test_checksum_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ @@ -1059,7 +1150,7 @@ tests_test_checksum_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ am_tests_test_gpg_verify_result_OBJECTS = tests/tests_test_gpg_verify_result-test-gpg-verify-result.$(OBJEXT) tests_test_gpg_verify_result_OBJECTS = \ $(am_tests_test_gpg_verify_result_OBJECTS) -tests_test_gpg_verify_result_DEPENDENCIES = $(am__DEPENDENCIES_12) \ +tests_test_gpg_verify_result_DEPENDENCIES = $(am__DEPENDENCIES_13) \ $(am__DEPENDENCIES_3) tests_test_gpg_verify_result_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ @@ -1068,7 +1159,7 @@ tests_test_gpg_verify_result_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ tests_test_keyfile_utils_SOURCES = tests/test-keyfile-utils.c tests_test_keyfile_utils_OBJECTS = \ tests/tests_test_keyfile_utils-test-keyfile-utils.$(OBJEXT) -tests_test_keyfile_utils_DEPENDENCIES = $(am__DEPENDENCIES_12) +tests_test_keyfile_utils_DEPENDENCIES = $(am__DEPENDENCIES_13) tests_test_keyfile_utils_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_test_keyfile_utils_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ @@ -1076,7 +1167,7 @@ tests_test_keyfile_utils_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ am_tests_test_libarchive_import_OBJECTS = tests/tests_test_libarchive_import-test-libarchive-import.$(OBJEXT) tests_test_libarchive_import_OBJECTS = \ $(am_tests_test_libarchive_import_OBJECTS) -tests_test_libarchive_import_DEPENDENCIES = $(am__DEPENDENCIES_12) \ +tests_test_libarchive_import_DEPENDENCIES = $(am__DEPENDENCIES_13) \ $(am__DEPENDENCIES_1) tests_test_libarchive_import_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ @@ -1088,7 +1179,7 @@ am_tests_test_lzma_OBJECTS = \ src/libostree/tests_test_lzma-ostree-lzma-decompressor.$(OBJEXT) \ tests/tests_test_lzma-test-lzma.$(OBJEXT) tests_test_lzma_OBJECTS = $(am_tests_test_lzma_OBJECTS) -tests_test_lzma_DEPENDENCIES = $(am__DEPENDENCIES_12) \ +tests_test_lzma_DEPENDENCIES = $(am__DEPENDENCIES_13) \ $(am__DEPENDENCIES_1) tests_test_lzma_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ @@ -1097,7 +1188,7 @@ tests_test_lzma_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ tests_test_mutable_tree_SOURCES = tests/test-mutable-tree.c tests_test_mutable_tree_OBJECTS = \ tests/tests_test_mutable_tree-test-mutable-tree.$(OBJEXT) -tests_test_mutable_tree_DEPENDENCIES = $(am__DEPENDENCIES_12) +tests_test_mutable_tree_DEPENDENCIES = $(am__DEPENDENCIES_13) tests_test_mutable_tree_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_test_mutable_tree_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ @@ -1105,7 +1196,7 @@ tests_test_mutable_tree_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ tests_test_ot_opt_utils_SOURCES = tests/test-ot-opt-utils.c tests_test_ot_opt_utils_OBJECTS = \ tests/tests_test_ot_opt_utils-test-ot-opt-utils.$(OBJEXT) -tests_test_ot_opt_utils_DEPENDENCIES = $(am__DEPENDENCIES_12) +tests_test_ot_opt_utils_DEPENDENCIES = $(am__DEPENDENCIES_13) tests_test_ot_opt_utils_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_test_ot_opt_utils_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ @@ -1113,7 +1204,7 @@ tests_test_ot_opt_utils_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ tests_test_ot_tool_util_SOURCES = tests/test-ot-tool-util.c tests_test_ot_tool_util_OBJECTS = \ tests/tests_test_ot_tool_util-test-ot-tool-util.$(OBJEXT) -tests_test_ot_tool_util_DEPENDENCIES = $(am__DEPENDENCIES_12) +tests_test_ot_tool_util_DEPENDENCIES = $(am__DEPENDENCIES_13) tests_test_ot_tool_util_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_test_ot_tool_util_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ @@ -1121,7 +1212,7 @@ tests_test_ot_tool_util_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ tests_test_ot_unix_utils_SOURCES = tests/test-ot-unix-utils.c tests_test_ot_unix_utils_OBJECTS = \ tests/tests_test_ot_unix_utils-test-ot-unix-utils.$(OBJEXT) -tests_test_ot_unix_utils_DEPENDENCIES = $(am__DEPENDENCIES_12) +tests_test_ot_unix_utils_DEPENDENCIES = $(am__DEPENDENCIES_13) tests_test_ot_unix_utils_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_test_ot_unix_utils_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ @@ -1129,17 +1220,46 @@ tests_test_ot_unix_utils_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ tests_test_pull_c_SOURCES = tests/test-pull-c.c tests_test_pull_c_OBJECTS = \ tests/tests_test_pull_c-test-pull-c.$(OBJEXT) -tests_test_pull_c_DEPENDENCIES = $(am__DEPENDENCIES_12) +tests_test_pull_c_DEPENDENCIES = $(am__DEPENDENCIES_13) tests_test_pull_c_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_test_pull_c_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ -o $@ +am__tests_test_repo_finder_avahi_SOURCES_DIST = \ + src/libostree/ostree-repo-finder-avahi-parser.c \ + tests/test-repo-finder-avahi.c +@USE_AVAHI_TRUE@am_tests_test_repo_finder_avahi_OBJECTS = src/libostree/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.$(OBJEXT) \ +@USE_AVAHI_TRUE@ tests/tests_test_repo_finder_avahi-test-repo-finder-avahi.$(OBJEXT) +tests_test_repo_finder_avahi_OBJECTS = \ + $(am_tests_test_repo_finder_avahi_OBJECTS) +@USE_AVAHI_TRUE@tests_test_repo_finder_avahi_DEPENDENCIES = \ +@USE_AVAHI_TRUE@ $(am__DEPENDENCIES_13) +tests_test_repo_finder_avahi_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(tests_test_repo_finder_avahi_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_tests_test_repo_finder_config_OBJECTS = tests/tests_test_repo_finder_config-test-repo-finder-config.$(OBJEXT) +tests_test_repo_finder_config_OBJECTS = \ + $(am_tests_test_repo_finder_config_OBJECTS) +tests_test_repo_finder_config_DEPENDENCIES = $(am__DEPENDENCIES_13) +tests_test_repo_finder_config_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(tests_test_repo_finder_config_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am_tests_test_repo_finder_mount_OBJECTS = tests/tests_test_repo_finder_mount-test-repo-finder-mount.$(OBJEXT) +tests_test_repo_finder_mount_OBJECTS = \ + $(am_tests_test_repo_finder_mount_OBJECTS) +tests_test_repo_finder_mount_DEPENDENCIES = $(am__DEPENDENCIES_13) +tests_test_repo_finder_mount_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ + $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(tests_test_repo_finder_mount_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ am_tests_test_rollsum_OBJECTS = \ src/libostree/tests_test_rollsum-ostree-rollsum.$(OBJEXT) \ tests/tests_test_rollsum-test-rollsum.$(OBJEXT) tests_test_rollsum_OBJECTS = $(am_tests_test_rollsum_OBJECTS) tests_test_rollsum_DEPENDENCIES = $(bupsplitpath) \ - $(am__DEPENDENCIES_12) $(am__DEPENDENCIES_1) + $(am__DEPENDENCIES_13) $(am__DEPENDENCIES_1) tests_test_rollsum_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_test_rollsum_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ @@ -1149,7 +1269,7 @@ am_tests_test_rollsum_cli_OBJECTS = \ tests/tests_test_rollsum_cli-test-rollsum-cli.$(OBJEXT) tests_test_rollsum_cli_OBJECTS = $(am_tests_test_rollsum_cli_OBJECTS) tests_test_rollsum_cli_DEPENDENCIES = $(bupsplitpath) \ - $(am__DEPENDENCIES_12) $(am__DEPENDENCIES_1) + $(am__DEPENDENCIES_13) $(am__DEPENDENCIES_1) tests_test_rollsum_cli_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_test_rollsum_cli_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ @@ -1157,7 +1277,7 @@ tests_test_rollsum_cli_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ tests_test_sysroot_c_SOURCES = tests/test-sysroot-c.c tests_test_sysroot_c_OBJECTS = \ tests/tests_test_sysroot_c-test-sysroot-c.$(OBJEXT) -tests_test_sysroot_c_DEPENDENCIES = $(am__DEPENDENCIES_12) +tests_test_sysroot_c_DEPENDENCIES = $(am__DEPENDENCIES_13) tests_test_sysroot_c_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_test_sysroot_c_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ @@ -1166,7 +1286,7 @@ am_tests_test_varint_OBJECTS = \ src/libostree/tests_test_varint-ostree-varint.$(OBJEXT) \ tests/tests_test_varint-test-varint.$(OBJEXT) tests_test_varint_OBJECTS = $(am_tests_test_varint_OBJECTS) -tests_test_varint_DEPENDENCIES = $(am__DEPENDENCIES_12) +tests_test_varint_DEPENDENCIES = $(am__DEPENDENCIES_13) tests_test_varint_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(tests_test_varint_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) \ @@ -1218,7 +1338,8 @@ SOURCES = $(libbsdiff_la_SOURCES) $(libbupsplit_la_SOURCES) \ $(ostree_system_generator_SOURCES) \ $(ostree_trivial_httpd_SOURCES) $(rofiles_fuse_SOURCES) \ $(test_libglnx_errors_SOURCES) $(test_libglnx_fdio_SOURCES) \ - $(test_libglnx_xattrs_SOURCES) tests/test-basic-c.c \ + $(test_libglnx_macros_SOURCES) $(test_libglnx_xattrs_SOURCES) \ + tests/test-basic-c.c $(tests_test_bloom_SOURCES) \ tests/test-bsdiff.c $(tests_test_checksum_SOURCES) \ $(tests_test_gpg_verify_result_SOURCES) \ tests/test-keyfile-utils.c \ @@ -1226,6 +1347,9 @@ SOURCES = $(libbsdiff_la_SOURCES) $(libbupsplit_la_SOURCES) \ $(tests_test_lzma_SOURCES) tests/test-mutable-tree.c \ tests/test-ot-opt-utils.c tests/test-ot-tool-util.c \ tests/test-ot-unix-utils.c tests/test-pull-c.c \ + $(tests_test_repo_finder_avahi_SOURCES) \ + $(tests_test_repo_finder_config_SOURCES) \ + $(tests_test_repo_finder_mount_SOURCES) \ $(tests_test_rollsum_SOURCES) \ $(tests_test_rollsum_cli_SOURCES) tests/test-sysroot-c.c \ $(tests_test_varint_SOURCES) @@ -1240,7 +1364,8 @@ DIST_SOURCES = $(libbsdiff_la_SOURCES) \ $(am__ostree_trivial_httpd_SOURCES_DIST) \ $(am__rofiles_fuse_SOURCES_DIST) \ $(test_libglnx_errors_SOURCES) $(test_libglnx_fdio_SOURCES) \ - $(test_libglnx_xattrs_SOURCES) tests/test-basic-c.c \ + $(test_libglnx_macros_SOURCES) $(test_libglnx_xattrs_SOURCES) \ + tests/test-basic-c.c $(tests_test_bloom_SOURCES) \ tests/test-bsdiff.c $(tests_test_checksum_SOURCES) \ $(tests_test_gpg_verify_result_SOURCES) \ tests/test-keyfile-utils.c \ @@ -1248,6 +1373,9 @@ DIST_SOURCES = $(libbsdiff_la_SOURCES) \ $(tests_test_lzma_SOURCES) tests/test-mutable-tree.c \ tests/test-ot-opt-utils.c tests/test-ot-tool-util.c \ tests/test-ot-unix-utils.c tests/test-pull-c.c \ + $(am__tests_test_repo_finder_avahi_SOURCES_DIST) \ + $(tests_test_repo_finder_config_SOURCES) \ + $(tests_test_repo_finder_mount_SOURCES) \ $(tests_test_rollsum_SOURCES) \ $(tests_test_rollsum_cli_SOURCES) tests/test-sysroot-c.c \ $(tests_test_varint_SOURCES) @@ -1296,7 +1424,12 @@ am__libostreeinclude_HEADERS_DIST = src/libostree/ostree.h \ src/libostree/ostree-deployment.h \ src/libostree/ostree-bootconfig-parser.h \ src/libostree/ostree-repo-deprecated.h \ - src/libostree/ostree-remote.h src/libostree/ostree-version.h + src/libostree/ostree-ref.h src/libostree/ostree-remote.h \ + src/libostree/ostree-repo-finder.h \ + src/libostree/ostree-repo-finder-avahi.h \ + src/libostree/ostree-repo-finder-config.h \ + src/libostree/ostree-repo-finder-mount.h \ + src/libostree/ostree-version.h HEADERS = $(libostreeinclude_HEADERS) RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ distclean-recursive maintainer-clean-recursive @@ -1483,20 +1616,30 @@ am__set_TESTS_bases = \ bases=`for i in $$bases; do echo $$i; done | sed 's/\.log$$//'`; \ bases=`echo $$bases` RECHECK_LOGS = $(TEST_LOGS) -am__EXEEXT_17 = tests/test-basic.sh tests/test-basic-user.sh \ +@ENABLE_EXPERIMENTAL_API_TRUE@am__EXEEXT_19 = \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-find-remotes.sh \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-fsck-collections.sh \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-init-collections.sh \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-prune-collections.sh \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-refs-collections.sh \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-remote-add-collections.sh \ +@ENABLE_EXPERIMENTAL_API_TRUE@ tests/test-summary-collections.sh \ +@ENABLE_EXPERIMENTAL_API_TRUE@ $(am__EXEEXT_2) +am__EXEEXT_20 = tests/test-basic.sh tests/test-basic-user.sh \ tests/test-basic-user-only.sh tests/test-basic-root.sh \ tests/test-pull-subpath.sh tests/test-archivez.sh \ tests/test-remote-add.sh tests/test-remote-headers.sh \ tests/test-remote-gpg-import.sh tests/test-commit-sign.sh \ tests/test-export.sh tests/test-help.sh \ tests/test-libarchive.sh tests/test-parent.sh \ - tests/test-pull-archive-z.sh tests/test-pull-commit-only.sh \ + tests/test-pull-archive.sh tests/test-pull-commit-only.sh \ tests/test-pull-depth.sh tests/test-pull-mirror-summary.sh \ tests/test-pull-large-metadata.sh tests/test-pull-metalink.sh \ tests/test-pull-summary-sigs.sh tests/test-pull-resume.sh \ tests/test-pull-repeated.sh tests/test-pull-untrusted.sh \ - tests/test-pull-override-url.sh tests/test-local-pull.sh \ - tests/test-local-pull-depth.sh tests/test-gpg-signed-commit.sh \ + tests/test-pull-override-url.sh tests/test-pull-localcache.sh \ + tests/test-local-pull.sh tests/test-local-pull-depth.sh \ + tests/test-gpg-signed-commit.sh \ tests/test-admin-upgrade-unconfigured.sh \ tests/test-admin-upgrade-endoflife.sh \ tests/test-admin-deploy-syslinux.sh \ @@ -1516,12 +1659,12 @@ am__EXEEXT_17 = tests/test-basic.sh tests/test-basic-user.sh \ tests/test-auto-summary.sh tests/test-prune.sh \ tests/test-refs.sh tests/test-demo-buildsystem.sh \ tests/test-switchroot.sh tests/test-pull-contenturl.sh \ - tests/test-pull-mirrorlist.sh tests/test-summary-view.sh \ - $(am__EXEEXT_2) $(am__append_58) $(am__append_60) \ - $(am__append_61) -@ENABLE_INSTALLED_TESTS_EXCLUSIVE_FALSE@am__EXEEXT_18 = \ -@ENABLE_INSTALLED_TESTS_EXCLUSIVE_FALSE@ $(am__EXEEXT_17) -am__EXEEXT_19 = $(am__EXEEXT_2) $(am__EXEEXT_18) + tests/test-pull-mirrorlist.sh tests/test-summary-update.sh \ + tests/test-summary-view.sh $(am__EXEEXT_2) $(am__EXEEXT_19) \ + $(am__append_64) $(am__append_66) $(am__append_67) +@ENABLE_INSTALLED_TESTS_EXCLUSIVE_FALSE@am__EXEEXT_21 = \ +@ENABLE_INSTALLED_TESTS_EXCLUSIVE_FALSE@ $(am__EXEEXT_20) +am__EXEEXT_22 = $(am__EXEEXT_2) $(am__EXEEXT_21) TEST_SUITE_LOG = test-suite.log TEST_EXTENSIONS = @EXEEXT@ .test LOG_COMPILE = $(LOG_COMPILER) $(AM_LOG_FLAGS) $(LOG_FLAGS) @@ -1689,6 +1832,8 @@ OBJEXT = @OBJEXT@ OSTREE_FEATURES = @OSTREE_FEATURES@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ +OT_DEP_AVAHI_CFLAGS = @OT_DEP_AVAHI_CFLAGS@ +OT_DEP_AVAHI_LIBS = @OT_DEP_AVAHI_LIBS@ OT_DEP_CURL_CFLAGS = @OT_DEP_CURL_CFLAGS@ OT_DEP_CURL_LIBS = @OT_DEP_CURL_LIBS@ OT_DEP_E2P_CFLAGS = @OT_DEP_E2P_CFLAGS@ @@ -1803,24 +1948,24 @@ AM_CPPFLAGS = -DDATADIR='"$(datadir)"' -DLIBEXECDIR='"$(libexecdir)"' \ -DOSTREE_FEATURES='"$(OSTREE_FEATURES)"' -DOSTREE_COMPILATION \ -DG_LOG_DOMAIN=\"OSTree\" -DOSTREE_GITREV='"$(OSTREE_GITREV)"' \ -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_40 \ - -DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_40 \ + -DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_50 \ -DSOUP_VERSION_MIN_REQUIRED=SOUP_VERSION_2_40 \ -DSOUP_VERSION_MAX_ALLOWED=SOUP_VERSION_2_48 AM_CFLAGS = -std=gnu99 $(WARN_CFLAGS) AM_DISTCHECK_CONFIGURE_FLAGS = --enable-gtk-doc --enable-man \ - --disable-maintainer-mode $(NULL) $(am__append_69) + --disable-maintainer-mode $(NULL) $(am__append_77) SUBDIRS = . $(am__append_14) NULL = BUILT_SOURCES = $(nodist_libostree_1_la_SOURCES) MANPAGES = -CLEANFILES = $(am__append_13) $(BUILT_SOURCES) $(am__append_42) \ +CLEANFILES = $(am__append_13) $(BUILT_SOURCES) $(am__append_46) \ src/ostree/parse-datetime.c tests/libreaddir-rand.so \ tests/ostree-symlink-stamp \ tests/ostree-prepare-root-symlink-stamp \ tests/ostree-remount-symlink-stamp \ tests/rofiles-fuse-symlink-stamp tests/ostree \ tests/ostree-prepare-root tests/ostree-remount \ - tests/rofiles-fuse $(am__append_75) + tests/rofiles-fuse $(am__append_84) EXTRA_DIST = $(all_dist_test_scripts) $(all_dist_test_data) autogen.sh \ COPYING README.md $(am__append_15) libglnx/README.md \ libglnx/COPYING libglnx/libglnx.m4 $(NULL) \ @@ -1834,17 +1979,18 @@ EXTRA_DIST = $(all_dist_test_scripts) $(all_dist_test_data) autogen.sh \ src/libostree/ostree-repo-deprecated.h \ src/libostree/ostree-version.h src/ostree/parse-datetime.y \ buildutil/tap-driver.sh buildutil/tap-test tests/glib.supp \ - tests/ostree.supp $(NULL) $(am__append_59) $(am__append_62) \ - tests/libtest.sh $(am__append_63) tests/libostreetest.h \ + tests/ostree.supp $(NULL) $(am__append_65) $(am__append_68) \ + tests/libtest.sh $(am__append_69) tests/libostreetest.h \ tests/libtest.sh tests/gpg-verify-data/README.md $(NULL) \ src/boot/dracut/module-setup.sh src/boot/dracut/ostree.conf \ src/boot/mkinitcpio/ostree \ src/boot/ostree-prepare-root.service \ src/boot/ostree-remount.service src/boot/grub2/grub2-15_ostree \ - src/boot/grub2/ostree-grub-generator $(NULL) $(am__append_74) + src/boot/grub2/ostree-grub-generator $(NULL) $(am__append_81) \ + $(am__append_83) bin_SCRIPTS = lib_LTLIBRARIES = libostree-1.la -pkglibexec_SCRIPTS = $(am__append_70) +pkglibexec_SCRIPTS = $(am__append_78) noinst_LTLIBRARIES = $(am__append_1) libglnx.la libbsdiff.la \ libotutil.la libostree-kernel-args.la $(am__append_18) \ libostreetest.la @@ -1852,11 +1998,11 @@ privlibdir = $(pkglibdir) privlib_LTLIBRARIES = pkgconfigdir = $(libdir)/pkgconfig pkgconfig_DATA = src/libostree/ostree-1.pc -INTROSPECTION_GIRS = $(am__append_39) +INTROSPECTION_GIRS = $(am__append_43) girdir = $(datadir)/gir-1.0 -gir_DATA = $(am__append_40) +gir_DATA = $(am__append_44) typelibdir = $(libdir)/girepository-1.0 -typelib_DATA = $(am__append_41) +typelib_DATA = $(am__append_45) gsettings_SCHEMAS = ostree_bootdir = $(prefix)/lib/ostree @@ -1874,7 +2020,7 @@ AM_TESTS_ENVIRONMENT = G_TEST_SRCDIR="$(abs_srcdir)" \ LD_LIBRARY_PATH=$$(cd $(top_builddir)/.libs && \ pwd)$${LD_LIBRARY_PATH:+:$${LD_LIBRARY_PATH}} PATH=$$(cd \ $(top_builddir)/tests && pwd):$${PATH} \ - OSTREE_FEATURES="$(OSTREE_FEATURES)" $(NULL) $(am__append_57) + OSTREE_FEATURES="$(OSTREE_FEATURES)" $(NULL) $(am__append_62) LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/buildutil/tap-driver.sh LOG_COMPILER = $(top_srcdir)/buildutil/tap-test installed_test_LTLIBRARIES = $(am__append_12) @@ -1914,14 +2060,14 @@ all_test_ltlibs = $(test_ltlibraries) $(uninstalled_test_ltlibraries) $(installe # This initializes some more variables # This is a special facility to chain together hooks easily -INSTALL_DATA_HOOKS = install-mkdir-remotes-d-hook $(am__append_68) \ - $(am__append_71) +INSTALL_DATA_HOOKS = install-mkdir-remotes-d-hook $(am__append_76) \ + $(am__append_79) ALL_LOCAL_RULES = tests/libreaddir-rand.so shortened_sysconfdir = $$(echo "$(sysconfdir)" | sed -e 's|^$(prefix)||' -e 's|^/||') OSTREE_GITREV = $(shell cd $(srcdir) && if command -v git >/dev/null 2>&1 && test -d .git; then git describe --abbrev=42 --tags --always HEAD; fi) ACLOCAL_AMFLAGS = -I buildutil -I libglnx ${ACLOCAL_FLAGS} GITIGNOREFILES = aclocal.m4 build-aux/ buildutil/*.m4 config.h.in \ - gtk-doc.make $(am__append_55) + gtk-doc.make $(am__append_60) OT_INTERNAL_GIO_UNIX_CFLAGS = $(OT_DEP_GIO_UNIX_CFLAGS) OT_INTERNAL_GIO_UNIX_LIBS = $(OT_DEP_GIO_UNIX_LIBS) OT_INTERNAL_SOUP_CFLAGS = $(OT_DEP_SOUP_CFLAGS) @@ -1940,7 +2086,7 @@ libglnx_srcpath := $(srcdir)/libglnx libglnx_cflags := $(OT_DEP_GIO_UNIX_CFLAGS) "-I$(libglnx_srcpath)" libglnx_libs := $(OT_DEP_GIO_UNIX_LIBS) libglnx_la_SOURCES = \ - libglnx/glnx-alloca.h \ + libglnx/glnx-macros.h \ libglnx/glnx-backport-autocleanups.h \ libglnx/glnx-backport-autoptr.h \ libglnx/glnx-backports.h \ @@ -1969,7 +2115,7 @@ libglnx_la_SOURCES = \ libglnx_la_CFLAGS = $(AM_CFLAGS) $(libglnx_cflags) libglnx_la_LDFLAGS = -avoid-version -Bsymbolic-functions -export-symbols-regex "^glnx_" -no-undefined -export-dynamic libglnx_la_LIBADD = $(libglnx_libs) -libglnx_tests = test-libglnx-xattrs test-libglnx-fdio test-libglnx-errors +libglnx_tests = test-libglnx-xattrs test-libglnx-fdio test-libglnx-errors test-libglnx-macros test_libglnx_xattrs_SOURCES = libglnx/tests/test-libglnx-xattrs.c test_libglnx_xattrs_CFLAGS = $(AM_CFLAGS) $(libglnx_cflags) test_libglnx_xattrs_LDADD = $(libglnx_libs) libglnx.la @@ -1979,6 +2125,9 @@ test_libglnx_fdio_LDADD = $(libglnx_libs) libglnx.la test_libglnx_errors_SOURCES = libglnx/tests/test-libglnx-errors.c test_libglnx_errors_CFLAGS = $(AM_CFLAGS) $(libglnx_cflags) test_libglnx_errors_LDADD = $(libglnx_libs) libglnx.la +test_libglnx_macros_SOURCES = libglnx/tests/test-libglnx-macros.c +test_libglnx_macros_CFLAGS = $(AM_CFLAGS) $(libglnx_cflags) +test_libglnx_macros_LDADD = $(libglnx_libs) libglnx.la libbsdiff_srcpath := $(srcdir)/bsdiff libbsdiff_cflags := $(OT_DEP_GIO_UNIX_CFLAGS) "-I$(bsdiff_srcpath)" libbsdiff_libs := $(OT_DEP_GIO_UNIX_LIBS) @@ -2075,7 +2224,7 @@ libostree_1_la_SOURCES = src/libostree/ostree-async-progress.c \ src/libostree/ostree-varint.h src/libostree/ostree-varint.c \ src/libostree/ostree-linuxfsutil.h \ src/libostree/ostree-linuxfsutil.c src/libostree/ostree-diff.c \ - src/libostree/ostree-mutable-tree.c \ + src/libostree/ostree-mutable-tree.c src/libostree/ostree-ref.c \ src/libostree/ostree-remote.c \ src/libostree/ostree-remote-private.h \ src/libostree/ostree-repo.c \ @@ -2118,10 +2267,20 @@ libostree_1_la_SOURCES = src/libostree/ostree-async-progress.c \ src/libostree/ostree-gpg-verify-result.c \ src/libostree/ostree-gpg-verify-result-private.h \ src/libostree/ostree-autocleanups.h $(NULL) $(am__append_19) \ - $(am__append_20) $(am__append_21) $(am__append_28) \ - $(am__append_29) $(am__append_32) + $(am__append_20) $(am__append_21) $(am__append_22) \ + $(am__append_23) $(am__append_32) $(am__append_33) \ + $(am__append_36) +libostree_experimental_headers = \ + src/libostree/ostree-ref.h \ + src/libostree/ostree-remote.h \ + src/libostree/ostree-repo-finder.h \ + src/libostree/ostree-repo-finder-avahi.h \ + src/libostree/ostree-repo-finder-config.h \ + src/libostree/ostree-repo-finder-mount.h \ + $(NULL) + symbol_files = $(top_srcdir)/src/libostree/libostree-released.sym \ - $(am__append_22) $(am__append_23) + $(am__append_24) $(am__append_25) # http://blog.jgc.org/2007/06/escaping-comma-and-space-in-gnu-make.html wl_versionscript_arg = -Wl,--version-script= libostree_1_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/bsdiff \ @@ -2131,22 +2290,24 @@ libostree_1_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/bsdiff \ $(OT_DEP_LZMA_CFLAGS) $(OT_DEP_ZLIB_CFLAGS) \ $(OT_DEP_OPENSSL_CFLAGS) -fvisibility=hidden \ '-D_OSTREE_PUBLIC=__attribute__((visibility("default"))) \ - extern' $(am__append_24) $(am__append_26) $(am__append_30) \ - $(am__append_33) $(am__append_35) $(am__append_37) + extern' $(am__append_26) $(am__append_28) $(am__append_30) \ + $(am__append_34) $(am__append_37) $(am__append_39) \ + $(am__append_41) libostree_1_la_LDFLAGS = -version-number 1:0:0 -Bsymbolic-functions $(addprefix $(wl_versionscript_arg),$(symbol_files)) libostree_1_la_LIBADD = libotutil.la libglnx.la libbsdiff.la \ libostree-kernel-args.la $(OT_INTERNAL_GIO_UNIX_LIBS) \ $(OT_INTERNAL_GPGME_LIBS) $(OT_DEP_LZMA_LIBS) \ $(OT_DEP_ZLIB_LIBS) $(OT_DEP_OPENSSL_LIBS) $(bupsplitpath) \ - $(am__append_25) $(am__append_27) $(am__append_31) \ - $(am__append_34) $(am__append_36) $(am__append_38) + $(am__append_27) $(am__append_29) $(am__append_31) \ + $(am__append_35) $(am__append_38) $(am__append_40) \ + $(am__append_42) EXTRA_libostree_1_la_DEPENDENCIES = $(symbol_files) @BUILDOPT_INTROSPECTION_TRUE@OSTree_1_0_gir_EXPORT_PACKAGES = ostree-1 @BUILDOPT_INTROSPECTION_TRUE@OSTree_1_0_gir_INCLUDES = Gio-2.0 @BUILDOPT_INTROSPECTION_TRUE@OSTree_1_0_gir_CFLAGS = $(libostree_1_la_CFLAGS) @BUILDOPT_INTROSPECTION_TRUE@OSTree_1_0_gir_LIBS = libostree-1.la -@BUILDOPT_INTROSPECTION_TRUE@OSTree_1_0_gir_SCANNERFLAGS = --warn-all --identifier-prefix=Ostree --symbol-prefix=ostree -@BUILDOPT_INTROSPECTION_TRUE@OSTree_1_0_gir_FILES = $(libostreeinclude_HEADERS) $(filter-out %-private.h %/ostree-soup-uri.h,$(libostree_1_la_SOURCES)) +@BUILDOPT_INTROSPECTION_TRUE@OSTree_1_0_gir_SCANNERFLAGS = --warn-all --identifier-prefix=Ostree --symbol-prefix=ostree $(GI_SCANNERFLAGS) +@BUILDOPT_INTROSPECTION_TRUE@OSTree_1_0_gir_FILES = $(libostreeinclude_HEADERS) $(filter-out %-private.h %/ostree-soup-uri.h $(libostree_experimental_headers),$(libostree_1_la_SOURCES)) gpgreadme_DATA = src/libostree/README-gpg gpgreadmedir = $(datadir)/ostree/trusted.gpg.d @@ -2171,7 +2332,7 @@ ostree_SOURCES = src/ostree/main.c src/ostree/ot-builtin-admin.c \ src/ostree/ot-main.c src/ostree/ot-dump.h src/ostree/ot-dump.c \ src/ostree/ot-editor.c src/ostree/ot-editor.h \ src/ostree/parse-datetime.h src/ostree/parse-datetime.c \ - $(NULL) src/ostree/ot-admin-builtin-init-fs.c \ + $(NULL) $(am__append_47) src/ostree/ot-admin-builtin-init-fs.c \ src/ostree/ot-admin-builtin-diff.c \ src/ostree/ot-admin-builtin-deploy.c \ src/ostree/ot-admin-builtin-undeploy.c \ @@ -2198,7 +2359,7 @@ ostree_SOURCES = src/ostree/main.c src/ostree/ot-builtin-admin.c \ src/ostree/ot-remote-builtin-show-url.c \ src/ostree/ot-remote-builtin-refs.c \ src/ostree/ot-remote-builtin-summary.c $(NULL) \ - $(am__append_43) $(am__append_44) + $(am__append_48) $(am__append_49) ostree_bin_shared_cflags = $(AM_CFLAGS) -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree \ -I$(builddir)/src/libostree -I$(srcdir)/src/ostree -I$(srcdir)/libglnx $(OT_INTERNAL_GIO_UNIX_CFLAGS) \ -DPKGLIBEXECDIR=\"$(pkglibexecdir)\" @@ -2206,11 +2367,11 @@ ostree_bin_shared_cflags = $(AM_CFLAGS) -I$(srcdir)/src/libotutil -I$(srcdir)/sr ostree_bin_shared_ldadd = $(AM_LDFLAGS) libglnx.la libotutil.la libostree-1.la \ $(OT_INTERNAL_GIO_UNIX_LIBS) -ostree_CFLAGS = $(ostree_bin_shared_cflags) $(am__append_46) \ - $(am__append_48) +ostree_CFLAGS = $(ostree_bin_shared_cflags) $(am__append_51) \ + $(am__append_53) ostree_LDADD = $(ostree_bin_shared_ldadd) libbsdiff.la \ - libostree-kernel-args.la $(LIBSYSTEMD_LIBS) $(am__append_47) \ - $(am__append_49) + libostree-kernel-args.la $(LIBSYSTEMD_LIBS) $(am__append_52) \ + $(am__append_54) @USE_LIBSOUP_TRUE@ostree_trivial_httpd_SOURCES = src/ostree/ostree-trivial-httpd.c @USE_LIBSOUP_TRUE@ostree_trivial_httpd_CFLAGS = $(ostree_bin_shared_cflags) $(OT_INTERNAL_SOUP_CFLAGS) @USE_LIBSOUP_TRUE@ostree_trivial_httpd_LDADD = $(ostree_bin_shared_ldadd) $(OT_INTERNAL_SOUP_LIBS) @@ -2219,7 +2380,7 @@ ostree_prepare_root_SOURCES = \ src/switchroot/ostree-prepare-root.c \ $(NULL) -ostree_prepare_root_CPPFLAGS = $(AM_CPPFLAGS) $(am__append_53) +ostree_prepare_root_CPPFLAGS = $(AM_CPPFLAGS) $(am__append_58) # We're using our internal generator @BUILDOPT_BUILTIN_GRUB2_MKCONFIG_TRUE@ostree_boot_SCRIPTS = src/boot/grub2/ostree-grub-generator @@ -2241,7 +2402,7 @@ ostree_remount_SOURCES = \ $(NULL) ostree_remount_CPPFLAGS = $(AM_CPPFLAGS) -Isrc/switchroot \ - $(am__append_54) + $(am__append_59) @BUILDOPT_SYSTEMD_AND_LIBMOUNT_TRUE@ostree_system_generator_SOURCES = src/switchroot/ostree-mount-util.h \ @BUILDOPT_SYSTEMD_AND_LIBMOUNT_TRUE@ src/switchroot/ostree-system-generator.c @@ -2261,8 +2422,9 @@ dist_uninstalled_test_scripts = tests/test-symbols.sh tests/coccinelle.sh # tests *only* run installed, to avoid having to run them twice in CI. # This overrides the glib-tap.mk emphasis on doing both, if we'd # used e.g. `dist_test_scripts`. -dist_test_scripts = $(NULL) $(am__append_66) -test_programs = $(NULL) $(am__append_67) +dist_test_scripts = $(NULL) $(am__append_74) +test_programs = $(NULL) $(am__append_71) $(am__append_72) \ + $(am__append_75) _installed_or_uninstalled_test_scripts = tests/test-basic.sh \ tests/test-basic-user.sh tests/test-basic-user-only.sh \ tests/test-basic-root.sh tests/test-pull-subpath.sh \ @@ -2270,14 +2432,15 @@ _installed_or_uninstalled_test_scripts = tests/test-basic.sh \ tests/test-remote-headers.sh tests/test-remote-gpg-import.sh \ tests/test-commit-sign.sh tests/test-export.sh \ tests/test-help.sh tests/test-libarchive.sh \ - tests/test-parent.sh tests/test-pull-archive-z.sh \ + tests/test-parent.sh tests/test-pull-archive.sh \ tests/test-pull-commit-only.sh tests/test-pull-depth.sh \ tests/test-pull-mirror-summary.sh \ tests/test-pull-large-metadata.sh tests/test-pull-metalink.sh \ tests/test-pull-summary-sigs.sh tests/test-pull-resume.sh \ tests/test-pull-repeated.sh tests/test-pull-untrusted.sh \ - tests/test-pull-override-url.sh tests/test-local-pull.sh \ - tests/test-local-pull-depth.sh tests/test-gpg-signed-commit.sh \ + tests/test-pull-override-url.sh tests/test-pull-localcache.sh \ + tests/test-local-pull.sh tests/test-local-pull-depth.sh \ + tests/test-gpg-signed-commit.sh \ tests/test-admin-upgrade-unconfigured.sh \ tests/test-admin-upgrade-endoflife.sh \ tests/test-admin-deploy-syslinux.sh \ @@ -2297,8 +2460,9 @@ _installed_or_uninstalled_test_scripts = tests/test-basic.sh \ tests/test-auto-summary.sh tests/test-prune.sh \ tests/test-refs.sh tests/test-demo-buildsystem.sh \ tests/test-switchroot.sh tests/test-pull-contenturl.sh \ - tests/test-pull-mirrorlist.sh tests/test-summary-view.sh \ - $(NULL) $(am__append_58) $(am__append_60) $(am__append_61) + tests/test-pull-mirrorlist.sh tests/test-summary-update.sh \ + tests/test-summary-view.sh $(NULL) $(am__append_63) \ + $(am__append_64) $(am__append_66) $(am__append_67) # These call into gjs scripts js_tests = tests/test-corruption.sh tests/test-pull-corruption.sh @@ -2353,17 +2517,17 @@ libreaddir_rand_la_LIBADD = \ $(NULL) libreaddir_rand_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version \ - $(am__append_64) + $(am__append_70) _installed_or_uninstalled_test_programs = tests/test-varint \ tests/test-ot-unix-utils tests/test-bsdiff \ tests/test-mutable-tree tests/test-keyfile-utils \ tests/test-ot-opt-utils tests/test-ot-tool-util \ tests/test-gpg-verify-result tests/test-checksum \ tests/test-lzma tests/test-rollsum tests/test-basic-c \ - tests/test-sysroot-c tests/test-pull-c $(am__append_65) + tests/test-sysroot-c tests/test-pull-c $(am__append_73) common_tests_cflags = $(ostree_bin_shared_cflags) $(OT_INTERNAL_GIO_UNIX_CFLAGS) -I$(srcdir)/libglnx common_tests_ldadd = $(ostree_bin_shared_ldadd) $(OT_INTERNAL_GIO_UNIX_LIBS) -libostreetest_la_SOURCES = tests/libostreetest.c +libostreetest_la_SOURCES = tests/libostreetest.c tests/test-mock-gio.c tests/test-mock-gio.h libostreetest_la_CFLAGS = $(common_tests_cflags) -I $(srcdir)/tests libostreetest_la_LIBADD = $(common_tests_ldadd) TESTS_CFLAGS = $(common_tests_cflags) @@ -2374,6 +2538,18 @@ tests_test_rollsum_cli_LDADD = $(bupsplitpath) $(TESTS_LDADD) $(OT_DEP_ZLIB_LIBS tests_test_rollsum_SOURCES = src/libostree/ostree-rollsum.c tests/test-rollsum.c tests_test_rollsum_CFLAGS = $(TESTS_CFLAGS) $(OT_DEP_ZLIB_CFLAGS) tests_test_rollsum_LDADD = $(bupsplitpath) $(TESTS_LDADD) $(OT_DEP_ZLIB_LIBS) +tests_test_bloom_SOURCES = src/libostree/ostree-bloom.c tests/test-bloom.c +tests_test_bloom_CFLAGS = $(TESTS_CFLAGS) +tests_test_bloom_LDADD = $(TESTS_LDADD) +@USE_AVAHI_TRUE@tests_test_repo_finder_avahi_SOURCES = src/libostree/ostree-repo-finder-avahi-parser.c tests/test-repo-finder-avahi.c +@USE_AVAHI_TRUE@tests_test_repo_finder_avahi_CFLAGS = $(TESTS_CFLAGS) +@USE_AVAHI_TRUE@tests_test_repo_finder_avahi_LDADD = $(TESTS_LDADD) +tests_test_repo_finder_config_SOURCES = tests/test-repo-finder-config.c +tests_test_repo_finder_config_CFLAGS = $(TESTS_CFLAGS) +tests_test_repo_finder_config_LDADD = $(TESTS_LDADD) +tests_test_repo_finder_mount_SOURCES = tests/test-repo-finder-mount.c +tests_test_repo_finder_mount_CFLAGS = $(TESTS_CFLAGS) +tests_test_repo_finder_mount_LDADD = $(TESTS_LDADD) tests_test_mutable_tree_CFLAGS = $(TESTS_CFLAGS) tests_test_mutable_tree_LDADD = $(TESTS_LDADD) tests_test_basic_c_CFLAGS = $(TESTS_CFLAGS) @@ -2444,8 +2620,8 @@ tests_test_gpg_verify_result_LDADD = $(TESTS_LDADD) $(OT_INTERNAL_GPGME_LIBS) @ENABLE_MAN_TRUE@ ostree-pull.1 ostree-refs.1 ostree-remote.1 \ @ENABLE_MAN_TRUE@ ostree-reset.1 ostree-rev-parse.1 \ @ENABLE_MAN_TRUE@ ostree-show.1 ostree-summary.1 \ -@ENABLE_MAN_TRUE@ ostree-static-delta.1 $(am__append_72) \ -@ENABLE_MAN_TRUE@ $(am__append_73) +@ENABLE_MAN_TRUE@ ostree-static-delta.1 $(am__append_80) \ +@ENABLE_MAN_TRUE@ $(am__append_82) @ENABLE_MAN_TRUE@man5_files = ostree.repo.5 ostree.repo-config.5 @ENABLE_MAN_TRUE@man1_MANS = $(addprefix man/,$(man1_files)) @ENABLE_MAN_TRUE@man5_MANS = $(addprefix man/,$(man5_files)) @@ -2739,6 +2915,9 @@ src/libostree/libostree_1_la-ostree-diff.lo: \ src/libostree/libostree_1_la-ostree-mutable-tree.lo: \ src/libostree/$(am__dirstamp) \ src/libostree/$(DEPDIR)/$(am__dirstamp) +src/libostree/libostree_1_la-ostree-ref.lo: \ + src/libostree/$(am__dirstamp) \ + src/libostree/$(DEPDIR)/$(am__dirstamp) src/libostree/libostree_1_la-ostree-remote.lo: \ src/libostree/$(am__dirstamp) \ src/libostree/$(DEPDIR)/$(am__dirstamp) @@ -2832,6 +3011,24 @@ src/libostree/libostree_1_la-ostree-libarchive-input-stream.lo: \ src/libostree/libostree_1_la-ostree-tls-cert-interaction.lo: \ src/libostree/$(am__dirstamp) \ src/libostree/$(DEPDIR)/$(am__dirstamp) +src/libostree/libostree_1_la-ostree-bloom.lo: \ + src/libostree/$(am__dirstamp) \ + src/libostree/$(DEPDIR)/$(am__dirstamp) +src/libostree/libostree_1_la-ostree-repo-finder.lo: \ + src/libostree/$(am__dirstamp) \ + src/libostree/$(DEPDIR)/$(am__dirstamp) +src/libostree/libostree_1_la-ostree-repo-finder-avahi.lo: \ + src/libostree/$(am__dirstamp) \ + src/libostree/$(DEPDIR)/$(am__dirstamp) +src/libostree/libostree_1_la-ostree-repo-finder-config.lo: \ + src/libostree/$(am__dirstamp) \ + src/libostree/$(DEPDIR)/$(am__dirstamp) +src/libostree/libostree_1_la-ostree-repo-finder-mount.lo: \ + src/libostree/$(am__dirstamp) \ + src/libostree/$(DEPDIR)/$(am__dirstamp) +src/libostree/libostree_1_la-ostree-repo-finder-avahi-parser.lo: \ + src/libostree/$(am__dirstamp) \ + src/libostree/$(DEPDIR)/$(am__dirstamp) src/libostree/libostree_1_la-ostree-fetcher-util.lo: \ src/libostree/$(am__dirstamp) \ src/libostree/$(DEPDIR)/$(am__dirstamp) @@ -2873,6 +3070,8 @@ tests/$(DEPDIR)/$(am__dirstamp): @: > tests/$(DEPDIR)/$(am__dirstamp) tests/libostreetest_la-libostreetest.lo: tests/$(am__dirstamp) \ tests/$(DEPDIR)/$(am__dirstamp) +tests/libostreetest_la-test-mock-gio.lo: tests/$(am__dirstamp) \ + tests/$(DEPDIR)/$(am__dirstamp) libostreetest.la: $(libostreetest_la_OBJECTS) $(libostreetest_la_DEPENDENCIES) $(EXTRA_libostreetest_la_DEPENDENCIES) $(AM_V_CCLD)$(libostreetest_la_LINK) $(libostreetest_la_OBJECTS) $(libostreetest_la_LIBADD) $(LIBS) @@ -3368,6 +3567,9 @@ src/ostree/ostree-ot-editor.$(OBJEXT): src/ostree/$(am__dirstamp) \ src/ostree/ostree-parse-datetime.$(OBJEXT): \ src/ostree/$(am__dirstamp) \ src/ostree/$(DEPDIR)/$(am__dirstamp) +src/ostree/ostree-ot-builtin-find-remotes.$(OBJEXT): \ + src/ostree/$(am__dirstamp) \ + src/ostree/$(DEPDIR)/$(am__dirstamp) src/ostree/ostree-ot-admin-builtin-init-fs.$(OBJEXT): \ src/ostree/$(am__dirstamp) \ src/ostree/$(DEPDIR)/$(am__dirstamp) @@ -3526,6 +3728,13 @@ libglnx/tests/test_libglnx_fdio-test-libglnx-fdio.$(OBJEXT): \ test-libglnx-fdio$(EXEEXT): $(test_libglnx_fdio_OBJECTS) $(test_libglnx_fdio_DEPENDENCIES) $(EXTRA_test_libglnx_fdio_DEPENDENCIES) @rm -f test-libglnx-fdio$(EXEEXT) $(AM_V_CCLD)$(test_libglnx_fdio_LINK) $(test_libglnx_fdio_OBJECTS) $(test_libglnx_fdio_LDADD) $(LIBS) +libglnx/tests/test_libglnx_macros-test-libglnx-macros.$(OBJEXT): \ + libglnx/tests/$(am__dirstamp) \ + libglnx/tests/$(DEPDIR)/$(am__dirstamp) + +test-libglnx-macros$(EXEEXT): $(test_libglnx_macros_OBJECTS) $(test_libglnx_macros_DEPENDENCIES) $(EXTRA_test_libglnx_macros_DEPENDENCIES) + @rm -f test-libglnx-macros$(EXEEXT) + $(AM_V_CCLD)$(test_libglnx_macros_LINK) $(test_libglnx_macros_OBJECTS) $(test_libglnx_macros_LDADD) $(LIBS) libglnx/tests/test_libglnx_xattrs-test-libglnx-xattrs.$(OBJEXT): \ libglnx/tests/$(am__dirstamp) \ libglnx/tests/$(DEPDIR)/$(am__dirstamp) @@ -3539,6 +3748,15 @@ tests/tests_test_basic_c-test-basic-c.$(OBJEXT): \ tests/test-basic-c$(EXEEXT): $(tests_test_basic_c_OBJECTS) $(tests_test_basic_c_DEPENDENCIES) $(EXTRA_tests_test_basic_c_DEPENDENCIES) tests/$(am__dirstamp) @rm -f tests/test-basic-c$(EXEEXT) $(AM_V_CCLD)$(tests_test_basic_c_LINK) $(tests_test_basic_c_OBJECTS) $(tests_test_basic_c_LDADD) $(LIBS) +src/libostree/tests_test_bloom-ostree-bloom.$(OBJEXT): \ + src/libostree/$(am__dirstamp) \ + src/libostree/$(DEPDIR)/$(am__dirstamp) +tests/tests_test_bloom-test-bloom.$(OBJEXT): tests/$(am__dirstamp) \ + tests/$(DEPDIR)/$(am__dirstamp) + +tests/test-bloom$(EXEEXT): $(tests_test_bloom_OBJECTS) $(tests_test_bloom_DEPENDENCIES) $(EXTRA_tests_test_bloom_DEPENDENCIES) tests/$(am__dirstamp) + @rm -f tests/test-bloom$(EXEEXT) + $(AM_V_CCLD)$(tests_test_bloom_LINK) $(tests_test_bloom_OBJECTS) $(tests_test_bloom_LDADD) $(LIBS) tests/tests_test_bsdiff-test-bsdiff.$(OBJEXT): tests/$(am__dirstamp) \ tests/$(DEPDIR)/$(am__dirstamp) @@ -3617,6 +3835,27 @@ tests/tests_test_pull_c-test-pull-c.$(OBJEXT): tests/$(am__dirstamp) \ tests/test-pull-c$(EXEEXT): $(tests_test_pull_c_OBJECTS) $(tests_test_pull_c_DEPENDENCIES) $(EXTRA_tests_test_pull_c_DEPENDENCIES) tests/$(am__dirstamp) @rm -f tests/test-pull-c$(EXEEXT) $(AM_V_CCLD)$(tests_test_pull_c_LINK) $(tests_test_pull_c_OBJECTS) $(tests_test_pull_c_LDADD) $(LIBS) +src/libostree/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.$(OBJEXT): \ + src/libostree/$(am__dirstamp) \ + src/libostree/$(DEPDIR)/$(am__dirstamp) +tests/tests_test_repo_finder_avahi-test-repo-finder-avahi.$(OBJEXT): \ + tests/$(am__dirstamp) tests/$(DEPDIR)/$(am__dirstamp) + +tests/test-repo-finder-avahi$(EXEEXT): $(tests_test_repo_finder_avahi_OBJECTS) $(tests_test_repo_finder_avahi_DEPENDENCIES) $(EXTRA_tests_test_repo_finder_avahi_DEPENDENCIES) tests/$(am__dirstamp) + @rm -f tests/test-repo-finder-avahi$(EXEEXT) + $(AM_V_CCLD)$(tests_test_repo_finder_avahi_LINK) $(tests_test_repo_finder_avahi_OBJECTS) $(tests_test_repo_finder_avahi_LDADD) $(LIBS) +tests/tests_test_repo_finder_config-test-repo-finder-config.$(OBJEXT): \ + tests/$(am__dirstamp) tests/$(DEPDIR)/$(am__dirstamp) + +tests/test-repo-finder-config$(EXEEXT): $(tests_test_repo_finder_config_OBJECTS) $(tests_test_repo_finder_config_DEPENDENCIES) $(EXTRA_tests_test_repo_finder_config_DEPENDENCIES) tests/$(am__dirstamp) + @rm -f tests/test-repo-finder-config$(EXEEXT) + $(AM_V_CCLD)$(tests_test_repo_finder_config_LINK) $(tests_test_repo_finder_config_OBJECTS) $(tests_test_repo_finder_config_LDADD) $(LIBS) +tests/tests_test_repo_finder_mount-test-repo-finder-mount.$(OBJEXT): \ + tests/$(am__dirstamp) tests/$(DEPDIR)/$(am__dirstamp) + +tests/test-repo-finder-mount$(EXEEXT): $(tests_test_repo_finder_mount_OBJECTS) $(tests_test_repo_finder_mount_DEPENDENCIES) $(EXTRA_tests_test_repo_finder_mount_DEPENDENCIES) tests/$(am__dirstamp) + @rm -f tests/test-repo-finder-mount$(EXEEXT) + $(AM_V_CCLD)$(tests_test_repo_finder_mount_LINK) $(tests_test_repo_finder_mount_OBJECTS) $(tests_test_repo_finder_mount_LDADD) $(LIBS) src/libostree/tests_test_rollsum-ostree-rollsum.$(OBJEXT): \ src/libostree/$(am__dirstamp) \ src/libostree/$(DEPDIR)/$(am__dirstamp) @@ -3894,9 +4133,11 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@libglnx/$(DEPDIR)/libglnx_la-glnx-xattrs.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@libglnx/tests/$(DEPDIR)/test_libglnx_errors-test-libglnx-errors.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@libglnx/tests/$(DEPDIR)/test_libglnx_fdio-test-libglnx-fdio.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@libglnx/tests/$(DEPDIR)/test_libglnx_macros-test-libglnx-macros.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@libglnx/tests/$(DEPDIR)/test_libglnx_xattrs-test-libglnx-xattrs.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/bupsplit.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-async-progress.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-bloom.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-bootconfig-parser.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-bootloader-grub2.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-bootloader-syslinux.Plo@am__quote@ @@ -3924,11 +4165,17 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-lzma-decompressor.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-metalink.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-mutable-tree.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-ref.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-remote.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-checkout.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-commit.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-file-enumerator.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-file.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-avahi-parser.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-avahi.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-config.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-mount.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-libarchive.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-prune.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-pull.Plo@am__quote@ @@ -3950,10 +4197,12 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-tls-cert-interaction.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_1_la-ostree-varint.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/libostree_kernel_args_la-ostree-kernel-args.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/tests_test_bloom-ostree-bloom.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/tests_test_checksum-ostree-core.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/tests_test_lzma-ostree-lzma-common.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/tests_test_lzma-ostree-lzma-compressor.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/tests_test_lzma-ostree-lzma-decompressor.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/tests_test_rollsum-ostree-rollsum.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/tests_test_rollsum_cli-ostree-rollsum.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/libostree/$(DEPDIR)/tests_test_varint-ostree-varint.Po@am__quote@ @@ -3994,6 +4243,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@src/ostree/$(DEPDIR)/ostree-ot-builtin-config.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/ostree/$(DEPDIR)/ostree-ot-builtin-diff.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/ostree/$(DEPDIR)/ostree-ot-builtin-export.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@src/ostree/$(DEPDIR)/ostree-ot-builtin-find-remotes.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/ostree/$(DEPDIR)/ostree-ot-builtin-fsck.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/ostree/$(DEPDIR)/ostree-ot-builtin-gpg-sign.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/ostree/$(DEPDIR)/ostree-ot-builtin-init.Po@am__quote@ @@ -4031,8 +4281,10 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@src/switchroot/$(DEPDIR)/ostree_remount-ostree-remount.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@src/switchroot/$(DEPDIR)/ostree_system_generator-ostree-system-generator.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/libostreetest_la-libostreetest.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/libostreetest_la-test-mock-gio.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/libreaddir_rand_la-readdir-rand.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/tests_test_basic_c-test-basic-c.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/tests_test_bloom-test-bloom.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/tests_test_bsdiff-test-bsdiff.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/tests_test_checksum-test-checksum.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/tests_test_gpg_verify_result-test-gpg-verify-result.Po@am__quote@ @@ -4044,6 +4296,9 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/tests_test_ot_tool_util-test-ot-tool-util.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/tests_test_ot_unix_utils-test-ot-unix-utils.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/tests_test_pull_c-test-pull-c.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/tests_test_repo_finder_avahi-test-repo-finder-avahi.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/tests_test_repo_finder_config-test-repo-finder-config.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/tests_test_repo_finder_mount-test-repo-finder-mount.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/tests_test_rollsum-test-rollsum.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/tests_test_rollsum_cli-test-rollsum-cli.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@tests/$(DEPDIR)/tests_test_sysroot_c-test-sysroot-c.Po@am__quote@ @@ -4248,6 +4503,13 @@ src/libostree/libostree_1_la-ostree-mutable-tree.lo: src/libostree/ostree-mutabl @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -c -o src/libostree/libostree_1_la-ostree-mutable-tree.lo `test -f 'src/libostree/ostree-mutable-tree.c' || echo '$(srcdir)/'`src/libostree/ostree-mutable-tree.c +src/libostree/libostree_1_la-ostree-ref.lo: src/libostree/ostree-ref.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -MT src/libostree/libostree_1_la-ostree-ref.lo -MD -MP -MF src/libostree/$(DEPDIR)/libostree_1_la-ostree-ref.Tpo -c -o src/libostree/libostree_1_la-ostree-ref.lo `test -f 'src/libostree/ostree-ref.c' || echo '$(srcdir)/'`src/libostree/ostree-ref.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/libostree/$(DEPDIR)/libostree_1_la-ostree-ref.Tpo src/libostree/$(DEPDIR)/libostree_1_la-ostree-ref.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/libostree/ostree-ref.c' object='src/libostree/libostree_1_la-ostree-ref.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -c -o src/libostree/libostree_1_la-ostree-ref.lo `test -f 'src/libostree/ostree-ref.c' || echo '$(srcdir)/'`src/libostree/ostree-ref.c + src/libostree/libostree_1_la-ostree-remote.lo: src/libostree/ostree-remote.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -MT src/libostree/libostree_1_la-ostree-remote.lo -MD -MP -MF src/libostree/$(DEPDIR)/libostree_1_la-ostree-remote.Tpo -c -o src/libostree/libostree_1_la-ostree-remote.lo `test -f 'src/libostree/ostree-remote.c' || echo '$(srcdir)/'`src/libostree/ostree-remote.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/libostree/$(DEPDIR)/libostree_1_la-ostree-remote.Tpo src/libostree/$(DEPDIR)/libostree_1_la-ostree-remote.Plo @@ -4465,6 +4727,48 @@ src/libostree/libostree_1_la-ostree-tls-cert-interaction.lo: src/libostree/ostre @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -c -o src/libostree/libostree_1_la-ostree-tls-cert-interaction.lo `test -f 'src/libostree/ostree-tls-cert-interaction.c' || echo '$(srcdir)/'`src/libostree/ostree-tls-cert-interaction.c +src/libostree/libostree_1_la-ostree-bloom.lo: src/libostree/ostree-bloom.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -MT src/libostree/libostree_1_la-ostree-bloom.lo -MD -MP -MF src/libostree/$(DEPDIR)/libostree_1_la-ostree-bloom.Tpo -c -o src/libostree/libostree_1_la-ostree-bloom.lo `test -f 'src/libostree/ostree-bloom.c' || echo '$(srcdir)/'`src/libostree/ostree-bloom.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/libostree/$(DEPDIR)/libostree_1_la-ostree-bloom.Tpo src/libostree/$(DEPDIR)/libostree_1_la-ostree-bloom.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/libostree/ostree-bloom.c' object='src/libostree/libostree_1_la-ostree-bloom.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -c -o src/libostree/libostree_1_la-ostree-bloom.lo `test -f 'src/libostree/ostree-bloom.c' || echo '$(srcdir)/'`src/libostree/ostree-bloom.c + +src/libostree/libostree_1_la-ostree-repo-finder.lo: src/libostree/ostree-repo-finder.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -MT src/libostree/libostree_1_la-ostree-repo-finder.lo -MD -MP -MF src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder.Tpo -c -o src/libostree/libostree_1_la-ostree-repo-finder.lo `test -f 'src/libostree/ostree-repo-finder.c' || echo '$(srcdir)/'`src/libostree/ostree-repo-finder.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder.Tpo src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/libostree/ostree-repo-finder.c' object='src/libostree/libostree_1_la-ostree-repo-finder.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -c -o src/libostree/libostree_1_la-ostree-repo-finder.lo `test -f 'src/libostree/ostree-repo-finder.c' || echo '$(srcdir)/'`src/libostree/ostree-repo-finder.c + +src/libostree/libostree_1_la-ostree-repo-finder-avahi.lo: src/libostree/ostree-repo-finder-avahi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -MT src/libostree/libostree_1_la-ostree-repo-finder-avahi.lo -MD -MP -MF src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-avahi.Tpo -c -o src/libostree/libostree_1_la-ostree-repo-finder-avahi.lo `test -f 'src/libostree/ostree-repo-finder-avahi.c' || echo '$(srcdir)/'`src/libostree/ostree-repo-finder-avahi.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-avahi.Tpo src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-avahi.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/libostree/ostree-repo-finder-avahi.c' object='src/libostree/libostree_1_la-ostree-repo-finder-avahi.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -c -o src/libostree/libostree_1_la-ostree-repo-finder-avahi.lo `test -f 'src/libostree/ostree-repo-finder-avahi.c' || echo '$(srcdir)/'`src/libostree/ostree-repo-finder-avahi.c + +src/libostree/libostree_1_la-ostree-repo-finder-config.lo: src/libostree/ostree-repo-finder-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -MT src/libostree/libostree_1_la-ostree-repo-finder-config.lo -MD -MP -MF src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-config.Tpo -c -o src/libostree/libostree_1_la-ostree-repo-finder-config.lo `test -f 'src/libostree/ostree-repo-finder-config.c' || echo '$(srcdir)/'`src/libostree/ostree-repo-finder-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-config.Tpo src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-config.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/libostree/ostree-repo-finder-config.c' object='src/libostree/libostree_1_la-ostree-repo-finder-config.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -c -o src/libostree/libostree_1_la-ostree-repo-finder-config.lo `test -f 'src/libostree/ostree-repo-finder-config.c' || echo '$(srcdir)/'`src/libostree/ostree-repo-finder-config.c + +src/libostree/libostree_1_la-ostree-repo-finder-mount.lo: src/libostree/ostree-repo-finder-mount.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -MT src/libostree/libostree_1_la-ostree-repo-finder-mount.lo -MD -MP -MF src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-mount.Tpo -c -o src/libostree/libostree_1_la-ostree-repo-finder-mount.lo `test -f 'src/libostree/ostree-repo-finder-mount.c' || echo '$(srcdir)/'`src/libostree/ostree-repo-finder-mount.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-mount.Tpo src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-mount.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/libostree/ostree-repo-finder-mount.c' object='src/libostree/libostree_1_la-ostree-repo-finder-mount.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -c -o src/libostree/libostree_1_la-ostree-repo-finder-mount.lo `test -f 'src/libostree/ostree-repo-finder-mount.c' || echo '$(srcdir)/'`src/libostree/ostree-repo-finder-mount.c + +src/libostree/libostree_1_la-ostree-repo-finder-avahi-parser.lo: src/libostree/ostree-repo-finder-avahi-parser.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -MT src/libostree/libostree_1_la-ostree-repo-finder-avahi-parser.lo -MD -MP -MF src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-avahi-parser.Tpo -c -o src/libostree/libostree_1_la-ostree-repo-finder-avahi-parser.lo `test -f 'src/libostree/ostree-repo-finder-avahi-parser.c' || echo '$(srcdir)/'`src/libostree/ostree-repo-finder-avahi-parser.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-avahi-parser.Tpo src/libostree/$(DEPDIR)/libostree_1_la-ostree-repo-finder-avahi-parser.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/libostree/ostree-repo-finder-avahi-parser.c' object='src/libostree/libostree_1_la-ostree-repo-finder-avahi-parser.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -c -o src/libostree/libostree_1_la-ostree-repo-finder-avahi-parser.lo `test -f 'src/libostree/ostree-repo-finder-avahi-parser.c' || echo '$(srcdir)/'`src/libostree/ostree-repo-finder-avahi-parser.c + src/libostree/libostree_1_la-ostree-fetcher-util.lo: src/libostree/ostree-fetcher-util.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostree_1_la_CFLAGS) $(CFLAGS) -MT src/libostree/libostree_1_la-ostree-fetcher-util.lo -MD -MP -MF src/libostree/$(DEPDIR)/libostree_1_la-ostree-fetcher-util.Tpo -c -o src/libostree/libostree_1_la-ostree-fetcher-util.lo `test -f 'src/libostree/ostree-fetcher-util.c' || echo '$(srcdir)/'`src/libostree/ostree-fetcher-util.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/libostree/$(DEPDIR)/libostree_1_la-ostree-fetcher-util.Tpo src/libostree/$(DEPDIR)/libostree_1_la-ostree-fetcher-util.Plo @@ -4535,6 +4839,13 @@ tests/libostreetest_la-libostreetest.lo: tests/libostreetest.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostreetest_la_CFLAGS) $(CFLAGS) -c -o tests/libostreetest_la-libostreetest.lo `test -f 'tests/libostreetest.c' || echo '$(srcdir)/'`tests/libostreetest.c +tests/libostreetest_la-test-mock-gio.lo: tests/test-mock-gio.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostreetest_la_CFLAGS) $(CFLAGS) -MT tests/libostreetest_la-test-mock-gio.lo -MD -MP -MF tests/$(DEPDIR)/libostreetest_la-test-mock-gio.Tpo -c -o tests/libostreetest_la-test-mock-gio.lo `test -f 'tests/test-mock-gio.c' || echo '$(srcdir)/'`tests/test-mock-gio.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/$(DEPDIR)/libostreetest_la-test-mock-gio.Tpo tests/$(DEPDIR)/libostreetest_la-test-mock-gio.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/test-mock-gio.c' object='tests/libostreetest_la-test-mock-gio.lo' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libostreetest_la_CFLAGS) $(CFLAGS) -c -o tests/libostreetest_la-test-mock-gio.lo `test -f 'tests/test-mock-gio.c' || echo '$(srcdir)/'`tests/test-mock-gio.c + src/libotutil/libotutil_la-ot-checksum-utils.lo: src/libotutil/ot-checksum-utils.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libotutil_la_CFLAGS) $(CFLAGS) -MT src/libotutil/libotutil_la-ot-checksum-utils.lo -MD -MP -MF src/libotutil/$(DEPDIR)/libotutil_la-ot-checksum-utils.Tpo -c -o src/libotutil/libotutil_la-ot-checksum-utils.lo `test -f 'src/libotutil/ot-checksum-utils.c' || echo '$(srcdir)/'`src/libotutil/ot-checksum-utils.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/libotutil/$(DEPDIR)/libotutil_la-ot-checksum-utils.Tpo src/libotutil/$(DEPDIR)/libotutil_la-ot-checksum-utils.Plo @@ -5004,6 +5315,20 @@ src/ostree/ostree-parse-datetime.obj: src/ostree/parse-datetime.c @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-parse-datetime.obj `if test -f 'src/ostree/parse-datetime.c'; then $(CYGPATH_W) 'src/ostree/parse-datetime.c'; else $(CYGPATH_W) '$(srcdir)/src/ostree/parse-datetime.c'; fi` +src/ostree/ostree-ot-builtin-find-remotes.o: src/ostree/ot-builtin-find-remotes.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ostree_CFLAGS) $(CFLAGS) -MT src/ostree/ostree-ot-builtin-find-remotes.o -MD -MP -MF src/ostree/$(DEPDIR)/ostree-ot-builtin-find-remotes.Tpo -c -o src/ostree/ostree-ot-builtin-find-remotes.o `test -f 'src/ostree/ot-builtin-find-remotes.c' || echo '$(srcdir)/'`src/ostree/ot-builtin-find-remotes.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ostree/$(DEPDIR)/ostree-ot-builtin-find-remotes.Tpo src/ostree/$(DEPDIR)/ostree-ot-builtin-find-remotes.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/ostree/ot-builtin-find-remotes.c' object='src/ostree/ostree-ot-builtin-find-remotes.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-builtin-find-remotes.o `test -f 'src/ostree/ot-builtin-find-remotes.c' || echo '$(srcdir)/'`src/ostree/ot-builtin-find-remotes.c + +src/ostree/ostree-ot-builtin-find-remotes.obj: src/ostree/ot-builtin-find-remotes.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ostree_CFLAGS) $(CFLAGS) -MT src/ostree/ostree-ot-builtin-find-remotes.obj -MD -MP -MF src/ostree/$(DEPDIR)/ostree-ot-builtin-find-remotes.Tpo -c -o src/ostree/ostree-ot-builtin-find-remotes.obj `if test -f 'src/ostree/ot-builtin-find-remotes.c'; then $(CYGPATH_W) 'src/ostree/ot-builtin-find-remotes.c'; else $(CYGPATH_W) '$(srcdir)/src/ostree/ot-builtin-find-remotes.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ostree/$(DEPDIR)/ostree-ot-builtin-find-remotes.Tpo src/ostree/$(DEPDIR)/ostree-ot-builtin-find-remotes.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/ostree/ot-builtin-find-remotes.c' object='src/ostree/ostree-ot-builtin-find-remotes.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-builtin-find-remotes.obj `if test -f 'src/ostree/ot-builtin-find-remotes.c'; then $(CYGPATH_W) 'src/ostree/ot-builtin-find-remotes.c'; else $(CYGPATH_W) '$(srcdir)/src/ostree/ot-builtin-find-remotes.c'; fi` + src/ostree/ostree-ot-admin-builtin-init-fs.o: src/ostree/ot-admin-builtin-init-fs.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-init-fs.o -MD -MP -MF src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-init-fs.Tpo -c -o src/ostree/ostree-ot-admin-builtin-init-fs.o `test -f 'src/ostree/ot-admin-builtin-init-fs.c' || echo '$(srcdir)/'`src/ostree/ot-admin-builtin-init-fs.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-init-fs.Tpo src/ostree/$(DEPDIR)/ostree-ot-admin-builtin-init-fs.Po @@ -5508,6 +5833,20 @@ libglnx/tests/test_libglnx_fdio-test-libglnx-fdio.obj: libglnx/tests/test-libgln @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) $(test_libglnx_fdio_CFLAGS) $(CFLAGS) -c -o libglnx/tests/test_libglnx_fdio-test-libglnx-fdio.obj `if test -f 'libglnx/tests/test-libglnx-fdio.c'; then $(CYGPATH_W) 'libglnx/tests/test-libglnx-fdio.c'; else $(CYGPATH_W) '$(srcdir)/libglnx/tests/test-libglnx-fdio.c'; fi` +libglnx/tests/test_libglnx_macros-test-libglnx-macros.o: libglnx/tests/test-libglnx-macros.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_libglnx_macros_CFLAGS) $(CFLAGS) -MT libglnx/tests/test_libglnx_macros-test-libglnx-macros.o -MD -MP -MF libglnx/tests/$(DEPDIR)/test_libglnx_macros-test-libglnx-macros.Tpo -c -o libglnx/tests/test_libglnx_macros-test-libglnx-macros.o `test -f 'libglnx/tests/test-libglnx-macros.c' || echo '$(srcdir)/'`libglnx/tests/test-libglnx-macros.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libglnx/tests/$(DEPDIR)/test_libglnx_macros-test-libglnx-macros.Tpo libglnx/tests/$(DEPDIR)/test_libglnx_macros-test-libglnx-macros.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libglnx/tests/test-libglnx-macros.c' object='libglnx/tests/test_libglnx_macros-test-libglnx-macros.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) $(test_libglnx_macros_CFLAGS) $(CFLAGS) -c -o libglnx/tests/test_libglnx_macros-test-libglnx-macros.o `test -f 'libglnx/tests/test-libglnx-macros.c' || echo '$(srcdir)/'`libglnx/tests/test-libglnx-macros.c + +libglnx/tests/test_libglnx_macros-test-libglnx-macros.obj: libglnx/tests/test-libglnx-macros.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_libglnx_macros_CFLAGS) $(CFLAGS) -MT libglnx/tests/test_libglnx_macros-test-libglnx-macros.obj -MD -MP -MF libglnx/tests/$(DEPDIR)/test_libglnx_macros-test-libglnx-macros.Tpo -c -o libglnx/tests/test_libglnx_macros-test-libglnx-macros.obj `if test -f 'libglnx/tests/test-libglnx-macros.c'; then $(CYGPATH_W) 'libglnx/tests/test-libglnx-macros.c'; else $(CYGPATH_W) '$(srcdir)/libglnx/tests/test-libglnx-macros.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libglnx/tests/$(DEPDIR)/test_libglnx_macros-test-libglnx-macros.Tpo libglnx/tests/$(DEPDIR)/test_libglnx_macros-test-libglnx-macros.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libglnx/tests/test-libglnx-macros.c' object='libglnx/tests/test_libglnx_macros-test-libglnx-macros.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) $(test_libglnx_macros_CFLAGS) $(CFLAGS) -c -o libglnx/tests/test_libglnx_macros-test-libglnx-macros.obj `if test -f 'libglnx/tests/test-libglnx-macros.c'; then $(CYGPATH_W) 'libglnx/tests/test-libglnx-macros.c'; else $(CYGPATH_W) '$(srcdir)/libglnx/tests/test-libglnx-macros.c'; fi` + libglnx/tests/test_libglnx_xattrs-test-libglnx-xattrs.o: libglnx/tests/test-libglnx-xattrs.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(test_libglnx_xattrs_CFLAGS) $(CFLAGS) -MT libglnx/tests/test_libglnx_xattrs-test-libglnx-xattrs.o -MD -MP -MF libglnx/tests/$(DEPDIR)/test_libglnx_xattrs-test-libglnx-xattrs.Tpo -c -o libglnx/tests/test_libglnx_xattrs-test-libglnx-xattrs.o `test -f 'libglnx/tests/test-libglnx-xattrs.c' || echo '$(srcdir)/'`libglnx/tests/test-libglnx-xattrs.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) libglnx/tests/$(DEPDIR)/test_libglnx_xattrs-test-libglnx-xattrs.Tpo libglnx/tests/$(DEPDIR)/test_libglnx_xattrs-test-libglnx-xattrs.Po @@ -5536,6 +5875,34 @@ tests/tests_test_basic_c-test-basic-c.obj: tests/test-basic-c.c @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) $(tests_test_basic_c_CFLAGS) $(CFLAGS) -c -o tests/tests_test_basic_c-test-basic-c.obj `if test -f 'tests/test-basic-c.c'; then $(CYGPATH_W) 'tests/test-basic-c.c'; else $(CYGPATH_W) '$(srcdir)/tests/test-basic-c.c'; fi` +src/libostree/tests_test_bloom-ostree-bloom.o: src/libostree/ostree-bloom.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_test_bloom_CFLAGS) $(CFLAGS) -MT src/libostree/tests_test_bloom-ostree-bloom.o -MD -MP -MF src/libostree/$(DEPDIR)/tests_test_bloom-ostree-bloom.Tpo -c -o src/libostree/tests_test_bloom-ostree-bloom.o `test -f 'src/libostree/ostree-bloom.c' || echo '$(srcdir)/'`src/libostree/ostree-bloom.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/libostree/$(DEPDIR)/tests_test_bloom-ostree-bloom.Tpo src/libostree/$(DEPDIR)/tests_test_bloom-ostree-bloom.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/libostree/ostree-bloom.c' object='src/libostree/tests_test_bloom-ostree-bloom.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) $(tests_test_bloom_CFLAGS) $(CFLAGS) -c -o src/libostree/tests_test_bloom-ostree-bloom.o `test -f 'src/libostree/ostree-bloom.c' || echo '$(srcdir)/'`src/libostree/ostree-bloom.c + +src/libostree/tests_test_bloom-ostree-bloom.obj: src/libostree/ostree-bloom.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_test_bloom_CFLAGS) $(CFLAGS) -MT src/libostree/tests_test_bloom-ostree-bloom.obj -MD -MP -MF src/libostree/$(DEPDIR)/tests_test_bloom-ostree-bloom.Tpo -c -o src/libostree/tests_test_bloom-ostree-bloom.obj `if test -f 'src/libostree/ostree-bloom.c'; then $(CYGPATH_W) 'src/libostree/ostree-bloom.c'; else $(CYGPATH_W) '$(srcdir)/src/libostree/ostree-bloom.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/libostree/$(DEPDIR)/tests_test_bloom-ostree-bloom.Tpo src/libostree/$(DEPDIR)/tests_test_bloom-ostree-bloom.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/libostree/ostree-bloom.c' object='src/libostree/tests_test_bloom-ostree-bloom.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) $(tests_test_bloom_CFLAGS) $(CFLAGS) -c -o src/libostree/tests_test_bloom-ostree-bloom.obj `if test -f 'src/libostree/ostree-bloom.c'; then $(CYGPATH_W) 'src/libostree/ostree-bloom.c'; else $(CYGPATH_W) '$(srcdir)/src/libostree/ostree-bloom.c'; fi` + +tests/tests_test_bloom-test-bloom.o: tests/test-bloom.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_test_bloom_CFLAGS) $(CFLAGS) -MT tests/tests_test_bloom-test-bloom.o -MD -MP -MF tests/$(DEPDIR)/tests_test_bloom-test-bloom.Tpo -c -o tests/tests_test_bloom-test-bloom.o `test -f 'tests/test-bloom.c' || echo '$(srcdir)/'`tests/test-bloom.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/$(DEPDIR)/tests_test_bloom-test-bloom.Tpo tests/$(DEPDIR)/tests_test_bloom-test-bloom.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/test-bloom.c' object='tests/tests_test_bloom-test-bloom.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) $(tests_test_bloom_CFLAGS) $(CFLAGS) -c -o tests/tests_test_bloom-test-bloom.o `test -f 'tests/test-bloom.c' || echo '$(srcdir)/'`tests/test-bloom.c + +tests/tests_test_bloom-test-bloom.obj: tests/test-bloom.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_test_bloom_CFLAGS) $(CFLAGS) -MT tests/tests_test_bloom-test-bloom.obj -MD -MP -MF tests/$(DEPDIR)/tests_test_bloom-test-bloom.Tpo -c -o tests/tests_test_bloom-test-bloom.obj `if test -f 'tests/test-bloom.c'; then $(CYGPATH_W) 'tests/test-bloom.c'; else $(CYGPATH_W) '$(srcdir)/tests/test-bloom.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/$(DEPDIR)/tests_test_bloom-test-bloom.Tpo tests/$(DEPDIR)/tests_test_bloom-test-bloom.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/test-bloom.c' object='tests/tests_test_bloom-test-bloom.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) $(tests_test_bloom_CFLAGS) $(CFLAGS) -c -o tests/tests_test_bloom-test-bloom.obj `if test -f 'tests/test-bloom.c'; then $(CYGPATH_W) 'tests/test-bloom.c'; else $(CYGPATH_W) '$(srcdir)/tests/test-bloom.c'; fi` + tests/tests_test_bsdiff-test-bsdiff.o: tests/test-bsdiff.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_test_bsdiff_CFLAGS) $(CFLAGS) -MT tests/tests_test_bsdiff-test-bsdiff.o -MD -MP -MF tests/$(DEPDIR)/tests_test_bsdiff-test-bsdiff.Tpo -c -o tests/tests_test_bsdiff-test-bsdiff.o `test -f 'tests/test-bsdiff.c' || echo '$(srcdir)/'`tests/test-bsdiff.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/$(DEPDIR)/tests_test_bsdiff-test-bsdiff.Tpo tests/$(DEPDIR)/tests_test_bsdiff-test-bsdiff.Po @@ -5746,6 +6113,62 @@ tests/tests_test_pull_c-test-pull-c.obj: tests/test-pull-c.c @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) $(tests_test_pull_c_CFLAGS) $(CFLAGS) -c -o tests/tests_test_pull_c-test-pull-c.obj `if test -f 'tests/test-pull-c.c'; then $(CYGPATH_W) 'tests/test-pull-c.c'; else $(CYGPATH_W) '$(srcdir)/tests/test-pull-c.c'; fi` +src/libostree/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.o: src/libostree/ostree-repo-finder-avahi-parser.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_test_repo_finder_avahi_CFLAGS) $(CFLAGS) -MT src/libostree/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.o -MD -MP -MF src/libostree/$(DEPDIR)/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.Tpo -c -o src/libostree/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.o `test -f 'src/libostree/ostree-repo-finder-avahi-parser.c' || echo '$(srcdir)/'`src/libostree/ostree-repo-finder-avahi-parser.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/libostree/$(DEPDIR)/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.Tpo src/libostree/$(DEPDIR)/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/libostree/ostree-repo-finder-avahi-parser.c' object='src/libostree/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.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) $(tests_test_repo_finder_avahi_CFLAGS) $(CFLAGS) -c -o src/libostree/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.o `test -f 'src/libostree/ostree-repo-finder-avahi-parser.c' || echo '$(srcdir)/'`src/libostree/ostree-repo-finder-avahi-parser.c + +src/libostree/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.obj: src/libostree/ostree-repo-finder-avahi-parser.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_test_repo_finder_avahi_CFLAGS) $(CFLAGS) -MT src/libostree/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.obj -MD -MP -MF src/libostree/$(DEPDIR)/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.Tpo -c -o src/libostree/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.obj `if test -f 'src/libostree/ostree-repo-finder-avahi-parser.c'; then $(CYGPATH_W) 'src/libostree/ostree-repo-finder-avahi-parser.c'; else $(CYGPATH_W) '$(srcdir)/src/libostree/ostree-repo-finder-avahi-parser.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/libostree/$(DEPDIR)/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.Tpo src/libostree/$(DEPDIR)/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='src/libostree/ostree-repo-finder-avahi-parser.c' object='src/libostree/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.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) $(tests_test_repo_finder_avahi_CFLAGS) $(CFLAGS) -c -o src/libostree/tests_test_repo_finder_avahi-ostree-repo-finder-avahi-parser.obj `if test -f 'src/libostree/ostree-repo-finder-avahi-parser.c'; then $(CYGPATH_W) 'src/libostree/ostree-repo-finder-avahi-parser.c'; else $(CYGPATH_W) '$(srcdir)/src/libostree/ostree-repo-finder-avahi-parser.c'; fi` + +tests/tests_test_repo_finder_avahi-test-repo-finder-avahi.o: tests/test-repo-finder-avahi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_test_repo_finder_avahi_CFLAGS) $(CFLAGS) -MT tests/tests_test_repo_finder_avahi-test-repo-finder-avahi.o -MD -MP -MF tests/$(DEPDIR)/tests_test_repo_finder_avahi-test-repo-finder-avahi.Tpo -c -o tests/tests_test_repo_finder_avahi-test-repo-finder-avahi.o `test -f 'tests/test-repo-finder-avahi.c' || echo '$(srcdir)/'`tests/test-repo-finder-avahi.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/$(DEPDIR)/tests_test_repo_finder_avahi-test-repo-finder-avahi.Tpo tests/$(DEPDIR)/tests_test_repo_finder_avahi-test-repo-finder-avahi.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/test-repo-finder-avahi.c' object='tests/tests_test_repo_finder_avahi-test-repo-finder-avahi.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) $(tests_test_repo_finder_avahi_CFLAGS) $(CFLAGS) -c -o tests/tests_test_repo_finder_avahi-test-repo-finder-avahi.o `test -f 'tests/test-repo-finder-avahi.c' || echo '$(srcdir)/'`tests/test-repo-finder-avahi.c + +tests/tests_test_repo_finder_avahi-test-repo-finder-avahi.obj: tests/test-repo-finder-avahi.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_test_repo_finder_avahi_CFLAGS) $(CFLAGS) -MT tests/tests_test_repo_finder_avahi-test-repo-finder-avahi.obj -MD -MP -MF tests/$(DEPDIR)/tests_test_repo_finder_avahi-test-repo-finder-avahi.Tpo -c -o tests/tests_test_repo_finder_avahi-test-repo-finder-avahi.obj `if test -f 'tests/test-repo-finder-avahi.c'; then $(CYGPATH_W) 'tests/test-repo-finder-avahi.c'; else $(CYGPATH_W) '$(srcdir)/tests/test-repo-finder-avahi.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/$(DEPDIR)/tests_test_repo_finder_avahi-test-repo-finder-avahi.Tpo tests/$(DEPDIR)/tests_test_repo_finder_avahi-test-repo-finder-avahi.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/test-repo-finder-avahi.c' object='tests/tests_test_repo_finder_avahi-test-repo-finder-avahi.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) $(tests_test_repo_finder_avahi_CFLAGS) $(CFLAGS) -c -o tests/tests_test_repo_finder_avahi-test-repo-finder-avahi.obj `if test -f 'tests/test-repo-finder-avahi.c'; then $(CYGPATH_W) 'tests/test-repo-finder-avahi.c'; else $(CYGPATH_W) '$(srcdir)/tests/test-repo-finder-avahi.c'; fi` + +tests/tests_test_repo_finder_config-test-repo-finder-config.o: tests/test-repo-finder-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_test_repo_finder_config_CFLAGS) $(CFLAGS) -MT tests/tests_test_repo_finder_config-test-repo-finder-config.o -MD -MP -MF tests/$(DEPDIR)/tests_test_repo_finder_config-test-repo-finder-config.Tpo -c -o tests/tests_test_repo_finder_config-test-repo-finder-config.o `test -f 'tests/test-repo-finder-config.c' || echo '$(srcdir)/'`tests/test-repo-finder-config.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/$(DEPDIR)/tests_test_repo_finder_config-test-repo-finder-config.Tpo tests/$(DEPDIR)/tests_test_repo_finder_config-test-repo-finder-config.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/test-repo-finder-config.c' object='tests/tests_test_repo_finder_config-test-repo-finder-config.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) $(tests_test_repo_finder_config_CFLAGS) $(CFLAGS) -c -o tests/tests_test_repo_finder_config-test-repo-finder-config.o `test -f 'tests/test-repo-finder-config.c' || echo '$(srcdir)/'`tests/test-repo-finder-config.c + +tests/tests_test_repo_finder_config-test-repo-finder-config.obj: tests/test-repo-finder-config.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_test_repo_finder_config_CFLAGS) $(CFLAGS) -MT tests/tests_test_repo_finder_config-test-repo-finder-config.obj -MD -MP -MF tests/$(DEPDIR)/tests_test_repo_finder_config-test-repo-finder-config.Tpo -c -o tests/tests_test_repo_finder_config-test-repo-finder-config.obj `if test -f 'tests/test-repo-finder-config.c'; then $(CYGPATH_W) 'tests/test-repo-finder-config.c'; else $(CYGPATH_W) '$(srcdir)/tests/test-repo-finder-config.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/$(DEPDIR)/tests_test_repo_finder_config-test-repo-finder-config.Tpo tests/$(DEPDIR)/tests_test_repo_finder_config-test-repo-finder-config.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/test-repo-finder-config.c' object='tests/tests_test_repo_finder_config-test-repo-finder-config.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) $(tests_test_repo_finder_config_CFLAGS) $(CFLAGS) -c -o tests/tests_test_repo_finder_config-test-repo-finder-config.obj `if test -f 'tests/test-repo-finder-config.c'; then $(CYGPATH_W) 'tests/test-repo-finder-config.c'; else $(CYGPATH_W) '$(srcdir)/tests/test-repo-finder-config.c'; fi` + +tests/tests_test_repo_finder_mount-test-repo-finder-mount.o: tests/test-repo-finder-mount.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_test_repo_finder_mount_CFLAGS) $(CFLAGS) -MT tests/tests_test_repo_finder_mount-test-repo-finder-mount.o -MD -MP -MF tests/$(DEPDIR)/tests_test_repo_finder_mount-test-repo-finder-mount.Tpo -c -o tests/tests_test_repo_finder_mount-test-repo-finder-mount.o `test -f 'tests/test-repo-finder-mount.c' || echo '$(srcdir)/'`tests/test-repo-finder-mount.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/$(DEPDIR)/tests_test_repo_finder_mount-test-repo-finder-mount.Tpo tests/$(DEPDIR)/tests_test_repo_finder_mount-test-repo-finder-mount.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/test-repo-finder-mount.c' object='tests/tests_test_repo_finder_mount-test-repo-finder-mount.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) $(tests_test_repo_finder_mount_CFLAGS) $(CFLAGS) -c -o tests/tests_test_repo_finder_mount-test-repo-finder-mount.o `test -f 'tests/test-repo-finder-mount.c' || echo '$(srcdir)/'`tests/test-repo-finder-mount.c + +tests/tests_test_repo_finder_mount-test-repo-finder-mount.obj: tests/test-repo-finder-mount.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_test_repo_finder_mount_CFLAGS) $(CFLAGS) -MT tests/tests_test_repo_finder_mount-test-repo-finder-mount.obj -MD -MP -MF tests/$(DEPDIR)/tests_test_repo_finder_mount-test-repo-finder-mount.Tpo -c -o tests/tests_test_repo_finder_mount-test-repo-finder-mount.obj `if test -f 'tests/test-repo-finder-mount.c'; then $(CYGPATH_W) 'tests/test-repo-finder-mount.c'; else $(CYGPATH_W) '$(srcdir)/tests/test-repo-finder-mount.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) tests/$(DEPDIR)/tests_test_repo_finder_mount-test-repo-finder-mount.Tpo tests/$(DEPDIR)/tests_test_repo_finder_mount-test-repo-finder-mount.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tests/test-repo-finder-mount.c' object='tests/tests_test_repo_finder_mount-test-repo-finder-mount.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) $(tests_test_repo_finder_mount_CFLAGS) $(CFLAGS) -c -o tests/tests_test_repo_finder_mount-test-repo-finder-mount.obj `if test -f 'tests/test-repo-finder-mount.c'; then $(CYGPATH_W) 'tests/test-repo-finder-mount.c'; else $(CYGPATH_W) '$(srcdir)/tests/test-repo-finder-mount.c'; fi` + src/libostree/tests_test_rollsum-ostree-rollsum.o: src/libostree/ostree-rollsum.c @am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(tests_test_rollsum_CFLAGS) $(CFLAGS) -MT src/libostree/tests_test_rollsum-ostree-rollsum.o -MD -MP -MF src/libostree/$(DEPDIR)/tests_test_rollsum-ostree-rollsum.Tpo -c -o src/libostree/tests_test_rollsum-ostree-rollsum.o `test -f 'src/libostree/ostree-rollsum.c' || echo '$(srcdir)/'`src/libostree/ostree-rollsum.c @am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) src/libostree/$(DEPDIR)/tests_test_rollsum-ostree-rollsum.Tpo src/libostree/$(DEPDIR)/tests_test_rollsum-ostree-rollsum.Po @@ -6484,6 +6907,34 @@ recheck: all $(check_LTLIBRARIES) $(check_PROGRAMS) $(check_SCRIPTS) $(check_DAT am__force_recheck=am--force-recheck \ TEST_LOGS="$$log_list"; \ exit $$? +tests/test-bloom.log: tests/test-bloom$(EXEEXT) + @p='tests/test-bloom$(EXEEXT)'; \ + b='tests/test-bloom'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +tests/test-repo-finder-config.log: tests/test-repo-finder-config$(EXEEXT) + @p='tests/test-repo-finder-config$(EXEEXT)'; \ + b='tests/test-repo-finder-config'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +tests/test-repo-finder-mount.log: tests/test-repo-finder-mount$(EXEEXT) + @p='tests/test-repo-finder-mount$(EXEEXT)'; \ + b='tests/test-repo-finder-mount'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +tests/test-repo-finder-avahi.log: tests/test-repo-finder-avahi$(EXEEXT) + @p='tests/test-repo-finder-avahi$(EXEEXT)'; \ + b='tests/test-repo-finder-avahi'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) tests/test-varint.log: tests/test-varint$(EXEEXT) @p='tests/test-varint$(EXEEXT)'; \ b='tests/test-varint'; \ @@ -6687,9 +7138,9 @@ tests/test-parent.sh.log: tests/test-parent.sh --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) -tests/test-pull-archive-z.sh.log: tests/test-pull-archive-z.sh - @p='tests/test-pull-archive-z.sh'; \ - b='tests/test-pull-archive-z.sh'; \ +tests/test-pull-archive.sh.log: tests/test-pull-archive.sh + @p='tests/test-pull-archive.sh'; \ + b='tests/test-pull-archive.sh'; \ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ @@ -6764,6 +7215,13 @@ tests/test-pull-override-url.sh.log: tests/test-pull-override-url.sh --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) +tests/test-pull-localcache.sh.log: tests/test-pull-localcache.sh + @p='tests/test-pull-localcache.sh'; \ + b='tests/test-pull-localcache.sh'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) tests/test-local-pull.sh.log: tests/test-local-pull.sh @p='tests/test-local-pull.sh'; \ b='tests/test-local-pull.sh'; \ @@ -6974,6 +7432,13 @@ tests/test-pull-mirrorlist.sh.log: tests/test-pull-mirrorlist.sh --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) +tests/test-summary-update.sh.log: tests/test-summary-update.sh + @p='tests/test-summary-update.sh'; \ + b='tests/test-summary-update.sh'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) tests/test-summary-view.sh.log: tests/test-summary-view.sh @p='tests/test-summary-view.sh'; \ b='tests/test-summary-view.sh'; \ @@ -6981,6 +7446,55 @@ tests/test-summary-view.sh.log: tests/test-summary-view.sh --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) +tests/test-find-remotes.sh.log: tests/test-find-remotes.sh + @p='tests/test-find-remotes.sh'; \ + b='tests/test-find-remotes.sh'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +tests/test-fsck-collections.sh.log: tests/test-fsck-collections.sh + @p='tests/test-fsck-collections.sh'; \ + b='tests/test-fsck-collections.sh'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +tests/test-init-collections.sh.log: tests/test-init-collections.sh + @p='tests/test-init-collections.sh'; \ + b='tests/test-init-collections.sh'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +tests/test-prune-collections.sh.log: tests/test-prune-collections.sh + @p='tests/test-prune-collections.sh'; \ + b='tests/test-prune-collections.sh'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +tests/test-refs-collections.sh.log: tests/test-refs-collections.sh + @p='tests/test-refs-collections.sh'; \ + b='tests/test-refs-collections.sh'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +tests/test-remote-add-collections.sh.log: tests/test-remote-add-collections.sh + @p='tests/test-remote-add-collections.sh'; \ + b='tests/test-remote-add-collections.sh'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) +tests/test-summary-collections.sh.log: tests/test-summary-collections.sh + @p='tests/test-summary-collections.sh'; \ + b='tests/test-summary-collections.sh'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) tests/test-rofiles-fuse.sh.log: tests/test-rofiles-fuse.sh @p='tests/test-rofiles-fuse.sh'; \ b='tests/test-rofiles-fuse.sh'; \ @@ -7044,6 +7558,13 @@ test-libglnx-errors.log: test-libglnx-errors$(EXEEXT) --log-file $$b.log --trs-file $$b.trs \ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ "$$tst" $(AM_TESTS_FD_REDIRECT) +test-libglnx-macros.log: test-libglnx-macros$(EXEEXT) + @p='test-libglnx-macros$(EXEEXT)'; \ + b='test-libglnx-macros'; \ + $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \ + --log-file $$b.log --trs-file $$b.trs \ + $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \ + "$$tst" $(AM_TESTS_FD_REDIRECT) .test.log: @p='$<'; \ $(am__set_b); \ diff --git a/apidoc/Makefile.in b/apidoc/Makefile.in index 22ad83e0..26ac6b1e 100644 --- a/apidoc/Makefile.in +++ b/apidoc/Makefile.in @@ -131,7 +131,12 @@ POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ @ENABLE_EXPERIMENTAL_API_TRUE@am__append_1 = \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-ref.h \ @ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-remote.h \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-repo-finder.h \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-repo-finder-avahi.h \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-repo-finder-config.h \ +@ENABLE_EXPERIMENTAL_API_TRUE@ src/libostree/ostree-repo-finder-mount.h \ @ENABLE_EXPERIMENTAL_API_TRUE@ $(NULL) subdir = apidoc @@ -257,6 +262,8 @@ OBJEXT = @OBJEXT@ OSTREE_FEATURES = @OSTREE_FEATURES@ OTOOL = @OTOOL@ OTOOL64 = @OTOOL64@ +OT_DEP_AVAHI_CFLAGS = @OT_DEP_AVAHI_CFLAGS@ +OT_DEP_AVAHI_LIBS = @OT_DEP_AVAHI_LIBS@ OT_DEP_CURL_CFLAGS = @OT_DEP_CURL_CFLAGS@ OT_DEP_CURL_LIBS = @OT_DEP_CURL_LIBS@ OT_DEP_E2P_CFLAGS = @OT_DEP_E2P_CFLAGS@ diff --git a/apidoc/html/index.html b/apidoc/html/index.html index 9f374a47..a37ff507 100644 --- a/apidoc/html/index.html +++ b/apidoc/html/index.html @@ -14,7 +14,7 @@
-

for OSTree 2017.7

+

for OSTree 2017.8


diff --git a/apidoc/html/ostree-Content-addressed-object-store.html b/apidoc/html/ostree-Content-addressed-object-store.html index ede56744..e5f98da0 100644 --- a/apidoc/html/ostree-Content-addressed-object-store.html +++ b/apidoc/html/ostree-Content-addressed-object-store.html @@ -1130,6 +1130,22 @@ filesystem and write content, possibly multiple times.

Once the OstreeMutableTree is complete, write all of its metadata with ostree_repo_write_mtree(), and finally create a commit with ostree_repo_write_commit().

+
+

Collection IDs

+

A collection ID is a globally unique identifier which, if set, is used to +identify refs from a repository which are mirrored elsewhere, such as in +mirror repositories or peer to peer networks.

+

This is separate from the collection-id configuration key for a remote, which +is used to store the collection ID of the repository that remote points to.

+

The collection ID should only be set on an OstreeRepo if it is the canonical +collection for some refs.

+

A collection ID must be a reverse DNS name, where the domain name is under the +control of the curator of the collection, so they can demonstrate ownership +of the collection. The later elements in the reverse DNS name can be used to +disambiguate between multiple collections from the same curator. For example, +org.exampleos.Main and org.exampleos.Apps. For the complete format of +collection IDs, see ostree_validate_collection_id().

+

Functions

@@ -6325,6 +6341,9 @@ ostree_repo_pull_with_options (

Parameters

@@ -6537,7 +6557,7 @@ ostree_repo_add_gpg_signature_summary (const gchar *homedir, GCancellable *cancellable, GError **error); -

Add a GPG signature to a static delta.

+

Add a GPG signature to a summary file.

Parameters

@@ -6911,6 +6931,12 @@ regular, setting the ostree.summary.expires key in will aid clients in working out when to check for updates.

It is regenerated automatically after a commit if core/commit-update-summary is set.

+

If the core/collection-id key is set in the configuration, it will be +included as OSTREE_SUMMARY_COLLECTION_ID in the summary file. Refs from the +refs/mirrors directory will be included in the generated summary file, +listed under the OSTREE_SUMMARY_COLLECTION_MAP key. Collection IDs and refs +in OSTREE_SUMMARY_COLLECTION_MAP are guaranteed to be in lexicographic +order.

Parameters

diff --git a/apidoc/html/ostree-Core-repository-independent-functions.html b/apidoc/html/ostree-Core-repository-independent-functions.html index 6fbf6eeb..184b7509 100644 --- a/apidoc/html/ostree-Core-repository-independent-functions.html +++ b/apidoc/html/ostree-Core-repository-independent-functions.html @@ -175,6 +175,14 @@ gboolean + + + + @@ -979,6 +987,41 @@ is a valid ref string


+

ostree_validate_remote_name ()

+
gboolean
+ostree_validate_remote_name (const char *remote_name,
+                             GError **error);
+
+

Parameters

+
+ostree_validate_remote_name () +
+gboolean + ostree_parse_refspec ()
+++++ + + + + + + + + + + + + +

remote_name

A remote name

 

error

Error

 
+
+
+

Returns

+

TRUE if remote_name +is a valid remote name

+
+

Since: 2017.8

+
+
+

ostree_parse_refspec ()

gboolean
 ostree_parse_refspec (const char *refspec,
diff --git a/apidoc/html/ostree.devhelp2 b/apidoc/html/ostree.devhelp2
index 77df577a..dc4f8980 100644
--- a/apidoc/html/ostree.devhelp2
+++ b/apidoc/html/ostree.devhelp2
@@ -37,6 +37,7 @@
     
     
     
+    
     
     
     
diff --git a/apidoc/html/reference.html b/apidoc/html/reference.html
index 4e82bd93..a4cc4436 100644
--- a/apidoc/html/reference.html
+++ b/apidoc/html/reference.html
@@ -1481,6 +1481,10 @@ OSTREE_RELEASE_VERSION, macro in ostree-version
 
 
+ostree_validate_remote_name, function in Core repository-independent functions +
+
+
ostree_validate_rev, function in Core repository-independent functions
diff --git a/apidoc/ostree-experimental-sections.txt b/apidoc/ostree-experimental-sections.txt index 998a4f9c..4c71fad2 100644 --- a/apidoc/ostree-experimental-sections.txt +++ b/apidoc/ostree-experimental-sections.txt @@ -1,3 +1,18 @@ +
+ostree-ref +OstreeCollectionRef +ostree_collection_ref_new +ostree_collection_ref_dup +ostree_collection_ref_free +ostree_collection_ref_hash +ostree_collection_ref_equal +OstreeCollectionRefv +ostree_collection_ref_dupv +ostree_collection_ref_freev + +ostree_collection_ref_get_type +
+
ostree-remote OstreeRemote @@ -5,3 +20,67 @@ ostree_remote_ref ostree_remote_unref ostree_remote_get_name
+ +
+ostree-repo-experimental +ostree_repo_find_remotes_async +ostree_repo_find_remotes_finish +ostree_repo_pull_from_remotes_async +ostree_repo_pull_from_remotes_finish +ostree_repo_resolve_keyring_for_collection +
+ +
+ostree-repo-finder +OstreeRepoFinder +ostree_repo_finder_resolve_async +ostree_repo_finder_resolve_finish +ostree_repo_finder_resolve_all_async +ostree_repo_finder_resolve_all_finish +OstreeRepoFinderResult +ostree_repo_finder_result_new +ostree_repo_finder_result_dup +ostree_repo_finder_result_free +ostree_repo_finder_result_compare +OstreeRepoFinderResultv +ostree_repo_finder_result_freev + +ostree_repo_finder_get_type +ostree_repo_finder_result_get_type +
+ +
+ostree-repo-finder-avahi +OstreeRepoFinderAvahi +ostree_repo_finder_avahi_new +ostree_repo_finder_avahi_start +ostree_repo_finder_avahi_stop + +ostree_repo_finder_avahi_get_type +
+ +
+ostree-repo-finder-config +OstreeRepoFinderConfig +ostree_repo_finder_config_new + +ostree_repo_finder_config_get_type +
+ +
+ostree-repo-finder-mount +OstreeRepoFinderMount +ostree_repo_finder_mount_new + +ostree_repo_finder_mount_get_type +
+ +
+ostree-misc-experimental +ostree_repo_get_collection_id +ostree_repo_set_collection_id +ostree_validate_collection_id +ostree_repo_list_collection_refs +ostree_repo_set_collection_ref_immediate +ostree_repo_transaction_set_collection_ref +
diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index 116c50e8..47f351d2 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -118,6 +118,7 @@ ostree_checksum_b64_inplace_from_bytes ostree_checksum_b64_inplace_to_bytes ostree_cmp_checksum_bytes ostree_validate_rev +ostree_validate_remote_name ostree_parse_refspec ostree_object_type_to_string ostree_object_type_from_string diff --git a/apidoc/ostree.types b/apidoc/ostree.types index b229e7da..089cae3c 100644 --- a/apidoc/ostree.types +++ b/apidoc/ostree.types @@ -1,7 +1,9 @@ ostree_async_progress_get_type +ostree_bloom_get_type ostree_bootconfig_parser_get_type ostree_chain_input_stream_get_type ostree_checksum_input_stream_get_type +ostree_collection_ref_get_type ostree_deployment_get_type ostree_diff_item_get_type ostree_gpg_verify_result_get_type @@ -10,6 +12,11 @@ ostree_remote_get_type ostree_repo_commit_modifier_get_type ostree_repo_devino_cache_get_type ostree_repo_file_get_type +ostree_repo_finder_avahi_get_type +ostree_repo_finder_config_get_type +ostree_repo_finder_get_type +ostree_repo_finder_mount_get_type +ostree_repo_finder_result_get_type ostree_repo_get_type ostree_repo_transaction_stats_get_type ostree_sepolicy_get_type diff --git a/apidoc/version.xml b/apidoc/version.xml index 5c006737..295cdc53 100644 --- a/apidoc/version.xml +++ b/apidoc/version.xml @@ -1 +1 @@ -2017.7 \ No newline at end of file +2017.8 \ No newline at end of file diff --git a/config.h.in b/config.h.in index 3cc2f9e0..2e5f07d6 100644 --- a/config.h.in +++ b/config.h.in @@ -3,6 +3,9 @@ /* Define if we are enabling ostree trivial-httpd entrypoint */ #undef BUILDOPT_ENABLE_TRIVIAL_HTTPD_CMDLINE +/* Define if doing a development build */ +#undef BUILDOPT_IS_DEVEL_BUILD + /* Define if systemd and libmount */ #undef BUILDOPT_LIBSYSTEMD_AND_LIBMOUNT @@ -18,6 +21,9 @@ /* Define to 1 if you have the `archive_read_support_filter_all' function. */ #undef HAVE_ARCHIVE_READ_SUPPORT_FILTER_ALL +/* Define if we have avahi-client.pc and avahi-glib.pc */ +#undef HAVE_AVAHI + /* Define to 1 if you have the declaration of `', and to 0 if you don't. */ #undef HAVE_DECL_ diff --git a/configure b/configure index 617905ca..8a1ae39b 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 2017.7. +# Generated by GNU Autoconf 2.69 for libostree 2017.8. # # Report bugs to . # @@ -590,8 +590,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='libostree' PACKAGE_TARNAME='libostree' -PACKAGE_VERSION='2017.7' -PACKAGE_STRING='libostree 2017.7' +PACKAGE_VERSION='2017.8' +PACKAGE_STRING='libostree 2017.8' PACKAGE_BUGREPORT='walters@verbum.org' PACKAGE_URL='' @@ -672,6 +672,10 @@ USE_LIBMOUNT_FALSE USE_LIBMOUNT_TRUE OT_DEP_LIBMOUNT_LIBS OT_DEP_LIBMOUNT_CFLAGS +USE_AVAHI_FALSE +USE_AVAHI_TRUE +OT_DEP_AVAHI_LIBS +OT_DEP_AVAHI_CFLAGS USE_OPENSSL_FALSE USE_OPENSSL_TRUE OT_DEP_OPENSSL_LIBS @@ -932,6 +936,7 @@ with_libarchive with_selinux with_smack with_openssl +with_avahi with_libmount enable_rofiles_fuse with_dracut @@ -980,6 +985,8 @@ OT_DEP_SELINUX_CFLAGS OT_DEP_SELINUX_LIBS OT_DEP_OPENSSL_CFLAGS OT_DEP_OPENSSL_LIBS +OT_DEP_AVAHI_CFLAGS +OT_DEP_AVAHI_LIBS OT_DEP_LIBMOUNT_CFLAGS OT_DEP_LIBMOUNT_LIBS BUILDOPT_FUSE_CFLAGS @@ -1526,7 +1533,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 2017.7 to adapt to many kinds of systems. +\`configure' configures libostree 2017.8 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1596,7 +1603,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of libostree 2017.7:";; + short | recursive ) echo "Configuration of libostree 2017.8:";; esac cat <<\_ACEOF @@ -1667,6 +1674,7 @@ Optional Packages: --without-selinux Do not use SELinux --with-smack Enable smack --with-openssl Enable use of OpenSSL libcrypto (checksums) + --without-avahi Do not use Avahi --without-libmount Do not use libmount --with-dracut Install dracut module (default: no) --with-mkinitcpio Install mkinitcpio module (default: no) @@ -1748,6 +1756,10 @@ Some influential environment variables: C compiler flags for OT_DEP_OPENSSL, overriding pkg-config OT_DEP_OPENSSL_LIBS linker flags for OT_DEP_OPENSSL, overriding pkg-config + OT_DEP_AVAHI_CFLAGS + C compiler flags for OT_DEP_AVAHI, overriding pkg-config + OT_DEP_AVAHI_LIBS + linker flags for OT_DEP_AVAHI, overriding pkg-config OT_DEP_LIBMOUNT_CFLAGS C compiler flags for OT_DEP_LIBMOUNT, overriding pkg-config OT_DEP_LIBMOUNT_LIBS @@ -1827,7 +1839,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -libostree configure 2017.7 +libostree configure 2017.8 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -2242,7 +2254,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 2017.7, which was +It was created by libostree $as_me 2017.8, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -3110,7 +3122,7 @@ fi # Define the identity of the package. PACKAGE='libostree' - VERSION='2017.7' + VERSION='2017.8' # Some tools Automake needs. @@ -5844,9 +5856,9 @@ test -n "$YACC" || YACC="yacc" YEAR_VERSION=2017 -RELEASE_VERSION=7 +RELEASE_VERSION=8 -PACKAGE_VERSION=2017.7 +PACKAGE_VERSION=2017.8 @@ -16431,6 +16443,155 @@ else fi +AVAHI_DEPENDENCY="avahi-client >= 0.6.31 avahi-glib >= 0.6.31" + + +# Check whether --with-avahi was given. +if test "${with_avahi+set}" = set; then : + withval=$with_avahi; : +else + with_avahi=maybe +fi + + +if test x$with_avahi != xno ; then : + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $AVAHI_DEPENDENCY" >&5 +$as_echo_n "checking for $AVAHI_DEPENDENCY... " >&6; } + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$AVAHI_DEPENDENCY\""; } >&5 + ($PKG_CONFIG --exists --print-errors "$AVAHI_DEPENDENCY") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + have_avahi=yes +else + have_avahi=no +fi + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $have_avahi" >&5 +$as_echo "$have_avahi" >&6; } + if test x$have_avahi = xno && test x$with_avahi != xmaybe ; then : + + as_fn_error $? "Avahi is enabled but could not be found" "$LINENO" 5 + +fi + if test x$have_avahi = xyes; then : + + +$as_echo "#define HAVE_AVAHI 1" >>confdefs.h + + +pkg_failed=no +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for OT_DEP_AVAHI" >&5 +$as_echo_n "checking for OT_DEP_AVAHI... " >&6; } + +if test -n "$OT_DEP_AVAHI_CFLAGS"; then + pkg_cv_OT_DEP_AVAHI_CFLAGS="$OT_DEP_AVAHI_CFLAGS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$AVAHI_DEPENDENCY\""; } >&5 + ($PKG_CONFIG --exists --print-errors "$AVAHI_DEPENDENCY") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_OT_DEP_AVAHI_CFLAGS=`$PKG_CONFIG --cflags "$AVAHI_DEPENDENCY" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi +if test -n "$OT_DEP_AVAHI_LIBS"; then + pkg_cv_OT_DEP_AVAHI_LIBS="$OT_DEP_AVAHI_LIBS" + elif test -n "$PKG_CONFIG"; then + if test -n "$PKG_CONFIG" && \ + { { $as_echo "$as_me:${as_lineno-$LINENO}: \$PKG_CONFIG --exists --print-errors \"\$AVAHI_DEPENDENCY\""; } >&5 + ($PKG_CONFIG --exists --print-errors "$AVAHI_DEPENDENCY") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then + pkg_cv_OT_DEP_AVAHI_LIBS=`$PKG_CONFIG --libs "$AVAHI_DEPENDENCY" 2>/dev/null` + test "x$?" != "x0" && pkg_failed=yes +else + pkg_failed=yes +fi + else + pkg_failed=untried +fi + + + +if test $pkg_failed = yes; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } + +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi + if test $_pkg_short_errors_supported = yes; then + OT_DEP_AVAHI_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$AVAHI_DEPENDENCY" 2>&1` + else + OT_DEP_AVAHI_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$AVAHI_DEPENDENCY" 2>&1` + fi + # Put the nasty error message in config.log where it belongs + echo "$OT_DEP_AVAHI_PKG_ERRORS" >&5 + + as_fn_error $? "Package requirements ($AVAHI_DEPENDENCY) were not met: + +$OT_DEP_AVAHI_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 OT_DEP_AVAHI_CFLAGS +and OT_DEP_AVAHI_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; } + { { $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 OT_DEP_AVAHI_CFLAGS +and OT_DEP_AVAHI_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 + OT_DEP_AVAHI_CFLAGS=$pkg_cv_OT_DEP_AVAHI_CFLAGS + OT_DEP_AVAHI_LIBS=$pkg_cv_OT_DEP_AVAHI_LIBS + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } + +fi + with_avahi=yes + +else + + with_avahi=no + +fi + +else + with_avahi=no +fi +if test x$with_avahi != xno; then OSTREE_FEATURES="$OSTREE_FEATURES avahi"; fi + if test $with_avahi != no; then + USE_AVAHI_TRUE= + USE_AVAHI_FALSE='#' +else + USE_AVAHI_TRUE='#' + USE_AVAHI_FALSE= +fi + + LIBMOUNT_DEPENDENCY="mount >= 2.23.0" @@ -17094,10 +17255,14 @@ else fi if test -z "$BUILDOPT_IS_DEVEL_BUILD_TRUE"; then : - release_build_type=devel + +$as_echo "#define BUILDOPT_IS_DEVEL_BUILD 1" >>confdefs.h + + release_build_type=devel else release_build_type=release fi +OSTREE_FEATURES="$OSTREE_FEATURES $release_build_type" ac_config_files="$ac_config_files Makefile apidoc/Makefile src/libostree/ostree-1.pc src/libostree/ostree-version.h" @@ -17338,6 +17503,10 @@ if test -z "${USE_OPENSSL_TRUE}" && test -z "${USE_OPENSSL_FALSE}"; then as_fn_error $? "conditional \"USE_OPENSSL\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi +if test -z "${USE_AVAHI_TRUE}" && test -z "${USE_AVAHI_FALSE}"; then + as_fn_error $? "conditional \"USE_AVAHI\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi if test -z "${USE_LIBMOUNT_TRUE}" && test -z "${USE_LIBMOUNT_FALSE}"; then as_fn_error $? "conditional \"USE_LIBMOUNT\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 @@ -17787,7 +17956,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 2017.7, which was +This file was extended by libostree $as_me 2017.8, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -17853,7 +18022,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 2017.7 +libostree config.status 2017.8 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index 7946f4fd..30ae793b 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], [2017]) -m4_define([release_version], [7]) +m4_define([release_version], [8]) m4_define([package_version], [year_version.release_version]) AC_INIT([libostree], [package_version], [walters@verbum.org]) is_release_build=yes @@ -320,6 +320,31 @@ if test x$with_openssl != xno; then OSTREE_FEATURES="$OSTREE_FEATURES openssl"; AM_CONDITIONAL(USE_OPENSSL, test $with_openssl != no) dnl end openssl +dnl Avahi dependency for finding repos +AVAHI_DEPENDENCY="avahi-client >= 0.6.31 avahi-glib >= 0.6.31" + +AC_ARG_WITH(avahi, + AS_HELP_STRING([--without-avahi], [Do not use Avahi]), + :, with_avahi=maybe) + +AS_IF([ test x$with_avahi != xno ], [ + AC_MSG_CHECKING([for $AVAHI_DEPENDENCY]) + PKG_CHECK_EXISTS($AVAHI_DEPENDENCY, have_avahi=yes, have_avahi=no) + AC_MSG_RESULT([$have_avahi]) + AS_IF([ test x$have_avahi = xno && test x$with_avahi != xmaybe ], [ + AC_MSG_ERROR([Avahi is enabled but could not be found]) + ]) + AS_IF([ test x$have_avahi = xyes], [ + AC_DEFINE([HAVE_AVAHI], 1, [Define if we have avahi-client.pc and avahi-glib.pc]) + PKG_CHECK_MODULES(OT_DEP_AVAHI, $AVAHI_DEPENDENCY) + with_avahi=yes + ], [ + with_avahi=no + ]) +], [ with_avahi=no ]) +if test x$with_avahi != xno; then OSTREE_FEATURES="$OSTREE_FEATURES avahi"; fi +AM_CONDITIONAL(USE_AVAHI, test $with_avahi != no) + dnl This is what is in RHEL7.2 right now, picking it arbitrarily LIBMOUNT_DEPENDENCY="mount >= 2.23.0" @@ -460,7 +485,11 @@ AS_IF([test x$enable_experimental_api = xyes], ) AM_CONDITIONAL([ENABLE_EXPERIMENTAL_API],[test x$enable_experimental_api = xyes]) AM_CONDITIONAL([BUILDOPT_IS_DEVEL_BUILD],[test x$is_release_build != xyes]) -AM_COND_IF([BUILDOPT_IS_DEVEL_BUILD], release_build_type=devel, release_build_type=release) +AM_COND_IF([BUILDOPT_IS_DEVEL_BUILD], + AC_DEFINE([BUILDOPT_IS_DEVEL_BUILD], [1], [Define if doing a development build]) + release_build_type=devel, + release_build_type=release) +OSTREE_FEATURES="$OSTREE_FEATURES $release_build_type" AC_CONFIG_FILES([ Makefile diff --git a/libglnx/Makefile-libglnx.am b/libglnx/Makefile-libglnx.am index 805a9d04..6df05ea1 100644 --- a/libglnx/Makefile-libglnx.am +++ b/libglnx/Makefile-libglnx.am @@ -22,7 +22,7 @@ EXTRA_DIST += \ $(NULL) libglnx_la_SOURCES = \ - $(libglnx_srcpath)/glnx-alloca.h \ + $(libglnx_srcpath)/glnx-macros.h \ $(libglnx_srcpath)/glnx-backport-autocleanups.h \ $(libglnx_srcpath)/glnx-backport-autoptr.h \ $(libglnx_srcpath)/glnx-backports.h \ @@ -52,7 +52,7 @@ libglnx_la_CFLAGS = $(AM_CFLAGS) $(libglnx_cflags) libglnx_la_LDFLAGS = -avoid-version -Bsymbolic-functions -export-symbols-regex "^glnx_" -no-undefined -export-dynamic libglnx_la_LIBADD = $(libglnx_libs) -libglnx_tests = test-libglnx-xattrs test-libglnx-fdio test-libglnx-errors +libglnx_tests = test-libglnx-xattrs test-libglnx-fdio test-libglnx-errors test-libglnx-macros TESTS += $(libglnx_tests) check_PROGRAMS += $(libglnx_tests) @@ -67,3 +67,7 @@ test_libglnx_fdio_LDADD = $(libglnx_libs) libglnx.la test_libglnx_errors_SOURCES = $(libglnx_srcpath)/tests/test-libglnx-errors.c test_libglnx_errors_CFLAGS = $(AM_CFLAGS) $(libglnx_cflags) test_libglnx_errors_LDADD = $(libglnx_libs) libglnx.la + +test_libglnx_macros_SOURCES = $(libglnx_srcpath)/tests/test-libglnx-macros.c +test_libglnx_macros_CFLAGS = $(AM_CFLAGS) $(libglnx_cflags) +test_libglnx_macros_LDADD = $(libglnx_libs) libglnx.la diff --git a/libglnx/Makefile-libglnx.am.inc b/libglnx/Makefile-libglnx.am.inc index daaae9ec..82883c76 100644 --- a/libglnx/Makefile-libglnx.am.inc +++ b/libglnx/Makefile-libglnx.am.inc @@ -22,7 +22,7 @@ EXTRA_DIST += \ $(NULL) libglnx_la_SOURCES = \ - libglnx/glnx-alloca.h \ + libglnx/glnx-macros.h \ libglnx/glnx-backport-autocleanups.h \ libglnx/glnx-backport-autoptr.h \ libglnx/glnx-backports.h \ @@ -52,7 +52,7 @@ libglnx_la_CFLAGS = $(AM_CFLAGS) $(libglnx_cflags) libglnx_la_LDFLAGS = -avoid-version -Bsymbolic-functions -export-symbols-regex "^glnx_" -no-undefined -export-dynamic libglnx_la_LIBADD = $(libglnx_libs) -libglnx_tests = test-libglnx-xattrs test-libglnx-fdio test-libglnx-errors +libglnx_tests = test-libglnx-xattrs test-libglnx-fdio test-libglnx-errors test-libglnx-macros TESTS += $(libglnx_tests) check_PROGRAMS += $(libglnx_tests) @@ -67,3 +67,7 @@ test_libglnx_fdio_LDADD = $(libglnx_libs) libglnx.la test_libglnx_errors_SOURCES = libglnx/tests/test-libglnx-errors.c test_libglnx_errors_CFLAGS = $(AM_CFLAGS) $(libglnx_cflags) test_libglnx_errors_LDADD = $(libglnx_libs) libglnx.la + +test_libglnx_macros_SOURCES = libglnx/tests/test-libglnx-macros.c +test_libglnx_macros_CFLAGS = $(AM_CFLAGS) $(libglnx_cflags) +test_libglnx_macros_LDADD = $(libglnx_libs) libglnx.la diff --git a/libglnx/glnx-alloca.h b/libglnx/glnx-alloca.h deleted file mode 100644 index b0bf6a65..00000000 --- a/libglnx/glnx-alloca.h +++ /dev/null @@ -1,47 +0,0 @@ -/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- - * - * Copyright (C) 2014,2015 Colin Walters . - * - * 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. - */ - -#pragma once - -#include -#include - -G_BEGIN_DECLS - -/* Taken from https://github.com/systemd/systemd/src/basic/string-util.h - * at revision v228-666-gcf6c8c4 - */ -#define glnx_strjoina(a, ...) \ - ({ \ - const char *_appendees_[] = { a, __VA_ARGS__ }; \ - char *_d_, *_p_; \ - int _len_ = 0; \ - unsigned _i_; \ - for (_i_ = 0; _i_ < G_N_ELEMENTS(_appendees_) && _appendees_[_i_]; _i_++) \ - _len_ += strlen(_appendees_[_i_]); \ - _p_ = _d_ = alloca(_len_ + 1); \ - for (_i_ = 0; _i_ < G_N_ELEMENTS(_appendees_) && _appendees_[_i_]; _i_++) \ - _p_ = stpcpy(_p_, _appendees_[_i_]); \ - *_p_ = 0; \ - _d_; \ - }) - - -G_END_DECLS diff --git a/libglnx/glnx-dirfd.c b/libglnx/glnx-dirfd.c index 12b983c1..bb73b015 100644 --- a/libglnx/glnx-dirfd.c +++ b/libglnx/glnx-dirfd.c @@ -99,24 +99,19 @@ glnx_dirfd_iterator_init_at (int dfd, GLnxDirFdIterator *out_dfd_iter, GError **error) { - gboolean ret = FALSE; glnx_fd_close int fd = -1; - if (!glnx_opendirat (dfd, path, follow, &fd, error)) - goto out; + return FALSE; - if (!glnx_dirfd_iterator_init_take_fd (fd, out_dfd_iter, error)) - goto out; - fd = -1; /* Transfer ownership */ + if (!glnx_dirfd_iterator_init_take_fd (&fd, out_dfd_iter, error)) + return FALSE; - ret = TRUE; - out: - return ret; + return TRUE; } /** * glnx_dirfd_iterator_init_take_fd: - * @dfd: File descriptor - ownership is taken + * @dfd: File descriptor - ownership is taken, and the value is set to -1 * @dfd_iter: A directory iterator * @error: Error * @@ -124,28 +119,20 @@ glnx_dirfd_iterator_init_at (int dfd, * iteration. */ gboolean -glnx_dirfd_iterator_init_take_fd (int dfd, +glnx_dirfd_iterator_init_take_fd (int *dfd, GLnxDirFdIterator *dfd_iter, GError **error) { - gboolean ret = FALSE; GLnxRealDirfdIterator *real_dfd_iter = (GLnxRealDirfdIterator*) dfd_iter; - DIR *d = NULL; - - d = fdopendir (dfd); + DIR *d = fdopendir (*dfd); if (!d) - { - glnx_set_prefix_error_from_errno (error, "%s", "fdopendir"); - goto out; - } + return glnx_throw_errno_prefix (error, "fdopendir"); - real_dfd_iter->fd = dfd; + real_dfd_iter->fd = glnx_steal_fd (dfd); real_dfd_iter->d = d; real_dfd_iter->initialized = TRUE; - ret = TRUE; - out: - return ret; + return TRUE; } /** @@ -165,31 +152,25 @@ glnx_dirfd_iterator_next_dent (GLnxDirFdIterator *dfd_iter, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; GLnxRealDirfdIterator *real_dfd_iter = (GLnxRealDirfdIterator*) dfd_iter; g_return_val_if_fail (out_dent, FALSE); g_return_val_if_fail (dfd_iter->initialized, FALSE); if (g_cancellable_set_error_if_cancelled (cancellable, error)) - goto out; + return FALSE; do { errno = 0; *out_dent = readdir (real_dfd_iter->d); if (*out_dent == NULL && errno != 0) - { - glnx_set_prefix_error_from_errno (error, "%s", "fdopendir"); - goto out; - } + return glnx_throw_errno_prefix (error, "readdir"); } while (*out_dent && (strcmp ((*out_dent)->d_name, ".") == 0 || strcmp ((*out_dent)->d_name, "..") == 0)); - ret = TRUE; - out: - return ret; + return TRUE; } /** diff --git a/libglnx/glnx-dirfd.h b/libglnx/glnx-dirfd.h index 0cb79e69..581b3032 100644 --- a/libglnx/glnx-dirfd.h +++ b/libglnx/glnx-dirfd.h @@ -55,7 +55,7 @@ typedef struct GLnxDirFdIterator GLnxDirFdIterator; gboolean glnx_dirfd_iterator_init_at (int dfd, const char *path, gboolean follow, GLnxDirFdIterator *dfd_iter, GError **error); -gboolean glnx_dirfd_iterator_init_take_fd (int dfd, GLnxDirFdIterator *dfd_iter, GError **error); +gboolean glnx_dirfd_iterator_init_take_fd (int *dfd, GLnxDirFdIterator *dfd_iter, GError **error); gboolean glnx_dirfd_iterator_next_dent (GLnxDirFdIterator *dfd_iter, struct dirent **out_dent, GCancellable *cancellable, diff --git a/libglnx/glnx-fdio.c b/libglnx/glnx-fdio.c index df4c8518..c8da35e8 100644 --- a/libglnx/glnx-fdio.c +++ b/libglnx/glnx-fdio.c @@ -37,7 +37,6 @@ #include #include -#include #include #include #include @@ -65,7 +64,7 @@ glnx_renameat2_noreplace (int olddirfd, const char *oldpath, #ifndef ENABLE_WRPSEUDO_COMPAT if (renameat2 (olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE) < 0) { - if (errno == EINVAL || errno == ENOSYS) + if (G_IN_SET(errno, EINVAL, ENOSYS)) { /* Fall through */ } @@ -119,7 +118,7 @@ glnx_renameat2_exchange (int olddirfd, const char *oldpath, return 0; else { - if (errno == ENOSYS || errno == EINVAL) + if (G_IN_SET(errno, ENOSYS, EINVAL)) { /* Fall through */ } @@ -146,17 +145,51 @@ glnx_renameat2_exchange (int olddirfd, const char *oldpath, return 0; } +/* Deallocate a tmpfile, closing the fd and deleting the path, if any. This is + * normally called by default by the autocleanup attribute, but you can also + * invoke this directly. + */ +void +glnx_tmpfile_clear (GLnxTmpfile *tmpf) +{ + /* Support being passed NULL so we work nicely in a GPtrArray */ + if (!tmpf) + return; + if (!tmpf->initialized) + return; + if (tmpf->fd == -1) + return; + (void) close (tmpf->fd); + /* If ->path is set, we're likely aborting due to an error. Clean it up */ + if (tmpf->path) + { + (void) unlinkat (tmpf->src_dfd, tmpf->path, 0); + g_free (tmpf->path); + } + tmpf->initialized = FALSE; +} + +/* Allocate a temporary file, using Linux O_TMPFILE if available. + * The result will be stored in @out_tmpf, which is caller allocated + * so you can store it on the stack in common scenarios. + * + * Note that with O_TMPFILE, the file mode will be `000`; you likely + * want to chmod it before calling glnx_link_tmpfile_at(). + * + * The directory fd @dfd must live at least as long as the output @out_tmpf. + */ gboolean glnx_open_tmpfile_linkable_at (int dfd, const char *subpath, int flags, - int *out_fd, - char **out_path, + GLnxTmpfile *out_tmpf, GError **error) { glnx_fd_close int fd = -1; int count; + dfd = glnx_dirfd_canonicalize (dfd); + /* Don't allow O_EXCL, as that has a special meaning for O_TMPFILE */ g_return_val_if_fail ((flags & O_EXCL) == 0, FALSE); @@ -168,13 +201,14 @@ glnx_open_tmpfile_linkable_at (int dfd, * in full. */ #if defined(O_TMPFILE) && !defined(DISABLE_OTMPFILE) && !defined(ENABLE_WRPSEUDO_COMPAT) fd = openat (dfd, subpath, O_TMPFILE|flags, 0600); - if (fd == -1 && !(errno == ENOSYS || errno == EISDIR || errno == EOPNOTSUPP)) + if (fd == -1 && !(G_IN_SET(errno, ENOSYS, EISDIR, EOPNOTSUPP))) return glnx_throw_errno_prefix (error, "open(O_TMPFILE)"); if (fd != -1) { - *out_fd = fd; - fd = -1; - *out_path = NULL; + out_tmpf->initialized = TRUE; + out_tmpf->src_dfd = dfd; /* Copied; caller must keep open */ + out_tmpf->fd = glnx_steal_fd (&fd); + out_tmpf->path = NULL; return TRUE; } /* Fallthrough */ @@ -182,7 +216,7 @@ glnx_open_tmpfile_linkable_at (int dfd, { g_autofree char *tmp = g_strconcat (subpath, "/tmp.XXXXXX", NULL); const guint count_max = 100; - + for (count = 0; count < count_max; count++) { glnx_gen_temp_name (tmp); @@ -197,9 +231,10 @@ glnx_open_tmpfile_linkable_at (int dfd, } else { - *out_fd = fd; - fd = -1; - *out_path = g_steal_pointer (&tmp); + out_tmpf->initialized = TRUE; + out_tmpf->src_dfd = dfd; /* Copied; caller must keep open */ + out_tmpf->fd = glnx_steal_fd (&fd); + out_tmpf->path = g_steal_pointer (&tmp); return TRUE; } } @@ -209,11 +244,33 @@ glnx_open_tmpfile_linkable_at (int dfd, return FALSE; } +/* A variant of `glnx_open_tmpfile_linkable_at()` which doesn't support linking. + * Useful for true temporary storage. The fd will be allocated in /var/tmp to + * ensure maximum storage space. + */ gboolean -glnx_link_tmpfile_at (int dfd, +glnx_open_anonymous_tmpfile (int flags, + GLnxTmpfile *out_tmpf, + GError **error) +{ + if (!glnx_open_tmpfile_linkable_at (AT_FDCWD, "/var/tmp", flags, out_tmpf, error)) + return FALSE; + if (out_tmpf->path) + { + (void) unlinkat (out_tmpf->src_dfd, out_tmpf->path, 0); + g_clear_pointer (&out_tmpf->path, g_free); + } + out_tmpf->anonymous = TRUE; + out_tmpf->src_dfd = -1; + return TRUE; +} + +/* Use this after calling glnx_open_tmpfile_linkable_at() to give + * the file its final name (link into place). + */ +gboolean +glnx_link_tmpfile_at (GLnxTmpfile *tmpf, GLnxLinkTmpfileReplaceMode mode, - int fd, - const char *tmpfile_path, int target_dfd, const char *target, GError **error) @@ -221,46 +278,42 @@ glnx_link_tmpfile_at (int dfd, const gboolean replace = (mode == GLNX_LINK_TMPFILE_REPLACE); const gboolean ignore_eexist = (mode == GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST); - g_return_val_if_fail (fd >= 0, FALSE); + g_return_val_if_fail (!tmpf->anonymous, FALSE); + g_return_val_if_fail (tmpf->fd >= 0, FALSE); + g_return_val_if_fail (tmpf->src_dfd == AT_FDCWD || tmpf->src_dfd >= 0, FALSE); /* Unlike the original systemd code, this function also supports * replacing existing files. */ /* We have `tmpfile_path` for old systems without O_TMPFILE. */ - if (tmpfile_path) + if (tmpf->path) { if (replace) { /* We have a regular tempfile, we're overwriting - this is a * simple renameat(). */ - if (renameat (dfd, tmpfile_path, target_dfd, target) < 0) - { - int errsv = errno; - (void) unlinkat (dfd, tmpfile_path, 0); - errno = errsv; - return glnx_throw_errno_prefix (error, "renameat"); - } + if (renameat (tmpf->src_dfd, tmpf->path, target_dfd, target) < 0) + return glnx_throw_errno_prefix (error, "renameat"); } else { /* We need to use renameat2(..., NOREPLACE) or emulate it */ - if (!rename_file_noreplace_at (dfd, tmpfile_path, target_dfd, target, + if (!rename_file_noreplace_at (tmpf->src_dfd, tmpf->path, target_dfd, target, ignore_eexist, error)) - { - (void) unlinkat (dfd, tmpfile_path, 0); - return FALSE; - } + return FALSE; } + /* Now, clear the pointer so we don't try to unlink it */ + g_clear_pointer (&tmpf->path, g_free); } else { /* This case we have O_TMPFILE, so our reference to it is via /proc/self/fd */ - char proc_fd_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(fd) + 1]; + char proc_fd_path[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(tmpf->fd) + 1]; - sprintf (proc_fd_path, "/proc/self/fd/%i", fd); + sprintf (proc_fd_path, "/proc/self/fd/%i", tmpf->fd); if (replace) { @@ -294,16 +347,15 @@ glnx_link_tmpfile_at (int dfd, { g_set_error (error, G_IO_ERROR, G_IO_ERROR_EXISTS, "Exhausted %u attempts to create temporary file", count); + return FALSE; } - if (renameat (target_dfd, tmpname_buf, target_dfd, target) < 0) + if (!glnx_renameat (target_dfd, tmpname_buf, target_dfd, target, error)) { /* This is currently the only case where we need to have * a cleanup unlinkat() still with O_TMPFILE. */ - int errsv = errno; (void) unlinkat (target_dfd, tmpname_buf, 0); - errno = errsv; - return glnx_throw_errno_prefix (error, "renameat"); + return FALSE; } } else @@ -895,7 +947,6 @@ glnx_file_replace_contents_with_perms_at (int dfd, GCancellable *cancellable, GError **error) { - int r; char *dnbuf = strdupa (subpath); const char *dn = dirname (dnbuf); @@ -907,28 +958,18 @@ glnx_file_replace_contents_with_perms_at (int dfd, if (mode == (mode_t) -1) mode = 0644; - glnx_fd_close int fd = -1; - g_autofree char *tmpfile_path = NULL; + g_auto(GLnxTmpfile) tmpf = { 0, }; if (!glnx_open_tmpfile_linkable_at (dfd, dn, O_WRONLY | O_CLOEXEC, - &fd, &tmpfile_path, - error)) + &tmpf, error)) return FALSE; if (len == -1) len = strlen ((char*)buf); - /* Note that posix_fallocate does *not* set errno but returns it. */ - if (len > 0) - { - r = posix_fallocate (fd, 0, len); - if (r != 0) - { - errno = r; - return glnx_throw_errno_prefix (error, "fallocate"); - } - } + if (!glnx_try_fallocate (tmpf.fd, 0, len, error)) + return FALSE; - if (glnx_loop_write (fd, buf, len) < 0) + if (glnx_loop_write (tmpf.fd, buf, len) < 0) return glnx_throw_errno (error); if (!(flags & GLNX_FILE_REPLACE_NODATASYNC)) @@ -947,22 +988,22 @@ glnx_file_replace_contents_with_perms_at (int dfd, if (do_sync) { - if (fdatasync (fd) != 0) + if (fdatasync (tmpf.fd) != 0) return glnx_throw_errno_prefix (error, "fdatasync"); } } if (uid != (uid_t) -1) { - if (fchown (fd, uid, gid) != 0) + if (fchown (tmpf.fd, uid, gid) != 0) return glnx_throw_errno (error); } - if (fchmod (fd, mode) != 0) + if (fchmod (tmpf.fd, mode) != 0) return glnx_throw_errno (error); - if (!glnx_link_tmpfile_at (dfd, GLNX_LINK_TMPFILE_REPLACE, - fd, tmpfile_path, dfd, subpath, error)) + if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_REPLACE, + dfd, subpath, error)) return FALSE; return TRUE; diff --git a/libglnx/glnx-fdio.h b/libglnx/glnx-fdio.h index deff22b2..f459e93a 100644 --- a/libglnx/glnx-fdio.h +++ b/libglnx/glnx-fdio.h @@ -27,6 +27,7 @@ #include #include #include +#include #include /* From systemd/src/shared/util.h */ /* When we include libgen.h because we need dirname() we immediately @@ -35,6 +36,7 @@ #include #undef basename +#include #include G_BEGIN_DECLS @@ -48,12 +50,26 @@ const char *glnx_basename (const char *path) return (basename) (path); } +typedef struct { + gboolean initialized; + gboolean anonymous; + int src_dfd; + int fd; + char *path; +} GLnxTmpfile; +void glnx_tmpfile_clear (GLnxTmpfile *tmpf); +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GLnxTmpfile, glnx_tmpfile_clear) + +gboolean +glnx_open_anonymous_tmpfile (int flags, + GLnxTmpfile *out_tmpf, + GError **error); + gboolean glnx_open_tmpfile_linkable_at (int dfd, const char *subpath, int flags, - int *out_fd, - char **out_path, + GLnxTmpfile *out_tmpf, GError **error); typedef enum { @@ -63,10 +79,8 @@ typedef enum { } GLnxLinkTmpfileReplaceMode; gboolean -glnx_link_tmpfile_at (int dfd, +glnx_link_tmpfile_at (GLnxTmpfile *tmpf, GLnxLinkTmpfileReplaceMode flags, - int fd, - const char *tmpfile_path, int target_dfd, const char *target, GError **error); @@ -160,6 +174,38 @@ int glnx_renameat2_noreplace (int olddirfd, const char *oldpath, int glnx_renameat2_exchange (int olddirfd, const char *oldpath, int newdirfd, const char *newpath); +/** + * glnx_try_fallocate: + * @fd: File descriptor + * @size: Size + * @error: Error + * + * Wrapper for Linux fallocate(). Explicitly ignores a @size of zero. + * Also, will silently do nothing if the underlying filesystem doesn't + * support it. Use this instead of posix_fallocate(), since the glibc fallback + * is bad: https://sourceware.org/bugzilla/show_bug.cgi?id=18515 + */ +static inline gboolean +glnx_try_fallocate (int fd, + off_t offset, + off_t size, + GError **error) +{ + /* This is just nicer than throwing an error */ + if (size == 0) + return TRUE; + + if (fallocate (fd, 0, offset, size) < 0) + { + if (G_IN_SET(errno, ENOSYS, EOPNOTSUPP)) + ; /* Ignore */ + else + return glnx_throw_errno_prefix (error, "fallocate"); + } + + return TRUE; +} + /** * glnx_fstat: * @fd: FD to stat @@ -205,8 +251,42 @@ glnx_fstatat (int dfd, GError **error) { if (TEMP_FAILURE_RETRY (fstatat (dfd, path, buf, flags)) != 0) - return glnx_throw_errno (error); + return glnx_throw_errno_prefix (error, "fstatat(%s)", path); + return TRUE; +} +/** + * glnx_renameat: + * + * Wrapper around renameat() which adds #GError support and ensures that it + * retries on %EINTR. + */ +static inline gboolean +glnx_renameat (int src_dfd, + const gchar *src_path, + int dest_dfd, + const gchar *dest_path, + GError **error) +{ + if (TEMP_FAILURE_RETRY (renameat (src_dfd, src_path, dest_dfd, dest_path)) != 0) + return glnx_throw_errno_prefix (error, "renameat(%s, %s)", src_path, dest_path); + return TRUE; +} + +/** + * glnx_unlinkat: + * + * Wrapper around unlinkat() which adds #GError support and ensures that it + * retries on %EINTR. + */ +static inline gboolean +glnx_unlinkat (int dfd, + const gchar *path, + int flags, + GError **error) +{ + if (TEMP_FAILURE_RETRY (unlinkat (dfd, path, flags)) != 0) + return glnx_throw_errno_prefix (error, "unlinkat(%s)", path); return TRUE; } diff --git a/libglnx/glnx-macros.h b/libglnx/glnx-macros.h new file mode 100644 index 00000000..1d4e1753 --- /dev/null +++ b/libglnx/glnx-macros.h @@ -0,0 +1,189 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2017 Colin Walters + * With original source from systemd: + * Copyright 2010 Lennart Poettering + * + * 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. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +/* All of these are for C only. */ +#ifndef __GI_SCANNER__ + +/* Taken from https://github.com/systemd/systemd/src/basic/string-util.h + * at revision v228-666-gcf6c8c4 + */ +#define glnx_strjoina(a, ...) \ + ({ \ + const char *_appendees_[] = { a, __VA_ARGS__ }; \ + char *_d_, *_p_; \ + int _len_ = 0; \ + unsigned _i_; \ + for (_i_ = 0; _i_ < G_N_ELEMENTS(_appendees_) && _appendees_[_i_]; _i_++) \ + _len_ += strlen(_appendees_[_i_]); \ + _p_ = _d_ = alloca(_len_ + 1); \ + for (_i_ = 0; _i_ < G_N_ELEMENTS(_appendees_) && _appendees_[_i_]; _i_++) \ + _p_ = stpcpy(_p_, _appendees_[_i_]); \ + *_p_ = 0; \ + _d_; \ + }) + +#ifndef G_IN_SET + +/* Infrastructure for `G_IN_SET`; this code is copied from + * systemd's macro.h - please treat that version as canonical + * and submit patches first to systemd. + */ +#define _G_INSET_CASE_F(X) case X: +#define _G_INSET_CASE_F_1(CASE, X) _G_INSET_CASE_F(X) +#define _G_INSET_CASE_F_2(CASE, X, ...) CASE(X) _G_INSET_CASE_F_1(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_3(CASE, X, ...) CASE(X) _G_INSET_CASE_F_2(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_4(CASE, X, ...) CASE(X) _G_INSET_CASE_F_3(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_5(CASE, X, ...) CASE(X) _G_INSET_CASE_F_4(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_6(CASE, X, ...) CASE(X) _G_INSET_CASE_F_5(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_7(CASE, X, ...) CASE(X) _G_INSET_CASE_F_6(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_8(CASE, X, ...) CASE(X) _G_INSET_CASE_F_7(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_9(CASE, X, ...) CASE(X) _G_INSET_CASE_F_8(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_10(CASE, X, ...) CASE(X) _G_INSET_CASE_F_9(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_11(CASE, X, ...) CASE(X) _G_INSET_CASE_F_10(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_12(CASE, X, ...) CASE(X) _G_INSET_CASE_F_11(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_13(CASE, X, ...) CASE(X) _G_INSET_CASE_F_12(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_14(CASE, X, ...) CASE(X) _G_INSET_CASE_F_13(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_15(CASE, X, ...) CASE(X) _G_INSET_CASE_F_14(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_16(CASE, X, ...) CASE(X) _G_INSET_CASE_F_15(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_17(CASE, X, ...) CASE(X) _G_INSET_CASE_F_16(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_18(CASE, X, ...) CASE(X) _G_INSET_CASE_F_17(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_19(CASE, X, ...) CASE(X) _G_INSET_CASE_F_18(CASE, __VA_ARGS__) +#define _G_INSET_CASE_F_20(CASE, X, ...) CASE(X) _G_INSET_CASE_F_19(CASE, __VA_ARGS__) + +#define _G_INSET_GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME +#define _G_INSET_FOR_EACH_MAKE_CASE(...) \ + _G_INSET_GET_CASE_F(__VA_ARGS__,_G_INSET_CASE_F_20,_G_INSET_CASE_F_19,_G_INSET_CASE_F_18,_G_INSET_CASE_F_17,_G_INSET_CASE_F_16,_G_INSET_CASE_F_15,_G_INSET_CASE_F_14,_G_INSET_CASE_F_13,_G_INSET_CASE_F_12,_G_INSET_CASE_F_11, \ + _G_INSET_CASE_F_10,_G_INSET_CASE_F_9,_G_INSET_CASE_F_8,_G_INSET_CASE_F_7,_G_INSET_CASE_F_6,_G_INSET_CASE_F_5,_G_INSET_CASE_F_4,_G_INSET_CASE_F_3,_G_INSET_CASE_F_2,_G_INSET_CASE_F_1) \ + (_G_INSET_CASE_F,__VA_ARGS__) + +/* Note: claiming the name here even though it isn't upstream yet + * https://bugzilla.gnome.org/show_bug.cgi?id=783751 + */ +/** + * G_IN_SET: + * @x: Integer (or smaller) sized value + * @...: Elements to compare + * + * It's quite common to test whether or not `char` values or Unix @errno (among) others + * are members of a small set. Normally one has to choose to either use `if (x == val || x == otherval ...)` + * or a `switch` statement. This macro is useful to reduce duplication in the first case, + * where one can write simply `if (G_IN_SET (x, val, otherval))`, and avoid the verbosity + * that the `switch` statement requires. + */ +#define G_IN_SET(x, ...) \ + ({ \ + gboolean _g_inset_found = FALSE; \ + /* If the build breaks in the line below, you need to extend the case macros */ \ + static G_GNUC_UNUSED char _static_assert__macros_need_to_be_extended[20 - sizeof((int[]){__VA_ARGS__})/sizeof(int)]; \ + switch(x) { \ + _G_INSET_FOR_EACH_MAKE_CASE(__VA_ARGS__) \ + _g_inset_found = TRUE; \ + break; \ + default: \ + break; \ + } \ + _g_inset_found; \ + }) + +#endif /* ifndef G_IN_SET */ + +#define _GLNX_CONCAT(a, b) a##b +#define _GLNX_CONCAT_INDIRECT(a, b) _GLNX_CONCAT(a, b) +#define _GLNX_MAKE_ANONYMOUS(a) _GLNX_CONCAT_INDIRECT(a, __COUNTER__) + +#define _GLNX_HASH_TABLE_FOREACH_IMPL_KV(guard, ht, it, kt, k, vt, v) \ + gboolean guard = TRUE; \ + G_STATIC_ASSERT (sizeof (kt) == sizeof (void*)); \ + G_STATIC_ASSERT (sizeof (vt) == sizeof (void*)); \ + for (GHashTableIter it; \ + guard && ({ g_hash_table_iter_init (&it, ht), TRUE; }); \ + guard = FALSE) \ + for (kt k; guard; guard = FALSE) \ + for (vt v; g_hash_table_iter_next (&it, (gpointer)&k, (gpointer)&v);) + + +/* Cleaner method to iterate over a GHashTable. I.e. rather than + * + * gpointer k, v; + * GHashTableIter it; + * g_hash_table_iter_init (&it, table); + * while (g_hash_table_iter_next (&it, &k, &v)) + * { + * const char *str = k; + * GPtrArray *arr = v; + * ... + * } + * + * you can simply do + * + * GLNX_HASH_TABLE_FOREACH_IT (table, it, const char*, str, GPtrArray*, arr) + * { + * ... + * } + * + * All variables are scoped within the loop. You may use the `it` variable as + * usual, e.g. to remove an element using g_hash_table_iter_remove(&it). There + * are shorter variants for the more common cases where you do not need access + * to the iterator or to keys/values: + * + * GLNX_HASH_TABLE_FOREACH (table, const char*, str) { ... } + * GLNX_HASH_TABLE_FOREACH_V (table, MyData*, data) { ... } + * GLNX_HASH_TABLE_FOREACH_KV (table, const char*, str, MyData*, data) { ... } + * + */ +#define GLNX_HASH_TABLE_FOREACH_IT(ht, it, kt, k, vt, v) \ + _GLNX_HASH_TABLE_FOREACH_IMPL_KV( \ + _GLNX_MAKE_ANONYMOUS(_glnx_ht_iter_guard_), ht, it, kt, k, vt, v) + +/* Variant of GLNX_HASH_TABLE_FOREACH without having to specify an iterator. An + * anonymous iterator will be created. */ +#define GLNX_HASH_TABLE_FOREACH_KV(ht, kt, k, vt, v) \ + _GLNX_HASH_TABLE_FOREACH_IMPL_KV( \ + _GLNX_MAKE_ANONYMOUS(_glnx_ht_iter_guard_), ht, \ + _GLNX_MAKE_ANONYMOUS(_glnx_ht_iter_it_), kt, k, vt, v) + +/* Variant of GLNX_HASH_TABLE_FOREACH_KV which omits unpacking keys. */ +#define GLNX_HASH_TABLE_FOREACH_V(ht, vt, v) \ + _GLNX_HASH_TABLE_FOREACH_IMPL_KV( \ + _GLNX_MAKE_ANONYMOUS(_glnx_ht_iter_guard_), ht, \ + _GLNX_MAKE_ANONYMOUS(_glnx_ht_iter_it_), \ + gpointer, _GLNX_MAKE_ANONYMOUS(_glnx_ht_iter_v_), \ + vt, v) + +/* Variant of GLNX_HASH_TABLE_FOREACH_KV which omits unpacking vals. */ +#define GLNX_HASH_TABLE_FOREACH(ht, kt, k) \ + _GLNX_HASH_TABLE_FOREACH_IMPL_KV( \ + _GLNX_MAKE_ANONYMOUS(_glnx_ht_iter_guard_), ht, \ + _GLNX_MAKE_ANONYMOUS(_glnx_ht_iter_it_), kt, k, \ + gpointer, _GLNX_MAKE_ANONYMOUS(_glnx_ht_iter_v_)) + +#endif /* GI_SCANNER */ + +G_END_DECLS diff --git a/libglnx/glnx-shutil.c b/libglnx/glnx-shutil.c index 6a1cdd4a..2e02eea4 100644 --- a/libglnx/glnx-shutil.c +++ b/libglnx/glnx-shutil.c @@ -110,9 +110,8 @@ glnx_shutil_rm_rf_at (int dfd, } else { - if (!glnx_dirfd_iterator_init_take_fd (target_dfd, &dfd_iter, error)) + if (!glnx_dirfd_iterator_init_take_fd (&target_dfd, &dfd_iter, error)) return FALSE; - target_dfd = -1; if (!glnx_shutil_rm_rf_children (&dfd_iter, cancellable, error)) return FALSE; diff --git a/libglnx/glnx-xattrs.c b/libglnx/glnx-xattrs.c index eadb6b15..3ade89c1 100644 --- a/libglnx/glnx-xattrs.c +++ b/libglnx/glnx-xattrs.c @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -249,7 +250,7 @@ glnx_dfd_name_get_all_xattrs (int dfd, GCancellable *cancellable, GError **error) { - if (dfd == AT_FDCWD || dfd == -1) + if (G_IN_SET(dfd, AT_FDCWD, -1)) { return get_xattrs_impl (name, -1, out_xattrs, cancellable, error); } @@ -270,31 +271,22 @@ set_all_xattrs_for_path (const char *path, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - int i, n; - - n = g_variant_n_children (xattrs); - for (i = 0; i < n; i++) + const guint n = g_variant_n_children (xattrs); + for (guint i = 0; i < n; i++) { const guint8* name; g_autoptr(GVariant) value = NULL; - const guint8* value_data; - gsize value_len; - g_variant_get_child (xattrs, i, "(^&ay@ay)", &name, &value); - value_data = g_variant_get_fixed_array (value, &value_len, 1); - + + gsize value_len; + const guint8* value_data = g_variant_get_fixed_array (value, &value_len, 1); + if (lsetxattr (path, (char*)name, (char*)value_data, value_len, 0) < 0) - { - glnx_set_prefix_error_from_errno (error, "%s", "lsetxattr"); - goto out; - } + return glnx_throw_errno_prefix (error, "lsetxattr"); } - ret = TRUE; - out: - return ret; + return TRUE; } /** @@ -315,7 +307,7 @@ glnx_dfd_name_set_all_xattrs (int dfd, GCancellable *cancellable, GError **error) { - if (dfd == AT_FDCWD || dfd == -1) + if (G_IN_SET(dfd, AT_FDCWD, -1)) { return set_all_xattrs_for_path (name, xattrs, cancellable, error); } @@ -347,35 +339,22 @@ glnx_fd_set_all_xattrs (int fd, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - int i, n; - - n = g_variant_n_children (xattrs); - for (i = 0; i < n; i++) + const guint n = g_variant_n_children (xattrs); + for (guint i = 0; i < n; i++) { const guint8* name; - const guint8* value_data; g_autoptr(GVariant) value = NULL; - gsize value_len; - int res; - g_variant_get_child (xattrs, i, "(^&ay@ay)", &name, &value); - value_data = g_variant_get_fixed_array (value, &value_len, 1); - - do - res = fsetxattr (fd, (char*)name, (char*)value_data, value_len, 0); - while (G_UNLIKELY (res == -1 && errno == EINTR)); - if (G_UNLIKELY (res == -1)) - { - glnx_set_prefix_error_from_errno (error, "%s", "fsetxattr"); - goto out; - } + + gsize value_len; + const guint8* value_data = g_variant_get_fixed_array (value, &value_len, 1); + + if (TEMP_FAILURE_RETRY (fsetxattr (fd, (char*)name, (char*)value_data, value_len, 0)) < 0) + return glnx_throw_errno_prefix (error, "fsetxattr"); } - ret = TRUE; - out: - return ret; + return TRUE; } /** @@ -395,35 +374,17 @@ glnx_lgetxattrat (int dfd, GError **error) { char pathbuf[PATH_MAX]; - GBytes *bytes = NULL; - ssize_t bytes_read, real_size; - guint8 *buf; - snprintf (pathbuf, sizeof (pathbuf), "/proc/self/fd/%d/%s", dfd, subpath); - do - bytes_read = lgetxattr (pathbuf, attribute, NULL, 0); - while (G_UNLIKELY (bytes_read < 0 && errno == EINTR)); - if (G_UNLIKELY (bytes_read < 0)) - { - glnx_set_error_from_errno (error); - goto out; - } + ssize_t bytes_read, real_size; + if (TEMP_FAILURE_RETRY (bytes_read = lgetxattr (pathbuf, attribute, NULL, 0)) < 0) + return glnx_null_throw_errno_prefix (error, "lgetxattr"); - buf = g_malloc (bytes_read); - do - real_size = lgetxattr (pathbuf, attribute, buf, bytes_read); - while (G_UNLIKELY (real_size < 0 && errno == EINTR)); - if (G_UNLIKELY (real_size < 0)) - { - glnx_set_error_from_errno (error); - g_free (buf); - goto out; - } + g_autofree guint8 *buf = g_malloc (bytes_read); + if (TEMP_FAILURE_RETRY (real_size = lgetxattr (pathbuf, attribute, buf, bytes_read)) < 0) + return glnx_null_throw_errno_prefix (error, "lgetxattr"); - bytes = g_bytes_new_take (buf, real_size); - out: - return bytes; + return g_bytes_new_take (g_steal_pointer (&buf), real_size); } /** @@ -439,33 +400,16 @@ glnx_fgetxattr_bytes (int fd, const char *attribute, GError **error) { - GBytes *bytes = NULL; ssize_t bytes_read, real_size; - guint8 *buf; - do - bytes_read = fgetxattr (fd, attribute, NULL, 0); - while (G_UNLIKELY (bytes_read < 0 && errno == EINTR)); - if (G_UNLIKELY (bytes_read < 0)) - { - glnx_set_error_from_errno (error); - goto out; - } + if (TEMP_FAILURE_RETRY (bytes_read = fgetxattr (fd, attribute, NULL, 0)) < 0) + return glnx_null_throw_errno_prefix (error, "fgetxattr"); - buf = g_malloc (bytes_read); - do - real_size = fgetxattr (fd, attribute, buf, bytes_read); - while (G_UNLIKELY (real_size < 0 && errno == EINTR)); - if (G_UNLIKELY (real_size < 0)) - { - glnx_set_error_from_errno (error); - g_free (buf); - goto out; - } + g_autofree guint8 *buf = g_malloc (bytes_read); + if (TEMP_FAILURE_RETRY (real_size = fgetxattr (fd, attribute, buf, bytes_read)) < 0) + return glnx_null_throw_errno_prefix (error, "fgetxattr"); - bytes = g_bytes_new_take (buf, real_size); - out: - return bytes; + return g_bytes_new_take (g_steal_pointer (&buf), real_size); } /** @@ -490,18 +434,10 @@ glnx_lsetxattrat (int dfd, GError **error) { char pathbuf[PATH_MAX]; - int res; - snprintf (pathbuf, sizeof (pathbuf), "/proc/self/fd/%d/%s", dfd, subpath); - do - res = lsetxattr (subpath, attribute, value, len, flags); - while (G_UNLIKELY (res == -1 && errno == EINTR)); - if (G_UNLIKELY (res == -1)) - { - glnx_set_error_from_errno (error); - return FALSE; - } + if (TEMP_FAILURE_RETRY (lsetxattr (subpath, attribute, value, len, flags)) < 0) + return glnx_throw_errno_prefix (error, "lsetxattr"); return TRUE; } diff --git a/libglnx/libglnx.h b/libglnx/libglnx.h index a5b23d07..494810d4 100644 --- a/libglnx/libglnx.h +++ b/libglnx/libglnx.h @@ -24,7 +24,7 @@ G_BEGIN_DECLS -#include +#include #include #include #include diff --git a/libglnx/tests/test-libglnx-fdio.c b/libglnx/tests/test-libglnx-fdio.c index 9830c107..311e7f0f 100644 --- a/libglnx/tests/test-libglnx-fdio.c +++ b/libglnx/tests/test-libglnx-fdio.c @@ -80,11 +80,8 @@ test_renameat2_noreplace (void) glnx_set_error_from_errno (error); goto out; } - if (fstatat (destfd, "bar", &stbuf, AT_SYMLINK_NOFOLLOW) < 0) - { - glnx_set_error_from_errno (error); - goto out; - } + if (!glnx_fstatat (destfd, "bar", &stbuf, AT_SYMLINK_NOFOLLOW, error)) + goto out; if (fstatat (srcfd, "foo", &stbuf, AT_SYMLINK_NOFOLLOW) == 0) g_assert_not_reached (); diff --git a/libglnx/tests/test-libglnx-macros.c b/libglnx/tests/test-libglnx-macros.c new file mode 100644 index 00000000..ffde8fae --- /dev/null +++ b/libglnx/tests/test-libglnx-macros.c @@ -0,0 +1,109 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2017 Red Hat, Inc. + * + * 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 "libglnx.h" +#include +#include +#include +#include + +static void +test_inset (void) +{ + g_assert (G_IN_SET (7, 7)); + g_assert (G_IN_SET (7, 42, 7)); + g_assert (G_IN_SET (7, 7,42,3,9)); + g_assert (G_IN_SET (42, 7,42,3,9)); + g_assert (G_IN_SET (3, 7,42,3,9)); + g_assert (G_IN_SET (9, 7,42,3,9)); + g_assert (!G_IN_SET (8, 7,42,3,9)); + g_assert (!G_IN_SET (-1, 7,42,3,9)); + g_assert (G_IN_SET ('x', 'a', 'x', 'c')); + g_assert (!G_IN_SET ('y', 'a', 'x', 'c')); +} + +static void +test_hash_table_foreach (void) +{ + /* use var names all different from the macro metavars to ensure proper + * substitution */ + g_autoptr(GHashTable) table = g_hash_table_new (g_str_hash, g_str_equal); + const char *keys[] = {"key1", "key2"}; + const char *vals[] = {"val1", "val2"}; + g_hash_table_insert (table, (gpointer)keys[0], (gpointer)vals[0]); + g_hash_table_insert (table, (gpointer)keys[1], (gpointer)vals[1]); + + guint i = 0; + GLNX_HASH_TABLE_FOREACH_IT (table, it, const char*, key, const char*, val) + { + g_assert_cmpstr (key, ==, keys[i]); + g_assert_cmpstr (val, ==, vals[i]); + i++; + } + g_assert_cmpuint (i, ==, 2); + + i = 0; + GLNX_HASH_TABLE_FOREACH_IT (table, it, const char*, key, const char*, val) + { + g_hash_table_iter_remove (&it); + break; + } + g_assert_cmpuint (g_hash_table_size (table), ==, 1); + + g_hash_table_insert (table, (gpointer)keys[1], (gpointer)vals[1]); + g_assert_cmpuint (g_hash_table_size (table), ==, 1); + + g_hash_table_insert (table, (gpointer)keys[0], (gpointer)vals[0]); + g_assert_cmpuint (g_hash_table_size (table), ==, 2); + + i = 0; + GLNX_HASH_TABLE_FOREACH_KV (table, const char*, key, const char*, val) + { + g_assert_cmpstr (key, ==, keys[i]); + g_assert_cmpstr (val, ==, vals[i]); + i++; + } + g_assert_cmpuint (i, ==, 2); + + i = 0; + GLNX_HASH_TABLE_FOREACH (table, const char*, key) + { + g_assert_cmpstr (key, ==, keys[i]); + i++; + } + g_assert_cmpuint (i, ==, 2); + + i = 0; + GLNX_HASH_TABLE_FOREACH_V (table, const char*, val) + { + g_assert_cmpstr (val, ==, vals[i]); + i++; + } + g_assert_cmpuint (i, ==, 2); +} + +int main (int argc, char **argv) +{ + g_test_init (&argc, &argv, NULL); + g_test_add_func ("/inset", test_inset); + g_test_add_func ("/hash_table_foreach", test_hash_table_foreach); + return g_test_run(); +} diff --git a/man/ostree-admin-cleanup.1 b/man/ostree-admin-cleanup.1 index d9ed4485..85ca63eb 100644 --- a/man/ostree-admin-cleanup.1 +++ b/man/ostree-admin-cleanup.1 @@ -2,7 +2,7 @@ .\" Title: ostree admin cleanup .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree admin cleanup .\" Source: OSTree .\" Language: English diff --git a/man/ostree-admin-config-diff.1 b/man/ostree-admin-config-diff.1 index d225fc30..40a1cb60 100644 --- a/man/ostree-admin-config-diff.1 +++ b/man/ostree-admin-config-diff.1 @@ -2,7 +2,7 @@ .\" Title: ostree admin config-diff .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree admin config-diff .\" Source: OSTree .\" Language: English diff --git a/man/ostree-admin-deploy.1 b/man/ostree-admin-deploy.1 index 4940abc7..103252f5 100644 --- a/man/ostree-admin-deploy.1 +++ b/man/ostree-admin-deploy.1 @@ -2,7 +2,7 @@ .\" Title: ostree admin deploy .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree admin deploy .\" Source: OSTree .\" Language: English diff --git a/man/ostree-admin-init-fs.1 b/man/ostree-admin-init-fs.1 index a297882c..aa26f535 100644 --- a/man/ostree-admin-init-fs.1 +++ b/man/ostree-admin-init-fs.1 @@ -2,7 +2,7 @@ .\" Title: ostree admin init-fs .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree admin init-fs .\" Source: OSTree .\" Language: English diff --git a/man/ostree-admin-instutil.1 b/man/ostree-admin-instutil.1 index e6ca5c83..d6922af4 100644 --- a/man/ostree-admin-instutil.1 +++ b/man/ostree-admin-instutil.1 @@ -2,7 +2,7 @@ .\" Title: ostree admin instutil .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree admin instutil .\" Source: OSTree .\" Language: English diff --git a/man/ostree-admin-os-init.1 b/man/ostree-admin-os-init.1 index 071ba7b7..034e1385 100644 --- a/man/ostree-admin-os-init.1 +++ b/man/ostree-admin-os-init.1 @@ -2,7 +2,7 @@ .\" Title: ostree admin os-init .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree admin os-init .\" Source: OSTree .\" Language: English diff --git a/man/ostree-admin-set-origin.1 b/man/ostree-admin-set-origin.1 index c198370e..b84fc5da 100644 --- a/man/ostree-admin-set-origin.1 +++ b/man/ostree-admin-set-origin.1 @@ -2,7 +2,7 @@ .\" Title: ostree admin set-origin .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree admin set-origin .\" Source: OSTree .\" Language: English diff --git a/man/ostree-admin-status.1 b/man/ostree-admin-status.1 index 7f53c64b..ae2fc2ac 100644 --- a/man/ostree-admin-status.1 +++ b/man/ostree-admin-status.1 @@ -2,7 +2,7 @@ .\" Title: ostree admin status .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree admin status .\" Source: OSTree .\" Language: English diff --git a/man/ostree-admin-switch.1 b/man/ostree-admin-switch.1 index 8ba9f9a3..c9977aa8 100644 --- a/man/ostree-admin-switch.1 +++ b/man/ostree-admin-switch.1 @@ -2,7 +2,7 @@ .\" Title: ostree admin switch .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree admin switch .\" Source: OSTree .\" Language: English diff --git a/man/ostree-admin-undeploy.1 b/man/ostree-admin-undeploy.1 index e3fbde2f..6a3633bd 100644 --- a/man/ostree-admin-undeploy.1 +++ b/man/ostree-admin-undeploy.1 @@ -2,7 +2,7 @@ .\" Title: ostree admin undeploy .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree admin undeploy .\" Source: OSTree .\" Language: English diff --git a/man/ostree-admin-unlock.1 b/man/ostree-admin-unlock.1 index 00aa50a8..2731da6c 100644 --- a/man/ostree-admin-unlock.1 +++ b/man/ostree-admin-unlock.1 @@ -2,7 +2,7 @@ .\" Title: ostree admin unlock .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree admin unlock .\" Source: OSTree .\" Language: English diff --git a/man/ostree-admin-upgrade.1 b/man/ostree-admin-upgrade.1 index b38fbb4b..108a0a2c 100644 --- a/man/ostree-admin-upgrade.1 +++ b/man/ostree-admin-upgrade.1 @@ -2,7 +2,7 @@ .\" Title: ostree admin upgrade .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree admin upgrade .\" Source: OSTree .\" Language: English diff --git a/man/ostree-admin.1 b/man/ostree-admin.1 index 08b208b8..1f15728f 100644 --- a/man/ostree-admin.1 +++ b/man/ostree-admin.1 @@ -2,7 +2,7 @@ .\" Title: ostree admin .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree admin .\" Source: OSTree .\" Language: English diff --git a/man/ostree-cat.1 b/man/ostree-cat.1 index 30682883..6cbaf8b5 100644 --- a/man/ostree-cat.1 +++ b/man/ostree-cat.1 @@ -2,7 +2,7 @@ .\" Title: ostree cat .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree cat .\" Source: OSTree .\" Language: English diff --git a/man/ostree-checkout.1 b/man/ostree-checkout.1 index 9ebe63a8..49a33aea 100644 --- a/man/ostree-checkout.1 +++ b/man/ostree-checkout.1 @@ -2,7 +2,7 @@ .\" Title: ostree checkout .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree checkout .\" Source: OSTree .\" Language: English diff --git a/man/ostree-checksum.1 b/man/ostree-checksum.1 index fdc8f435..e002cab8 100644 --- a/man/ostree-checksum.1 +++ b/man/ostree-checksum.1 @@ -2,7 +2,7 @@ .\" Title: ostree checksum .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree checksum .\" Source: OSTree .\" Language: English diff --git a/man/ostree-commit.1 b/man/ostree-commit.1 index 10518ead..dcb64168 100644 --- a/man/ostree-commit.1 +++ b/man/ostree-commit.1 @@ -2,7 +2,7 @@ .\" Title: ostree commit .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree commit .\" Source: OSTree .\" Language: English diff --git a/man/ostree-config.1 b/man/ostree-config.1 index d12f72a2..c5d73303 100644 --- a/man/ostree-config.1 +++ b/man/ostree-config.1 @@ -2,7 +2,7 @@ .\" Title: ostree config .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree config .\" Source: OSTree .\" Language: English diff --git a/man/ostree-diff.1 b/man/ostree-diff.1 index c0b5b895..ea8bfac5 100644 --- a/man/ostree-diff.1 +++ b/man/ostree-diff.1 @@ -2,7 +2,7 @@ .\" Title: ostree diff .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree diff .\" Source: OSTree .\" Language: English diff --git a/man/ostree-export.1 b/man/ostree-export.1 index 79384e65..bab63bd3 100644 --- a/man/ostree-export.1 +++ b/man/ostree-export.1 @@ -2,7 +2,7 @@ .\" Title: ostree export .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree export .\" Source: OSTree .\" Language: English diff --git a/man/ostree-fsck.1 b/man/ostree-fsck.1 index febe37ff..7a933155 100644 --- a/man/ostree-fsck.1 +++ b/man/ostree-fsck.1 @@ -2,7 +2,7 @@ .\" Title: ostree fsck .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree fsck .\" Source: OSTree .\" Language: English diff --git a/man/ostree-gpg-sign.1 b/man/ostree-gpg-sign.1 index 0e6601fd..7a630729 100644 --- a/man/ostree-gpg-sign.1 +++ b/man/ostree-gpg-sign.1 @@ -2,7 +2,7 @@ .\" Title: ostree gpg-sign .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree gpg-sign .\" Source: OSTree .\" Language: English diff --git a/man/ostree-init.1 b/man/ostree-init.1 index 5d4a9f12..63434fe2 100644 --- a/man/ostree-init.1 +++ b/man/ostree-init.1 @@ -2,7 +2,7 @@ .\" Title: ostree init .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 07/04/2017 .\" Manual: ostree init .\" Source: OSTree .\" Language: English @@ -41,3 +41,14 @@ Creates a new empty repository\&. .RS 4 Initialize repository in given mode (bare, bare\-user, archive\-z2)\&. Default is "bare"\&. .RE +.PP +\fB\-\-collection\-id\fR=COLLECTION\-ID +.RS 4 +Set the collection ID of the repository\&. Remotes in clones of this repository must configure the same value in order to pull refs which originated in this repository over peer to peer\&. +.sp +This collection ID must be persistent and globally unique\&. It is formatted as a reverse DNS name (like a D\-Bus interface)\&. It must be set to a reverse DNS domain under your control\&. +.sp +This option may be omitted (the default) to leave peer to peer distribution unsupported for the repository\&. A collection ID may be added to an existing repository in future to enable peer to peer distribution from that point onwards\&. +.sp +If the collection ID is changed for the repository in future, peer to peer distribution of refs from the repository will break for all peers who do not update their remote configuration to the new collection ID\&. +.RE diff --git a/man/ostree-init.xml b/man/ostree-init.xml index 95f0b162..fb35c0da 100644 --- a/man/ostree-init.xml +++ b/man/ostree-init.xml @@ -71,6 +71,32 @@ Boston, MA 02111-1307, USA. Initialize repository in given mode (bare, bare-user, archive-z2). Default is "bare". + + + =COLLECTION-ID + + Set the collection ID of the repository. Remotes in clones + of this repository must configure the same value in order to + pull refs which originated in this repository over peer to + peer. + + This collection ID must be persistent and globally + unique. It is formatted as a reverse DNS name (like a D-Bus + interface). It must be set to a reverse DNS domain under your + control. + + This option may be omitted (the default) to leave + peer to peer distribution unsupported for the repository. A + collection ID may be added to an existing repository in + future to enable peer to peer distribution from that point + onwards. + + If the collection ID is changed for the repository + in future, peer to peer distribution of refs from the + repository will break for all peers who do not update their + remote configuration to the new collection ID. + + diff --git a/man/ostree-log.1 b/man/ostree-log.1 index f085e242..262a13c3 100644 --- a/man/ostree-log.1 +++ b/man/ostree-log.1 @@ -2,7 +2,7 @@ .\" Title: ostree log .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree log .\" Source: OSTree .\" Language: English diff --git a/man/ostree-ls.1 b/man/ostree-ls.1 index 1c40c990..2d8250d9 100644 --- a/man/ostree-ls.1 +++ b/man/ostree-ls.1 @@ -2,7 +2,7 @@ .\" Title: ostree ls .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree ls .\" Source: OSTree .\" Language: English diff --git a/man/ostree-prune.1 b/man/ostree-prune.1 index c25f23ce..e08fe91c 100644 --- a/man/ostree-prune.1 +++ b/man/ostree-prune.1 @@ -2,7 +2,7 @@ .\" Title: ostree prune .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree prune .\" Source: OSTree .\" Language: English diff --git a/man/ostree-pull-local.1 b/man/ostree-pull-local.1 index c6124196..07762f26 100644 --- a/man/ostree-pull-local.1 +++ b/man/ostree-pull-local.1 @@ -2,7 +2,7 @@ .\" Title: ostree pull-local .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree pull-local .\" Source: OSTree .\" Language: English diff --git a/man/ostree-pull.1 b/man/ostree-pull.1 index f0ad3ba5..acf88971 100644 --- a/man/ostree-pull.1 +++ b/man/ostree-pull.1 @@ -2,7 +2,7 @@ .\" Title: ostree pull .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 07/04/2017 .\" Manual: ostree pull .\" Source: OSTree .\" Language: English @@ -44,6 +44,12 @@ Fetch only the commit metadata\&. Do no invoke fsync()\&. .RE .PP +\fB\-\-localcache\-repo\fR +.RS 4 +Like git\*(Aqs +clone \-\-reference\&. Reuse the provided OSTree repo as a local object cache of objects when doing HTTP fetches\&. May be specified multiple times\&. +.RE +.PP \fB\-\-untrusted\fR .RS 4 Do not trust local sources, verify checksums and don\*(Aqt hardlink into source\&. diff --git a/man/ostree-pull.xml b/man/ostree-pull.xml index 24ab0b72..394a29c2 100644 --- a/man/ostree-pull.xml +++ b/man/ostree-pull.xml @@ -73,6 +73,16 @@ Boston, MA 02111-1307, USA. + + + + + Like git's clone --reference. Reuse the provided + OSTree repo as a local object cache of objects when doing HTTP fetches. + May be specified multiple times. + + + diff --git a/man/ostree-refs.1 b/man/ostree-refs.1 index 665c3837..85dfdc4f 100644 --- a/man/ostree-refs.1 +++ b/man/ostree-refs.1 @@ -2,7 +2,7 @@ .\" Title: ostree refs .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 07/04/2017 .\" Manual: ostree refs .\" Source: OSTree .\" Language: English @@ -51,6 +51,21 @@ Delete refs which match PREFIX, rather than listing them\&. If you are trying to or \fBostree admin cleanup\fR\&. .RE +.PP +\fB\-\-collections\fR +.RS 4 +Enable interactions with refs using the combination of their collection IDs and ref names\&. When listing refs, this changes the output format to include collection IDs, and enables listing remote mirrored refs\&. +.sp +When creating refs, the refspec value passed to the +\fB\-\-create\fR +option is treated as +COLLECTION\-ID:REF\-NAME +and a mirrored ref is created\&. (This is an abuse of the refspec syntax\&.) +.sp +When deleting refs, all refs whose collection ID equals the value of the +\fB\-\-delete\fR +argument are deleted\&. +.RE .SH "EXAMPLE" .PP \fB$ ostree refs\fR diff --git a/man/ostree-refs.xml b/man/ostree-refs.xml index 43e934ff..726a94ac 100644 --- a/man/ostree-refs.xml +++ b/man/ostree-refs.xml @@ -83,6 +83,26 @@ Boston, MA 02111-1307, USA. you will then need to ostree prune or ostree admin cleanup. + + + + + + Enable interactions with refs using the combination of their + collection IDs and ref names. When listing refs, this changes + the output format to include collection IDs, and enables + listing remote mirrored refs. + + When creating refs, the refspec value passed to the + option is treated as + COLLECTION-ID:REF-NAME and a mirrored ref + is created. (This is an abuse of the refspec syntax.) + + When deleting refs, all refs whose collection ID equals + the value of the argument are + deleted. + + diff --git a/man/ostree-remote.1 b/man/ostree-remote.1 index c0fed9e1..55b65284 100644 --- a/man/ostree-remote.1 +++ b/man/ostree-remote.1 @@ -2,7 +2,7 @@ .\" Title: ostree remote .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 07/04/2017 .\" Manual: ostree remote .\" Source: OSTree .\" Language: English @@ -86,6 +86,11 @@ Import one or more GPG keys from a file\&. Equivalent to \fBostree remote gpg\-import \-\-keyring=FILE\fR\&. .RE +.PP +\fB\-\-collection\-id\fR=COLLECTION\-ID +.RS 4 +Set the collection ID for the remote to a value provided by the repository owner, which allows refs from this remote to be shared peer to peer\&. +.RE .SH "\*(AQLIST\*(AQ OPTIONS" .PP \fB\-u, \-\-show\-urls\fR diff --git a/man/ostree-remote.xml b/man/ostree-remote.xml index 88e61ac0..2bb8aa71 100644 --- a/man/ostree-remote.xml +++ b/man/ostree-remote.xml @@ -137,6 +137,16 @@ Boston, MA 02111-1307, USA. ostree remote gpg-import --keyring=FILE. + + + =COLLECTION-ID + + + Set the collection ID for the remote to a value provided by + the repository owner, which allows refs from this remote to be + shared peer to peer. + + diff --git a/man/ostree-reset.1 b/man/ostree-reset.1 index 04f8db1a..f234f1f9 100644 --- a/man/ostree-reset.1 +++ b/man/ostree-reset.1 @@ -2,7 +2,7 @@ .\" Title: ostree reset .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree reset .\" Source: OSTree .\" Language: English diff --git a/man/ostree-rev-parse.1 b/man/ostree-rev-parse.1 index 4b0984aa..99fd1397 100644 --- a/man/ostree-rev-parse.1 +++ b/man/ostree-rev-parse.1 @@ -2,7 +2,7 @@ .\" Title: ostree rev-parse .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree rev-parse .\" Source: OSTree .\" Language: English diff --git a/man/ostree-show.1 b/man/ostree-show.1 index 5fdd55d5..7c891e7f 100644 --- a/man/ostree-show.1 +++ b/man/ostree-show.1 @@ -2,7 +2,7 @@ .\" Title: ostree show .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree show .\" Source: OSTree .\" Language: English diff --git a/man/ostree-static-delta.1 b/man/ostree-static-delta.1 index 6a8a3215..323e541c 100644 --- a/man/ostree-static-delta.1 +++ b/man/ostree-static-delta.1 @@ -2,7 +2,7 @@ .\" Title: ostree static-delta .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree static-delta .\" Source: OSTree .\" Language: English diff --git a/man/ostree-summary.1 b/man/ostree-summary.1 index 0bd1252e..61e5563f 100644 --- a/man/ostree-summary.1 +++ b/man/ostree-summary.1 @@ -2,7 +2,7 @@ .\" Title: ostree summary .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 07/04/2017 .\" Manual: ostree summary .\" Source: OSTree .\" Language: English @@ -42,6 +42,11 @@ file is an optional higher level form of repository metadata that describes the \fB\-u\fR .RS 4 Update the summary file\&. +.sp +Any additional arguments to the command are treated as additional key\(envalue pairs to be added to the summary file as additional metadata\&. They must be in the format +\fB\fIKEY\fR\fR\fB=\fR\fB\fIVALUE\fR\fR +or as two separate arguments\&. The keys must be namespaced for your organisation or repository using a dot prefix\&. The values must be in GVariant text format\&. For example, +\fBexampleos\&.end\-of\-life "@t 1445385600"\fR\&. .RE .PP \fB\-\-gpg\-sign\fR=KEYID diff --git a/man/ostree-summary.xml b/man/ostree-summary.xml index 3fa287eb..4e897592 100644 --- a/man/ostree-summary.xml +++ b/man/ostree-summary.xml @@ -73,7 +73,16 @@ Boston, MA 02111-1307, USA. - Update the summary file. + Update the summary file. + + Any additional arguments to the command + are treated as additional key–value pairs to be added to the + summary file as additional metadata. They must be in the format + KEY=VALUE + or as two separate arguments. The keys must be namespaced for + your organisation or repository using a dot prefix. The values + must be in GVariant text format. For example, + exampleos.end-of-life "@t 1445385600". diff --git a/man/ostree-trivial-httpd.xml b/man/ostree-trivial-httpd.xml new file mode 100644 index 00000000..2b846858 --- /dev/null +++ b/man/ostree-trivial-httpd.xml @@ -0,0 +1,116 @@ + + + + + + + + + ostree trivial-httpd + OSTree + + + + Developer + Colin + Walters + walters@verbum.org + + + + + + ostree trivial-httpd + 1 + + + + ostree-trivial-httpd + Simple webserver + + + + + ostree trivial-httpd OPTIONS DIR + + + + + + Description + + + This runs a simple webserver and keeps it running until killed. If DIR is not specified, it defaults to the current directory. + + + + + Options + + + + , + + + Fork into background when ready. + + + + + + + + Automatically exit when directory is deleted. + + + + + ,="PATH" + + + Write port number to PATH (- for standard output). + + + + + , + + + Use the specified TCP port to listen on. + + + + + + + + Force range requests by only serving half of files. + + + + + + + + Example + $ ostree trivial-httpd + + diff --git a/man/ostree.1 b/man/ostree.1 index 413926aa..a1bcd31f 100644 --- a/man/ostree.1 +++ b/man/ostree.1 @@ -2,7 +2,7 @@ .\" Title: ostree .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree .\" Source: OSTree .\" Language: English diff --git a/man/ostree.repo-config.5 b/man/ostree.repo-config.5 index 39f0b09b..dd2a9586 100644 --- a/man/ostree.repo-config.5 +++ b/man/ostree.repo-config.5 @@ -2,7 +2,7 @@ .\" Title: ostree.repo-config .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 07/05/2017 .\" Manual: ostree.repo-config .\" Source: OSTree .\" Language: English @@ -136,6 +136,11 @@ Path to file containing trusted anchors instead of the system CA database\&. .RS 4 If set, pulls from this remote will fail with the configured text\&. This is intended for OS vendors which have a subscription process to access content\&. .RE +.PP +\fImin\-free\-space\-percent\fR +.RS 4 +Integer percentage value (0\-99) that specifies a minimum percentage of total space (in blocks) in the underlying filesystem to keep free\&. The default value is 3\&. +.RE .SH "/ETC/OSTREE/REMOTES\&.D" .PP In addition to the diff --git a/man/ostree.repo-config.xml b/man/ostree.repo-config.xml index d187f89f..60458dfa 100644 --- a/man/ostree.repo-config.xml +++ b/man/ostree.repo-config.xml @@ -190,6 +190,14 @@ Boston, MA 02111-1307, USA. unconfigured-state If set, pulls from this remote will fail with the configured text. This is intended for OS vendors which have a subscription process to access content. + + + min-free-space-percent + Integer percentage value (0-99) that specifies a minimum + percentage of total space (in blocks) in the underlying filesystem to + keep free. The default value is 3. + + diff --git a/man/ostree.repo.5 b/man/ostree.repo.5 index ff7ef223..dc94c16f 100644 --- a/man/ostree.repo.5 +++ b/man/ostree.repo.5 @@ -2,7 +2,7 @@ .\" Title: ostree.repo .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: ostree.repo .\" Source: OSTree .\" Language: English diff --git a/man/rofiles-fuse.1 b/man/rofiles-fuse.1 index 1b498f70..1b545f3f 100644 --- a/man/rofiles-fuse.1 +++ b/man/rofiles-fuse.1 @@ -2,7 +2,7 @@ .\" Title: rofiles-fuse .\" Author: Colin Walters .\" Generator: DocBook XSL Stylesheets v1.79.1 -.\" Date: 06/19/2017 +.\" Date: 06/29/2017 .\" Manual: rofiles-fuse .\" Source: rofiles-fuse .\" Language: English diff --git a/src/libostree/libostree-released.sym b/src/libostree/libostree-released.sym index 5fc8a9b1..b68d6a5e 100644 --- a/src/libostree/libostree-released.sym +++ b/src/libostree/libostree-released.sym @@ -408,6 +408,11 @@ global: ostree_sysroot_query_deployments_for; } LIBOSTREE_2017.6; +LIBOSTREE_2017.8 { +global: + ostree_validate_remote_name; +} LIBOSTREE_2017.7; + /* NOTE: Only add more content here in release commits! See the * comments at the top of this file. */ diff --git a/src/libostree/ostree-autocleanups.h b/src/libostree/ostree-autocleanups.h index 1fdb50ad..b375413c 100644 --- a/src/libostree/ostree-autocleanups.h +++ b/src/libostree/ostree-autocleanups.h @@ -60,7 +60,15 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeSysrootUpgrader, g_object_unref) G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (OstreeRepoCommitTraverseIter, ostree_repo_commit_traverse_iter_clear) #ifdef OSTREE_ENABLE_EXPERIMENTAL_API +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeCollectionRef, ostree_collection_ref_free) +G_DEFINE_AUTO_CLEANUP_FREE_FUNC (OstreeCollectionRefv, ostree_collection_ref_freev, NULL) G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRemote, ostree_remote_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinder, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderAvahi, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderConfig, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderMount, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderResult, ostree_repo_finder_result_free) +G_DEFINE_AUTO_CLEANUP_FREE_FUNC (OstreeRepoFinderResultv, ostree_repo_finder_result_freev, NULL) #endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ #endif diff --git a/src/libostree/ostree-bloom-private.h b/src/libostree/ostree-bloom-private.h new file mode 100644 index 00000000..47f828b3 --- /dev/null +++ b/src/libostree/ostree-bloom-private.h @@ -0,0 +1,104 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +/** + * OstreeBloom: + * + * An implementation of a [bloom filter](https://en.wikipedia.org/wiki/Bloom_filter) + * which is suitable for building a filter and looking keys up in an existing + * filter. + * + * Since: 2017.8 + */ +typedef struct _OstreeBloom OstreeBloom; + +/** + * OstreeBloomHashFunc: + * @element: a pointer to the element to hash + * @k: hash function parameter + * + * Function prototype for a + * [universal hash function](https://en.wikipedia.org/wiki/Universal_hashing), + * parameterised on @k, which hashes @element to a #guint64 hash value. + * + * It is up to the implementer of the hash function whether %NULL is valid for + * @element. + * + * Since: 2017.8 + */ +typedef guint64 (*OstreeBloomHashFunc) (gconstpointer element, + guint8 k); + +#define OSTREE_TYPE_BLOOM (ostree_bloom_get_type ()) + +G_GNUC_INTERNAL +GType ostree_bloom_get_type (void); + +G_GNUC_INTERNAL +OstreeBloom *ostree_bloom_new (gsize n_bytes, + guint8 k, + OstreeBloomHashFunc hash_func); + +G_GNUC_INTERNAL +OstreeBloom *ostree_bloom_new_from_bytes (GBytes *bytes, + guint8 k, + OstreeBloomHashFunc hash_func); + +G_GNUC_INTERNAL +OstreeBloom *ostree_bloom_ref (OstreeBloom *bloom); +G_GNUC_INTERNAL +void ostree_bloom_unref (OstreeBloom *bloom); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeBloom, ostree_bloom_unref) + +G_GNUC_INTERNAL +gboolean ostree_bloom_maybe_contains (OstreeBloom *bloom, + gconstpointer element); + +G_GNUC_INTERNAL +GBytes *ostree_bloom_seal (OstreeBloom *bloom); + +G_GNUC_INTERNAL +void ostree_bloom_add_element (OstreeBloom *bloom, + gconstpointer element); + +G_GNUC_INTERNAL +gsize ostree_bloom_get_size (OstreeBloom *bloom); +G_GNUC_INTERNAL +guint8 ostree_bloom_get_k (OstreeBloom *bloom); +G_GNUC_INTERNAL +OstreeBloomHashFunc ostree_bloom_get_hash_func (OstreeBloom *bloom); + +G_GNUC_INTERNAL +guint64 ostree_str_bloom_hash (gconstpointer element, + guint8 k); + +G_END_DECLS diff --git a/src/libostree/ostree-bloom.c b/src/libostree/ostree-bloom.c new file mode 100644 index 00000000..7677b3ba --- /dev/null +++ b/src/libostree/ostree-bloom.c @@ -0,0 +1,603 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "ostree-bloom-private.h" + +/** + * SECTION:bloom + * @title: Bloom filter + * @short_description: Bloom filter implementation supporting building and + * reading filters + * @stability: Unstable + * @include: libostree/ostree-bloom-private.h + * + * #OstreeBloom is an implementation of a bloom filter which supports writing to + * and loading from a #GBytes bit array. The caller must store metadata about + * the bloom filter (its hash function and `k` parameter value) separately, as + * the same values must be used when reading from a serialised bit array as were + * used to build the array in the first place. + * + * This is a standard implementation of a bloom filter, and background reading + * on the theory can be + * [found on Wikipedia](https://en.wikipedia.org/wiki/Bloom_filter). In + * particular, a bloom filter is parameterised by `m` and `k` parameters: the + * size of the bit array (in bits) is `m`, and the number of hash functions + * applied to each element is `k`. Bloom filters require a universal hash + * function which can be parameterised by `k`. We have #OstreeBloomHashFunc, + * with ostree_str_bloom_hash() being an implementation for strings. + * + * The serialised output from a bloom filter is guaranteed to be stable across + * versions of libostree as long as the same values for `k` and the hash + * function are used. + * + * #OstreeBloom is mutable when constructed with ostree_bloom_new(), and elements + * can be added to it using ostree_bloom_add_element(), until ostree_bloom_seal() + * is called to serialise it and make it immutable. After then, the bloom filter + * can only be queried using ostree_bloom_maybe_contains(). + * + * If constructed with ostree_bloom_new_from_bytes(), the bloom filter is + * immutable from construction, and can only be queried. + * + * Reference: + * - https://en.wikipedia.org/wiki/Bloom_filter + * - https://llimllib.github.io/bloomfilter-tutorial/ + * + * Since: 2017.8 + */ + +struct _OstreeBloom +{ + guint ref_count; + gsize n_bytes; + gboolean is_mutable; /* determines which of [im]mutable_bytes is accessed */ + union + { + guint8 *mutable_bytes; /* owned; mutually exclusive */ + GBytes *immutable_bytes; /* owned; mutually exclusive */ + }; + guint8 k; + OstreeBloomHashFunc hash_func; +}; + +G_DEFINE_BOXED_TYPE (OstreeBloom, ostree_bloom, ostree_bloom_ref, ostree_bloom_unref) + +/** + * ostree_bloom_new: + * @n_bytes: size to make the bloom filter, in bytes + * @k: number of hash functions to use + * @hash_func: universal hash function to use + * + * Create a new mutable #OstreeBloom filter, with all its bits initialised to + * zero. Set elements in the filter using ostree_bloom_add_element(), and seal + * it to return an immutable #GBytes using ostree_bloom_seal(). + * + * To load an #OstreeBloom from an existing #GBytes, use + * ostree_bloom_new_from_bytes(). + * + * Note that @n_bytes is in bytes, so is 8 times smaller than the parameter `m` + * which is used when describing bloom filters academically. + * + * Returns: (transfer full): a new mutable bloom filter + * + * Since: 2017.8 + */ +OstreeBloom * +ostree_bloom_new (gsize n_bytes, + guint8 k, + OstreeBloomHashFunc hash_func) +{ + g_autoptr(OstreeBloom) bloom = NULL; + + g_return_val_if_fail (n_bytes > 0, NULL); + g_return_val_if_fail (k > 0, NULL); + g_return_val_if_fail (hash_func != NULL, NULL); + + bloom = g_new0 (OstreeBloom, 1); + bloom->ref_count = 1; + + bloom->is_mutable = TRUE; + bloom->mutable_bytes = g_malloc0 (n_bytes); + bloom->n_bytes = n_bytes; + bloom->k = k; + bloom->hash_func = hash_func; + + return g_steal_pointer (&bloom); +} + +/** + * ostree_bloom_new_from_bytes: + * @bytes: array of bytes containing the filter data + * @k: number of hash functions to use + * @hash_func: universal hash function to use + * + * Load an immutable #OstreeBloom filter from the given @bytes. Check whether + * elements are probably set in the filter using ostree_bloom_maybe_contains(). + * + * To create a new mutable #OstreeBloom, use ostree_bloom_new(). + * + * Note that all the bits in @bytes are loaded, so the parameter `m` for the + * filter (as commonly used in academic literature) is always a multiple of 8. + * + * Returns: (transfer full): a new immutable bloom filter + * + * Since: 2017.8 + */ +OstreeBloom * +ostree_bloom_new_from_bytes (GBytes *bytes, + guint8 k, + OstreeBloomHashFunc hash_func) +{ + g_autoptr(OstreeBloom) bloom = NULL; + + g_return_val_if_fail (bytes != NULL, NULL); + g_return_val_if_fail (g_bytes_get_size (bytes) > 0, NULL); + g_return_val_if_fail (k > 0, NULL); + g_return_val_if_fail (hash_func != NULL, NULL); + + bloom = g_new0 (OstreeBloom, 1); + bloom->ref_count = 1; + + bloom->is_mutable = FALSE; + bloom->immutable_bytes = g_bytes_ref (bytes); + bloom->n_bytes = g_bytes_get_size (bytes); + bloom->k = k; + bloom->hash_func = hash_func; + + return g_steal_pointer (&bloom); +} + +/** + * ostree_bloom_ref: + * @bloom: an #OstreeBloom + * + * Increase the reference count of @bloom. + * + * Returns: (transfer full): @bloom + * Since: 2017.8 + */ +OstreeBloom * +ostree_bloom_ref (OstreeBloom *bloom) +{ + g_return_val_if_fail (bloom != NULL, NULL); + g_return_val_if_fail (bloom->ref_count >= 1, NULL); + g_return_val_if_fail (bloom->ref_count == G_MAXUINT - 1, NULL); + + bloom->ref_count++; + + return bloom; +} + +/** + * ostree_bloom_unref: + * @bloom: (transfer full): an #OstreeBloom + * + * Decrement the reference count of @bloom. If it reaches zero, the filter + * is destroyed. + * + * Since: 2017.8 + */ +void +ostree_bloom_unref (OstreeBloom *bloom) +{ + g_return_if_fail (bloom != NULL); + g_return_if_fail (bloom->ref_count >= 1); + + bloom->ref_count--; + + if (bloom->ref_count == 0) + { + if (bloom->is_mutable) + g_clear_pointer (&bloom->mutable_bytes, g_free); + else + g_clear_pointer (&bloom->immutable_bytes, g_bytes_unref); + bloom->n_bytes = 0; + g_free (bloom); + } +} + +/* @idx is in bits, not bytes. */ +static inline gboolean +ostree_bloom_get_bit (OstreeBloom *bloom, + gsize idx) +{ + const guint8 *bytes; + + if (bloom->is_mutable) + bytes = bloom->mutable_bytes; + else + bytes = g_bytes_get_data (bloom->immutable_bytes, NULL); + + g_assert (idx / 8 < bloom->n_bytes); + return (bytes[idx / 8] & (1 << (idx % 8))); +} + +/* @idx is in bits, not bytes. */ +static inline void +ostree_bloom_set_bit (OstreeBloom *bloom, + gsize idx) +{ + g_assert (bloom->is_mutable); + g_assert (idx / 8 < bloom->n_bytes); + bloom->mutable_bytes[idx / 8] |= (1 << (idx % 8)); +} + +/** + * ostree_bloom_maybe_contains: + * @bloom: an #OstreeBloom + * @element: (nullable): element to check for membership + * + * Check whether @element is potentially in @bloom, or whether it definitely + * isn’t. @element may be %NULL only if the hash function passed to @bloom at + * construction time supports %NULL elements. + * + * Returns: %TRUE if @element is potentially in @bloom; %FALSE if it definitely + * isn’t + * Since: 2017.8 + */ +gboolean +ostree_bloom_maybe_contains (OstreeBloom *bloom, + gconstpointer element) +{ + guint8 i; + + g_return_val_if_fail (bloom != NULL, TRUE); + g_return_val_if_fail (bloom->ref_count >= 1, TRUE); + + for (i = 0; i < bloom->k; i++) + { + gsize idx; + + idx = bloom->hash_func (element, i); + + if (!ostree_bloom_get_bit (bloom, idx % (bloom->n_bytes * 8))) + return FALSE; /* definitely not in the set */ + } + + return TRUE; /* possibly in the set */ +} + +/** + * ostree_bloom_seal: + * @bloom: an #OstreeBloom + * + * Seal a constructed bloom filter, so that elements may no longer be added to + * it, and queries can now be performed against it. The serialised form of the + * bloom filter is returned as a bit array. Note that this does not include + * information about the filter hash function or parameters; the caller is + * responsible for serialising those separately if appropriate. + * + * It is safe to call this function multiple times. + * + * Returns: (transfer full): a #GBytes containing the immutable filter data + * Since: 2017.8 + */ +GBytes * +ostree_bloom_seal (OstreeBloom *bloom) +{ + g_return_val_if_fail (bloom != NULL, NULL); + g_return_val_if_fail (bloom->ref_count >= 1, NULL); + + if (bloom->is_mutable) + { + bloom->is_mutable = FALSE; + bloom->immutable_bytes = g_bytes_new_take (g_steal_pointer (&bloom->mutable_bytes), bloom->n_bytes); + } + + return g_bytes_ref (bloom->immutable_bytes); +} + +/** + * ostree_bloom_add_element: + * @bloom: an #OstreeBloom + * @element: (nullable): element to add to the filter + * + * Add the given @element to the bloom filter, which must not yet have been + * sealed (ostree_bloom_seal()). @element may be %NULL if the hash function + * passed to @bloom at construction time supports %NULL elements. + * + * Since: 2017.8 + */ +void +ostree_bloom_add_element (OstreeBloom *bloom, + gconstpointer element) +{ + guint8 i; + + g_return_if_fail (bloom != NULL); + g_return_if_fail (bloom->ref_count >= 1); + g_return_if_fail (bloom->is_mutable); + + for (i = 0; i < bloom->k; i++) + { + gsize idx = bloom->hash_func (element, i); + ostree_bloom_set_bit (bloom, idx % (bloom->n_bytes * 8)); + } +} + +/** + * ostree_bloom_get_size: + * @bloom: an #OstreeBloom + * + * Get the size of the #OstreeBloom filter, in bytes, as configured at + * construction time. + * + * Returns: the bloom filter’s size in bytes, guaranteed to be >0 + * Since: 2017.8 + */ +gsize +ostree_bloom_get_size (OstreeBloom *bloom) +{ + g_return_val_if_fail (bloom != NULL, 0); + + return bloom->n_bytes; +} + +/** + * ostree_bloom_get_k: + * @bloom: an #OstreeBloom + * + * Get the `k` value from the #OstreeBloom filter, as configured at + * construction time. + * + * Returns: the bloom filter’s `k` value, guaranteed to be >0 + * Since: 2017.8 + */ +guint8 +ostree_bloom_get_k (OstreeBloom *bloom) +{ + g_return_val_if_fail (bloom != NULL, 0); + + return bloom->k; +} + +/** + * ostree_bloom_get_hash_func: + * @bloom: an #OstreeBloom + * + * Get the #OstreeBloomHashFunc from the #OstreeBloom filter, as configured at + * construction time. + * + * Returns: the bloom filter’s universal hash function + * Since: 2017.8 + */ +OstreeBloomHashFunc +ostree_bloom_get_hash_func (OstreeBloom *bloom) +{ + g_return_val_if_fail (bloom != NULL, NULL); + + return bloom->hash_func; +} + +/* SipHash code adapted from https://github.com/veorq/SipHash/blob/master/siphash.c */ + +/* + SipHash reference C implementation + Copyright (c) 2012-2016 Jean-Philippe Aumasson + + Copyright (c) 2012-2014 Daniel J. Bernstein + To the extent possible under law, the author(s) have dedicated all copyright + and related and neighboring rights to this software to the public domain + worldwide. This software is distributed without any warranty. + You should have received a copy of the CC0 Public Domain Dedication along + with + this software. If not, see + . + */ + +/* default: SipHash-2-4 */ +#define cROUNDS 2 +#define dROUNDS 4 + +#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b)))) + +#define U32TO8_LE(p, v) \ + (p)[0] = (uint8_t)((v)); \ + (p)[1] = (uint8_t)((v) >> 8); \ + (p)[2] = (uint8_t)((v) >> 16); \ + (p)[3] = (uint8_t)((v) >> 24); + +#define U64TO8_LE(p, v) \ + U32TO8_LE((p), (uint32_t)((v))); \ + U32TO8_LE((p) + 4, (uint32_t)((v) >> 32)); + +#define U8TO64_LE(p) \ + (((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) | \ + ((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) | \ + ((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) | \ + ((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56)) + +#define SIPROUND \ + do { \ + v0 += v1; \ + v1 = ROTL(v1, 13); \ + v1 ^= v0; \ + v0 = ROTL(v0, 32); \ + v2 += v3; \ + v3 = ROTL(v3, 16); \ + v3 ^= v2; \ + v0 += v3; \ + v3 = ROTL(v3, 21); \ + v3 ^= v0; \ + v2 += v1; \ + v1 = ROTL(v1, 17); \ + v1 ^= v2; \ + v2 = ROTL(v2, 32); \ + } while (0) + +#ifdef DEBUG +#define TRACE \ + do { \ + printf("(%3d) v0 %08x %08x\n", (int)inlen, (uint32_t)(v0 >> 32), \ + (uint32_t)v0); \ + printf("(%3d) v1 %08x %08x\n", (int)inlen, (uint32_t)(v1 >> 32), \ + (uint32_t)v1); \ + printf("(%3d) v2 %08x %08x\n", (int)inlen, (uint32_t)(v2 >> 32), \ + (uint32_t)v2); \ + printf("(%3d) v3 %08x %08x\n", (int)inlen, (uint32_t)(v3 >> 32), \ + (uint32_t)v3); \ + } while (0) +#else +#define TRACE +#endif + +static int siphash(const uint8_t *in, const size_t inlen, const uint8_t *k, + uint8_t *out, const size_t outlen) { + + assert((outlen == 8) || (outlen == 16)); + uint64_t v0 = 0x736f6d6570736575ULL; + uint64_t v1 = 0x646f72616e646f6dULL; + uint64_t v2 = 0x6c7967656e657261ULL; + uint64_t v3 = 0x7465646279746573ULL; + uint64_t k0 = U8TO64_LE(k); + uint64_t k1 = U8TO64_LE(k + 8); + uint64_t m; + int i; + const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t)); + const int left = inlen & 7; + uint64_t b = ((uint64_t)inlen) << 56; + v3 ^= k1; + v2 ^= k0; + v1 ^= k1; + v0 ^= k0; + + if (outlen == 16) + v1 ^= 0xee; + + for (; in != end; in += 8) { + m = U8TO64_LE(in); + v3 ^= m; + + TRACE; + for (i = 0; i < cROUNDS; ++i) + SIPROUND; + + v0 ^= m; + } + + switch (left) { + case 7: + b |= ((uint64_t)in[6]) << 48; + case 6: + b |= ((uint64_t)in[5]) << 40; + case 5: + b |= ((uint64_t)in[4]) << 32; + case 4: + b |= ((uint64_t)in[3]) << 24; + case 3: + b |= ((uint64_t)in[2]) << 16; + case 2: + b |= ((uint64_t)in[1]) << 8; + case 1: + b |= ((uint64_t)in[0]); + break; + case 0: + break; + } + + v3 ^= b; + + TRACE; + for (i = 0; i < cROUNDS; ++i) + SIPROUND; + + v0 ^= b; + + if (outlen == 16) + v2 ^= 0xee; + else + v2 ^= 0xff; + + TRACE; + for (i = 0; i < dROUNDS; ++i) + SIPROUND; + + b = v0 ^ v1 ^ v2 ^ v3; + U64TO8_LE(out, b); + + if (outlen == 8) + return 0; + + v1 ^= 0xdd; + + TRACE; + for (i = 0; i < dROUNDS; ++i) + SIPROUND; + + b = v0 ^ v1 ^ v2 ^ v3; + U64TO8_LE(out + 8, b); + + return 0; +} + +/* End SipHash copied code. */ + +/** + * ostree_str_bloom_hash: + * @element: element to calculate the hash for + * @k: hash function index + * + * A universal hash function implementation for strings. It expects @element to + * be a pointer to a string (i.e. @element has type `const gchar*`), and expects + * @k to be in the range `[0, k_max)`, where `k_max` is the `k` value used to + * construct the bloom filter. The output range from this hash function could be + * any value in #guint64, and it handles input strings of any length. + * + * This function does not allow %NULL as a valid value for @element. + * + * Reference: + * - https://www.131002.net/siphash/ + * + * Returns: hash of the string at @element using parameter @k + * Since: 2017.8 + */ +guint64 +ostree_str_bloom_hash (gconstpointer element, + guint8 k) +{ + const gchar *str = element; + gsize str_len; + union + { + guint64 u64; + guint8 u8[8]; + } out_le; + guint8 k_array[16]; + gsize i; + + str_len = strlen (str); + for (i = 0; i < G_N_ELEMENTS (k_array); i++) + k_array[i] = k; + + siphash ((const guint8 *) str, str_len, k_array, out_le.u8, sizeof (out_le)); + + return le64toh (out_le.u64); +} diff --git a/src/libostree/ostree-bootconfig-parser.c b/src/libostree/ostree-bootconfig-parser.c index a7c473dd..9de0e56b 100644 --- a/src/libostree/ostree-bootconfig-parser.c +++ b/src/libostree/ostree-bootconfig-parser.c @@ -48,15 +48,11 @@ OstreeBootconfigParser * ostree_bootconfig_parser_clone (OstreeBootconfigParser *self) { OstreeBootconfigParser *parser = ostree_bootconfig_parser_new (); - guint i; - GHashTableIter hashiter; - gpointer k, v; - for (i = 0; i < self->lines->len; i++) + for (guint i = 0; i < self->lines->len; i++) g_ptr_array_add (parser->lines, g_variant_ref (self->lines->pdata[i])); - g_hash_table_iter_init (&hashiter, self->options); - while (g_hash_table_iter_next (&hashiter, &k, &v)) + GLNX_HASH_TABLE_FOREACH_KV (self->options, const char*, k, const char*, v) g_hash_table_replace (parser->options, g_strdup (k), g_strdup (v)); return parser; @@ -183,14 +179,11 @@ ostree_bootconfig_parser_write_at (OstreeBootconfigParser *self, } } - GHashTableIter hashiter; - gpointer hashkey, hashvalue; - g_hash_table_iter_init (&hashiter, self->options); - while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue)) + GLNX_HASH_TABLE_FOREACH_KV (self->options, const char*, k, const char*, v) { - if (g_hash_table_lookup (written_overrides, hashkey)) + if (g_hash_table_lookup (written_overrides, k)) continue; - write_key (self, buf, hashkey, hashvalue); + write_key (self, buf, k, v); } if (!glnx_file_replace_contents_at (dfd, path, (guint8*)buf->str, buf->len, diff --git a/src/libostree/ostree-core-private.h b/src/libostree/ostree-core-private.h index 72b88aba..799bd228 100644 --- a/src/libostree/ostree-core-private.h +++ b/src/libostree/ostree-core-private.h @@ -21,6 +21,7 @@ #pragma once #include "ostree-core.h" +#include G_BEGIN_DECLS @@ -88,7 +89,8 @@ _ostree_make_temporary_symlink_at (int tmp_dirfd, GCancellable *cancellable, GError **error); -GFileInfo * _ostree_header_gfile_info_new (mode_t mode, uid_t uid, gid_t gid); +GFileInfo * _ostree_stbuf_to_gfileinfo (const struct stat *stbuf); +GFileInfo * _ostree_mode_uidgid_to_gfileinfo (mode_t mode, uid_t uid, gid_t gid); static inline void _ostree_checksum_inplace_from_bytes_v (GVariant *csum_v, char *buf) @@ -173,6 +175,29 @@ _ostree_raw_file_to_archive_stream (GInputStream *input, GCancellable *cancellable, GError **error); +#ifndef OSTREE_ENABLE_EXPERIMENTAL_API +gboolean ostree_validate_collection_id (const char *collection_id, GError **error); +#endif /* !OSTREE_ENABLE_EXPERIMENTAL_API */ +#if (defined(OSTREE_COMPILATION) || GLIB_CHECK_VERSION(2, 44, 0)) && !defined(OSTREE_ENABLE_EXPERIMENTAL_API) +#include +#include "ostree-ref.h" +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeCollectionRef, ostree_collection_ref_free) +G_DEFINE_AUTO_CLEANUP_FREE_FUNC (OstreeCollectionRefv, ostree_collection_ref_freev, NULL) + +#include "ostree-repo-finder.h" +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinder, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderResult, ostree_repo_finder_result_free) +G_DEFINE_AUTO_CLEANUP_FREE_FUNC (OstreeRepoFinderResultv, ostree_repo_finder_result_freev, NULL) + +#include "ostree-repo-finder-avahi.h" +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderAvahi, g_object_unref) + +#include "ostree-repo-finder-config.h" +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderConfig, g_object_unref) + +#include "ostree-repo-finder-mount.h" +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFinderMount, g_object_unref) +#endif G_END_DECLS diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c index 54e01bcb..6442bd52 100644 --- a/src/libostree/ostree-core.c +++ b/src/libostree/ostree-core.c @@ -101,6 +101,7 @@ ostree_validate_checksum_string (const char *sha256, #define OSTREE_REF_FRAGMENT_REGEXP "[-._\\w\\d]+" #define OSTREE_REF_REGEXP "(?:" OSTREE_REF_FRAGMENT_REGEXP "/)*" OSTREE_REF_FRAGMENT_REGEXP +#define OSTREE_REMOTE_NAME_REGEXP OSTREE_REF_FRAGMENT_REGEXP /** * ostree_parse_refspec: @@ -125,7 +126,7 @@ ostree_parse_refspec (const char *refspec, static gsize regex_initialized; if (g_once_init_enter (®ex_initialized)) { - regex = g_regex_new ("^(" OSTREE_REF_FRAGMENT_REGEXP ":)?(" OSTREE_REF_REGEXP ")$", 0, 0, NULL); + regex = g_regex_new ("^(" OSTREE_REMOTE_NAME_REGEXP ":)?(" OSTREE_REF_REGEXP ")$", 0, 0, NULL); g_assert (regex); g_once_init_leave (®ex_initialized, 1); } @@ -180,6 +181,67 @@ ostree_validate_rev (const char *rev, return TRUE; } +/** + * ostree_validate_remote_name: + * @remote_name: A remote name + * @error: Error + * + * Returns: %TRUE if @remote_name is a valid remote name + * Since: 2017.8 + */ +gboolean +ostree_validate_remote_name (const char *remote_name, + GError **error) +{ + g_autoptr(GMatchInfo) match = NULL; + + static gsize regex_initialized; + static GRegex *regex; + if (g_once_init_enter (®ex_initialized)) + { + regex = g_regex_new ("^" OSTREE_REMOTE_NAME_REGEXP "$", 0, 0, NULL); + g_assert (regex); + g_once_init_leave (®ex_initialized, 1); + } + + if (!g_regex_match (regex, remote_name, 0, &match)) + return glnx_throw (error, "Invalid remote name %s", remote_name); + + return TRUE; +} + +/** + * ostree_validate_collection_id: + * @rev: (nullable): A collection ID + * @error: Error + * + * Check whether the given @collection_id is valid. Return an error if it is + * invalid or %NULL. + * + * Valid collection IDs are reverse DNS names: + * * They are composed of 1 or more elements separated by a period (`.`) character. + * All elements must contain at least one character. + * * Each element must only contain the ASCII characters `[A-Z][a-z][0-9]_` and must not + * begin with a digit. + * * They must contain at least one `.` (period) character (and thus at least two elements). + * * They must not begin with a `.` (period) character. + * * They must not exceed 255 characters in length. + * + * (This makes their format identical to D-Bus interface names, for consistency.) + * + * Returns: %TRUE if @collection_id is a valid collection ID, %FALSE if it is invalid + * or %NULL + */ +gboolean +ostree_validate_collection_id (const char *collection_id, GError **error) +{ + /* Abuse g_dbus_is_interface_name(), since collection IDs have the same format. */ + if (collection_id == NULL || !g_dbus_is_interface_name (collection_id)) + return glnx_throw (error, "Invalid collection ID %s", collection_id); + + return TRUE; +} + GVariant * _ostree_file_header_new (GFileInfo *file_info, GVariant *xattrs) @@ -614,7 +676,7 @@ ostree_content_stream_parse (gboolean compressed, if (compressed) { if (!zlib_file_header_parse (file_header, - out_file_info ? &ret_file_info : NULL, + &ret_file_info, out_xattrs ? &ret_xattrs : NULL, error)) return FALSE; @@ -622,12 +684,11 @@ ostree_content_stream_parse (gboolean compressed, else { if (!file_header_parse (file_header, - out_file_info ? &ret_file_info : NULL, + &ret_file_info, out_xattrs ? &ret_xattrs : NULL, error)) return FALSE; - if (ret_file_info) - g_file_info_set_size (ret_file_info, input_length - archive_header_size - 8); + g_file_info_set_size (ret_file_info, input_length - archive_header_size - 8); } g_autoptr(GInputStream) ret_input = NULL; @@ -1432,7 +1493,7 @@ _ostree_loose_path (char *buf, } /** - * _ostree_header_gfile_info_new: + * _ostree_stbuf_to_gfileinfo: * @mode: File mode * @uid: File uid * @gid: File gid @@ -1445,17 +1506,42 @@ _ostree_loose_path (char *buf, * Returns: (transfer full): A new #GFileInfo mapping a subset of @stbuf. */ GFileInfo * -_ostree_header_gfile_info_new (mode_t mode, uid_t uid, gid_t gid) +_ostree_stbuf_to_gfileinfo (const struct stat *stbuf) { GFileInfo *ret = g_file_info_new (); - g_file_info_set_attribute_uint32 (ret, "standard::type", ot_gfile_type_for_mode (mode)); + GFileType ftype; + const mode_t mode = stbuf->st_mode; + if (S_ISDIR (mode)) + ftype = G_FILE_TYPE_DIRECTORY; + else if (S_ISREG (mode)) + ftype = G_FILE_TYPE_REGULAR; + else if (S_ISLNK (mode)) + ftype = G_FILE_TYPE_SYMBOLIC_LINK; + else if (S_ISBLK (mode) || S_ISCHR(mode) || S_ISFIFO(mode)) + ftype = G_FILE_TYPE_SPECIAL; + else + ftype = G_FILE_TYPE_UNKNOWN; + g_file_info_set_attribute_uint32 (ret, "standard::type", ftype); g_file_info_set_attribute_boolean (ret, "standard::is-symlink", S_ISLNK (mode)); - g_file_info_set_attribute_uint32 (ret, "unix::uid", uid); - g_file_info_set_attribute_uint32 (ret, "unix::gid", gid); + g_file_info_set_attribute_uint32 (ret, "unix::uid", stbuf->st_uid); + g_file_info_set_attribute_uint32 (ret, "unix::gid", stbuf->st_gid); g_file_info_set_attribute_uint32 (ret, "unix::mode", mode); + if (S_ISREG (mode)) + g_file_info_set_attribute_uint64 (ret, "standard::size", stbuf->st_size); + return ret; } +GFileInfo * +_ostree_mode_uidgid_to_gfileinfo (mode_t mode, uid_t uid, gid_t gid) +{ + struct stat stbuf; + stbuf.st_mode = mode; + stbuf.st_uid = uid; + stbuf.st_gid = gid; + return _ostree_stbuf_to_gfileinfo (&stbuf); +} + /* * _ostree_get_relative_object_path: * @checksum: ASCII checksum string @@ -1619,8 +1705,7 @@ file_header_parse (GVariant *metadata, uid = GUINT32_FROM_BE (uid); gid = GUINT32_FROM_BE (gid); mode = GUINT32_FROM_BE (mode); - - g_autoptr(GFileInfo) ret_file_info = _ostree_header_gfile_info_new (mode, uid, gid); + g_autoptr(GFileInfo) ret_file_info = _ostree_mode_uidgid_to_gfileinfo (mode, uid, gid); if (S_ISREG (mode)) { @@ -1670,7 +1755,7 @@ zlib_file_header_parse (GVariant *metadata, uid = GUINT32_FROM_BE (uid); gid = GUINT32_FROM_BE (gid); mode = GUINT32_FROM_BE (mode); - g_autoptr(GFileInfo) ret_file_info = _ostree_header_gfile_info_new (mode, uid, gid); + g_autoptr(GFileInfo) ret_file_info = _ostree_mode_uidgid_to_gfileinfo (mode, uid, gid); g_file_info_set_size (ret_file_info, GUINT64_FROM_BE (size)); if (S_ISREG (mode)) diff --git a/src/libostree/ostree-core.h b/src/libostree/ostree-core.h index c1e014e2..fa9e5e86 100644 --- a/src/libostree/ostree-core.h +++ b/src/libostree/ostree-core.h @@ -242,6 +242,14 @@ int ostree_cmp_checksum_bytes (const guchar *a, const guchar *b); _OSTREE_PUBLIC gboolean ostree_validate_rev (const char *rev, GError **error); +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API +_OSTREE_PUBLIC +gboolean ostree_validate_collection_id (const char *collection_id, GError **error); +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ + +_OSTREE_PUBLIC +gboolean ostree_validate_remote_name (const char *remote_name, GError **error); + _OSTREE_PUBLIC gboolean ostree_parse_refspec (const char *refspec, char **out_remote, diff --git a/src/libostree/ostree-fetcher-curl.c b/src/libostree/ostree-fetcher-curl.c index f483a6bb..77844ec7 100644 --- a/src/libostree/ostree-fetcher-curl.c +++ b/src/libostree/ostree-fetcher-curl.c @@ -99,7 +99,7 @@ struct FetcherRequest { OstreeFetcherRequestFlags flags; gboolean is_membuf; GError *caught_write_error; - OtTmpfile tmpf; + GLnxTmpfile tmpf; GString *output_buf; CURL *easy; @@ -270,9 +270,9 @@ ensure_tmpfile (FetcherRequest *req, GError **error) { if (!req->tmpf.initialized) { - if (!ot_open_tmpfile_linkable_at (req->fetcher->tmpdir_dfd, ".", - O_WRONLY | O_CLOEXEC, &req->tmpf, - error)) + if (!glnx_open_tmpfile_linkable_at (req->fetcher->tmpdir_dfd, ".", + O_WRONLY | O_CLOEXEC, &req->tmpf, + error)) return FALSE; } return TRUE; @@ -390,9 +390,9 @@ check_multi_info (OstreeFetcher *fetcher) glnx_set_error_from_errno (error); g_task_return_error (task, g_steal_pointer (&local_error)); } - else if (!ot_link_tmpfile_at (&req->tmpf, GLNX_LINK_TMPFILE_REPLACE, - fetcher->tmpdir_dfd, tmpfile_path, - error)) + else if (!glnx_link_tmpfile_at (&req->tmpf, GLNX_LINK_TMPFILE_REPLACE, + fetcher->tmpdir_dfd, tmpfile_path, + error)) g_task_return_error (task, g_steal_pointer (&local_error)); else { @@ -616,7 +616,7 @@ request_unref (FetcherRequest *req) g_ptr_array_unref (req->mirrorlist); g_free (req->filename); g_clear_error (&req->caught_write_error); - ot_tmpfile_clear (&req->tmpf); + glnx_tmpfile_clear (&req->tmpf); if (req->output_buf) g_string_free (req->output_buf, TRUE); curl_easy_cleanup (req->easy); @@ -690,9 +690,6 @@ static void adopt_steal_mainctx (OstreeFetcher *self, GMainContext *mainctx) { - GHashTableIter hiter; - gpointer key, value; - g_assert (self->mainctx == NULL); self->mainctx = mainctx; /* Transfer */ @@ -706,12 +703,8 @@ adopt_steal_mainctx (OstreeFetcher *self, update_timeout_cb (self->multi, timeout_micros / 1000, self); } - g_hash_table_iter_init (&hiter, self->sockets); - while (g_hash_table_iter_next (&hiter, &key, &value)) - { - SockInfo *fdp = key; - setsock (fdp, fdp->sockfd, fdp->action, self); - } + GLNX_HASH_TABLE_FOREACH (self->sockets, SockInfo*, fdp) + setsock (fdp, fdp->sockfd, fdp->action, self); } static void diff --git a/src/libostree/ostree-fetcher-soup.c b/src/libostree/ostree-fetcher-soup.c index 1ca2e771..b877d27c 100644 --- a/src/libostree/ostree-fetcher-soup.c +++ b/src/libostree/ostree-fetcher-soup.c @@ -1323,24 +1323,18 @@ _ostree_fetcher_request_to_membuf_finish (OstreeFetcher *self, guint64 _ostree_fetcher_bytes_transferred (OstreeFetcher *self) { - GHashTableIter hiter; - gpointer key, value; - guint64 ret; - g_return_val_if_fail (OSTREE_IS_FETCHER (self), 0); g_mutex_lock (&self->thread_closure->output_stream_set_lock); - ret = self->thread_closure->total_downloaded; + guint64 ret = self->thread_closure->total_downloaded; - g_hash_table_iter_init (&hiter, self->thread_closure->output_stream_set); - while (g_hash_table_iter_next (&hiter, &key, &value)) + GLNX_HASH_TABLE_FOREACH (self->thread_closure->output_stream_set, + GFileOutputStream*, stream) { - GFileOutputStream *stream = key; - struct stat stbuf; - if (G_IS_FILE_DESCRIPTOR_BASED (stream)) { + struct stat stbuf; if (glnx_stream_fstat ((GFileDescriptorBased*)stream, &stbuf, NULL)) ret += stbuf.st_size; } diff --git a/src/libostree/ostree-impl-system-generator.c b/src/libostree/ostree-impl-system-generator.c index 72f52bc5..5630348c 100644 --- a/src/libostree/ostree-impl-system-generator.c +++ b/src/libostree/ostree-impl-system-generator.c @@ -172,12 +172,11 @@ _ostree_impl_system_generator (const char *ostree_cmdline, /* Generate our bind mount unit */ const char *stateroot_var_path = glnx_strjoina ("/sysroot/ostree/deploy/", stateroot, "/var"); - glnx_fd_close int tmpfd = -1; - g_autofree char *tmppath = NULL; + g_auto(GLnxTmpfile) tmpf = { 0, }; if (!glnx_open_tmpfile_linkable_at (normal_dir_dfd, ".", O_WRONLY | O_CLOEXEC, - &tmpfd, &tmppath, error)) + &tmpf, error)) return FALSE; - g_autoptr(GOutputStream) outstream = g_unix_output_stream_new (tmpfd, FALSE); + g_autoptr(GOutputStream) outstream = g_unix_output_stream_new (tmpf.fd, FALSE); gsize bytes_written; /* This code is inspired by systemd's fstab-generator.c. * @@ -204,12 +203,11 @@ _ostree_impl_system_generator (const char *ostree_cmdline, return FALSE; g_clear_object (&outstream); /* It should be readable */ - if (fchmod (tmpfd, 0644) < 0) + if (fchmod (tmpf.fd, 0644) < 0) return glnx_throw_errno_prefix (error, "fchmod"); /* Error out if somehow it already exists, that'll help us debug conflicts */ - if (!glnx_link_tmpfile_at (normal_dir_dfd, GLNX_LINK_TMPFILE_NOREPLACE, - tmpfd, tmppath, normal_dir_dfd, - "var.mount", error)) + if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_NOREPLACE, + normal_dir_dfd, "var.mount", error)) return FALSE; /* And ensure it's required; newer systemd will auto-inject fs dependencies diff --git a/src/libostree/ostree-mutable-tree.c b/src/libostree/ostree-mutable-tree.c index 5540cc7c..c263fbe4 100644 --- a/src/libostree/ostree-mutable-tree.c +++ b/src/libostree/ostree-mutable-tree.c @@ -114,9 +114,6 @@ ostree_mutable_tree_set_contents_checksum (OstreeMutableTree *self, const char * ostree_mutable_tree_get_contents_checksum (OstreeMutableTree *self) { - GHashTableIter iter; - gpointer key, value; - if (!self->contents_checksum) return NULL; @@ -127,10 +124,8 @@ ostree_mutable_tree_get_contents_checksum (OstreeMutableTree *self) * * However, we only call this function once right now. */ - g_hash_table_iter_init (&iter, self->subdirs); - while (g_hash_table_iter_next (&iter, &key, &value)) + GLNX_HASH_TABLE_FOREACH_V (self->subdirs, OstreeMutableTree*, subdir) { - OstreeMutableTree *subdir = value; if (!ostree_mutable_tree_get_contents_checksum (subdir)) { g_free (self->contents_checksum); diff --git a/src/libostree/ostree-ref.c b/src/libostree/ostree-ref.c new file mode 100644 index 00000000..8c84946c --- /dev/null +++ b/src/libostree/ostree-ref.c @@ -0,0 +1,193 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "ostree-autocleanups.h" +#include "ostree-core.h" +#include "ostree-core-private.h" +#include "ostree-ref.h" + +G_DEFINE_BOXED_TYPE (OstreeCollectionRef, ostree_collection_ref, + ostree_collection_ref_dup, ostree_collection_ref_free) + +/** + * ostree_collection_ref_new: + * @collection_id: (nullable): a collection ID, or %NULL for a plain ref + * @ref_name: a ref name + * + * Create a new #OstreeCollectionRef containing (@collection_id, @ref_name). If + * @collection_id is %NULL, this is equivalent to a plain ref name string (not a + * refspec; no remote name is included), which can be used for non-P2P + * operations. + * + * Returns: (transfer full): a new #OstreeCollectionRef + * Since: 2017.8 + */ +OstreeCollectionRef * +ostree_collection_ref_new (const gchar *collection_id, + const gchar *ref_name) +{ + g_autoptr(OstreeCollectionRef) collection_ref = NULL; + + g_return_val_if_fail (collection_id == NULL || + ostree_validate_collection_id (collection_id, NULL), NULL); + g_return_val_if_fail (ostree_validate_rev (ref_name, NULL), NULL); + + collection_ref = g_new0 (OstreeCollectionRef, 1); + collection_ref->collection_id = g_strdup (collection_id); + collection_ref->ref_name = g_strdup (ref_name); + + return g_steal_pointer (&collection_ref); +} + +/** + * ostree_collection_ref_dup: + * @ref: an #OstreeCollectionRef + * + * Create a copy of the given @ref. + * + * Returns: (transfer full): a newly allocated copy of @ref + * Since: 2017.8 + */ +OstreeCollectionRef * +ostree_collection_ref_dup (const OstreeCollectionRef *ref) +{ + g_return_val_if_fail (ref != NULL, NULL); + + return ostree_collection_ref_new (ref->collection_id, ref->ref_name); +} + +/** + * ostree_collection_ref_free: + * @ref: (transfer full): an #OstreeCollectionRef + * + * Free the given @ref. + * + * Since: 2017.8 + */ +void +ostree_collection_ref_free (OstreeCollectionRef *ref) +{ + g_return_if_fail (ref != NULL); + + g_free (ref->collection_id); + g_free (ref->ref_name); + g_free (ref); +} + +/** + * ostree_collection_ref_hash: + * @ref: an #OstreeCollectionRef + * + * Hash the given @ref. This function is suitable for use with #GHashTable. + * @ref must be non-%NULL. + * + * Returns: hash value for @ref + * Since: 2017.8 + */ +guint +ostree_collection_ref_hash (gconstpointer ref) +{ + const OstreeCollectionRef *_ref = ref; + + if (_ref->collection_id != NULL) + return g_str_hash (_ref->collection_id) ^ g_str_hash (_ref->ref_name); + else + return g_str_hash (_ref->ref_name); +} + +/** + * ostree_collection_ref_equal: + * @ref1: an #OstreeCollectionRef + * @ref2: another #OstreeCollectionRef + * + * Compare @ref1 and @ref2 and return %TRUE if they have the same collection ID and + * ref name, and %FALSE otherwise. Both @ref1 and @ref2 must be non-%NULL. + * + * Returns: %TRUE if @ref1 and @ref2 are equal, %FALSE otherwise + * Since: 2017.8 + */ +gboolean +ostree_collection_ref_equal (gconstpointer ref1, + gconstpointer ref2) +{ + const OstreeCollectionRef *_ref1 = ref1, *_ref2 = ref2; + + return (g_strcmp0 (_ref1->collection_id, _ref2->collection_id) == 0 && + g_strcmp0 (_ref1->ref_name, _ref2->ref_name) == 0); +} + +/** + * ostree_collection_ref_dupv: + * @refs: (array zero-terminated=1): %NULL-terminated array of #OstreeCollectionRefs + * + * Copy an array of #OstreeCollectionRefs, including deep copies of all its + * elements. @refs must be %NULL-terminated; it may be empty, but must not be + * %NULL. + * + * Returns: (transfer full) (array zero-terminated=1): a newly allocated copy of @refs + * Since: 2017.8 + */ +OstreeCollectionRef ** +ostree_collection_ref_dupv (const OstreeCollectionRef * const *refs) +{ + gsize i, n_refs = g_strv_length ((gchar **) refs); /* hack */ + g_auto(OstreeCollectionRefv) new_refs = NULL; + + g_return_val_if_fail (refs != NULL, NULL); + + new_refs = g_new0 (OstreeCollectionRef*, n_refs + 1); + + for (i = 0; i < n_refs; i++) + new_refs[i] = ostree_collection_ref_dup (refs[i]); + new_refs[i] = NULL; + + return g_steal_pointer (&new_refs); +} + +/** + * ostree_collection_ref_freev: + * @refs: (transfer full) (array zero-terminated=1): an array of #OstreeCollectionRefs + * + * Free the given array of @refs, including freeing all its elements. @refs + * must be %NULL-terminated; it may be empty, but must not be %NULL. + * + * Since: 2017.8 + */ +void +ostree_collection_ref_freev (OstreeCollectionRef **refs) +{ + gsize i; + + g_return_if_fail (refs != NULL); + + for (i = 0; refs[i] != NULL; i++) + ostree_collection_ref_free (refs[i]); + g_free (refs); +} diff --git a/src/libostree/ostree-ref.h b/src/libostree/ostree-ref.h new file mode 100644 index 00000000..b23af171 --- /dev/null +++ b/src/libostree/ostree-ref.h @@ -0,0 +1,90 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#pragma once + +#include +#include +#include + +#include "ostree-types.h" + +G_BEGIN_DECLS + +/** + * OstreeCollectionRef: + * @collection_id: (nullable): collection ID which provided the ref, or %NULL if there + * is no associated collection + * @ref_name: ref name + * + * A structure which globally uniquely identifies a ref as the tuple + * (@collection_id, @ref_name). For backwards compatibility, @collection_id may be %NULL, + * indicating a ref name which is not globally unique. + * + * Since: 2017.8 + */ +typedef struct +{ + gchar *collection_id; /* (nullable) */ + gchar *ref_name; /* (not nullable) */ +} OstreeCollectionRef; + +#ifndef __GI_SCANNER__ +_OSTREE_PUBLIC +GType ostree_collection_ref_get_type (void); + +_OSTREE_PUBLIC +OstreeCollectionRef *ostree_collection_ref_new (const gchar *collection_id, + const gchar *ref_name); +_OSTREE_PUBLIC +OstreeCollectionRef *ostree_collection_ref_dup (const OstreeCollectionRef *ref); +_OSTREE_PUBLIC +void ostree_collection_ref_free (OstreeCollectionRef *ref); +#endif + +_OSTREE_PUBLIC +guint ostree_collection_ref_hash (gconstpointer ref); +_OSTREE_PUBLIC +gboolean ostree_collection_ref_equal (gconstpointer ref1, + gconstpointer ref2); + +_OSTREE_PUBLIC +OstreeCollectionRef **ostree_collection_ref_dupv (const OstreeCollectionRef * const *refs); +_OSTREE_PUBLIC +void ostree_collection_ref_freev (OstreeCollectionRef **refs); + +/** + * OstreeCollectionRefv: + * + * A %NULL-terminated array of #OstreeCollectionRef instances, designed to + * be used with g_auto(): + * + * |[ + * g_auto(OstreeCollectionRefv) refs = NULL; + * ]| + * + * Since: 2017.8 + */ +typedef OstreeCollectionRef** OstreeCollectionRefv; + +G_END_DECLS diff --git a/src/libostree/ostree-remote.h b/src/libostree/ostree-remote.h index 96fd4556..df76f67e 100644 --- a/src/libostree/ostree-remote.h +++ b/src/libostree/ostree-remote.h @@ -41,7 +41,7 @@ G_BEGIN_DECLS * remotes can only be passed around as (reference counted) opaque handles. In * future, more API may be added to create and interrogate them. * - * Since: 2016.7 + * Since: 2017.6 */ #ifndef OSTREE_ENABLE_EXPERIMENTAL_API /* This is in ostree-types.h otherwise */ diff --git a/src/libostree/ostree-repo-checkout.c b/src/libostree/ostree-repo-checkout.c index af5c021f..9ab9a623 100644 --- a/src/libostree/ostree-repo-checkout.c +++ b/src/libostree/ostree-repo-checkout.c @@ -60,9 +60,9 @@ checkout_object_for_uncompressed_cache (OstreeRepo *self, guint32 file_mode = g_file_info_get_attribute_uint32 (src_info, "unix::mode"); file_mode &= ~(S_ISUID|S_ISGID); - g_auto(OtTmpfile) tmpf = { 0, }; - if (!ot_open_tmpfile_linkable_at (self->tmp_dir_fd, ".", O_WRONLY | O_CLOEXEC, - &tmpf, error)) + g_auto(GLnxTmpfile) tmpf = { 0, }; + if (!glnx_open_tmpfile_linkable_at (self->tmp_dir_fd, ".", O_WRONLY | O_CLOEXEC, + &tmpf, error)) return FALSE; g_autoptr(GOutputStream) temp_out = g_unix_output_stream_new (tmpf.fd, FALSE); @@ -89,9 +89,9 @@ checkout_object_for_uncompressed_cache (OstreeRepo *self, cancellable, error)) return FALSE; - if (!ot_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST, - self->uncompressed_objects_dir_fd, loose_path, - error)) + if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST, + self->uncompressed_objects_dir_fd, loose_path, + error)) return FALSE; return TRUE; @@ -254,11 +254,11 @@ create_file_copy_from_input_at (OstreeRepo *repo, } else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR) { - g_auto(OtTmpfile) tmpf = { 0, }; + g_auto(GLnxTmpfile) tmpf = { 0, }; GLnxLinkTmpfileReplaceMode replace_mode; - if (!ot_open_tmpfile_linkable_at (destination_dfd, ".", O_WRONLY | O_CLOEXEC, - &tmpf, error)) + if (!glnx_open_tmpfile_linkable_at (destination_dfd, ".", O_WRONLY | O_CLOEXEC, + &tmpf, error)) return FALSE; if (sepolicy_enabled && options->mode != OSTREE_REPO_CHECKOUT_MODE_USER) @@ -285,9 +285,9 @@ create_file_copy_from_input_at (OstreeRepo *repo, else replace_mode = GLNX_LINK_TMPFILE_NOREPLACE; - if (!ot_link_tmpfile_at (&tmpf, replace_mode, - destination_dfd, destination_name, - error)) + if (!glnx_link_tmpfile_at (&tmpf, replace_mode, + destination_dfd, destination_name, + error)) return FALSE; } else @@ -446,7 +446,7 @@ checkout_one_file_at (OstreeRepo *repo, /* But only under these conditions */ if (is_bare || is_archive_z2_with_cache) { - /* Override repo mode; for archive-z2 we're looking in + /* Override repo mode; for archive we're looking in the cache, which is in "bare" form */ _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, OSTREE_REPO_MODE_BARE); if (!checkout_file_hardlink (current_repo, @@ -488,7 +488,7 @@ checkout_one_file_at (OstreeRepo *repo, g_autoptr(GInputStream) input = NULL; g_autoptr(GVariant) xattrs = NULL; - /* Ok, if we're archive-z2 and we didn't find an object, uncompress + /* Ok, if we're archive and we didn't find an object, uncompress * it now, stick it in the cache, and then hardlink to that. */ if (can_cache @@ -1088,13 +1088,12 @@ ostree_repo_checkout_gc (OstreeRepo *self, self->updated_uncompressed_dirs = g_hash_table_new (NULL, NULL); g_mutex_unlock (&self->cache_lock); - GHashTableIter iter; - gpointer key, value; - if (to_clean_dirs) - g_hash_table_iter_init (&iter, to_clean_dirs); - while (to_clean_dirs && g_hash_table_iter_next (&iter, &key, &value)) + if (!to_clean_dirs) + return TRUE; /* Note early return */ + + GLNX_HASH_TABLE_FOREACH (to_clean_dirs, gpointer, prefix) { - g_autofree char *objdir_name = g_strdup_printf ("%02x", GPOINTER_TO_UINT (key)); + g_autofree char *objdir_name = g_strdup_printf ("%02x", GPOINTER_TO_UINT (prefix)); g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; if (!glnx_dirfd_iterator_init_at (self->uncompressed_objects_dir_fd, objdir_name, FALSE, diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index 45b5bc6e..baeef3f9 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -23,6 +23,7 @@ #include "config.h" #include +#include #include #include #include @@ -81,31 +82,21 @@ create_file_metadata (guint32 uid, return ret_metadata; } -static gboolean -write_file_metadata_to_xattr (int fd, - guint32 uid, - guint32 gid, - guint32 mode, - GVariant *xattrs, - GError **error) +gboolean +_ostree_write_bareuser_metadata (int fd, + guint32 uid, + guint32 gid, + guint32 mode, + GVariant *xattrs, + GError **error) { - g_autoptr(GVariant) filemeta = NULL; - int res; + g_autoptr(GVariant) filemeta = create_file_metadata (uid, gid, mode, xattrs); - filemeta = create_file_metadata (uid, gid, mode, xattrs); - - do - res = fsetxattr (fd, "user.ostreemeta", - (char*)g_variant_get_data (filemeta), - g_variant_get_size (filemeta), - 0); - while (G_UNLIKELY (res == -1 && errno == EINTR)); - if (G_UNLIKELY (res == -1)) - { - glnx_set_error_from_errno (error); - g_prefix_error (error, "Unable to set xattr: "); - return FALSE; - } + if (TEMP_FAILURE_RETRY (fsetxattr (fd, "user.ostreemeta", + (char*)g_variant_get_data (filemeta), + g_variant_get_size (filemeta), + 0)) != 0) + return glnx_throw_errno_prefix (error, "fsetxattr(user.ostreemeta)"); return TRUE; } @@ -134,21 +125,19 @@ ot_security_smack_reset_fd (int fd) #endif } +/* Given an O_TMPFILE regular file, link it into place. */ gboolean -_ostree_repo_commit_loose_final (OstreeRepo *self, - const char *checksum, - OstreeObjectType objtype, - int temp_dfd, - int fd, - const char *temp_filename, - GCancellable *cancellable, - GError **error) +_ostree_repo_commit_tmpf_final (OstreeRepo *self, + const char *checksum, + OstreeObjectType objtype, + GLnxTmpfile *tmpf, + GCancellable *cancellable, + GError **error) { - int dest_dfd; char tmpbuf[_OSTREE_LOOSE_PATH_MAX]; - _ostree_loose_path (tmpbuf, checksum, objtype, self->mode); + int dest_dfd; if (self->in_transaction) dest_dfd = self->commit_stagedir_fd; else @@ -158,41 +147,68 @@ _ostree_repo_commit_loose_final (OstreeRepo *self, cancellable, error)) return FALSE; - if (fd != -1) + if (!glnx_link_tmpfile_at (tmpf, GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST, + dest_dfd, tmpbuf, error)) + return FALSE; + /* We're done with the fd */ + glnx_tmpfile_clear (tmpf); + return TRUE; +} + +/* Given a dfd+path combination (may be regular file or symlink), + * rename it into place. + */ +gboolean +_ostree_repo_commit_path_final (OstreeRepo *self, + const char *checksum, + OstreeObjectType objtype, + OtCleanupUnlinkat *tmp_path, + GCancellable *cancellable, + GError **error) +{ + /* The final renameat() */ + char tmpbuf[_OSTREE_LOOSE_PATH_MAX]; + _ostree_loose_path (tmpbuf, checksum, objtype, self->mode); + + int dest_dfd; + if (self->in_transaction) + dest_dfd = self->commit_stagedir_fd; + else + dest_dfd = self->objects_dir_fd; + + if (!_ostree_repo_ensure_loose_objdir_at (dest_dfd, tmpbuf, + cancellable, error)) + return FALSE; + + if (renameat (tmp_path->dfd, tmp_path->path, + dest_dfd, tmpbuf) == -1) { - if (!glnx_link_tmpfile_at (temp_dfd, GLNX_LINK_TMPFILE_NOREPLACE_IGNORE_EXIST, - fd, temp_filename, dest_dfd, tmpbuf, error)) - return FALSE; + if (errno != EEXIST) + return glnx_throw_errno_prefix (error, "Storing file '%s'", tmp_path->path); + /* Otherwise, the caller's cleanup will unlink+free */ } else { - if (G_UNLIKELY (renameat (temp_dfd, temp_filename, - dest_dfd, tmpbuf) == -1)) - { - if (errno != EEXIST) - return glnx_throw_errno_prefix (error, "Storing file '%s'", temp_filename); - else - (void) unlinkat (temp_dfd, temp_filename, 0); - } + /* The tmp path was consumed */ + ot_cleanup_unlinkat_clear (tmp_path); } return TRUE; } + /* Given either a file or symlink, apply the final metadata to it depending on * the repository mode. Note that @checksum is assumed to have been validated by * the caller. */ static gboolean -commit_loose_content_object (OstreeRepo *self, +commit_loose_regfile_object (OstreeRepo *self, const char *checksum, - const char *temp_filename, - gboolean object_is_symlink, + GLnxTmpfile *tmpf, guint32 uid, guint32 gid, guint32 mode, GVariant *xattrs, - int fd, GCancellable *cancellable, GError **error) { @@ -200,115 +216,82 @@ commit_loose_content_object (OstreeRepo *self, * automatically inherit the non-root ownership. */ if (self->mode == OSTREE_REPO_MODE_ARCHIVE_Z2 - && self->target_owner_uid != -1) + && self->target_owner_uid != -1) { - if (fd != -1) - { - if (fchown (fd, self->target_owner_uid, self->target_owner_gid) < 0) - return glnx_throw_errno_prefix (error, "fchown"); - } - else if (G_UNLIKELY (fchownat (self->tmp_dir_fd, temp_filename, - self->target_owner_uid, - self->target_owner_gid, - AT_SYMLINK_NOFOLLOW) == -1)) - return glnx_throw_errno_prefix (error, "fchownat"); + if (fchown (tmpf->fd, self->target_owner_uid, self->target_owner_gid) < 0) + return glnx_throw_errno_prefix (error, "fchown"); } + else if (self->mode == OSTREE_REPO_MODE_BARE) + { + if (TEMP_FAILURE_RETRY (fchown (tmpf->fd, uid, gid)) < 0) + return glnx_throw_errno_prefix (error, "fchown"); - /* Special handling for symlinks in bare repositories */ - if (object_is_symlink && self->mode == OSTREE_REPO_MODE_BARE_USER_ONLY) - { - /* We don't store the metadata in bare-user-only, so we're done. */ + if (TEMP_FAILURE_RETRY (fchmod (tmpf->fd, mode)) < 0) + return glnx_throw_errno_prefix (error, "fchmod"); + + if (xattrs) + { + ot_security_smack_reset_fd (tmpf->fd); + if (!glnx_fd_set_all_xattrs (tmpf->fd, xattrs, cancellable, error)) + return FALSE; + } } - else if (object_is_symlink && self->mode == OSTREE_REPO_MODE_BARE) + else if (self->mode == OSTREE_REPO_MODE_BARE_USER) { - /* Now that we know the checksum is valid, apply uid/gid, mode bits, - * and extended attributes. + if (!_ostree_write_bareuser_metadata (tmpf->fd, uid, gid, mode, xattrs, error)) + return FALSE; + + /* Note that previously this path added `| 0755` which made every + * file executable, see + * https://github.com/ostreedev/ostree/issues/907 + * We then changed it to mask by 0775, but we always need at least read + * permission when running as non-root, so explicitly mask that in. * - * Note, this does not apply for bare-user repos, as they store symlinks - * as regular files. + * Again here, symlinks in bare-user are a hairy special case; only do a + * chmod for a *real* regular file, otherwise we'll take the default 0644. */ - if (G_UNLIKELY (fchownat (self->tmp_dir_fd, temp_filename, - uid, gid, - AT_SYMLINK_NOFOLLOW) == -1)) - return glnx_throw_errno_prefix (error, "fchownat"); - - if (xattrs != NULL) + if (S_ISREG (mode)) { - ot_security_smack_reset_dfd_name (self->tmp_dir_fd, temp_filename); - if (!glnx_dfd_name_set_all_xattrs (self->tmp_dir_fd, temp_filename, - xattrs, cancellable, error)) - return FALSE; + const mode_t content_mode = (mode & (S_IFREG | 0775)) | S_IRUSR; + if (fchmod (tmpf->fd, content_mode) < 0) + return glnx_throw_errno_prefix (error, "fchmod"); } + else + g_assert (S_ISLNK (mode)); } - else + else if (self->mode == OSTREE_REPO_MODE_BARE_USER_ONLY) { - if (self->mode == OSTREE_REPO_MODE_BARE) - { - if (TEMP_FAILURE_RETRY (fchown (fd, uid, gid)) < 0) - return glnx_throw_errno_prefix (error, "fchown"); + guint32 invalid_modebits = (mode & ~S_IFMT) & ~0775; + if (invalid_modebits > 0) + return glnx_throw (error, "Invalid mode 0%04o with bits 0%04o in bare-user-only repository", + mode, invalid_modebits); - if (TEMP_FAILURE_RETRY (fchmod (fd, mode)) < 0) - return glnx_throw_errno_prefix (error, "fchmod"); - - if (xattrs) - { - ot_security_smack_reset_fd (fd); - if (!glnx_fd_set_all_xattrs (fd, xattrs, cancellable, error)) - return FALSE; - } - } - else if (self->mode == OSTREE_REPO_MODE_BARE_USER) - { - if (!write_file_metadata_to_xattr (fd, uid, gid, mode, xattrs, error)) - return FALSE; - - if (!object_is_symlink) - { - /* Note that previously this path added `| 0755` which made every - * file executable, see - * https://github.com/ostreedev/ostree/issues/907 - */ - const mode_t content_mode = (mode & (S_IFREG | 0775)); - if (fchmod (fd, content_mode) < 0) - return glnx_throw_errno_prefix (error, "fchmod"); - } - } - else if (self->mode == OSTREE_REPO_MODE_BARE_USER_ONLY - && !object_is_symlink) - { - guint32 invalid_modebits = (mode & ~S_IFMT) & ~0775; - if (invalid_modebits > 0) - return glnx_throw (error, "Invalid mode 0%04o with bits 0%04o in bare-user-only repository", - mode, invalid_modebits); - - if (fchmod (fd, mode) < 0) - return glnx_throw_errno_prefix (error, "fchmod"); - } - - if (_ostree_repo_mode_is_bare (self->mode)) - { - /* To satisfy tools such as guile which compare mtimes - * to determine whether or not source files need to be compiled, - * set the modification time to OSTREE_TIMESTAMP. - */ - const struct timespec times[2] = { { OSTREE_TIMESTAMP, UTIME_OMIT }, { OSTREE_TIMESTAMP, 0} }; - if (TEMP_FAILURE_RETRY (futimens (fd, times)) < 0) - return glnx_throw_errno_prefix (error, "futimens"); - } - - /* Ensure that in case of a power cut, these files have the data we - * want. See http://lwn.net/Articles/322823/ - */ - if (!self->in_transaction && !self->disable_fsync) - { - if (fsync (fd) == -1) - return glnx_throw_errno_prefix (error, "fsync"); - } + if (fchmod (tmpf->fd, mode) < 0) + return glnx_throw_errno_prefix (error, "fchmod"); } - if (!_ostree_repo_commit_loose_final (self, checksum, OSTREE_OBJECT_TYPE_FILE, - self->tmp_dir_fd, fd, temp_filename, - cancellable, error)) + if (_ostree_repo_mode_is_bare (self->mode)) + { + /* To satisfy tools such as guile which compare mtimes + * to determine whether or not source files need to be compiled, + * set the modification time to OSTREE_TIMESTAMP. + */ + const struct timespec times[2] = { { OSTREE_TIMESTAMP, UTIME_OMIT }, { OSTREE_TIMESTAMP, 0} }; + if (TEMP_FAILURE_RETRY (futimens (tmpf->fd, times)) < 0) + return glnx_throw_errno_prefix (error, "futimens"); + } + + /* Ensure that in case of a power cut, these files have the data we + * want. See http://lwn.net/Articles/322823/ + */ + if (!self->in_transaction && !self->disable_fsync) + { + if (fsync (tmpf->fd) == -1) + return glnx_throw_errno_prefix (error, "fsync"); + } + + if (!_ostree_repo_commit_tmpf_final (self, checksum, OSTREE_OBJECT_TYPE_FILE, + tmpf, cancellable, error)) return FALSE; return TRUE; @@ -367,42 +350,29 @@ compare_ascii_checksums_for_sorting (gconstpointer a_pp, /* * Create sizes metadata GVariant and add it to the metadata variant given. */ -static gboolean +static GVariant * add_size_index_to_metadata (OstreeRepo *self, - GVariant *original_metadata, - GVariant **out_metadata, - GCancellable *cancellable, - GError **error) + GVariant *original_metadata) { - gboolean ret = FALSE; g_autoptr(GVariantBuilder) builder = NULL; - + /* original_metadata may be NULL */ builder = ot_util_variant_builder_from_variant (original_metadata, G_VARIANT_TYPE ("a{sv}")); if (self->object_sizes && g_hash_table_size (self->object_sizes) > 0) { - GHashTableIter entries = { 0 }; - gchar *e_checksum = NULL; - OstreeContentSizeCacheEntry *e_size = NULL; GVariantBuilder index_builder; - guint i; - g_autoptr(GPtrArray) sorted_keys = NULL; - - g_hash_table_iter_init (&entries, self->object_sizes); g_variant_builder_init (&index_builder, G_VARIANT_TYPE ("a" _OSTREE_OBJECT_SIZES_ENTRY_SIGNATURE)); /* Sort the checksums so we can bsearch if desired */ - sorted_keys = g_ptr_array_new (); - while (g_hash_table_iter_next (&entries, - (gpointer *) &e_checksum, - (gpointer *) &e_size)) - g_ptr_array_add (sorted_keys, e_checksum); + g_autoptr(GPtrArray) sorted_keys = g_ptr_array_new (); + GLNX_HASH_TABLE_FOREACH (self->object_sizes, const char*, e_checksum) + g_ptr_array_add (sorted_keys, (gpointer)e_checksum); g_ptr_array_sort (sorted_keys, compare_ascii_checksums_for_sorting); - for (i = 0; i < sorted_keys->len; i++) + for (guint i = 0; i < sorted_keys->len; i++) { guint8 csum[OSTREE_SHA256_DIGEST_LEN]; const char *e_checksum = sorted_keys->pdata[i]; @@ -411,92 +381,55 @@ add_size_index_to_metadata (OstreeRepo *self, ostree_checksum_inplace_to_bytes (e_checksum, csum); g_string_append_len (buffer, (char*)csum, sizeof (csum)); - e_size = g_hash_table_lookup (self->object_sizes, e_checksum); + OstreeContentSizeCacheEntry *e_size = + g_hash_table_lookup (self->object_sizes, e_checksum); _ostree_write_varuint64 (buffer, e_size->archived); _ostree_write_varuint64 (buffer, e_size->unpacked); g_variant_builder_add (&index_builder, "@ay", ot_gvariant_new_bytearray ((guint8*)buffer->str, buffer->len)); } - + g_variant_builder_add (builder, "{sv}", "ostree.sizes", g_variant_builder_end (&index_builder)); } - - ret = TRUE; - *out_metadata = g_variant_builder_end (builder); - g_variant_ref_sink (*out_metadata); - return ret; -} - -static gboolean -ot_fallocate (int fd, goffset size, GError **error) -{ - if (size == 0) - return TRUE; - - int r = posix_fallocate (fd, 0, size); - if (r != 0) - { - /* posix_fallocate is a weird deviation from errno standards */ - errno = r; - return glnx_throw_errno_prefix (error, "fallocate"); - } - return TRUE; + return g_variant_ref_sink (g_variant_builder_end (builder)); } +/* Combines a check for whether or not we already have the object with + * allocating a tempfile if we don't. Used by the static delta code. + */ gboolean _ostree_repo_open_content_bare (OstreeRepo *self, const char *checksum, guint64 content_len, - OstreeRepoContentBareCommit *out_state, - GOutputStream **out_stream, + GLnxTmpfile *out_tmpf, gboolean *out_have_object, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - g_autofree char *temp_filename = NULL; - g_autoptr(GOutputStream) ret_stream = NULL; gboolean have_obj; - if (!_ostree_repo_has_loose_object (self, checksum, OSTREE_OBJECT_TYPE_FILE, &have_obj, cancellable, error)) - goto out; - - if (!have_obj) - { - int fd; - - if (!glnx_open_tmpfile_linkable_at (self->tmp_dir_fd, ".", O_WRONLY|O_CLOEXEC, - &fd, &temp_filename, error)) - goto out; - - if (!ot_fallocate (fd, content_len, error)) - goto out; - - ret_stream = g_unix_output_stream_new (fd, TRUE); - } - - ret = TRUE; - if (!have_obj) - { - out_state->temp_filename = temp_filename; - temp_filename = NULL; - out_state->fd = g_file_descriptor_based_get_fd ((GFileDescriptorBased*)ret_stream); - if (out_stream) - *out_stream = g_steal_pointer (&ret_stream); - } + return FALSE; + /* Do we already have this object? */ *out_have_object = have_obj; - out: - return ret; + if (have_obj) + { + /* Make sure the tempfile is unset */ + out_tmpf->initialized = 0; + return TRUE; + } + + return glnx_open_tmpfile_linkable_at (self->tmp_dir_fd, ".", O_WRONLY|O_CLOEXEC, + out_tmpf, error); } gboolean _ostree_repo_commit_trusted_content_bare (OstreeRepo *self, const char *checksum, - OstreeRepoContentBareCommit *state, + GLnxTmpfile *tmpf, guint32 uid, guint32 gid, guint32 mode, @@ -504,86 +437,71 @@ _ostree_repo_commit_trusted_content_bare (OstreeRepo *self, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; + /* I don't think this is necessary, but a similar check was here previously, + * keeping it for extra redundancy. + */ + if (!tmpf->initialized || tmpf->fd == -1) + return TRUE; - if (state->fd != -1) - { - if (!commit_loose_content_object (self, checksum, - state->temp_filename, - FALSE, uid, gid, mode, - xattrs, state->fd, - cancellable, error)) - { - g_prefix_error (error, "Writing object %s.%s: ", checksum, ostree_object_type_to_string (OSTREE_OBJECT_TYPE_FILE)); - goto out; - } - } - - ret = TRUE; - out: - g_free (state->temp_filename); - return ret; + return commit_loose_regfile_object (self, checksum, + tmpf, uid, gid, mode, xattrs, + cancellable, error); } static gboolean create_regular_tmpfile_linkable_with_content (OstreeRepo *self, guint64 length, GInputStream *input, - int *out_fd, - char **out_path, + GLnxTmpfile *out_tmpf, GCancellable *cancellable, GError **error) { - glnx_fd_close int temp_fd = -1; - g_autofree char *temp_filename = NULL; - + g_auto(GLnxTmpfile) tmpf = { 0, }; if (!glnx_open_tmpfile_linkable_at (self->tmp_dir_fd, ".", O_WRONLY|O_CLOEXEC, - &temp_fd, &temp_filename, - error)) + &tmpf, error)) return FALSE; - if (!ot_fallocate (temp_fd, length, error)) + if (!glnx_try_fallocate (tmpf.fd, 0, length, error)) return FALSE; if (G_IS_FILE_DESCRIPTOR_BASED (input)) { int infd = g_file_descriptor_based_get_fd ((GFileDescriptorBased*) input); - if (glnx_regfile_copy_bytes (infd, temp_fd, (off_t)length, TRUE) < 0) + if (glnx_regfile_copy_bytes (infd, tmpf.fd, (off_t)length, TRUE) < 0) return glnx_throw_errno_prefix (error, "regfile copy"); } else { - g_autoptr(GOutputStream) temp_out = g_unix_output_stream_new (temp_fd, FALSE); - if (g_output_stream_splice (temp_out, input, 0, - cancellable, error) < 0) - return FALSE; + /* We used to do a g_output_stream_splice(), but there are two issues with that: + * - We want to honor the size provided, to avoid malicious content that says it's + * e.g. 10 bytes but is actually gigabytes. + * - Due to GLib bugs that pointlessly calls `poll()` on the output fd for every write + */ + char buf[8192]; + guint64 remaining = length; + while (remaining > 0) + { + const gssize bytes_read = + g_input_stream_read (input, buf, MIN (remaining, sizeof (buf)), cancellable, error); + if (bytes_read < 0) + return FALSE; + else if (G_UNLIKELY (bytes_read == 0 && remaining > 0)) + return glnx_throw (error, "Unexpected EOF with %" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT " bytes remaining", remaining, length); + else if (bytes_read == 0) + break; + if (glnx_loop_write (tmpf.fd, buf, bytes_read) < 0) + return glnx_throw_errno_prefix (error, "write"); + remaining -= bytes_read; + } } - if (fchmod (temp_fd, 0644) < 0) + if (fchmod (tmpf.fd, 0644) < 0) return glnx_throw_errno_prefix (error, "fchmod"); - *out_fd = temp_fd; temp_fd = -1; - *out_path = g_steal_pointer (&temp_filename); + *out_tmpf = tmpf; tmpf.initialized = FALSE; return TRUE; } -/* A little helper to call unlinkat() as a cleanup - * function. Mostly only necessary to handle - * deletion of temporary symlinks. - */ -typedef struct { - int dfd; - const char *path; -} CleanupUnlinkat; - -static void -cleanup_unlinkat (CleanupUnlinkat *cleanup) -{ - if (cleanup->path) - (void) unlinkat (cleanup->dfd, cleanup->path, 0); -} -G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(CleanupUnlinkat, cleanup_unlinkat); - /* Write a content object. */ static gboolean write_content_object (OstreeRepo *self, @@ -614,29 +532,56 @@ write_content_object (OstreeRepo *self, cancellable, error)) return FALSE; - gboolean temp_file_is_regular = g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR; - gboolean temp_file_is_symlink = g_file_info_get_file_type (file_info) == G_FILE_TYPE_SYMBOLIC_LINK; - gboolean object_is_symlink = temp_file_is_symlink; + 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: + break; + case G_FILE_TYPE_SYMBOLIC_LINK: + if (self->mode == OSTREE_REPO_MODE_BARE || self->mode == OSTREE_REPO_MODE_BARE_USER_ONLY) + phys_object_is_symlink = TRUE; + break; + default: + return glnx_throw (error, "Unsupported file type %u", object_file_type); + } - if (repo_mode == OSTREE_REPO_MODE_BARE_USER && object_is_symlink) + guint64 size; + + /* For bare-user, convert the symlink target to the input stream */ + if (repo_mode == OSTREE_REPO_MODE_BARE_USER && object_file_type == G_FILE_TYPE_SYMBOLIC_LINK) { const char *target_str = g_file_info_get_symlink_target (file_info); g_autoptr(GBytes) target = g_bytes_new (target_str, strlen (target_str) + 1); - /* For bare-user we can't store symlinks as symlinks, as symlinks don't - support user xattrs to store the ownership. So, instead store them - as regular files */ - temp_file_is_regular = TRUE; - temp_file_is_symlink = FALSE; - if (file_input != NULL) g_object_unref (file_input); /* Include the terminating zero so we can e.g. mmap this file */ file_input = g_memory_input_stream_new_from_bytes (target); + size = g_bytes_get_size (target); } + else if (!phys_object_is_symlink) + size = g_file_info_get_size (file_info); + else + size = 0; - if (!(temp_file_is_regular || temp_file_is_symlink)) - return glnx_throw (error, "Unsupported file type %u", g_file_info_get_file_type (file_info)); + /* Free space check; only applies during transactions */ + if (self->min_free_space_percent > 0 && self->in_transaction) + { + g_mutex_lock (&self->txn_stats_lock); + g_assert_cmpint (self->txn_blocksize, >, 0); + const fsblkcnt_t object_blocks = (size / self->txn_blocksize) + 1; + if (object_blocks > self->max_txn_blocks) + { + g_mutex_unlock (&self->txn_stats_lock); + g_autofree char *formatted_required = g_format_size ((guint64)object_blocks * self->txn_blocksize); + return glnx_throw (error, "min-free-space-percent '%u%%' would be exceeded, %s more required", + self->min_free_space_percent, formatted_required); + } + /* This is the main bit that needs mutex protection */ + self->max_txn_blocks -= object_blocks; + g_mutex_unlock (&self->txn_stats_lock); + } /* For regular files, we create them with default mode, and only * later apply any xattrs and setuid bits. The rationale here @@ -644,54 +589,46 @@ write_content_object (OstreeRepo *self, * could potentially cause the system to make a temporary setuid * binary with trailing garbage, creating a window on the local * system where a malicious setuid binary exists. + * + * We use GLnxTmpfile for regular files, and OtCleanupUnlinkat for symlinks. */ - /* These variables are almost equivalent to OtTmpfile, except - * temp_filename might also be a symlink. Hence the CleanupUnlinkat - * which handles that case. - */ - g_auto(CleanupUnlinkat) tmp_unlinker = { self->tmp_dir_fd, NULL }; - glnx_fd_close int temp_fd = -1; - g_autofree char *temp_filename = NULL; + g_auto(OtCleanupUnlinkat) tmp_unlinker = { self->tmp_dir_fd, NULL }; + g_auto(GLnxTmpfile) tmpf = { 0, }; gssize unpacked_size = 0; gboolean indexable = FALSE; - if ((_ostree_repo_mode_is_bare (repo_mode)) && temp_file_is_regular) + /* Is it a symlink physically? */ + if (phys_object_is_symlink) { - guint64 size = g_file_info_get_size (file_info); - - if (!create_regular_tmpfile_linkable_with_content (self, size, file_input, - &temp_fd, &temp_filename, - cancellable, error)) - return FALSE; - tmp_unlinker.path = temp_filename; - } - else if (_ostree_repo_mode_is_bare (repo_mode) && temp_file_is_symlink) - { - /* Note: This will not be hit for bare-user mode because its converted to a - regular file and take the branch above */ + /* This will not be hit for bare-user or archive */ + g_assert (self->mode == OSTREE_REPO_MODE_BARE || self->mode == OSTREE_REPO_MODE_BARE_USER_ONLY); if (!_ostree_make_temporary_symlink_at (self->tmp_dir_fd, g_file_info_get_symlink_target (file_info), - &temp_filename, + &tmp_unlinker.path, cancellable, error)) return FALSE; - tmp_unlinker.path = temp_filename; } - else if (repo_mode == OSTREE_REPO_MODE_ARCHIVE_Z2) + else if (repo_mode != OSTREE_REPO_MODE_ARCHIVE_Z2) + { + if (!create_regular_tmpfile_linkable_with_content (self, size, file_input, + &tmpf, cancellable, error)) + return FALSE; + } + else { g_autoptr(GVariant) file_meta = NULL; g_autoptr(GConverter) zlib_compressor = NULL; g_autoptr(GOutputStream) compressed_out_stream = NULL; g_autoptr(GOutputStream) temp_out = NULL; + g_assert (repo_mode == OSTREE_REPO_MODE_ARCHIVE_Z2); + if (self->generate_sizes) indexable = TRUE; if (!glnx_open_tmpfile_linkable_at (self->tmp_dir_fd, ".", O_WRONLY|O_CLOEXEC, - &temp_fd, &temp_filename, - error)) + &tmpf, error)) return FALSE; - tmp_unlinker.path = temp_filename; - temp_file_is_regular = TRUE; - temp_out = g_unix_output_stream_new (temp_fd, FALSE); + temp_out = g_unix_output_stream_new (tmpf.fd, FALSE); file_meta = _ostree_zlib_file_header_new (file_info, xattrs); @@ -715,7 +652,7 @@ write_content_object (OstreeRepo *self, if (!g_output_stream_flush (temp_out, cancellable, error)) return FALSE; - if (fchmod (temp_fd, 0644) < 0) + if (fchmod (tmpf.fd, 0644) < 0) return glnx_throw_errno_prefix (error, "fchmod"); } @@ -756,23 +693,61 @@ write_content_object (OstreeRepo *self, const guint32 uid = g_file_info_get_attribute_uint32 (file_info, "unix::uid"); const guint32 gid = g_file_info_get_attribute_uint32 (file_info, "unix::gid"); const guint32 mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); - if (!commit_loose_content_object (self, actual_checksum, - temp_filename, - object_is_symlink, - uid, gid, mode, - xattrs, temp_fd, - cancellable, error)) - return glnx_prefix_error (error, "Writing object %s.%s: ", actual_checksum, - ostree_object_type_to_string (OSTREE_OBJECT_TYPE_FILE)); - /* Clear the unlinker path, it was consumed */ - tmp_unlinker.path = NULL; + /* Is it "physically" a symlink? */ + if (phys_object_is_symlink) + { + if (self->mode == OSTREE_REPO_MODE_BARE_USER_ONLY) + { + /* We don't store the metadata in bare-user-only, so we're done. */ + } + else if (self->mode == OSTREE_REPO_MODE_BARE) + { + /* Now that we know the checksum is valid, apply uid/gid, mode bits, + * and extended attributes. + * + * Note, this does not apply for bare-user repos, as they store symlinks + * as regular files. + */ + if (G_UNLIKELY (fchownat (self->tmp_dir_fd, tmp_unlinker.path, + uid, gid, AT_SYMLINK_NOFOLLOW) == -1)) + return glnx_throw_errno_prefix (error, "fchownat"); + + if (xattrs != NULL) + { + ot_security_smack_reset_dfd_name (self->tmp_dir_fd, tmp_unlinker.path); + if (!glnx_dfd_name_set_all_xattrs (self->tmp_dir_fd, tmp_unlinker.path, + xattrs, cancellable, error)) + return FALSE; + } + } + else + { + /* We don't do symlinks in archive or bare-user */ + g_assert_not_reached (); + } + + if (!_ostree_repo_commit_path_final (self, actual_checksum, OSTREE_OBJECT_TYPE_FILE, + &tmp_unlinker, + cancellable, error)) + return FALSE; + } + else + { + /* This path is for regular files */ + if (!commit_loose_regfile_object (self, actual_checksum, &tmpf, + uid, gid, mode, + xattrs, + cancellable, error)) + return glnx_prefix_error (error, "Writing object %s.%s", actual_checksum, + ostree_object_type_to_string (OSTREE_OBJECT_TYPE_FILE)); + } /* Update size metadata if configured */ - if (indexable && temp_file_is_regular) + if (indexable && object_file_type == G_FILE_TYPE_REGULAR) { struct stat stbuf; - if (!glnx_fstat (temp_fd, &stbuf, error)) + if (!glnx_fstat (tmpf.fd, &stbuf, error)) return FALSE; repo_store_size_entry (self, actual_checksum, unpacked_size, stbuf.st_size); @@ -868,11 +843,11 @@ write_metadata_object (OstreeRepo *self, } /* Write the metadata to a temporary file */ - g_auto(OtTmpfile) tmpf = { 0, }; - if (!ot_open_tmpfile_linkable_at (self->tmp_dir_fd, ".", O_WRONLY|O_CLOEXEC, - &tmpf, error)) + g_auto(GLnxTmpfile) tmpf = { 0, }; + if (!glnx_open_tmpfile_linkable_at (self->tmp_dir_fd, ".", O_WRONLY|O_CLOEXEC, + &tmpf, error)) return FALSE; - if (!ot_fallocate (tmpf.fd, len, error)) + if (!glnx_try_fallocate (tmpf.fd, 0, len, error)) return FALSE; if (glnx_loop_write (tmpf.fd, bufp, len) < 0) return glnx_throw_errno_prefix (error, "write()"); @@ -880,12 +855,9 @@ write_metadata_object (OstreeRepo *self, return glnx_throw_errno_prefix (error, "fchmod"); /* And commit it into place */ - if (!_ostree_repo_commit_loose_final (self, actual_checksum, objtype, - self->tmp_dir_fd, tmpf.fd, tmpf.path, - cancellable, error)) + if (!_ostree_repo_commit_tmpf_final (self, actual_checksum, objtype, + &tmpf, cancellable, error)) return FALSE; - /* The temp path was consumed */ - g_clear_pointer (&tmpf.path, g_free); if (objtype == OSTREE_OBJECT_TYPE_COMMIT) { @@ -926,22 +898,18 @@ scan_one_loose_devino (OstreeRepo *self, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - int res; g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; - if (!glnx_dirfd_iterator_init_at (object_dir_fd, ".", FALSE, &dfd_iter, error)) - goto out; - + return FALSE; + while (TRUE) { struct dirent *dent; g_auto(GLnxDirFdIterator) child_dfd_iter = { 0, }; if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error)) - goto out; - + return FALSE; if (dent == NULL) break; @@ -951,25 +919,20 @@ scan_one_loose_devino (OstreeRepo *self, if (!glnx_dirfd_iterator_init_at (dfd_iter.fd, dent->d_name, FALSE, &child_dfd_iter, error)) - goto out; + return FALSE; while (TRUE) { - struct stat stbuf; - OstreeDevIno *key; struct dirent *child_dent; - const char *dot; - gboolean skip; - const char *name; if (!glnx_dirfd_iterator_next_dent (&child_dfd_iter, &child_dent, cancellable, error)) - goto out; - + return FALSE; if (child_dent == NULL) break; - name = child_dent->d_name; + const char *name = child_dent->d_name; + gboolean skip; switch (self->mode) { case OSTREE_REPO_MODE_ARCHIVE_Z2: @@ -984,36 +947,29 @@ scan_one_loose_devino (OstreeRepo *self, if (skip) continue; - dot = strrchr (name, '.'); + const char *dot = strrchr (name, '.'); g_assert (dot); - + /* Skip anything that doesn't look like a 64 character checksum */ if ((dot - name) != 62) continue; - do - res = fstatat (child_dfd_iter.fd, child_dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW); - while (G_UNLIKELY (res == -1 && errno == EINTR)); - if (res == -1) - { - glnx_set_error_from_errno (error); - goto out; - } + struct stat stbuf; + if (!glnx_fstatat (child_dfd_iter.fd, child_dent->d_name, + &stbuf, AT_SYMLINK_NOFOLLOW, error)) + return FALSE; - key = g_new (OstreeDevIno, 1); + OstreeDevIno *key = g_new (OstreeDevIno, 1); key->dev = stbuf.st_dev; key->ino = stbuf.st_ino; memcpy (key->checksum, dent->d_name, 2); memcpy (key->checksum + 2, name, 62); key->checksum[sizeof(key->checksum)-1] = '\0'; - g_hash_table_add (devino_cache, key); } } - ret = TRUE; - out: - return ret; + return TRUE; } static gboolean @@ -1022,28 +978,24 @@ scan_loose_devino (OstreeRepo *self, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - if (self->parent_repo) { if (!scan_loose_devino (self->parent_repo, devino_cache, cancellable, error)) - goto out; + return FALSE; } if (self->mode == OSTREE_REPO_MODE_ARCHIVE_Z2) { if (!scan_one_loose_devino (self, self->uncompressed_objects_dir_fd, devino_cache, cancellable, error)) - goto out; + return FALSE; } if (!scan_one_loose_devino (self, self->objects_dir_fd, devino_cache, cancellable, error)) - goto out; - - ret = TRUE; - out: - return ret; + return FALSE; + + return TRUE; } static const char * @@ -1137,6 +1089,29 @@ ostree_repo_prepare_transaction (OstreeRepo *self, memset (&self->txn_stats, 0, sizeof (OstreeRepoTransactionStats)); self->in_transaction = TRUE; + if (self->min_free_space_percent > 0) + { + struct statvfs stvfsbuf; + if (TEMP_FAILURE_RETRY (fstatvfs (self->repo_dir_fd, &stvfsbuf)) < 0) + return glnx_throw_errno_prefix (error, "fstatvfs"); + g_mutex_lock (&self->txn_stats_lock); + self->txn_blocksize = stvfsbuf.f_bsize; + /* Convert fragment to blocks to compute the total */ + guint64 total_blocks = (stvfsbuf.f_frsize * stvfsbuf.f_blocks) / stvfsbuf.f_bsize; + /* Use the appropriate free block count if we're unprivileged */ + guint64 bfree = (getuid () != 0 ? stvfsbuf.f_bavail : stvfsbuf.f_bfree); + guint64 reserved_blocks = ((double)total_blocks) * (self->min_free_space_percent/100.0); + if (bfree > reserved_blocks) + self->max_txn_blocks = bfree - reserved_blocks; + else + { + g_mutex_unlock (&self->txn_stats_lock); + g_autofree char *formatted_free = g_format_size (bfree * self->txn_blocksize); + return glnx_throw (error, "min-free-space-percent '%u%%' would be exceeded, %s available", + self->min_free_space_percent, formatted_free); + } + g_mutex_unlock (&self->txn_stats_lock); + } gboolean ret_transaction_resume = FALSE; if (!_ostree_repo_allocate_tmpdir (self->tmp_dir_fd, @@ -1207,9 +1182,9 @@ rename_pending_loose_objects (OstreeRepo *self, cancellable, error)) return FALSE; - if (G_UNLIKELY (renameat (child_dfd_iter.fd, loose_objpath + 3, - self->objects_dir_fd, loose_objpath) < 0)) - return glnx_throw_errno (error); + if (!glnx_renameat (child_dfd_iter.fd, loose_objpath + 3, + self->objects_dir_fd, loose_objpath, error)) + return FALSE; renamed_some_object = TRUE; } @@ -1347,6 +1322,11 @@ ensure_txn_refs (OstreeRepo *self) { if (self->txn_refs == NULL) self->txn_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + if (self->txn_collection_refs == NULL) + self->txn_collection_refs = g_hash_table_new_full (ostree_collection_ref_hash, + ostree_collection_ref_equal, + (GDestroyNotify) ostree_collection_ref_free, + g_free); } /** @@ -1410,6 +1390,41 @@ ostree_repo_transaction_set_ref (OstreeRepo *self, g_hash_table_replace (self->txn_refs, refspec, g_strdup (checksum)); } +/** + * ostree_repo_transaction_set_collection_ref: + * @self: An #OstreeRepo + * @ref: The collection–ref to write + * @checksum: (nullable): The checksum to point it to + * + * If @checksum is not %NULL, then record it as the target of local ref named + * @ref. + * + * Otherwise, if @checksum is %NULL, then record that the ref should + * be deleted. + * + * The change will not be written out immediately, but when the transaction + * is completed with ostree_repo_commit_transaction(). If the transaction + * is instead aborted with ostree_repo_abort_transaction(), no changes will + * be made to the repository. + * + * Since: 2017.8 + */ +void +ostree_repo_transaction_set_collection_ref (OstreeRepo *self, + const OstreeCollectionRef *ref, + const char *checksum) +{ + g_return_if_fail (OSTREE_IS_REPO (self)); + g_return_if_fail (self->in_transaction == TRUE); + g_return_if_fail (ref != NULL); + g_return_if_fail (checksum == NULL || ostree_validate_checksum_string (checksum, NULL)); + + ensure_txn_refs (self); + + g_hash_table_replace (self->txn_collection_refs, + ostree_collection_ref_dup (ref), g_strdup (checksum)); +} + /** * ostree_repo_set_ref_immediate: * @self: An #OstreeRepo @@ -1431,7 +1446,40 @@ ostree_repo_set_ref_immediate (OstreeRepo *self, GCancellable *cancellable, GError **error) { - return _ostree_repo_write_ref (self, remote, ref, checksum, + const OstreeCollectionRef _ref = { NULL, (gchar *) ref }; + return _ostree_repo_write_ref (self, remote, &_ref, checksum, + cancellable, error); +} + +/** + * ostree_repo_set_collection_ref_immediate: + * @self: An #OstreeRepo + * @ref: The collection–ref to write + * @checksum: (nullable): The checksum to point it to, or %NULL to unset + * @cancellable: GCancellable + * @error: GError + * + * This is like ostree_repo_transaction_set_collection_ref(), except it may be + * invoked outside of a transaction. This is presently safe for the + * case where we're creating or overwriting an existing ref. + * + * Returns: %TRUE on success, %FALSE otherwise + * Since: 2017.8 + */ +gboolean +ostree_repo_set_collection_ref_immediate (OstreeRepo *self, + const OstreeCollectionRef *ref, + const char *checksum, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (OSTREE_IS_REPO (self), FALSE); + g_return_val_if_fail (ref != NULL, FALSE); + g_return_val_if_fail (checksum == NULL || ostree_validate_checksum_string (checksum, NULL), FALSE); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + return _ostree_repo_write_ref (self, NULL, ref, checksum, cancellable, error); } @@ -1481,6 +1529,11 @@ ostree_repo_commit_transaction (OstreeRepo *self, return FALSE; g_clear_pointer (&self->txn_refs, g_hash_table_destroy); + if (self->txn_collection_refs) + if (!_ostree_repo_update_collection_refs (self, self->txn_collection_refs, cancellable, error)) + return FALSE; + g_clear_pointer (&self->txn_collection_refs, g_hash_table_destroy); + if (self->commit_stagedir_fd != -1) { (void) close (self->commit_stagedir_fd); @@ -1518,6 +1571,7 @@ ostree_repo_abort_transaction (OstreeRepo *self, g_hash_table_remove_all (self->loose_object_devino_hash); g_clear_pointer (&self->txn_refs, g_hash_table_destroy); + g_clear_pointer (&self->txn_collection_refs, g_hash_table_destroy); if (self->commit_stagedir_fd != -1) { @@ -2066,10 +2120,7 @@ ostree_repo_write_commit_with_time (OstreeRepo *self, OstreeRepoFile *repo_root = OSTREE_REPO_FILE (root); /* Add sizes information to our metadata object */ - g_autoptr(GVariant) new_metadata = NULL; - if (!add_size_index_to_metadata (self, metadata, &new_metadata, - cancellable, error)) - return FALSE; + g_autoptr(GVariant) new_metadata = add_size_index_to_metadata (self, metadata); g_autoptr(GVariant) commit = g_variant_new ("(@a{sv}@ay@a(say)sst@ay@ay)", @@ -2199,8 +2250,6 @@ create_tree_variant_from_hashes (GHashTable *file_checksums, GHashTable *dir_contents_checksums, GHashTable *dir_metadata_checksums) { - GHashTableIter hash_iter; - gpointer key, value; GVariantBuilder files_builder; GVariantBuilder dirs_builder; GSList *sorted_filenames = NULL; @@ -2210,11 +2259,8 @@ create_tree_variant_from_hashes (GHashTable *file_checksums, g_variant_builder_init (&files_builder, G_VARIANT_TYPE ("a(say)")); g_variant_builder_init (&dirs_builder, G_VARIANT_TYPE ("a(sayay)")); - g_hash_table_iter_init (&hash_iter, file_checksums); - while (g_hash_table_iter_next (&hash_iter, &key, &value)) + GLNX_HASH_TABLE_FOREACH (file_checksums, const char*, name) { - const char *name = key; - /* Should have been validated earlier, but be paranoid */ g_assert (ot_util_filename_validate (name, NULL)); @@ -2235,13 +2281,8 @@ create_tree_variant_from_hashes (GHashTable *file_checksums, g_slist_free (sorted_filenames); sorted_filenames = NULL; - - g_hash_table_iter_init (&hash_iter, dir_metadata_checksums); - while (g_hash_table_iter_next (&hash_iter, &key, &value)) - { - const char *name = key; - sorted_filenames = g_slist_prepend (sorted_filenames, (char*)name); - } + GLNX_HASH_TABLE_FOREACH (dir_metadata_checksums, const char*, name) + sorted_filenames = g_slist_prepend (sorted_filenames, (char*)name); sorted_filenames = g_slist_sort (sorted_filenames, (GCompareFunc)strcmp); @@ -2742,7 +2783,7 @@ write_dfd_iter_to_mtree_internal (OstreeRepo *self, if (fstat (src_dfd_iter->fd, &dir_stbuf) != 0) return glnx_throw_errno (error); - child_info = _ostree_header_gfile_info_new (dir_stbuf.st_mode, dir_stbuf.st_uid, dir_stbuf.st_gid); + child_info = _ostree_stbuf_to_gfileinfo (&dir_stbuf); if (modifier != NULL) { @@ -2803,13 +2844,11 @@ write_dfd_iter_to_mtree_internal (OstreeRepo *self, continue; } - child_info = _ostree_header_gfile_info_new (stbuf.st_mode, stbuf.st_uid, stbuf.st_gid); + child_info = _ostree_stbuf_to_gfileinfo (&stbuf); g_file_info_set_name (child_info, dent->d_name); if (S_ISREG (stbuf.st_mode)) - { - g_file_info_set_size (child_info, stbuf.st_size); - } + ; else if (S_ISLNK (stbuf.st_mode)) { if (!ot_readlinkat_gfile_info (src_dfd_iter->fd, dent->d_name, @@ -2958,15 +2997,10 @@ ostree_repo_write_mtree (OstreeRepo *self, dir_metadata_checksums = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify)g_free, (GDestroyNotify)g_free); - GHashTableIter hash_iter; - gpointer key, value; - g_hash_table_iter_init (&hash_iter, ostree_mutable_tree_get_subdirs (mtree)); - while (g_hash_table_iter_next (&hash_iter, &key, &value)) + GLNX_HASH_TABLE_FOREACH_KV (ostree_mutable_tree_get_subdirs (mtree), + const char*, name, OstreeMutableTree*, child_dir) { - const char *name = key; g_autoptr(GFile) child_file = NULL; - OstreeMutableTree *child_dir = value; - if (!ostree_repo_write_mtree (self, child_dir, &child_file, cancellable, error)) return FALSE; diff --git a/src/libostree/ostree-repo-finder-avahi-parser.c b/src/libostree/ostree-repo-finder-avahi-parser.c new file mode 100644 index 00000000..805f5dff --- /dev/null +++ b/src/libostree/ostree-repo-finder-avahi-parser.c @@ -0,0 +1,137 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2016 Kinvolk GmbH + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Krzesimir Nowak + * - Philip Withnall + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "ostree-autocleanups.h" +#include "ostree-repo-finder-avahi.h" +#include "ostree-repo-finder-avahi-private.h" + +/* Reference: RFC 6763, §6. */ +static gboolean +parse_txt_record (const guint8 *txt, + gsize txt_len, + const gchar **key, + gsize *key_len, + const guint8 **value, + gsize *value_len) +{ + gsize i; + + g_return_val_if_fail (key != NULL, FALSE); + g_return_val_if_fail (key_len != NULL, FALSE); + g_return_val_if_fail (value != NULL, FALSE); + g_return_val_if_fail (value_len != NULL, FALSE); + + /* RFC 6763, §6.1. */ + if (txt_len > 8900) + return FALSE; + + *key = (const gchar *) txt; + *key_len = 0; + *value = NULL; + *value_len = 0; + + for (i = 0; i < txt_len; i++) + { + if (txt[i] >= 0x20 && txt[i] <= 0x7e && txt[i] != '=') + { + /* Key character. */ + *key_len = *key_len + 1; + continue; + } + else if (*key_len > 0 && txt[i] == '=') + { + /* Separator. */ + *value = txt + (i + 1); + *value_len = txt_len - (i + 1); + return TRUE; + } + else + { + return FALSE; + } + } + + /* The entire TXT record is the key; there is no ‘=’ or value. */ + *value = NULL; + *value_len = 0; + + return (*key_len > 0); +} + +/* TODO: Docs. Return value is only valid as long as @txt is. Reference: RFC 6763, §6. */ +GHashTable * +_ostree_txt_records_parse (AvahiStringList *txt) +{ + AvahiStringList *l; + g_autoptr(GHashTable) out = NULL; + + out = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_bytes_unref); + + for (l = txt; l != NULL; l = avahi_string_list_get_next (l)) + { + const guint8 *txt; + gsize txt_len; + const gchar *key; + const guint8 *value; + gsize key_len, value_len; + g_autofree gchar *key_allocated = NULL; + g_autoptr(GBytes) value_allocated = NULL; + + txt = avahi_string_list_get_text (l); + txt_len = avahi_string_list_get_size (l); + + if (!parse_txt_record (txt, txt_len, &key, &key_len, &value, &value_len)) + { + g_debug ("Ignoring invalid TXT record of length %" G_GSIZE_FORMAT, + txt_len); + continue; + } + + key_allocated = g_ascii_strdown (key, key_len); + + if (g_hash_table_lookup_extended (out, key_allocated, NULL, NULL)) + { + g_debug ("Ignoring duplicate TXT record ‘%s’", key_allocated); + continue; + } + + /* Distinguish between the case where the entire record is the key + * (value == NULL) and the case where the record is the key + ‘=’ and the + * value is empty (value != NULL && value_len == 0). */ + if (value != NULL) + value_allocated = g_bytes_new_static (value, value_len); + + g_hash_table_insert (out, g_steal_pointer (&key_allocated), g_steal_pointer (&value_allocated)); + } + + return g_steal_pointer (&out); +} diff --git a/src/libostree/ostree-repo-finder-avahi-private.h b/src/libostree/ostree-repo-finder-avahi-private.h new file mode 100644 index 00000000..6429cd7f --- /dev/null +++ b/src/libostree/ostree-repo-finder-avahi-private.h @@ -0,0 +1,35 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#pragma once + +#include +#include +#include +#include + +G_BEGIN_DECLS + +GHashTable *_ostree_txt_records_parse (AvahiStringList *txt); + +G_END_DECLS diff --git a/src/libostree/ostree-repo-finder-avahi.c b/src/libostree/ostree-repo-finder-avahi.c new file mode 100644 index 00000000..433914b4 --- /dev/null +++ b/src/libostree/ostree-repo-finder-avahi.c @@ -0,0 +1,1506 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2016 Kinvolk GmbH + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Krzesimir Nowak + * - Philip Withnall + */ + +#include "config.h" + +#ifdef HAVE_AVAHI +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif /* HAVE_AVAHI */ + +#include +#include +#include + +#include "ostree-autocleanups.h" +#include "ostree-repo-finder.h" +#include "ostree-repo-finder-avahi.h" + +#ifdef HAVE_AVAHI +#include "ostree-bloom-private.h" +#include "ostree-remote-private.h" +#include "ostree-repo-private.h" +#include "ostree-repo.h" +#include "ostree-repo-finder-avahi-private.h" +#include "otutil.h" +#endif /* HAVE_AVAHI */ + +/** + * SECTION:ostree-repo-finder-avahi + * @title: OstreeRepoFinderAvahi + * @short_description: Finds remote repositories from ref names by looking at + * adverts of refs from peers on the local network + * @stability: Unstable + * @include: libostree/ostree-repo-finder-avahi.h + * + * #OstreeRepoFinderAvahi is an implementation of #OstreeRepoFinder which looks + * for refs being hosted by peers on the local network. + * + * Any ref which matches by collection ID and ref name is returned as a result, + * with no limitations on the peers which host them, as long as they are + * accessible over the local network, and their adverts reach this machine via + * DNS-SD/mDNS. + * + * For each repository which is found, a result will be returned for the + * intersection of the refs being searched for, and the refs in `refs/mirrors` + * in the remote repository. + * + * DNS-SD resolution is performed using Avahi, which will continue to scan for + * matching peers throughout the lifetime of the process. It’s recommended that + * ostree_repo_finder_avahi_start() be called early on in the process’ lifetime, + * and the #GMainContext which is passed to ostree_repo_finder_avahi_new() + * continues to be iterated until ostree_repo_finder_avahi_stop() is called. + * + * The values stored in DNS-SD TXT records are stored as big-endian whenever + * endianness is relevant. + * + * Internally, #OstreeRepoFinderAvahi has an Avahi client, browser and resolver + * which work in the background to track all available peers on the local + * network. Whenever a resolve request is made using + * ostree_repo_finder_resolve_async(), the request is blocked until the + * background tracking is in a consistent state (typically this only happens at + * startup), and is then answered using the current cache of background data. + * The Avahi client tracks the #OstreeRepoFinderAvahi’s connection with the + * Avahi D-Bus service. The browser looks for DNS-SD peers on the local network; + * and the resolver is used to retrieve information about services advertised by + * each peer, including the services’ TXT records. + * + * Since: 2017.8 + */ + +#ifdef HAVE_AVAHI +/* FIXME: Submit these upstream */ +G_DEFINE_AUTOPTR_CLEANUP_FUNC (AvahiClient, avahi_client_free) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (AvahiServiceBrowser, avahi_service_browser_free) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (AvahiServiceResolver, avahi_service_resolver_free) + +/* FIXME: Register this with IANA? https://tools.ietf.org/html/rfc6335#section-5.2 */ +const gchar * const OSTREE_AVAHI_SERVICE_TYPE = "_ostree_repo._tcp"; + +static const gchar * +ostree_avahi_client_state_to_string (AvahiClientState state) +{ + switch (state) + { + case AVAHI_CLIENT_S_REGISTERING: + return "registering"; + case AVAHI_CLIENT_S_RUNNING: + return "running"; + case AVAHI_CLIENT_S_COLLISION: + return "collision"; + case AVAHI_CLIENT_CONNECTING: + return "connecting"; + case AVAHI_CLIENT_FAILURE: + return "failure"; + default: + return "unknown"; + } +} + +static const gchar * +ostree_avahi_resolver_event_to_string (AvahiResolverEvent event) +{ + switch (event) + { + case AVAHI_RESOLVER_FOUND: + return "found"; + case AVAHI_RESOLVER_FAILURE: + return "failure"; + default: + return "unknown"; + } +} + +static const gchar * +ostree_avahi_browser_event_to_string (AvahiBrowserEvent event) +{ + switch (event) + { + case AVAHI_BROWSER_NEW: + return "new"; + case AVAHI_BROWSER_REMOVE: + return "remove"; + case AVAHI_BROWSER_CACHE_EXHAUSTED: + return "cache-exhausted"; + case AVAHI_BROWSER_ALL_FOR_NOW: + return "all-for-now"; + case AVAHI_BROWSER_FAILURE: + return "failure"; + default: + return "unknown"; + } +} + +typedef struct +{ + gchar *uri; + gchar *keyring; +} UriAndKeyring; + +static void +uri_and_keyring_free (UriAndKeyring *data) +{ + g_free (data->uri); + g_free (data->keyring); + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (UriAndKeyring, uri_and_keyring_free) + +static UriAndKeyring * +uri_and_keyring_new (const gchar *uri, + const gchar *keyring) +{ + g_autoptr(UriAndKeyring) data = NULL; + + data = g_new0 (UriAndKeyring, 1); + data->uri = g_strdup (uri); + data->keyring = g_strdup (keyring); + + return g_steal_pointer (&data); +} + +static guint +uri_and_keyring_hash (gconstpointer key) +{ + const UriAndKeyring *_key = key; + + return g_str_hash (_key->uri) ^ g_str_hash (_key->keyring); +} + +static gboolean +uri_and_keyring_equal (gconstpointer a, + gconstpointer b) +{ + const UriAndKeyring *_a = a, *_b = b; + + return g_str_equal (_a->uri, _b->uri) && g_str_equal (_a->keyring, _b->keyring); +} + +/* This must return a valid remote name (suitable for use in a refspec). */ +static gchar * +uri_and_keyring_to_name (UriAndKeyring *data) +{ + g_autofree gchar *escaped_uri = g_uri_escape_string (data->uri, NULL, FALSE); + g_autofree gchar *escaped_keyring = g_uri_escape_string (data->keyring, NULL, FALSE); + + /* FIXME: Need a better separator than `_`, since it’s not escaped in the input. */ + g_autofree gchar *out = g_strdup_printf ("%s_%s", escaped_uri, escaped_keyring); + + for (gsize i = 0; out[i] != '\0'; i++) + { + if (out[i] == '%') + out[i] = '_'; + } + + g_return_val_if_fail (ostree_validate_remote_name (out, NULL), NULL); + + return g_steal_pointer (&out); +} + +/* Internal structure representing a service found advertised by a peer on the + * local network. This includes details for connecting to the service, and the + * metadata associated with the advert (@txt). */ +typedef struct +{ + gchar *name; + gchar *domain; + gchar *address; + guint16 port; + AvahiStringList *txt; +} OstreeAvahiService; + +static void +ostree_avahi_service_free (OstreeAvahiService *service) +{ + g_free (service->name); + g_free (service->domain); + g_free (service->address); + avahi_string_list_free (service->txt); + g_free (service); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeAvahiService, ostree_avahi_service_free) + +/* Convert an AvahiAddress to a string which is suitable for use in URIs (for + * example). Take into account the scope ID, if the address is IPv6 and a + * link-local address. + * (See https://en.wikipedia.org/wiki/IPv6_address#Link-local_addresses_and_zone_indices and + * https://github.com/lathiat/avahi/issues/110.) */ +static gchar * +address_to_string (const AvahiAddress *address, + AvahiIfIndex interface) +{ + char address_string[AVAHI_ADDRESS_STR_MAX]; + + avahi_address_snprint (address_string, sizeof (address_string), address); + + switch (address->proto) + { + case AVAHI_PROTO_INET6: + if (IN6_IS_ADDR_LINKLOCAL (address->data.data) || + IN6_IS_ADDR_LOOPBACK (address->data.data)) + return g_strdup_printf ("%s%%%d", address_string, interface); + /* else fall through */ + case AVAHI_PROTO_INET: + case AVAHI_PROTO_UNSPEC: + default: + return g_strdup (address_string); + } +} + +static OstreeAvahiService * +ostree_avahi_service_new (const gchar *name, + const gchar *domain, + const AvahiAddress *address, + AvahiIfIndex interface, + guint16 port, + AvahiStringList *txt) +{ + g_autoptr(OstreeAvahiService) service = NULL; + + g_return_val_if_fail (name != NULL, NULL); + g_return_val_if_fail (domain != NULL, NULL); + g_return_val_if_fail (address != NULL, NULL); + g_return_val_if_fail (port > 0, NULL); + + service = g_new0 (OstreeAvahiService, 1); + + service->name = g_strdup (name); + service->domain = g_strdup (domain); + service->address = address_to_string (address, interface); + service->port = port; + service->txt = avahi_string_list_copy (txt); + + return g_steal_pointer (&service); +} + +/* Check whether @str is entirely lower case. */ +static gboolean +str_is_lowercase (const gchar *str) +{ + gsize i; + + for (i = 0; str[i] != '\0'; i++) + { + if (!g_ascii_islower (str[i])) + return FALSE; + } + + return TRUE; +} + +/* Look up @key in the @attributes table derived from a TXT record, and validate + * that its value is of type @value_type. If the key is not found, or its value + * is of the wrong type or is not in normal form, %NULL is returned. @key must + * be lowercase in order to match reliably. */ +static GVariant * +_ostree_txt_records_lookup_variant (GHashTable *attributes, + const gchar *key, + const GVariantType *value_type) +{ + GBytes *value; + g_autoptr(GVariant) variant = NULL; + + g_return_val_if_fail (attributes != NULL, NULL); + g_return_val_if_fail (str_is_lowercase (key), NULL); + g_return_val_if_fail (value_type != NULL, NULL); + + value = g_hash_table_lookup (attributes, key); + + if (value == NULL) + { + g_debug ("TXT attribute ‘%s’ not found.", key); + return NULL; + } + + variant = g_variant_new_from_bytes (value_type, value, FALSE); + + if (!g_variant_is_normal_form (variant)) + { + g_debug ("TXT attribute ‘%s’ value is not in normal form. Ignoring.", key); + return NULL; + } + + return g_steal_pointer (&variant); +} + +/* Bloom hash function family for #OstreeCollectionRef, parameterised by @k. */ +static guint64 +ostree_collection_ref_bloom_hash (gconstpointer element, + guint8 k) +{ + const OstreeCollectionRef *ref = element; + + return ostree_str_bloom_hash (ref->collection_id, k) ^ ostree_str_bloom_hash (ref->ref_name, k); +} + +/* Return the (possibly empty) subset of @refs which are possibly in the given + * encoded bloom filter, @bloom_encoded. The returned array is not + * %NULL-terminated. If there is an error decoding the bloom filter (invalid + * type, zero length, unknown hash function), %NULL will be returned. */ +static GPtrArray * +bloom_refs_intersection (GVariant *bloom_encoded, + const OstreeCollectionRef * const *refs) +{ + g_autoptr(OstreeBloom) bloom = NULL; + g_autoptr(GVariant) bloom_variant = NULL; + guint8 k, hash_id; + OstreeBloomHashFunc hash_func; + const guint8 *bloom_bytes; + gsize n_bloom_bytes; + g_autoptr(GBytes) bytes = NULL; + gsize i; + g_autoptr(GPtrArray) possible_refs = NULL; /* (element-type OstreeCollectionRef) */ + + g_variant_get (bloom_encoded, "(yy@ay)", &k, &hash_id, &bloom_variant); + + if (k == 0) + return NULL; + + switch (hash_id) + { + case 1: + hash_func = ostree_collection_ref_bloom_hash; + break; + default: + return NULL; + } + + bloom_bytes = g_variant_get_fixed_array (bloom_variant, &n_bloom_bytes, sizeof (guint8)); + bytes = g_bytes_new_static (bloom_bytes, n_bloom_bytes); + bloom = ostree_bloom_new_from_bytes (bytes, k, hash_func); + + possible_refs = g_ptr_array_new_with_free_func (NULL); + + for (i = 0; refs[i] != NULL; i++) + { + if (ostree_bloom_maybe_contains (bloom, refs[i])) + g_ptr_array_add (possible_refs, (gpointer) refs[i]); + } + + return g_steal_pointer (&possible_refs); +} + +/* Given a @summary_map of ref name to commit details, and the @collection_id + * for all the refs in the @summary_map (which may be %NULL if the summary does + * not specify one), add the refs to @refs_and_checksums. + * + * The @summary_map is validated as it’s iterated over; on error, @error will be + * set and @refs_and_checksums will be left in an undefined state. */ +static gboolean +fill_refs_and_checksums_from_summary_map (GVariantIter *summary_map, + const gchar *collection_id, + GHashTable *refs_and_checksums /* (element-type OstreeCollectionRef utf8) */, + GError **error) +{ + g_autofree gchar *ref_name = NULL; + g_autoptr(GVariant) checksum_variant = NULL; + + while (g_variant_iter_next (summary_map, "(s(t@aya{sv}))", + (gpointer *) &ref_name, NULL, + (gpointer *) &checksum_variant, NULL)) + { + const OstreeCollectionRef ref = { (gchar *) collection_id, ref_name }; + + if (!ostree_validate_rev (ref_name, error)) + return FALSE; + if (!ostree_validate_structureof_csum_v (checksum_variant, error)) + return FALSE; + + if (g_hash_table_contains (refs_and_checksums, &ref)) + { + g_autofree gchar *checksum_string = ostree_checksum_from_bytes_v (checksum_variant); + + g_hash_table_replace (refs_and_checksums, + ostree_collection_ref_dup (&ref), + g_steal_pointer (&checksum_string)); + } + } + + return TRUE; +} + +/* Given a @summary file, add the refs it lists to @refs_and_checksums. This + * includes the main refs list in the summary, and the map of collection IDs + * to further refs lists. + * + * The @summary is validated as it’s explored; on error, @error will be + * set and @refs_and_checksums will be left in an undefined state. */ +static gboolean +fill_refs_and_checksums_from_summary (GVariant *summary, + GHashTable *refs_and_checksums /* (element-type OstreeCollectionRef utf8) */, + GError **error) +{ + g_autoptr(GVariant) ref_map_v = NULL; + g_autoptr(GVariant) additional_metadata_v = NULL; + GVariantIter ref_map; + g_auto(GVariantDict) additional_metadata = OT_VARIANT_BUILDER_INITIALIZER; + const gchar *collection_id; + g_autoptr(GVariantIter) collection_map = NULL; + + ref_map_v = g_variant_get_child_value (summary, 0); + additional_metadata_v = g_variant_get_child_value (summary, 1); + + g_variant_iter_init (&ref_map, ref_map_v); + g_variant_dict_init (&additional_metadata, additional_metadata_v); + + /* If the summary file specifies a collection ID (to apply to all the refs in its + * ref map), use that to start matching against the queried refs. Otherwise, + * it might specify all its refs in a collection-map; or the summary format is + * old and unsuitable for P2P redistribution and we should bail. */ + if (g_variant_dict_lookup (&additional_metadata, OSTREE_SUMMARY_COLLECTION_ID, "&s", &collection_id)) + { + if (!ostree_validate_collection_id (collection_id, error)) + return FALSE; + if (!fill_refs_and_checksums_from_summary_map (&ref_map, collection_id, refs_and_checksums, error)) + return FALSE; + } + + /* Repeat for the other collections listed in the summary. */ + if (g_variant_dict_lookup (&additional_metadata, OSTREE_SUMMARY_COLLECTION_MAP, "a{sa(s(taya{sv}))}", &collection_map)) + { + while (g_variant_iter_loop (collection_map, "{sa(s(taya{sv}))}", &collection_id, &ref_map)) + { + if (!ostree_validate_collection_id (collection_id, error)) + return FALSE; + if (!fill_refs_and_checksums_from_summary_map (&ref_map, collection_id, refs_and_checksums, error)) + return FALSE; + } + } + + return TRUE; +} + +/* Given a summary file (@summary_bytes), extract the refs it lists, and use that + * to fill in the checksums in the @supported_ref_to_checksum map. This includes + * the main refs list in the summary, and the map of collection IDs to further + * refs lists. + * + * The @summary is validated as it’s explored; on error, @error will be + * set and %FALSE will be returned. If the intersection of the summary file refs + * and the keys in @supported_ref_to_checksum is empty, an error is set. */ +static gboolean +get_refs_and_checksums_from_summary (GBytes *summary_bytes, + GHashTable *supported_ref_to_checksum /* (element-type OstreeCollectionRef utf8) */, + GError **error) +{ + g_autoptr(GVariant) summary = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, summary_bytes, FALSE)); + GHashTableIter iter; + const OstreeCollectionRef *ref; + const gchar *checksum; + + if (!g_variant_is_normal_form (summary)) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Not normal form"); + return FALSE; + } + if (!g_variant_is_of_type (summary, OSTREE_SUMMARY_GVARIANT_FORMAT)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Doesn't match variant type '%s'", + (char *)OSTREE_SUMMARY_GVARIANT_FORMAT); + return FALSE; + } + + if (!fill_refs_and_checksums_from_summary (summary, supported_ref_to_checksum, error)) + return FALSE; + + /* Check that at least one of the refs has a non-%NULL checksum set, otherwise + * we can discard this peer. */ + g_hash_table_iter_init (&iter, supported_ref_to_checksum); + while (g_hash_table_iter_next (&iter, + (gpointer *) &ref, + (gpointer *) &checksum)) + { + if (checksum != NULL) + return TRUE; + } + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "No matching refs were found in the summary file"); + return FALSE; +} + +/* Download the summary file from @remote, and return the bytes of the file in + * @out_summary_bytes. */ +static gboolean +fetch_summary_from_remote (OstreeRepo *repo, + OstreeRemote *remote, + GBytes **out_summary_bytes, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GBytes) summary_bytes = NULL; + gboolean remote_already_existed = _ostree_repo_add_remote (repo, remote); + gboolean success = ostree_repo_remote_fetch_summary_with_options (repo, + remote->name, + NULL /* options */, + &summary_bytes, + NULL /* signature */, + cancellable, + error); + + if (!remote_already_existed) + _ostree_repo_remove_remote (repo, remote); + + if (!success) + return FALSE; + + g_assert (out_summary_bytes != NULL); + *out_summary_bytes = g_steal_pointer (&summary_bytes); + return TRUE; +} +#endif /* HAVE_AVAHI */ + +struct _OstreeRepoFinderAvahi +{ + GObject parent_instance; + +#ifdef HAVE_AVAHI + /* All elements of this structure must only be accessed from @avahi_context + * after construction. */ + + /* Note: There is a ref-count loop here: each #GTask has a reference to the + * #OstreeRepoFinderAvahi, and we have to keep a reference to the #GTask. */ + GPtrArray *resolve_tasks; /* (element-type (owned) GTask) */ + + AvahiGLibPoll *poll; + AvahiClient *client; + AvahiServiceBrowser *browser; + + AvahiClientState client_state; + gboolean browser_failed; + gboolean browser_all_for_now; + + GCancellable *avahi_cancellable; + GMainContext *avahi_context; + + /* Map of service name (typically human readable) to a #GPtrArray of the + * #AvahiServiceResolver instances we have running against that name. We + * could end up with more than one resolver if the same name is advertised to + * us over multiple interfaces or protocols (for example, IPv4 and IPv6). + * Resolve all of them just in case one doesn’t work. */ + GHashTable *resolvers; /* (element-type (owned) utf8 (owned) GPtrArray (element-type (owned) AvahiServiceResolver)) */ + + /* Array of #OstreeAvahiService instances representing all the services which + * we currently think are valid. */ + GPtrArray *found_services; /* (element-type (owned OstreeAvahiService) */ +#endif /* HAVE_AVAHI */ +}; + +static void ostree_repo_finder_avahi_iface_init (OstreeRepoFinderInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (OstreeRepoFinderAvahi, ostree_repo_finder_avahi, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (OSTREE_TYPE_REPO_FINDER, ostree_repo_finder_avahi_iface_init)) + +#ifdef HAVE_AVAHI + +/* Download the summary file from @remote and fill in the checksums in the given + * @supported_ref_to_checksum hash table, given the existing refs in it as keys. + * See get_refs_and_checksums_from_summary() for more details. */ +static gboolean +get_checksums (OstreeRepoFinderAvahi *finder, + OstreeRepo *repo, + OstreeRemote *remote, + GHashTable *supported_ref_to_checksum /* (element-type OstreeCollectionRef utf8) */, + GError **error) +{ + g_autoptr(GBytes) summary_bytes = NULL; + + if (!fetch_summary_from_remote (repo, + remote, + &summary_bytes, + finder->avahi_cancellable, + error)) + return FALSE; + + return get_refs_and_checksums_from_summary (summary_bytes, supported_ref_to_checksum, error); +} + +/* Build some #OstreeRepoFinderResults out of the given #OstreeAvahiService by + * parsing its DNS-SD TXT records and finding the intersection between the refs + * it advertises and @refs. One or more results will be added to @results, with + * multiple results being added if the intersection of refs covers refs which + * need different GPG keyrings. One result is added per (uri, keyring) pair. + * + * If any of the TXT records are malformed or missing, or if the intersection of + * refs is empty, return early without modifying @results. + * + * This recognises the following TXT records: + * - `v` (`y`): Version of the TXT record format. Only version `1` is currently + * supported. + * - `rb` (`(yyay)`): Bloom filter indicating which refs are served by the peer. + * - `st` (`t`): Timestamp (seconds since the Unix epoch, big endian) the + * summary file was last modified. + * - `ri` (`q`): Repository index, indicating which of several repositories + * hosted on the peer this is. Big endian. + */ +static void +ostree_avahi_service_build_repo_finder_result (OstreeAvahiService *service, + OstreeRepoFinderAvahi *finder, + OstreeRepo *parent_repo, + gint priority, + const OstreeCollectionRef * const *refs, + GPtrArray *results, + GCancellable *cancellable) +{ + g_autoptr(GHashTable) attributes = NULL; + g_autoptr(GVariant) version = NULL; + g_autoptr(GVariant) bloom = NULL; + g_autoptr(GVariant) summary_timestamp = NULL; + g_autoptr(GVariant) repo_index = NULL; + g_autofree gchar *repo_path = NULL; + g_autoptr(GPtrArray) possible_refs = NULL; /* (element-type OstreeCollectionRef) */ + SoupURI *_uri = NULL; + g_autofree gchar *uri = NULL; + g_autoptr(GError) error = NULL; + gsize i; + g_autoptr(GHashTable) repo_to_refs = NULL; /* (element-type UriAndKeyring GHashTable) */ + GHashTable *supported_ref_to_checksum; /* (element-type OstreeCollectionRef utf8) */ + GHashTableIter iter; + UriAndKeyring *repo; + + g_return_if_fail (service != NULL); + g_return_if_fail (refs != NULL); + + attributes = _ostree_txt_records_parse (service->txt); + + /* Check the record version. */ + version = _ostree_txt_records_lookup_variant (attributes, "v", G_VARIANT_TYPE_BYTE); + + if (g_variant_get_byte (version) != 1) + { + g_debug ("Unknown v=%02x attribute provided in TXT record. Ignoring.", + g_variant_get_byte (version)); + return; + } + + /* Refs bloom filter? */ + bloom = _ostree_txt_records_lookup_variant (attributes, "rb", G_VARIANT_TYPE ("(yyay)")); + + if (bloom == NULL) + { + g_debug ("Missing rb (refs bloom) attribute in TXT record. Ignoring."); + return; + } + + possible_refs = bloom_refs_intersection (bloom, refs); + if (possible_refs == NULL) + { + g_debug ("Wrong k parameter or hash id in rb (refs bloom) attribute in TXT record. Ignoring."); + return; + } + if (possible_refs->len == 0) + { + g_debug ("TXT record definitely has no matching refs. Ignoring."); + return; + } + + /* Summary timestamp. */ + summary_timestamp = _ostree_txt_records_lookup_variant (attributes, "st", G_VARIANT_TYPE_UINT64); + if (summary_timestamp == NULL) + { + g_debug ("Missing st (summary timestamp) attribute in TXT record. Ignoring."); + return; + } + + /* Repository index. */ + repo_index = _ostree_txt_records_lookup_variant (attributes, "ri", G_VARIANT_TYPE_UINT16); + if (repo_index == NULL) + { + g_debug ("Missing ri (repository index) attribute in TXT record. Ignoring."); + return; + } + repo_path = g_strdup_printf ("/%u", GUINT16_FROM_BE (g_variant_get_uint16 (repo_index))); + + /* Create a new result for each keyring needed by @possible_refs. Typically, + * there will be a separate keyring per collection, but some might be shared. */ + repo_to_refs = g_hash_table_new_full (uri_and_keyring_hash, uri_and_keyring_equal, + (GDestroyNotify) uri_and_keyring_free, (GDestroyNotify) g_hash_table_unref); + + _uri = soup_uri_new (NULL); + soup_uri_set_scheme (_uri, "http"); + soup_uri_set_host (_uri, service->address); + soup_uri_set_port (_uri, service->port); + soup_uri_set_path (_uri, repo_path); + uri = soup_uri_to_string (_uri, FALSE); + soup_uri_free (_uri); + + for (i = 0; i < possible_refs->len; i++) + { + const OstreeCollectionRef *ref = g_ptr_array_index (possible_refs, i); + g_autofree gchar *keyring = NULL; + g_autoptr(UriAndKeyring) resolved_repo = NULL; + + /* Look up the GPG keyring for this ref. */ + keyring = ostree_repo_resolve_keyring_for_collection (parent_repo, ref->collection_id, + cancellable, &error); + + if (keyring == NULL) + { + g_debug ("Ignoring ref (%s, %s) on host ‘%s’ due to missing keyring: %s", + ref->collection_id, refs[i]->ref_name, service->address, + error->message); + g_clear_error (&error); + continue; + } + + /* Add this repo to the results, keyed by the canonicalised repository URI + * to deduplicate the results. */ + g_debug ("Resolved ref (%s, %s) to repo URI ‘%s’ with keyring ‘%s’.", + ref->collection_id, ref->ref_name, uri, keyring); + + resolved_repo = uri_and_keyring_new (uri, keyring); + + supported_ref_to_checksum = g_hash_table_lookup (repo_to_refs, resolved_repo); + + if (supported_ref_to_checksum == NULL) + { + supported_ref_to_checksum = g_hash_table_new_full (ostree_collection_ref_hash, + ostree_collection_ref_equal, + NULL, g_free); + g_hash_table_insert (repo_to_refs, g_steal_pointer (&resolved_repo), supported_ref_to_checksum /* transfer */); + } + + /* Add a placeholder to @supported_ref_to_checksum for this ref. It will + * be filled out by the get_checksums() call below. */ + g_hash_table_insert (supported_ref_to_checksum, (gpointer) ref, NULL); + } + + /* Aggregate the results. */ + g_hash_table_iter_init (&iter, repo_to_refs); + + while (g_hash_table_iter_next (&iter, (gpointer *) &repo, (gpointer *) &supported_ref_to_checksum)) + { + g_autoptr(OstreeRemote) remote = NULL; + + /* Build an #OstreeRemote. Use the escaped URI, since remote->name + * is used in file paths, so needs to not contain special characters. */ + g_autofree gchar *name = uri_and_keyring_to_name (repo); + remote = ostree_remote_new (name); + + g_clear_pointer (&remote->keyring, g_free); + remote->keyring = g_strdup (repo->keyring); + + g_key_file_set_string (remote->options, remote->group, "url", repo->uri); + g_key_file_set_boolean (remote->options, remote->group, "gpg-verify", TRUE); + g_key_file_set_boolean (remote->options, remote->group, "gpg-verify-summary", TRUE); + + get_checksums (finder, parent_repo, remote, supported_ref_to_checksum, &error); + if (error != NULL) + { + g_debug ("Failed to get checksums for possible refs; ignoring: %s", error->message); + g_clear_error (&error); + continue; + } + + g_ptr_array_add (results, ostree_repo_finder_result_new (remote, OSTREE_REPO_FINDER (finder), + priority, supported_ref_to_checksum, + (summary_timestamp != NULL) ? GUINT64_FROM_BE (g_variant_get_uint64 (summary_timestamp)) : 0)); + } +} + +typedef struct +{ + OstreeCollectionRef **refs; /* (owned) (array zero-terminated=1) */ + OstreeRepo *parent_repo; /* (owned) */ +} ResolveData; + +static void +resolve_data_free (ResolveData *data) +{ + g_object_unref (data->parent_repo); + ostree_collection_ref_freev (data->refs); + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (ResolveData, resolve_data_free) + +static ResolveData * +resolve_data_new (const OstreeCollectionRef * const *refs, + OstreeRepo *parent_repo) +{ + g_autoptr(ResolveData) data = NULL; + + data = g_new0 (ResolveData, 1); + data->refs = ostree_collection_ref_dupv (refs); + data->parent_repo = g_object_ref (parent_repo); + + return g_steal_pointer (&data); +} + +static void +fail_all_pending_tasks (OstreeRepoFinderAvahi *self, + GQuark domain, + gint code, + const gchar *format, + ...) G_GNUC_PRINTF(4, 5); + +/* Executed in @self->avahi_context. + * + * Return the given error from all the pending resolve tasks in + * self->resolve_tasks. */ +static void +fail_all_pending_tasks (OstreeRepoFinderAvahi *self, + GQuark domain, + gint code, + const gchar *format, + ...) +{ + gsize i; + va_list args; + g_autoptr(GError) error = NULL; + + g_assert (g_main_context_is_owner (self->avahi_context)); + + va_start (args, format); + error = g_error_new_valist (domain, code, format, args); + va_end (args); + + for (i = 0; i < self->resolve_tasks->len; i++) + { + GTask *task = G_TASK (g_ptr_array_index (self->resolve_tasks, i)); + g_task_return_error (task, g_error_copy (error)); + } + + g_ptr_array_set_size (self->resolve_tasks, 0); +} + +static gint +results_compare_cb (gconstpointer a, + gconstpointer b) +{ + const OstreeRepoFinderResult *result_a = *((const OstreeRepoFinderResult **) a); + const OstreeRepoFinderResult *result_b = *((const OstreeRepoFinderResult **) b); + + return ostree_repo_finder_result_compare (result_a, result_b); +} + +/* Executed in @self->avahi_context. + * + * For each of the pending resolve tasks in self->resolve_tasks, calculate and + * return the result set for its query given the currently known services from + * Avahi which are stored in self->found_services. */ +static void +complete_all_pending_tasks (OstreeRepoFinderAvahi *self) +{ + gsize i; + const gint priority = 60; /* arbitrarily chosen */ + g_autoptr(GPtrArray) results_for_tasks = g_ptr_array_new_full (self->resolve_tasks->len, (GDestroyNotify)g_ptr_array_unref); + gboolean cancelled = FALSE; + + g_assert (g_main_context_is_owner (self->avahi_context)); + g_debug ("%s: Completing %u tasks", G_STRFUNC, self->resolve_tasks->len); + + for (i = 0; i < self->resolve_tasks->len; i++) + { + g_autoptr(GPtrArray) results = NULL; + GTask *task; + ResolveData *data; + const OstreeCollectionRef * const *refs; + gsize j; + + task = G_TASK (g_ptr_array_index (self->resolve_tasks, i)); + data = g_task_get_task_data (task); + refs = (const OstreeCollectionRef * const *) data->refs; + results = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_repo_finder_result_free); + + for (j = 0; j < self->found_services->len; j++) + { + OstreeAvahiService *service = g_ptr_array_index (self->found_services, j); + + ostree_avahi_service_build_repo_finder_result (service, self, data->parent_repo, + priority, refs, results, + self->avahi_cancellable); + if (g_cancellable_is_cancelled (self->avahi_cancellable)) + { + cancelled = TRUE; + break; + } + } + if (cancelled) + break; + + g_ptr_array_add (results_for_tasks, g_steal_pointer (&results)); + } + + if (!cancelled) + { + for (i = 0; i < self->resolve_tasks->len; i++) + { + GTask *task = G_TASK (g_ptr_array_index (self->resolve_tasks, i)); + GPtrArray *results = g_ptr_array_index (results_for_tasks, i); + + g_ptr_array_sort (results, results_compare_cb); + + g_task_return_pointer (task, + g_ptr_array_ref (results), + (GDestroyNotify) g_ptr_array_unref); + } + + g_ptr_array_set_size (self->resolve_tasks, 0); + } + else + { + fail_all_pending_tasks (self, G_IO_ERROR, G_IO_ERROR_CANCELLED, + "Avahi service resolution cancelled."); + } +} + +/* Executed in @self->avahi_context. */ +static void +maybe_complete_all_pending_tasks (OstreeRepoFinderAvahi *self) +{ + g_assert (g_main_context_is_owner (self->avahi_context)); + g_debug ("%s: client_state: %s, browser_failed: %u, cancelled: %u, " + "browser_all_for_now: %u, n_resolvers: %u", + G_STRFUNC, ostree_avahi_client_state_to_string (self->client_state), + self->browser_failed, + g_cancellable_is_cancelled (self->avahi_cancellable), + self->browser_all_for_now, g_hash_table_size (self->resolvers)); + + if (self->client_state == AVAHI_CLIENT_FAILURE) + fail_all_pending_tasks (self, G_IO_ERROR, G_IO_ERROR_FAILED, + "Avahi client error: %s", + avahi_strerror (avahi_client_errno (self->client))); + else if (self->browser_failed) + fail_all_pending_tasks (self, G_IO_ERROR, G_IO_ERROR_FAILED, + "Avahi browser error: %s", + avahi_strerror (avahi_client_errno (self->client))); + else if (g_cancellable_is_cancelled (self->avahi_cancellable)) + fail_all_pending_tasks (self, G_IO_ERROR, G_IO_ERROR_CANCELLED, + "Avahi service resolution cancelled."); + else if (self->browser_all_for_now && + g_hash_table_size (self->resolvers) == 0) + complete_all_pending_tasks (self); +} + +/* Executed in @self->avahi_context. */ +static void +client_cb (AvahiClient *client, + AvahiClientState state, + void *finder_ptr) +{ + /* Completing the pending tasks might drop the final reference to @self. */ + g_autoptr(OstreeRepoFinderAvahi) self = g_object_ref (finder_ptr); + + /* self->client will be NULL if client_cb() is called from + * ostree_repo_finder_avahi_start(). */ + g_assert (self->client == NULL || g_main_context_is_owner (self->avahi_context)); + + g_debug ("%s: Entered state ‘%s’.", + G_STRFUNC, ostree_avahi_client_state_to_string (state)); + + /* We only care about entering and leaving %AVAHI_CLIENT_FAILURE. */ + self->client_state = state; + if (self->client != NULL) + maybe_complete_all_pending_tasks (self); +} + +/* Executed in @self->avahi_context. */ +static void +resolve_cb (AvahiServiceResolver *resolver, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiResolverEvent event, + const char *name, + const char *type, + const char *domain, + const char *host_name, + const AvahiAddress *address, + uint16_t port, + AvahiStringList *txt, + AvahiLookupResultFlags flags, + void *finder_ptr) +{ + /* Completing the pending tasks might drop the final reference to @self. */ + g_autoptr(OstreeRepoFinderAvahi) self = g_object_ref (finder_ptr); + g_autoptr(OstreeAvahiService) service = NULL; + GPtrArray *resolvers; + + g_assert (g_main_context_is_owner (self->avahi_context)); + + g_debug ("%s: Resolve event ‘%s’ for name ‘%s’.", + G_STRFUNC, ostree_avahi_resolver_event_to_string (event), name); + + /* Track the resolvers active for this @name. There may be several, + * as @name might appear to us over several interfaces or protocols. Most + * commonly this happens when both hosts are connected via IPv4 and IPv6. */ + resolvers = g_hash_table_lookup (self->resolvers, name); + + if (resolvers == NULL || resolvers->len == 0) + { + /* maybe it was removed in the meantime */ + g_hash_table_remove (self->resolvers, name); + return; + } + else if (resolvers->len == 1) + { + g_hash_table_remove (self->resolvers, name); + } + else + { + g_ptr_array_remove_fast (resolvers, resolver); + } + + /* Was resolution successful? */ + switch (event) + { + case AVAHI_RESOLVER_FOUND: + service = ostree_avahi_service_new (name, domain, address, interface, + port, txt); + g_ptr_array_add (self->found_services, g_steal_pointer (&service)); + break; + case AVAHI_RESOLVER_FAILURE: + default: + g_warning ("Failed to resolve service ‘%s’: %s", name, + avahi_strerror (avahi_client_errno (self->client))); + break; + } + + maybe_complete_all_pending_tasks (self); +} + +/* Executed in @self->avahi_context. */ +static void +browse_new (OstreeRepoFinderAvahi *self, + AvahiIfIndex interface, + AvahiProtocol protocol, + const gchar *name, + const gchar *type, + const gchar *domain) +{ + g_autoptr(AvahiServiceResolver) resolver = NULL; + GPtrArray *resolvers; /* (element-type AvahiServiceResolver) */ + + g_assert (g_main_context_is_owner (self->avahi_context)); + + resolver = avahi_service_resolver_new (self->client, + interface, + protocol, + name, + type, + domain, + AVAHI_PROTO_UNSPEC, + 0, + resolve_cb, + self); + if (resolver == NULL) + { + g_warning ("Failed to resolve service ‘%s’: %s", name, + avahi_strerror (avahi_client_errno (self->client))); + return; + } + + g_debug ("Found name service %s on the network; type: %s, domain: %s, " + "protocol: %u, interface: %u", name, type, domain, protocol, + interface); + + /* Start a resolver for this (interface, protocol, name, type, domain) + * combination. */ + resolvers = g_hash_table_lookup (self->resolvers, name); + if (resolvers == NULL) + { + resolvers = g_ptr_array_new_with_free_func ((GDestroyNotify) avahi_service_resolver_free); + g_hash_table_insert (self->resolvers, g_strdup (name), resolvers); + } + + g_ptr_array_add (resolvers, g_steal_pointer (&resolver)); +} + +/* Executed in @self->avahi_context. Caller must call maybe_complete_all_pending_tasks(). */ +static void +browse_remove (OstreeRepoFinderAvahi *self, + const char *name) +{ + gsize i; + gboolean removed = FALSE; + + g_assert (g_main_context_is_owner (self->avahi_context)); + + g_hash_table_remove (self->resolvers, name); + + for (i = 0; i < self->found_services->len; i += (removed ? 0 : 1)) + { + OstreeAvahiService *service = g_ptr_array_index (self->found_services, i); + + removed = FALSE; + + if (g_strcmp0 (service->name, name) == 0) + { + g_ptr_array_remove_index_fast (self->found_services, i); + removed = TRUE; + continue; + } + } +} + +/* Executed in @self->avahi_context. */ +static void +browse_cb (AvahiServiceBrowser *browser, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *type, + const char *domain, + AvahiLookupResultFlags flags, + void *finder_ptr) +{ + /* Completing the pending tasks might drop the final reference to @self. */ + g_autoptr(OstreeRepoFinderAvahi) self = g_object_ref (finder_ptr); + + g_assert (g_main_context_is_owner (self->avahi_context)); + + g_debug ("%s: Browse event ‘%s’ for name ‘%s’.", + G_STRFUNC, ostree_avahi_browser_event_to_string (event), name); + + self->browser_failed = FALSE; + + switch (event) + { + case AVAHI_BROWSER_NEW: + browse_new (self, interface, protocol, name, type, domain); + break; + + case AVAHI_BROWSER_REMOVE: + browse_remove (self, name); + break; + + case AVAHI_BROWSER_CACHE_EXHAUSTED: + /* don’t care about this. */ + break; + + case AVAHI_BROWSER_ALL_FOR_NOW: + self->browser_all_for_now = TRUE; + break; + + case AVAHI_BROWSER_FAILURE: + self->browser_failed = TRUE; + break; + + default: + g_assert_not_reached (); + } + + /* Check all the tasks for any event, since the @browser_failed state + * may have changed. */ + maybe_complete_all_pending_tasks (self); +} + +static gboolean add_resolve_task_cb (gpointer user_data); +#endif /* HAVE_AVAHI */ + +static void +ostree_repo_finder_avahi_resolve_async (OstreeRepoFinder *finder, + const OstreeCollectionRef * const *refs, + OstreeRepo *parent_repo, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + OstreeRepoFinderAvahi *self = OSTREE_REPO_FINDER_AVAHI (finder); + g_autoptr(GTask) task = NULL; + + g_debug ("%s: Starting resolving", G_STRFUNC); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, ostree_repo_finder_avahi_resolve_async); + +#ifdef HAVE_AVAHI + g_task_set_task_data (task, resolve_data_new (refs, parent_repo), (GDestroyNotify) resolve_data_free); + + /* Move @task to the @avahi_context where it can be processed. */ + g_main_context_invoke (self->avahi_context, add_resolve_task_cb, g_steal_pointer (&task)); +#else /* if !HAVE_AVAHI */ + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Avahi support was not compiled in to libostree"); +#endif /* !HAVE_AVAHI */ +} + +#ifdef HAVE_AVAHI +/* Executed in @self->avahi_context. */ +static gboolean +add_resolve_task_cb (gpointer user_data) +{ + g_autoptr(GTask) task = G_TASK (user_data); + OstreeRepoFinderAvahi *self = g_task_get_source_object (task); + + g_assert (g_main_context_is_owner (self->avahi_context)); + g_debug ("%s", G_STRFUNC); + + /* Track the task and check to see if the browser and resolvers are in a + * quiescent state suitable for returning a result immediately. */ + g_ptr_array_add (self->resolve_tasks, g_object_ref (task)); + maybe_complete_all_pending_tasks (self); + + return G_SOURCE_REMOVE; +} +#endif /* HAVE_AVAHI */ + +static GPtrArray * +ostree_repo_finder_avahi_resolve_finish (OstreeRepoFinder *finder, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, finder), NULL); + return g_task_propagate_pointer (G_TASK (result), error); +} + +static void +ostree_repo_finder_avahi_dispose (GObject *obj) +{ +#ifdef HAVE_AVAHI + OstreeRepoFinderAvahi *self = OSTREE_REPO_FINDER_AVAHI (obj); + + ostree_repo_finder_avahi_stop (self); + + g_assert (self->resolve_tasks == NULL || self->resolve_tasks->len == 0); + + g_clear_pointer (&self->resolve_tasks, g_ptr_array_unref); + g_clear_pointer (&self->browser, avahi_service_browser_free); + g_clear_pointer (&self->client, avahi_client_free); + g_clear_pointer (&self->poll, avahi_glib_poll_free); + g_clear_pointer (&self->avahi_context, g_main_context_unref); + g_clear_pointer (&self->found_services, g_ptr_array_unref); + g_clear_pointer (&self->resolvers, g_hash_table_unref); + g_clear_object (&self->avahi_cancellable); +#endif /* HAVE_AVAHI */ + + /* Chain up. */ + G_OBJECT_CLASS (ostree_repo_finder_avahi_parent_class)->dispose (obj); +} + +static void +ostree_repo_finder_avahi_class_init (OstreeRepoFinderAvahiClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = ostree_repo_finder_avahi_dispose; +} + +static void +ostree_repo_finder_avahi_iface_init (OstreeRepoFinderInterface *iface) +{ + iface->resolve_async = ostree_repo_finder_avahi_resolve_async; + iface->resolve_finish = ostree_repo_finder_avahi_resolve_finish; +} + +static void +ostree_repo_finder_avahi_init (OstreeRepoFinderAvahi *self) +{ +#ifdef HAVE_AVAHI + self->resolve_tasks = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); + self->avahi_cancellable = g_cancellable_new (); + self->client_state = AVAHI_CLIENT_S_REGISTERING; + self->resolvers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref); + self->found_services = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_avahi_service_free); +#endif /* HAVE_AVAHI */ +} + +/** + * ostree_repo-finder_avahi_new: + * @context: (transfer none) (nullable): a #GMainContext for processing Avahi + * events in, or %NULL to use the current thread-default + * + * Create a new #OstreeRepoFinderAvahi instance. It is intended that one such + * instance be created per process, and it be used to answer all resolution + * requests from #OstreeRepos. + * + * The calling code is responsible for ensuring that @context is iterated while + * the #OstreeRepoFinderAvahi is running (after ostree_repo_finder_avahi_start() + * is called). This may be done from any thread. + * + * If @context is %NULL, the current thread-default #GMainContext is used. + * + * Returns: (transfer full): a new #OstreeRepoFinderAvahi + * Since: 2017.8 + */ +OstreeRepoFinderAvahi * +ostree_repo_finder_avahi_new (GMainContext *context) +{ + g_autoptr(OstreeRepoFinderAvahi) finder = NULL; + + finder = g_object_new (OSTREE_TYPE_REPO_FINDER_AVAHI, NULL); + +#ifdef HAVE_AVAHI + /* FIXME: Make this a property */ + if (context != NULL) + finder->avahi_context = g_main_context_ref (context); + else + finder->avahi_context = g_main_context_ref_thread_default (); + + /* Avahi setup. Note: Technically the allocator is per-process state which we + * shouldn’t set here, but it’s probably fine. It’s unlikely that code which + * is using libostree is going to use an allocator which is not GLib, and + * *also* use Avahi API itself. */ + avahi_set_allocator (avahi_glib_allocator ()); + finder->poll = avahi_glib_poll_new (finder->avahi_context, G_PRIORITY_DEFAULT); +#endif /* HAVE_AVAHI */ + + return g_steal_pointer (&finder); +} + +/** + * ostree_repo_finder_avahi_start: + * @self: an #OstreeRepoFinderAvahi + * @error: return location for a #GError + * + * Start monitoring the local network for peers who are advertising OSTree + * repositories, using Avahi. In order for this to work, the #GMainContext + * passed to @self at construction time must be iterated (so it will typically + * be the global #GMainContext, or be a separate #GMainContext in a worker + * thread). + * + * This will return an error (%G_IO_ERROR_FAILED) if initialisation fails, or if + * Avahi support is not available (%G_IO_ERROR_NOT_SUPPORTED). In either case, + * the #OstreeRepoFinderAvahi instance is useless afterwards and should be + * destroyed. + * + * Call ostree_repo_finder_avahi_stop() to stop the repo finder. + * + * It is an error to call this function multiple times on the same + * #OstreeRepoFinderAvahi instance, or to call it after + * ostree_repo_finder_avahi_stop(). + * + * Since: 2017.8 + */ +void +ostree_repo_finder_avahi_start (OstreeRepoFinderAvahi *self, + GError **error) +{ + g_return_if_fail (OSTREE_IS_REPO_FINDER_AVAHI (self)); + g_return_if_fail (error == NULL || *error == NULL); + +#ifdef HAVE_AVAHI + g_autoptr(AvahiClient) client = NULL; + g_autoptr(AvahiServiceBrowser) browser = NULL; + int failure = 0; + + if (g_cancellable_set_error_if_cancelled (self->avahi_cancellable, error)) + return; + + g_assert (self->client == NULL); + + client = avahi_client_new (avahi_glib_poll_get (self->poll), + AVAHI_CLIENT_NO_FAIL, + client_cb, self, &failure); + + if (client == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to create finder client: %s", + avahi_strerror (failure)); + return; + } + + /* Query for the OSTree DNS-SD service on the local network. */ + browser = avahi_service_browser_new (client, + AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, + OSTREE_AVAHI_SERVICE_TYPE, + NULL, + 0, + browse_cb, + self); + + if (browser == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to create service browser: %s", + avahi_strerror (avahi_client_errno (client))); + return; + } + + /* Success. */ + self->client = g_steal_pointer (&client); + self->browser = g_steal_pointer (&browser); +#else /* if !HAVE_AVAHI */ + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "Avahi support was not compiled in to libostree"); +#endif /* !HAVE_AVAHI */ +} + +#ifdef HAVE_AVAHI +static gboolean stop_cb (gpointer user_data); +#endif /* HAVE_AVAHI */ + +/** + * ostree_repo_finder_avahi_stop: + * @self: an #OstreeRepoFinderAvahi + * + * Stop monitoring the local network for peers who are advertising OSTree + * repositories. If any resolve tasks (from ostree_repo_finder_resolve_async()) + * are in progress, they will be cancelled and will return %G_IO_ERROR_CANCELLED. + * + * Call ostree_repo_finder_avahi_start() to start the repo finder. + * + * It is an error to call this function multiple times on the same + * #OstreeRepoFinderAvahi instance, or to call it before + * ostree_repo_finder_avahi_start(). + * + * Since: 2017.8 + */ +void +ostree_repo_finder_avahi_stop (OstreeRepoFinderAvahi *self) +{ + g_return_if_fail (OSTREE_IS_REPO_FINDER_AVAHI (self)); + +#ifdef HAVE_AVAHI + if (self->browser == NULL) + return; + + g_main_context_invoke (self->avahi_context, stop_cb, g_object_ref (self)); +#endif /* HAVE_AVAHI */ +} + +#ifdef HAVE_AVAHI +static gboolean +stop_cb (gpointer user_data) +{ + g_autoptr(OstreeRepoFinderAvahi) self = OSTREE_REPO_FINDER_AVAHI (user_data); + + g_cancellable_cancel (self->avahi_cancellable); + maybe_complete_all_pending_tasks (self); + + g_clear_pointer (&self->browser, avahi_service_browser_free); + g_clear_pointer (&self->client, avahi_client_free); + g_hash_table_remove_all (self->resolvers); + + return G_SOURCE_REMOVE; +} +#endif /* HAVE_AVAHI */ diff --git a/src/libostree/ostree-repo-finder-avahi.h b/src/libostree/ostree-repo-finder-avahi.h new file mode 100644 index 00000000..98d37723 --- /dev/null +++ b/src/libostree/ostree-repo-finder-avahi.h @@ -0,0 +1,62 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#pragma once + +#include +#include +#include + +#include "ostree-repo-finder.h" +#include "ostree-types.h" + +G_BEGIN_DECLS + +#define OSTREE_TYPE_REPO_FINDER_AVAHI (ostree_repo_finder_avahi_get_type ()) + +/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44): +_OSTREE_PUBLIC +G_DECLARE_FINAL_TYPE (OstreeRepoFinderAvahi, ostree_repo_finder_avahi, OSTREE, REPO_FINDER_AVAHI, GObject) */ + +_OSTREE_PUBLIC +GType ostree_repo_finder_avahi_get_type (void); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +typedef struct _OstreeRepoFinderAvahi OstreeRepoFinderAvahi; +typedef struct { GObjectClass parent_class; } OstreeRepoFinderAvahiClass; + +static inline OstreeRepoFinderAvahi *OSTREE_REPO_FINDER_AVAHI (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_repo_finder_avahi_get_type (), OstreeRepoFinderAvahi); } +static inline gboolean OSTREE_IS_REPO_FINDER_AVAHI (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_repo_finder_avahi_get_type ()); } +G_GNUC_END_IGNORE_DEPRECATIONS + +_OSTREE_PUBLIC +OstreeRepoFinderAvahi *ostree_repo_finder_avahi_new (GMainContext *context); + +_OSTREE_PUBLIC +void ostree_repo_finder_avahi_start (OstreeRepoFinderAvahi *self, + GError **error); + +_OSTREE_PUBLIC +void ostree_repo_finder_avahi_stop (OstreeRepoFinderAvahi *self); + +G_END_DECLS diff --git a/src/libostree/ostree-repo-finder-config.c b/src/libostree/ostree-repo-finder-config.c new file mode 100644 index 00000000..79a63536 --- /dev/null +++ b/src/libostree/ostree-repo-finder-config.c @@ -0,0 +1,235 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "ostree-remote-private.h" +#include "ostree-repo.h" +#include "ostree-repo-private.h" +#include "ostree-repo-finder.h" +#include "ostree-repo-finder-config.h" + +/** + * SECTION:ostree-repo-finder-config + * @title: OstreeRepoFinderConfig + * @short_description: Finds remote repositories from ref names using the local + * repository configuration files + * @stability: Unstable + * @include: libostree/ostree-repo-finder-config.h + * + * #OstreeRepoFinderConfig is an implementation of #OstreeRepoFinder which looks + * refs up in locally configured remotes and returns remote URIs. + * Duplicate remote URIs are combined into a single #OstreeRepoFinderResult + * which lists multiple refs. + * + * For all the locally configured remotes which have an `collection-id` specified + * (see [ostree.repo-config(5)](man:ostree.repo-config(5))), it finds the + * intersection of their refs and the set of refs to resolve. If the + * intersection is non-empty, that remote is returned as a result. Remotes which + * do not have their `collection-id` key configured are ignored. + * + * Since: 2017.8 + */ + +static void ostree_repo_finder_config_iface_init (OstreeRepoFinderInterface *iface); + +struct _OstreeRepoFinderConfig +{ + GObject parent_instance; +}; + +G_DEFINE_TYPE_WITH_CODE (OstreeRepoFinderConfig, ostree_repo_finder_config, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (OSTREE_TYPE_REPO_FINDER, ostree_repo_finder_config_iface_init)) + +static gint +results_compare_cb (gconstpointer a, + gconstpointer b) +{ + const OstreeRepoFinderResult *result_a = *((const OstreeRepoFinderResult **) a); + const OstreeRepoFinderResult *result_b = *((const OstreeRepoFinderResult **) b); + + return ostree_repo_finder_result_compare (result_a, result_b); +} + +static void +ostree_repo_finder_config_resolve_async (OstreeRepoFinder *finder, + const OstreeCollectionRef * const *refs, + OstreeRepo *parent_repo, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + g_autoptr(GPtrArray) results = NULL; + const gint priority = 100; /* arbitrarily chosen; lower than the others */ + gsize i, j; + g_autoptr(GHashTable) repo_name_to_refs = NULL; /* (element-type utf8 GHashTable) */ + GHashTable *supported_ref_to_checksum; /* (element-type OstreeCollectionRef utf8) */ + GHashTableIter iter; + const gchar *remote_name; + g_auto(GStrv) remotes = NULL; + gsize n_remotes = 0; + + task = g_task_new (finder, cancellable, callback, user_data); + g_task_set_source_tag (task, ostree_repo_finder_config_resolve_async); + results = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_repo_finder_result_free); + repo_name_to_refs = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, + (GDestroyNotify) g_hash_table_unref); + + /* List all remotes in this #OstreeRepo and see which of their ref lists + * intersect with @refs. */ + remotes = ostree_repo_remote_list (parent_repo, (guint *) &n_remotes); + + g_debug ("%s: Checking %" G_GSIZE_FORMAT " remotes", G_STRFUNC, n_remotes); + + for (i = 0; i < n_remotes; i++) + { + g_autoptr(GError) local_error = NULL; + g_autoptr(GHashTable) remote_refs = NULL; /* (element-type utf8 utf8) */ + const gchar *checksum; + g_autofree gchar *remote_collection_id = NULL; + + remote_name = remotes[i]; + + if (!ostree_repo_get_remote_option (parent_repo, remote_name, "collection-id", + NULL, &remote_collection_id, &local_error) || + !ostree_validate_collection_id (remote_collection_id, &local_error)) + { + g_debug ("Ignoring remote ‘%s’ due to no valid collection ID being configured for it: %s", + remote_name, local_error->message); + g_clear_error (&local_error); + continue; + } + + if (!ostree_repo_remote_list_refs (parent_repo, remote_name, &remote_refs, + cancellable, &local_error)) + { + g_debug ("Ignoring remote ‘%s’ due to error loading its refs: %s", + remote_name, local_error->message); + g_clear_error (&local_error); + continue; + } + + for (j = 0; refs[j] != NULL; j++) + { + if (g_strcmp0 (refs[j]->collection_id, remote_collection_id) == 0 && + g_hash_table_lookup_extended (remote_refs, refs[j]->ref_name, NULL, (gpointer *) &checksum)) + { + /* The requested ref is listed in the refs for this remote. Add + * the remote to the results, and the ref to its + * @supported_ref_to_checksum. */ + g_debug ("Resolved ref (%s, %s) to remote ‘%s’.", + refs[j]->collection_id, refs[j]->ref_name, remote_name); + + supported_ref_to_checksum = g_hash_table_lookup (repo_name_to_refs, remote_name); + + if (supported_ref_to_checksum == NULL) + { + supported_ref_to_checksum = g_hash_table_new_full (ostree_collection_ref_hash, + ostree_collection_ref_equal, + NULL, g_free); + g_hash_table_insert (repo_name_to_refs, (gpointer) remote_name, supported_ref_to_checksum /* transfer */); + } + + g_hash_table_insert (supported_ref_to_checksum, + (gpointer) refs[j], g_strdup (checksum)); + } + } + } + + /* Aggregate the results. */ + g_hash_table_iter_init (&iter, repo_name_to_refs); + + while (g_hash_table_iter_next (&iter, (gpointer *) &remote_name, (gpointer *) &supported_ref_to_checksum)) + { + g_autoptr(GError) local_error = NULL; + OstreeRemote *remote; + + /* We don’t know what last-modified timestamp the remote has without + * making expensive HTTP queries, so leave that information blank. We + * assume that the configuration which says the refs and commits in + * @supported_ref_to_checksum are in the repository is correct; the code + * in ostree_repo_find_remotes_async() will check that. */ + remote = _ostree_repo_get_remote_inherited (parent_repo, remote_name, &local_error); + if (remote == NULL) + { + g_debug ("Configuration for remote ‘%s’ could not be found. Ignoring.", + remote_name); + continue; + } + + g_ptr_array_add (results, ostree_repo_finder_result_new (remote, finder, priority, supported_ref_to_checksum, 0)); + } + + g_ptr_array_sort (results, results_compare_cb); + + g_task_return_pointer (task, g_steal_pointer (&results), (GDestroyNotify) g_ptr_array_unref); +} + +static GPtrArray * +ostree_repo_finder_config_resolve_finish (OstreeRepoFinder *finder, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, finder), NULL); + return g_task_propagate_pointer (G_TASK (result), error); +} + +static void +ostree_repo_finder_config_init (OstreeRepoFinderConfig *self) +{ + /* Nothing to see here. */ +} + +static void +ostree_repo_finder_config_class_init (OstreeRepoFinderConfigClass *klass) +{ + /* Nothing to see here. */ +} + +static void +ostree_repo_finder_config_iface_init (OstreeRepoFinderInterface *iface) +{ + iface->resolve_async = ostree_repo_finder_config_resolve_async; + iface->resolve_finish = ostree_repo_finder_config_resolve_finish; +} + +/** + * ostree_repo_finder_config_new: + * + * Create a new #OstreeRepoFinderConfig. + * + * Returns: (transfer full): a new #OstreeRepoFinderConfig + * Since: 2017.8 + */ +OstreeRepoFinderConfig * +ostree_repo_finder_config_new (void) +{ + return g_object_new (OSTREE_TYPE_REPO_FINDER_CONFIG, NULL); +} diff --git a/src/libostree/ostree-repo-finder-config.h b/src/libostree/ostree-repo-finder-config.h new file mode 100644 index 00000000..28e6fc84 --- /dev/null +++ b/src/libostree/ostree-repo-finder-config.h @@ -0,0 +1,55 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#pragma once + +#include +#include +#include + +#include "ostree-repo-finder.h" +#include "ostree-types.h" + +G_BEGIN_DECLS + +#define OSTREE_TYPE_REPO_FINDER_CONFIG (ostree_repo_finder_config_get_type ()) + +/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44): +_OSTREE_PUBLIC +G_DECLARE_FINAL_TYPE (OstreeRepoFinderConfig, ostree_repo_finder_config, OSTREE, REPO_FINDER_CONFIG, GObject) */ + +_OSTREE_PUBLIC +GType ostree_repo_finder_config_get_type (void); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +typedef struct _OstreeRepoFinderConfig OstreeRepoFinderConfig; +typedef struct { GObjectClass parent_class; } OstreeRepoFinderConfigClass; + +static inline OstreeRepoFinderConfig *OSTREE_REPO_FINDER_CONFIG (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_repo_finder_config_get_type (), OstreeRepoFinderConfig); } +static inline gboolean OSTREE_IS_REPO_FINDER_CONFIG (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_repo_finder_config_get_type ()); } +G_GNUC_END_IGNORE_DEPRECATIONS + +_OSTREE_PUBLIC +OstreeRepoFinderConfig *ostree_repo_finder_config_new (void); + +G_END_DECLS diff --git a/src/libostree/ostree-repo-finder-mount.c b/src/libostree/ostree-repo-finder-mount.c new file mode 100644 index 00000000..ffe31e99 --- /dev/null +++ b/src/libostree/ostree-repo-finder-mount.c @@ -0,0 +1,547 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "ostree-autocleanups.h" +#include "ostree-remote-private.h" +#include "ostree-repo-finder.h" +#include "ostree-repo-finder-mount.h" + +/** + * SECTION:ostree-repo-finder-mount + * @title: OstreeRepoFinderMount + * @short_description: Finds remote repositories from ref names by looking at + * mounted removable volumes + * @stability: Unstable + * @include: libostree/ostree-repo-finder-mount.h + * + * #OstreeRepoFinderMount is an implementation of #OstreeRepoFinder which looks + * refs up in well-known locations on any mounted removable volumes. + * + * For an #OstreeCollectionRef, (`C`, `R`), it checks whether `.ostree/repos/C/R` + * exists and is an OSTree repository on each mounted removable volume. Collection + * IDs and ref names are not escaped when building the path, so if either + * contains `/` in its name, the repository will be checked for in a + * subdirectory of `.ostree/repos`. Non-removable volumes are ignored. + * + * For each repository which is found, a result will be returned for the + * intersection of the refs being searched for, and the refs in `refs/heads` and + * `refs/mirrors` in the repository on the removable volume. + * + * Symlinks are followed when resolving the refs, so a volume might contain a + * single OSTree at some arbitrary path, with a number of refs linking to it + * from `.ostree/repos`. Any symlink which points outside the volume’s file + * system will be ignored. Repositories are deduplicated in the results. + * + * The volume monitor used to find mounted volumes can be overridden by setting + * #OstreeRepoFinderMount:monitor. By default, g_volume_monitor_get() is used. + * + * Since: 2017.8 + */ + +typedef GList/**/ ObjectList; + +static void +object_list_free (ObjectList *list) +{ + g_list_free_full (list, g_object_unref); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (ObjectList, object_list_free) + +static void ostree_repo_finder_mount_iface_init (OstreeRepoFinderInterface *iface); + +struct _OstreeRepoFinderMount +{ + GObject parent_instance; + + GVolumeMonitor *monitor; /* owned */ +}; + +G_DEFINE_TYPE_WITH_CODE (OstreeRepoFinderMount, ostree_repo_finder_mount, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (OSTREE_TYPE_REPO_FINDER, ostree_repo_finder_mount_iface_init)) + +typedef struct +{ + gchar *uri; + gchar *keyring; +} UriAndKeyring; + +static void +uri_and_keyring_free (UriAndKeyring *data) +{ + g_free (data->uri); + g_free (data->keyring); + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (UriAndKeyring, uri_and_keyring_free) + +static UriAndKeyring * +uri_and_keyring_new (const gchar *uri, + const gchar *keyring) +{ + g_autoptr(UriAndKeyring) data = NULL; + + data = g_new0 (UriAndKeyring, 1); + data->uri = g_strdup (uri); + data->keyring = g_strdup (keyring); + + return g_steal_pointer (&data); +} + +static guint +uri_and_keyring_hash (gconstpointer key) +{ + const UriAndKeyring *_key = key; + + return g_str_hash (_key->uri) ^ g_str_hash (_key->keyring); +} + +static gboolean +uri_and_keyring_equal (gconstpointer a, + gconstpointer b) +{ + const UriAndKeyring *_a = a, *_b = b; + + return g_str_equal (_a->uri, _b->uri) && g_str_equal (_a->keyring, _b->keyring); +} + +/* This must return a valid remote name (suitable for use in a refspec). */ +static gchar * +uri_and_keyring_to_name (UriAndKeyring *data) +{ + g_autofree gchar *escaped_uri = g_uri_escape_string (data->uri, NULL, FALSE); + g_autofree gchar *escaped_keyring = g_uri_escape_string (data->keyring, NULL, FALSE); + + /* FIXME: Need a better separator than `_`, since it’s not escaped in the input. */ + g_autofree gchar *out = g_strdup_printf ("%s_%s", escaped_uri, escaped_keyring); + + for (gsize i = 0; out[i] != '\0'; i++) + { + if (out[i] == '%') + out[i] = '_'; + } + + g_return_val_if_fail (ostree_validate_remote_name (out, NULL), NULL); + + return g_steal_pointer (&out); +} + +static gint +results_compare_cb (gconstpointer a, + gconstpointer b) +{ + const OstreeRepoFinderResult *result_a = *((const OstreeRepoFinderResult **) a); + const OstreeRepoFinderResult *result_b = *((const OstreeRepoFinderResult **) b); + + return ostree_repo_finder_result_compare (result_a, result_b); +} + +static void +ostree_repo_finder_mount_resolve_async (OstreeRepoFinder *finder, + const OstreeCollectionRef * const *refs, + OstreeRepo *parent_repo, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + OstreeRepoFinderMount *self = OSTREE_REPO_FINDER_MOUNT (finder); + g_autoptr(GTask) task = NULL; + g_autoptr(ObjectList) mounts = NULL; + g_autoptr(GPtrArray) results = NULL; /* (element-type OstreeRepoFinderResult) */ + GList *l; + const gint priority = 50; /* arbitrarily chosen */ + + task = g_task_new (finder, cancellable, callback, user_data); + g_task_set_source_tag (task, ostree_repo_finder_mount_resolve_async); + + mounts = g_volume_monitor_get_mounts (self->monitor); + results = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_repo_finder_result_free); + + g_debug ("%s: Found %u mounts", G_STRFUNC, g_list_length (mounts)); + + for (l = mounts; l != NULL; l = l->next) + { + GMount *mount = G_MOUNT (l->data); + g_autofree gchar *mount_name = NULL; + g_autoptr(GFile) mount_root = NULL; + g_autofree gchar *mount_root_path = NULL; + glnx_fd_close int mount_root_dfd = -1; + struct stat mount_root_stbuf; + glnx_fd_close int repos_dfd = -1; + gsize i; + g_autoptr(GHashTable) repo_to_refs = NULL; /* (element-type UriAndKeyring GHashTable) */ + GHashTable *supported_ref_to_checksum; /* (element-type OstreeCollectionRef utf8) */ + GHashTableIter iter; + UriAndKeyring *repo; + g_autoptr(GError) local_error = NULL; + + mount_name = g_mount_get_name (mount); + + /* Check the mount’s general properties. */ + if (g_mount_is_shadowed (mount)) + { + g_debug ("Ignoring mount ‘%s’ as it’s shadowed.", mount_name); + continue; + } + + /* Check if it contains a .ostree/repos directory. */ + mount_root = g_mount_get_root (mount); + mount_root_path = g_file_get_path (mount_root); + + if (!glnx_opendirat (AT_FDCWD, mount_root_path, TRUE, &mount_root_dfd, &local_error)) + { + g_debug ("Ignoring mount ‘%s’ as ‘%s’ directory can’t be opened: %s", + mount_name, mount_root_path, local_error->message); + continue; + } + + if (!glnx_opendirat (mount_root_dfd, ".ostree/repos", TRUE, &repos_dfd, &local_error)) + { + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + g_debug ("Ignoring mount ‘%s’ as ‘%s/.ostree/repos’ directory doesn’t exist.", + mount_name, mount_root_path); + else + g_debug ("Ignoring mount ‘%s’ as ‘%s/.ostree/repos’ directory can’t be opened: %s", + mount_name, mount_root_path, local_error->message); + + continue; + } + + /* stat() the mount root so we can later check whether the resolved + * repositories for individual refs are on the same device (to avoid the + * symlinks for them pointing outside the mount root). */ + if (!glnx_fstat (mount_root_dfd, &mount_root_stbuf, &local_error)) + { + g_debug ("Ignoring mount ‘%s’ as querying info of ‘%s’ failed: %s", + mount_name, mount_root_path, local_error->message); + continue; + } + + /* Check whether a subdirectory exists for any of the @refs we’re looking + * for. If so, and it’s a symbolic link, dereference it so multiple links + * to the same repository (containing multiple refs) are coalesced. + * Otherwise, include it as a result by itself. */ + repo_to_refs = g_hash_table_new_full (uri_and_keyring_hash, uri_and_keyring_equal, + (GDestroyNotify) uri_and_keyring_free, (GDestroyNotify) g_hash_table_unref); + + for (i = 0; refs[i] != NULL; i++) + { + struct stat stbuf; + g_autofree gchar *collection_and_ref = NULL; + g_autofree gchar *repo_dir_path = NULL; + g_autofree gchar *resolved_repo_uri = NULL; + g_autofree gchar *keyring = NULL; + g_autoptr(UriAndKeyring) resolved_repo = NULL; + + collection_and_ref = g_build_filename (refs[i]->collection_id, refs[i]->ref_name, NULL); + repo_dir_path = g_build_filename (mount_root_path, ".ostree", "repos", + collection_and_ref, NULL); + + if (!glnx_fstatat (repos_dfd, collection_and_ref, &stbuf, AT_NO_AUTOMOUNT, &local_error)) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ as querying info of ‘%s’ failed: %s", + refs[i]->collection_id, refs[i]->ref_name, mount_name, repo_dir_path, local_error->message); + g_clear_error (&local_error); + continue; + } + + if ((stbuf.st_mode & S_IFMT) != S_IFDIR) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ as ‘%s’ is of type %u, not a directory.", + refs[i]->collection_id, refs[i]->ref_name, mount_name, repo_dir_path, (stbuf.st_mode & S_IFMT)); + g_clear_error (&local_error); + continue; + } + + /* Check the resolved repository path is below the mount point. Do not + * allow ref symlinks to point somewhere outside of the mounted + * volume. */ + if (stbuf.st_dev != mount_root_stbuf.st_dev) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ as it’s on a different file system from the mount.", + refs[i]->collection_id, refs[i]->ref_name, mount_name); + g_clear_error (&local_error); + continue; + } + + /* Exclude repositories which resolve to @parent_repo. */ + g_autofree char *canonical_repo_dir_path = realpath (repo_dir_path, NULL); + g_autofree gchar *parent_repo_path = g_file_get_path (ostree_repo_get_path (parent_repo)); + g_autofree char *canonical_parent_repo_path = realpath (parent_repo_path, NULL); + + if (g_strcmp0 (canonical_repo_dir_path, canonical_parent_repo_path) == 0) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ as its repository was the one we are resolving for: %s", + refs[i]->collection_id, refs[i]->ref_name, mount_name, canonical_parent_repo_path); + g_clear_error (&local_error); + continue; + } + + /* Grab the given ref and a checksum for it from the repo. + * FIXME: Ideally, there would be some ostree_repo_open_at() which we + * could use to keep the openat() chain going. See + * https://github.com/ostreedev/ostree/pull/820. */ + g_autoptr(OstreeRepo) repo = NULL; + g_autoptr(GFile) repo_dir_file = g_file_new_for_path (repo_dir_path); + repo = ostree_repo_new (repo_dir_file); + + if (!ostree_repo_open (repo, cancellable, &local_error)) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ as its repository could not be opened: %s", + refs[i]->collection_id, refs[i]->ref_name, mount_name, local_error->message); + g_clear_error (&local_error); + continue; + } + + g_autoptr(GHashTable) repo_refs = NULL; /* (element-type OstreeCollectionRef utf8) */ + + if (!ostree_repo_list_collection_refs (repo, refs[i]->collection_id, &repo_refs, cancellable, &local_error)) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ as its refs could not be listed: %s", + refs[i]->collection_id, refs[i]->ref_name, mount_name, local_error->message); + g_clear_error (&local_error); + continue; + } + + const gchar *checksum = g_hash_table_lookup (repo_refs, refs[i]); + + if (checksum == NULL) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ as its repository doesn’t contain the ref.", + refs[i]->collection_id, refs[i]->ref_name, mount_name); + g_clear_error (&local_error); + continue; + } + + /* Finally, look up the GPG keyring for this ref. */ + keyring = ostree_repo_resolve_keyring_for_collection (parent_repo, refs[i]->collection_id, + cancellable, &local_error); + + if (keyring == NULL) + { + g_debug ("Ignoring ref (%s, %s) on mount ‘%s’ due to missing keyring: %s", + refs[i]->collection_id, refs[i]->ref_name, mount_name, local_error->message); + g_clear_error (&local_error); + continue; + } + + /* There is a valid repo at (or pointed to by) + * $mount_root/.ostree/repos/$refs[i]->collection_id/$refs[i]->ref_name. + * Add it to the results, keyed by the canonicalised repository URI + * to deduplicate the results. */ + resolved_repo_uri = g_strconcat ("file://", canonical_repo_dir_path, NULL); + g_debug ("Resolved ref (%s, %s) on mount ‘%s’ to repo URI ‘%s’ with keyring ‘%s’.", + refs[i]->collection_id, refs[i]->ref_name, mount_name, resolved_repo_uri, keyring); + + resolved_repo = uri_and_keyring_new (resolved_repo_uri, keyring); + + supported_ref_to_checksum = g_hash_table_lookup (repo_to_refs, resolved_repo); + + if (supported_ref_to_checksum == NULL) + { + supported_ref_to_checksum = g_hash_table_new_full (ostree_collection_ref_hash, + ostree_collection_ref_equal, + NULL, g_free); + g_hash_table_insert (repo_to_refs, g_steal_pointer (&resolved_repo), supported_ref_to_checksum /* transfer */); + } + + g_hash_table_insert (supported_ref_to_checksum, (gpointer) refs[i], g_strdup (checksum)); + } + + /* Aggregate the results. */ + g_hash_table_iter_init (&iter, repo_to_refs); + + while (g_hash_table_iter_next (&iter, (gpointer *) &repo, (gpointer *) &supported_ref_to_checksum)) + { + g_autoptr(OstreeRemote) remote = NULL; + + /* Build an #OstreeRemote. Use the escaped URI, since remote->name + * is used in file paths, so needs to not contain special characters. */ + g_autofree gchar *name = uri_and_keyring_to_name (repo); + remote = ostree_remote_new (name); + + g_clear_pointer (&remote->keyring, g_free); + remote->keyring = g_strdup (repo->keyring); + + g_key_file_set_string (remote->options, remote->group, "url", repo->uri); + g_key_file_set_boolean (remote->options, remote->group, "gpg-verify", TRUE); + g_key_file_set_boolean (remote->options, remote->group, "gpg-verify-summary", TRUE); + + /* Set the timestamp in the #OstreeRepoFinderResult to 0 because + * the code in ostree_repo_pull_from_remotes_async() will be able to + * check it just as quickly as we can here; so don’t duplicate the + * code. */ + g_ptr_array_add (results, ostree_repo_finder_result_new (remote, finder, priority, supported_ref_to_checksum, 0)); + } + } + + g_ptr_array_sort (results, results_compare_cb); + + g_task_return_pointer (task, g_steal_pointer (&results), (GDestroyNotify) g_ptr_array_unref); +} + +static GPtrArray * +ostree_repo_finder_mount_resolve_finish (OstreeRepoFinder *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, self), NULL); + return g_task_propagate_pointer (G_TASK (result), error); +} + +static void +ostree_repo_finder_mount_init (OstreeRepoFinderMount *self) +{ + /* Nothing to see here. */ +} + +static void +ostree_repo_finder_mount_constructed (GObject *object) +{ + OstreeRepoFinderMount *self = OSTREE_REPO_FINDER_MOUNT (object); + + G_OBJECT_CLASS (ostree_repo_finder_mount_parent_class)->constructed (object); + + if (self->monitor == NULL) + self->monitor = g_volume_monitor_get (); +} + +typedef enum +{ + PROP_MONITOR = 1, +} OstreeRepoFinderMountProperty; + +static void +ostree_repo_finder_mount_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + OstreeRepoFinderMount *self = OSTREE_REPO_FINDER_MOUNT (object); + + switch ((OstreeRepoFinderMountProperty) property_id) + { + case PROP_MONITOR: + g_value_set_object (value, self->monitor); + break; + default: + g_assert_not_reached (); + } +} + +static void +ostree_repo_finder_mount_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + OstreeRepoFinderMount *self = OSTREE_REPO_FINDER_MOUNT (object); + + switch ((OstreeRepoFinderMountProperty) property_id) + { + case PROP_MONITOR: + /* Construct-only. */ + g_assert (self->monitor == NULL); + self->monitor = g_value_dup_object (value); + break; + default: + g_assert_not_reached (); + } +} + +static void +ostree_repo_finder_mount_dispose (GObject *object) +{ + OstreeRepoFinderMount *self = OSTREE_REPO_FINDER_MOUNT (object); + + g_clear_object (&self->monitor); + + G_OBJECT_CLASS (ostree_repo_finder_mount_parent_class)->dispose (object); +} + +static void +ostree_repo_finder_mount_class_init (OstreeRepoFinderMountClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = ostree_repo_finder_mount_get_property; + object_class->set_property = ostree_repo_finder_mount_set_property; + object_class->constructed = ostree_repo_finder_mount_constructed; + object_class->dispose = ostree_repo_finder_mount_dispose; + + /** + * OstreeRepoFinderMount:monitor: + * + * Volume monitor to use to look up mounted volumes when queried. + * + * Since: 2017.8 + */ + g_object_class_install_property (object_class, PROP_MONITOR, + g_param_spec_object ("monitor", + "Volume Monitor", + "Volume monitor to use " + "to look up mounted " + "volumes when queried.", + G_TYPE_VOLUME_MONITOR, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); +} + +static void +ostree_repo_finder_mount_iface_init (OstreeRepoFinderInterface *iface) +{ + iface->resolve_async = ostree_repo_finder_mount_resolve_async; + iface->resolve_finish = ostree_repo_finder_mount_resolve_finish; +} + +/** + * ostree_repo_finder_mount_new: + * @monitor: (nullable) (transfer none): volume monitor to use, or %NULL to use + * the system default + * + * Create a new #OstreeRepoFinderMount, using the given @monitor to look up + * volumes. If @monitor is %NULL, the monitor from g_volume_monitor_get() will + * be used. + * + * Returns: (transfer full): a new #OstreeRepoFinderMount + * Since: 2017.8 + */ +OstreeRepoFinderMount * +ostree_repo_finder_mount_new (GVolumeMonitor *monitor) +{ + g_return_val_if_fail (monitor == NULL || G_IS_VOLUME_MONITOR (monitor), NULL); + + return g_object_new (OSTREE_TYPE_REPO_FINDER_MOUNT, + "monitor", monitor, + NULL); +} diff --git a/src/libostree/ostree-repo-finder-mount.h b/src/libostree/ostree-repo-finder-mount.h new file mode 100644 index 00000000..d844dd44 --- /dev/null +++ b/src/libostree/ostree-repo-finder-mount.h @@ -0,0 +1,55 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#pragma once + +#include +#include +#include + +#include "ostree-repo-finder.h" +#include "ostree-types.h" + +G_BEGIN_DECLS + +#define OSTREE_TYPE_REPO_FINDER_MOUNT (ostree_repo_finder_mount_get_type ()) + +/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44): +_OSTREE_PUBLIC +G_DECLARE_FINAL_TYPE (OstreeRepoFinderMount, ostree_repo_finder_mount, OSTREE, REPO_FINDER_MOUNT, GObject) */ + +_OSTREE_PUBLIC +GType ostree_repo_finder_mount_get_type (void); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +typedef struct _OstreeRepoFinderMount OstreeRepoFinderMount; +typedef struct { GObjectClass parent_class; } OstreeRepoFinderMountClass; + +static inline OstreeRepoFinderMount *OSTREE_REPO_FINDER_MOUNT (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_repo_finder_mount_get_type (), OstreeRepoFinderMount); } +static inline gboolean OSTREE_IS_REPO_FINDER_MOUNT (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_repo_finder_mount_get_type ()); } +G_GNUC_END_IGNORE_DEPRECATIONS + +_OSTREE_PUBLIC +OstreeRepoFinderMount *ostree_repo_finder_mount_new (GVolumeMonitor *monitor); + +G_END_DECLS diff --git a/src/libostree/ostree-repo-finder.c b/src/libostree/ostree-repo-finder.c new file mode 100644 index 00000000..7893978d --- /dev/null +++ b/src/libostree/ostree-repo-finder.c @@ -0,0 +1,576 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#include "config.h" + +#include +#include +#include + +#include "ostree-autocleanups.h" +#include "ostree-core.h" +#include "ostree-remote-private.h" +#include "ostree-repo-finder.h" +#include "ostree-repo.h" + +static void ostree_repo_finder_default_init (OstreeRepoFinderInterface *iface); + +G_DEFINE_INTERFACE (OstreeRepoFinder, ostree_repo_finder, G_TYPE_OBJECT) + +static void +ostree_repo_finder_default_init (OstreeRepoFinderInterface *iface) +{ + /* Nothing to see here. */ +} + +/* Validate the given struct contains a valid collection ID and ref name, and that + * the collection ID is non-%NULL. */ +static gboolean +is_valid_collection_ref (const OstreeCollectionRef *ref) +{ + return (ref != NULL && + ostree_validate_rev (ref->ref_name, NULL) && + ostree_validate_collection_id (ref->collection_id, NULL)); +} + +/* Validate @refs is non-%NULL, non-empty, and contains only valid collection + * and ref names. */ +static gboolean +is_valid_collection_ref_array (const OstreeCollectionRef * const *refs) +{ + gsize i; + + if (refs == NULL || *refs == NULL) + return FALSE; + + for (i = 0; refs[i] != NULL; i++) + { + if (!is_valid_collection_ref (refs[i])) + return FALSE; + } + + return TRUE; +} + +/* Validate @ref_to_checksum is non-%NULL, non-empty, and contains only valid + * OstreeCollectionRefs as keys and only valid commit checksums as values. */ +static gboolean +is_valid_collection_ref_map (GHashTable *ref_to_checksum) +{ + GHashTableIter iter; + const OstreeCollectionRef *ref; + const gchar *checksum; + + if (ref_to_checksum == NULL || g_hash_table_size (ref_to_checksum) == 0) + return FALSE; + + g_hash_table_iter_init (&iter, ref_to_checksum); + + while (g_hash_table_iter_next (&iter, (gpointer *) &ref, (gpointer *) &checksum)) + { + g_assert (ref != NULL); + g_assert (checksum != NULL); + + if (!is_valid_collection_ref (ref)) + return FALSE; + if (!ostree_validate_checksum_string (checksum, NULL)) + return FALSE; + } + + return TRUE; +} + +static void resolve_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data); + +/** + * ostree_repo_finder_resolve_async: + * @self: an #OstreeRepoFinder + * @refs: (array zero-terminated=1): non-empty array of collection–ref pairs to find remotes for + * @parent_repo: (transfer none): the local repository which the refs are being resolved for, + * which provides configuration information and GPG keys + * @cancellable: (nullable): a #GCancellable, or %NULL + * @callback: asynchronous completion callback + * @user_data: data to pass to @callback + * + * Find reachable remote URIs which claim to provide any of the given @refs. The + * specific method for finding the remotes depends on the #OstreeRepoFinder + * implementation. + * + * Any remote which is found and which claims to support any of the given @refs + * will be returned in the results. It is possible that a remote claims to + * support a given ref, but turns out not to — it is not possible to verify this + * until ostree_repo_pull_from_remotes_async() is called. + * + * The returned results will be sorted with the most useful first — this is + * typically the remote which claims to provide the most @refs, at the lowest + * latency. + * + * Each result contains a mapping of @refs to the checksums of the commits + * which the result provides. If the result provides the latest commit for a ref + * across all of the results, the checksum will be set. Otherwise, if the + * result provides an outdated commit, or doesn’t provide a given ref at all, + * the ref will not be set. Results which provide none of the requested @refs + * may be listed with an empty refs map. + * + * Pass the results to ostree_repo_pull_from_remotes_async() to pull the given + * @refs from those remotes. + * + * Since: 2017.8 + */ +void +ostree_repo_finder_resolve_async (OstreeRepoFinder *self, + const OstreeCollectionRef * const *refs, + OstreeRepo *parent_repo, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + OstreeRepoFinder *finders[2] = { NULL, }; + + g_return_if_fail (OSTREE_IS_REPO_FINDER (self)); + g_return_if_fail (is_valid_collection_ref_array (refs)); + g_return_if_fail (OSTREE_IS_REPO (parent_repo)); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, ostree_repo_finder_resolve_async); + + finders[0] = self; + + ostree_repo_finder_resolve_all_async (finders, refs, parent_repo, cancellable, + resolve_cb, g_steal_pointer (&task)); +} + +static void +resolve_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + g_autoptr(GPtrArray) results = NULL; + g_autoptr(GError) local_error = NULL; + + task = G_TASK (user_data); + + results = ostree_repo_finder_resolve_all_finish (result, &local_error); + + g_assert ((local_error == NULL) != (results == NULL)); + + if (local_error != NULL) + g_task_return_error (task, g_steal_pointer (&local_error)); + else + g_task_return_pointer (task, g_steal_pointer (&results), (GDestroyNotify) g_ptr_array_unref); +} + +/** + * ostree_repo_finder_resolve_finish: + * @self: an #OstreeRepoFinder + * @result: #GAsyncResult from the callback + * @error: return location for a #GError + * + * Get the results from a ostree_repo_finder_resolve_async() operation. + * + * Returns: (transfer full) (element-type OstreeRepoFinderResult): array of zero + * or more results + * Since: 2017.8 + */ +GPtrArray * +ostree_repo_finder_resolve_finish (OstreeRepoFinder *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (OSTREE_IS_REPO_FINDER (self), NULL); + g_return_val_if_fail (g_task_is_valid (result, self), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +static gint +sort_results_cb (gconstpointer a, + gconstpointer b) +{ + const OstreeRepoFinderResult *result_a = *((const OstreeRepoFinderResult **) a); + const OstreeRepoFinderResult *result_b = *((const OstreeRepoFinderResult **) b); + + return ostree_repo_finder_result_compare (result_a, result_b); +} + +typedef struct +{ + gsize n_finders_pending; + GPtrArray *results; +} ResolveAllData; + +static void +resolve_all_data_free (ResolveAllData *data) +{ + g_assert (data->n_finders_pending == 0); + g_clear_pointer (&data->results, g_ptr_array_unref); + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (ResolveAllData, resolve_all_data_free) + +static void resolve_all_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data); +static void resolve_all_finished_one (GTask *task); + +/** + * ostree_repo_finder_resolve_all_async: + * @finders: (array zero-terminated=1): non-empty array of #OstreeRepoFinders + * @refs: (array zero-terminated=1): non-empty array of collection–ref pairs to find remotes for + * @parent_repo: (transfer none): the local repository which the refs are being resolved for, + * which provides configuration information and GPG keys + * @cancellable: (nullable): a #GCancellable, or %NULL + * @callback: asynchronous completion callback + * @user_data: data to pass to @callback + * + * A version of ostree_repo_finder_resolve_async() which queries one or more + * @finders in parallel and combines the results. + * + * Since: 2017.8 + */ +void +ostree_repo_finder_resolve_all_async (OstreeRepoFinder * const *finders, + const OstreeCollectionRef * const *refs, + OstreeRepo *parent_repo, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + g_autoptr(ResolveAllData) data = NULL; + gsize i; + g_autoptr(GString) refs_str = NULL; + g_autoptr(GString) finders_str = NULL; + + g_return_if_fail (finders != NULL && finders[0] != NULL); + g_return_if_fail (is_valid_collection_ref_array (refs)); + g_return_if_fail (OSTREE_IS_REPO (parent_repo)); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + refs_str = g_string_new (""); + for (i = 0; refs[i] != NULL; i++) + { + if (i != 0) + g_string_append (refs_str, ", "); + g_string_append_printf (refs_str, "(%s, %s)", + refs[i]->collection_id, refs[i]->ref_name); + } + + finders_str = g_string_new (""); + for (i = 0; finders[i] != NULL; i++) + { + if (i != 0) + g_string_append (finders_str, ", "); + g_string_append (finders_str, g_type_name (G_TYPE_FROM_INSTANCE (finders[i]))); + } + + g_debug ("%s: Resolving refs [%s] with finders [%s]", G_STRFUNC, + refs_str->str, finders_str->str); + + task = g_task_new (NULL, cancellable, callback, user_data); + g_task_set_source_tag (task, ostree_repo_finder_resolve_all_async); + + data = g_new0 (ResolveAllData, 1); + data->n_finders_pending = 1; /* while setting up the loop */ + data->results = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_repo_finder_result_free); + g_task_set_task_data (task, data, (GDestroyNotify) resolve_all_data_free); + + /* Start all the asynchronous queries in parallel. */ + for (i = 0; finders[i] != NULL; i++) + { + OstreeRepoFinder *finder = OSTREE_REPO_FINDER (finders[i]); + OstreeRepoFinderInterface *iface; + + iface = OSTREE_REPO_FINDER_GET_IFACE (finder); + g_assert (iface->resolve_async != NULL); + iface->resolve_async (finder, refs, parent_repo, cancellable, resolve_all_cb, g_object_ref (task)); + data->n_finders_pending++; + } + + resolve_all_finished_one (task); + data = NULL; /* passed to the GTask above */ +} + +/* Modifies both arrays in place. */ +static void +array_concatenate_steal (GPtrArray *array, + GPtrArray *to_concatenate) /* (transfer full) */ +{ + g_autoptr(GPtrArray) array_to_concatenate = to_concatenate; + gsize i; + + for (i = 0; i < array_to_concatenate->len; i++) + { + /* Sanity check that the arrays do not contain any %NULL elements + * (particularly NULL terminators). */ + g_assert (g_ptr_array_index (array_to_concatenate, i) != NULL); + g_ptr_array_add (array, g_steal_pointer (&g_ptr_array_index (array_to_concatenate, i))); + } + + g_ptr_array_set_free_func (array_to_concatenate, NULL); + g_ptr_array_set_size (array_to_concatenate, 0); +} + +static void +resolve_all_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data) +{ + OstreeRepoFinder *finder; + OstreeRepoFinderInterface *iface; + g_autoptr(GTask) task = NULL; + g_autoptr(GPtrArray) results = NULL; + g_autoptr(GError) local_error = NULL; + ResolveAllData *data; + + finder = OSTREE_REPO_FINDER (obj); + iface = OSTREE_REPO_FINDER_GET_IFACE (finder); + task = G_TASK (user_data); + data = g_task_get_task_data (task); + results = iface->resolve_finish (finder, result, &local_error); + + g_assert ((local_error == NULL) != (results == NULL)); + + if (local_error != NULL) + g_debug ("Error resolving refs to repository URI using %s: %s", + g_type_name (G_TYPE_FROM_INSTANCE (finder)), local_error->message); + else + array_concatenate_steal (data->results, g_steal_pointer (&results)); + + resolve_all_finished_one (task); +} + +static void +resolve_all_finished_one (GTask *task) +{ + ResolveAllData *data; + + data = g_task_get_task_data (task); + + data->n_finders_pending--; + + if (data->n_finders_pending == 0) + { + gsize i; + g_autoptr(GString) results_str = NULL; + + g_ptr_array_sort (data->results, sort_results_cb); + + results_str = g_string_new (""); + for (i = 0; i < data->results->len; i++) + { + const OstreeRepoFinderResult *result = g_ptr_array_index (data->results, i); + + if (i != 0) + g_string_append (results_str, ", "); + g_string_append (results_str, ostree_remote_get_name (result->remote)); + } + if (i == 0) + g_string_append (results_str, "(none)"); + + g_debug ("%s: Finished, results: %s", G_STRFUNC, results_str->str); + + g_task_return_pointer (task, g_steal_pointer (&data->results), (GDestroyNotify) g_ptr_array_unref); + } +} + +/** + * ostree_repo_finder_resolve_all_finish: + * @result: #GAsyncResult from the callback + * @error: return location for a #GError + * + * Get the results from a ostree_repo_finder_resolve_all_async() operation. + * + * Returns: (transfer full) (element-type OstreeRepoFinderResult): array of zero + * or more results + * Since: 2017.8 + */ +GPtrArray * +ostree_repo_finder_resolve_all_finish (GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, NULL), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + return g_task_propagate_pointer (G_TASK (result), error); +} + +G_DEFINE_BOXED_TYPE (OstreeRepoFinderResult, ostree_repo_finder_result, + ostree_repo_finder_result_dup, ostree_repo_finder_result_free) + +/** + * ostree_repo_finder_result_new: + * @remote: (transfer none): an #OstreeRemote containing the transport details + * for the result + * @finder: (transfer none): the #OstreeRepoFinder instance which produced the + * result + * @priority: static priority of the result, where higher numbers indicate lower + * priority + * @ref_to_checksum: (element-type OstreeCollectionRef utf8): map of collection–ref pairs + * to checksums provided by this result + * @summary_last_modified: Unix timestamp (seconds since the epoch, UTC) when + * the summary file for the result was last modified, or `0` if this is unknown + * + * Create a new #OstreeRepoFinderResult instance. The semantics for the arguments + * are as described in the #OstreeRepoFinderResult documentation. + * + * Returns: (transfer full): a new #OstreeRepoFinderResult + * Since: 2017.8 + */ +OstreeRepoFinderResult * +ostree_repo_finder_result_new (OstreeRemote *remote, + OstreeRepoFinder *finder, + gint priority, + GHashTable *ref_to_checksum, + guint64 summary_last_modified) +{ + g_autoptr(OstreeRepoFinderResult) result = NULL; + + g_return_val_if_fail (remote != NULL, NULL); + g_return_val_if_fail (OSTREE_IS_REPO_FINDER (finder), NULL); + g_return_val_if_fail (is_valid_collection_ref_map (ref_to_checksum), NULL); + + result = g_new0 (OstreeRepoFinderResult, 1); + result->remote = ostree_remote_ref (remote); + result->finder = g_object_ref (finder); + result->priority = priority; + result->ref_to_checksum = g_hash_table_ref (ref_to_checksum); + result->summary_last_modified = summary_last_modified; + + return g_steal_pointer (&result); +} + +/** + * ostree_repo_finder_result_dup: + * @result: (transfer none): an #OstreeRepoFinderResult to copy + * + * Copy an #OstreeRepoFinderResult. + * + * Returns: (transfer full): a newly allocated copy of @result + * Since: 2017.8 + */ +OstreeRepoFinderResult * +ostree_repo_finder_result_dup (OstreeRepoFinderResult *result) +{ + g_return_val_if_fail (result != NULL, NULL); + + return ostree_repo_finder_result_new (result->remote, result->finder, + result->priority, result->ref_to_checksum, + result->summary_last_modified); +} + +/** + * ostree_repo_finder_result_compare: + * @a: an #OstreeRepoFinderResult + * @b: an #OstreeRepoFinderResult + * + * Compare two #OstreeRepoFinderResult instances to work out which one is better + * to pull from, and hence needs to be ordered before the other. + * + * Returns: <0 if @a is ordered before @b, 0 if they are ordered equally, + * >0 if @b is ordered before @a + * Since: 2017.8 + */ +gint +ostree_repo_finder_result_compare (const OstreeRepoFinderResult *a, + const OstreeRepoFinderResult *b) +{ + guint a_n_refs, b_n_refs; + + g_return_val_if_fail (a != NULL, 0); + g_return_val_if_fail (b != NULL, 0); + + /* FIXME: Check if this is really the ordering we want. For example, we + * probably don’t want a result with 0 refs to be ordered before one with >0 + * refs, just because its priority is higher. */ + if (a->priority != b->priority) + return a->priority - b->priority; + + if (a->summary_last_modified != 0 && b->summary_last_modified != 0 && + a->summary_last_modified != b->summary_last_modified) + return a->summary_last_modified - b->summary_last_modified; + + gpointer value; + GHashTableIter iter; + a_n_refs = b_n_refs = 0; + + g_hash_table_iter_init (&iter, a->ref_to_checksum); + while (g_hash_table_iter_next (&iter, NULL, &value)) + if (value != NULL) + a_n_refs++; + + g_hash_table_iter_init (&iter, b->ref_to_checksum); + while (g_hash_table_iter_next (&iter, NULL, &value)) + if (value != NULL) + b_n_refs++; + + if (a_n_refs != b_n_refs) + return (gint) a_n_refs - (gint) b_n_refs; + + return g_strcmp0 (a->remote->name, b->remote->name); +} + +/** + * ostree_repo_finder_result_free: + * @result: (transfer full): an #OstreeRepoFinderResult + * + * Free the given @result. + * + * Since: 2017.8 + */ +void +ostree_repo_finder_result_free (OstreeRepoFinderResult *result) +{ + g_return_if_fail (result != NULL); + + g_hash_table_unref (result->ref_to_checksum); + g_object_unref (result->finder); + ostree_remote_unref (result->remote); + g_free (result); +} + +/** + * ostree_repo_finder_result_freev: + * @results: (array zero-terminated=1) (transfer full): an #OstreeRepoFinderResult + * + * Free the given @results array, freeing each element and the container. + * + * Since: 2017.8 + */ +void +ostree_repo_finder_result_freev (OstreeRepoFinderResult **results) +{ + gsize i; + + for (i = 0; results[i] != NULL; i++) + ostree_repo_finder_result_free (results[i]); + + g_free (results); +} diff --git a/src/libostree/ostree-repo-finder.h b/src/libostree/ostree-repo-finder.h new file mode 100644 index 00000000..6b0ce8ca --- /dev/null +++ b/src/libostree/ostree-repo-finder.h @@ -0,0 +1,172 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#pragma once + +#include +#include +#include + +#include "ostree-ref.h" +#include "ostree-remote.h" +#include "ostree-types.h" + +G_BEGIN_DECLS + +#define OSTREE_TYPE_REPO_FINDER (ostree_repo_finder_get_type ()) + +/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44): +_OSTREE_PUBLIC +G_DECLARE_INTERFACE (OstreeRepoFinder, ostree_repo_finder, OSTREE, REPO_FINDER, GObject) */ + +_OSTREE_PUBLIC +GType ostree_repo_finder_get_type (void); +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +typedef struct _OstreeRepoFinder OstreeRepoFinder; +typedef struct _OstreeRepoFinderInterface OstreeRepoFinderInterface; + +static inline OstreeRepoFinder *OSTREE_REPO_FINDER (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_repo_finder_get_type (), OstreeRepoFinder); } +static inline gboolean OSTREE_IS_REPO_FINDER (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_repo_finder_get_type ()); } +static inline OstreeRepoFinderInterface *OSTREE_REPO_FINDER_GET_IFACE (gpointer ptr) { return G_TYPE_INSTANCE_GET_INTERFACE (ptr, ostree_repo_finder_get_type (), OstreeRepoFinderInterface); } +G_GNUC_END_IGNORE_DEPRECATIONS + +struct _OstreeRepoFinderInterface +{ + GTypeInterface g_iface; + + void (*resolve_async) (OstreeRepoFinder *self, + const OstreeCollectionRef * const *refs, + OstreeRepo *parent_repo, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + GPtrArray *(*resolve_finish) (OstreeRepoFinder *self, + GAsyncResult *result, + GError **error); +}; + +_OSTREE_PUBLIC +void ostree_repo_finder_resolve_async (OstreeRepoFinder *self, + const OstreeCollectionRef * const *refs, + OstreeRepo *parent_repo, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +_OSTREE_PUBLIC +GPtrArray *ostree_repo_finder_resolve_finish (OstreeRepoFinder *self, + GAsyncResult *result, + GError **error); + +_OSTREE_PUBLIC +void ostree_repo_finder_resolve_all_async (OstreeRepoFinder * const *finders, + const OstreeCollectionRef * const *refs, + OstreeRepo *parent_repo, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +_OSTREE_PUBLIC +GPtrArray *ostree_repo_finder_resolve_all_finish (GAsyncResult *result, + GError **error); + +/** + * OstreeRepoFinderResult: + * @remote: #OstreeRemote which contains the transport details for the result, + * such as its URI and GPG key + * @finder: the #OstreeRepoFinder instance which produced this result + * @priority: static priority of the result, where higher numbers indicate lower + * priority + * @ref_to_checksum: (element-type OstreeCollectionRef utf8): map of collection–ref + * pairs to checksums provided by this remote; values may be %NULL to + * indicate this remote doesn’t provide that ref + * @summary_last_modified: Unix timestamp (seconds since the epoch, UTC) when + * the summary file on the remote was last modified, or `0` if unknown + * + * #OstreeRepoFinderResult gives a single result from an + * ostree_repo_finder_resolve_async() or ostree_repo_finder_resolve_all_async() + * operation. This represents a single remote which provides none, some or all + * of the refs being resolved. The structure includes various bits of metadata + * which allow ostree_repo_pull_from_remotes_async() (for example) to prioritise + * how to pull the refs. + * + * The @priority is used as one input of many to ordering functions like + * ostree_repo_finder_result_compare(). + * + * @ref_to_checksum indicates which refs (out of the ones queried for as inputs + * to ostree_repo_finder_resolve_async()) are provided by this remote. The refs + * are present as keys (of type #OstreeCollectionRef), and the corresponding values + * are the checksums of the commits the remote currently has for those refs. (These + * might not be the latest commits available out of all results.) A + * checksum may be %NULL if the remote does not advertise the corresponding ref. + * After ostree_repo_finder_resolve_async() has been called, the commit metadata + * should be available locally, so the details for each checksum can be looked + * up using ostree_repo_load_commit(). + * + * Since: 2017.8 + */ +typedef struct +{ + OstreeRemote *remote; + OstreeRepoFinder *finder; + gint priority; + GHashTable *ref_to_checksum; + guint64 summary_last_modified; + + /*< private >*/ + gpointer padding[4]; +} OstreeRepoFinderResult; + +_OSTREE_PUBLIC +GType ostree_repo_finder_result_get_type (void); + +_OSTREE_PUBLIC +OstreeRepoFinderResult *ostree_repo_finder_result_new (OstreeRemote *remote, + OstreeRepoFinder *finder, + gint priority, + GHashTable *ref_to_checksum, + guint64 summary_last_modified); +_OSTREE_PUBLIC +OstreeRepoFinderResult *ostree_repo_finder_result_dup (OstreeRepoFinderResult *result); +_OSTREE_PUBLIC +gint ostree_repo_finder_result_compare (const OstreeRepoFinderResult *a, + const OstreeRepoFinderResult *b); +_OSTREE_PUBLIC +void ostree_repo_finder_result_free (OstreeRepoFinderResult *result); + +/** + * OstreeRepoFinderResultv: + * + * A %NULL-terminated array of #OstreeRepoFinderResult instances, designed to + * be used with g_auto(): + * + * |[ + * g_auto(OstreeRepoFinderResultv) results = NULL; + * ]| + * + * Since: 2017.8 + */ +typedef OstreeRepoFinderResult** OstreeRepoFinderResultv; + +_OSTREE_PUBLIC +void ostree_repo_finder_result_freev (OstreeRepoFinderResult **results); + +G_END_DECLS diff --git a/src/libostree/ostree-repo-libarchive.c b/src/libostree/ostree-repo-libarchive.c index 02f1364e..2e1696b5 100644 --- a/src/libostree/ostree-repo-libarchive.c +++ b/src/libostree/ostree-repo-libarchive.c @@ -126,33 +126,24 @@ squash_trailing_slashes (char *path) static GFileInfo * file_info_from_archive_entry (struct archive_entry *entry) { - g_autoptr(GFileInfo) info = NULL; - const struct stat *st = NULL; - guint32 file_type; - mode_t mode; - - st = archive_entry_stat (entry); - mode = st->st_mode; + const struct stat *st = archive_entry_stat (entry); + struct stat st_copy; /* Some archives only store the permission mode bits in hardlink entries, so * let's just make it into a regular file. Yes, this hack will work even if * it's a hardlink to a symlink. */ if (archive_entry_hardlink (entry)) - mode |= S_IFREG; - - info = _ostree_header_gfile_info_new (mode, st->st_uid, st->st_gid); - - file_type = ot_gfile_type_for_mode (mode); - if (file_type == G_FILE_TYPE_REGULAR) { - g_file_info_set_attribute_uint64 (info, "standard::size", st->st_size); - } - else if (file_type == G_FILE_TYPE_SYMBOLIC_LINK) - { - g_file_info_set_attribute_byte_string (info, "standard::symlink-target", - archive_entry_symlink (entry)); + st_copy = *st; + st_copy.st_mode |= S_IFREG; + st = &st_copy; } + g_autoptr(GFileInfo) info = _ostree_stbuf_to_gfileinfo (st); + if (S_ISLNK (st->st_mode)) + g_file_info_set_attribute_byte_string (info, "standard::symlink-target", + archive_entry_symlink (entry)); + return g_steal_pointer (&info); } @@ -773,14 +764,11 @@ aic_import_deferred_hardlinks (OstreeRepoArchiveImportContext *ctx, GCancellable *cancellable, GError **error) { - GHashTableIter iter; - gpointer key, value; - - g_hash_table_iter_init (&iter, ctx->deferred_hardlinks); - while (g_hash_table_iter_next (&iter, &key, &value)) - if (!aic_import_deferred_hardlinks_for (ctx, key, value, error)) - return FALSE; - + GLNX_HASH_TABLE_FOREACH_KV (ctx->deferred_hardlinks, const char*, target, GSList*, links) + { + if (!aic_import_deferred_hardlinks_for (ctx, target, links, error)) + return FALSE; + } return TRUE; } diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index 6cbf9ebe..407e2cb3 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -20,9 +20,11 @@ #pragma once +#include +#include "ostree-ref.h" #include "ostree-repo.h" #include "ostree-remote-private.h" -#include "libglnx.h" +#include "otutil.h" G_BEGIN_DECLS @@ -47,6 +49,8 @@ G_BEGIN_DECLS /* Well-known keys for the additional metadata field in a summary file. */ #define OSTREE_SUMMARY_LAST_MODIFIED "ostree.summary.last-modified" #define OSTREE_SUMMARY_EXPIRES "ostree.summary.expires" +#define OSTREE_SUMMARY_COLLECTION_ID "ostree.summary.collection-id" +#define OSTREE_SUMMARY_COLLECTION_MAP "ostree.summary.collection-map" /* Well-known keys for the additional metadata field in a commit in a ref entry * in a summary file. */ @@ -95,9 +99,13 @@ struct OstreeRepo { GFile *sysroot_dir; char *remotes_config_dir; - GHashTable *txn_refs; + GHashTable *txn_refs; /* (element-type utf8 utf8) */ + GHashTable *txn_collection_refs; /* (element-type OstreeCollectionRef utf8) */ GMutex txn_stats_lock; OstreeRepoTransactionStats txn_stats; + /* Implementation of min-free-space-percent */ + gulong txn_blocksize; + fsblkcnt_t max_txn_blocks; GMutex cache_lock; guint dirmeta_cache_refcount; @@ -119,6 +127,7 @@ struct OstreeRepo { uid_t owner_uid; uid_t target_owner_uid; gid_t target_owner_gid; + guint min_free_space_percent; guint test_error_flags; /* OstreeRepoTestErrorFlags */ @@ -129,6 +138,7 @@ struct OstreeRepo { gboolean enable_uncompressed_cache; gboolean generate_sizes; guint64 tmp_expiry_seconds; + gchar *collection_id; OstreeRepo *parent_repo; }; @@ -206,6 +216,14 @@ _ostree_repo_has_loose_object (OstreeRepo *self, GCancellable *cancellable, GError **error); +gboolean +_ostree_write_bareuser_metadata (int fd, + guint32 uid, + guint32 gid, + guint32 mode, + GVariant *xattrs, + GError **error); + gboolean _ostree_repo_write_directory_meta (OstreeRepo *self, GFileInfo *file_info, @@ -219,7 +237,13 @@ _ostree_repo_update_refs (OstreeRepo *self, GCancellable *cancellable, GError **error); -gboolean +gboolean +_ostree_repo_update_collection_refs (OstreeRepo *self, + GHashTable *refs, + GCancellable *cancellable, + GError **error); + +gboolean _ostree_repo_file_replace_contents (OstreeRepo *self, int dfd, const char *path, @@ -228,13 +252,13 @@ _ostree_repo_file_replace_contents (OstreeRepo *self, GCancellable *cancellable, GError **error); -gboolean -_ostree_repo_write_ref (OstreeRepo *self, - const char *remote, - const char *ref, - const char *rev, - GCancellable *cancellable, - GError **error); +gboolean +_ostree_repo_write_ref (OstreeRepo *self, + const char *remote, + const OstreeCollectionRef *ref, + const char *rev, + GCancellable *cancellable, + GError **error); OstreeRepoFile * _ostree_repo_file_new_for_commit (OstreeRepo *repo, @@ -285,26 +309,26 @@ _ostree_repo_verify_commit_internal (OstreeRepo *self, GError **error); gboolean -_ostree_repo_commit_loose_final (OstreeRepo *self, - const char *checksum, - OstreeObjectType objtype, - int temp_dfd, - int fd, - const char *temp_filename, - GCancellable *cancellable, - GError **error); +_ostree_repo_commit_tmpf_final (OstreeRepo *self, + const char *checksum, + OstreeObjectType objtype, + GLnxTmpfile *tmpf, + GCancellable *cancellable, + GError **error); -typedef struct { - int fd; - char *temp_filename; -} OstreeRepoContentBareCommit; +gboolean +_ostree_repo_commit_path_final (OstreeRepo *self, + const char *checksum, + OstreeObjectType objtype, + OtCleanupUnlinkat *tmp_path, + GCancellable *cancellable, + GError **error); gboolean _ostree_repo_open_content_bare (OstreeRepo *self, const char *checksum, guint64 content_len, - OstreeRepoContentBareCommit *out_state, - GOutputStream **out_stream, + GLnxTmpfile *out_tmpf, gboolean *out_have_object, GCancellable *cancellable, GError **error); @@ -312,7 +336,7 @@ _ostree_repo_open_content_bare (OstreeRepo *self, gboolean _ostree_repo_commit_trusted_content_bare (OstreeRepo *self, const char *checksum, - OstreeRepoContentBareCommit *state, + GLnxTmpfile *tmpf, guint32 uid, guint32 gid, guint32 mode, @@ -321,11 +345,14 @@ _ostree_repo_commit_trusted_content_bare (OstreeRepo *self, GError **error); gboolean -_ostree_repo_read_bare_fd (OstreeRepo *self, - const char *checksum, - int *out_fd, - GCancellable *cancellable, - GError **error); +_ostree_repo_load_file_bare (OstreeRepo *self, + const char *checksum, + int *out_fd, + struct stat *out_stbuf, + char **out_symlink, + GVariant **out_xattrs, + GCancellable *cancellable, + GError **error); gboolean _ostree_repo_update_mtime (OstreeRepo *self, @@ -346,4 +373,29 @@ _ostree_repo_get_remote_inherited (OstreeRepo *self, const char *name, GError **error); +#ifndef OSTREE_ENABLE_EXPERIMENTAL_API + +const gchar * ostree_repo_get_collection_id (OstreeRepo *self); +gboolean ostree_repo_set_collection_id (OstreeRepo *self, + const gchar *collection_id, + GError **error); + +gboolean ostree_repo_list_collection_refs (OstreeRepo *self, + const char *match_collection_id, + GHashTable **out_all_refs, + GCancellable *cancellable, + GError **error); + +void ostree_repo_transaction_set_collection_ref (OstreeRepo *self, + const OstreeCollectionRef *ref, + const char *checksum); + +gboolean ostree_repo_set_collection_ref_immediate (OstreeRepo *self, + const OstreeCollectionRef *ref, + const char *checksum, + GCancellable *cancellable, + GError **error); + +#endif /* !OSTREE_ENABLE_EXPERIMENTAL_API */ + G_END_DECLS diff --git a/src/libostree/ostree-repo-prune.c b/src/libostree/ostree-repo-prune.c index bf0a2530..7cd9eb2c 100644 --- a/src/libostree/ostree-repo-prune.c +++ b/src/libostree/ostree-repo-prune.c @@ -232,8 +232,6 @@ repo_prune_internal (OstreeRepo *self, GCancellable *cancellable, GError **error) { - GHashTableIter hash_iter; - gpointer key, value; OtPruneData data = { 0, }; data.repo = self; @@ -241,11 +239,8 @@ repo_prune_internal (OstreeRepo *self, g_autoptr(GHashTable) reachable_owned = g_hash_table_ref (options->reachable); data.reachable = reachable_owned; - g_hash_table_iter_init (&hash_iter, objects); - while (g_hash_table_iter_next (&hash_iter, &key, &value)) + GLNX_HASH_TABLE_FOREACH_KV (objects, GVariant*, serialized_key, GVariant*, objdata) { - GVariant *serialized_key = key; - GVariant *objdata = value; const char *checksum; OstreeObjectType objtype; gboolean is_loose; @@ -309,14 +304,10 @@ ostree_repo_prune (OstreeRepo *self, GCancellable *cancellable, GError **error) { - GHashTableIter hash_iter; - gpointer key, value; g_autoptr(GHashTable) objects = NULL; - g_autoptr(GHashTable) all_refs = NULL; - g_autoptr(GHashTable) reachable = NULL; gboolean refs_only = flags & OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY; - reachable = ostree_repo_traverse_new_reachable (); + g_autoptr(GHashTable) reachable = ostree_repo_traverse_new_reachable (); /* This original prune API has fixed logic for traversing refs or all commits * combined with actually deleting content. The newer backend API just does @@ -325,16 +316,30 @@ ostree_repo_prune (OstreeRepo *self, if (refs_only) { + /* Ignoring collections. */ + g_autoptr(GHashTable) all_refs = NULL; /* (element-type utf8 utf8) */ + if (!ostree_repo_list_refs (self, NULL, &all_refs, cancellable, error)) return FALSE; - g_hash_table_iter_init (&hash_iter, all_refs); - - while (g_hash_table_iter_next (&hash_iter, &key, &value)) + GLNX_HASH_TABLE_FOREACH_V (all_refs, const char*, checksum) { - const char *checksum = value; + g_debug ("Finding objects to keep for commit %s", checksum); + if (!ostree_repo_traverse_commit_union (self, checksum, depth, reachable, + cancellable, error)) + return FALSE; + } + /* Using collections. */ + g_autoptr(GHashTable) all_collection_refs = NULL; /* (element-type OstreeChecksumRef utf8) */ + + if (!ostree_repo_list_collection_refs (self, NULL, &all_collection_refs, + cancellable, error)) + return FALSE; + + GLNX_HASH_TABLE_FOREACH_V (all_collection_refs, const char*, checksum) + { g_debug ("Finding objects to keep for commit %s", checksum); if (!ostree_repo_traverse_commit_union (self, checksum, depth, reachable, cancellable, error)) @@ -348,10 +353,8 @@ ostree_repo_prune (OstreeRepo *self, if (!refs_only) { - g_hash_table_iter_init (&hash_iter, objects); - while (g_hash_table_iter_next (&hash_iter, &key, &value)) + GLNX_HASH_TABLE_FOREACH (objects, GVariant*, serialized_key) { - GVariant *serialized_key = key; const char *checksum; OstreeObjectType objtype; diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index 2c87fd60..b51220bb 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -1,6 +1,7 @@ /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- * * Copyright (C) 2011,2012,2013 Colin Walters + * Copyright © 2017 Endless Mobile, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -17,7 +18,9 @@ * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * - * Author: Colin Walters + * Authors: + * - Colin Walters + * - Philip Withnall */ #include "config.h" @@ -33,9 +36,20 @@ #include "ostree-repo-static-delta-private.h" #include "ostree-metalink.h" #include "ostree-fetcher-util.h" +#include "ostree-remote-private.h" #include "ot-fs-utils.h" +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API +#include "ostree-repo-finder.h" +#include "ostree-repo-finder-config.h" +#include "ostree-repo-finder-mount.h" +#ifdef HAVE_AVAHI +#include "ostree-repo-finder-avahi.h" +#endif /* HAVE_AVAHI */ +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ + #include +#include #define OSTREE_REPO_PULL_CONTENT_PRIORITY (OSTREE_FETCHER_DEFAULT_PRIORITY) #define OSTREE_REPO_PULL_METADATA_PRIORITY (OSTREE_REPO_PULL_CONTENT_PRIORITY - 100) @@ -50,6 +64,7 @@ typedef struct { GPtrArray *meta_mirrorlist; /* List of base URIs for fetching metadata */ GPtrArray *content_mirrorlist; /* List of base URIs for fetching content */ OstreeRepo *remote_repo_local; + GPtrArray *localcache_repos; /* Array */ GMainContext *main_context; GCancellable *cancellable; @@ -104,6 +119,9 @@ typedef struct { guint n_fetched_deltapart_fallbacks; guint n_fetched_metadata; guint n_fetched_content; + /* Objects from pull --localcache-repo */ + guint n_fetched_localcache_metadata; + guint n_fetched_localcache_content; int maxdepth; guint64 start_time; @@ -222,6 +240,8 @@ update_progress (gpointer user_data) "scanned-metadata", "u", n_scanned_metadata, "bytes-transferred", "t", bytes_transferred, "start-time", "t", start_time, + "metadata-fetched-localcache", "u", pull_data->n_fetched_localcache_metadata, + "content-fetched-localcache", "u", pull_data->n_fetched_localcache_content, /* Deltas */ "fetched-delta-parts", "u", pull_data->n_fetched_deltaparts, @@ -592,16 +612,15 @@ validate_bareuseronly_mode (OtPullData *pull_data, */ static gboolean import_one_local_content_object (OtPullData *pull_data, + OstreeRepo *src_repo, const char *checksum, GCancellable *cancellable, GError **error) { - g_assert (pull_data->remote_repo_local); - const gboolean trusted = !pull_data->is_untrusted; if (trusted && !pull_data->is_bareuseronly_files) { - if (!ostree_repo_import_object_from_with_trust (pull_data->repo, pull_data->remote_repo_local, + if (!ostree_repo_import_object_from_with_trust (pull_data->repo, src_repo, OSTREE_OBJECT_TYPE_FILE, checksum, trusted, cancellable, error)) @@ -616,7 +635,7 @@ import_one_local_content_object (OtPullData *pull_data, g_autoptr(GFileInfo) content_finfo = NULL; g_autoptr(GVariant) content_xattrs = NULL; - if (!ostree_repo_load_file (pull_data->remote_repo_local, checksum, + if (!ostree_repo_load_file (src_repo, checksum, &content_input, &content_finfo, &content_xattrs, cancellable, error)) return FALSE; @@ -688,19 +707,50 @@ scan_dirtree_object (OtPullData *pull_data, if (file_is_stored) continue; + /* Already have a request pending? If so, move on to the next */ + if (g_hash_table_lookup (pull_data->requested_content, file_checksum)) + continue; + /* Is this a local repo? */ if (pull_data->remote_repo_local) { - if (!import_one_local_content_object (pull_data, file_checksum, cancellable, error)) + if (!import_one_local_content_object (pull_data, pull_data->remote_repo_local, + file_checksum, cancellable, error)) return FALSE; + + /* Note early loop continue */ + continue; } - else if (!g_hash_table_lookup (pull_data->requested_content, file_checksum)) + + /* We're doing HTTP, but see if we have the object in a local cache first */ + gboolean did_import_from_cache_repo = FALSE; + if (pull_data->localcache_repos) { - /* In this case we're doing HTTP pulls */ - g_hash_table_add (pull_data->requested_content, file_checksum); - enqueue_one_object_request (pull_data, file_checksum, OSTREE_OBJECT_TYPE_FILE, path, FALSE, FALSE); - file_checksum = NULL; /* Transfer ownership */ + for (guint j = 0; j < pull_data->localcache_repos->len; j++) + { + OstreeRepo *localcache_repo = pull_data->localcache_repos->pdata[j]; + gboolean localcache_repo_has_obj; + + if (!ostree_repo_has_object (localcache_repo, OSTREE_OBJECT_TYPE_FILE, file_checksum, + &localcache_repo_has_obj, cancellable, error)) + return FALSE; + if (!localcache_repo_has_obj) + continue; + if (!import_one_local_content_object (pull_data, localcache_repo, file_checksum, + cancellable, error)) + return FALSE; + did_import_from_cache_repo = TRUE; + pull_data->n_fetched_localcache_content++; + break; + } } + if (did_import_from_cache_repo) + continue; /* Note early continue */ + + /* Not available locally, queue a HTTP request */ + g_hash_table_add (pull_data->requested_content, file_checksum); + enqueue_one_object_request (pull_data, file_checksum, OSTREE_OBJECT_TYPE_FILE, path, FALSE, FALSE); + file_checksum = NULL; /* Transfer ownership */ } g_autoptr(GVariant) dirs_variant = g_variant_get_child_value (tree, 1); @@ -738,13 +788,20 @@ scan_dirtree_object (OtPullData *pull_data, } static gboolean -fetch_ref_contents (OtPullData *pull_data, - const char *ref, - char **out_contents, - GCancellable *cancellable, - GError **error) +fetch_ref_contents (OtPullData *pull_data, + const char *main_collection_id, + const OstreeCollectionRef *ref, + char **out_contents, + GCancellable *cancellable, + GError **error) { - g_autofree char *filename = g_build_filename ("refs", "heads", ref, NULL); + g_autofree char *filename = NULL; + + if (ref->collection_id == NULL || g_strcmp0 (ref->collection_id, main_collection_id) == 0) + filename = g_build_filename ("refs", "heads", ref->ref_name, NULL); + else + filename = g_build_filename ("refs", "mirrors", ref->collection_id, ref->ref_name, NULL); + g_autofree char *ret_contents = NULL; if (!fetch_mirrored_uri_contents_utf8_sync (pull_data->fetcher, pull_data->meta_mirrorlist, @@ -762,16 +819,46 @@ fetch_ref_contents (OtPullData *pull_data, } static gboolean -lookup_commit_checksum_from_summary (OtPullData *pull_data, - const char *ref, - char **out_checksum, - gsize *out_size, - GError **error) +lookup_commit_checksum_and_collection_from_summary (OtPullData *pull_data, + const OstreeCollectionRef *ref, + char **out_checksum, + gsize *out_size, + char **out_collection_id, + GError **error) { - g_autoptr(GVariant) refs = g_variant_get_child_value (pull_data->summary, 0); + g_autoptr(GVariant) additional_metadata = g_variant_get_child_value (pull_data->summary, 1); + const gchar *main_collection_id; + + if (!g_variant_lookup (additional_metadata, OSTREE_SUMMARY_COLLECTION_ID, "&s", &main_collection_id)) + main_collection_id = NULL; + + g_autoptr(GVariant) refs = NULL; + const gchar *resolved_collection_id = NULL; + + if (ref->collection_id == NULL || g_strcmp0 (ref->collection_id, main_collection_id) == 0) + { + refs = g_variant_get_child_value (pull_data->summary, 0); + resolved_collection_id = main_collection_id; + } + else if (ref->collection_id != NULL) + { + g_autoptr(GVariant) collection_map = NULL; + + collection_map = g_variant_lookup_value (additional_metadata, OSTREE_SUMMARY_COLLECTION_MAP, + G_VARIANT_TYPE ("a{sa(s(taya{sv}))}")); + if (collection_map != NULL) + refs = g_variant_lookup_value (collection_map, ref->collection_id, G_VARIANT_TYPE ("a(s(taya{sv}))")); + resolved_collection_id = ref->collection_id; + } + int i; - if (!ot_variant_bsearch_str (refs, ref, &i)) - return glnx_throw (error, "No such branch '%s' in repository summary", ref); + if (refs == NULL || !ot_variant_bsearch_str (refs, ref->ref_name, &i)) + { + if (ref->collection_id != NULL) + return glnx_throw (error, "No such branch (%s, %s) in repository summary", ref->collection_id, ref->ref_name); + else + return glnx_throw (error, "No such branch '%s' in repository summary", ref->ref_name); + } g_autoptr(GVariant) refdata = g_variant_get_child_value (refs, i); g_autoptr(GVariant) reftargetdata = g_variant_get_child_value (refdata, 1); @@ -779,11 +866,15 @@ lookup_commit_checksum_from_summary (OtPullData *pull_data, g_autoptr(GVariant) commit_csum_v = NULL; g_variant_get (reftargetdata, "(t@ay@a{sv})", &commit_size, &commit_csum_v, NULL); + if (resolved_collection_id != NULL && + !ostree_validate_collection_id (resolved_collection_id, error)) + return FALSE; if (!ostree_validate_structureof_csum_v (commit_csum_v, error)) return FALSE; *out_checksum = ostree_checksum_from_bytes_v (commit_csum_v); *out_size = commit_size; + *out_collection_id = g_strdup (resolved_collection_id); return TRUE; } @@ -856,13 +947,13 @@ content_fetch_on_complete (GObject *object, g_autoptr(GVariant) xattrs = NULL; g_autoptr(GInputStream) file_in = NULL; g_autoptr(GInputStream) object_input = NULL; - g_autofree char *temp_path = NULL; + g_auto(OtCleanupUnlinkat) tmp_unlinker = { _ostree_fetcher_get_dfd (fetcher), NULL }; const char *checksum; g_autofree char *checksum_obj = NULL; OstreeObjectType objtype; gboolean free_fetch_data = TRUE; - if (!_ostree_fetcher_request_to_tmpfile_finish (fetcher, result, &temp_path, error)) + if (!_ostree_fetcher_request_to_tmpfile_finish (fetcher, result, &tmp_unlinker.path, error)) goto out; ostree_object_name_deserialize (fetch_data->object, &checksum, &objtype); @@ -886,9 +977,9 @@ content_fetch_on_complete (GObject *object, if (!have_object) { - if (!_ostree_repo_commit_loose_final (pull_data->repo, checksum, OSTREE_OBJECT_TYPE_FILE, - _ostree_fetcher_get_dfd (fetcher), -1, temp_path, - cancellable, error)) + if (!_ostree_repo_commit_path_final (pull_data->repo, checksum, objtype, + &tmp_unlinker, + cancellable, error)) goto out; } pull_data->n_fetched_content++; @@ -897,21 +988,18 @@ content_fetch_on_complete (GObject *object, { /* Non-mirroring path */ + /* If it appears corrupted, we'll delete it below */ if (!ostree_content_file_parse_at (TRUE, _ostree_fetcher_get_dfd (fetcher), - temp_path, FALSE, + tmp_unlinker.path, FALSE, &file_in, &file_info, &xattrs, cancellable, error)) - { - /* If it appears corrupted, delete it */ - (void) unlinkat (_ostree_fetcher_get_dfd (fetcher), temp_path, 0); - goto out; - } + goto out; /* Also, delete it now that we've opened it, we'll hold * a reference to the fd. If we fail to validate or write, then * the temp space will be cleaned up. */ - (void) unlinkat (_ostree_fetcher_get_dfd (fetcher), temp_path, 0); + ot_cleanup_unlinkat (&tmp_unlinker); if (!validate_bareuseronly_mode (pull_data, checksum, @@ -992,7 +1080,7 @@ meta_fetch_on_complete (GObject *object, FetchObjectData *fetch_data = user_data; OtPullData *pull_data = fetch_data->pull_data; g_autoptr(GVariant) metadata = NULL; - g_autofree char *temp_path = NULL; + g_auto(OtCleanupUnlinkat) tmp_unlinker = { _ostree_fetcher_get_dfd (fetcher), NULL }; const char *checksum; g_autofree char *checksum_obj = NULL; OstreeObjectType objtype; @@ -1006,7 +1094,7 @@ meta_fetch_on_complete (GObject *object, g_debug ("fetch of %s%s complete", checksum_obj, fetch_data->is_detached_meta ? " (detached)" : ""); - if (!_ostree_fetcher_request_to_tmpfile_finish (fetcher, result, &temp_path, error)) + if (!_ostree_fetcher_request_to_tmpfile_finish (fetcher, result, &tmp_unlinker.path, error)) { if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { @@ -1049,22 +1137,24 @@ meta_fetch_on_complete (GObject *object, if (objtype == OSTREE_OBJECT_TYPE_TOMBSTONE_COMMIT) goto out; - fd = openat (_ostree_fetcher_get_dfd (fetcher), temp_path, O_RDONLY | O_CLOEXEC); + fd = openat (_ostree_fetcher_get_dfd (fetcher), tmp_unlinker.path, O_RDONLY | O_CLOEXEC); if (fd == -1) { glnx_set_error_from_errno (error); goto out; } + /* Now delete it, keeping the fd open as the last reference; see comment in + * corresponding content fetch path. + */ + ot_cleanup_unlinkat (&tmp_unlinker); + if (fetch_data->is_detached_meta) { if (!ot_util_variant_map_fd (fd, 0, G_VARIANT_TYPE ("a{sv}"), FALSE, &metadata, error)) goto out; - /* Now delete it, see comment in corresponding content fetch path */ - (void) unlinkat (_ostree_fetcher_get_dfd (fetcher), temp_path, 0); - if (!ostree_repo_write_commit_detached_metadata (pull_data->repo, checksum, metadata, pull_data->cancellable, error)) goto out; @@ -1082,8 +1172,6 @@ meta_fetch_on_complete (GObject *object, FALSE, &metadata, error)) goto out; - (void) unlinkat (_ostree_fetcher_get_dfd (fetcher), temp_path, 0); - /* Write the commitpartial file now while we're still fetching data */ if (objtype == OSTREE_OBJECT_TYPE_COMMIT) { @@ -1437,22 +1525,17 @@ scan_one_metadata_object_c (OtPullData *pull_data, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - g_autoptr(GVariant) object = NULL; - g_autofree char *tmp_checksum = NULL; - gboolean is_requested; - gboolean is_stored; - - tmp_checksum = ostree_checksum_from_bytes (csum); - object = ostree_object_name_serialize (tmp_checksum, objtype); + g_autofree char *tmp_checksum = ostree_checksum_from_bytes (csum); + g_autoptr(GVariant) object = ostree_object_name_serialize (tmp_checksum, objtype); if (g_hash_table_lookup (pull_data->scanned_metadata, object)) return TRUE; - is_requested = g_hash_table_lookup (pull_data->requested_metadata, object) != NULL; + gboolean is_requested = g_hash_table_lookup (pull_data->requested_metadata, object) != NULL; + gboolean is_stored; if (!ostree_repo_has_object (pull_data->repo, objtype, tmp_checksum, &is_stored, cancellable, error)) - goto out; + return FALSE; if (pull_data->remote_repo_local) { @@ -1461,16 +1544,48 @@ scan_one_metadata_object_c (OtPullData *pull_data, if (objtype == OSTREE_OBJECT_TYPE_COMMIT) { if (!write_commitpartial_for (pull_data, tmp_checksum, error)) - goto out; + return FALSE; } if (!ostree_repo_import_object_from_with_trust (pull_data->repo, pull_data->remote_repo_local, objtype, tmp_checksum, !pull_data->is_untrusted, cancellable, error)) - goto out; + return FALSE; + /* The import API will fetch both the commit and detached metadata, so + * add it to the hash to avoid re-fetching it below. + */ + if (objtype == OSTREE_OBJECT_TYPE_COMMIT) + g_hash_table_add (pull_data->fetched_detached_metadata, g_strdup (tmp_checksum)); } is_stored = TRUE; is_requested = TRUE; } + /* Do we have any localcache repos? */ + else if (!is_stored && pull_data->localcache_repos) + { + for (guint i = 0; i < pull_data->localcache_repos->len; i++) + { + OstreeRepo *refd_repo = pull_data->localcache_repos->pdata[i]; + gboolean localcache_repo_has_obj; + + if (!ostree_repo_has_object (refd_repo, objtype, tmp_checksum, + &localcache_repo_has_obj, cancellable, error)) + return FALSE; + if (!localcache_repo_has_obj) + continue; + if (!ostree_repo_import_object_from_with_trust (pull_data->repo, refd_repo, + objtype, tmp_checksum, + !pull_data->is_untrusted, + cancellable, error)) + return FALSE; + /* See comment above */ + if (objtype == OSTREE_OBJECT_TYPE_COMMIT) + g_hash_table_add (pull_data->fetched_detached_metadata, g_strdup (tmp_checksum)); + is_stored = TRUE; + is_requested = TRUE; + pull_data->n_fetched_localcache_metadata++; + break; + } + } if (!is_stored && !is_requested) { @@ -1492,7 +1607,7 @@ scan_one_metadata_object_c (OtPullData *pull_data, { if (!scan_commit_object (pull_data, tmp_checksum, recursion_depth, pull_data->cancellable, error)) - goto out; + return FALSE; g_hash_table_add (pull_data->scanned_metadata, g_variant_ref (object)); pull_data->n_scanned_metadata++; @@ -1502,15 +1617,13 @@ scan_one_metadata_object_c (OtPullData *pull_data, { if (!scan_dirtree_object (pull_data, tmp_checksum, path, recursion_depth, pull_data->cancellable, error)) - goto out; + return FALSE; g_hash_table_add (pull_data->scanned_metadata, g_variant_ref (object)); pull_data->n_scanned_metadata++; } - ret = TRUE; - out: - return ret; + return TRUE; } static void @@ -1625,26 +1738,21 @@ load_remote_repo_config (OtPullData *pull_data, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; g_autofree char *contents = NULL; - GKeyFile *ret_keyfile = NULL; if (!fetch_mirrored_uri_contents_utf8_sync (pull_data->fetcher, pull_data->meta_mirrorlist, "config", &contents, cancellable, error)) - goto out; + return FALSE; - ret_keyfile = g_key_file_new (); + g_autoptr(GKeyFile) ret_keyfile = g_key_file_new (); if (!g_key_file_load_from_data (ret_keyfile, contents, strlen (contents), 0, error)) - goto out; + return FALSE; - ret = TRUE; ot_transfer_out_value (out_keyfile, &ret_keyfile); - out: - g_clear_pointer (&ret_keyfile, (GDestroyNotify) g_key_file_unref); - return ret; + return TRUE; } static gboolean @@ -1654,20 +1762,17 @@ process_one_static_delta_fallback (OtPullData *pull_data, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - g_autoptr(GVariant) csum_v = NULL; - g_autofree char *checksum = NULL; guint8 objtype_y; - OstreeObjectType objtype; - gboolean is_stored; + g_autoptr(GVariant) csum_v = NULL; guint64 compressed_size, uncompressed_size; g_variant_get (fallback_object, "(y@aytt)", &objtype_y, &csum_v, &compressed_size, &uncompressed_size); + if (!ostree_validate_structureof_objtype (objtype_y, error)) - goto out; + return FALSE; if (!ostree_validate_structureof_csum_v (csum_v, error)) - goto out; + return FALSE; compressed_size = maybe_swap_endian_u64 (delta_byteswap, compressed_size); uncompressed_size = maybe_swap_endian_u64 (delta_byteswap, uncompressed_size); @@ -1676,33 +1781,27 @@ process_one_static_delta_fallback (OtPullData *pull_data, pull_data->total_deltapart_size += compressed_size; pull_data->total_deltapart_usize += uncompressed_size; - objtype = (OstreeObjectType)objtype_y; - checksum = ostree_checksum_from_bytes_v (csum_v); + OstreeObjectType objtype = (OstreeObjectType)objtype_y; + g_autofree char *checksum = ostree_checksum_from_bytes_v (csum_v); + gboolean is_stored; if (!ostree_repo_has_object (pull_data->repo, objtype, checksum, &is_stored, cancellable, error)) - goto out; + return FALSE; if (is_stored) pull_data->fetched_deltapart_size += compressed_size; if (pull_data->dry_run) - { - ret = TRUE; - goto out; - } + return TRUE; /* Note early return */ if (!is_stored) { /* The delta compiler never did this, there's no reason to support it */ if (OSTREE_OBJECT_TYPE_IS_META (objtype)) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Found metadata object as fallback: %s.%s", checksum, - ostree_object_type_to_string (objtype)); - goto out; - } + return glnx_throw (error, "Found metadata object as fallback: %s.%s", checksum, + ostree_object_type_to_string (objtype)); else { if (!g_hash_table_lookup (pull_data->requested_content, checksum)) @@ -1719,9 +1818,7 @@ process_one_static_delta_fallback (OtPullData *pull_data, } } - ret = TRUE; - out: - return ret; + return TRUE; } static void @@ -1762,6 +1859,11 @@ process_one_static_delta (OtPullData *pull_data, headers = g_variant_get_child_value (delta_superblock, 6); fallback_objects = g_variant_get_child_value (delta_superblock, 7); + /* Gather free space so we can do a check below */ + struct statvfs stvfsbuf; + if (TEMP_FAILURE_RETRY (fstatvfs (pull_data->repo->repo_dir_fd, &stvfsbuf)) < 0) + return glnx_throw_errno_prefix (error, "fstatvfs"); + /* First process the fallbacks */ n = g_variant_n_children (fallback_objects); for (i = 0; i < n; i++) @@ -1930,6 +2032,21 @@ process_one_static_delta (OtPullData *pull_data, } } + /* The free space check is here since at this point we've parsed the delta not + * only the total size of the parts, but also whether or not we already have + * them. TODO: Ideally this free space check would be above, but we'd have to + * walk everything twice and keep track of state. + */ + const guint64 delta_required_blocks = (pull_data->total_deltapart_usize / stvfsbuf.f_bsize); + if (delta_required_blocks > stvfsbuf.f_bfree) + { + g_autofree char *formatted_required = g_format_size (pull_data->total_deltapart_usize); + g_autofree char *formatted_avail = g_format_size (((guint64)stvfsbuf.f_bsize) * stvfsbuf.f_bfree); + glnx_throw (error, "Delta requires %s free space, but only %s available", + formatted_required, formatted_avail); + goto out; + } + ret = TRUE; out: return ret; @@ -1951,23 +2068,19 @@ get_best_static_delta_start_for (OtPullData *pull_data, GCancellable *cancellable, GError **error) { - GHashTableIter hiter; - gpointer hkey, hvalue; /* Array of possible from checksums */ g_autoptr(GPtrArray) candidates = g_ptr_array_new_with_free_func (g_free); const char *newest_candidate = NULL; guint64 newest_candidate_timestamp = 0; g_assert (pull_data->summary_deltas_checksums != NULL); - g_hash_table_iter_init (&hiter, pull_data->summary_deltas_checksums); *out_have_scratch_delta = FALSE; /* Loop over all deltas known from the summary file, * finding ones which go to to_revision */ - while (g_hash_table_iter_next (&hiter, &hkey, &hvalue)) + GLNX_HASH_TABLE_FOREACH (pull_data->summary_deltas_checksums, const char*, delta_name) { - const char *delta_name = hkey; g_autofree char *cur_from_rev = NULL; g_autofree char *cur_to_rev = NULL; @@ -2570,6 +2683,11 @@ repo_remote_fetch_summary (OstreeRepo *self, } } + /* FIXME: Send the ETag from the cache with the request for summary.sig to + * avoid downloading summary.sig unnecessarily. This won’t normally provide + * any benefits (but won’t do any harm) since summary.sig is typically 500B + * in size. But if a repository has multiple keys, the signature file will + * grow and this optimisation may be useful. */ if (!_ostree_preload_metadata_file (self, fetcher, mirrorlist, @@ -2675,7 +2793,9 @@ initiate_delta_request (OtPullData *pull_data, pull_data->n_requested_metadata++; } -/* @ref - Optional ref name +/* + * initiate_request: + * @ref: Optional ref name and collection ID * @to_revision: Target commit revision we want to fetch * * Start a request for either a ref or a commit. In the @@ -2685,10 +2805,10 @@ initiate_delta_request (OtPullData *pull_data, * `disable_static_deltas` and `require_static_deltas`. */ static gboolean -initiate_request (OtPullData *pull_data, - const char *ref, - const char *to_revision, - GError **error) +initiate_request (OtPullData *pull_data, + const OstreeCollectionRef *ref, + const char *to_revision, + GError **error) { g_autofree char *delta_from_revision = NULL; @@ -2716,7 +2836,7 @@ initiate_request (OtPullData *pull_data, initiate_delta_request (pull_data, NULL, to_revision); else if (pull_data->require_static_deltas) /* No deltas found; are they required? */ { - set_required_deltas_error (error, ref, to_revision); + set_required_deltas_error (error, (ref != NULL) ? ref->ref_name : "", to_revision); return FALSE; } else /* No deltas, fall back to object fetches. */ @@ -2726,7 +2846,11 @@ initiate_request (OtPullData *pull_data, { /* Are we doing a delta via a ref? In that case we can fall back to the older * logic of just using the current tip of the ref as a delta FROM source. */ - if (!ostree_repo_resolve_rev (pull_data->repo, ref, TRUE, + g_autofree char *refspec = NULL; + if (pull_data->remote_name != NULL) + refspec = g_strdup_printf ("%s:%s", pull_data->remote_name, ref->ref_name); + if (!ostree_repo_resolve_rev (pull_data->repo, + (refspec != NULL) ? refspec : ref->ref_name, TRUE, &delta_from_revision, error)) return FALSE; @@ -2782,6 +2906,9 @@ initiate_request (OtPullData *pull_data, * The following are currently defined: * * * refs (as): Array of string refs + * * collection-refs (a(sss)): Array of (collection ID, ref name, checksum) tuples to pull; + * mutually exclusive with `refs` and `override-commit-ids`. Checksums may be the empty + * string to pull the latest commit for that ref * * flags (i): An instance of #OstreeRepoPullFlags * * subdir (s): Pull just this subdirectory * * subdirs (as): Pull just these subdirectories @@ -2797,6 +2924,7 @@ initiate_request (OtPullData *pull_data, * * inherit-transaction (b): Don't initiate, finish or abort a transaction, useful to do multiple pulls in one transaction. * * http-headers (a(ss)): Additional headers to add to all HTTP requests * * update-frequency (u): Frequency to call the async progress callback in milliseconds, if any; only values higher than 0 are valid + * * localcache-repos (as): File paths for local repos to use as caches when doing remote fetches */ gboolean ostree_repo_pull_with_options (OstreeRepo *self, @@ -2807,11 +2935,9 @@ ostree_repo_pull_with_options (OstreeRepo *self, GError **error) { gboolean ret = FALSE; - GHashTableIter hash_iter; - gpointer key, value; g_autoptr(GBytes) bytes_summary = NULL; g_autofree char *metalink_url_str = NULL; - g_autoptr(GHashTable) requested_refs_to_fetch = NULL; + g_autoptr(GHashTable) requested_refs_to_fetch = NULL; /* (element-type OstreeCollectionRef utf8) */ g_autoptr(GHashTable) commits_to_fetch = NULL; g_autofree char *remote_mode_str = NULL; glnx_unref_object OstreeMetalink *metalink = NULL; @@ -2826,18 +2952,25 @@ ostree_repo_pull_with_options (OstreeRepo *self, const char *dir_to_pull = NULL; g_autofree char **dirs_to_pull = NULL; g_autofree char **refs_to_fetch = NULL; + g_autoptr(GVariantIter) collection_refs_iter = NULL; g_autofree char **override_commit_ids = NULL; GSource *update_timeout = NULL; gboolean opt_gpg_verify_set = FALSE; gboolean opt_gpg_verify_summary_set = FALSE; + gboolean opt_collection_refs_set = FALSE; + const char *main_collection_id = NULL; const char *url_override = NULL; gboolean inherit_transaction = FALSE; + g_autoptr(GHashTable) updated_requested_refs_to_fetch = NULL; /* (element-type OstreeCollectionRef utf8) */ int i; + g_autofree char **opt_localcache_repos = NULL; if (options) { int flags_i = OSTREE_REPO_PULL_FLAGS_NONE; (void) g_variant_lookup (options, "refs", "^a&s", &refs_to_fetch); + opt_collection_refs_set = + g_variant_lookup (options, "collection-refs", "a(sss)", &collection_refs_iter); (void) g_variant_lookup (options, "flags", "i", &flags_i); /* Reduce risk of issues if enum happens to be 64 bit for some reason */ flags = flags_i; @@ -2857,9 +2990,12 @@ ostree_repo_pull_with_options (OstreeRepo *self, (void) g_variant_lookup (options, "inherit-transaction", "b", &inherit_transaction); (void) g_variant_lookup (options, "http-headers", "@a(ss)", &pull_data->extra_headers); (void) g_variant_lookup (options, "update-frequency", "u", &update_frequency); + (void) g_variant_lookup (options, "localcache-repos", "^a&s", &opt_localcache_repos); } g_return_val_if_fail (pull_data->maxdepth >= -1, FALSE); + g_return_val_if_fail (!opt_collection_refs_set || + (refs_to_fetch == NULL && override_commit_ids == NULL), FALSE); if (refs_to_fetch && override_commit_ids) g_return_val_if_fail (g_strv_length (refs_to_fetch) == g_strv_length (override_commit_ids), FALSE); @@ -2919,6 +3055,20 @@ ostree_repo_pull_with_options (OstreeRepo *self, (GDestroyNotify)fetch_object_data_free); pull_data->pending_fetch_deltaparts = g_hash_table_new_full (NULL, NULL, (GDestroyNotify)fetch_static_delta_data_free, NULL); + if (opt_localcache_repos && *opt_localcache_repos) + { + pull_data->localcache_repos = g_ptr_array_new_with_free_func (g_object_unref); + for (char **it = opt_localcache_repos; it && *it; it++) + { + const char *localcache_path = *it; + g_autoptr(GFile) localcache_file = g_file_new_for_path (localcache_path); + g_autoptr(OstreeRepo) cacherepo = ostree_repo_new (localcache_file); + if (!ostree_repo_open (cacherepo, cancellable, error)) + goto out; + g_ptr_array_add (pull_data->localcache_repos, g_steal_pointer (&cacherepo)); + } + } + if (dir_to_pull != NULL || dirs_to_pull != NULL) { pull_data->dirs = g_ptr_array_new_with_free_func (g_free); @@ -2990,7 +3140,10 @@ ostree_repo_pull_with_options (OstreeRepo *self, goto out; pull_data->tmpdir_dfd = pull_data->repo->tmp_dir_fd; - requested_refs_to_fetch = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + requested_refs_to_fetch = g_hash_table_new_full (ostree_collection_ref_hash, + ostree_collection_ref_equal, + (GDestroyNotify) ostree_collection_ref_free, + g_free); commits_to_fetch = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); if (!ostree_repo_get_remote_option (self, @@ -3102,6 +3255,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, } } + /* FIXME: Do we want an analogue of this which supports collection IDs? */ if (!ostree_repo_get_remote_list_option (self, remote_name_or_baseurl, "branches", &configured_branches, error)) @@ -3240,16 +3394,40 @@ ostree_repo_pull_with_options (OstreeRepo *self, goto out; } + if (pull_data->gpg_verify_summary && bytes_summary && bytes_sig) + { + g_autoptr(OstreeGpgVerifyResult) result = NULL; + + result = ostree_repo_verify_summary (self, pull_data->remote_name, + bytes_summary, bytes_sig, + cancellable, error); + if (!ostree_gpg_verify_result_require_valid_signature (result, error)) + goto out; + } + if (bytes_summary) { pull_data->summary_data = g_bytes_ref (bytes_summary); pull_data->summary = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, bytes_summary, FALSE); + if (!g_variant_is_normal_form (pull_data->summary)) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Not normal form"); + goto out; + } + if (!g_variant_is_of_type (pull_data->summary, OSTREE_SUMMARY_GVARIANT_FORMAT)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Doesn't match variant type '%s'", + (char *)OSTREE_SUMMARY_GVARIANT_FORMAT); + goto out; + } + if (bytes_sig) pull_data->summary_data_sig = g_bytes_ref (bytes_sig); } - if (!summary_from_cache && bytes_summary && bytes_sig) { if (!pull_data->remote_repo_local && @@ -3262,26 +3440,15 @@ ostree_repo_pull_with_options (OstreeRepo *self, goto out; } - if (pull_data->gpg_verify_summary && bytes_summary && bytes_sig) - { - g_autoptr(GVariant) sig_variant = NULL; - glnx_unref_object OstreeGpgVerifyResult *result = NULL; - - sig_variant = g_variant_new_from_bytes (OSTREE_SUMMARY_SIG_GVARIANT_FORMAT, bytes_sig, FALSE); - result = _ostree_repo_gpg_verify_with_metadata (self, - bytes_summary, - sig_variant, - pull_data->remote_name, - NULL, - NULL, - cancellable, - error); - if (!ostree_gpg_verify_result_require_valid_signature (result, error)) - goto out; - } - if (pull_data->summary) { + additional_metadata = g_variant_get_child_value (pull_data->summary, 1); + + if (!g_variant_lookup (additional_metadata, OSTREE_SUMMARY_COLLECTION_ID, "&s", &main_collection_id)) + main_collection_id = NULL; + else if (!ostree_validate_collection_id (main_collection_id, error)) + goto out; + refs = g_variant_get_child_value (pull_data->summary, 0); for (i = 0, n = g_variant_n_children (refs); i < n; i++) { @@ -3293,11 +3460,47 @@ ostree_repo_pull_with_options (OstreeRepo *self, if (!ostree_validate_rev (refname, error)) goto out; - if (pull_data->is_mirror && !refs_to_fetch) - g_hash_table_insert (requested_refs_to_fetch, g_strdup (refname), NULL); + if (pull_data->is_mirror && !refs_to_fetch && !opt_collection_refs_set) + { + g_hash_table_insert (requested_refs_to_fetch, + ostree_collection_ref_new (main_collection_id, refname), NULL); + } + } + + g_autoptr(GVariant) collection_map = NULL; + collection_map = g_variant_lookup_value (additional_metadata, OSTREE_SUMMARY_COLLECTION_MAP, G_VARIANT_TYPE ("a{sa(s(taya{sv}))}")); + if (collection_map != NULL) + { + GVariantIter collection_map_iter; + const char *collection_id; + g_autoptr(GVariant) collection_refs = NULL; + + g_variant_iter_init (&collection_map_iter, collection_map); + + while (g_variant_iter_loop (&collection_map_iter, "{&s@a(s(taya{sv}))}", &collection_id, &collection_refs)) + { + if (!ostree_validate_collection_id (collection_id, error)) + goto out; + + for (i = 0, n = g_variant_n_children (collection_refs); i < n; i++) + { + const char *refname; + g_autoptr(GVariant) ref = g_variant_get_child_value (collection_refs, i); + + g_variant_get_child (ref, 0, "&s", &refname); + + if (!ostree_validate_rev (refname, error)) + goto out; + + if (pull_data->is_mirror && !refs_to_fetch && !opt_collection_refs_set) + { + g_hash_table_insert (requested_refs_to_fetch, + ostree_collection_ref_new (collection_id, refname), NULL); + } + } + } } - additional_metadata = g_variant_get_child_value (pull_data->summary, 1); deltas = g_variant_lookup_value (additional_metadata, OSTREE_SUMMARY_STATIC_DELTAS, G_VARIANT_TYPE ("a{sv}")); n = deltas ? g_variant_n_children (deltas) : 0; for (i = 0; i < n; i++) @@ -3321,7 +3524,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, } } - if (pull_data->is_mirror && !refs_to_fetch && !configured_branches) + if (pull_data->is_mirror && !refs_to_fetch && !opt_collection_refs_set && !configured_branches) { if (!bytes_summary) { @@ -3330,7 +3533,18 @@ ostree_repo_pull_with_options (OstreeRepo *self, goto out; } - } + } + else if (opt_collection_refs_set) + { + const gchar *collection_id, *ref_name, *checksum; + + while (g_variant_iter_loop (collection_refs_iter, "(&s&s&s)", &collection_id, &ref_name, &checksum)) + { + g_hash_table_insert (requested_refs_to_fetch, + ostree_collection_ref_new (collection_id, ref_name), + (*checksum != '\0') ? g_strdup (checksum) : NULL); + } + } else if (refs_to_fetch != NULL) { char **strviter = refs_to_fetch; @@ -3347,8 +3561,11 @@ ostree_repo_pull_with_options (OstreeRepo *self, } else { + if (!ostree_validate_rev (branch, error)) + goto out; char *commitid = commitid_strviter ? g_strdup (*commitid_strviter) : NULL; - g_hash_table_insert (requested_refs_to_fetch, g_strdup (branch), commitid); + g_hash_table_insert (requested_refs_to_fetch, + ostree_collection_ref_new (NULL, branch), commitid); } strviter++; @@ -3371,47 +3588,65 @@ ostree_repo_pull_with_options (OstreeRepo *self, for (;branches_iter && *branches_iter; branches_iter++) { const char *branch = *branches_iter; - - g_hash_table_insert (requested_refs_to_fetch, g_strdup (branch), NULL); + + g_hash_table_insert (requested_refs_to_fetch, + ostree_collection_ref_new (NULL, branch), NULL); } } - g_hash_table_iter_init (&hash_iter, requested_refs_to_fetch); - while (g_hash_table_iter_next (&hash_iter, &key, &value)) + /* Resolve the checksum for each ref. This has to be done into a new hash table, + * since we can’t modify the keys of @requested_refs_to_fetch while iterating + * over it, and we need to ensure the collection IDs are resolved too. */ + updated_requested_refs_to_fetch = g_hash_table_new_full (ostree_collection_ref_hash, + ostree_collection_ref_equal, + (GDestroyNotify) ostree_collection_ref_free, + g_free); + GLNX_HASH_TABLE_FOREACH_KV (requested_refs_to_fetch, const OstreeCollectionRef*, ref, + const char*, override_commitid) { - const char *branch = key; - const char *override_commitid = value; - char *contents = NULL; + g_autofree char *contents = NULL; /* Support specifying "" for an override commitid */ if (override_commitid && *override_commitid) { - g_hash_table_replace (requested_refs_to_fetch, g_strdup (branch), g_strdup (override_commitid)); + g_hash_table_replace (updated_requested_refs_to_fetch, ostree_collection_ref_dup (ref), g_strdup (override_commitid)); } - else + else { + g_autoptr(OstreeCollectionRef) ref_with_collection = NULL; + if (pull_data->summary) { gsize commit_size = 0; guint64 *malloced_size; + g_autofree gchar *collection_id = NULL; - if (!lookup_commit_checksum_from_summary (pull_data, branch, &contents, &commit_size, error)) + if (!lookup_commit_checksum_and_collection_from_summary (pull_data, ref, &contents, &commit_size, &collection_id, error)) goto out; + ref_with_collection = ostree_collection_ref_new (collection_id, ref->ref_name); + malloced_size = g_new0 (guint64, 1); *malloced_size = commit_size; g_hash_table_insert (pull_data->expected_commit_sizes, g_strdup (contents), malloced_size); } else { - if (!fetch_ref_contents (pull_data, branch, &contents, cancellable, error)) + if (!fetch_ref_contents (pull_data, main_collection_id, ref, &contents, cancellable, error)) goto out; + + ref_with_collection = ostree_collection_ref_dup (ref); } - /* Transfer ownership of contents */ - g_hash_table_replace (requested_refs_to_fetch, g_strdup (branch), contents); + + g_hash_table_replace (updated_requested_refs_to_fetch, + g_steal_pointer (&ref_with_collection), + g_steal_pointer (&contents)); } } + g_hash_table_unref (requested_refs_to_fetch); + requested_refs_to_fetch = g_steal_pointer (&updated_requested_refs_to_fetch); + /* Create the state directory here - it's new with the commitpartial code, * and may not exist in older repositories. */ @@ -3442,20 +3677,16 @@ ostree_repo_pull_with_options (OstreeRepo *self, g_debug ("resuming legacy transaction"); /* Initiate requests for explicit commit revisions */ - g_hash_table_iter_init (&hash_iter, commits_to_fetch); - while (g_hash_table_iter_next (&hash_iter, &key, &value)) + GLNX_HASH_TABLE_FOREACH_V (commits_to_fetch, const char*, commit) { - const char *commit = value; if (!initiate_request (pull_data, NULL, commit, error)) goto out; } /* Initiate requests for refs */ - g_hash_table_iter_init (&hash_iter, requested_refs_to_fetch); - while (g_hash_table_iter_next (&hash_iter, &key, &value)) + GLNX_HASH_TABLE_FOREACH_KV (requested_refs_to_fetch, const OstreeCollectionRef*, ref, + const char*, to_revision) { - const char *ref = key; - const char *to_revision = value; if (!initiate_request (pull_data, ref, to_revision, error)) goto out; } @@ -3492,29 +3723,31 @@ ostree_repo_pull_with_options (OstreeRepo *self, g_assert_cmpint (pull_data->n_outstanding_content_fetches, ==, 0); g_assert_cmpint (pull_data->n_outstanding_content_write_requests, ==, 0); - g_hash_table_iter_init (&hash_iter, requested_refs_to_fetch); - while (g_hash_table_iter_next (&hash_iter, &key, &value)) + GLNX_HASH_TABLE_FOREACH_KV (requested_refs_to_fetch, const OstreeCollectionRef*, ref, + const char*, checksum) { - const char *ref = key; - const char *checksum = value; g_autofree char *remote_ref = NULL; g_autofree char *original_rev = NULL; if (pull_data->remote_name) - remote_ref = g_strdup_printf ("%s:%s", pull_data->remote_name, ref); + remote_ref = g_strdup_printf ("%s:%s", pull_data->remote_name, ref->ref_name); else - remote_ref = g_strdup (ref); + remote_ref = g_strdup (ref->ref_name); if (!ostree_repo_resolve_rev (pull_data->repo, remote_ref, TRUE, &original_rev, error)) goto out; - + if (original_rev && strcmp (checksum, original_rev) == 0) { } else { - ostree_repo_transaction_set_ref (pull_data->repo, pull_data->is_mirror ? NULL : pull_data->remote_name, - ref, checksum); + if (pull_data->is_mirror) + ostree_repo_transaction_set_collection_ref (pull_data->repo, + ref, checksum); + else + ostree_repo_transaction_set_ref (pull_data->repo, pull_data->remote_name, + ref->ref_name, checksum); } } @@ -3567,6 +3800,11 @@ ostree_repo_pull_with_options (OstreeRepo *self, else g_string_append_printf (buf, "%u metadata, %u content objects fetched", pull_data->n_fetched_metadata, pull_data->n_fetched_content); + if (pull_data->n_fetched_localcache_metadata || + pull_data->n_fetched_localcache_content) + g_string_append_printf (buf, " (%u meta, %u content local)", + pull_data->n_fetched_localcache_metadata, + pull_data->n_fetched_localcache_content); g_string_append_printf (buf, "; %" G_GUINT64_FORMAT " %s transferred in %u seconds", (guint64)(bytes_transferred / shift), @@ -3579,22 +3817,16 @@ ostree_repo_pull_with_options (OstreeRepo *self, /* iterate over commits fetched and delete any commitpartial files */ if (pull_data->dirs == NULL && !pull_data->is_commit_only) { - g_hash_table_iter_init (&hash_iter, requested_refs_to_fetch); - while (g_hash_table_iter_next (&hash_iter, &key, &value)) + GLNX_HASH_TABLE_FOREACH_V (requested_refs_to_fetch, const char*, checksum) { - const char *checksum = value; g_autofree char *commitpartial_path = _ostree_get_commitpartial_path (checksum); - if (!ot_ensure_unlinked_at (pull_data->repo->repo_dir_fd, commitpartial_path, 0)) goto out; } - g_hash_table_iter_init (&hash_iter, commits_to_fetch); - while (g_hash_table_iter_next (&hash_iter, &key, &value)) + GLNX_HASH_TABLE_FOREACH_V (commits_to_fetch, const char*, commit) { - const char *commit = value; g_autofree char *commitpartial_path = _ostree_get_commitpartial_path (commit); - if (!ot_ensure_unlinked_at (pull_data->repo->repo_dir_fd, commitpartial_path, 0)) goto out; } @@ -3620,6 +3852,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, g_clear_object (&pull_data->fetcher); g_clear_pointer (&pull_data->extra_headers, (GDestroyNotify)g_variant_unref); g_clear_object (&pull_data->cancellable); + 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_clear_pointer (&pull_data->meta_mirrorlist, (GDestroyNotify) g_ptr_array_unref); @@ -3645,6 +3878,1324 @@ ostree_repo_pull_with_options (OstreeRepo *self, return ret; } +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API + +/* Structure used in ostree_repo_find_remotes_async() which stores metadata + * about a given OSTree commit. This includes the metadata from the commit + * #GVariant, plus some working state which is used to work out which remotes + * have refs pointing to this commit. */ +typedef struct +{ + gchar *checksum; /* always set */ + guint64 commit_size; /* always set */ + guint64 timestamp; /* 0 for unknown */ + GVariant *additional_metadata; + GArray *refs; /* (element-type gsize), indexes to refs which point to this commit on at least one remote */ +} CommitMetadata; + +static void +commit_metadata_free (CommitMetadata *info) +{ + g_clear_pointer (&info->refs, g_array_unref); + g_free (info->checksum); + g_clear_pointer (&info->additional_metadata, g_variant_unref); + g_free (info); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (CommitMetadata, commit_metadata_free) + +static CommitMetadata * +commit_metadata_new (const gchar *checksum, + guint64 commit_size, + guint64 timestamp, + GVariant *additional_metadata) +{ + g_autoptr(CommitMetadata) info = NULL; + + info = g_new0 (CommitMetadata, 1); + info->checksum = g_strdup (checksum); + info->commit_size = commit_size; + info->timestamp = timestamp; + info->additional_metadata = (additional_metadata != NULL) ? g_variant_ref (additional_metadata) : NULL; + info->refs = g_array_new (FALSE, FALSE, sizeof (gsize)); + + return g_steal_pointer (&info); +} + +/* Structure used in ostree_repo_find_remotes_async() to store a grid (or table) + * of pointers, indexed by rows and columns. Basically an encapsulated 2D array. + * See the comments in ostree_repo_find_remotes_async() for its semantics + * there. */ +typedef struct +{ + gsize width; /* pointers */ + gsize height; /* pointers */ + gconstpointer pointers[]; /* n_pointers = width * height */ +} PointerTable; + +static void +pointer_table_free (PointerTable *table) +{ + g_free (table); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (PointerTable, pointer_table_free) + +/* Both dimensions are in numbers of pointers. */ +static PointerTable * +pointer_table_new (gsize width, + gsize height) +{ + g_autoptr(PointerTable) table = NULL; + + g_return_val_if_fail (width > 0, NULL); + g_return_val_if_fail (height > 0, NULL); + g_return_val_if_fail (width <= (G_MAXSIZE - sizeof (PointerTable)) / sizeof (gconstpointer) / height, NULL); + + table = g_malloc0 (sizeof (PointerTable) + sizeof (gconstpointer) * width * height); + table->width = width; + table->height = height; + + return g_steal_pointer (&table); +} + +static gconstpointer +pointer_table_get (const PointerTable *table, + gsize x, + gsize y) +{ + g_return_val_if_fail (table != NULL, FALSE); + g_return_val_if_fail (x < table->width, FALSE); + g_return_val_if_fail (y < table->height, FALSE); + + return table->pointers[table->width * y + x]; +} + +static void +pointer_table_set (PointerTable *table, + gsize x, + gsize y, + gconstpointer value) +{ + g_return_if_fail (table != NULL); + g_return_if_fail (x < table->width); + g_return_if_fail (y < table->height); + + table->pointers[table->width * y + x] = value; +} + +/* Validate the given struct contains a valid collection ID and ref name. */ +static gboolean +is_valid_collection_ref (const OstreeCollectionRef *ref) +{ + return (ref != NULL && + ostree_validate_rev (ref->ref_name, NULL) && + ostree_validate_collection_id (ref->collection_id, NULL)); +} + +/* Validate @refs is non-%NULL, non-empty, and contains only valid collection + * and ref names. */ +static gboolean +is_valid_collection_ref_array (const OstreeCollectionRef * const *refs) +{ + gsize i; + + if (refs == NULL || *refs == NULL) + return FALSE; + + for (i = 0; refs[i] != NULL; i++) + { + if (!is_valid_collection_ref (refs[i])) + return FALSE; + } + + return TRUE; +} + +/* Validate @finders is non-%NULL, non-empty, and contains only valid + * #OstreeRepoFinder instances. */ +static gboolean +is_valid_finder_array (OstreeRepoFinder **finders) +{ + gsize i; + + if (finders == NULL || *finders == NULL) + return FALSE; + + for (i = 0; finders[i] != NULL; i++) + { + if (!OSTREE_IS_REPO_FINDER (finders[i])) + return FALSE; + } + + return TRUE; +} + +/* Closure used to carry inputs from ostree_repo_find_remotes_async() to + * find_remotes_cb(). */ +typedef struct +{ + OstreeCollectionRef **refs; + GVariant *options; + OstreeAsyncProgress *progress; + OstreeRepoFinder *default_finder_avahi; +} FindRemotesData; + +static void +find_remotes_data_free (FindRemotesData *data) +{ + g_clear_object (&data->default_finder_avahi); + g_clear_object (&data->progress); + g_clear_pointer (&data->options, g_variant_unref); + ostree_collection_ref_freev (data->refs); + + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (FindRemotesData, find_remotes_data_free) + +static FindRemotesData * +find_remotes_data_new (const OstreeCollectionRef * const *refs, + GVariant *options, + OstreeAsyncProgress *progress, + OstreeRepoFinder *default_finder_avahi) +{ + g_autoptr(FindRemotesData) data = NULL; + + data = g_new0 (FindRemotesData, 1); + data->refs = ostree_collection_ref_dupv (refs); + data->options = (options != NULL) ? g_variant_ref (options) : NULL; + data->progress = (progress != NULL) ? g_object_ref (progress) : NULL; + data->default_finder_avahi = (default_finder_avahi != NULL) ? g_object_ref (default_finder_avahi) : NULL; + + return g_steal_pointer (&data); +} + +static gchar * +uint64_secs_to_iso8601 (guint64 secs) +{ + g_autoptr(GDateTime) dt = g_date_time_new_from_unix_utc (secs); + + if (dt != NULL) + return g_date_time_format (dt, "%FT%TZ"); + else + return g_strdup ("invalid"); +} + +static gint +sort_results_cb (gconstpointer a, + gconstpointer b) +{ + const OstreeRepoFinderResult **result_a = (const OstreeRepoFinderResult **) a; + const OstreeRepoFinderResult **result_b = (const OstreeRepoFinderResult **) b; + + return ostree_repo_finder_result_compare (*result_a, *result_b); +} + +static void +repo_finder_result_free0 (OstreeRepoFinderResult *result) +{ + if (result == NULL) + return; + + ostree_repo_finder_result_free (result); +} + +static void find_remotes_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data); + +/** + * ostree_repo_find_remotes_async: + * @self: an #OstreeRepo + * @refs: (array zero-terminated=1): non-empty array of collection–ref pairs to find remotes for + * @options: (nullable): a GVariant `a{sv}` with an extensible set of flags + * @finders: (array zero-terminated=1) (transfer none): non-empty array of + * #OstreeRepoFinder instances to use, or %NULL to use the system defaults + * @progress: (nullable): an #OstreeAsyncProgress to update with the operation’s + * progress, or %NULL + * @cancellable: (nullable): a #GCancellable, or %NULL + * @callback: asynchronous completion callback + * @user_data: data to pass to @callback + * + * Find reachable remote URIs which claim to provide any of the given named + * @refs. This will search for configured remotes (#OstreeRepoFinderConfig), + * mounted volumes (#OstreeRepoFinderMount) and (if enabled at compile time) + * local network peers (#OstreeRepoFinderAvahi). In order to use a custom + * configuration of #OstreeRepoFinder instances, call + * ostree_repo_finder_resolve_all_async() on them individually. + * + * Any remote which is found and which claims to support any of the given @refs + * will be returned in the results. It is possible that a remote claims to + * support a given ref, but turns out not to — it is not possible to verify this + * until ostree_repo_pull_from_remotes_async() is called. + * + * The returned results will be sorted with the most useful first — this is + * typically the remote which claims to provide the most of @refs, at the lowest + * latency. + * + * Each result contains a list of the subset of @refs it claims to provide. It + * is possible for a non-empty list of results to be returned, but for some of + * @refs to not be listed in any of the results. Callers must check for this. + * + * Pass the results to ostree_repo_pull_from_remotes_async() to pull the given @refs + * from those remotes. + * + * No @options are currently supported. + * + * @finders must be a non-empty %NULL-terminated array of the #OstreeRepoFinder + * instances to use, or %NULL to use the system default set of finders, which + * will typically be all available finders using their default options (but + * this is not guaranteed). + * + * GPG verification of the summary and all commits will be used unconditionally. + * + * This will use the thread-default #GMainContext, but will not iterate it. + * + * Since: 2017.8 + */ +void +ostree_repo_find_remotes_async (OstreeRepo *self, + const OstreeCollectionRef * const *refs, + GVariant *options, + OstreeRepoFinder **finders, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_autoptr(GTask) task = NULL; + g_autoptr(FindRemotesData) data = NULL; + OstreeRepoFinder *default_finders[4] = { NULL, }; + g_autoptr(OstreeRepoFinder) finder_config = NULL; + g_autoptr(OstreeRepoFinder) finder_mount = NULL; + g_autoptr(OstreeRepoFinder) finder_avahi = NULL; + + g_return_if_fail (OSTREE_IS_REPO (self)); + g_return_if_fail (is_valid_collection_ref_array (refs)); + g_return_if_fail (options == NULL || + g_variant_is_of_type (options, G_VARIANT_TYPE_VARDICT)); + g_return_if_fail (finders == NULL || is_valid_finder_array (finders)); + g_return_if_fail (progress == NULL || OSTREE_IS_ASYNC_PROGRESS (progress)); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + /* Set up a task for the whole operation. */ + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, ostree_repo_find_remotes_async); + + /* Are we using #OstreeRepoFinders provided by the user, or the defaults? */ + if (finders == NULL) + { +#ifdef HAVE_AVAHI + GMainContext *context = g_main_context_get_thread_default (); + g_autoptr(GError) local_error = NULL; +#endif /* HAVE_AVAHI */ + + finder_config = OSTREE_REPO_FINDER (ostree_repo_finder_config_new ()); + finder_mount = OSTREE_REPO_FINDER (ostree_repo_finder_mount_new (NULL)); +#ifdef HAVE_AVAHI + finder_avahi = OSTREE_REPO_FINDER (ostree_repo_finder_avahi_new (context)); +#endif /* HAVE_AVAHI */ + + default_finders[0] = finder_config; + default_finders[1] = finder_mount; + default_finders[2] = finder_avahi; + + finders = default_finders; + +#ifdef HAVE_AVAHI + ostree_repo_finder_avahi_start (OSTREE_REPO_FINDER_AVAHI (finder_avahi), + &local_error); + + if (local_error != NULL) + { + g_warning ("Avahi finder failed; removing it: %s", local_error->message); + default_finders[2] = NULL; + g_clear_object (&finder_avahi); + } +#endif /* HAVE_AVAHI */ + } + + /* We need to keep a pointer to the default Avahi finder so we can stop it + * again after the operation, which happens implicitly by dropping the final + * ref. */ + data = find_remotes_data_new (refs, options, progress, finder_avahi); + g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) find_remotes_data_free); + + /* Asynchronously resolve all possible remotes for the given refs. */ + ostree_repo_finder_resolve_all_async (finders, refs, self, cancellable, + find_remotes_cb, g_steal_pointer (&task)); +} + +/* Find the first instance of (@collection_id, @ref_name) in @refs and return + * its index; or return %FALSE if nothing’s found. */ +static gboolean +collection_refv_contains (const OstreeCollectionRef * const *refs, + const gchar *collection_id, + const gchar *ref_name, + gsize *out_index) +{ + gsize i; + + for (i = 0; refs[i] != NULL; i++) + { + if (g_str_equal (refs[i]->collection_id, collection_id) && + g_str_equal (refs[i]->ref_name, ref_name)) + { + *out_index = i; + return TRUE; + } + } + + return FALSE; +} + +/* For each ref from @refs which is listed in @summary_refs, cache its metadata + * from the summary file entry into @commit_metadatas, and add the checksum it + * points to into @refs_and_remotes_table at (@ref_index, @result_index). + * @ref_index is the ref’s index in @refs. */ +static gboolean +find_remotes_process_refs (OstreeRepo *self, + const OstreeCollectionRef * const *refs, + OstreeRepoFinderResult *result, + gsize result_index, + const gchar *summary_collection_id, + GVariant *summary_refs, + GHashTable *commit_metadatas, + PointerTable *refs_and_remotes_table) +{ + gsize j, n; + + for (j = 0, n = g_variant_n_children (summary_refs); j < n; j++) + { + const guchar *csum_bytes; + g_autoptr(GVariant) ref_v = NULL, csum_v = NULL, commit_metadata_v = NULL, stored_commit_metadata_v = NULL; + guint64 commit_size, commit_timestamp; + gchar tmp_checksum[OSTREE_SHA256_STRING_LEN + 1]; + gsize ref_index; + g_autoptr(GDateTime) dt = NULL; + g_autoptr(GError) error = NULL; + const gchar *ref_name; + CommitMetadata *commit_metadata; + + /* Check the ref name. */ + ref_v = g_variant_get_child_value (summary_refs, j); + g_variant_get_child (ref_v, 0, "&s", &ref_name); + + if (!ostree_validate_rev (ref_name, &error)) + { + g_debug ("%s: Summary for result ‘%s’ contained invalid ref name ‘%s’: %s", + G_STRFUNC, result->remote->name, ref_name, error->message); + return FALSE; + } + + /* Check the commit checksum. */ + g_variant_get_child (ref_v, 1, "(t@ay@a{sv})", &commit_size, &csum_v, &commit_metadata_v); + + csum_bytes = ostree_checksum_bytes_peek_validate (csum_v, &error); + if (csum_bytes == NULL) + { + g_debug ("%s: Summary for result ‘%s’ contained invalid ref checksum: %s", + G_STRFUNC, result->remote->name, error->message); + return FALSE; + } + + ostree_checksum_inplace_from_bytes (csum_bytes, tmp_checksum); + + /* Is this a ref we care about? */ + if (!collection_refv_contains (refs, summary_collection_id, ref_name, &ref_index)) + continue; + + /* Load the commit metadata from disk if possible, for verification. */ + if (!ostree_repo_load_commit (self, tmp_checksum, &stored_commit_metadata_v, NULL, NULL)) + stored_commit_metadata_v = NULL; + + /* Check the additional metadata. */ + if (!g_variant_lookup (commit_metadata_v, OSTREE_COMMIT_TIMESTAMP, "t", &commit_timestamp)) + commit_timestamp = 0; /* unknown */ + else + commit_timestamp = GUINT64_FROM_BE (commit_timestamp); + + dt = g_date_time_new_from_unix_utc (commit_timestamp); + + if (dt == NULL) + { + g_debug ("%s: Summary for result ‘%s’ contained commit timestamp %" G_GUINT64_FORMAT " which is too far in the future. Resetting to 0.", + G_STRFUNC, result->remote->name, commit_timestamp); + commit_timestamp = 0; + } + + /* Check and store the commit metadata. */ + commit_metadata = g_hash_table_lookup (commit_metadatas, tmp_checksum); + + if (commit_metadata == NULL) + { + commit_metadata = commit_metadata_new (tmp_checksum, commit_size, + (stored_commit_metadata_v != NULL) ? ostree_commit_get_timestamp (stored_commit_metadata_v) : 0, + NULL); + g_hash_table_insert (commit_metadatas, commit_metadata->checksum, + commit_metadata /* transfer */); + } + + /* Update the metadata if possible. */ + if (commit_metadata->timestamp == 0) + { + commit_metadata->timestamp = commit_timestamp; + } + else if (commit_timestamp != 0 && commit_metadata->timestamp != commit_timestamp) + { + g_debug ("%s: Summary for result ‘%s’ contained commit timestamp %" G_GUINT64_FORMAT " which did not match existing timestamp %" G_GUINT64_FORMAT ". Ignoring.", + G_STRFUNC, result->remote->name, commit_timestamp, commit_metadata->timestamp); + return FALSE; + } + + if (commit_size != commit_metadata->commit_size) + { + g_debug ("%s: Summary for result ‘%s’ contained commit size %" G_GUINT64_FORMAT "B which did not match existing size %" G_GUINT64_FORMAT "B. Ignoring.", + G_STRFUNC, result->remote->name, commit_size, commit_metadata->commit_size); + return FALSE; + } + + pointer_table_set (refs_and_remotes_table, ref_index, result_index, commit_metadata->checksum); + g_array_append_val (commit_metadata->refs, ref_index); + + g_debug ("%s: Remote ‘%s’ lists ref ‘%s’ mapping to commit ‘%s’.", + G_STRFUNC, result->remote->name, ref_name, commit_metadata->checksum); + } + + return TRUE; +} + +static void +find_remotes_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data) +{ + OstreeRepo *self; + g_autoptr(GTask) task = NULL; + GCancellable *cancellable; + const FindRemotesData *data; + const OstreeCollectionRef * const *refs; + /* FIXME: We currently do nothing with @progress. Comment out to assuage -Wunused-variable */ + /* OstreeAsyncProgress *progress; */ + g_autoptr(GError) error = NULL; + g_autoptr(GPtrArray) results = NULL; /* (element-type OstreeRepoFinderResult) */ + gsize i; + g_autoptr(PointerTable) refs_and_remotes_table = NULL; /* (element-type commit-checksum) */ + g_autoptr(GHashTable) commit_metadatas = NULL; /* (element-type commit-checksum CommitMetadata) */ + g_autoptr(OstreeFetcher) fetcher = NULL; + g_autofree const gchar **ref_to_latest_commit = NULL; /* indexed as @refs; (element-type commit-checksum) */ + gsize n_refs; + g_autoptr(GPtrArray) remotes_to_remove = NULL; /* (element-type OstreeRemote) */ + g_autoptr(GPtrArray) final_results = NULL; /* (element-type OstreeRepoFinderResult) */ + + task = G_TASK (user_data); + self = OSTREE_REPO (g_task_get_source_object (task)); + cancellable = g_task_get_cancellable (task); + data = g_task_get_task_data (task); + + refs = (const OstreeCollectionRef * const *) data->refs; + /* progress = data->progress; */ + + /* Finish finding the remotes. */ + results = ostree_repo_finder_resolve_all_finish (result, &error); + + if (results == NULL) + { + g_task_return_error (task, g_steal_pointer (&error)); + return; + } + + if (results->len == 0) + { + g_task_return_pointer (task, g_steal_pointer (&results), (GDestroyNotify) g_ptr_array_unref); + return; + } + + /* Throughout this function, we eliminate invalid results from @results by + * clearing them to %NULL. We cannot remove them from the array, as that messes + * up iteration and stored array indices. Accordingly, we need the free function + * to be %NULL-safe. */ + g_ptr_array_set_free_func (results, (GDestroyNotify) repo_finder_result_free0); + + /* FIXME: Add support for options: + * - override-commit-ids (allow downgrades) + * + * Use case: multiple pulls of separate subdirs; want them to use the same + * configuration. + * Use case: downgrading a flatpak app. + */ + + /* FIXME: In future, we also want to pull static delta superblocks in this + * phase, so that we have all the metadata we need for accurate size + * estimation for the actual pull operation. This should check the + * disable-static-deltas option first. */ + + /* Each key must be a pointer to the #CommitMetadata.checksum field of its value. */ + commit_metadatas = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify) commit_metadata_free); + + /* X dimension is an index into @refs. Y dimension is an index into @results. + * Each cell stores the commit checksum which that ref resolves to on that + * remote, or %NULL if the remote doesn’t have that ref. */ + n_refs = g_strv_length ((gchar **) refs); /* it’s not a GStrv, but this works */ + refs_and_remotes_table = pointer_table_new (n_refs, results->len); + remotes_to_remove = g_ptr_array_new_with_free_func (NULL); + + /* Fetch and validate the summary file for each result. */ + /* FIXME: All these downloads could be parallelised; that requires the + * ostree_repo_remote_fetch_summary_with_options() API to be async. */ + for (i = 0; i < results->len; i++) + { + OstreeRepoFinderResult *result = g_ptr_array_index (results, i); + g_autoptr(GBytes) summary_bytes = NULL, summary_sig_bytes = NULL; + g_autoptr(GVariant) summary_v = NULL; + guint64 summary_last_modified; + g_autoptr(GVariant) summary_refs = NULL; + g_autoptr(GVariant) additional_metadata_v = NULL; + g_autofree gchar *summary_collection_id = NULL; + g_autoptr(GVariantIter) summary_collection_map = NULL; + gboolean invalid_result = FALSE; + + /* Add the remote to our internal list of remotes, so other libostree + * API can access it. */ + if (!_ostree_repo_add_remote (self, result->remote)) + g_ptr_array_add (remotes_to_remove, result->remote); + + g_debug ("%s: Fetching summary for remote ‘%s’ with keyring ‘%s’.", + G_STRFUNC, result->remote->name, result->remote->keyring); + + /* Download the summary and signature, and validate the signature. This + * will load from the cache if possible. */ + ostree_repo_remote_fetch_summary_with_options (self, + result->remote->name, + NULL, /* no options */ + &summary_bytes, + &summary_sig_bytes, + cancellable, + &error); + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + goto error; + else if (error != NULL) + { + g_debug ("%s: Failed to download summary for result ‘%s’. Ignoring. %s", + G_STRFUNC, result->remote->name, error->message); + g_clear_pointer (&g_ptr_array_index (results, i), (GDestroyNotify) ostree_repo_finder_result_free); + g_clear_error (&error); + continue; + } + + /* Check the metadata in the summary file, especially whether it contains + * all the @refs we are interested in. */ + summary_v = g_variant_new_from_bytes (OSTREE_SUMMARY_GVARIANT_FORMAT, + summary_bytes, FALSE); + + /* Check the summary’s additional metadata and set up @commit_metadata + * and @refs_and_remotes_table with all the refs listed in the summary + * file which intersect with @refs. */ + additional_metadata_v = g_variant_get_child_value (summary_v, 1); + + if (g_variant_lookup (additional_metadata_v, OSTREE_SUMMARY_COLLECTION_ID, "s", &summary_collection_id)) + { + summary_refs = g_variant_get_child_value (summary_v, 0); + + if (!find_remotes_process_refs (self, refs, result, i, summary_collection_id, summary_refs, + commit_metadatas, refs_and_remotes_table)) + { + g_clear_pointer (&g_ptr_array_index (results, i), (GDestroyNotify) ostree_repo_finder_result_free); + continue; + } + } + + if (!g_variant_lookup (additional_metadata_v, OSTREE_SUMMARY_COLLECTION_MAP, "a{sa(s(taya{sv}))}", &summary_collection_map)) + summary_collection_map = NULL; + + while (summary_collection_map != NULL && + g_variant_iter_loop (summary_collection_map, "{s@a(s(taya{sv}))}", &summary_collection_id, &summary_refs)) + { + if (!find_remotes_process_refs (self, refs, result, i, summary_collection_id, summary_refs, + commit_metadatas, refs_and_remotes_table)) + { + g_clear_pointer (&g_ptr_array_index (results, i), (GDestroyNotify) ostree_repo_finder_result_free); + invalid_result = TRUE; + break; + } + } + + if (invalid_result) + continue; + + /* Check the summary timestamp. */ + if (!g_variant_lookup (additional_metadata_v, OSTREE_SUMMARY_LAST_MODIFIED, "t", &summary_last_modified)) + summary_last_modified = 0; + else + summary_last_modified = GUINT64_FROM_BE (summary_last_modified); + + /* Update the stored result data. Clear the @ref_to_checksum map, since + * it’s been moved to @refs_and_remotes_table and is now potentially out + * of date. */ + g_clear_pointer (&result->ref_to_checksum, g_hash_table_unref); + result->summary_last_modified = summary_last_modified; + } + + /* Fill in any gaps in the metadata for the most recent commits by pulling + * the commit metadata from the remotes. The ‘most recent commits’ are the + * set of head commits pointed to by the refs we just resolved from the + * summary files. */ + GLNX_HASH_TABLE_FOREACH_V (commit_metadatas, CommitMetadata*, commit_metadata) + { + char buf[_OSTREE_LOOSE_PATH_MAX]; + g_autofree gchar *commit_filename = NULL; + g_autoptr(GPtrArray) mirrorlist = NULL; /* (element-type OstreeFetcherURI) */ + g_autoptr(GBytes) commit_bytes = NULL; + g_autoptr(GVariant) commit_v = NULL; + guint64 commit_timestamp; + g_autoptr(GDateTime) dt = NULL; + + /* Already complete? */ + if (commit_metadata->timestamp != 0) + continue; + + _ostree_loose_path (buf, commit_metadata->checksum, OSTREE_OBJECT_TYPE_COMMIT, OSTREE_REPO_MODE_ARCHIVE_Z2); + commit_filename = g_build_filename ("objects", buf, NULL); + + /* For each of the remotes whose summary files contain this ref, try + * downloading the commit metadata until we succeed. Since the results are + * in priority order, the most important remotes are tried first. */ + for (i = 0; i < commit_metadata->refs->len; i++) + { + gsize ref_index = g_array_index (commit_metadata->refs, gsize, i); + gsize j; + + for (j = 0; j < results->len; j++) + { + OstreeRepoFinderResult *result = g_ptr_array_index (results, j); + + /* Previous error processing this result? */ + if (result == NULL) + continue; + + if (pointer_table_get (refs_and_remotes_table, ref_index, j) != commit_metadata->checksum) + continue; + + g_autofree gchar *uri = NULL; + g_autoptr(OstreeFetcherURI) fetcher_uri = NULL; + + if (!ostree_repo_remote_get_url (self, result->remote->name, + &uri, &error)) + goto error; + + fetcher_uri = _ostree_fetcher_uri_parse (uri, &error); + if (fetcher_uri == NULL) + goto error; + + fetcher = _ostree_repo_remote_new_fetcher (self, result->remote->name, + TRUE, &error); + if (fetcher == NULL) + goto error; + + g_debug ("%s: Fetching metadata for commit ‘%s’ from remote ‘%s’.", + G_STRFUNC, commit_metadata->checksum, result->remote->name); + + /* FIXME: Support remotes which have contenturl, mirrorlist, etc. */ + mirrorlist = g_ptr_array_new_with_free_func ((GDestroyNotify) _ostree_fetcher_uri_free); + g_ptr_array_add (mirrorlist, g_steal_pointer (&fetcher_uri)); + + if (!_ostree_fetcher_mirrored_request_to_membuf (fetcher, + mirrorlist, + commit_filename, + FALSE, /* don’t add trailing nul */ + TRUE, /* return NULL on ENOENT */ + &commit_bytes, + 0, /* no maximum size */ + cancellable, + &error)) + goto error; + + glnx_unref_object OstreeGpgVerifyResult *verify_result = NULL; + + verify_result = ostree_repo_verify_commit_for_remote (self, + commit_metadata->checksum, + result->remote->name, + cancellable, + &error); + if (verify_result == NULL) + { + g_prefix_error (&error, "Commit %s: ", commit_metadata->checksum); + goto error; + } + + if (!ostree_gpg_verify_result_require_valid_signature (verify_result, &error)) + { + g_prefix_error (&error, "Commit %s: ", commit_metadata->checksum); + goto error; + } + + if (commit_bytes != NULL) + break; + } + + if (commit_bytes != NULL) + break; + } + + if (commit_bytes == NULL) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Metadata not found for commit ‘%s’", commit_metadata->checksum); + goto error; + } + + /* Parse the commit metadata. */ + commit_v = g_variant_new_from_bytes (OSTREE_COMMIT_GVARIANT_FORMAT, + commit_bytes, FALSE); + g_variant_get_child (commit_v, 5, "t", &commit_timestamp); + commit_timestamp = GUINT64_FROM_BE (commit_timestamp); + dt = g_date_time_new_from_unix_utc (commit_timestamp); + + if (dt == NULL) + { + g_debug ("%s: Commit ‘%s’ metadata contained timestamp %" G_GUINT64_FORMAT " which is too far in the future. Resetting to 0.", + G_STRFUNC, commit_metadata->checksum, commit_timestamp); + commit_timestamp = 0; + } + + /* Update the #CommitMetadata. */ + commit_metadata->timestamp = commit_timestamp; + } + + /* Find the latest commit for each ref. This is where we resolve the + * differences between remotes: two remotes could both contain ref R, but one + * remote could be outdated compared to the other, and point to an older + * commit. For each ref, we want to find the most recent commit any remote + * points to for it. + * + * @ref_to_latest_commit is indexed by @ref_index, and its values are the + * latest checksum for each ref. */ + ref_to_latest_commit = g_new0 (const gchar *, n_refs); + + for (i = 0; i < n_refs; i++) + { + gsize j; + const gchar *latest_checksum = NULL; + const CommitMetadata *latest_commit_metadata = NULL; + g_autofree gchar *latest_commit_timestamp_str = NULL; + + for (j = 0; j < results->len; j++) + { + const CommitMetadata *candidate_commit_metadata; + const gchar *candidate_checksum; + + candidate_checksum = pointer_table_get (refs_and_remotes_table, i, j); + + if (candidate_checksum == NULL) + continue; + + candidate_commit_metadata = g_hash_table_lookup (commit_metadatas, candidate_checksum); + g_assert (candidate_commit_metadata != NULL); + + if (latest_commit_metadata == NULL || + candidate_commit_metadata->timestamp > latest_commit_metadata->timestamp) + { + latest_checksum = candidate_checksum; + latest_commit_metadata = candidate_commit_metadata; + } + } + + /* @latest_checksum could be %NULL here if there was an error downloading + * the summary or commit metadata files above. */ + ref_to_latest_commit[i] = latest_checksum; + + if (latest_commit_metadata != NULL) + { + latest_commit_timestamp_str = uint64_secs_to_iso8601 (latest_commit_metadata->timestamp); + g_debug ("%s: Latest commit for ref (%s, %s) across all remotes is ‘%s’ with timestamp %s.", + G_STRFUNC, refs[i]->collection_id, refs[i]->ref_name, + latest_checksum, latest_commit_timestamp_str); + } + else + { + g_debug ("%s: Latest commit for ref (%s, %s) is unknown due to failure to download metadata.", + G_STRFUNC, refs[i]->collection_id, refs[i]->ref_name); + } + } + + /* Recombine @commit_metadatas and @results so that each + * #OstreeRepoFinderResult.refs lists the refs for which that remote has the + * latest commits (i.e. it’s not out of date compared to some other remote). */ + final_results = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_repo_finder_result_free); + + for (i = 0; i < results->len; i++) + { + OstreeRepoFinderResult *result = g_ptr_array_index (results, i); + g_autoptr(GHashTable) validated_ref_to_checksum = NULL; /* (element-type utf8 utf8) */ + gsize j; + + /* Previous error processing this result? */ + if (result == NULL) + continue; + + /* Map of refs to checksums provided by this result. The checksums should + * be %NULL for each ref unless this result provides the latest checksum. */ + validated_ref_to_checksum = g_hash_table_new_full (ostree_collection_ref_hash, + ostree_collection_ref_equal, + (GDestroyNotify) ostree_collection_ref_free, + g_free); + + for (j = 0; refs[j] != NULL; j++) + { + const gchar *latest_commit_for_ref = ref_to_latest_commit[j]; + + if (pointer_table_get (refs_and_remotes_table, j, i) != latest_commit_for_ref) + latest_commit_for_ref = NULL; + + g_hash_table_insert (validated_ref_to_checksum, ostree_collection_ref_dup (refs[j]), g_strdup (latest_commit_for_ref)); + } + + if (g_hash_table_size (validated_ref_to_checksum) == 0) + { + g_debug ("%s: Omitting remote ‘%s’ from results as none of its refs are new enough.", + G_STRFUNC, result->remote->name); + ostree_repo_finder_result_free (g_steal_pointer (&g_ptr_array_index (results, i))); + continue; + } + + result->ref_to_checksum = g_steal_pointer (&validated_ref_to_checksum); + g_ptr_array_add (final_results, g_steal_pointer (&g_ptr_array_index (results, i))); + } + + /* Ensure the updated results are still in priority order. */ + g_ptr_array_sort (final_results, sort_results_cb); + + /* Remove the remotes we temporarily added. + * FIXME: It would be so much better if we could pass #OstreeRemote pointers + * around internally, to avoid serialising on the global table of them. */ + for (i = 0; i < remotes_to_remove->len; i++) + { + OstreeRemote *remote = g_ptr_array_index (remotes_to_remove, i); + _ostree_repo_remove_remote (self, remote); + } + + g_task_return_pointer (task, g_steal_pointer (&final_results), (GDestroyNotify) g_ptr_array_unref); + + return; + +error: + /* Remove the remotes we temporarily added. */ + for (i = 0; i < remotes_to_remove->len; i++) + { + OstreeRemote *remote = g_ptr_array_index (remotes_to_remove, i); + _ostree_repo_remove_remote (self, remote); + } + + g_task_return_error (task, g_steal_pointer (&error)); +} + +/** + * ostree_repo_find_remotes_finish: + * @self: an #OstreeRepo + * @result: the asynchronous result + * @error: return location for a #GError, or %NULL + * + * Finish an asynchronous pull operation started with + * ostree_repo_find_remotes_async(). + * + * Returns: (transfer full) (array zero-terminated=1): a potentially empty array + * of #OstreeRepoFinderResults, followed by a %NULL terminator element; or + * %NULL on error + * Since: 2017.8 + */ +OstreeRepoFinderResult ** +ostree_repo_find_remotes_finish (OstreeRepo *self, + GAsyncResult *result, + GError **error) +{ + g_autoptr(GPtrArray) results = NULL; + + g_return_val_if_fail (OSTREE_IS_REPO (self), NULL); + g_return_val_if_fail (g_task_is_valid (result, self), NULL); + g_return_val_if_fail (g_async_result_is_tagged (result, ostree_repo_find_remotes_async), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + results = g_task_propagate_pointer (G_TASK (result), error); + + if (results != NULL) + { + g_ptr_array_add (results, NULL); /* NULL terminator */ + return (OstreeRepoFinderResult **) g_ptr_array_free (g_steal_pointer (&results), FALSE); + } + else + return NULL; +} + +static void +copy_option (GVariantDict *master_options, + GVariantDict *slave_options, + const gchar *key, + const GVariantType *expected_type) +{ + g_autoptr(GVariant) option_v = g_variant_dict_lookup_value (master_options, key, expected_type); + if (option_v != NULL) + g_variant_dict_insert_value (slave_options, key, g_steal_pointer (&option_v)); +} + +/** + * ostree_repo_pull_from_remotes_async: + * @self: an #OstreeRepo + * @results: (array zero-terminated=1): %NULL-terminated array of remotes to + * pull from, including the refs to pull from each + * @options: (nullable): A GVariant `a{sv}` with an extensible set of flags + * @progress: (nullable): an #OstreeAsyncProgress to update with the operation’s + * progress, or %NULL + * @cancellable: (nullable): a #GCancellable, or %NULL + * @callback: asynchronous completion callback + * @user_data: data to pass to @callback + * + * Pull refs from multiple remotes which have been found using + * ostree_repo_find_remotes_async(). + * + * @results are expected to be in priority order, with the best remotes to pull + * from listed first. ostree_repo_pull_from_remotes_async() will generally pull + * from the remotes in order, but may parallelise its downloads. + * + * If an error is encountered when pulling from a given remote, that remote will + * be ignored and another will be tried instead. If any refs have not been + * downloaded successfully after all remotes have been tried, %G_IO_ERROR_FAILED + * will be returned. The results of any successful downloads will remain cached + * in the local repository. + * + * If @cancellable is cancelled, %G_IO_ERROR_CANCELLED will be returned + * immediately. The results of any successfully completed downloads at that + * point will remain cached in the local repository. + * + * GPG verification of the summary and all commits will be used unconditionally. + * + * The following @options are currently defined: + * + * * `flags` (`i`): #OstreeRepoPullFlags to apply to the pull operation + * * `inherit-transaction` (`b`): %TRUE to inherit an ongoing transaction on + * the #OstreeRepo, rather than encapsulating the pull in a new one + * + * Since: 2017.8 + */ +void +ostree_repo_pull_from_remotes_async (OstreeRepo *self, + const OstreeRepoFinderResult * const *results, + GVariant *options, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (OSTREE_IS_REPO (self)); + g_return_if_fail (results != NULL && results[0] != NULL); + g_return_if_fail (options == NULL || g_variant_is_of_type (options, G_VARIANT_TYPE ("a{sv}"))); + g_return_if_fail (progress == NULL || OSTREE_IS_ASYNC_PROGRESS (progress)); + g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + g_autoptr(GTask) task = NULL; + g_autoptr(GHashTable) refs_pulled = NULL; /* (element-type OstreeCollectionRef gboolean) */ + gsize i, j; + g_autoptr(GString) refs_unpulled_string = NULL; + g_autoptr(GError) local_error = NULL; + g_auto(GVariantDict) options_dict = OT_VARIANT_BUILDER_INITIALIZER; + OstreeRepoPullFlags flags; + gboolean inherit_transaction; + + /* Set up a task for the whole operation. */ + task = g_task_new (self, cancellable, callback, user_data); + g_task_set_source_tag (task, ostree_repo_pull_from_remotes_async); + + /* Keep track of the set of refs we’ve pulled already. Value is %TRUE if the + * ref has been pulled; %FALSE if it has not. */ + refs_pulled = g_hash_table_new_full (ostree_collection_ref_hash, + ostree_collection_ref_equal, NULL, NULL); + + g_variant_dict_init (&options_dict, options); + + if (!g_variant_dict_lookup (&options_dict, "flags", "i", &flags)) + flags = OSTREE_REPO_PULL_FLAGS_NONE; + if (!g_variant_dict_lookup (&options_dict, "inherit-transaction", "b", &inherit_transaction)) + inherit_transaction = FALSE; + + /* Run all the local pull operations in a single overall transaction. */ + if (!inherit_transaction && + !ostree_repo_prepare_transaction (self, NULL, cancellable, &local_error)) + { + g_task_return_error (task, g_steal_pointer (&local_error)); + return; + } + + /* FIXME: Rework this code to pull in parallel where possible. At the moment + * we expect the (i == 0) iteration will do all the work (all the refs) and + * subsequent iterations are only there in case of error. + * + * The code is currently all synchronous, too. Making it asynchronous requires + * the underlying pull code to be asynchronous. */ + for (i = 0; results[i] != NULL; i++) + { + const OstreeRepoFinderResult *result = results[i]; + + g_autoptr(GString) refs_to_pull_str = NULL; + g_autoptr(GPtrArray) refs_to_pull = NULL; /* (element-type OstreeCollectionRef) */ + g_auto(GVariantBuilder) refs_to_pull_builder = OT_VARIANT_BUILDER_INITIALIZER; + g_auto(GVariantDict) local_options_dict = OT_VARIANT_BUILDER_INITIALIZER; + g_autoptr(GVariant) local_options = NULL; + gboolean remove_remote; + + refs_to_pull = g_ptr_array_new_with_free_func (NULL); + refs_to_pull_str = g_string_new (""); + g_variant_builder_init (&refs_to_pull_builder, G_VARIANT_TYPE ("a(sss)")); + + GLNX_HASH_TABLE_FOREACH_KV (result->ref_to_checksum, const OstreeCollectionRef*, ref, + const char*, checksum) + { + if (checksum != NULL && + !GPOINTER_TO_INT (g_hash_table_lookup (refs_pulled, ref))) + { + g_ptr_array_add (refs_to_pull, (gpointer) ref); + g_variant_builder_add (&refs_to_pull_builder, "(sss)", + ref->collection_id, ref->ref_name, checksum); + + if (refs_to_pull_str->len > 0) + g_string_append (refs_to_pull_str, ", "); + g_string_append_printf (refs_to_pull_str, "(%s, %s)", + ref->collection_id, ref->ref_name); + } + } + + if (refs_to_pull->len == 0) + { + g_debug ("Ignoring remote ‘%s’ as it has no relevant refs or they " + "have already been pulled.", + result->remote->name); + continue; + } + + /* NULL terminators. */ + g_ptr_array_add (refs_to_pull, NULL); + + g_debug ("Pulling from remote ‘%s’: %s", + result->remote->name, refs_to_pull_str->str); + + /* Set up the pull options. */ + g_variant_dict_init (&local_options_dict, NULL); + + g_variant_dict_insert (&local_options_dict, "flags", "i", OSTREE_REPO_PULL_FLAGS_UNTRUSTED | flags); + g_variant_dict_insert_value (&local_options_dict, "collection-refs", g_variant_builder_end (&refs_to_pull_builder)); + g_variant_dict_insert (&local_options_dict, "gpg-verify", "b", TRUE); + g_variant_dict_insert (&local_options_dict, "gpg-verify-summary", "b", TRUE); + g_variant_dict_insert (&local_options_dict, "inherit-transaction", "b", TRUE); + copy_option (&options_dict, &local_options_dict, "depth", G_VARIANT_TYPE ("i")); + copy_option (&options_dict, &local_options_dict, "disable-static-deltas", G_VARIANT_TYPE ("b")); + 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")); + + local_options = g_variant_dict_end (&local_options_dict); + + /* FIXME: We do nothing useful with @progress at the moment. */ + remove_remote = !_ostree_repo_add_remote (self, result->remote); + ostree_repo_pull_with_options (self, result->remote->name, local_options, + progress, cancellable, &local_error); + if (remove_remote) + _ostree_repo_remove_remote (self, result->remote); + + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + { + if (!inherit_transaction) + ostree_repo_abort_transaction (self, NULL, NULL); + g_task_return_error (task, g_steal_pointer (&local_error)); + return; + } + + for (j = 0; refs_to_pull->pdata[j] != NULL; j++) + g_hash_table_replace (refs_pulled, refs_to_pull->pdata[j], + GINT_TO_POINTER (local_error == NULL)); + + if (local_error != NULL) + { + g_debug ("Failed to pull refs from ‘%s’: %s", + result->remote->name, local_error->message); + g_clear_error (&local_error); + continue; + } + else + { + g_debug ("Pulled refs from ‘%s’.", result->remote->name); + } + } + + /* Commit the transaction. */ + if (!inherit_transaction && + !ostree_repo_commit_transaction (self, NULL, cancellable, &local_error)) + { + g_task_return_error (task, g_steal_pointer (&local_error)); + return; + } + + /* Any refs left un-downloaded? If so, we’ve failed. */ + GLNX_HASH_TABLE_FOREACH_KV (refs_pulled, const OstreeCollectionRef*, ref, + gpointer, is_pulled_pointer) + { + gboolean is_pulled = GPOINTER_TO_INT (is_pulled_pointer); + + if (is_pulled) + continue; + + if (refs_unpulled_string == NULL) + refs_unpulled_string = g_string_new (""); + else + g_string_append (refs_unpulled_string, ", "); + + g_string_append_printf (refs_unpulled_string, "(%s, %s)", + ref->collection_id, ref->ref_name); + } + + if (refs_unpulled_string != NULL) + { + g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to pull some refs from the remotes: %s", + refs_unpulled_string->str); + return; + } + + g_task_return_boolean (task, TRUE); +} + +/** + * ostree_repo_pull_from_remotes_finish: + * @self: an #OstreeRepo + * @result: the asynchronous result + * @error: return location for a #GError, or %NULL + * + * Finish an asynchronous pull operation started with + * ostree_repo_pull_from_remotes_async(). + * + * Returns: %TRUE on success, %FALSE otherwise + * Since: 2017.8 + */ +gboolean +ostree_repo_pull_from_remotes_finish (OstreeRepo *self, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (OSTREE_IS_REPO (self), FALSE); + g_return_val_if_fail (g_task_is_valid (result, self), FALSE); + g_return_val_if_fail (g_async_result_is_tagged (result, ostree_repo_pull_from_remotes_async), FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +/* Check whether the given remote exists, has a `collection-id` key set, and it + * equals @collection_id. If so, return %TRUE. Otherwise, %FALSE. */ +static gboolean +check_remote_matches_collection_id (OstreeRepo *repo, + const gchar *remote_name, + const gchar *collection_id) +{ + g_autofree gchar *remote_collection_id = NULL; + + if (!ostree_repo_get_remote_option (repo, remote_name, "collection-id", NULL, + &remote_collection_id, NULL) || + remote_collection_id == NULL) + return FALSE; + + return g_str_equal (remote_collection_id, collection_id); +} + +/** + * ostree_repo_resolve_keyring_for_collection: + * @self: an #OstreeRepo + * @collection_id: the collection ID to look up a keyring for + * @cancellable: (nullable): a #GCancellable, or %NULL + * @error: return location for a #GError, or %NULL + * + * Find the GPG keyring for the given @collection_id, using the local + * configuration from the given #OstreeRepo. This will search the configured + * remotes for ones whose `collection-id` key matches @collection_id, and will + * return the GPG keyring from the first matching remote. + * + * If multiple remotes match and have different keyrings, a debug message will + * be emitted, and the first result will be returned. It is expected that the + * keyrings should match. + * + * If no match can be found, a %G_IO_ERROR_NOT_FOUND error will be returned. + * + * Returns: (transfer full): filename of the GPG keyring for @collection_id + * Since: 2017.8 + */ +gchar * +ostree_repo_resolve_keyring_for_collection (OstreeRepo *self, + const gchar *collection_id, + GCancellable *cancellable, + GError **error) +{ + gsize i; + g_auto(GStrv) remotes = NULL; + const 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); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + /* Look through all the currently configured remotes for the given collection. */ + remotes = ostree_repo_remote_list (self, NULL); + + for (i = 0; remotes != NULL && remotes[i] != NULL; i++) + { + g_autoptr(GError) local_error = NULL; + + if (!check_remote_matches_collection_id (self, remotes[i], collection_id)) + continue; + + if (keyring_remote == NULL) + { + g_debug ("%s: Found match for collection ‘%s’ in remote ‘%s’.", + G_STRFUNC, collection_id, remotes[i]); + keyring_remote = _ostree_repo_get_remote_inherited (self, remotes[i], &local_error); + + if (keyring_remote == NULL) + { + g_debug ("%s: Error loading remote ‘%s’: %s", + G_STRFUNC, remotes[i], local_error->message); + continue; + } + + if (g_strcmp0 (keyring_remote->keyring, "") == 0 || + g_strcmp0 (keyring_remote->keyring, "/dev/null") == 0) + { + g_debug ("%s: Ignoring remote ‘%s’ as it has no keyring configured.", + G_STRFUNC, remotes[i]); + continue; + } + + /* continue so we can catch duplicates */ + } + else + { + g_debug ("%s: Duplicate keyring for collection ‘%s’ in remote ‘%s’." + "Keyring will be loaded from remote ‘%s’.", + G_STRFUNC, collection_id, remotes[i], + keyring_remote->name); + } + } + + if (keyring_remote != NULL) + return g_strdup (keyring_remote->keyring); + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "No keyring found configured locally for collection ‘%s’", + collection_id); + return NULL; + } +} + +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ + /** * ostree_repo_remote_fetch_summary_with_options: * @self: Self diff --git a/src/libostree/ostree-repo-refs.c b/src/libostree/ostree-repo-refs.c index adab50fe..491f22bd 100644 --- a/src/libostree/ostree-repo-refs.c +++ b/src/libostree/ostree-repo-refs.c @@ -20,18 +20,25 @@ #include "config.h" +#include "ostree-core-private.h" #include "ostree-repo-private.h" #include "otutil.h" #include "ot-fs-utils.h" +/* This is polymorphic in @collection_id: if non-%NULL, @refs will be treated as of + * type OstreeCollectionRef ↦ checksum. Otherwise, it will be treated as of type + * refspec ↦ checksum. */ static gboolean add_ref_to_set (const char *remote, + const char *collection_id, int base_fd, const char *path, GHashTable *refs, GCancellable *cancellable, GError **error) { + g_return_val_if_fail (remote == NULL || collection_id == NULL, FALSE); + gsize len; char *contents = glnx_file_get_contents_utf8_at (base_fd, path, &len, cancellable, error); if (!contents) @@ -39,14 +46,21 @@ add_ref_to_set (const char *remote, g_strchomp (contents); - g_autoptr(GString) refname = g_string_new (""); - if (remote) + if (collection_id == NULL) { - g_string_append (refname, remote); - g_string_append_c (refname, ':'); + g_autoptr(GString) refname = g_string_new (""); + if (remote) + { + g_string_append (refname, remote); + g_string_append_c (refname, ':'); + } + g_string_append (refname, path); + g_hash_table_insert (refs, g_string_free (g_steal_pointer (&refname), FALSE), contents); + } + else + { + g_hash_table_insert (refs, ostree_collection_ref_new (collection_id, path), contents); } - g_string_append (refname, path); - g_hash_table_insert (refs, g_string_free (g_steal_pointer (&refname), FALSE), contents); return TRUE; } @@ -99,6 +113,8 @@ write_checksum_file_at (OstreeRepo *self, g_clear_error (&temp_error); + /* FIXME: Conflict detection needs to be extended to collection–refs + * using ostree_repo_list_collection_refs(). */ if (!ostree_repo_list_refs (self, name, &refs, cancellable, error)) return FALSE; @@ -456,6 +472,7 @@ ostree_repo_resolve_rev_ext (OstreeRepo *self, static gboolean enumerate_refs_recurse (OstreeRepo *repo, const char *remote, + const char *collection_id, int base_dfd, GString *base_path, int child_dfd, @@ -485,14 +502,14 @@ enumerate_refs_recurse (OstreeRepo *repo, { g_string_append_c (base_path, '/'); - if (!enumerate_refs_recurse (repo, remote, base_dfd, base_path, + if (!enumerate_refs_recurse (repo, remote, collection_id, base_dfd, base_path, dfd_iter.fd, dent->d_name, refs, cancellable, error)) return FALSE; } else if (dent->d_type == DT_REG) { - if (!add_ref_to_set (remote, base_dfd, base_path->str, refs, + if (!add_ref_to_set (remote, collection_id, base_dfd, base_path->str, refs, cancellable, error)) return FALSE; } @@ -554,7 +571,7 @@ _ostree_repo_list_refs_internal (OstreeRepo *self, if (!glnx_opendirat (self->repo_dir_fd, cut_prefix ? path : prefix_path, TRUE, &base_fd, error)) return FALSE; - if (!enumerate_refs_recurse (self, remote, base_fd, base_path, + if (!enumerate_refs_recurse (self, remote, NULL, base_fd, base_path, base_fd, cut_prefix ? "." : ref_prefix, ret_all_refs, cancellable, error)) return FALSE; @@ -566,7 +583,7 @@ _ostree_repo_list_refs_internal (OstreeRepo *self, if (!glnx_opendirat (self->repo_dir_fd, prefix_path, TRUE, &prefix_dfd, error)) return FALSE; - if (!add_ref_to_set (remote, prefix_dfd, ref_prefix, ret_all_refs, + if (!add_ref_to_set (remote, NULL, prefix_dfd, ref_prefix, ret_all_refs, cancellable, error)) return FALSE; } @@ -581,7 +598,7 @@ _ostree_repo_list_refs_internal (OstreeRepo *self, if (!glnx_opendirat (self->repo_dir_fd, "refs/heads", TRUE, &refs_heads_dfd, error)) return FALSE; - if (!enumerate_refs_recurse (self, NULL, refs_heads_dfd, base_path, + if (!enumerate_refs_recurse (self, NULL, NULL, refs_heads_dfd, base_path, refs_heads_dfd, ".", ret_all_refs, cancellable, error)) return FALSE; @@ -607,7 +624,7 @@ _ostree_repo_list_refs_internal (OstreeRepo *self, if (!glnx_opendirat (dfd_iter.fd, dent->d_name, TRUE, &remote_dfd, error)) return FALSE; - if (!enumerate_refs_recurse (self, dent->d_name, remote_dfd, base_path, + if (!enumerate_refs_recurse (self, dent->d_name, NULL, remote_dfd, base_path, remote_dfd, ".", ret_all_refs, cancellable, error)) @@ -741,16 +758,26 @@ ostree_repo_remote_list_refs (OstreeRepo *self, } gboolean -_ostree_repo_write_ref (OstreeRepo *self, - const char *remote, - const char *ref, - const char *rev, - GCancellable *cancellable, - GError **error) +_ostree_repo_write_ref (OstreeRepo *self, + const char *remote, + const OstreeCollectionRef *ref, + const char *rev, + GCancellable *cancellable, + GError **error) { glnx_fd_close int dfd = -1; - if (remote == NULL) + g_return_val_if_fail (remote == NULL || ref->collection_id == NULL, FALSE); + + if (remote != NULL && !ostree_validate_remote_name (remote, error)) + return FALSE; + if (ref->collection_id != NULL && !ostree_validate_collection_id (ref->collection_id, error)) + return FALSE; + if (!ostree_validate_rev (ref->ref_name, error)) + return FALSE; + + if (remote == NULL && + (ref->collection_id == NULL || g_strcmp0 (ref->collection_id, ostree_repo_get_collection_id (self)) == 0)) { if (!glnx_opendirat (self->repo_dir_fd, "refs/heads", TRUE, &dfd, error)) @@ -759,6 +786,29 @@ _ostree_repo_write_ref (OstreeRepo *self, return FALSE; } } + else if (remote == NULL && ref->collection_id != NULL) + { + glnx_fd_close int refs_mirrors_dfd = -1; + + /* 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; + } + + if (rev != NULL) + { + /* Ensure we have a dir for the collection */ + if (!glnx_shutil_mkdir_p_at (refs_mirrors_dfd, ref->collection_id, 0777, cancellable, error)) + return FALSE; + } + + dfd = glnx_opendirat_with_errno (refs_mirrors_dfd, ref->collection_id, TRUE); + if (dfd < 0 && (errno != ENOENT || rev != NULL)) + return glnx_throw_errno_prefix (error, "Opening mirrors/ dir %s", ref->collection_id); + } else { glnx_fd_close int refs_remotes_dfd = -1; @@ -786,7 +836,7 @@ _ostree_repo_write_ref (OstreeRepo *self, { if (dfd >= 0) { - if (unlinkat (dfd, ref, 0) != 0) + if (unlinkat (dfd, ref->ref_name, 0) != 0) { if (errno != ENOENT) return glnx_throw_errno (error); @@ -795,7 +845,7 @@ _ostree_repo_write_ref (OstreeRepo *self, } else { - if (!write_checksum_file_at (self, dfd, ref, rev, cancellable, error)) + if (!write_checksum_file_at (self, dfd, ref->ref_name, rev, cancellable, error)) return FALSE; } @@ -807,7 +857,7 @@ _ostree_repo_write_ref (OstreeRepo *self, gboolean _ostree_repo_update_refs (OstreeRepo *self, - GHashTable *refs, + GHashTable *refs, /* (element-type utf8 utf8) */ GCancellable *cancellable, GError **error) { @@ -820,15 +870,136 @@ _ostree_repo_update_refs (OstreeRepo *self, const char *refspec = key; const char *rev = value; g_autofree char *remote = NULL; - g_autofree char *ref = NULL; + g_autofree char *ref_name = NULL; - if (!ostree_parse_refspec (refspec, &remote, &ref, error)) + if (!ostree_parse_refspec (refspec, &remote, &ref_name, error)) return FALSE; - if (!_ostree_repo_write_ref (self, remote, ref, rev, + const OstreeCollectionRef ref = { NULL, ref_name }; + if (!_ostree_repo_write_ref (self, remote, &ref, rev, cancellable, error)) return FALSE; } return TRUE; } + +gboolean +_ostree_repo_update_collection_refs (OstreeRepo *self, + GHashTable *refs, /* (element-type OstreeCollectionRef utf8) */ + GCancellable *cancellable, + GError **error) +{ + GHashTableIter hash_iter; + gpointer key, value; + + g_hash_table_iter_init (&hash_iter, refs); + while (g_hash_table_iter_next (&hash_iter, &key, &value)) + { + const OstreeCollectionRef *ref = key; + const char *rev = value; + + if (!_ostree_repo_write_ref (self, NULL, ref, rev, + cancellable, error)) + return FALSE; + } + + return TRUE; +} + +/** + * ostree_repo_list_collection_refs: + * @self: Repo + * @match_collection_id: (nullable): If non-%NULL, only list refs from this collection + * @out_all_refs: (out) (element-type OstreeCollectionRef utf8): Mapping from collection–ref to checksum + * @cancellable: Cancellable + * @error: Error + * + * List all local and mirrored refs, mapping them to the commit checksums they + * currently point to in @out_all_refs. If @match_collection_id is specified, + * the results will be limited to those with an equal collection ID. + * + * #OstreeCollectionRefs are guaranteed to be returned with their collection ID + * set to a non-%NULL value; so no refs from `refs/heads` will be listed if no + * collection ID is configured for the repository + * (ostree_repo_get_collection_id()). + * + * Returns: %TRUE on success, %FALSE otherwise + * Since: 2017.8 + */ +gboolean +ostree_repo_list_collection_refs (OstreeRepo *self, + const char *match_collection_id, + GHashTable **out_all_refs, + GCancellable *cancellable, + GError **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); + + if (match_collection_id != NULL && !ostree_validate_collection_id (match_collection_id, error)) + return FALSE; + + g_autoptr(GHashTable) ret_all_refs = NULL; + + ret_all_refs = g_hash_table_new_full (ostree_collection_ref_hash, + ostree_collection_ref_equal, + (GDestroyNotify) ostree_collection_ref_free, + g_free); + + g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; + g_autoptr(GString) base_path = g_string_new (""); + + const gchar *main_collection_id = ostree_repo_get_collection_id (self); + + if (main_collection_id != NULL && + (match_collection_id == NULL || g_strcmp0 (match_collection_id, main_collection_id) == 0)) + { + glnx_fd_close int refs_heads_dfd = -1; + + if (!glnx_opendirat (self->repo_dir_fd, "refs/heads", TRUE, &refs_heads_dfd, error)) + return FALSE; + + if (!enumerate_refs_recurse (self, NULL, main_collection_id, refs_heads_dfd, base_path, + refs_heads_dfd, ".", + ret_all_refs, cancellable, error)) + return FALSE; + } + + g_string_truncate (base_path, 0); + + gboolean refs_mirrors_exists = FALSE; + if (!ot_dfd_iter_init_allow_noent (self->repo_dir_fd, "refs/mirrors", + &dfd_iter, &refs_mirrors_exists, error)) + return FALSE; + + while (refs_mirrors_exists) + { + struct dirent *dent; + glnx_fd_close int collection_dfd = -1; + + if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error)) + return FALSE; + if (!dent) + break; + + if (dent->d_type != DT_DIR) + continue; + + if (match_collection_id != NULL && g_strcmp0 (match_collection_id, dent->d_name) != 0) + continue; + + if (!glnx_opendirat (dfd_iter.fd, dent->d_name, TRUE, &collection_dfd, error)) + return FALSE; + + if (!enumerate_refs_recurse (self, NULL, dent->d_name, collection_dfd, base_path, + collection_dfd, ".", + ret_all_refs, + cancellable, error)) + return FALSE; + } + + ot_transfer_out_value (out_all_refs, &ret_all_refs); + return TRUE; +} diff --git a/src/libostree/ostree-repo-static-delta-compilation.c b/src/libostree/ostree-repo-static-delta-compilation.c index 47fae9f5..851f3fcb 100644 --- a/src/libostree/ostree-repo-static-delta-compilation.c +++ b/src/libostree/ostree-repo-static-delta-compilation.c @@ -138,11 +138,11 @@ xattr_chunk_hash (const void *vp) g_variant_get_child (v, i, "(^&ay@ay)", &name, &value); value_data = g_variant_get_fixed_array (value, &value_len, 1); - + h += g_str_hash (name); h += bufhash (value_data, value_len); } - + return h; } @@ -235,7 +235,7 @@ objtype_checksum_array_new (GPtrArray *objects) const char *checksum; guint8 csum[OSTREE_SHA256_DIGEST_LEN]; guint8 objtype_v; - + ostree_object_name_deserialize (serialized_key, &checksum, &objtype); objtype_v = (guint8) objtype; @@ -253,29 +253,25 @@ splice_stream_to_payload (OstreeStaticDeltaPartBuilder *current_part, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - const guint readlen = 4096; - gsize bytes_read; - while (TRUE) { + const guint readlen = 4096; allocate_part_buffer_space (current_part, readlen); + gsize bytes_read; if (!g_input_stream_read_all (istream, current_part->payload->str + current_part->payload->len, readlen, &bytes_read, cancellable, error)) - goto out; + return FALSE; if (bytes_read == 0) break; - + current_part->payload->len += bytes_read; } - ret = TRUE; - out: - return ret; + return TRUE; } static void @@ -293,7 +289,7 @@ write_content_mode_xattrs (OstreeRepo *repo, guint32 mode = g_file_info_get_attribute_uint32 (content_finfo, "unix::mode"); g_autoptr(GVariant) modev - = g_variant_ref_sink (g_variant_new ("(uuu)", + = g_variant_ref_sink (g_variant_new ("(uuu)", GUINT32_TO_BE (uid), GUINT32_TO_BE (gid), GUINT32_TO_BE (mode))); @@ -317,41 +313,40 @@ process_one_object (OstreeRepo *repo, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - guint64 content_size; - g_autoptr(GInputStream) content_stream = NULL; + OstreeStaticDeltaPartBuilder *current_part = *current_part_val; g_autoptr(GFileInfo) content_finfo = NULL; g_autoptr(GVariant) content_xattrs = NULL; - guint64 compressed_size; - OstreeStaticDeltaPartBuilder *current_part = *current_part_val; + guint64 content_size; + g_autoptr(GInputStream) content_stream = NULL; if (OSTREE_OBJECT_TYPE_IS_META (objtype)) { if (!ostree_repo_load_object_stream (repo, objtype, checksum, &content_stream, &content_size, cancellable, error)) - goto out; + return FALSE; } else { if (!ostree_repo_load_file (repo, checksum, &content_stream, &content_finfo, &content_xattrs, cancellable, error)) - goto out; + return FALSE; content_size = g_file_info_get_size (content_finfo); } - + /* Check to see if this delta is maximum size */ if (current_part->objects->len > 0 && current_part->payload->len + content_size > builder->max_chunk_size_bytes) { *current_part_val = current_part = allocate_part (builder); - } + } + guint64 compressed_size; if (!ostree_repo_query_object_storage_size (repo, objtype, checksum, &compressed_size, cancellable, error)) - goto out; + return FALSE; builder->loose_compressed_size += compressed_size; current_part->uncompressed_size += content_size; @@ -366,7 +361,7 @@ process_one_object (OstreeRepo *repo, if (!splice_stream_to_payload (current_part, content_stream, cancellable, error)) - goto out; + return FALSE; g_string_append_c (current_part->operations, (gchar)OSTREE_STATIC_DELTA_OP_OPEN_SPLICE_AND_CLOSE); _ostree_write_varuint64 (current_part->operations, content_size); @@ -375,21 +370,16 @@ process_one_object (OstreeRepo *repo, else { gsize mode_offset, xattr_offset, content_offset; - guint32 mode; - - mode = g_file_info_get_attribute_uint32 (content_finfo, "unix::mode"); + guint32 mode = g_file_info_get_attribute_uint32 (content_finfo, "unix::mode"); write_content_mode_xattrs (repo, current_part, content_finfo, content_xattrs, &mode_offset, &xattr_offset); if (S_ISLNK (mode)) { - const char *target; - g_assert (content_stream == NULL); - - target = g_file_info_get_symlink_target (content_finfo); - content_stream = + const char *target = g_file_info_get_symlink_target (content_finfo); + content_stream = g_memory_input_stream_new_from_data (target, strlen (target), NULL); content_size = strlen (target); } @@ -401,7 +391,7 @@ process_one_object (OstreeRepo *repo, content_offset = current_part->payload->len; if (!splice_stream_to_payload (current_part, content_stream, cancellable, error)) - goto out; + return FALSE; g_string_append_c (current_part->operations, (gchar)OSTREE_STATIC_DELTA_OP_OPEN_SPLICE_AND_CLOSE); _ostree_write_varuint64 (current_part->operations, mode_offset); @@ -410,9 +400,7 @@ process_one_object (OstreeRepo *repo, _ostree_write_varuint64 (current_part->operations, content_offset); } - ret = TRUE; - out: - return ret; + return TRUE; } typedef struct { @@ -449,36 +437,15 @@ get_unpacked_unlinked_content (OstreeRepo *repo, GCancellable *cancellable, GError **error) { - g_autofree char *tmpname = NULL; - glnx_fd_close int fd = -1; - g_autoptr(GBytes) ret_content = NULL; g_autoptr(GInputStream) istream = NULL; - g_autoptr(GOutputStream) out = NULL; - - if (!glnx_open_tmpfile_linkable_at (AT_FDCWD, "/tmp", O_RDWR | O_CLOEXEC, - &fd, &tmpname, error)) - return FALSE; - /* We don't need the file name */ - if (tmpname) - (void) unlinkat (AT_FDCWD, tmpname, 0); if (!ostree_repo_load_file (repo, checksum, &istream, NULL, NULL, cancellable, error)) return FALSE; - out = g_unix_output_stream_new (fd, FALSE); - if (g_output_stream_splice (out, istream, G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, - cancellable, error) < 0) + *out_content = ot_map_anonymous_tmpfile_from_content (istream, cancellable, error); + if (!*out_content) return FALSE; - - { g_autoptr(GMappedFile) mfile = g_mapped_file_new_from_fd (fd, FALSE, error); - if (!mfile) - return FALSE; - ret_content = g_mapped_file_get_bytes (mfile); - } - - if (out_content) - *out_content = g_steal_pointer (&ret_content); return TRUE; } @@ -491,34 +458,28 @@ try_content_bsdiff (OstreeRepo *repo, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; + + g_autoptr(GFileInfo) from_finfo = NULL; - g_autoptr(GFileInfo) to_finfo = NULL; - ContentBsdiff *ret_bsdiff = NULL; - - *out_bsdiff = NULL; - if (!ostree_repo_load_file (repo, from, NULL, &from_finfo, NULL, cancellable, error)) return FALSE; + g_autoptr(GFileInfo) to_finfo = NULL; if (!ostree_repo_load_file (repo, to, NULL, &to_finfo, NULL, cancellable, error)) return FALSE; - if (g_file_info_get_size (to_finfo) + g_file_info_get_size (from_finfo) > max_bsdiff_size_bytes) - { - ret = TRUE; - goto out; - } + *out_bsdiff = NULL; - ret_bsdiff = g_new0 (ContentBsdiff, 1); + /* Ignore this if it's too large */ + if (g_file_info_get_size (to_finfo) + g_file_info_get_size (from_finfo) > max_bsdiff_size_bytes) + return TRUE; + + ContentBsdiff *ret_bsdiff = g_new0 (ContentBsdiff, 1); ret_bsdiff->from_checksum = g_strdup (from); - ret = TRUE; - if (out_bsdiff) - *out_bsdiff = g_steal_pointer (&ret_bsdiff); - out: - return ret; + ot_transfer_out_value (out_bsdiff, &ret_bsdiff); + return TRUE; } static gboolean @@ -530,35 +491,27 @@ try_content_rollsum (OstreeRepo *repo, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - g_autoptr(GBytes) tmp_from = NULL; - g_autoptr(GBytes) tmp_to = NULL; - OstreeRollsumMatches *matches = NULL; - ContentRollsum *ret_rollsum = NULL; - *out_rollsum = NULL; /* Load the content objects, splice them to uncompressed temporary files that * we can just mmap() and seek around in conveniently. */ + g_autoptr(GBytes) tmp_from = NULL; if (!get_unpacked_unlinked_content (repo, from, &tmp_from, cancellable, error)) - goto out; + return FALSE; + g_autoptr(GBytes) tmp_to = NULL; if (!get_unpacked_unlinked_content (repo, to, &tmp_to, cancellable, error)) - goto out; + return FALSE; - matches = _ostree_compute_rollsum_matches (tmp_from, tmp_to); + OstreeRollsumMatches *matches = _ostree_compute_rollsum_matches (tmp_from, tmp_to); - { guint match_ratio = (matches->bufmatches*100)/matches->total; + const guint match_ratio = (matches->bufmatches*100)/matches->total; - /* Only proceed if the file contains (arbitrary) more than 50% of - * the previous chunks. - */ - if (match_ratio < 50) - { - ret = TRUE; - goto out; - } - } + /* Only proceed if the file contains (arbitrary) more than 50% of + * the previous chunks. + */ + if (match_ratio < 50) + return TRUE; if (opts & DELTAOPT_FLAG_VERBOSE) { @@ -568,17 +521,11 @@ try_content_rollsum (OstreeRepo *repo, matches->total, (unsigned long long)matches->match_size); } - ret_rollsum = g_new0 (ContentRollsum, 1); + ContentRollsum *ret_rollsum = g_new0 (ContentRollsum, 1); ret_rollsum->from_checksum = g_strdup (from); ret_rollsum->matches = g_steal_pointer (&matches); - - ret = TRUE; - if (out_rollsum) - *out_rollsum = g_steal_pointer (&ret_rollsum); - out: - if (matches) - _ostree_rollsum_matches_free (matches); - return ret; + ot_transfer_out_value (out_rollsum, &ret_rollsum); + return TRUE; } struct bzdiff_opaque_s @@ -625,14 +572,7 @@ process_one_rollsum (OstreeRepo *repo, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - guint64 content_size; - g_autoptr(GBytes) tmp_to = NULL; - g_autoptr(GFileInfo) content_finfo = NULL; - g_autoptr(GVariant) content_xattrs = NULL; OstreeStaticDeltaPartBuilder *current_part = *current_part_val; - const guint8 *tmp_to_buf; - gsize tmp_to_len; /* Check to see if this delta has gone over maximum size */ if (current_part->objects->len > 0 && @@ -641,17 +581,21 @@ process_one_rollsum (OstreeRepo *repo, *current_part_val = current_part = allocate_part (builder); } + g_autoptr(GBytes) tmp_to = NULL; if (!get_unpacked_unlinked_content (repo, to_checksum, &tmp_to, cancellable, error)) - goto out; + return FALSE; - tmp_to_buf = g_bytes_get_data (tmp_to, &tmp_to_len); + gsize tmp_to_len; + const guint8 *tmp_to_buf = g_bytes_get_data (tmp_to, &tmp_to_len); + g_autoptr(GFileInfo) content_finfo = NULL; + g_autoptr(GVariant) content_xattrs = NULL; if (!ostree_repo_load_file (repo, to_checksum, NULL, &content_finfo, &content_xattrs, cancellable, error)) - goto out; - content_size = g_file_info_get_size (content_finfo); + return FALSE; + guint64 content_size = g_file_info_get_size (content_finfo); g_assert_cmpint (tmp_to_len, ==, content_size); current_part->uncompressed_size += content_size; @@ -685,11 +629,10 @@ process_one_rollsum (OstreeRepo *repo, { GVariant *match = matchlist->pdata[i]; guint32 crc; - guint64 prefix; g_variant_get (match, "(uttt)", &crc, &offset, &to_start, &from_start); - prefix = to_start - writing_offset; + const guint64 prefix = to_start - writing_offset; if (prefix > 0) { @@ -720,13 +663,11 @@ process_one_rollsum (OstreeRepo *repo, if (!reading_payload) g_string_append_c (current_part->operations, (gchar)OSTREE_STATIC_DELTA_OP_UNSET_READ_SOURCE); - { guint64 remainder = tmp_to_len - writing_offset; - if (remainder > 0) - append_payload_chunk_and_write (current_part, tmp_to_buf + writing_offset, remainder); - writing_offset += remainder; - g_assert_cmpint (writing_offset, ==, tmp_to_len); - } - + const guint64 remainder = tmp_to_len - writing_offset; + if (remainder > 0) + append_payload_chunk_and_write (current_part, tmp_to_buf + writing_offset, remainder); + writing_offset += remainder; + g_assert_cmpint (writing_offset, ==, tmp_to_len); g_assert_cmpint (writing_offset, ==, content_size); } @@ -734,9 +675,7 @@ process_one_rollsum (OstreeRepo *repo, g_string_append_c (current_part->operations, (gchar)OSTREE_STATIC_DELTA_OP_CLOSE); } - ret = TRUE; - out: - return ret; + return TRUE; } static gboolean @@ -748,17 +687,7 @@ process_one_bsdiff (OstreeRepo *repo, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - guint64 content_size; - g_autoptr(GFileInfo) content_finfo = NULL; - g_autoptr(GVariant) content_xattrs = NULL; OstreeStaticDeltaPartBuilder *current_part = *current_part_val; - g_autoptr(GBytes) tmp_from = NULL; - g_autoptr(GBytes) tmp_to = NULL; - const guint8 *tmp_to_buf; - gsize tmp_to_len; - const guint8 *tmp_from_buf; - gsize tmp_from_len; /* Check to see if this delta has gone over maximum size */ if (current_part->objects->len > 0 && @@ -767,21 +696,27 @@ process_one_bsdiff (OstreeRepo *repo, *current_part_val = current_part = allocate_part (builder); } + g_autoptr(GBytes) tmp_from = NULL; if (!get_unpacked_unlinked_content (repo, bsdiff_content->from_checksum, &tmp_from, cancellable, error)) - goto out; + return FALSE; + g_autoptr(GBytes) tmp_to = NULL; if (!get_unpacked_unlinked_content (repo, to_checksum, &tmp_to, cancellable, error)) - goto out; + return FALSE; - tmp_to_buf = g_bytes_get_data (tmp_to, &tmp_to_len); - tmp_from_buf = g_bytes_get_data (tmp_from, &tmp_from_len); + gsize tmp_to_len; + const guint8 *tmp_to_buf = g_bytes_get_data (tmp_to, &tmp_to_len); + gsize tmp_from_len; + const guint8 *tmp_from_buf = g_bytes_get_data (tmp_from, &tmp_from_len); + g_autoptr(GFileInfo) content_finfo = NULL; + g_autoptr(GVariant) content_xattrs = NULL; if (!ostree_repo_load_file (repo, to_checksum, NULL, &content_finfo, &content_xattrs, cancellable, error)) - goto out; - content_size = g_file_info_get_size (content_finfo); + return FALSE; + const guint64 content_size = g_file_info_get_size (content_finfo); g_assert_cmpint (tmp_to_len, ==, content_size); current_part->uncompressed_size += content_size; @@ -819,10 +754,8 @@ process_one_bsdiff (OstreeRepo *repo, op.cancellable = cancellable; op.error = error; stream.opaque = &op; - if (bsdiff (tmp_from_buf, tmp_from_len, tmp_to_buf, tmp_to_len, &stream) < 0) { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "bsdiff generation failed"); - goto out; - } + if (bsdiff (tmp_from_buf, tmp_from_len, tmp_to_buf, tmp_to_len, &stream) < 0) + return glnx_throw (error, "bsdiff generation failed"); payload = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (out)); payload_size = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (out)); @@ -838,9 +771,7 @@ process_one_bsdiff (OstreeRepo *repo, g_string_append_c (current_part->operations, (gchar)OSTREE_STATIC_DELTA_OP_UNSET_READ_SOURCE); - ret = TRUE; - out: - return ret; + return TRUE; } static gboolean @@ -862,7 +793,7 @@ check_object_world_readable (OstreeRepo *repo, return TRUE; } -static gboolean +static gboolean generate_delta_lowlatency (OstreeRepo *repo, const char *from, const char *to, @@ -871,7 +802,6 @@ generate_delta_lowlatency (OstreeRepo *repo, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; GHashTableIter hashiter; gpointer key, value; OstreeStaticDeltaPartBuilder *current_part = NULL; @@ -892,27 +822,27 @@ generate_delta_lowlatency (OstreeRepo *repo, { if (!ostree_repo_read_commit (repo, from, &root_from, NULL, cancellable, error)) - goto out; + return FALSE; if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, from, &from_commit, error)) - goto out; + return FALSE; if (!ostree_repo_traverse_commit (repo, from, 0, &from_reachable_objects, cancellable, error)) - goto out; + return FALSE; } if (!ostree_repo_read_commit (repo, to, &root_to, NULL, cancellable, error)) - goto out; + return FALSE; if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, to, &to_commit, error)) - goto out; + return FALSE; if (!ostree_repo_traverse_commit (repo, to, 0, &to_reachable_objects, cancellable, error)) - goto out; + return FALSE; new_reachable_metadata = ostree_repo_traverse_new_reachable (); new_reachable_regfile_content = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); @@ -939,7 +869,7 @@ generate_delta_lowlatency (OstreeRepo *repo, if (!ostree_repo_load_file (repo, checksum, NULL, &finfo, NULL, cancellable, error)) - goto out; + return FALSE; ftype = g_file_info_get_file_type (finfo); if (ftype == G_FILE_TYPE_REGULAR) @@ -958,7 +888,7 @@ generate_delta_lowlatency (OstreeRepo *repo, CONTENT_SIZE_SIMILARITY_THRESHOLD_PERCENT, &modified_regfile_content, cancellable, error)) - goto out; + return FALSE; } else modified_regfile_content = g_hash_table_new (g_str_hash, g_str_equal); @@ -1001,13 +931,13 @@ generate_delta_lowlatency (OstreeRepo *repo, * bare repository defined as its parent. */ if (!check_object_world_readable (repo, from_checksum, &from_world_readable, cancellable, error)) - goto out; + return FALSE; if (!from_world_readable) continue; if (!try_content_rollsum (repo, opts, from_checksum, to_checksum, &rollsum, cancellable, error)) - goto out; + return FALSE; if (rollsum) { @@ -1021,7 +951,7 @@ generate_delta_lowlatency (OstreeRepo *repo, if (!try_content_bsdiff (repo, from_checksum, to_checksum, &bsdiff, builder->max_bsdiff_size_bytes, cancellable, error)) - goto out; + return FALSE; if (bsdiff) g_hash_table_insert (bsdiff_optimized_content_objects, g_strdup (to_checksum), bsdiff); @@ -1050,7 +980,7 @@ generate_delta_lowlatency (OstreeRepo *repo, if (!process_one_object (repo, builder, ¤t_part, checksum, objtype, cancellable, error)) - goto out; + return FALSE; } /* Now do rollsummed objects */ @@ -1064,7 +994,7 @@ generate_delta_lowlatency (OstreeRepo *repo, if (!process_one_rollsum (repo, builder, ¤t_part, checksum, rollsum, cancellable, error)) - goto out; + return FALSE; builder->n_rollsum++; } @@ -1080,7 +1010,7 @@ generate_delta_lowlatency (OstreeRepo *repo, if (!process_one_bsdiff (repo, builder, ¤t_part, checksum, bsdiff, cancellable, error)) - goto out; + return FALSE; builder->n_bsdiff++; } @@ -1103,11 +1033,11 @@ generate_delta_lowlatency (OstreeRepo *repo, if (!ostree_repo_load_object_stream (repo, OSTREE_OBJECT_TYPE_FILE, checksum, NULL, &uncompressed_size, cancellable, error)) - goto out; + return FALSE; if (builder->min_fallback_size_bytes > 0 && uncompressed_size > builder->min_fallback_size_bytes) fallback = TRUE; - + if (fallback) { g_autofree char *size = g_format_size (uncompressed_size); @@ -1115,7 +1045,7 @@ generate_delta_lowlatency (OstreeRepo *repo, if (opts & DELTAOPT_FLAG_VERBOSE) g_printerr ("fallback for %s (%s)\n", checksum, size); - g_ptr_array_add (builder->fallback_objects, + g_ptr_array_add (builder->fallback_objects, ostree_object_name_serialize (checksum, OSTREE_OBJECT_TYPE_FILE)); g_hash_table_iter_remove (&hashiter); builder->n_fallback++; @@ -1136,7 +1066,7 @@ generate_delta_lowlatency (OstreeRepo *repo, if (!process_one_object (repo, builder, ¤t_part, checksum, OSTREE_OBJECT_TYPE_FILE, cancellable, error)) - goto out; + return FALSE; } /* Now symlinks */ @@ -1148,12 +1078,10 @@ generate_delta_lowlatency (OstreeRepo *repo, if (!process_one_object (repo, builder, ¤t_part, checksum, OSTREE_OBJECT_TYPE_FILE, cancellable, error)) - goto out; + return FALSE; } - ret = TRUE; - out: - return ret; + return TRUE; } static gboolean @@ -1163,14 +1091,10 @@ get_fallback_headers (OstreeRepo *self, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - guint i; - g_autoptr(GVariant) ret_headers = NULL; - g_autoptr(GVariantBuilder) fallback_builder = NULL; + g_autoptr(GVariantBuilder) fallback_builder = + g_variant_builder_new (G_VARIANT_TYPE ("a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT)); - fallback_builder = g_variant_builder_new (G_VARIANT_TYPE ("a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT)); - - for (i = 0; i < builder->fallback_objects->len; i++) + for (guint i = 0; i < builder->fallback_objects->len; i++) { GVariant *serialized = builder->fallback_objects->pdata[i]; const char *checksum; @@ -1185,23 +1109,22 @@ get_fallback_headers (OstreeRepo *self, if (!ostree_repo_load_object_stream (self, objtype, checksum, NULL, &uncompressed_size, cancellable, error)) - goto out; + return FALSE; compressed_size = uncompressed_size; } else { - g_autoptr(GFileInfo) file_info = NULL; - if (!ostree_repo_query_object_storage_size (self, OSTREE_OBJECT_TYPE_FILE, checksum, &compressed_size, cancellable, error)) - goto out; + return FALSE; + g_autoptr(GFileInfo) file_info = NULL; if (!ostree_repo_load_file (self, checksum, NULL, &file_info, NULL, cancellable, error)) - goto out; + return FALSE; uncompressed_size = g_file_info_get_size (file_info); } @@ -1214,13 +1137,9 @@ get_fallback_headers (OstreeRepo *self, maybe_swap_endian_u64 (builder->swap_endian, uncompressed_size))); } - ret_headers = g_variant_ref_sink (g_variant_builder_end (fallback_builder)); - - ret = TRUE; - if (out_headers) - *out_headers = g_steal_pointer (&ret_headers); - out: - return ret; + g_autoptr(GVariant) ret_headers = g_variant_ref_sink (g_variant_builder_end (fallback_builder)); + ot_transfer_out_value (out_headers, &ret_headers); + return TRUE; } /** @@ -1273,7 +1192,6 @@ ostree_repo_static_delta_generate (OstreeRepo *self, guint64 total_compressed_size = 0; guint64 total_uncompressed_size = 0; g_autoptr(GVariantBuilder) part_headers = NULL; - g_autoptr(GArray) part_temp_fds = NULL; g_autoptr(GPtrArray) part_temp_paths = NULL; g_autoptr(GVariant) delta_descriptor = NULL; g_autoptr(GVariant) to_commit = NULL; @@ -1352,7 +1270,7 @@ ostree_repo_static_delta_generate (OstreeRepo *self, } { guint8 endianness_char; - + switch (endianness) { case G_LITTLE_ENDIAN: @@ -1385,8 +1303,7 @@ ostree_repo_static_delta_generate (OstreeRepo *self, } part_headers = g_variant_builder_new (G_VARIANT_TYPE ("a" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT)); - part_temp_paths = g_ptr_array_new_with_free_func (g_free); - part_temp_fds = g_array_new (FALSE, TRUE, sizeof(int)); + part_temp_paths = g_ptr_array_new_with_free_func ((GDestroyNotify)glnx_tmpfile_clear); for (i = 0; i < builder.parts->len; i++) { OstreeStaticDeltaPartBuilder *part_builder = builder.parts->pdata[i]; @@ -1413,14 +1330,14 @@ ostree_repo_static_delta_generate (OstreeRepo *self, { guint j; for (j = 0; j < part_builder->modes->len; j++) g_variant_builder_add_value (&mode_builder, part_builder->modes->pdata[j]); - + for (j = 0; j < part_builder->xattrs->len; j++) g_variant_builder_add_value (&xattr_builder, part_builder->xattrs->pdata[j]); } - + 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; /* FIXME - avoid duplicating memory here */ @@ -1459,17 +1376,15 @@ ostree_repo_static_delta_generate (OstreeRepo *self, } else { - char *part_tempfile; - int part_temp_fd; + GLnxTmpfile *part_tmpf = g_new0 (GLnxTmpfile, 1); if (!glnx_open_tmpfile_linkable_at (tmp_dfd, ".", O_WRONLY | O_CLOEXEC, - &part_temp_fd, &part_tempfile, error)) + part_tmpf, error)) goto out; - /* Transfer tempfile ownership to arrays */ - g_array_append_val (part_temp_fds, part_temp_fd); - g_ptr_array_add (part_temp_paths, g_steal_pointer (&part_tempfile)); - part_temp_outstream = g_unix_output_stream_new (part_temp_fd, FALSE); + /* Transfer tempfile ownership */ + part_temp_outstream = g_unix_output_stream_new (part_tmpf->fd, FALSE); + g_ptr_array_add (part_temp_paths, g_steal_pointer (&part_tmpf)); } part_in = ot_variant_read (delta_part); @@ -1528,17 +1443,16 @@ ostree_repo_static_delta_generate (OstreeRepo *self, { g_autofree char *partstr = g_strdup_printf ("%u", i); /* Take ownership of the path/fd here */ - g_autofree char *path = g_steal_pointer (&part_temp_paths->pdata[i]); - glnx_fd_close int fd = g_array_index (part_temp_fds, int, i); - g_array_index (part_temp_fds, int, i) = -1; + g_auto(GLnxTmpfile) tmpf = *((GLnxTmpfile*)part_temp_paths->pdata[i]); + g_clear_pointer (&(part_temp_paths->pdata[i]), g_free); - if (fchmod (fd, 0644) < 0) + if (fchmod (tmpf.fd, 0644) < 0) { glnx_set_error_from_errno (error); goto out; } - if (!glnx_link_tmpfile_at (tmp_dfd, GLNX_LINK_TMPFILE_REPLACE, fd, path, + if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_REPLACE, descriptor_dfd, partstr, error)) goto out; } @@ -1598,14 +1512,6 @@ ostree_repo_static_delta_generate (OstreeRepo *self, ret = TRUE; out: - if (part_temp_fds) - for (i = 0; i < part_temp_fds->len; i++) - { - int fd = g_array_index (part_temp_fds, int, i); - if (fd == -1) - continue; - (void) close (fd); - } g_clear_pointer (&builder.parts, g_ptr_array_unref); g_clear_pointer (&builder.fallback_objects, g_ptr_array_unref); return ret; diff --git a/src/libostree/ostree-repo-static-delta-core.c b/src/libostree/ostree-repo-static-delta-core.c index d83b7f0f..c8cefdef 100644 --- a/src/libostree/ostree-repo-static-delta-core.c +++ b/src/libostree/ostree-repo-static-delta-core.c @@ -65,7 +65,7 @@ _ostree_static_delta_parse_checksum_array (GVariant *array, * * This function synchronously enumerates all static deltas in the * repository, returning its result in @out_deltas. - */ + */ gboolean ostree_repo_list_static_delta_names (OstreeRepo *self, GPtrArray **out_deltas, @@ -165,31 +165,30 @@ _ostree_repo_static_delta_part_have_all_objects (OstreeRepo *repo, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; guint8 *checksums_data; - guint i,n_checksums; + guint n_checksums; gboolean have_object = TRUE; if (!_ostree_static_delta_parse_checksum_array (checksum_array, &checksums_data, &n_checksums, error)) - goto out; + return FALSE; - for (i = 0; i < n_checksums; i++) + for (guint i = 0; i < n_checksums; i++) { guint8 objtype = *checksums_data; const guint8 *csum = checksums_data + 1; char tmp_checksum[OSTREE_SHA256_STRING_LEN+1]; if (G_UNLIKELY(!ostree_validate_structureof_objtype (objtype, error))) - goto out; + return FALSE; ostree_checksum_inplace_from_bytes (csum, tmp_checksum); if (!ostree_repo_has_object (repo, (OstreeObjectType) objtype, tmp_checksum, &have_object, cancellable, error)) - goto out; + return FALSE; if (!have_object) break; @@ -197,10 +196,8 @@ _ostree_repo_static_delta_part_have_all_objects (OstreeRepo *repo, checksums_data += OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN; } - ret = TRUE; *out_have_all = have_object; - out: - return ret; + return TRUE; } /** @@ -223,57 +220,43 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - guint i, n; - const char *dir_or_file_path = NULL; - glnx_fd_close int meta_fd = -1; - glnx_fd_close int dfd = -1; - g_autoptr(GVariant) meta = NULL; - g_autoptr(GVariant) headers = NULL; - g_autoptr(GVariant) metadata = NULL; - g_autoptr(GVariant) fallback = NULL; - g_autofree char *to_checksum = NULL; - g_autofree char *from_checksum = NULL; g_autofree char *basename = NULL; - dir_or_file_path = gs_file_get_path_cached (dir_or_file); + const char *dir_or_file_path = gs_file_get_path_cached (dir_or_file); /* First, try opening it as a directory */ - dfd = glnx_opendirat_with_errno (AT_FDCWD, dir_or_file_path, TRUE); + glnx_fd_close int dfd = glnx_opendirat_with_errno (AT_FDCWD, dir_or_file_path, TRUE); if (dfd < 0) { if (errno != ENOTDIR) - { - glnx_set_error_from_errno (error); - goto out; - } + return glnx_throw_errno_prefix (error, "openat(O_DIRECTORY)"); else { g_autofree char *dir = dirname (g_strdup (dir_or_file_path)); basename = g_path_get_basename (dir_or_file_path); if (!glnx_opendirat (AT_FDCWD, dir, TRUE, &dfd, error)) - goto out; + return FALSE; } } else basename = g_strdup ("superblock"); - meta_fd = openat (dfd, basename, O_RDONLY | O_CLOEXEC); + glnx_fd_close int meta_fd = openat (dfd, basename, O_RDONLY | O_CLOEXEC); if (meta_fd < 0) - { - glnx_set_error_from_errno (error); - goto out; - } - + return glnx_throw_errno_prefix (error, "openat(%s)", basename); + + g_autoptr(GVariant) meta = NULL; if (!ot_util_variant_map_fd (meta_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), FALSE, &meta, error)) - goto out; + return FALSE; /* Parsing OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT */ - metadata = g_variant_get_child_value (meta, 0); + g_autoptr(GVariant) metadata = g_variant_get_child_value (meta, 0); + g_autofree char *to_checksum = NULL; + g_autofree char *from_checksum = NULL; /* Write the to-commit object */ { g_autoptr(GVariant) to_csum_v = NULL; @@ -284,32 +267,28 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self, to_csum_v = g_variant_get_child_value (meta, 3); if (!ostree_validate_structureof_csum_v (to_csum_v, error)) - goto out; + return FALSE; to_checksum = ostree_checksum_from_bytes_v (to_csum_v); from_csum_v = g_variant_get_child_value (meta, 2); if (g_variant_n_children (from_csum_v) > 0) { if (!ostree_validate_structureof_csum_v (from_csum_v, error)) - goto out; + return FALSE; from_checksum = ostree_checksum_from_bytes_v (from_csum_v); if (!ostree_repo_has_object (self, OSTREE_OBJECT_TYPE_COMMIT, from_checksum, &have_from_commit, cancellable, error)) - goto out; + return FALSE; if (!have_from_commit) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Commit %s, which is the delta source, is not in repository", from_checksum); - goto out; - } + return glnx_throw (error, "Commit %s, which is the delta source, is not in repository", from_checksum); } if (!ostree_repo_has_object (self, OSTREE_OBJECT_TYPE_COMMIT, to_checksum, &have_to_commit, cancellable, error)) - goto out; - + return FALSE; + if (!have_to_commit) { g_autofree char *detached_path = _ostree_get_relative_static_delta_path (from_checksum, to_checksum, "commitmeta"); @@ -321,27 +300,23 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self, detached_data, cancellable, error)) - goto out; + return FALSE; to_commit = g_variant_get_child_value (meta, 4); if (!ostree_repo_write_metadata (self, OSTREE_OBJECT_TYPE_COMMIT, to_checksum, to_commit, NULL, cancellable, error)) - goto out; + return FALSE; } } - fallback = g_variant_get_child_value (meta, 7); + g_autoptr(GVariant) fallback = g_variant_get_child_value (meta, 7); if (g_variant_n_children (fallback) > 0) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Cannot execute delta offline: contains nonempty http fallback entries"); - goto out; - } + return glnx_throw (error, "Cannot execute delta offline: contains nonempty http fallback entries"); - headers = g_variant_get_child_value (meta, 6); - n = g_variant_n_children (headers); - for (i = 0; i < n; i++) + g_autoptr(GVariant) headers = g_variant_get_child_value (meta, 6); + const guint n = g_variant_n_children (headers); + for (guint i = 0; i < n; i++) { guint32 version; guint64 size; @@ -349,29 +324,21 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self, const guchar *csum; char checksum[OSTREE_SHA256_STRING_LEN+1]; gboolean have_all; - g_autoptr(GInputStream) part_in = NULL; - g_autoptr(GVariant) inline_part_data = NULL; - g_autoptr(GVariant) header = NULL; g_autoptr(GVariant) csum_v = NULL; g_autoptr(GVariant) objects = NULL; g_autoptr(GVariant) part = NULL; g_autofree char *deltapart_path = NULL; - OstreeStaticDeltaOpenFlags delta_open_flags = + OstreeStaticDeltaOpenFlags delta_open_flags = skip_validation ? OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM : 0; - - header = g_variant_get_child_value (headers, i); + g_autoptr(GVariant) header = g_variant_get_child_value (headers, i); g_variant_get (header, "(u@aytt@ay)", &version, &csum_v, &size, &usize, &objects); if (version > OSTREE_DELTAPART_VERSION) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Delta part has too new version %u", version); - goto out; - } + return glnx_throw (error, "Delta part has too new version %u", version); if (!_ostree_repo_static_delta_part_have_all_objects (self, objects, &have_all, cancellable, error)) - goto out; + return FALSE; /* If we already have these objects, don't bother executing the * static delta. @@ -381,13 +348,14 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self, csum = ostree_checksum_bytes_peek_validate (csum_v, error); if (!csum) - goto out; + return FALSE; ostree_checksum_inplace_from_bytes (csum, checksum); deltapart_path = _ostree_get_relative_static_delta_part_path (from_checksum, to_checksum, i); - inline_part_data = g_variant_lookup_value (metadata, deltapart_path, G_VARIANT_TYPE("(yay)")); + g_autoptr(GInputStream) part_in = NULL; + g_autoptr(GVariant) inline_part_data = g_variant_lookup_value (metadata, deltapart_path, G_VARIANT_TYPE("(yay)")); if (inline_part_data) { g_autoptr(GBytes) inline_part_bytes = g_variant_get_data_as_bytes (inline_part_data); @@ -405,40 +373,31 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self, NULL, &part, cancellable, error)) - goto out; + return FALSE; } else { g_autofree char *relpath = g_strdup_printf ("%u", i); /* TODO avoid malloc here */ glnx_fd_close int part_fd = openat (dfd, relpath, O_RDONLY | O_CLOEXEC); if (part_fd < 0) - { - glnx_set_error_from_errno (error); - g_prefix_error (error, "Opening deltapart '%s': ", deltapart_path); - goto out; - } + return glnx_throw_errno_prefix (error, "Opening deltapart '%s'", relpath); part_in = g_unix_input_stream_new (part_fd, FALSE); - if (!_ostree_static_delta_part_open (part_in, NULL, + if (!_ostree_static_delta_part_open (part_in, NULL, delta_open_flags, checksum, &part, cancellable, error)) - goto out; + return FALSE; } if (!_ostree_static_delta_part_execute (self, objects, part, skip_validation, NULL, cancellable, error)) - { - g_prefix_error (error, "Executing delta part %i: ", i); - goto out; - } + return glnx_prefix_error (error, "Executing delta part %i", i); } - ret = TRUE; - out: - return ret; + return TRUE; } gboolean @@ -450,14 +409,12 @@ _ostree_static_delta_part_open (GInputStream *part_in, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; const gboolean trusted = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_VARIANT_TRUSTED) > 0; const gboolean skip_checksum = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM) > 0; gsize bytes_read; guint8 comptype; g_autoptr(GChecksum) checksum = NULL; g_autoptr(GInputStream) checksum_in = NULL; - g_autoptr(GVariant) ret_part = NULL; GInputStream *source_in; /* We either take a fd or a GBytes reference */ @@ -479,13 +436,11 @@ _ostree_static_delta_part_open (GInputStream *part_in, /* First byte is compression type */ if (!g_input_stream_read_all (source_in, buf, sizeof(buf), &bytes_read, cancellable, error)) - { - g_prefix_error (error, "Reading initial compression flag byte: "); - goto out; - } + return glnx_prefix_error (error, "Reading initial compression flag byte"); comptype = buf[0]; } + g_autoptr(GVariant) ret_part = NULL; switch (comptype) { case 0: @@ -496,7 +451,7 @@ _ostree_static_delta_part_open (GInputStream *part_in, /* No compression, no checksums - a fast path */ if (!ot_util_variant_map_fd (part_fd, 1, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0), trusted, &ret_part, error)) - goto out; + return FALSE; } else { @@ -510,51 +465,22 @@ _ostree_static_delta_part_open (GInputStream *part_in, if (!skip_checksum) g_checksum_update (checksum, g_variant_get_data (ret_part), g_variant_get_size (ret_part)); - + break; case 'x': { - g_autofree char *tmppath = g_strdup ("/var/tmp/ostree-delta-XXXXXX"); g_autoptr(GConverter) decomp = (GConverter*) _ostree_lzma_decompressor_new (); g_autoptr(GInputStream) convin = g_converter_input_stream_new (source_in, decomp); - g_autoptr(GOutputStream) unpacked_out = NULL; - glnx_fd_close int unpacked_fd = -1; - gssize n_bytes_written; + g_autoptr(GBytes) buf = ot_map_anonymous_tmpfile_from_content (convin, cancellable, error); + if (!buf) + return FALSE; - unpacked_fd = g_mkstemp_full (tmppath, O_RDWR | O_CLOEXEC, 0640); - if (unpacked_fd < 0) - { - glnx_set_error_from_errno (error); - goto out; - } - - /* Now make it autocleanup on process exit - in the future, we - * should consider caching unpacked deltas as well. - */ - if (unlink (tmppath) < 0) - { - glnx_set_error_from_errno (error); - goto out; - } - - unpacked_out = g_unix_output_stream_new (unpacked_fd, FALSE); - - n_bytes_written = g_output_stream_splice (unpacked_out, convin, - G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | - G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, - cancellable, error); - if (n_bytes_written < 0) - goto out; - - if (!ot_util_variant_map_fd (unpacked_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0), - trusted, &ret_part, error)) - goto out; + ret_part = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0), + buf, FALSE); } break; default: - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Invalid compression type '%u'", comptype); - goto out; + return glnx_throw (error, "Invalid compression type '%u'", comptype); } if (checksum) @@ -562,18 +488,12 @@ _ostree_static_delta_part_open (GInputStream *part_in, const char *actual_checksum = g_checksum_get_string (checksum); g_assert (expected_checksum != NULL); if (strcmp (actual_checksum, expected_checksum) != 0) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Checksum mismatch in static delta part; expected=%s actual=%s", - expected_checksum, actual_checksum); - goto out; - } + return glnx_throw (error, "Checksum mismatch in static delta part; expected=%s actual=%s", + expected_checksum, actual_checksum); } - - ret = TRUE; + *out_part = g_steal_pointer (&ret_part); - out: - return ret; + return TRUE; } /* @@ -592,15 +512,12 @@ show_one_part (OstreeRepo *self, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; + g_autoptr(GVariant) part = NULL; + g_autofree char *part_path = _ostree_get_relative_static_delta_part_path (from, to, i); + guint32 version; guint64 size, usize; g_autoptr(GVariant) objects = NULL; - g_autoptr(GInputStream) part_in = NULL; - g_autoptr(GVariant) part = NULL; - g_autofree char *part_path = _ostree_get_relative_static_delta_part_path (from, to, i); - gint part_fd = -1; - g_variant_get_child (meta_entries, i, "(u@aytt@ay)", &version, NULL, &size, &usize, &objects); size = maybe_swap_endian_u64 (swap_endian, size); usize = maybe_swap_endian_u64 (swap_endian, usize); @@ -609,21 +526,17 @@ show_one_part (OstreeRepo *self, g_print ("PartMeta%u: nobjects=%u size=%" G_GUINT64_FORMAT " usize=%" G_GUINT64_FORMAT "\n", i, (guint)(g_variant_get_size (objects) / OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN), size, usize); - part_fd = openat (self->repo_dir_fd, part_path, O_RDONLY | O_CLOEXEC); + glnx_fd_close gint part_fd = openat (self->repo_dir_fd, part_path, O_RDONLY | O_CLOEXEC); if (part_fd < 0) - { - glnx_set_error_from_errno (error); - goto out; - } + return glnx_throw_errno_prefix (error, "openat(%s)", part_path); + g_autoptr(GInputStream) part_in = g_unix_input_stream_new (part_fd, FALSE); - part_in = g_unix_input_stream_new (part_fd, FALSE); - - if (!_ostree_static_delta_part_open (part_in, NULL, + if (!_ostree_static_delta_part_open (part_in, NULL, OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM, NULL, &part, cancellable, error)) - goto out; + return FALSE; { g_autoptr(GVariant) modes = NULL; g_autoptr(GVariant) xattrs = NULL; @@ -648,7 +561,7 @@ show_one_part (OstreeRepo *self, if (!_ostree_static_delta_part_execute (self, objects, part, TRUE, &stats, cancellable, error)) - goto out; + return FALSE; { const guint *n_ops = stats.n_ops_executed; g_print ("PartPayloadOps%u: openspliceclose=%u open=%u write=%u setread=%u " @@ -656,10 +569,8 @@ show_one_part (OstreeRepo *self, i, n_ops[0], n_ops[1], n_ops[2], n_ops[3], n_ops[4], n_ops[5], n_ops[6]); } } - - ret = TRUE; - out: - return ret; + + return TRUE; } OstreeDeltaEndianness @@ -781,35 +692,30 @@ _ostree_repo_static_delta_delete (OstreeRepo *self, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; g_autofree char *from = NULL; g_autofree char *to = NULL; - g_autofree char *deltadir = NULL; - struct stat buf; - if (!_ostree_parse_delta_name (delta_id, &from, &to, error)) - goto out; - - deltadir = _ostree_get_relative_static_delta_path (from, to, NULL); + return FALSE; + g_autofree char *deltadir = _ostree_get_relative_static_delta_path (from, to, NULL); + struct stat buf; if (fstatat (self->repo_dir_fd, deltadir, &buf, 0) != 0) { if (errno == ENOENT) - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "Can't find delta %s", delta_id); + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Can't find delta %s", delta_id); + return FALSE; + } else - glnx_set_error_from_errno (error); - - goto out; + return glnx_throw_errno_prefix (error, "fstatat(%s)", deltadir); } if (!glnx_shutil_rm_rf_at (self->repo_dir_fd, deltadir, cancellable, error)) - goto out; + return FALSE; - ret = TRUE; - out: - return ret; + return TRUE; } gboolean @@ -821,13 +727,12 @@ _ostree_repo_static_delta_query_exists (OstreeRepo *self, { g_autofree char *from = NULL; g_autofree char *to = NULL; - g_autofree char *superblock_path = NULL; struct stat stbuf; if (!_ostree_parse_delta_name (delta_id, &from, &to, error)) return FALSE; - superblock_path = _ostree_get_relative_static_delta_superblock_path (from, to); + g_autofree char *superblock_path = _ostree_get_relative_static_delta_superblock_path (from, to); if (fstatat (self->repo_dir_fd, superblock_path, &stbuf, 0) < 0) { @@ -837,10 +742,7 @@ _ostree_repo_static_delta_query_exists (OstreeRepo *self, return TRUE; } else - { - glnx_set_error_from_errno (error); - return FALSE; - } + return glnx_throw_errno_prefix (error, "fstatat(%s)", superblock_path); } *out_exists = TRUE; return TRUE; @@ -852,28 +754,27 @@ _ostree_repo_static_delta_dump (OstreeRepo *self, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - g_autofree char *from = NULL; + g_autofree char *from = NULL; g_autofree char *to = NULL; g_autofree char *superblock_path = NULL; g_autoptr(GVariant) delta_superblock = NULL; guint64 total_size = 0, total_usize = 0; guint64 total_fallback_size = 0, total_fallback_usize = 0; - guint i; OstreeDeltaEndianness endianness; gboolean swap_endian = FALSE; if (!_ostree_parse_delta_name (delta_id, &from, &to, error)) - goto out; + return FALSE; superblock_path = _ostree_get_relative_static_delta_superblock_path (from, to); if (!ot_util_variant_map_at (self->repo_dir_fd, superblock_path, (GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT, OT_VARIANT_MAP_TRUSTED, &delta_superblock, error)) - goto out; + return FALSE; g_print ("Delta: %s\n", delta_id); + { const char *endianness_description; gboolean was_heuristic; @@ -903,65 +804,63 @@ _ostree_repo_static_delta_dump (OstreeRepo *self, default: g_assert_not_reached (); } - + g_print ("Endianness: %s\n", endianness_description); } - { guint64 ts; - g_variant_get_child (delta_superblock, 1, "t", &ts); - g_print ("Timestamp: %" G_GUINT64_FORMAT "\n", GUINT64_FROM_BE (ts)); - } - { g_autoptr(GVariant) recurse = NULL; - g_variant_get_child (delta_superblock, 5, "@ay", &recurse); - g_print ("Number of parents: %u\n", (guint)(g_variant_get_size (recurse) / (OSTREE_SHA256_DIGEST_LEN * 2))); - } - { g_autoptr(GVariant) fallback = NULL; - guint n_fallback; + guint64 ts; + g_variant_get_child (delta_superblock, 1, "t", &ts); + g_print ("Timestamp: %" G_GUINT64_FORMAT "\n", GUINT64_FROM_BE (ts)); - g_variant_get_child (delta_superblock, 7, "@a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT, &fallback); - n_fallback = g_variant_n_children (fallback); + g_autoptr(GVariant) recurse = NULL; + g_variant_get_child (delta_superblock, 5, "@ay", &recurse); + g_print ("Number of parents: %u\n", (guint)(g_variant_get_size (recurse) / (OSTREE_SHA256_DIGEST_LEN * 2))); - g_print ("Number of fallback entries: %u\n", n_fallback); + g_autoptr(GVariant) fallback = NULL; + g_variant_get_child (delta_superblock, 7, "@a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT, &fallback); + const guint n_fallback = g_variant_n_children (fallback); - for (i = 0; i < n_fallback; i++) - { - guint64 size, usize; - g_autoptr(GVariant) checksum_v = NULL; - char checksum[OSTREE_SHA256_STRING_LEN+1]; - g_variant_get_child (fallback, i, "(y@aytt)", NULL, &checksum_v, &size, &usize); - ostree_checksum_inplace_from_bytes (ostree_checksum_bytes_peek (checksum_v), checksum); - size = maybe_swap_endian_u64 (swap_endian, size); - usize = maybe_swap_endian_u64 (swap_endian, usize); - g_print (" %s\n", checksum); - total_fallback_size += size; - total_fallback_usize += usize; - } - { g_autofree char *sizestr = g_format_size (total_fallback_size); - g_autofree char *usizestr = g_format_size (total_fallback_usize); - g_print ("Total Fallback Size: %" G_GUINT64_FORMAT " (%s)\n", total_fallback_size, sizestr); - g_print ("Total Fallback Uncompressed Size: %" G_GUINT64_FORMAT " (%s)\n", total_fallback_usize, usizestr); + g_print ("Number of fallback entries: %u\n", n_fallback); + + for (guint i = 0; i < n_fallback; i++) + { + guint64 size, usize; + g_autoptr(GVariant) checksum_v = NULL; + char checksum[OSTREE_SHA256_STRING_LEN+1]; + g_variant_get_child (fallback, i, "(y@aytt)", NULL, &checksum_v, &size, &usize); + ostree_checksum_inplace_from_bytes (ostree_checksum_bytes_peek (checksum_v), checksum); + size = maybe_swap_endian_u64 (swap_endian, size); + usize = maybe_swap_endian_u64 (swap_endian, usize); + g_print (" %s\n", checksum); + total_fallback_size += size; + total_fallback_usize += usize; } + { g_autofree char *sizestr = g_format_size (total_fallback_size); + g_autofree char *usizestr = g_format_size (total_fallback_usize); + g_print ("Total Fallback Size: %" G_GUINT64_FORMAT " (%s)\n", total_fallback_size, sizestr); + g_print ("Total Fallback Uncompressed Size: %" G_GUINT64_FORMAT " (%s)\n", total_fallback_usize, usizestr); } - { g_autoptr(GVariant) meta_entries = NULL; - guint n_parts; - g_variant_get_child (delta_superblock, 6, "@a" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT, &meta_entries); - n_parts = g_variant_n_children (meta_entries); - g_print ("Number of parts: %u\n", n_parts); + g_autoptr(GVariant) meta_entries = NULL; + guint n_parts; - for (i = 0; i < n_parts; i++) - { - if (!show_one_part (self, swap_endian, from, to, meta_entries, i, - &total_size, &total_usize, - cancellable, error)) - goto out; - } - } + g_variant_get_child (delta_superblock, 6, "@a" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT, &meta_entries); + n_parts = g_variant_n_children (meta_entries); + g_print ("Number of parts: %u\n", n_parts); + + for (guint i = 0; i < n_parts; i++) + { + if (!show_one_part (self, swap_endian, from, to, meta_entries, i, + &total_size, &total_usize, + cancellable, error)) + return FALSE; + } { g_autofree char *sizestr = g_format_size (total_size); g_autofree char *usizestr = g_format_size (total_usize); g_print ("Total Part Size: %" G_GUINT64_FORMAT " (%s)\n", total_size, sizestr); g_print ("Total Part Uncompressed Size: %" G_GUINT64_FORMAT " (%s)\n", total_usize, usizestr); } + { guint64 overall_size = total_size + total_fallback_size; guint64 overall_usize = total_usize + total_fallback_usize; g_autofree char *sizestr = g_format_size (overall_size); @@ -970,7 +869,5 @@ _ostree_repo_static_delta_dump (OstreeRepo *self, g_print ("Total Uncompressed Size: %" G_GUINT64_FORMAT " (%s)\n", overall_usize, usizestr); } - ret = TRUE; - out: - return ret; + return TRUE; } diff --git a/src/libostree/ostree-repo-static-delta-processing.c b/src/libostree/ostree-repo-static-delta-processing.c index ea157e77..028c700d 100644 --- a/src/libostree/ostree-repo-static-delta-processing.c +++ b/src/libostree/ostree-repo-static-delta-processing.c @@ -56,7 +56,7 @@ typedef struct { GError **async_error; OstreeObjectType output_objtype; - OstreeRepoContentBareCommit barecommitstate; + GLnxTmpfile tmpf; guint64 content_size; GOutputStream *content_out; GChecksum *content_checksum; @@ -281,6 +281,8 @@ _ostree_static_delta_part_execute (OstreeRepo *repo, ret = TRUE; out: + glnx_tmpfile_clear (&state->tmpf); + g_clear_object (&state->content_out); g_clear_pointer (&state->content_checksum, g_checksum_free); return ret; } @@ -419,8 +421,6 @@ do_content_open_generic (OstreeRepo *repo, if (!read_varuint64 (state, &xattr_offset, error)) goto out; - state->barecommitstate.fd = -1; - modev = g_variant_get_child_value (state->mode_dict, mode_offset); g_variant_get (modev, "(uuu)", &uid, &gid, &mode); state->uid = GUINT32_FROM_BE (uid); @@ -515,18 +515,15 @@ dispatch_bspatch (OstreeRepo *repo, static gboolean handle_untrusted_content_checksum (OstreeRepo *repo, StaticDeltaExecutionState *state, - GCancellable *cancellable, + GCancellable *cancellable, GError **error) { - g_autoptr(GVariant) header = NULL; - g_autoptr(GFileInfo) finfo = NULL; - gsize bytes_written; - - finfo = _ostree_header_gfile_info_new (state->mode, state->uid, state->gid); - header = _ostree_file_header_new (finfo, state->xattrs); + g_autoptr(GFileInfo) finfo = _ostree_mode_uidgid_to_gfileinfo (state->mode, state->uid, state->gid); + g_autoptr(GVariant) header = _ostree_file_header_new (finfo, state->xattrs); state->content_checksum = g_checksum_new (G_CHECKSUM_SHA256); + gsize bytes_written; if (!_ostree_write_variant_with_size (NULL, header, 0, &bytes_written, state->content_checksum, cancellable, error)) return FALSE; @@ -608,14 +605,14 @@ dispatch_open_splice_and_close (OstreeRepo *repo, { if (!_ostree_repo_open_content_bare (repo, state->checksum, state->content_size, - &state->barecommitstate, - &state->content_out, + &state->tmpf, &state->have_obj, cancellable, error)) goto out; if (!state->have_obj) { + state->content_out = g_unix_output_stream_new (state->tmpf.fd, FALSE); if (!handle_untrusted_content_checksum (repo, state, cancellable, error)) goto out; @@ -629,9 +626,8 @@ dispatch_open_splice_and_close (OstreeRepo *repo, else { /* Slower path, for symlinks and unpacking deltas into archive-z2 */ - g_autoptr(GFileInfo) finfo = NULL; - - finfo = _ostree_header_gfile_info_new (state->mode, state->uid, state->gid); + g_autoptr(GFileInfo) finfo = + _ostree_mode_uidgid_to_gfileinfo (state->mode, state->uid, state->gid); if (S_ISLNK (state->mode)) { @@ -711,11 +707,12 @@ dispatch_open (OstreeRepo *repo, if (!_ostree_repo_open_content_bare (repo, state->checksum, state->content_size, - &state->barecommitstate, - &state->content_out, + &state->tmpf, &state->have_obj, cancellable, error)) goto out; + if (!state->have_obj) + state->content_out = g_unix_output_stream_new (state->tmpf.fd, FALSE); if (!handle_untrusted_content_checksum (repo, state, cancellable, error)) goto out; @@ -830,11 +827,13 @@ dispatch_set_read_source (OstreeRepo *repo, g_free (state->read_source_object); state->read_source_object = ostree_checksum_from_bytes (state->payload_data + source_offset); - - if (!_ostree_repo_read_bare_fd (repo, state->read_source_object, &state->read_source_fd, - cancellable, error)) + + if (!_ostree_repo_load_file_bare (repo, state->read_source_object, + &state->read_source_fd, + NULL, NULL, NULL, + cancellable, error)) goto out; - + ret = TRUE; out: if (!ret) @@ -897,11 +896,12 @@ dispatch_close (OstreeRepo *repo, } } - if (!_ostree_repo_commit_trusted_content_bare (repo, state->checksum, &state->barecommitstate, + if (!_ostree_repo_commit_trusted_content_bare (repo, state->checksum, &state->tmpf, state->uid, state->gid, state->mode, state->xattrs, cancellable, error)) goto out; + g_clear_object (&state->content_out); } if (!dispatch_unset_read_source (repo, state, cancellable, error)) @@ -909,7 +909,6 @@ dispatch_close (OstreeRepo *repo, g_clear_pointer (&state->xattrs, g_variant_unref); g_clear_pointer (&state->content_checksum, g_checksum_free); - g_clear_object (&state->content_out); state->checksum_index++; state->output_target = NULL; diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index e556e464..815d2d65 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -44,6 +44,7 @@ #include #include #include +#include /** * SECTION:ostree-repo @@ -77,6 +78,25 @@ * Once the #OstreeMutableTree is complete, write all of its metadata * with ostree_repo_write_mtree(), and finally create a commit with * ostree_repo_write_commit(). + * + * ## Collection IDs + * + * A collection ID is a globally unique identifier which, if set, is used to + * identify refs from a repository which are mirrored elsewhere, such as in + * mirror repositories or peer to peer networks. + * + * This is separate from the `collection-id` configuration key for a remote, which + * is used to store the collection ID of the repository that remote points to. + * + * The collection ID should only be set on an #OstreeRepo if it is the canonical + * collection for some refs. + * + * A collection ID must be a reverse DNS name, where the domain name is under the + * control of the curator of the collection, so they can demonstrate ownership + * of the collection. The later elements in the reverse DNS name can be used to + * disambiguate between multiple collections from the same curator. For example, + * `org.exampleos.Main` and `org.exampleos.Apps`. For the complete format of + * collection IDs, see ostree_validate_collection_id(). */ typedef struct { GObjectClass parent_class; @@ -162,7 +182,7 @@ _ostree_repo_add_remote (OstreeRepo *self, g_mutex_lock (&self->remotes_lock); - already_existed = g_hash_table_replace (self->remotes, remote->name, ostree_remote_ref (remote)); + already_existed = !g_hash_table_replace (self->remotes, remote->name, ostree_remote_ref (remote)); g_mutex_unlock (&self->remotes_lock); @@ -456,11 +476,13 @@ ostree_repo_finalize (GObject *object) if (self->config) g_key_file_free (self->config); g_clear_pointer (&self->txn_refs, g_hash_table_destroy); + g_clear_pointer (&self->txn_collection_refs, g_hash_table_destroy); g_clear_error (&self->writable_error); g_clear_pointer (&self->object_sizes, (GDestroyNotify) g_hash_table_unref); g_clear_pointer (&self->dirmeta_cache, (GDestroyNotify) g_hash_table_unref); g_mutex_clear (&self->cache_lock); g_mutex_clear (&self->txn_stats_lock); + g_free (self->collection_id); g_clear_pointer (&self->remotes, g_hash_table_destroy); g_mutex_clear (&self->remotes_lock); @@ -876,8 +898,8 @@ impl_repo_remote_add (OstreeRepo *self, g_return_val_if_fail (url != NULL, FALSE); g_return_val_if_fail (options == NULL || g_variant_is_of_type (options, G_VARIANT_TYPE ("a{sv}")), FALSE); - if (strchr (name, '/') != NULL) - return glnx_throw (error, "Invalid character '/' in remote name: %s", name); + if (!ostree_validate_remote_name (name, error)) + return FALSE; g_autoptr(OstreeRemote) remote = _ostree_repo_get_remote (self, name, NULL); if (remote != NULL && if_not_exists) @@ -1005,8 +1027,8 @@ impl_repo_remote_delete (OstreeRepo *self, { g_return_val_if_fail (name != NULL, FALSE); - if (strchr (name, '/') != NULL) - return glnx_throw (error, "Invalid character '/' in remote name: %s", name); + if (!ostree_validate_remote_name (name, error)) + return FALSE; g_autoptr(OstreeRemote) remote = NULL; if (if_exists) @@ -1683,7 +1705,8 @@ ostree_repo_create (OstreeRepo *self, glnx_fd_close int dfd = -1; struct stat stbuf; const char *state_dirs[] = { "objects", "tmp", "extensions", "state", - "refs", "refs/heads", "refs/remotes" }; + "refs", "refs/heads", "refs/mirrors", + "refs/remotes" }; if (mkdir (repopath, 0755) != 0) { @@ -1707,6 +1730,9 @@ ostree_repo_create (OstreeRepo *self, g_string_append_printf (config_data, "mode=%s\n", mode_str); + if (self->collection_id != NULL) + g_string_append_printf (config_data, "collection-id=%s\n", self->collection_id); + if (!glnx_file_replace_contents_at (dfd, "config", (guint8*)config_data->str, config_data->len, 0, cancellable, error)) @@ -1726,6 +1752,21 @@ ostree_repo_create (OstreeRepo *self, } } + /* Test that the fs supports user xattrs now, so we get an error early rather + * than during an object write later. + */ + if (mode == OSTREE_REPO_MODE_BARE_USER) + { + g_auto(GLnxTmpfile) tmpf = { 0, }; + + if (!glnx_open_tmpfile_linkable_at (dfd, ".", O_RDWR|O_CLOEXEC, &tmpf, error)) + return FALSE; + if (fchmod (tmpf.fd, 0600) < 0) + return glnx_throw_errno_prefix (error, "fchmod"); + if (!_ostree_write_bareuser_metadata (tmpf.fd, 0, 0, 644, NULL, error)) + return FALSE; + } + if (!ostree_repo_open (self, cancellable, error)) return FALSE; @@ -1939,6 +1980,27 @@ reload_core_config (OstreeRepo *self, self->zlib_compression_level = OSTREE_ARCHIVE_DEFAULT_COMPRESSION_LEVEL; } + { g_autofree char *min_free_space_percent_str = NULL; + /* If changing this, be sure to change the man page too */ + const char *default_min_free_space = "3"; + + if (!ot_keyfile_get_value_with_default (self->config, "core", "min-free-space-percent", + default_min_free_space, + &min_free_space_percent_str, error)) + return FALSE; + + self->min_free_space_percent = g_ascii_strtoull (min_free_space_percent_str, NULL, 10); + if (self->min_free_space_percent > 99) + return glnx_throw (error, "Invalid min-free-space-percent '%s'", min_free_space_percent_str); + } + + { + g_clear_pointer (&self->collection_id, g_free); + if (!ot_keyfile_get_value_with_default (self->config, "core", "collection-id", + NULL, &self->collection_id, NULL)) + return FALSE; + } + if (!ot_keyfile_get_value_with_default (self->config, "core", "parent", NULL, &parent_repo_path, error)) return FALSE; @@ -2502,115 +2564,211 @@ load_metadata_internal (OstreeRepo *self, return TRUE; } -/* Basically fstatat(), but also looks in both the committed and staging - * directories, and returns *out_dfd for where we found the object. - */ -static gboolean -stat_bare_content_object (OstreeRepo *self, - const char *loose_path_buf, - int *out_dfd, - GFileInfo **out_info, - GCancellable *cancellable, - GError **error) -{ - struct stat stbuf; - int res; - int dirfd; - - dirfd = self->objects_dir_fd; - res = TEMP_FAILURE_RETRY (fstatat (dirfd, loose_path_buf, &stbuf, AT_SYMLINK_NOFOLLOW)); - if (res < 0 && errno == ENOENT && self->commit_stagedir_fd != -1) - { - dirfd = self->commit_stagedir_fd; - res = TEMP_FAILURE_RETRY (fstatat (dirfd, loose_path_buf, &stbuf, AT_SYMLINK_NOFOLLOW)); - } - if (res < 0) - { - if (errno == ENOENT) - { - *out_dfd = -1; - *out_info = NULL; - return TRUE; - } - return glnx_throw_errno (error); - } - - g_autoptr(GFileInfo) ret_info = _ostree_header_gfile_info_new (stbuf.st_mode, stbuf.st_uid, stbuf.st_gid); - - if (S_ISREG (stbuf.st_mode)) - { - g_file_info_set_size (ret_info, stbuf.st_size); - } - else if (S_ISLNK (stbuf.st_mode)) - { - if (!ot_readlinkat_gfile_info (dirfd, loose_path_buf, - ret_info, cancellable, error)) - return FALSE; - } - else - return glnx_throw (error, "Not a regular file or symlink: %s", loose_path_buf); - - *out_dfd = dirfd; - ot_transfer_out_value (out_info, &ret_info); - return TRUE; -} - static GVariant * -set_info_from_filemeta (GFileInfo *info, - GVariant *metadata) +filemeta_to_stat (struct stat *stbuf, + GVariant *metadata) { guint32 uid, gid, mode; GVariant *xattrs; g_variant_get (metadata, "(uuu@a(ayay))", &uid, &gid, &mode, &xattrs); - uid = GUINT32_FROM_BE (uid); - gid = GUINT32_FROM_BE (gid); - mode = GUINT32_FROM_BE (mode); - - g_file_info_set_attribute_uint32 (info, "unix::uid", uid); - g_file_info_set_attribute_uint32 (info, "unix::gid", gid); - g_file_info_set_attribute_uint32 (info, "unix::mode", mode); + stbuf->st_uid = GUINT32_FROM_BE (uid); + stbuf->st_gid = GUINT32_FROM_BE (gid); + stbuf->st_mode = GUINT32_FROM_BE (mode); return xattrs; } -gboolean -_ostree_repo_read_bare_fd (OstreeRepo *self, - const char *checksum, - int *out_fd, - GCancellable *cancellable, - GError **error) +static gboolean +repo_load_file_archive (OstreeRepo *self, + const char *checksum, + GInputStream **out_input, + GFileInfo **out_file_info, + GVariant **out_xattrs, + GCancellable *cancellable, + GError **error) { + struct stat stbuf; char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; - - g_assert (_ostree_repo_mode_is_bare (self->mode)); - _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, self->mode); - if (!ot_openat_ignore_enoent (self->objects_dir_fd, loose_path_buf, out_fd, error)) + glnx_fd_close int fd = -1; + if (!ot_openat_ignore_enoent (self->objects_dir_fd, loose_path_buf, &fd, + error)) return FALSE; - if (*out_fd == -1 && self->commit_stagedir_fd != -1) + if (fd < 0 && self->commit_stagedir_fd != -1) { - if (!ot_openat_ignore_enoent (self->commit_stagedir_fd, loose_path_buf, out_fd, error)) + if (!ot_openat_ignore_enoent (self->commit_stagedir_fd, loose_path_buf, &fd, + error)) return FALSE; } - if (*out_fd == -1) + if (fd != -1) { - if (self->parent_repo) - return _ostree_repo_read_bare_fd (self->parent_repo, - checksum, - out_fd, - cancellable, - error); + if (!glnx_fstat (fd, &stbuf, error)) + return FALSE; + g_autoptr(GInputStream) tmp_stream = g_unix_input_stream_new (glnx_steal_fd (&fd), TRUE); + /* Note return here */ + return ostree_content_stream_parse (TRUE, tmp_stream, stbuf.st_size, TRUE, + out_input, out_file_info, out_xattrs, + cancellable, error); + } + else if (self->parent_repo) + { + return ostree_repo_load_file (self->parent_repo, checksum, + out_input, out_file_info, out_xattrs, + cancellable, error); + } + else + { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "No such file object %s", checksum); + "Couldn't find file object '%s'", checksum); + return FALSE; + } +} + +gboolean +_ostree_repo_load_file_bare (OstreeRepo *self, + const char *checksum, + int *out_fd, + struct stat *out_stbuf, + char **out_symlink, + GVariant **out_xattrs, + GCancellable *cancellable, + GError **error) +{ + /* The bottom case recursing on the parent repo */ + if (self == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Couldn't find file object '%s'", checksum); return FALSE; } + struct stat stbuf; + glnx_fd_close int fd = -1; + g_autofree char *ret_symlink = NULL; + g_autoptr(GVariant) ret_xattrs = NULL; + char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; + _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, self->mode); + + /* Do a fstatat() and find the object directory that contains this object */ + int objdir_fd = self->objects_dir_fd; + int res; + if ((res = TEMP_FAILURE_RETRY (fstatat (objdir_fd, loose_path_buf, &stbuf, AT_SYMLINK_NOFOLLOW))) < 0 + && errno == ENOENT && self->commit_stagedir_fd != -1) + { + objdir_fd = self->commit_stagedir_fd; + res = TEMP_FAILURE_RETRY (fstatat (objdir_fd, loose_path_buf, &stbuf, AT_SYMLINK_NOFOLLOW)); + } + if (res < 0 && errno != ENOENT) + return glnx_throw_errno_prefix (error, "fstat"); + else if (res < 0) + { + g_assert (errno == ENOENT); + return _ostree_repo_load_file_bare (self->parent_repo, checksum, out_fd, + out_stbuf, out_symlink, out_xattrs, + cancellable, error); + } + + const gboolean need_open = + (out_fd || out_xattrs || self->mode == OSTREE_REPO_MODE_BARE_USER); + /* If it's a regular file and we're requested to return the fd, do it now. As + * a special case in bare-user, we always do an open, since the stat() metadata + * lives there. + */ + if (need_open && S_ISREG (stbuf.st_mode)) + { + fd = openat (objdir_fd, loose_path_buf, O_CLOEXEC | O_RDONLY); + if (fd < 0) + return glnx_throw_errno_prefix (error, "openat"); + } + + if (!(S_ISREG (stbuf.st_mode) || S_ISLNK (stbuf.st_mode))) + return glnx_throw (error, "Not a regular file or symlink: %s", loose_path_buf); + + /* In the non-bare-user case, gather symlink info if requested */ + if (self->mode != OSTREE_REPO_MODE_BARE_USER + && S_ISLNK (stbuf.st_mode) && out_symlink) + { + ret_symlink = glnx_readlinkat_malloc (objdir_fd, loose_path_buf, + cancellable, error); + if (!ret_symlink) + return FALSE; + } + + if (self->mode == OSTREE_REPO_MODE_BARE_USER) + { + g_autoptr(GBytes) bytes = glnx_fgetxattr_bytes (fd, "user.ostreemeta", error); + if (bytes == NULL) + return FALSE; + + g_autoptr(GVariant) metadata = g_variant_ref_sink (g_variant_new_from_bytes (OSTREE_FILEMETA_GVARIANT_FORMAT, + bytes, FALSE)); + ret_xattrs = filemeta_to_stat (&stbuf, metadata); + if (S_ISLNK (stbuf.st_mode)) + { + if (out_symlink) + { + char targetbuf[PATH_MAX+1]; + gsize target_size; + g_autoptr(GInputStream) target_input = g_unix_input_stream_new (fd, FALSE); + if (!g_input_stream_read_all (target_input, targetbuf, sizeof (targetbuf), + &target_size, cancellable, error)) + return FALSE; + + ret_symlink = g_strndup (targetbuf, target_size); + } + /* In the symlink case, we don't want to return the bare-user fd */ + (void) close (glnx_steal_fd (&fd)); + } + } + else if (self->mode == OSTREE_REPO_MODE_BARE_USER_ONLY) + { + /* Canonical info is: uid/gid is 0 and no xattrs, which + might be wrong and thus not validate correctly, but + at least we report something consistent. */ + stbuf.st_uid = stbuf.st_gid = 0; + + if (out_xattrs) + { + GVariantBuilder builder; + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)")); + ret_xattrs = g_variant_ref_sink (g_variant_builder_end (&builder)); + } + } + else + { + g_assert (self->mode == OSTREE_REPO_MODE_BARE); + + if (S_ISREG (stbuf.st_mode) && out_xattrs) + { + if (self->disable_xattrs) + ret_xattrs = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0)); + else if (!glnx_fd_get_all_xattrs (fd, &ret_xattrs, + cancellable, error)) + return FALSE; + } + else if (S_ISLNK (stbuf.st_mode) && out_xattrs) + { + if (self->disable_xattrs) + ret_xattrs = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0)); + else if (!glnx_dfd_name_get_all_xattrs (objdir_fd, loose_path_buf, + &ret_xattrs, + cancellable, error)) + return FALSE; + } + } + + if (out_fd) + *out_fd = glnx_steal_fd (&fd); + if (out_stbuf) + *out_stbuf = stbuf; + ot_transfer_out_value (out_symlink, &ret_symlink); + ot_transfer_out_value (out_xattrs, &ret_xattrs); return TRUE; } @@ -2636,209 +2794,43 @@ ostree_repo_load_file (OstreeRepo *self, GCancellable *cancellable, GError **error) { - gboolean found = FALSE; - g_autoptr(GInputStream) ret_input = NULL; - g_autoptr(GFileInfo) ret_file_info = NULL; - g_autoptr(GVariant) ret_xattrs = NULL; - - OstreeRepoMode repo_mode = ostree_repo_get_mode (self); - - char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; - _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, repo_mode); - - if (repo_mode == OSTREE_REPO_MODE_ARCHIVE_Z2) - { - int fd = -1; - struct stat stbuf; - g_autoptr(GInputStream) tmp_stream = NULL; - - if (!ot_openat_ignore_enoent (self->objects_dir_fd, loose_path_buf, &fd, - error)) - return FALSE; - - if (fd < 0 && self->commit_stagedir_fd != -1) - { - if (!ot_openat_ignore_enoent (self->commit_stagedir_fd, loose_path_buf, &fd, - error)) - return FALSE; - } - - if (fd != -1) - { - tmp_stream = g_unix_input_stream_new (fd, TRUE); - fd = -1; /* Transfer ownership */ - - if (!glnx_stream_fstat ((GFileDescriptorBased*) tmp_stream, &stbuf, - error)) - return FALSE; - - if (!ostree_content_stream_parse (TRUE, tmp_stream, stbuf.st_size, TRUE, - out_input ? &ret_input : NULL, - &ret_file_info, &ret_xattrs, - cancellable, error)) - return FALSE; - - found = TRUE; - } - } + if (self->mode == OSTREE_REPO_MODE_ARCHIVE_Z2) + return repo_load_file_archive (self, checksum, out_input, out_file_info, out_xattrs, + cancellable, error); else { - int objdir_fd; /* referenced */ - if (!stat_bare_content_object (self, loose_path_buf, - &objdir_fd, - &ret_file_info, - cancellable, error)) + glnx_fd_close int fd = -1; + struct stat stbuf; + g_autofree char *symlink_target = NULL; + g_autoptr(GVariant) ret_xattrs = NULL; + if (!_ostree_repo_load_file_bare (self, checksum, + out_input ? &fd : NULL, + out_file_info ? &stbuf : NULL, + out_file_info ? &symlink_target : NULL, + out_xattrs ? &ret_xattrs : NULL, + cancellable, error)) return FALSE; - if (ret_file_info) + /* Convert fd → GInputStream and struct stat → GFileInfo */ + if (out_input) { - found = TRUE; - - if (repo_mode == OSTREE_REPO_MODE_BARE_USER) - { - guint32 mode; - g_autoptr(GVariant) metadata = NULL; - g_autoptr(GBytes) bytes = NULL; - glnx_fd_close int fd = -1; - - /* In bare-user, symlinks are stored as regular files, so we just - * always do an open, then query the user.ostreemeta xattr for - * more information. - */ - fd = openat (objdir_fd, loose_path_buf, O_RDONLY | O_CLOEXEC); - if (fd < 0) - return glnx_throw_errno (error); - - bytes = glnx_fgetxattr_bytes (fd, "user.ostreemeta", error); - if (bytes == NULL) - return FALSE; - - metadata = g_variant_new_from_bytes (OSTREE_FILEMETA_GVARIANT_FORMAT, - bytes, FALSE); - g_variant_ref_sink (metadata); - - ret_xattrs = set_info_from_filemeta (ret_file_info, metadata); - - mode = g_file_info_get_attribute_uint32 (ret_file_info, "unix::mode"); - - if (S_ISREG (mode) && out_input) - { - g_assert (fd != -1); - ret_input = g_unix_input_stream_new (fd, TRUE); - fd = -1; /* Transfer ownership */ - } - else if (S_ISLNK (mode)) - { - g_autoptr(GInputStream) target_input = NULL; - char targetbuf[PATH_MAX+1]; - gsize target_size; - - g_file_info_set_file_type (ret_file_info, G_FILE_TYPE_SYMBOLIC_LINK); - g_file_info_set_size (ret_file_info, 0); - - target_input = g_unix_input_stream_new (fd, TRUE); - fd = -1; /* Transfer ownership */ - - if (!g_input_stream_read_all (target_input, targetbuf, sizeof (targetbuf), - &target_size, cancellable, error)) - return FALSE; - - g_file_info_set_symlink_target (ret_file_info, targetbuf); - } - } - else if (repo_mode == OSTREE_REPO_MODE_BARE_USER_ONLY) - { - glnx_fd_close int fd = -1; - - /* Canonical info is: uid/gid is 0 and no xattrs, which - might be wrong and thus not validate correctly, but - at least we report something consistent. */ - g_file_info_set_attribute_uint32 (ret_file_info, "unix::uid", 0); - g_file_info_set_attribute_uint32 (ret_file_info, "unix::gid", 0); - - if (g_file_info_get_file_type (ret_file_info) == G_FILE_TYPE_REGULAR && - out_input) - { - fd = openat (objdir_fd, loose_path_buf, O_RDONLY | O_CLOEXEC); - if (fd < 0) - return glnx_throw_errno (error); - - ret_input = g_unix_input_stream_new (fd, TRUE); - fd = -1; /* Transfer ownership */ - } - - if (out_xattrs) - { - GVariantBuilder builder; - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ayay)")); - ret_xattrs = g_variant_ref_sink (g_variant_builder_end (&builder)); - } - } + if (fd != -1) + *out_input = g_unix_input_stream_new (glnx_steal_fd (&fd), TRUE); else - { - g_assert (repo_mode == OSTREE_REPO_MODE_BARE); - - if (g_file_info_get_file_type (ret_file_info) == G_FILE_TYPE_REGULAR - && (out_input || out_xattrs)) - { - glnx_fd_close int fd = -1; - - fd = openat (objdir_fd, loose_path_buf, O_RDONLY | O_CLOEXEC); - if (fd < 0) - return glnx_throw_errno (error); - - if (out_xattrs) - { - if (self->disable_xattrs) - ret_xattrs = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0)); - else if (!glnx_fd_get_all_xattrs (fd, &ret_xattrs, - cancellable, error)) - return FALSE; - } - - if (out_input) - { - ret_input = g_unix_input_stream_new (fd, TRUE); - fd = -1; /* Transfer ownership */ - } - } - else if (g_file_info_get_file_type (ret_file_info) == G_FILE_TYPE_SYMBOLIC_LINK - && out_xattrs) - { - if (self->disable_xattrs) - ret_xattrs = g_variant_ref_sink (g_variant_new_array (G_VARIANT_TYPE ("(ayay)"), NULL, 0)); - else if (!glnx_dfd_name_get_all_xattrs (objdir_fd, loose_path_buf, - &ret_xattrs, - cancellable, error)) - return FALSE; - } - } + *out_input = NULL; } - } - - if (!found) - { - if (self->parent_repo) + if (out_file_info) { - if (!ostree_repo_load_file (self->parent_repo, checksum, - out_input ? &ret_input : NULL, - out_file_info ? &ret_file_info : NULL, - out_xattrs ? &ret_xattrs : NULL, - cancellable, error)) - return FALSE; + *out_file_info = _ostree_stbuf_to_gfileinfo (&stbuf); + if (S_ISLNK (stbuf.st_mode)) + g_file_info_set_symlink_target (*out_file_info, symlink_target); + else + g_assert (S_ISREG (stbuf.st_mode)); } - else - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, - "Couldn't find file object '%s'", checksum); - return FALSE; - } - } - ot_transfer_out_value (out_input, &ret_input); - ot_transfer_out_value (out_file_info, &ret_file_info); - ot_transfer_out_value (out_xattrs, &ret_xattrs); - return TRUE; + ot_transfer_out_value (out_xattrs, &ret_xattrs); + return TRUE; + } } /** @@ -3105,25 +3097,24 @@ import_one_object_link (OstreeRepo *self, */ if (import_is_bareuser_only_conversion (source, self, objtype)) { - g_autoptr(GFileInfo) finfo = NULL; + struct stat stbuf; - if (!ostree_repo_load_file (source, checksum, NULL, &finfo, NULL, - cancellable, error)) + if (!_ostree_repo_load_file_bare (source, checksum, NULL, &stbuf, + NULL, NULL, cancellable, error)) return FALSE; - switch (g_file_info_get_file_type (finfo)) + if (S_ISREG (stbuf.st_mode)) { - case G_FILE_TYPE_REGULAR: /* This is OK, we'll drop through and try a hardlink */ - break; - case G_FILE_TYPE_SYMBOLIC_LINK: + } + else if (S_ISLNK (stbuf.st_mode)) + { /* NOTE early return */ *out_was_supported = FALSE; return TRUE; - default: - g_assert_not_reached (); - break; } + else + g_assert_not_reached (); } if (!_ostree_repo_ensure_loose_objdir_at (self->objects_dir_fd, loose_path_buf, cancellable, error)) @@ -3893,8 +3884,7 @@ sign_data (OstreeRepo *self, GError **error) { gboolean ret = FALSE; - glnx_fd_close int tmp_fd = -1; - g_autofree char *tmp_path = NULL; + g_auto(GLnxTmpfile) tmpf = { 0, }; g_autoptr(GOutputStream) tmp_signature_output = NULL; gpgme_ctx_t context = NULL; g_autoptr(GBytes) ret_signature = NULL; @@ -3905,9 +3895,9 @@ sign_data (OstreeRepo *self, g_autoptr(GMappedFile) signature_file = NULL; if (!glnx_open_tmpfile_linkable_at (self->tmp_dir_fd, ".", O_RDWR | O_CLOEXEC, - &tmp_fd, &tmp_path, error)) + &tmpf, error)) goto out; - tmp_signature_output = g_unix_output_stream_new (tmp_fd, FALSE); + tmp_signature_output = g_unix_output_stream_new (tmpf.fd, FALSE); context = ot_gpgme_new_ctx (homedir, error); if (!context) @@ -3961,7 +3951,7 @@ sign_data (OstreeRepo *self, if (!g_output_stream_close (tmp_signature_output, cancellable, error)) goto out; - signature_file = g_mapped_file_new_from_fd (tmp_fd, FALSE, error); + signature_file = g_mapped_file_new_from_fd (tmpf.fd, FALSE, error); if (!signature_file) goto out; ret_signature = g_mapped_file_get_bytes (signature_file); @@ -4095,7 +4085,7 @@ ostree_repo_sign_delta (OstreeRepo *self, * @cancellable: A #GCancellable * @error: a #GError * - * Add a GPG signature to a static delta. + * Add a GPG signature to a summary file. */ gboolean ostree_repo_add_gpg_signature_summary (OstreeRepo *self, @@ -4540,6 +4530,51 @@ ostree_repo_verify_summary (OstreeRepo *self, error); } +/* Add an entry for a @ref ↦ @checksum mapping to an `a(s(t@ay@a{sv}))` + * @refs_builder to go into a `summary` file. This includes building the + * standard additional metadata keys for the ref. */ +static gboolean +summary_add_ref_entry (OstreeRepo *self, + const char *ref, + const char *checksum, + GVariantBuilder *refs_builder, + GError **error) +{ + g_auto(GVariantDict) commit_metadata_builder = OT_VARIANT_BUILDER_INITIALIZER; + + g_assert (ref); g_assert (checksum); + + g_autofree char *remotename = NULL; + if (!ostree_parse_refspec (ref, &remotename, NULL, NULL)) + g_assert_not_reached (); + + /* Don't put remote refs in the summary */ + if (remotename != NULL) + return TRUE; + + g_autoptr(GVariant) commit_obj = NULL; + if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_COMMIT, checksum, &commit_obj, error)) + return FALSE; + + g_variant_dict_init (&commit_metadata_builder, NULL); + + /* Forward the commit’s timestamp if it’s valid. */ + guint64 commit_timestamp = ostree_commit_get_timestamp (commit_obj); + g_autoptr(GDateTime) dt = g_date_time_new_from_unix_utc (commit_timestamp); + + if (dt != NULL) + g_variant_dict_insert_value (&commit_metadata_builder, OSTREE_COMMIT_TIMESTAMP, + g_variant_new_uint64 (GUINT64_TO_BE (commit_timestamp))); + + g_variant_builder_add_value (refs_builder, + g_variant_new ("(s(t@ay@a{sv}))", ref, + (guint64) g_variant_get_size (commit_obj), + ostree_checksum_to_bytes_v (checksum), + g_variant_dict_end (&commit_metadata_builder))); + + return TRUE; +} + /** * ostree_repo_regenerate_summary: * @self: Repo @@ -4556,6 +4591,13 @@ ostree_repo_verify_summary (OstreeRepo *self, * * It is regenerated automatically after a commit if * `core/commit-update-summary` is set. + * + * If the `core/collection-id` key is set in the configuration, it will be + * included as %OSTREE_SUMMARY_COLLECTION_ID in the summary file. Refs from the + * `refs/mirrors` directory will be included in the generated summary file, + * listed under the %OSTREE_SUMMARY_COLLECTION_MAP key. Collection IDs and refs + * in %OSTREE_SUMMARY_COLLECTION_MAP are guaranteed to be in lexicographic + * order. */ gboolean ostree_repo_regenerate_summary (OstreeRepo *self, @@ -4567,49 +4609,26 @@ ostree_repo_regenerate_summary (OstreeRepo *self, g_variant_dict_init (&additional_metadata_builder, additional_metadata); g_autoptr(GVariantBuilder) refs_builder = g_variant_builder_new (G_VARIANT_TYPE ("a(s(taya{sv}))")); + const gchar *main_collection_id = ostree_repo_get_collection_id (self); + { - g_autoptr(GHashTable) refs = NULL; - if (!ostree_repo_list_refs (self, NULL, &refs, cancellable, error)) - return FALSE; - - g_autoptr(GList) ordered_keys = g_hash_table_get_keys (refs); - ordered_keys = g_list_sort (ordered_keys, (GCompareFunc)strcmp); - - for (GList *iter = ordered_keys; iter; iter = iter->next) + if (main_collection_id == NULL) { - const char *ref = iter->data; - const char *commit = g_hash_table_lookup (refs, ref); - g_auto(GVariantDict) commit_metadata_builder = OT_VARIANT_BUILDER_INITIALIZER; - - g_assert (commit); - - g_autofree char *remotename = NULL; - if (!ostree_parse_refspec (ref, &remotename, NULL, NULL)) - g_assert_not_reached (); - - /* Don't put remote refs in the summary */ - if (remotename != NULL) - continue; - - g_autoptr(GVariant) commit_obj = NULL; - if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_COMMIT, commit, &commit_obj, error)) + g_autoptr(GHashTable) refs = NULL; + if (!ostree_repo_list_refs (self, NULL, &refs, cancellable, error)) return FALSE; - g_variant_dict_init (&commit_metadata_builder, NULL); + g_autoptr(GList) ordered_keys = g_hash_table_get_keys (refs); + ordered_keys = g_list_sort (ordered_keys, (GCompareFunc)strcmp); - /* Forward the commit’s timestamp if it’s valid. */ - guint64 commit_timestamp = ostree_commit_get_timestamp (commit_obj); - g_autoptr(GDateTime) dt = g_date_time_new_from_unix_utc (commit_timestamp); + for (GList *iter = ordered_keys; iter; iter = iter->next) + { + const char *ref = iter->data; + const char *commit = g_hash_table_lookup (refs, ref); - if (dt != NULL) - g_variant_dict_insert_value (&commit_metadata_builder, OSTREE_COMMIT_TIMESTAMP, - g_variant_new_uint64 (GUINT64_TO_BE (commit_timestamp))); - - g_variant_builder_add_value (refs_builder, - g_variant_new ("(s(t@ay@a{sv}))", ref, - (guint64) g_variant_get_size (commit_obj), - ostree_checksum_to_bytes_v (commit), - g_variant_dict_end (&commit_metadata_builder))); + if (!summary_add_ref_entry (self, ref, commit, refs_builder, error)) + return FALSE; + } } } @@ -4656,6 +4675,88 @@ ostree_repo_regenerate_summary (OstreeRepo *self, g_variant_new_uint64 (GUINT64_TO_BE (g_get_real_time () / G_USEC_PER_SEC))); } + /* Add refs which have a collection specified. ostree_repo_list_collection_refs() + * is guaranteed to only return refs which are in refs/mirrors, or those which + * are in refs/heads if the repository configuration specifies a collection ID + * (which we put in the main refs map, rather than the collection map, for + * backwards compatibility). */ + { + g_autoptr(GHashTable) collection_refs = NULL; + if (!ostree_repo_list_collection_refs (self, NULL, &collection_refs, cancellable, error)) + return FALSE; + + gsize collection_map_size = 0; + GHashTableIter iter; + g_autoptr(GHashTable) collection_map = NULL; /* (element-type utf8 GHashTable) */ + g_hash_table_iter_init (&iter, collection_refs); + collection_map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, + (GDestroyNotify) g_hash_table_unref); + + const OstreeCollectionRef *ref; + const char *checksum; + while (g_hash_table_iter_next (&iter, (gpointer *) &ref, (gpointer *) &checksum)) + { + GHashTable *ref_map = g_hash_table_lookup (collection_map, ref->collection_id); + + if (ref_map == NULL) + { + ref_map = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, NULL); + g_hash_table_insert (collection_map, ref->collection_id, ref_map); + } + + g_hash_table_insert (ref_map, ref->ref_name, (gpointer) checksum); + } + + g_autoptr(GVariantBuilder) collection_refs_builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sa(s(taya{sv}))}")); + + g_autoptr(GList) ordered_collection_ids = g_hash_table_get_keys (collection_map); + ordered_collection_ids = g_list_sort (ordered_collection_ids, (GCompareFunc) strcmp); + + for (GList *collection_iter = ordered_collection_ids; collection_iter; collection_iter = collection_iter->next) + { + const char *collection_id = collection_iter->data; + GHashTable *ref_map = g_hash_table_lookup (collection_map, collection_id); + + gboolean is_main_collection_id = (main_collection_id != NULL && g_str_equal (collection_id, main_collection_id)); + + if (!is_main_collection_id) + { + g_variant_builder_open (collection_refs_builder, G_VARIANT_TYPE ("{sa(s(taya{sv}))}")); + g_variant_builder_add (collection_refs_builder, "s", collection_id); + g_variant_builder_open (collection_refs_builder, G_VARIANT_TYPE ("a(s(taya{sv}))")); + } + + g_autoptr(GList) ordered_refs = g_hash_table_get_keys (ref_map); + ordered_refs = g_list_sort (ordered_refs, (GCompareFunc) strcmp); + + for (GList *ref_iter = ordered_refs; ref_iter != NULL; ref_iter = ref_iter->next) + { + const char *ref = ref_iter->data; + const char *commit = g_hash_table_lookup (ref_map, ref); + GVariantBuilder *builder = is_main_collection_id ? refs_builder : collection_refs_builder; + + if (!summary_add_ref_entry (self, ref, commit, builder, error)) + return FALSE; + + if (!is_main_collection_id) + collection_map_size++; + } + + if (!is_main_collection_id) + { + g_variant_builder_close (collection_refs_builder); /* array */ + g_variant_builder_close (collection_refs_builder); /* dict entry */ + } + } + + if (main_collection_id != NULL) + g_variant_dict_insert_value (&additional_metadata_builder, OSTREE_SUMMARY_COLLECTION_ID, + g_variant_new_string (main_collection_id)); + if (collection_map_size > 0) + g_variant_dict_insert_value (&additional_metadata_builder, OSTREE_SUMMARY_COLLECTION_MAP, + g_variant_builder_end (collection_refs_builder)); + } + g_autoptr(GVariant) summary = NULL; { g_autoptr(GVariantBuilder) summary_builder = @@ -4873,3 +4974,56 @@ _ostree_repo_memory_cache_ref_destroy (OstreeRepoMemoryCacheRef *state) g_mutex_unlock (lock); g_object_unref (repo); } + +/** + * ostree_repo_get_collection_id: + * @self: an #OstreeRepo + * + * Get the collection ID of this repository. See [collection IDs][collection-ids]. + * + * Returns: (nullable): collection ID for the repository + * Since: 2017.8 + */ +const gchar * +ostree_repo_get_collection_id (OstreeRepo *self) +{ + g_return_val_if_fail (OSTREE_IS_REPO (self), NULL); + + return self->collection_id; +} + +/** + * ostree_repo_set_collection_id: + * @self: an #OstreeRepo + * @collection_id: (nullable): new collection ID, or %NULL to unset it + * @error: return location for a #GError, or %NULL + * + * Set or clear the collection ID of this repository. See [collection IDs][collection-ids]. + * The update will be made in memory, but must be written out to the repository + * configuration on disk using ostree_repo_write_config(). + * + * Returns: %TRUE on success, %FALSE otherwise + * Since: 2017.8 + */ +gboolean +ostree_repo_set_collection_id (OstreeRepo *self, + const gchar *collection_id, + GError **error) +{ + if (collection_id != NULL && !ostree_validate_collection_id (collection_id, error)) + return FALSE; + + g_autofree gchar *new_collection_id = g_strdup (collection_id); + g_free (self->collection_id); + self->collection_id = g_steal_pointer (&new_collection_id); + + if (self->config != NULL) + { + if (collection_id != NULL) + g_key_file_set_string (self->config, "core", "collection-id", collection_id); + else + return g_key_file_remove_key (self->config, "core", "collection-id", error); + } + + return TRUE; +} diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index ed73d4a2..ea7a7789 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -25,6 +25,10 @@ #include "ostree-core.h" #include "ostree-types.h" #include "ostree-async-progress.h" +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API +#include "ostree-ref.h" +#include "ostree-repo-finder.h" +#endif #include "ostree-sepolicy.h" #include "ostree-gpg-verify-result.h" @@ -86,6 +90,17 @@ gboolean ostree_repo_create (OstreeRepo *self, GCancellable *cancellable, GError **error); +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API + +_OSTREE_PUBLIC +const gchar * ostree_repo_get_collection_id (OstreeRepo *self); +_OSTREE_PUBLIC +gboolean ostree_repo_set_collection_id (OstreeRepo *self, + const gchar *collection_id, + GError **error); + +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ + _OSTREE_PUBLIC GFile * ostree_repo_get_path (OstreeRepo *self); @@ -295,6 +310,15 @@ void ostree_repo_transaction_set_ref (OstreeRepo *self, const char *ref, const char *checksum); +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API + +_OSTREE_PUBLIC +void ostree_repo_transaction_set_collection_ref (OstreeRepo *self, + const OstreeCollectionRef *ref, + const char *checksum); + +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ + _OSTREE_PUBLIC gboolean ostree_repo_set_ref_immediate (OstreeRepo *self, const char *remote, @@ -303,6 +327,17 @@ gboolean ostree_repo_set_ref_immediate (OstreeRepo *self, GCancellable *cancellable, GError **error); +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API + +_OSTREE_PUBLIC +gboolean ostree_repo_set_collection_ref_immediate (OstreeRepo *self, + const OstreeCollectionRef *ref, + const char *checksum, + GCancellable *cancellable, + GError **error); + +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ + _OSTREE_PUBLIC gboolean ostree_repo_has_object (OstreeRepo *self, OstreeObjectType objtype, @@ -1046,6 +1081,33 @@ ostree_repo_pull_one_dir (OstreeRepo *self, GCancellable *cancellable, GError **error); + + +#if 0 +FIXME +Called with: remote_name, refs, override-commit-ids +or: URL, refs, override-commit-ids +=> we only need refs; could use the remote_name or URL as additional results + +Summary file is downloaded first, so this would result in multiple downloads of +the summary, but we don’t care because of caching. + +Big problem preventing this from being the overall API: presenting the download +sizes in the gnome-software UI before the user chooses to download. + +_OSTREE_PUBLIC +gboolean ostree_repo_find_remotes_squashed (OstreeRepo *self, + const gchar * const *refs, -> options + GVariant *options, + OstreeRepoFinder **finders, -> options + GMainContext *context, -> nope + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error); +#endif + + + _OSTREE_PUBLIC gboolean ostree_repo_pull_with_options (OstreeRepo *self, const char *remote_name_or_baseurl, @@ -1054,6 +1116,50 @@ gboolean ostree_repo_pull_with_options (OstreeRepo *self, GCancellable *cancellable, GError **error); +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API + +_OSTREE_PUBLIC +void ostree_repo_find_remotes_async (OstreeRepo *self, + const OstreeCollectionRef * const *refs, + GVariant *options, + OstreeRepoFinder **finders, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +_OSTREE_PUBLIC +OstreeRepoFinderResult **ostree_repo_find_remotes_finish (OstreeRepo *self, + GAsyncResult *result, + GError **error); + +_OSTREE_PUBLIC +void ostree_repo_pull_from_remotes_async (OstreeRepo *self, + const OstreeRepoFinderResult * const *results, + GVariant *options, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +_OSTREE_PUBLIC +gboolean ostree_repo_pull_from_remotes_finish (OstreeRepo *self, + GAsyncResult *result, + GError **error); + +_OSTREE_PUBLIC +gchar *ostree_repo_resolve_keyring_for_collection (OstreeRepo *self, + const gchar *collection_id, + GCancellable *cancellable, + GError **error); + +_OSTREE_PUBLIC +gboolean ostree_repo_list_collection_refs (OstreeRepo *self, + const char *match_collection_id, + GHashTable **out_all_refs, + GCancellable *cancellable, + GError **error); + +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ + _OSTREE_PUBLIC void ostree_repo_pull_default_console_progress_changed (OstreeAsyncProgress *progress, gpointer user_data); diff --git a/src/libostree/ostree-rollsum.h b/src/libostree/ostree-rollsum.h index 3a96ea59..ed832c6d 100644 --- a/src/libostree/ostree-rollsum.h +++ b/src/libostree/ostree-rollsum.h @@ -21,6 +21,7 @@ #pragma once #include +#include "libglnx.h" G_BEGIN_DECLS @@ -39,5 +40,6 @@ _ostree_compute_rollsum_matches (GBytes *from, GBytes *to); void _ostree_rollsum_matches_free (OstreeRollsumMatches *rollsum); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(OstreeRollsumMatches, _ostree_rollsum_matches_free) G_END_DECLS diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index ed4831c1..a1584f8c 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -73,15 +73,9 @@ symlink_at_replace (const char *oldpath, goto out; } - /* Rename it into place */ - do - res = renameat (parent_dfd, temppath, parent_dfd, newpath); - while (G_UNLIKELY (res == -1 && errno == EINTR)); - if (res == -1) - { - glnx_set_error_from_errno (error); - goto out; - } + /* Rename it into place */ + if (!glnx_renameat (parent_dfd, temppath, parent_dfd, newpath, error)) + goto out; ret = TRUE; out: @@ -819,8 +813,8 @@ merge_configuration (OstreeSysroot *sysroot, else if (etc_exists) { /* Compatibility hack */ - if (renameat (deployment_dfd, "etc", deployment_dfd, "usr/etc") < 0) - return glnx_throw_errno_prefix (error, "renameat"); + if (!glnx_renameat (deployment_dfd, "etc", deployment_dfd, "usr/etc", error)) + return FALSE; usretc_exists = TRUE; etc_exists = FALSE; } @@ -1424,7 +1418,6 @@ swap_bootloader (OstreeSysroot *sysroot, GError **error) { glnx_fd_close int boot_dfd = -1; - int res; g_assert ((current_bootversion == 0 && new_bootversion == 1) || (current_bootversion == 1 && new_bootversion == 0)); @@ -1436,11 +1429,8 @@ swap_bootloader (OstreeSysroot *sysroot, * its data is in place. Renaming now should give us atomic semantics; * see https://bugzilla.gnome.org/show_bug.cgi?id=755595 */ - do - res = renameat (boot_dfd, "loader.tmp", boot_dfd, "loader"); - while (G_UNLIKELY (res == -1 && errno == EINTR)); - if (res == -1) - return glnx_throw_errno (error); + if (!glnx_renameat (boot_dfd, "loader.tmp", boot_dfd, "loader", error)) + return FALSE; /* Now we explicitly fsync this directory, even though it * isn't required for atomicity, for two reasons: diff --git a/src/libostree/ostree-version.h b/src/libostree/ostree-version.h index 85491b0d..379b8482 100644 --- a/src/libostree/ostree-version.h +++ b/src/libostree/ostree-version.h @@ -44,7 +44,7 @@ * * Since: 2017.4 */ -#define OSTREE_RELEASE_VERSION (7) +#define OSTREE_RELEASE_VERSION (8) /** * OSTREE_VERSION @@ -53,7 +53,7 @@ * * Since: 2017.4 */ -#define OSTREE_VERSION (2017.7) +#define OSTREE_VERSION (2017.8) /** * OSTREE_VERSION_S: @@ -63,7 +63,7 @@ * * Since: 2017.4 */ -#define OSTREE_VERSION_S "2017.7" +#define OSTREE_VERSION_S "2017.8" #define OSTREE_ENCODE_VERSION(year,release) \ ((year) << 16 | (release)) diff --git a/src/libostree/ostree.h b/src/libostree/ostree.h index 5d1ac1e1..0f727384 100644 --- a/src/libostree/ostree.h +++ b/src/libostree/ostree.h @@ -35,5 +35,13 @@ #include #include +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API +#include +#include +#include +#include +#include +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ + #include #include diff --git a/src/libotutil/ot-fs-utils.c b/src/libotutil/ot-fs-utils.c index 8ed88984..2f0ae19c 100644 --- a/src/libotutil/ot-fs-utils.c +++ b/src/libotutil/ot-fs-utils.c @@ -24,61 +24,7 @@ #include "libglnx.h" #include #include - -/* Before https://github.com/GNOME/libglnx/commit/9929adc, the libglnx - * tmpfile API made it hard to clean up tmpfiles in failure cases. - * it's API breaking. Carry the fix here until we're ready to fully port. - */ -void -ot_tmpfile_clear (OtTmpfile *tmpf) -{ - if (!tmpf->initialized) - return; - if (tmpf->fd == -1) - return; - (void) close (tmpf->fd); - /* If ->path is set, we're likely aborting due to an error. Clean it up */ - if (tmpf->path) - { - (void) unlinkat (tmpf->src_dfd, tmpf->path, 0); - g_free (tmpf->path); - } -} - -gboolean -ot_open_tmpfile_linkable_at (int dfd, - const char *subpath, - int flags, - OtTmpfile *out_tmpf, - GError **error) -{ - if (!glnx_open_tmpfile_linkable_at (dfd, subpath, flags, &out_tmpf->fd, &out_tmpf->path, error)) - return FALSE; - out_tmpf->initialized = TRUE; - out_tmpf->src_dfd = dfd; - return TRUE; -} - -gboolean -ot_link_tmpfile_at (OtTmpfile *tmpf, - GLnxLinkTmpfileReplaceMode mode, - int target_dfd, - const char *target, - GError **error) -{ - g_return_val_if_fail (tmpf->initialized, FALSE); - glnx_fd_close int fd = glnx_steal_fd (&tmpf->fd); - if (!glnx_link_tmpfile_at (tmpf->src_dfd, mode, fd, tmpf->path, - target_dfd, target, error)) - { - if (tmpf->path) - (void) unlinkat (tmpf->src_dfd, tmpf->path, 0); - tmpf->initialized = FALSE; - return FALSE; - } - tmpf->initialized = FALSE; - return TRUE; -} +#include /* Convert a fd-relative path to a GFile* - use * for legacy code. @@ -213,9 +159,8 @@ ot_dfd_iter_init_allow_noent (int dfd, *out_exists = FALSE; return TRUE; } - if (!glnx_dirfd_iterator_init_take_fd (fd, dfd_iter, error)) + if (!glnx_dirfd_iterator_init_take_fd (&fd, dfd_iter, error)) return FALSE; - fd = -1; *out_exists = TRUE; return TRUE; } @@ -237,3 +182,29 @@ ot_file_mapat_bytes (int dfd, return g_mapped_file_get_bytes (mfile); } + +/* Given an input stream, splice it to an anonymous file (O_TMPFILE). + * Useful for potentially large but transient files. + */ +GBytes * +ot_map_anonymous_tmpfile_from_content (GInputStream *instream, + GCancellable *cancellable, + GError **error) +{ + g_auto(GLnxTmpfile) tmpf = { 0, }; + if (!glnx_open_anonymous_tmpfile (O_RDWR | O_CLOEXEC, &tmpf, error)) + return NULL; + + g_autoptr(GOutputStream) out = g_unix_output_stream_new (tmpf.fd, FALSE); + gssize n_bytes_written = g_output_stream_splice (out, instream, + G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | + G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, + cancellable, error); + if (n_bytes_written < 0) + return NULL; + + g_autoptr(GMappedFile) mfile = g_mapped_file_new_from_fd (tmpf.fd, FALSE, error); + if (!mfile) + return NULL; + return g_mapped_file_get_bytes (mfile); +} diff --git a/src/libotutil/ot-fs-utils.h b/src/libotutil/ot-fs-utils.h index 26c5a499..43bc6942 100644 --- a/src/libotutil/ot-fs-utils.h +++ b/src/libotutil/ot-fs-utils.h @@ -25,29 +25,31 @@ G_BEGIN_DECLS -/* This is a copy of https://github.com/GNOME/libglnx/pull/46 until we - * can do a full port; see https://github.com/ostreedev/ostree/pull/861 */ +/* A little helper to call unlinkat() as a cleanup + * function. Mostly only necessary to handle + * deletion of temporary symlinks. + */ typedef struct { - gboolean initialized; - int src_dfd; - int fd; + int dfd; char *path; -} OtTmpfile; -void ot_tmpfile_clear (OtTmpfile *tmpf); -G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(OtTmpfile, ot_tmpfile_clear); +} OtCleanupUnlinkat; -gboolean -ot_open_tmpfile_linkable_at (int dfd, - const char *subpath, - int flags, - OtTmpfile *out_tmpf, - GError **error); -gboolean -ot_link_tmpfile_at (OtTmpfile *tmpf, - GLnxLinkTmpfileReplaceMode flags, - int target_dfd, - const char *target, - GError **error); +static inline void +ot_cleanup_unlinkat_clear (OtCleanupUnlinkat *cleanup) +{ + g_clear_pointer (&cleanup->path, g_free); +} + +static inline void +ot_cleanup_unlinkat (OtCleanupUnlinkat *cleanup) +{ + if (cleanup->path) + { + (void) unlinkat (cleanup->dfd, cleanup->path, 0); + ot_cleanup_unlinkat_clear (cleanup); + } +} +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(OtCleanupUnlinkat, ot_cleanup_unlinkat); GFile * ot_fdrel_to_gfile (int dfd, const char *path); @@ -84,6 +86,11 @@ gboolean ot_dfd_iter_init_allow_noent (int dfd, gboolean *out_exists, GError **error); +GBytes * +ot_map_anonymous_tmpfile_from_content (GInputStream *instream, + GCancellable *cancellable, + GError **error); + GBytes *ot_file_mapat_bytes (int dfd, const char *path, GError **error); diff --git a/src/libotutil/ot-gio-utils.c b/src/libotutil/ot-gio-utils.c index a4b61842..af2abb63 100644 --- a/src/libotutil/ot-gio-utils.c +++ b/src/libotutil/ot-gio-utils.c @@ -35,22 +35,6 @@ #define O_BINARY 0 #endif -GFileType -ot_gfile_type_for_mode (guint32 mode) -{ - if (S_ISDIR (mode)) - return G_FILE_TYPE_DIRECTORY; - else if (S_ISREG (mode)) - return G_FILE_TYPE_REGULAR; - else if (S_ISLNK (mode)) - return G_FILE_TYPE_SYMBOLIC_LINK; - else if (S_ISBLK (mode) || S_ISCHR(mode) || S_ISFIFO(mode)) - return G_FILE_TYPE_SPECIAL; - else - return G_FILE_TYPE_UNKNOWN; -} - - GFile * ot_gfile_resolve_path_printf (GFile *path, const char *format, diff --git a/src/libotutil/ot-gio-utils.h b/src/libotutil/ot-gio-utils.h index 0fd3ddf0..a69b744d 100644 --- a/src/libotutil/ot-gio-utils.h +++ b/src/libotutil/ot-gio-utils.h @@ -33,8 +33,6 @@ G_BEGIN_DECLS #define OSTREE_GIO_FAST_QUERYINFO ("standard::name,standard::type,standard::size,standard::is-symlink,standard::symlink-target," \ "unix::device,unix::inode,unix::mode,unix::uid,unix::gid,unix::rdev") -GFileType ot_gfile_type_for_mode (guint32 mode); - GFile * ot_gfile_resolve_path_printf (GFile *path, const char *format, ...) G_GNUC_PRINTF(2, 3); diff --git a/src/ostree/main.c b/src/ostree/main.c index 5a2ed661..113edc66 100644 --- a/src/ostree/main.c +++ b/src/ostree/main.c @@ -41,6 +41,9 @@ static OstreeCommand commands[] = { { "config", ostree_builtin_config }, { "diff", ostree_builtin_diff }, { "export", ostree_builtin_export }, +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API + { "find-remotes", ostree_builtin_find_remotes }, +#endif { "fsck", ostree_builtin_fsck }, { "gpg-sign", ostree_builtin_gpg_sign }, { "init", ostree_builtin_init }, diff --git a/src/ostree/ot-builtin-commit.c b/src/ostree/ot-builtin-commit.c index c14cbec5..8e8b9233 100644 --- a/src/ostree/ot-builtin-commit.c +++ b/src/ostree/ot-builtin-commit.c @@ -63,12 +63,10 @@ parse_fsync_cb (const char *option_name, GError **error) { gboolean val; - if (!ot_parse_boolean (value, &val, error)) return FALSE; - - opt_disable_fsync = !val; + opt_disable_fsync = !val; return TRUE; } @@ -109,16 +107,12 @@ parse_file_by_line (const char *path, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - g_autofree char *contents = NULL; - g_autoptr(GFile) file = NULL; - char **lines = NULL; + g_autofree char *contents = + glnx_file_get_contents_utf8_at (AT_FDCWD, path, NULL, cancellable, error); + if (!contents) + return FALSE; - file = g_file_new_for_path (path); - if (!g_file_load_contents (file, cancellable, &contents, NULL, NULL, error)) - goto out; - - lines = g_strsplit (contents, "\n", -1); + g_auto(GStrv) lines = g_strsplit (contents, "\n", -1); for (char **iter = lines; iter && *iter; iter++) { /* skip empty lines at least */ @@ -126,33 +120,41 @@ parse_file_by_line (const char *path, continue; if (!cb (*iter, cbdata, error)) - goto out; + return FALSE; } - ret = TRUE; -out: - g_strfreev (lines); - return ret; + return TRUE; } +struct CommitFilterData { + GHashTable *mode_adds; + GHashTable *mode_overrides; + GHashTable *skip_list; +}; + static gboolean handle_statoverride_line (const char *line, void *data, GError **error) { - GHashTable *files = data; - const char *spc; - guint mode_add; - - spc = strchr (line, ' '); + struct CommitFilterData *cf = data; + const char *spc = strchr (line, ' '); if (spc == NULL) - { - return glnx_throw (error, "Malformed statoverride file (no space found)"); - } + return glnx_throw (error, "Malformed statoverride file (no space found)"); + const char *fn = spc + 1; - mode_add = (guint32)(gint32)g_ascii_strtod (line, NULL); - g_hash_table_insert (files, g_strdup (spc + 1), - GUINT_TO_POINTER((gint32)mode_add)); + if (g_str_has_prefix (line, "=")) + { + guint mode_override = (guint32)(gint32)g_ascii_strtod (line+1, NULL); + g_hash_table_insert (cf->mode_overrides, g_strdup (fn), + GUINT_TO_POINTER((gint32)mode_override)); + } + else + { + guint mode_add = (guint32)(gint32)g_ascii_strtod (line, NULL); + g_hash_table_insert (cf->mode_adds, g_strdup (fn), + GUINT_TO_POINTER((gint32)mode_add)); + } return TRUE; } @@ -166,11 +168,6 @@ handle_skiplist_line (const char *line, return TRUE; } -struct CommitFilterData { - GHashTable *mode_adds; - GHashTable *skip_list; -}; - static OstreeRepoCommitFilterResult commit_filter (OstreeRepo *self, const char *path, @@ -179,6 +176,7 @@ commit_filter (OstreeRepo *self, { struct CommitFilterData *data = user_data; GHashTable *mode_adds = data->mode_adds; + GHashTable *mode_overrides = data->mode_overrides; GHashTable *skip_list = data->skip_list; gpointer value; @@ -195,6 +193,14 @@ commit_filter (OstreeRepo *self, current_mode | mode_add); g_hash_table_remove (mode_adds, path); } + else if (mode_overrides && g_hash_table_lookup_extended (mode_overrides, path, NULL, &value)) + { + guint current_fmt = g_file_info_get_attribute_uint32 (file_info, "unix::mode") & S_IFMT; + guint mode_override = GPOINTER_TO_UINT (value); + g_file_info_set_attribute_uint32 (file_info, "unix::mode", + current_fmt | mode_override); + g_hash_table_remove (mode_adds, path); + } if (skip_list && g_hash_table_contains (skip_list, path)) { @@ -213,14 +219,7 @@ commit_editor (OstreeRepo *repo, GCancellable *cancellable, GError **error) { - g_autofree char *input = NULL; - g_autofree char *output = NULL; - gboolean ret = FALSE; - g_autoptr(GString) bodybuf = NULL; - char **lines = NULL; - int i; - - input = g_strdup_printf ("\n" + g_autofree char *input = g_strdup_printf ("\n" "# Please enter the commit message for your changes. The first line will\n" "# become the subject, and the remainder the body. Lines starting\n" "# with '#' will be ignored, and an empty message aborts the commit." @@ -233,12 +232,13 @@ commit_editor (OstreeRepo *repo, *subject = NULL; *body = NULL; - output = ot_editor_prompt (repo, input, cancellable, error); + g_autofree char *output = ot_editor_prompt (repo, input, cancellable, error); if (output == NULL) - goto out; + return FALSE; - lines = g_strsplit (output, "\n", -1); - for (i = 0; lines[i] != NULL; i++) + g_auto(GStrv) lines = g_strsplit (output, "\n", -1); + g_autoptr(GString) bodybuf = NULL; + for (guint i = 0; lines[i] != NULL; i++) { g_strchomp (lines[i]); @@ -269,24 +269,15 @@ commit_editor (OstreeRepo *repo, } if (!*subject) - { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Aborting commit due to empty commit subject."); - goto out; - } + return glnx_throw (error, "Aborting commit due to empty commit subject."); if (bodybuf) { - *body = g_string_free (bodybuf, FALSE); + *body = g_string_free (g_steal_pointer (&bodybuf), FALSE); g_strchomp (*body); - bodybuf = NULL; } - ret = TRUE; - -out: - g_strfreev (lines); - return ret; + return TRUE; } static gboolean @@ -294,38 +285,22 @@ parse_keyvalue_strings (char **strings, GVariant **out_metadata, GError **error) { - gboolean ret = FALSE; - char **iter; - g_autoptr(GVariantBuilder) builder = NULL; + g_autoptr(GVariantBuilder) builder = + g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); - builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); - - for (iter = strings; *iter; iter++) + for (char ** iter = strings; *iter; iter++) { - const char *s; - const char *eq; - g_autofree char *key = NULL; - - s = *iter; - - eq = strchr (s, '='); + const char *s = *iter; + const char *eq = strchr (s, '='); if (!eq) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Missing '=' in KEY=VALUE metadata '%s'", s); - goto out; - } - - key = g_strndup (s, eq - s); + return glnx_throw (error, "Missing '=' in KEY=VALUE metadata '%s'", s); + g_autofree char *key = g_strndup (s, eq - s); g_variant_builder_add (builder, "{sv}", key, g_variant_new_string (eq + 1)); } - ret = TRUE; - *out_metadata = g_variant_builder_end (builder); - g_variant_ref_sink (*out_metadata); - out: - return ret; + *out_metadata = g_variant_ref_sink (g_variant_builder_end (builder)); + return TRUE; } gboolean @@ -344,6 +319,7 @@ ostree_builtin_commit (int argc, char **argv, GCancellable *cancellable, GError glnx_unref_object OstreeMutableTree *mtree = NULL; g_autofree char *tree_type = NULL; g_autoptr(GHashTable) mode_adds = NULL; + g_autoptr(GHashTable) mode_overrides = NULL; g_autoptr(GHashTable) skip_list = NULL; OstreeRepoCommitModifierFlags flags = 0; OstreeRepoCommitModifier *modifier = NULL; @@ -361,9 +337,10 @@ ostree_builtin_commit (int argc, char **argv, GCancellable *cancellable, GError if (opt_statoverride_file) { - mode_adds = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + filter_data.mode_adds = mode_adds = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + filter_data.mode_overrides = mode_overrides = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); if (!parse_file_by_line (opt_statoverride_file, handle_statoverride_line, - mode_adds, cancellable, error)) + &filter_data, cancellable, error)) goto out; } @@ -387,7 +364,7 @@ ostree_builtin_commit (int argc, char **argv, GCancellable *cancellable, GError &detached_metadata, error)) goto out; } - + if (!(opt_branch || opt_orphan)) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, diff --git a/src/ostree/ot-builtin-find-remotes.c b/src/ostree/ot-builtin-find-remotes.c new file mode 100644 index 00000000..344febb5 --- /dev/null +++ b/src/ostree/ot-builtin-find-remotes.c @@ -0,0 +1,283 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#include "config.h" + +#include "ot-main.h" +#include "ot-builtins.h" +#include "ostree.h" +#include "otutil.h" + +#include "ostree-remote-private.h" + +static gchar *opt_cache_dir = NULL; +static gboolean opt_disable_fsync = FALSE; +static gboolean opt_pull = FALSE; + +static GOptionEntry options[] = + { + { "cache-dir", 0, 0, G_OPTION_ARG_FILENAME, &opt_cache_dir, "Use custom cache dir", NULL }, + { "disable-fsync", 0, 0, G_OPTION_ARG_NONE, &opt_disable_fsync, "Do not invoke fsync()", NULL }, + { "pull", 0, 0, G_OPTION_ARG_NONE, &opt_pull, "Pull the updates after finding them", NULL }, + { NULL } + }; + +static gchar * +uint64_secs_to_iso8601 (guint64 secs) +{ + g_autoptr(GDateTime) dt = g_date_time_new_from_unix_utc (secs); + + if (dt != NULL) + return g_date_time_format (dt, "%FT%TZ"); + else + return g_strdup ("invalid"); +} + +static gchar * +format_ref_to_checksum (GHashTable *ref_to_checksum /* (element-type OstreeCollectionRef utf8) */, + const gchar *line_prefix) +{ + GHashTableIter iter; + const OstreeCollectionRef *ref; + const gchar *checksum; + g_autoptr(GString) out = NULL; + + g_hash_table_iter_init (&iter, ref_to_checksum); + out = g_string_new (""); + + while (g_hash_table_iter_next (&iter, (gpointer *) &ref, (gpointer *) &checksum)) + g_string_append_printf (out, "%s - (%s, %s) = %s\n", + line_prefix, ref->collection_id, ref->ref_name, + (checksum != NULL) ? checksum : "(not found)"); + + return g_string_free (g_steal_pointer (&out), FALSE); +} + +static gchar * +remote_get_uri (OstreeRemote *remote) +{ + g_autoptr(GError) error = NULL; + g_autofree gchar *uri = NULL; + + uri = g_key_file_get_string (remote->options, remote->group, "url", &error); + g_assert_no_error (error); + + return g_steal_pointer (&uri); +} + +/* Add each key from @keys_input to @set iff its value is non-%NULL. */ +static void +add_keys_to_set_if_non_null (GHashTable *set, + GHashTable *keys_input) +{ + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, keys_input); + + while (g_hash_table_iter_next (&iter, &key, &value)) + if (value != NULL) + g_hash_table_add (set, key); +} + +static void +get_result_cb (GObject *obj, + GAsyncResult *result, + gpointer user_data) +{ + GAsyncResult **result_out = user_data; + *result_out = g_object_ref (result); +} + +static void +collection_ref_free0 (OstreeCollectionRef *ref) +{ + if (ref == NULL) + return; + ostree_collection_ref_free (ref); +} + +/* TODO: Add a man page. */ +gboolean +ostree_builtin_find_remotes (int argc, + char **argv, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GOptionContext) context = NULL; + glnx_unref_object OstreeRepo *repo = NULL; + g_autoptr(GPtrArray) refs = NULL; /* (element-type OstreeCollectionRef) */ + glnx_unref_object OstreeAsyncProgress *progress = NULL; + gsize i; + g_autoptr(GAsyncResult) find_result = NULL, pull_result = NULL; + g_auto(OstreeRepoFinderResultv) results = NULL; + g_auto(GLnxConsoleRef) console = { 0, }; + g_autoptr(GHashTable) refs_found = NULL; /* set (element-type OstreeCollectionRef) */ + + context = g_option_context_new ("COLLECTION-ID REF [COLLECTION-ID REF...] - Find remotes to serve the given refs"); + + /* Parse options. */ + if (!ostree_option_context_parse (context, options, &argc, &argv, OSTREE_BUILTIN_FLAG_NONE, &repo, cancellable, error)) + return FALSE; + + if (!ostree_ensure_repo_writable (repo, error)) + return FALSE; + + if (argc < 3) + { + ot_util_usage_error (context, "At least one COLLECTION-ID REF pair must be specified", error); + return FALSE; + } + + if (argc % 2 == 0) + { + ot_util_usage_error (context, "Only complete COLLECTION-ID REF pairs may be specified", error); + return FALSE; + } + + if (opt_disable_fsync) + ostree_repo_set_disable_fsync (repo, TRUE); + + if (opt_cache_dir && + !ostree_repo_set_cache_dir (repo, AT_FDCWD, opt_cache_dir, cancellable, error)) + return FALSE; + + /* Read in the refs to search for remotes for. */ + refs = g_ptr_array_new_full (argc, (GDestroyNotify) collection_ref_free0); + + for (i = 1; i < argc; i += 2) + { + if (!ostree_validate_collection_id (argv[i], error) || + !ostree_validate_rev (argv[i + 1], error)) + return FALSE; + + g_ptr_array_add (refs, ostree_collection_ref_new (argv[i], argv[i + 1])); + } + + g_ptr_array_add (refs, NULL); + + /* Run the operation. */ + glnx_console_lock (&console); + + if (console.is_tty) + progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, &console); + + /* FIXME: Eventually some command line options for customising the finders + * list would be good. */ + ostree_repo_find_remotes_async (repo, + (const OstreeCollectionRef * const *) refs->pdata, + NULL /* no options */, + NULL /* default finders */, + progress, cancellable, + get_result_cb, &find_result); + + while (find_result == NULL) + g_main_context_iteration (NULL, TRUE); + + results = ostree_repo_find_remotes_finish (repo, find_result, error); + + if (results == NULL) + return FALSE; + + if (progress) + ostree_async_progress_finish (progress); + + /* Print results and work out which refs were not found. */ + refs_found = g_hash_table_new_full (ostree_collection_ref_hash, + ostree_collection_ref_equal, NULL, NULL); + + for (i = 0; results[i] != NULL; i++) + { + g_autofree gchar *uri = NULL; + g_autofree gchar *refs_string = NULL; + g_autofree gchar *last_modified_string = NULL; + + uri = remote_get_uri (results[i]->remote); + refs_string = format_ref_to_checksum (results[i]->ref_to_checksum, " "); + add_keys_to_set_if_non_null (refs_found, results[i]->ref_to_checksum); + + if (results[i]->summary_last_modified > 0) + last_modified_string = uint64_secs_to_iso8601 (results[i]->summary_last_modified); + else + last_modified_string = g_strdup ("unknown"); + + g_print ("Result %" G_GSIZE_FORMAT ": %s\n" + " - Finder: %s\n" + " - Keyring: %s\n" + " - Priority: %d\n" + " - Summary last modified: %s\n" + " - Refs:\n" + "%s\n", + i, uri, G_OBJECT_TYPE_NAME (results[i]->finder), results[i]->remote->keyring, + results[i]->priority, last_modified_string, refs_string); + } + + if (results[0] == NULL) + { + g_print ("No results.\n"); + return TRUE; + } + + g_print ("%u/%u refs were found.\n", g_hash_table_size (refs_found), refs->len - 1); + + /* Print out the refs which weren’t found. */ + if (g_hash_table_size (refs_found) != refs->len - 1 /* NULL terminator */) + { + g_print ("Refs not found in any remote:\n"); + + for (i = 0; i < refs->len && refs->pdata[i] != NULL; i++) + { + const OstreeCollectionRef *ref = g_ptr_array_index (refs, i); + if (!g_hash_table_contains (refs_found, ref)) + g_print (" - (%s, %s)\n", ref->collection_id, ref->ref_name); + } + } + + /* Does the user want us to pull the updates? */ + if (!opt_pull) + return TRUE; + + /* Run the pull operation. */ + if (console.is_tty) + progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, &console); + + ostree_repo_pull_from_remotes_async (repo, + (const OstreeRepoFinderResult * const *) results, + NULL, /* no options */ + progress, cancellable, + get_result_cb, &pull_result); + + while (pull_result == NULL) + g_main_context_iteration (NULL, TRUE); + + if (!ostree_repo_pull_from_remotes_finish (repo, pull_result, error)) + return FALSE; + + if (progress) + ostree_async_progress_finish (progress); + + /* The pull operation fails if any of the refs can’t be pulled. */ + g_print ("Pulled %u/%u refs successfully.\n", refs->len - 1, refs->len - 1); + + return TRUE; +} diff --git a/src/ostree/ot-builtin-fsck.c b/src/ostree/ot-builtin-fsck.c index 7519d6db..66f5536d 100644 --- a/src/ostree/ot-builtin-fsck.c +++ b/src/ostree/ot-builtin-fsck.c @@ -47,13 +47,12 @@ load_and_fsck_one_object (OstreeRepo *repo, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; gboolean missing = FALSE; g_autoptr(GVariant) metadata = NULL; g_autoptr(GInputStream) input = NULL; g_autoptr(GFileInfo) file_info = NULL; g_autoptr(GVariant) xattrs = NULL; - GError *temp_error = NULL; + g_autoptr(GError) temp_error = NULL; if (OSTREE_OBJECT_TYPE_IS_META (objtype)) { @@ -69,8 +68,8 @@ load_and_fsck_one_object (OstreeRepo *repo, } else { - g_prefix_error (error, "Loading metadata object %s: ", checksum); - goto out; + g_propagate_error (error, g_steal_pointer (&temp_error)); + return glnx_prefix_error (error, "Loading metadata object %s", checksum); } } else @@ -78,28 +77,19 @@ load_and_fsck_one_object (OstreeRepo *repo, if (objtype == OSTREE_OBJECT_TYPE_COMMIT) { if (!ostree_validate_structureof_commit (metadata, error)) - { - g_prefix_error (error, "While validating commit metadata '%s': ", checksum); - goto out; - } + return glnx_prefix_error (error, "While validating commit metadata '%s'", checksum); } else if (objtype == OSTREE_OBJECT_TYPE_DIR_TREE) { if (!ostree_validate_structureof_dirtree (metadata, error)) - { - g_prefix_error (error, "While validating directory tree '%s': ", checksum); - goto out; - } + return glnx_prefix_error (error, "While validating directory tree '%s'", checksum); } else if (objtype == OSTREE_OBJECT_TYPE_DIR_META) { if (!ostree_validate_structureof_dirmeta (metadata, error)) - { - g_prefix_error (error, "While validating directory metadata '%s': ", checksum); - goto out; - } + return glnx_prefix_error (error, "While validating directory metadata '%s'", checksum); } - + input = g_memory_input_stream_new_from_data (g_variant_get_data (metadata), g_variant_get_size (metadata), NULL); @@ -122,19 +112,15 @@ load_and_fsck_one_object (OstreeRepo *repo, } else { - *error = temp_error; - g_prefix_error (error, "Loading file object %s: ", checksum); - goto out; + g_propagate_error (error, g_steal_pointer (&temp_error)); + return glnx_prefix_error (error, "Loading file object %s", checksum); } } else { mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); if (!ostree_validate_structureof_file_mode (mode, error)) - { - g_prefix_error (error, "While validating file '%s': ", checksum); - goto out; - } + return glnx_prefix_error (error, "While validating file '%s'", checksum); } } @@ -150,8 +136,8 @@ load_and_fsck_one_object (OstreeRepo *repo, if (!ostree_checksum_file_from_input (file_info, xattrs, input, objtype, &computed_csum, cancellable, error)) - goto out; - + return FALSE; + tmp_checksum = ostree_checksum_from_bytes (computed_csum); if (strcmp (checksum, tmp_checksum) != 0) { @@ -165,16 +151,11 @@ load_and_fsck_one_object (OstreeRepo *repo, *out_found_corruption = TRUE; } else - { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, msg); - goto out; - } + return glnx_throw (error, "%s", msg); } } - ret = TRUE; - out: - return ret; + return TRUE; } static gboolean @@ -184,16 +165,10 @@ fsck_reachable_objects_from_commits (OstreeRepo *repo, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; + g_autoptr(GHashTable) reachable_objects = ostree_repo_traverse_new_reachable (); + GHashTableIter hash_iter; gpointer key, value; - g_autoptr(GHashTable) reachable_objects = NULL; - guint i; - guint mod; - guint count; - - reachable_objects = ostree_repo_traverse_new_reachable (); - g_hash_table_iter_init (&hash_iter, commits); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { @@ -207,12 +182,12 @@ fsck_reachable_objects_from_commits (OstreeRepo *repo, if (!ostree_repo_traverse_commit_union (repo, checksum, 0, reachable_objects, cancellable, error)) - goto out; + return FALSE; } - count = g_hash_table_size (reachable_objects); - mod = count / 10; - i = 0; + const guint count = g_hash_table_size (reachable_objects); + const guint mod = count / 10; + guint i = 0; g_hash_table_iter_init (&hash_iter, reachable_objects); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { @@ -224,44 +199,37 @@ fsck_reachable_objects_from_commits (OstreeRepo *repo, if (!load_and_fsck_one_object (repo, checksum, objtype, out_found_corruption, cancellable, error)) - goto out; + return FALSE; if (mod == 0 || (i % mod == 0)) g_print ("%u/%u objects\n", i + 1, count); i++; } - ret = TRUE; - out: - return ret; + return TRUE; } gboolean ostree_builtin_fsck (int argc, char **argv, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - g_autoptr(GOptionContext) context = NULL; glnx_unref_object OstreeRepo *repo = NULL; - GHashTableIter hash_iter; - gpointer key, value; gboolean found_corruption = FALSE; - guint n_partial = 0; - g_autoptr(GHashTable) all_refs = NULL; - g_autoptr(GHashTable) objects = NULL; - g_autoptr(GHashTable) commits = NULL; - g_autoptr(GPtrArray) tombstones = NULL; - context = g_option_context_new ("- Check the repository for consistency"); + g_autoptr(GOptionContext) context = g_option_context_new ("- Check the repository for consistency"); if (!ostree_option_context_parse (context, options, &argc, &argv, OSTREE_BUILTIN_FLAG_NONE, &repo, cancellable, error)) - goto out; + return FALSE; if (!opt_quiet) g_print ("Validating refs...\n"); /* Validate that the commit for each ref is available */ + g_autoptr(GHashTable) all_refs = NULL; if (!ostree_repo_list_refs (repo, NULL, &all_refs, cancellable, error)) return FALSE; + + GHashTableIter hash_iter; + gpointer key, value; g_hash_table_iter_init (&hash_iter, all_refs); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { @@ -270,27 +238,49 @@ ostree_builtin_fsck (int argc, char **argv, GCancellable *cancellable, GError ** g_autoptr(GVariant) commit = NULL; if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, checksum, &commit, error)) - { - g_prefix_error (error, "Loading commit for ref %s: ", refname); - goto out; - } + return glnx_prefix_error (error, "Loading commit for ref %s", refname); } +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API + if (!opt_quiet) + g_print ("Validating refs in collections...\n"); + + g_autoptr(GHashTable) all_collection_refs = NULL; /* (element-type OstreeCollectionRef utf8) */ + if (!ostree_repo_list_collection_refs (repo, NULL, &all_collection_refs, + cancellable, error)) + return FALSE; + + g_hash_table_iter_init (&hash_iter, all_collection_refs); + while (g_hash_table_iter_next (&hash_iter, &key, &value)) + { + const OstreeCollectionRef *ref = key; + const char *checksum = value; + g_autoptr(GVariant) commit = NULL; + if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, + checksum, &commit, error)) + return glnx_prefix_error (error, "Loading commit for ref (%s, %s)", + ref->collection_id, ref->ref_name); + } +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ + if (!opt_quiet) g_print ("Enumerating objects...\n"); + g_autoptr(GHashTable) objects = NULL; if (!ostree_repo_list_objects (repo, OSTREE_REPO_LIST_OBJECTS_ALL, &objects, cancellable, error)) - goto out; + return FALSE; - commits = g_hash_table_new_full (ostree_hash_object_name, g_variant_equal, - (GDestroyNotify)g_variant_unref, NULL); - - g_hash_table_iter_init (&hash_iter, objects); + g_autoptr(GHashTable) commits = g_hash_table_new_full (ostree_hash_object_name, g_variant_equal, + (GDestroyNotify)g_variant_unref, NULL); + + g_autoptr(GPtrArray) tombstones = NULL; if (opt_add_tombstones) tombstones = g_ptr_array_new_with_free_func (g_free); + guint n_partial = 0; + g_hash_table_iter_init (&hash_iter, objects); while (g_hash_table_iter_next (&hash_iter, &key, &value)) { GVariant *serialized_key = key; @@ -304,7 +294,7 @@ ostree_builtin_fsck (int argc, char **argv, GCancellable *cancellable, GError ** if (objtype == OSTREE_OBJECT_TYPE_COMMIT) { if (!ostree_repo_load_commit (repo, checksum, &commit, &commitstate, error)) - goto out; + return FALSE; if (opt_add_tombstones) { @@ -324,7 +314,7 @@ ostree_builtin_fsck (int argc, char **argv, GCancellable *cancellable, GError ** else { g_propagate_error (error, local_error); - goto out; + return FALSE; } } } @@ -345,7 +335,7 @@ ostree_builtin_fsck (int argc, char **argv, GCancellable *cancellable, GError ** if (!fsck_reachable_objects_from_commits (repo, commits, &found_corruption, cancellable, error)) - goto out; + return FALSE; if (opt_add_tombstones) { @@ -353,14 +343,14 @@ ostree_builtin_fsck (int argc, char **argv, GCancellable *cancellable, GError ** if (tombstones->len) { if (!ot_enable_tombstone_commits (repo, error)) - goto out; + return FALSE; } for (i = 0; i < tombstones->len; i++) { const char *checksum = tombstones->pdata[i]; g_print ("Adding tombstone for commit %s\n", checksum); if (!ostree_repo_delete_object (repo, OSTREE_OBJECT_TYPE_COMMIT, checksum, cancellable, error)) - goto out; + return FALSE; } } else if (n_partial > 0) @@ -369,13 +359,7 @@ ostree_builtin_fsck (int argc, char **argv, GCancellable *cancellable, GError ** } if (found_corruption) - { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Repository corruption encountered"); - goto out; - } + return glnx_throw (error, "Repository corruption encountered"); - ret = TRUE; - out: - return ret; + return TRUE; } diff --git a/src/ostree/ot-builtin-init.c b/src/ostree/ot-builtin-init.c index 8180a8ae..0dabd458 100644 --- a/src/ostree/ot-builtin-init.c +++ b/src/ostree/ot-builtin-init.c @@ -27,9 +27,16 @@ #include "ostree.h" static char *opt_mode = "bare"; +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API +static char *opt_collection_id = NULL; +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ static GOptionEntry options[] = { { "mode", 0, 0, G_OPTION_ARG_STRING, &opt_mode, "Initialize repository in given mode (bare, archive-z2)", NULL }, +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API + { "collection-id", 0, 0, G_OPTION_ARG_STRING, &opt_collection_id, + "Globally unique ID for this repository as an collection of refs for redistribution to other repositories", "COLLECTION-ID" }, +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ { NULL } }; @@ -48,6 +55,10 @@ ostree_builtin_init (int argc, char **argv, GCancellable *cancellable, GError ** if (!ostree_repo_mode_from_string (opt_mode, &mode, error)) goto out; +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API + if (!ostree_repo_set_collection_id (repo, opt_collection_id, error)) + goto out; +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ if (!ostree_repo_create (repo, mode, NULL, error)) goto out; diff --git a/src/ostree/ot-builtin-prune.c b/src/ostree/ot-builtin-prune.c index 853c051f..8c595b73 100644 --- a/src/ostree/ot-builtin-prune.c +++ b/src/ostree/ot-builtin-prune.c @@ -50,11 +50,15 @@ static GOptionEntry options[] = { static gboolean delete_commit (OstreeRepo *repo, const char *commit_to_delete, GCancellable *cancellable, GError **error) { - g_autoptr(GHashTable) refs = NULL; + g_autoptr(GHashTable) refs = NULL; /* (element-type utf8 utf8) */ +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API + g_autoptr(GHashTable) collection_refs = NULL; /* (element-type OstreeCollectionRef utf8) */ +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ GHashTableIter hashiter; gpointer hashkey, hashvalue; gboolean ret = FALSE; + /* Check refs which are not in a collection. */ if (!ostree_repo_list_refs (repo, NULL, &refs, cancellable, error)) goto out; @@ -71,6 +75,26 @@ delete_commit (OstreeRepo *repo, const char *commit_to_delete, GCancellable *can } } +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API + /* And check refs which *are* in a collection. */ + if (!ostree_repo_list_collection_refs (repo, NULL, &collection_refs, cancellable, error)) + goto out; + + g_hash_table_iter_init (&hashiter, collection_refs); + while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue)) + { + const OstreeCollectionRef *ref = hashkey; + const char *commit = hashvalue; + if (g_strcmp0 (commit_to_delete, commit) == 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Commit '%s' is referenced by (%s, %s)", + commit_to_delete, ref->collection_id, ref->ref_name); + goto out; + } + } +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ + if (!ot_enable_tombstone_commits (repo, error)) goto out; @@ -249,6 +273,7 @@ ostree_builtin_prune (int argc, char **argv, GCancellable *cancellable, GError * } /* We start from the refs */ + /* FIXME: Do we also want to look at ostree_repo_list_collection_refs()? */ if (!ostree_repo_list_refs (repo, NULL, &all_refs, cancellable, error)) return FALSE; diff --git a/src/ostree/ot-builtin-pull-local.c b/src/ostree/ot-builtin-pull-local.c index b19a4c6a..66e189bc 100644 --- a/src/ostree/ot-builtin-pull-local.c +++ b/src/ostree/ot-builtin-pull-local.c @@ -111,6 +111,8 @@ ostree_builtin_pull_local (int argc, char **argv, GCancellable *cancellable, GEr if (!ostree_repo_open (src_repo, cancellable, error)) goto out; + /* FIXME: This should grow support for pulling refs from refs/mirrors on + * a local repository, using ostree_repo_list_collection_refs(). */ if (!ostree_repo_list_refs (src_repo, NULL, &refs_to_clone, cancellable, error)) goto out; diff --git a/src/ostree/ot-builtin-pull.c b/src/ostree/ot-builtin-pull.c index bd3ac115..7898e107 100644 --- a/src/ostree/ot-builtin-pull.c +++ b/src/ostree/ot-builtin-pull.c @@ -41,6 +41,7 @@ static char* opt_cache_dir; static int opt_depth = 0; static int opt_frequency = 0; static char* opt_url; +static char** opt_localcache_repos; static GOptionEntry options[] = { { "commit-metadata-only", 0, 0, G_OPTION_ARG_NONE, &opt_commit_only, "Fetch only the commit metadata", NULL }, @@ -57,6 +58,7 @@ static GOptionEntry options[] = { { "url", 0, 0, G_OPTION_ARG_STRING, &opt_url, "Pull objects from this URL instead of the one from the remote config", NULL }, { "http-header", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_http_headers, "Add NAME=VALUE as HTTP header to all requests", "NAME=VALUE" }, { "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" }, { NULL } }; @@ -125,6 +127,13 @@ dry_run_console_progress_changed (OstreeAsyncProgress *progress, g_print ("%s\n", buf->str); } +static void +noninteractive_console_progress_changed (OstreeAsyncProgress *progress, + gpointer user_data) +{ + /* We do nothing here - we just want the final status */ +} + gboolean ostree_builtin_pull (int argc, char **argv, GCancellable *cancellable, GError **error) { @@ -274,6 +283,9 @@ ostree_builtin_pull (int argc, char **argv, GCancellable *cancellable, GError ** if (override_commit_ids) g_variant_builder_add (&builder, "{s@v}", "override-commit-ids", g_variant_new_variant (g_variant_new_strv ((const char*const*)override_commit_ids->pdata, override_commit_ids->len))); + if (opt_localcache_repos) + g_variant_builder_add (&builder, "{s@v}", "localcache-repos", + g_variant_new_variant (g_variant_new_strv ((const char*const*)opt_localcache_repos, -1))); if (opt_http_headers) { @@ -302,6 +314,8 @@ ostree_builtin_pull (int argc, char **argv, GCancellable *cancellable, GError ** { if (console.is_tty) progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, &console); + else + progress = ostree_async_progress_new_and_connect (noninteractive_console_progress_changed, &console); } else { @@ -321,8 +335,15 @@ ostree_builtin_pull (int argc, char **argv, GCancellable *cancellable, GError ** progress, cancellable, error)) goto out; - if (progress) - ostree_async_progress_finish (progress); + if (!console.is_tty && !opt_dry_run) + { + g_assert (progress); + const char *status = ostree_async_progress_get_status (progress); + if (status) + g_print ("%s\n", status); + } + + ostree_async_progress_finish (progress); if (opt_dry_run) g_assert (printed_console_progress); diff --git a/src/ostree/ot-builtin-refs.c b/src/ostree/ot-builtin-refs.c index c7ea9a95..19420842 100644 --- a/src/ostree/ot-builtin-refs.c +++ b/src/ostree/ot-builtin-refs.c @@ -29,14 +29,106 @@ static gboolean opt_delete; static gboolean opt_list; static char *opt_create; +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API +static gboolean opt_collections; +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ static GOptionEntry options[] = { { "delete", 0, 0, G_OPTION_ARG_NONE, &opt_delete, "Delete refs which match PREFIX, rather than listing them", NULL }, { "list", 0, 0, G_OPTION_ARG_NONE, &opt_list, "Do not remove the prefix from the refs", NULL }, { "create", 0, 0, G_OPTION_ARG_STRING, &opt_create, "Create a new ref for an existing commit", "NEWREF" }, +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API + { "collections", 'c', 0, G_OPTION_ARG_NONE, &opt_collections, "Enable listing collection IDs for refs", NULL }, +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ { NULL } }; +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API +static gboolean +do_ref_with_collections (OstreeRepo *repo, + const char *refspec_prefix, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GHashTable) refs = NULL; /* (element-type OstreeCollectionRef utf8) */ + GHashTableIter hashiter; + gpointer hashkey, hashvalue; + gboolean ret = FALSE; + + if (!ostree_repo_list_collection_refs (repo, + (!opt_create) ? refspec_prefix : NULL, + &refs, cancellable, error)) + goto out; + + if (!opt_delete && !opt_create) + { + g_hash_table_iter_init (&hashiter, refs); + while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue)) + { + const OstreeCollectionRef *ref = hashkey; + g_print ("(%s, %s)\n", ref->collection_id, ref->ref_name); + } + } + else if (opt_create) + { + g_autofree char *checksum = NULL; + g_autofree char *checksum_existing = NULL; + + if (!ostree_repo_resolve_rev_ext (repo, opt_create, TRUE, OSTREE_REPO_RESOLVE_REV_EXT_NONE, &checksum_existing, error)) + { + if (g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY)) + { + /* A folder exists with the specified ref name, + * which is handled by _ostree_repo_write_ref */ + g_clear_error (error); + } + else goto out; + } + + if (checksum_existing != NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "--create specified but ref %s already exists", opt_create); + goto out; + } + + if (!ostree_repo_resolve_rev (repo, refspec_prefix, FALSE, &checksum, error)) + goto out; + + /* This is technically an abuse of the refspec syntax: collection IDs + * should not be treated like remote names. */ + g_auto(GStrv) parts = g_strsplit (opt_create, ":", 2); + const char *collection_id = parts[0]; + const char *ref_name = parts[1]; + if (!ostree_validate_collection_id (collection_id, error)) + goto out; + if (!ostree_validate_rev (ref_name, error)) + goto out; + + const OstreeCollectionRef ref = { (gchar *) collection_id, (gchar *) ref_name }; + if (!ostree_repo_set_collection_ref_immediate (repo, &ref, checksum, + cancellable, error)) + goto out; + } + else + /* delete */ + { + g_hash_table_iter_init (&hashiter, refs); + while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue)) + { + const OstreeCollectionRef *ref = hashkey; + + if (!ostree_repo_set_collection_ref_immediate (repo, ref, NULL, + cancellable, error)) + goto out; + } + } + ret = TRUE; + out: + return ret; +} +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ + static gboolean do_ref (OstreeRepo *repo, const char *refspec_prefix, GCancellable *cancellable, GError **error) { g_autoptr(GHashTable) refs = NULL; @@ -44,6 +136,11 @@ static gboolean do_ref (OstreeRepo *repo, const char *refspec_prefix, GCancellab gpointer hashkey, hashvalue; gboolean ret = FALSE; +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API + if (opt_collections) + return do_ref_with_collections (repo, refspec_prefix, cancellable, error); +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ + if (opt_delete || opt_list) { if (!ostree_repo_list_refs_ext (repo, refspec_prefix, &refs, OSTREE_REPO_LIST_REFS_EXT_NONE, diff --git a/src/ostree/ot-builtin-reset.c b/src/ostree/ot-builtin-reset.c index cab579ae..344d692c 100644 --- a/src/ostree/ot-builtin-reset.c +++ b/src/ostree/ot-builtin-reset.c @@ -45,6 +45,7 @@ ostree_builtin_reset (int argc, const char *target = NULL; g_autofree char *checksum = NULL; + /* FIXME: Add support for collection–refs. */ context = g_option_context_new ("REF COMMIT - Reset a REF to a previous COMMIT"); if (!ostree_option_context_parse (context, options, &argc, &argv, OSTREE_BUILTIN_FLAG_NONE, &repo, cancellable, error)) diff --git a/src/ostree/ot-builtin-summary.c b/src/ostree/ot-builtin-summary.c index 9055d972..f2e687ec 100644 --- a/src/ostree/ot-builtin-summary.c +++ b/src/ostree/ot-builtin-summary.c @@ -30,6 +30,7 @@ static gboolean opt_update, opt_view, opt_raw; static char **opt_key_ids; static char *opt_gpg_homedir; +static char **opt_metadata; static GOptionEntry options[] = { { "update", 'u', 0, G_OPTION_ARG_NONE, &opt_update, "Update the summary", NULL }, @@ -37,9 +38,44 @@ static GOptionEntry options[] = { { "raw", 0, 0, G_OPTION_ARG_NONE, &opt_raw, "View the raw bytes of the summary file", NULL }, { "gpg-sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_key_ids, "GPG Key ID to sign the summary with", "KEY-ID"}, { "gpg-homedir", 0, 0, G_OPTION_ARG_FILENAME, &opt_gpg_homedir, "GPG Homedir to use when looking for keyrings", "HOMEDIR"}, + { "add-metadata", 'm', 0, G_OPTION_ARG_STRING_ARRAY, &opt_metadata, "Additional metadata field to add to the summary", "KEY=VALUE" }, { NULL } }; +/* Take arguments of the form KEY=VALUE and put them into an a{sv} variant. The + * value arguments must be parsable using g_variant_parse(). */ +static GVariant * +build_additional_metadata (const char * const *args, + GError **error) +{ + g_autoptr(GVariantBuilder) builder = NULL; + + builder = g_variant_builder_new (G_VARIANT_TYPE_VARDICT); + + for (gsize i = 0; args[i] != NULL; i++) + { + const gchar *equals = strchr (args[i], '='); + g_autofree gchar *key = NULL; + const gchar *value_str; + g_autoptr(GVariant) value = NULL; + + if (equals == NULL) + return glnx_null_throw (error, + "Missing '=' in KEY=VALUE metadata '%s'", args[i]); + + key = g_strndup (args[i], equals - args[i]); + value_str = equals + 1; + + value = g_variant_parse (NULL, value_str, NULL, NULL, error); + if (value == NULL) + return glnx_prefix_error_null (error, "Error parsing variant ‘%s’: ", value_str); + + g_variant_builder_add (builder, "{sv}", key, value); + } + + return g_variant_ref_sink (g_variant_builder_end (builder)); +} + gboolean ostree_builtin_summary (int argc, char **argv, GCancellable *cancellable, GError **error) { @@ -55,10 +91,19 @@ ostree_builtin_summary (int argc, char **argv, GCancellable *cancellable, GError if (opt_update) { + g_autoptr(GVariant) additional_metadata = NULL; + if (!ostree_ensure_repo_writable (repo, error)) goto out; - if (!ostree_repo_regenerate_summary (repo, NULL, cancellable, error)) + if (opt_metadata != NULL) + { + additional_metadata = build_additional_metadata ((const char * const *) opt_metadata, error); + if (additional_metadata == NULL) + goto out; + } + + if (!ostree_repo_regenerate_summary (repo, additional_metadata, cancellable, error)) goto out; if (opt_key_ids) diff --git a/src/ostree/ot-builtins.h b/src/ostree/ot-builtins.h index 1c862925..9c648458 100644 --- a/src/ostree/ot-builtins.h +++ b/src/ostree/ot-builtins.h @@ -22,6 +22,8 @@ #pragma once +#include "config.h" + #include "ostree.h" G_BEGIN_DECLS @@ -36,6 +38,9 @@ BUILTINPROTO(checksum); BUILTINPROTO(commit); BUILTINPROTO(diff); BUILTINPROTO(export); +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API +BUILTINPROTO(find_remotes); +#endif BUILTINPROTO(gpg_sign); BUILTINPROTO(init); BUILTINPROTO(log); diff --git a/src/ostree/ot-dump.c b/src/ostree/ot-dump.c index e6b8859b..83eb307f 100644 --- a/src/ostree/ot-dump.c +++ b/src/ostree/ot-dump.c @@ -176,7 +176,8 @@ ot_dump_object (OstreeObjectType objtype, } static void -dump_summary_ref (const char *ref_name, +dump_summary_ref (const char *collection_id, + const char *ref_name, guint64 commit_size, GVariant *csum_v, GVariantIter *metadata) @@ -187,7 +188,10 @@ dump_summary_ref (const char *ref_name, GVariant *value; char *key; - g_print ("* %s\n", ref_name); + if (collection_id == NULL) + g_print ("* %s\n", ref_name); + else + g_print ("* (%s, %s)\n", collection_id, ref_name); size = g_format_size (commit_size); g_print (" Latest Commit (%s):\n", size); @@ -229,6 +233,39 @@ dump_summary_ref (const char *ref_name, } } +static void +dump_summary_refs (const gchar *collection_id, + GVariant *refs) +{ + GVariantIter iter; + GVariant *value; + + g_variant_iter_init (&iter, refs); + + while ((value = g_variant_iter_next_value (&iter)) != NULL) + { + const char *ref_name = NULL; + + g_variant_get_child (value, 0, "&s", &ref_name); + + if (ref_name != NULL) + { + g_autoptr(GVariant) csum_v = NULL; + g_autoptr(GVariantIter) metadata = NULL; + guint64 commit_size; + + g_variant_get_child (value, 1, "(t@aya{sv})", + &commit_size, &csum_v, &metadata); + + dump_summary_ref (collection_id, ref_name, commit_size, csum_v, metadata); + + g_print ("\n"); + } + + g_variant_unref (value); + } +} + void ot_dump_summary_bytes (GBytes *summary_bytes, OstreeDumpFlags flags) @@ -254,31 +291,26 @@ ot_dump_summary_bytes (GBytes *summary_bytes, refs = g_variant_get_child_value (summary, 0); exts = g_variant_get_child_value (summary, 1); - g_variant_iter_init (&iter, refs); + /* Print the refs, including those with a collection ID specified. */ + const gchar *main_collection_id; + g_autoptr(GVariant) collection_map = NULL; + const gchar *collection_id; - while ((value = g_variant_iter_next_value (&iter)) != NULL) + if (!g_variant_lookup (exts, OSTREE_SUMMARY_COLLECTION_ID, "&s", &main_collection_id)) + main_collection_id = NULL; + + dump_summary_refs (main_collection_id, refs); + + collection_map = g_variant_lookup_value (exts, OSTREE_SUMMARY_COLLECTION_MAP, G_VARIANT_TYPE ("a{sa(s(taya{sv}))}")); + if (collection_map != NULL) { - const char *ref_name = NULL; + g_variant_iter_init (&iter, collection_map); - g_variant_get_child (value, 0, "&s", &ref_name); - - if (ref_name != NULL) - { - g_autoptr(GVariant) csum_v = NULL; - g_autoptr(GVariantIter) metadata = NULL; - guint64 commit_size; - - g_variant_get_child (value, 1, "(t@aya{sv})", - &commit_size, &csum_v, &metadata); - - dump_summary_ref (ref_name, commit_size, csum_v, metadata); - - g_print ("\n"); - } - - g_variant_unref (value); + while (g_variant_iter_loop (&iter, "{&s@a(s(taya{sv}))}", &collection_id, &refs)) + dump_summary_refs (collection_id, refs); } + /* Print out the additional metadata. */ g_variant_iter_init (&iter, exts); while (g_variant_iter_loop (&iter, "{sv}", &key, &value)) @@ -301,6 +333,16 @@ ot_dump_summary_bytes (GBytes *summary_bytes, pretty_key = "Expires"; value_str = uint64_secs_to_iso8601 (GUINT64_FROM_BE (g_variant_get_uint64 (value))); } + else if (g_strcmp0 (key, OSTREE_SUMMARY_COLLECTION_ID) == 0) + { + pretty_key = "Collection ID"; + value_str = g_variant_dup_string (value, NULL); + } + else if (g_strcmp0 (key, OSTREE_SUMMARY_COLLECTION_MAP) == 0) + { + pretty_key = "Collection Map"; + value_str = g_strdup ("(printed above)"); + } else { value_str = g_variant_print (value, FALSE); diff --git a/src/ostree/ot-main.c b/src/ostree/ot-main.c index 9aca8287..40d77f5f 100644 --- a/src/ostree/ot-main.c +++ b/src/ostree/ot-main.c @@ -242,6 +242,9 @@ ostree_option_context_parse (GOptionContext *context, g_print (" Version: %s\n", PACKAGE_VERSION); if (strlen (OSTREE_GITREV) > 0) g_print (" Git: %s\n", OSTREE_GITREV); +#ifdef BUILDOPT_IS_DEVEL_BUILD + g_print (" DevelBuild: yes\n"); +#endif g_print (" Features:\n"); for (char **iter = features; iter && *iter; iter++) g_print (" - %s\n", *iter); diff --git a/src/ostree/ot-remote-builtin-add.c b/src/ostree/ot-remote-builtin-add.c index 3e3aeda9..db115efd 100644 --- a/src/ostree/ot-remote-builtin-add.c +++ b/src/ostree/ot-remote-builtin-add.c @@ -31,6 +31,9 @@ static gboolean opt_no_gpg_verify; static gboolean opt_if_not_exists; static char *opt_gpg_import; static char *opt_contenturl; +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API +static char *opt_collection_id; +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ static GOptionEntry option_entries[] = { { "set", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_set, "Set config option KEY=VALUE for remote", "KEY=VALUE" }, @@ -38,6 +41,10 @@ static GOptionEntry option_entries[] = { { "if-not-exists", 0, 0, G_OPTION_ARG_NONE, &opt_if_not_exists, "Do nothing if the provided remote exists", NULL }, { "gpg-import", 0, 0, G_OPTION_ARG_FILENAME, &opt_gpg_import, "Import GPG key from FILE", "FILE" }, { "contenturl", 0, 0, G_OPTION_ARG_STRING, &opt_contenturl, "Use URL when fetching content", "URL" }, +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API + { "collection-id", 0, 0, G_OPTION_ARG_STRING, &opt_collection_id, + "Globally unique ID for this repository as an collection of refs for redistribution to other repositories", "COLLECTION-ID" }, +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ { NULL } }; @@ -110,6 +117,12 @@ ot_remote_builtin_add (int argc, char **argv, GCancellable *cancellable, GError "gpg-verify", g_variant_new_variant (g_variant_new_boolean (FALSE))); +#ifdef OSTREE_ENABLE_EXPERIMENTAL_API + if (opt_collection_id != NULL) + g_variant_builder_add (optbuilder, "{s@v}", "collection-id", + g_variant_new_variant (g_variant_new_take_string (g_steal_pointer (&opt_collection_id)))); +#endif /* OSTREE_ENABLE_EXPERIMENTAL_API */ + options = g_variant_ref_sink (g_variant_builder_end (optbuilder)); if (!ostree_repo_remote_change (repo, NULL, diff --git a/src/ostree/ot-remote-cookie-util.c b/src/ostree/ot-remote-cookie-util.c index 9e152e18..a33b38bf 100644 --- a/src/ostree/ot-remote-cookie-util.c +++ b/src/ostree/ot-remote-cookie-util.c @@ -202,7 +202,7 @@ ot_delete_cookie_at (int dfd, const char *jar_path, { gboolean found = FALSE; #ifdef HAVE_LIBCURL - g_auto(OtTmpfile) tmpf = { 0, }; + g_auto(GLnxTmpfile) tmpf = { 0, }; g_autofree char *dnbuf = NULL; const char *dn = NULL; g_autoptr(OtCookieParser) parser = NULL; @@ -212,8 +212,8 @@ ot_delete_cookie_at (int dfd, const char *jar_path, dnbuf = g_strdup (jar_path); dn = dirname (dnbuf); - if (!ot_open_tmpfile_linkable_at (AT_FDCWD, dn, O_WRONLY | O_CLOEXEC, - &tmpf, error)) + if (!glnx_open_tmpfile_linkable_at (AT_FDCWD, dn, O_WRONLY | O_CLOEXEC, + &tmpf, error)) return FALSE; while (ot_parse_cookies_next (parser)) @@ -232,9 +232,9 @@ ot_delete_cookie_at (int dfd, const char *jar_path, return glnx_throw_errno_prefix (error, "write"); } - if (!ot_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_REPLACE, - AT_FDCWD, jar_path, - error)) + if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_REPLACE, + AT_FDCWD, jar_path, + error)) return FALSE; #else GSList *cookies; diff --git a/tests/basic-test.sh b/tests/basic-test.sh index d9b20938..07bbeddf 100644 --- a/tests/basic-test.sh +++ b/tests/basic-test.sh @@ -342,8 +342,11 @@ cd ${test_tmpdir} cat > test-statoverride.txt < a/readable-only +chmod 664 a/readable-only $OSTREE commit ${COMMIT_ARGS} -b test2-override -s "with statoverride" --statoverride=../test-statoverride.txt cd ${test_tmpdir} $OSTREE checkout test2-override checkout-test2-override @@ -354,6 +357,7 @@ else test '!' -g checkout-test2-override/a/nested/2 test '!' -u checkout-test2-override/a/nested/3 fi +assert_file_has_mode checkout-test2-override/a/readable-only 600 echo "ok commit statoverride" cd ${test_tmpdir} @@ -492,7 +496,7 @@ echo "ok pull-local with --remote arg" cd ${test_tmpdir} ${CMD_PREFIX} ostree --repo=repo3 prune find repo3/objects -name '*.commit' > objlist-before-prune -rm repo3/refs/heads/* repo3/refs/remotes/* -rf +rm repo3/refs/heads/* repo3/refs/mirrors/* repo3/refs/remotes/* -rf ${CMD_PREFIX} ostree --repo=repo3 prune --refs-only find repo3/objects -name '*.commit' > objlist-after-prune if cmp -s objlist-before-prune objlist-after-prune; then @@ -512,7 +516,7 @@ find repo3/objects -name '*.filez' > file-objects if test -s file-objects; then assert_not_reached "prune didn't delete all objects" fi -echo "ok prune in archive-z2 deleted everything" +echo "ok prune in archive deleted everything" cd ${test_tmpdir} rm -rf test2-checkout @@ -730,7 +734,7 @@ $OSTREE commit ${COMMIT_ARGS} -b test2 -s "Unfsynced commit" --fsync=false if test "$(id -u)" != "0"; then cd ${test_tmpdir} rm -f expected-fail error-message - $OSTREE init --mode=archive-z2 --repo=repo-noperm + $OSTREE init --mode=archive --repo=repo-noperm chmod -w repo-noperm/objects $OSTREE --repo=repo-noperm pull-local repo 2> error-message || touch expected-fail chmod +w repo-noperm/objects diff --git a/tests/libtest.sh b/tests/libtest.sh index 15802dfd..6f33c5b5 100755 --- a/tests/libtest.sh +++ b/tests/libtest.sh @@ -117,15 +117,14 @@ else fi files_are_hardlinked() { - f1=$(stat -c %i $1) - f2=$(stat -c %i $2) - [ "$f1" == "$f2" ] + inode1=$(stat -c %i $1) + inode2=$(stat -c %i $2) + test -n "${inode1}" && test -n "${inode2}" + [ "${inode1}" == "${inode2}" ] } assert_files_hardlinked() { - f1=$(stat -c %i $1) - f2=$(stat -c %i $2) - if ! files_are_hardlinked "$f1" "$f2"; then + if ! files_are_hardlinked "$1" "$2"; then fatal "Files '$1' and '$2' are not hardlinked" fi } @@ -537,3 +536,15 @@ ostree_file_path_to_object_path() { relpath=$(ostree_file_path_to_relative_object_path $repo $ref $path) echo ${repo}/${relpath} } + +# Assert ref $2 in repo $1 has checksum $3. +assert_ref () { + assert_streq $(${CMD_PREFIX} ostree rev-parse --repo=$1 $2) $3 +} + +# Assert no ref named $2 is present in repo $1. +assert_not_ref () { + if ${CMD_PREFIX} ostree rev-parse --repo=$1 $2 2>/dev/null; then + fatal "rev-parse $2 unexpectedly succeeded!" + fi +} diff --git a/tests/pull-test.sh b/tests/pull-test.sh index 24eb16a0..aaba2a7b 100644 --- a/tests/pull-test.sh +++ b/tests/pull-test.sh @@ -35,12 +35,14 @@ function verify_initial_contents() { assert_file_has_content baz/cow '^moo$' } -echo "1..25" +echo "1..26" # Try both syntaxes repo_init --no-gpg-verify -${CMD_PREFIX} ostree --repo=repo pull origin main -${CMD_PREFIX} ostree --repo=repo pull origin:main +${CMD_PREFIX} ostree --repo=repo pull origin main >out.txt +assert_file_has_content out.txt "[1-9][0-9]* metadata, [1-9][0-9]* content objects fetched" +${CMD_PREFIX} ostree --repo=repo pull origin:main > out.txt +assert_not_file_has_content out.txt "content objects fetched" ${CMD_PREFIX} ostree --repo=repo fsck echo "ok pull" @@ -50,7 +52,7 @@ echo "ok pull contents" cd ${test_tmpdir} mkdir mirrorrepo -ostree_repo_init mirrorrepo --mode=archive-z2 +ostree_repo_init mirrorrepo --mode=archive ${CMD_PREFIX} ostree --repo=mirrorrepo remote add --set=gpg-verify=false origin $(cat httpd-address)/ostree/gnomerepo ${CMD_PREFIX} ostree --repo=mirrorrepo pull --mirror origin main ${CMD_PREFIX} ostree --repo=mirrorrepo fsck @@ -63,7 +65,7 @@ ${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo commit -b otherbranch --tree=di ${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo summary -u rm mirrorrepo -rf # All refs -ostree_repo_init mirrorrepo --mode=archive-z2 +ostree_repo_init mirrorrepo --mode=archive ${CMD_PREFIX} ostree --repo=mirrorrepo remote add --set=gpg-verify=false origin $(cat httpd-address)/ostree/gnomerepo ${CMD_PREFIX} ostree --repo=mirrorrepo pull --mirror origin ${CMD_PREFIX} ostree --repo=mirrorrepo fsck @@ -73,7 +75,7 @@ done echo "ok pull mirror (all refs)" rm mirrorrepo -rf -ostree_repo_init mirrorrepo --mode=archive-z2 +ostree_repo_init mirrorrepo --mode=archive ${CMD_PREFIX} ostree --repo=mirrorrepo remote add --set=gpg-verify=false origin $(cat httpd-address)/ostree/gnomerepo # Generate a summary in the mirror ${CMD_PREFIX} ostree --repo=mirrorrepo summary -u @@ -152,13 +154,33 @@ echo "ok pull commit metadata only (should not apply deltas)" cd ${test_tmpdir} mkdir mirrorrepo-local -ostree_repo_init mirrorrepo-local --mode=archive-z2 +ostree_repo_init mirrorrepo-local --mode=archive ${CMD_PREFIX} ostree --repo=mirrorrepo-local remote add --set=gpg-verify=false origin file://$(pwd)/ostree-srv/gnomerepo ${CMD_PREFIX} ostree --repo=mirrorrepo-local pull --mirror origin main ${CMD_PREFIX} ostree --repo=mirrorrepo-local fsck $OSTREE show main >/dev/null echo "ok pull local mirror" +cd ${test_tmpdir} +# This is more of a known issue; test that we give a clean error right now +rm otherrepo -rf +ostree_repo_init otherrepo --mode=archive +rm checkout-origin-main -rf +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo checkout main checkout-origin-main +${CMD_PREFIX} ostree --repo=otherrepo commit -b localbranch --tree=dir=checkout-origin-main +${CMD_PREFIX} ostree --repo=otherrepo remote add --set=gpg-verify=false origin file://$(pwd)/ostree-srv/gnomerepo +${CMD_PREFIX} ostree --repo=otherrepo pull origin main +rm mirrorrepo-local -rf +ostree_repo_init mirrorrepo-local --mode=archive +if ${CMD_PREFIX} ostree --repo=mirrorrepo-local pull-local otherrepo 2>err.txt; then + fatal "pull with mixed refs succeeded?" +fi +assert_file_has_content err.txt "error: Invalid ref name origin:main" +${CMD_PREFIX} ostree --repo=mirrorrepo-local pull-local otherrepo localbranch +${CMD_PREFIX} ostree --repo=mirrorrepo-local rev-parse localbranch +${CMD_PREFIX} ostree --repo=mirrorrepo-local fsck +echo "ok pull-local mirror errors with mixed refs" + cd ${test_tmpdir} ${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo commit -b main -s "Metadata string" --add-detached-metadata-string=SIGNATURE=HANCOCK --tree=ref=main ${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo summary -u @@ -170,7 +192,7 @@ echo "ok pull detached metadata" cd ${test_tmpdir} mkdir parentpullrepo -ostree_repo_init parentpullrepo --mode=archive-z2 +ostree_repo_init parentpullrepo --mode=archive ${CMD_PREFIX} ostree --repo=parentpullrepo remote add --set=gpg-verify=false origin file://$(pwd)/ostree-srv/gnomerepo parent_rev=$(ostree --repo=ostree-srv/gnomerepo rev-parse main^) rev=$(ostree --repo=ostree-srv/gnomerepo rev-parse main) diff --git a/tests/test-basic-c.c b/tests/test-basic-c.c index dbab087e..dc6d33f2 100644 --- a/tests/test-basic-c.c +++ b/tests/test-basic-c.c @@ -185,6 +185,25 @@ static gboolean hi_content_stream_new (GInputStream **out_stream, return ostree_raw_file_to_content_stream ((GInputStream*)hi_memstream, finfo, NULL, out_stream, out_length, NULL, error); } +static void +test_validate_remotename (void) +{ + const char *valid[] = {"foo", "hello-world"}; + const char *invalid[] = {"foo/bar", ""}; + for (guint i = 0; i < G_N_ELEMENTS(valid); i++) + { + g_autoptr(GError) error = NULL; + g_assert (ostree_validate_remote_name (valid[i], &error)); + g_assert_no_error (error); + } + for (guint i = 0; i < G_N_ELEMENTS(invalid); i++) + { + g_autoptr(GError) error = NULL; + g_assert (!ostree_validate_remote_name (invalid[i], &error)); + g_assert (error != NULL); + } +} + static void test_object_writes (gconstpointer data) { @@ -232,6 +251,7 @@ int main (int argc, char **argv) g_test_add_data_func ("/repo-not-system", repo, test_repo_is_not_system); g_test_add_data_func ("/raw-file-to-archive-z2-stream", repo, test_raw_file_to_archive_z2_stream); g_test_add_data_func ("/objectwrites", repo, test_object_writes); + g_test_add_func ("/remotename", test_validate_remotename); return g_test_run(); out: diff --git a/tests/test-basic-user.sh b/tests/test-basic-user.sh index fa802df6..94866550 100755 --- a/tests/test-basic-user.sh +++ b/tests/test-basic-user.sh @@ -25,7 +25,7 @@ skip_without_user_xattrs setup_test_repository "bare-user" -extra_basic_tests=3 +extra_basic_tests=4 . $(dirname $0)/basic-test.sh # Reset things so we don't inherit a lot of state from earlier tests @@ -64,3 +64,16 @@ $OSTREE checkout -U -H test2-unwritable test2-checkout cd test2-checkout assert_file_has_mode unwritable 400 echo "ok bare-user unwritable" + +rm test2-checkout -rf +$OSTREE checkout -U -H test2 test2-checkout +cat > statoverride.txt < + */ + +#include "config.h" + +#include +#include + +#include "ostree-bloom-private.h" + +/* Test the two different constructors work at a basic level. */ +static void +test_bloom_init (void) +{ + g_autoptr(OstreeBloom) bloom = NULL; + g_autoptr(GBytes) bytes = NULL; + + bloom = ostree_bloom_new (1, 1, ostree_str_bloom_hash); + g_assert_cmpuint (ostree_bloom_get_size (bloom), ==, 1); + g_assert_cmpuint (ostree_bloom_get_k (bloom), ==, 1); + g_assert (ostree_bloom_get_hash_func (bloom) == ostree_str_bloom_hash); + g_clear_pointer (&bloom, ostree_bloom_unref); + + bytes = g_bytes_new_take (g_malloc0 (4), 4); + bloom = ostree_bloom_new_from_bytes (bytes, 1, ostree_str_bloom_hash); + g_assert_cmpuint (ostree_bloom_get_size (bloom), ==, 4); + g_assert_cmpuint (ostree_bloom_get_k (bloom), ==, 1); + g_assert (ostree_bloom_get_hash_func (bloom) == ostree_str_bloom_hash); + g_clear_pointer (&bloom, ostree_bloom_unref); +} + +/* Test that building a bloom filter, marshalling it through GBytes, and loading + * it again, gives the same element membership. */ +static void +test_bloom_construction (void) +{ + g_autoptr(OstreeBloom) bloom = NULL; + g_autoptr(OstreeBloom) immutable_bloom = NULL; + g_autoptr(GBytes) bytes = NULL; + gsize i; + const gchar *members[] = + { + "hello", "there", "these", "are", "test", "strings" + }; + const gchar *non_members[] = + { + "not", "an", "element" + }; + const gsize n_bytes = 256; + const guint8 k = 8; + const OstreeBloomHashFunc hash = ostree_str_bloom_hash; + + /* Build a bloom filter. */ + bloom = ostree_bloom_new (n_bytes, k, hash); + + for (i = 0; i < G_N_ELEMENTS (members); i++) + ostree_bloom_add_element (bloom, members[i]); + + bytes = ostree_bloom_seal (bloom); + + /* Read it back from the GBytes. */ + immutable_bloom = ostree_bloom_new_from_bytes (bytes, k, hash); + + for (i = 0; i < G_N_ELEMENTS (members); i++) + g_assert_true (ostree_bloom_maybe_contains (bloom, members[i])); + + /* This should never fail in future, as we guarantee the hash function will + * never change. But given the definition of a bloom filter, it would also + * be valid for these calls to return %TRUE. */ + for (i = 0; i < G_N_ELEMENTS (non_members); i++) + g_assert_false (ostree_bloom_maybe_contains (bloom, non_members[i])); +} + +/* Test that an empty bloom filter definitely contains no elements. */ +static void +test_bloom_empty (void) +{ + g_autoptr(OstreeBloom) bloom = NULL; + const gsize n_bytes = 256; + const guint8 k = 8; + const OstreeBloomHashFunc hash = ostree_str_bloom_hash; + + /* Build an empty bloom filter. */ + bloom = ostree_bloom_new (n_bytes, k, hash); + + g_assert_false (ostree_bloom_maybe_contains (bloom, "hello")); + g_assert_false (ostree_bloom_maybe_contains (bloom, "there")); +} + +/* Build a bloom filter, and check the membership of the members as they are + * added. */ +static void +test_bloom_membership_during_construction (void) +{ + g_autoptr(OstreeBloom) bloom = NULL; + gsize i, j; + const gchar *members[] = + { + "hello", "there", "these", "are", "test", "strings" + }; + const gsize n_bytes = 256; + const guint8 k = 8; + const OstreeBloomHashFunc hash = ostree_str_bloom_hash; + + /* These membership checks should never fail in future, as we guarantee + * the hash function will never change. But given the definition of a bloom + * filter, it would also be valid for these checks to fail. */ + bloom = ostree_bloom_new (n_bytes, k, hash); + + for (i = 0; i < G_N_ELEMENTS (members); i++) + { + ostree_bloom_add_element (bloom, members[i]); + + for (j = 0; j < G_N_ELEMENTS (members); j++) + { + if (j <= i) + g_assert_true (ostree_bloom_maybe_contains (bloom, members[j])); + else + g_assert_false (ostree_bloom_maybe_contains (bloom, members[j])); + } + } +} + +int main (int argc, char **argv) +{ + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/bloom/init", test_bloom_init); + g_test_add_func ("/bloom/construction", test_bloom_construction); + g_test_add_func ("/bloom/empty", test_bloom_empty); + g_test_add_func ("/bloom/membership-during-construction", test_bloom_membership_during_construction); + + return g_test_run(); +} diff --git a/tests/test-local-pull.sh b/tests/test-local-pull.sh index 43b96010..1c9767f6 100755 --- a/tests/test-local-pull.sh +++ b/tests/test-local-pull.sh @@ -84,13 +84,13 @@ if ${CMD_PREFIX} ostree --repo=repo6 pull-local --remote=origin --gpg-verify-sum assert_not_reached "GPG summary verification with no summary unexpectedly succeeded" fi -${OSTREE} summary -u update +${OSTREE} summary --update if ${CMD_PREFIX} ostree --repo=repo6 pull-local --remote=origin --gpg-verify-summary repo test2 2>&1; then assert_not_reached "GPG summary verification with signed no summary unexpectedly succeeded" fi -${OSTREE} summary -u update --gpg-sign=${TEST_GPG_KEYID_1} --gpg-homedir=${TEST_GPG_KEYHOME} +${OSTREE} summary --update --gpg-sign=${TEST_GPG_KEYID_1} --gpg-homedir=${TEST_GPG_KEYHOME} ${CMD_PREFIX} ostree --repo=repo6 pull-local --remote=origin --gpg-verify-summary repo test2 2>&1 diff --git a/tests/test-mock-gio.c b/tests/test-mock-gio.c new file mode 100644 index 00000000..f6d4f2a8 --- /dev/null +++ b/tests/test-mock-gio.c @@ -0,0 +1,400 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "test-mock-gio.h" + +/** + * SECTION:mock-gio + * @title: Mock GIO volume interfaces + * @short_description: Mock implementations of GIO volume, mount and drive + * interfaces + * @stability: Unstable + * @include: tests/test-mock-gio.h + * + * A set of classes implementing GIO interfaces for volumes, mounts, drives + * and volume monitoring, which return mock data to the caller when used. These + * are designed for use in unit tests, to mock up removable drives when testing + * code which monitors such drives being added and removed and then queries + * properties of them. + * + * By returning mock drive locations to the caller, for example, the contents of + * a removable drive may be mocked up using temporary files. + * + * Currently, all the mock data returned by these classes to callers is static, + * set at construction time. + * + * Since: 2017.8 + */ + +/* Mock volume monitor class. This returns a static set of data to the caller, + * which it was initialised with. */ +struct _OstreeMockVolumeMonitor +{ + GVolumeMonitor parent_instance; + + GList *mounts; /* (element-type OstreeMockMount) */ + GList *volumes; /* (element-type OstreeMockVolume) */ +}; + +G_DEFINE_TYPE (OstreeMockVolumeMonitor, ostree_mock_volume_monitor, G_TYPE_VOLUME_MONITOR) + +static GList * +ostree_mock_volume_monitor_get_mounts (GVolumeMonitor *monitor) +{ + OstreeMockVolumeMonitor *self = OSTREE_MOCK_VOLUME_MONITOR (monitor); + return g_list_copy_deep (self->mounts, (GCopyFunc) g_object_ref, NULL); +} + +static GList * +ostree_mock_volume_monitor_get_volumes (GVolumeMonitor *monitor) +{ + OstreeMockVolumeMonitor *self = OSTREE_MOCK_VOLUME_MONITOR (monitor); + return g_list_copy_deep (self->volumes, (GCopyFunc) g_object_ref, NULL); +} + +static void +ostree_mock_volume_monitor_init (OstreeMockVolumeMonitor *self) +{ + /* Nothing to see here. */ +} + +static void +ostree_mock_volume_monitor_dispose (GObject *object) +{ + OstreeMockVolumeMonitor *self = OSTREE_MOCK_VOLUME_MONITOR (object); + + g_list_free_full (self->volumes, g_object_unref); + self->volumes = NULL; + + g_list_free_full (self->mounts, g_object_unref); + self->mounts = NULL; + + G_OBJECT_CLASS (ostree_mock_volume_monitor_parent_class)->dispose (object); +} + +static void +ostree_mock_volume_monitor_class_init (OstreeMockVolumeMonitorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GVolumeMonitorClass *monitor_class = G_VOLUME_MONITOR_CLASS (klass); + + object_class->dispose = ostree_mock_volume_monitor_dispose; + + monitor_class->get_mounts = ostree_mock_volume_monitor_get_mounts; + monitor_class->get_volumes = ostree_mock_volume_monitor_get_volumes; +} + +/** + * ostree_mock_volume_monitor_new: + * @mounts: (element-type GMount) (transfer none): list of current #GMounts + * @volumes: (element-type GVolume) (transfer none): list of current #GVolumes + * + * Create a new mock #GVolumeMonitor which will return the given static lists of + * #GMounts and #GVolumes to any caller of g_volume_monitor_get_mounts() or + * g_volume_monitor_get_volumes(). + * + * Typically, the elements of @mounts will be #OstreeMockMount objects and the + * elements of @volumes will be #OstreeMockVolume objects; but this does not + * have to be the case. + * + * Returns: (transfer full): a new #GVolumeMonitor object + * Since: 2017.8 + */ +GVolumeMonitor * +ostree_mock_volume_monitor_new (GList *mounts, + GList *volumes) +{ + g_autoptr(OstreeMockVolumeMonitor) monitor = NULL; + + monitor = g_object_new (OSTREE_TYPE_MOCK_VOLUME_MONITOR, NULL); + monitor->mounts = g_list_copy_deep (mounts, (GCopyFunc) g_object_ref, NULL); + monitor->volumes = g_list_copy_deep (volumes, (GCopyFunc) g_object_ref, NULL); + + return g_steal_pointer (&monitor); +} + +/* Mock volume class. This returns a static set of data to the caller, which it + * was initialised with. */ +struct _OstreeMockVolume +{ + GObject parent_instance; + + gchar *name; + GDrive *drive; /* (owned) (nullable) */ + GMount *mount; /* (owned) (nullable) */ +}; + +static void ostree_mock_volume_iface_init (GVolumeIface *iface); + +G_DEFINE_TYPE_WITH_CODE (OstreeMockVolume, ostree_mock_volume, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_VOLUME, ostree_mock_volume_iface_init)) + +static gchar * +ostree_mock_volume_get_name (GVolume *volume) +{ + OstreeMockVolume *self = OSTREE_MOCK_VOLUME (volume); + return g_strdup (self->name); +} + +static GDrive * +ostree_mock_volume_get_drive (GVolume *volume) +{ + OstreeMockVolume *self = OSTREE_MOCK_VOLUME (volume); + return (self->drive != NULL) ? g_object_ref (self->drive) : NULL; +} + +static GMount * +ostree_mock_volume_get_mount (GVolume *volume) +{ + OstreeMockVolume *self = OSTREE_MOCK_VOLUME (volume); + return (self->mount != NULL) ? g_object_ref (self->mount) : NULL; +} + +static void +ostree_mock_volume_init (OstreeMockVolume *self) +{ + /* Nothing to see here. */ +} + +static void +ostree_mock_volume_dispose (GObject *object) +{ + OstreeMockVolume *self = OSTREE_MOCK_VOLUME (object); + + g_clear_pointer (&self->name, g_free); + g_clear_object (&self->drive); + g_clear_object (&self->mount); + + G_OBJECT_CLASS (ostree_mock_volume_parent_class)->dispose (object); +} + +static void +ostree_mock_volume_class_init (OstreeMockVolumeClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = ostree_mock_volume_dispose; +} + +static void +ostree_mock_volume_iface_init (GVolumeIface *iface) +{ + iface->get_name = ostree_mock_volume_get_name; + iface->get_drive = ostree_mock_volume_get_drive; + iface->get_mount = ostree_mock_volume_get_mount; +} + +/** + * ostree_mock_volume_new: + * @name: volume name + * @drive: (transfer none) (nullable): drive for the volume, or %NULL if none + * should be associated + * @mount: (transfer none) (nullable): mount for the volume, or %NULL if it’s + * not mounted + * + * Create a new mock #GVolume which will return the given static @name, @drive + * and @mount to any caller of its getter methods. There is currently no + * provision for changing these values dynamically. There is also currently no + * provision for mocking the other getters of #GVolume. + * + * Typically, @drive will be an #OstreeMockDrive object and @mount will be an + * #OstreeMockMount object; but this does not have to be the case. + * + * Returns: (transfer full): a new #GVolume object + * Since: 2017.8 + */ +OstreeMockVolume * +ostree_mock_volume_new (const gchar *name, + GDrive *drive, + GMount *mount) +{ + g_autoptr(OstreeMockVolume) volume = NULL; + + volume = g_object_new (OSTREE_TYPE_MOCK_VOLUME, NULL); + volume->name = g_strdup (name); + volume->drive = (drive != NULL) ? g_object_ref (drive) : NULL; + volume->mount = (mount != NULL) ? g_object_ref (mount) : NULL; + + return g_steal_pointer (&volume); +} + +/* Mock drive class. This returns a static set of data to the caller, which it + * was initialised with. */ +struct _OstreeMockDrive +{ + GObject parent_instance; + + gboolean is_removable; +}; + +static void ostree_mock_drive_iface_init (GDriveIface *iface); + +G_DEFINE_TYPE_WITH_CODE (OstreeMockDrive, ostree_mock_drive, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_DRIVE, ostree_mock_drive_iface_init)) + +#if GLIB_CHECK_VERSION(2, 50, 0) +static gboolean +ostree_mock_drive_is_removable (GDrive *drive) +{ + OstreeMockDrive *self = OSTREE_MOCK_DRIVE (drive); + return self->is_removable; +} +#endif + +static void +ostree_mock_drive_init (OstreeMockDrive *self) +{ + /* Nothing to see here. */ +} + +static void +ostree_mock_drive_class_init (OstreeMockDriveClass *klass) +{ + /* Nothing to see here. */ +} + +static void +ostree_mock_drive_iface_init (GDriveIface *iface) +{ +#if GLIB_CHECK_VERSION(2, 50, 0) + iface->is_removable = ostree_mock_drive_is_removable; +#endif +} + +/** + * ostree_mock_drive_new: + * @is_removable: %TRUE if the drive is removable; %FALSE otherwise + * + * Create a new mock #GDrive which will return the given static @is_removable to + * any caller of its getter methods. There is currently no provision for mocking + * the other getters of #GDrive. + * + * Returns: (transfer full): a new #GDrive object + * Since: 2017.8 + */ +OstreeMockDrive * +ostree_mock_drive_new (gboolean is_removable) +{ + g_autoptr(OstreeMockDrive) drive = NULL; + + drive = g_object_new (OSTREE_TYPE_MOCK_DRIVE, NULL); + drive->is_removable = is_removable; + + return g_steal_pointer (&drive); +} + +/* Mock mount class. This returns a static set of data to the caller, which it + * was initialised with. */ +struct _OstreeMockMount +{ + GObject parent_instance; + + gchar *name; /* (owned) */ + GFile *root; /* (owned) */ +}; + +static void ostree_mock_mount_iface_init (GMountIface *iface); + +G_DEFINE_TYPE_WITH_CODE (OstreeMockMount, ostree_mock_mount, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_MOUNT, ostree_mock_mount_iface_init)) + +static gchar * +ostree_mock_mount_get_name (GMount *mount) +{ + OstreeMockMount *self = OSTREE_MOCK_MOUNT (mount); + return g_strdup (self->name); +} + +static GFile * +ostree_mock_mount_get_root (GMount *mount) +{ + OstreeMockMount *self = OSTREE_MOCK_MOUNT (mount); + return g_object_ref (self->root); +} + +static void +ostree_mock_mount_init (OstreeMockMount *self) +{ + /* Nothing to see here. */ +} + +static void +ostree_mock_mount_dispose (GObject *object) +{ + OstreeMockMount *self = OSTREE_MOCK_MOUNT (object); + + g_clear_pointer (&self->name, g_free); + g_clear_object (&self->root); + + G_OBJECT_CLASS (ostree_mock_mount_parent_class)->dispose (object); +} + +static void +ostree_mock_mount_class_init (OstreeMockMountClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = ostree_mock_mount_dispose; +} + +static void +ostree_mock_mount_iface_init (GMountIface *iface) +{ + iface->get_name = ostree_mock_mount_get_name; + iface->get_root = ostree_mock_mount_get_root; +} + +/** + * ostree_mock_mount_new: + * @name: mount name + * @root: (transfer none): root path for the mounted file system + * + * Create a new mock #GMount which will return the given static @name and @root + * to any caller of its getter methods. There is currently no provision for + * mocking the other getters of #GMount. + * + * Typically, @root will point to a temporary directory where a mocked file + * system is present; but this does not have to be the case. + * + * Returns: (transfer full): a new #GMount object + * Since: 2017.8 + */ +OstreeMockMount * +ostree_mock_mount_new (const gchar *name, + GFile *root) +{ + g_autoptr(OstreeMockMount) mount = NULL; + + mount = g_object_new (OSTREE_TYPE_MOCK_MOUNT, NULL); + mount->name = g_strdup (name); + mount->root = g_object_ref (root); + + return g_steal_pointer (&mount); +} diff --git a/tests/test-mock-gio.h b/tests/test-mock-gio.h new file mode 100644 index 00000000..9f544759 --- /dev/null +++ b/tests/test-mock-gio.h @@ -0,0 +1,127 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#pragma once + +#include +#include +#include +#include + +#include "ostree-types.h" + +G_BEGIN_DECLS + +#define OSTREE_TYPE_MOCK_VOLUME_MONITOR (ostree_mock_volume_monitor_get_type ()) + +/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44): +G_GNUC_INTERNAL +G_DECLARE_FINAL_TYPE (OstreeMockVolumeMonitor, ostree_mock_volume_monitor, OSTREE, MOCK_VOLUME_MONITOR, GVolumeMonitor) */ + +G_GNUC_INTERNAL +GType ostree_mock_volume_monitor_get_type (void); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +typedef struct _OstreeMockVolumeMonitor OstreeMockVolumeMonitor; +typedef struct { GVolumeMonitorClass parent_class; } OstreeMockVolumeMonitorClass; + +static inline OstreeMockVolumeMonitor *OSTREE_MOCK_VOLUME_MONITOR (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_mock_volume_monitor_get_type (), OstreeMockVolumeMonitor); } +static inline gboolean OSTREE_IS_MOCK_VOLUME_MONITOR (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_mock_volume_monitor_get_type ()); } +G_GNUC_END_IGNORE_DEPRECATIONS + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeMockVolumeMonitor, g_object_unref) + +G_GNUC_INTERNAL +GVolumeMonitor *ostree_mock_volume_monitor_new (GList *mounts, + GList *volumes); + +#define OSTREE_TYPE_MOCK_VOLUME (ostree_mock_volume_get_type ()) + +/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44): +G_GNUC_INTERNAL +G_DECLARE_FINAL_TYPE (OstreeMockVolume, ostree_mock_volume, OSTREE, MOCK_VOLUME, GObject) */ + +G_GNUC_INTERNAL +GType ostree_mock_volume_get_type (void); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +typedef struct _OstreeMockVolume OstreeMockVolume; +typedef struct { GObjectClass parent_class; } OstreeMockVolumeClass; + +static inline OstreeMockVolume *OSTREE_MOCK_VOLUME (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_mock_volume_get_type (), OstreeMockVolume); } +static inline gboolean OSTREE_IS_MOCK_VOLUME (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_mock_volume_get_type ()); } +G_GNUC_END_IGNORE_DEPRECATIONS + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeMockVolume, g_object_unref) + +G_GNUC_INTERNAL +OstreeMockVolume *ostree_mock_volume_new (const gchar *name, + GDrive *drive, + GMount *mount); + +#define OSTREE_TYPE_MOCK_DRIVE (ostree_mock_drive_get_type ()) + +/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44): +G_GNUC_INTERNAL +G_DECLARE_FINAL_TYPE (OstreeMockDrive, ostree_mock_drive, OSTREE, MOCK_DRIVE, GObject) */ + +G_GNUC_INTERNAL +GType ostree_mock_drive_get_type (void); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +typedef struct _OstreeMockDrive OstreeMockDrive; +typedef struct { GObjectClass parent_class; } OstreeMockDriveClass; + +static inline OstreeMockDrive *OSTREE_MOCK_DRIVE (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_mock_drive_get_type (), OstreeMockDrive); } +static inline gboolean OSTREE_IS_MOCK_DRIVE (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_mock_drive_get_type ()); } +G_GNUC_END_IGNORE_DEPRECATIONS + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeMockDrive, g_object_unref) + +G_GNUC_INTERNAL +OstreeMockDrive *ostree_mock_drive_new (gboolean is_removable); + +#define OSTREE_TYPE_MOCK_MOUNT (ostree_mock_mount_get_type ()) + +/* Manually expanded version of the following, omitting autoptr support (for GLib < 2.44): +G_GNUC_INTERNAL +G_DECLARE_FINAL_TYPE (OstreeMockMount, ostree_mock_mount, OSTREE, MOCK_MOUNT, GObject) */ + +G_GNUC_INTERNAL +GType ostree_mock_mount_get_type (void); + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +typedef struct _OstreeMockMount OstreeMockMount; +typedef struct { GObjectClass parent_class; } OstreeMockMountClass; + +static inline OstreeMockMount *OSTREE_MOCK_MOUNT (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_CAST (ptr, ostree_mock_mount_get_type (), OstreeMockMount); } +static inline gboolean OSTREE_IS_MOCK_MOUNT (gpointer ptr) { return G_TYPE_CHECK_INSTANCE_TYPE (ptr, ostree_mock_mount_get_type ()); } +G_GNUC_END_IGNORE_DEPRECATIONS + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeMockMount, g_object_unref) + +G_GNUC_INTERNAL +OstreeMockMount *ostree_mock_mount_new (const gchar *name, + GFile *root); + +G_END_DECLS diff --git a/tests/test-pull-archive-z.sh b/tests/test-pull-archive.sh similarity index 95% rename from tests/test-pull-archive-z.sh rename to tests/test-pull-archive.sh index 66f8873e..d8b0f099 100755 --- a/tests/test-pull-archive-z.sh +++ b/tests/test-pull-archive.sh @@ -21,6 +21,6 @@ set -euo pipefail . $(dirname $0)/libtest.sh -setup_fake_remote_repo1 "archive-z2" +setup_fake_remote_repo1 "archive" . ${test_srcdir}/pull-test.sh diff --git a/tests/test-pull-c.c b/tests/test-pull-c.c index c2d64dcc..2b0cef6d 100644 --- a/tests/test-pull-c.c +++ b/tests/test-pull-c.c @@ -43,7 +43,7 @@ test_data_init (TestData *td) if (!td->repo) goto out; - if (!ot_test_run_libtest ("setup_fake_remote_repo1 archive-z2", error)) + if (!ot_test_run_libtest ("setup_fake_remote_repo1 archive", error)) goto out; if (!g_file_get_contents ("httpd-address", &http_address, NULL, error)) diff --git a/tests/test-pull-localcache.sh b/tests/test-pull-localcache.sh new file mode 100755 index 00000000..5f810acb --- /dev/null +++ b/tests/test-pull-localcache.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# +# Copyright (C) 2017 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +set -euo pipefail + +. $(dirname $0)/libtest.sh + +setup_fake_remote_repo1 "archive" + +echo '1..1' + +cd ${test_tmpdir} +gnomerepo_url="$(cat httpd-address)/ostree/gnomerepo" +ostree_repo_init repo --mode "archive" +ostree_repo_init repo-local --mode "archive" +for repo in repo{,-local}; do + ${CMD_PREFIX} ostree --repo=${repo} remote add --set=gpg-verify=false origin ${gnomerepo_url} +done + +# Pull the contents to our local cache +${CMD_PREFIX} ostree --repo=repo-local pull origin main +rm files -rf +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo checkout main files +echo anewfile > files/anewfile +${CMD_PREFIX} ostree --repo=ostree-srv/gnomerepo commit -b main --tree=dir=files + +${CMD_PREFIX} ostree --repo=repo pull -L repo-local origin main >out.txt +assert_file_has_content out.txt '3 metadata, 1 content objects fetched (4 meta, 5 content local)' +echo "ok pull --reference" diff --git a/tests/test-repo-finder-avahi.c b/tests/test-repo-finder-avahi.c new file mode 100644 index 00000000..b2fddf70 --- /dev/null +++ b/tests/test-repo-finder-avahi.c @@ -0,0 +1,228 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "ostree-autocleanups.h" +#include "ostree-repo-finder.h" +#include "ostree-repo-finder-avahi.h" +#include "ostree-repo-finder-avahi-private.h" + +/* FIXME: Upstream this */ +G_DEFINE_AUTOPTR_CLEANUP_FUNC (AvahiStringList, avahi_string_list_free) + +/* Test the object constructor works at a basic level. */ +static void +test_repo_finder_avahi_init (void) +{ + g_autoptr(OstreeRepoFinderAvahi) finder = NULL; + g_autoptr(GMainContext) context = NULL; + + /* Default main context. */ + finder = ostree_repo_finder_avahi_new (NULL); + g_clear_object (&finder); + + /* Explicit main context. */ + context = g_main_context_new (); + finder = ostree_repo_finder_avahi_new (context); + g_clear_object (&finder); +} + +/* Test parsing valid and invalid TXT records. */ +static void +test_repo_finder_avahi_txt_records_parse (void) +{ + struct + { + const guint8 *txt; + gsize txt_len; + const gchar *expected_key; /* (nullable) to indicate parse failure */ + const guint8 *expected_value; /* (nullable) to allow for valueless keys */ + gsize expected_value_len; + } + vectors[] = + { + { (const guint8 *) "", 0, NULL, NULL, 0 }, + { (const guint8 *) "\x00", 1, NULL, NULL, 0 }, + { (const guint8 *) "\xff", 1, NULL, NULL, 0 }, + { (const guint8 *) "k\x00", 2, NULL, NULL, 0 }, + { (const guint8 *) "k\xff", 2, NULL, NULL, 0 }, + { (const guint8 *) "=", 1, NULL, NULL, 0 }, + { (const guint8 *) "=value", 6, NULL, NULL, 0 }, + { (const guint8 *) "k=v", 3, "k", (const guint8 *) "v", 1 }, + { (const guint8 *) "key=value", 9, "key", (const guint8 *) "value", 5 }, + { (const guint8 *) "k=v=", 4, "k", (const guint8 *) "v=", 2 }, + { (const guint8 *) "k=", 2, "k", (const guint8 *) "", 0 }, + { (const guint8 *) "k", 1, "k", NULL, 0 }, + { (const guint8 *) "k==", 3, "k", (const guint8 *) "=", 1 }, + { (const guint8 *) "k=\x00\x01\x02", 5, "k", (const guint8 *) "\x00\x01\x02", 3 }, + }; + gsize i; + + for (i = 0; i < G_N_ELEMENTS (vectors); i++) + { + g_autoptr(AvahiStringList) string_list = NULL; + g_autoptr(GHashTable) attributes = NULL; + + g_test_message ("Vector %" G_GSIZE_FORMAT, i); + + string_list = avahi_string_list_add_arbitrary (NULL, vectors[i].txt, vectors[i].txt_len); + + attributes = _ostree_txt_records_parse (string_list); + + if (vectors[i].expected_key != NULL) + { + GBytes *value; + g_autoptr(GBytes) expected_value = NULL; + + g_assert_true (g_hash_table_lookup_extended (attributes, + vectors[i].expected_key, + NULL, + (gpointer *) &value)); + g_assert_cmpuint (g_hash_table_size (attributes), ==, 1); + + if (vectors[i].expected_value != NULL) + { + g_assert_nonnull (value); + expected_value = g_bytes_new_static (vectors[i].expected_value, vectors[i].expected_value_len); + g_assert_true (g_bytes_equal (value, expected_value)); + } + else + { + g_assert_null (value); + } + } + else + { + g_assert_cmpuint (g_hash_table_size (attributes), ==, 0); + } + } +} + +/* Test that the first value for a set of duplicate records is returned. + * See RFC 6763, §6.4. */ +static void +test_repo_finder_avahi_txt_records_duplicates (void) +{ + g_autoptr(AvahiStringList) string_list = NULL; + g_autoptr(GHashTable) attributes = NULL; + GBytes *value; + g_autoptr(GBytes) expected_value = NULL; + + /* Reverse the list before using it, as they are built in reverse order. + * (See the #AvahiStringList documentation.) */ + string_list = avahi_string_list_new ("k=value1", "k=value2", "k=value3", NULL); + string_list = avahi_string_list_reverse (string_list); + attributes = _ostree_txt_records_parse (string_list); + + g_assert_cmpuint (g_hash_table_size (attributes), ==, 1); + value = g_hash_table_lookup (attributes, "k"); + g_assert_nonnull (value); + + expected_value = g_bytes_new_static ("value1", strlen ("value1")); + g_assert_true (g_bytes_equal (value, expected_value)); +} + +/* Test that keys are parsed and looked up case insensitively. + * See RFC 6763, §6.4. */ +static void +test_repo_finder_avahi_txt_records_case_sensitivity (void) +{ + g_autoptr(AvahiStringList) string_list = NULL; + g_autoptr(GHashTable) attributes = NULL; + GBytes *value1, *value2; + g_autoptr(GBytes) expected_value1 = NULL, expected_value2 = NULL; + + /* Reverse the list before using it, as they are built in reverse order. + * (See the #AvahiStringList documentation.) */ + string_list = avahi_string_list_new ("k=value1", + "K=value2", + "KeY2=v", + NULL); + string_list = avahi_string_list_reverse (string_list); + attributes = _ostree_txt_records_parse (string_list); + + g_assert_cmpuint (g_hash_table_size (attributes), ==, 2); + + value1 = g_hash_table_lookup (attributes, "k"); + g_assert_nonnull (value1); + expected_value1 = g_bytes_new_static ("value1", strlen ("value1")); + g_assert_true (g_bytes_equal (value1, expected_value1)); + + g_assert_null (g_hash_table_lookup (attributes, "K")); + + value2 = g_hash_table_lookup (attributes, "key2"); + g_assert_nonnull (value2); + expected_value2 = g_bytes_new_static ("v", 1); + g_assert_true (g_bytes_equal (value2, expected_value2)); + + g_assert_null (g_hash_table_lookup (attributes, "KeY2")); +} + +/* Test that keys which have an empty value can be distinguished from those + * which have no value. See RFC 6763, §6.4. */ +static void +test_repo_finder_avahi_txt_records_empty_and_missing (void) +{ + g_autoptr(AvahiStringList) string_list = NULL; + g_autoptr(GHashTable) attributes = NULL; + GBytes *value1, *value2; + g_autoptr(GBytes) expected_value1 = NULL; + + string_list = avahi_string_list_new ("empty=", + "missing", + NULL); + attributes = _ostree_txt_records_parse (string_list); + + g_assert_cmpuint (g_hash_table_size (attributes), ==, 2); + + value1 = g_hash_table_lookup (attributes, "empty"); + g_assert_nonnull (value1); + expected_value1 = g_bytes_new_static ("", 0); + g_assert_true (g_bytes_equal (value1, expected_value1)); + + g_assert_true (g_hash_table_lookup_extended (attributes, "missing", NULL, (gpointer *) &value2)); + g_assert_null (value2); +} + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/repo-finder-avahi/init", test_repo_finder_avahi_init); + g_test_add_func ("/repo-finder-avahi/txt-records/parse", test_repo_finder_avahi_txt_records_parse); + g_test_add_func ("/repo-finder-avahi/txt-records/duplicates", test_repo_finder_avahi_txt_records_duplicates); + g_test_add_func ("/repo-finder-avahi/txt-records/case-sensitivity", test_repo_finder_avahi_txt_records_case_sensitivity); + g_test_add_func ("/repo-finder-avahi/txt-records/empty-and-missing", test_repo_finder_avahi_txt_records_empty_and_missing); + /* FIXME: Add tests for service processing, probably by splitting the + * code in OstreeRepoFinderAvahi around found_services. */ + + return g_test_run(); +} diff --git a/tests/test-repo-finder-config.c b/tests/test-repo-finder-config.c new file mode 100644 index 00000000..dc083754 --- /dev/null +++ b/tests/test-repo-finder-config.c @@ -0,0 +1,327 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "libostreetest.h" +#include "ostree-autocleanups.h" +#include "ostree-repo-finder.h" +#include "ostree-repo-finder-config.h" + +/* Test fixture. Creates a temporary directory. */ +typedef struct +{ + OstreeRepo *parent_repo; /* owned */ + int working_dfd; /* owned */ + GFile *working_dir; /* owned */ +} Fixture; + +static void +setup (Fixture *fixture, + gconstpointer test_data) +{ + g_autofree gchar *tmp_name = NULL; + g_autoptr(GError) error = NULL; + + tmp_name = g_strdup ("test-repo-finder-config-XXXXXX"); + glnx_mkdtempat_open_in_system (tmp_name, 0700, &fixture->working_dfd, &error); + g_assert_no_error (error); + + g_test_message ("Using temporary directory: %s", tmp_name); + + glnx_shutil_mkdir_p_at (fixture->working_dfd, "repo", 0700, NULL, &error); + g_assert_no_error (error); + + g_autoptr(GFile) tmp_dir = g_file_new_for_path (g_get_tmp_dir ()); + fixture->working_dir = g_file_get_child (tmp_dir, tmp_name); + + fixture->parent_repo = ot_test_setup_repo (NULL, &error); + g_assert_no_error (error); +} + +static void +teardown (Fixture *fixture, + gconstpointer test_data) +{ + glnx_fd_close int parent_repo_dfd = -1; + g_autoptr(GError) error = NULL; + + /* Recursively remove the temporary directory. */ + glnx_shutil_rm_rf_at (fixture->working_dfd, ".", NULL, NULL); + + close (fixture->working_dfd); + fixture->working_dfd = -1; + + /* The repo also needs its source files to be removed. This is the inverse + * of setup_test_repository() in libtest.sh. */ + g_autofree gchar *parent_repo_path = g_file_get_path (ostree_repo_get_path (fixture->parent_repo)); + glnx_opendirat (-1, parent_repo_path, TRUE, &parent_repo_dfd, &error); + g_assert_no_error (error); + + glnx_shutil_rm_rf_at (parent_repo_dfd, "../files", NULL, NULL); + glnx_shutil_rm_rf_at (parent_repo_dfd, "../repo", NULL, NULL); + + g_clear_object (&fixture->working_dir); + g_clear_object (&fixture->parent_repo); +} + +/* Test the object constructor works at a basic level. */ +static void +test_repo_finder_config_init (void) +{ + g_autoptr(OstreeRepoFinderConfig) finder = NULL; + + /* Default everything. */ + finder = ostree_repo_finder_config_new (); +} + +static void +result_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GAsyncResult **result_out = user_data; + *result_out = g_object_ref (result); +} + +/* Test that no remotes are found if there are no config files in the refs + * directory. */ +static void +test_repo_finder_config_no_configs (Fixture *fixture, + gconstpointer test_data) +{ + g_autoptr(OstreeRepoFinderConfig) finder = NULL; + g_autoptr(GMainContext) context = NULL; + g_autoptr(GAsyncResult) result = NULL; + g_autoptr(GPtrArray) results = NULL; /* (element-type OstreeRepoFinderResult) */ + g_autoptr(GError) error = NULL; + const OstreeCollectionRef ref1 = { "org.example.Os", "exampleos/x86_64/standard" }; + const OstreeCollectionRef ref2 = { "org.example.Os", "exampleos/x86_64/buildmaster/standard" }; + const OstreeCollectionRef * const refs[] = { &ref1, &ref2, NULL }; + + context = g_main_context_new (); + g_main_context_push_thread_default (context); + + finder = ostree_repo_finder_config_new (); + + ostree_repo_finder_resolve_async (OSTREE_REPO_FINDER (finder), refs, + fixture->parent_repo, NULL, result_cb, &result); + + while (result == NULL) + g_main_context_iteration (context, TRUE); + + results = ostree_repo_finder_resolve_finish (OSTREE_REPO_FINDER (finder), + result, &error); + g_assert_no_error (error); + g_assert_nonnull (results); + g_assert_cmpuint (results->len, ==, 0); + + g_main_context_pop_thread_default (context); +} + +/* Add configuration for a remote named @remote_name, at @remote_uri, with a + * remote collection ID of @collection_id, to the given @repo. */ +static void +assert_create_remote_config (OstreeRepo *repo, + const gchar *remote_name, + const gchar *remote_uri, + const gchar *collection_id) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GVariant) options = NULL; + + if (collection_id != NULL) + options = g_variant_new_parsed ("@a{sv} { 'collection-id': <%s> }", + collection_id); + + ostree_repo_remote_add (repo, remote_name, remote_uri, options, NULL, &error); + g_assert_no_error (error); +} + +static gchar *assert_create_remote (Fixture *fixture, + const gchar *collection_id, + ...) G_GNUC_NULL_TERMINATED; + +/* Create a new repository in a temporary directory with its collection ID set + * to @collection_id, and containing the refs given in @... (which must be + * %NULL-terminated). Return the `file://` URI of the new repository. */ +static gchar * +assert_create_remote (Fixture *fixture, + const gchar *collection_id, + ...) +{ + va_list args; + g_autoptr(GError) error = NULL; + const gchar *repo_name = (collection_id != NULL) ? collection_id : "no-collection"; + + glnx_shutil_mkdir_p_at (fixture->working_dfd, repo_name, 0700, NULL, &error); + g_assert_no_error (error); + + g_autoptr(GFile) repo_path = g_file_get_child (fixture->working_dir, repo_name); + g_autoptr(OstreeRepo) repo = ostree_repo_new (repo_path); + ostree_repo_set_collection_id (repo, collection_id, &error); + g_assert_no_error (error); + ostree_repo_create (repo, OSTREE_REPO_MODE_ARCHIVE_Z2, NULL, &error); + g_assert_no_error (error); + + /* Set up the refs from @.... */ + va_start (args, collection_id); + + for (const gchar *ref_name = va_arg (args, const gchar *); + ref_name != NULL; + ref_name = va_arg (args, const gchar *)) + { + OstreeCollectionRef collection_ref = { (gchar *) collection_id, (gchar *) ref_name }; + g_autofree gchar *checksum = NULL; + g_autoptr(OstreeMutableTree) mtree = NULL; + g_autoptr(OstreeRepoFile) repo_file = NULL; + + mtree = ostree_mutable_tree_new (); + ostree_repo_write_dfd_to_mtree (repo, AT_FDCWD, ".", mtree, NULL, NULL, &error); + g_assert_no_error (error); + ostree_repo_write_mtree (repo, mtree, (GFile **) &repo_file, NULL, &error); + g_assert_no_error (error); + + ostree_repo_write_commit (repo, NULL /* no parent */, ref_name, ref_name, + NULL /* no metadata */, repo_file, &checksum, + NULL, &error); + g_assert_no_error (error); + + if (collection_id != NULL) + ostree_repo_set_collection_ref_immediate (repo, &collection_ref, checksum, NULL, &error); + else + ostree_repo_set_ref_immediate (repo, NULL, ref_name, checksum, NULL, &error); + g_assert_no_error (error); + } + + va_end (args); + + /* Update the summary. */ + ostree_repo_regenerate_summary (repo, NULL /* no metadata */, NULL, &error); + g_assert_no_error (error); + + return g_file_get_uri (repo_path); +} + +/* Test resolving the refs against a collection of config files, which contain + * valid, invalid or duplicate repo information. */ +static void +test_repo_finder_config_mixed_configs (Fixture *fixture, + gconstpointer test_data) +{ + g_autoptr(OstreeRepoFinderConfig) finder = NULL; + g_autoptr(GMainContext) context = NULL; + g_autoptr(GAsyncResult) result = NULL; + g_autoptr(GPtrArray) results = NULL; /* (element-type OstreeRepoFinderResult) */ + g_autoptr(GError) error = NULL; + gsize i; + const OstreeCollectionRef ref0 = { "org.example.Collection0", "exampleos/x86_64/ref0" }; + const OstreeCollectionRef ref1 = { "org.example.Collection0", "exampleos/x86_64/ref1" }; + const OstreeCollectionRef ref2 = { "org.example.Collection1", "exampleos/x86_64/ref1" }; + const OstreeCollectionRef ref3 = { "org.example.Collection1", "exampleos/x86_64/ref2" }; + const OstreeCollectionRef ref4 = { "org.example.Collection2", "exampleos/x86_64/ref3" }; + const OstreeCollectionRef * const refs[] = { &ref0, &ref1, &ref2, &ref3, &ref4, NULL }; + + context = g_main_context_new (); + g_main_context_push_thread_default (context); + + /* Put together various ref configuration files. */ + g_autofree gchar *collection0_uri = assert_create_remote (fixture, "org.example.Collection0", + "exampleos/x86_64/ref0", + "exampleos/x86_64/ref1", + NULL); + g_autofree gchar *collection1_uri = assert_create_remote (fixture, "org.example.Collection1", + "exampleos/x86_64/ref2", + NULL); + g_autofree gchar *no_collection_uri = assert_create_remote (fixture, NULL, + "exampleos/x86_64/ref3", + NULL); + + assert_create_remote_config (fixture->parent_repo, "remote0", collection0_uri, "org.example.Collection0"); + assert_create_remote_config (fixture->parent_repo, "remote1", collection1_uri, "org.example.Collection1"); + assert_create_remote_config (fixture->parent_repo, "remote0-copy", collection0_uri, "org.example.Collection0"); + assert_create_remote_config (fixture->parent_repo, "remote1-bad-copy", collection1_uri, "org.example.NotCollection1"); + assert_create_remote_config (fixture->parent_repo, "remote2", no_collection_uri, NULL); + + finder = ostree_repo_finder_config_new (); + + /* Resolve the refs. */ + ostree_repo_finder_resolve_async (OSTREE_REPO_FINDER (finder), refs, + fixture->parent_repo, NULL, result_cb, &result); + + while (result == NULL) + g_main_context_iteration (context, TRUE); + + results = ostree_repo_finder_resolve_finish (OSTREE_REPO_FINDER (finder), + result, &error); + g_assert_no_error (error); + g_assert_nonnull (results); + g_assert_cmpuint (results->len, ==, 3); + + /* Check that the results are correct: the invalid refs should have been + * ignored, and the valid results canonicalised and deduplicated. */ + for (i = 0; i < results->len; i++) + { + const OstreeRepoFinderResult *result = g_ptr_array_index (results, i); + + if (g_strcmp0 (ostree_remote_get_name (result->remote), "remote0") == 0 || + g_strcmp0 (ostree_remote_get_name (result->remote), "remote0-copy") == 0) + { + g_assert_cmpuint (g_hash_table_size (result->ref_to_checksum), ==, 2); + g_assert_true (g_hash_table_contains (result->ref_to_checksum, &ref0)); + g_assert_true (g_hash_table_contains (result->ref_to_checksum, &ref1)); + } + else if (g_strcmp0 (ostree_remote_get_name (result->remote), "remote1") == 0) + { + g_assert_cmpuint (g_hash_table_size (result->ref_to_checksum), ==, 1); + g_assert_true (g_hash_table_contains (result->ref_to_checksum, &ref3)); + } + else + { + g_assert_not_reached (); + } + } + + g_main_context_pop_thread_default (context); +} + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/repo-finder-config/init", test_repo_finder_config_init); + g_test_add ("/repo-finder-config/no-configs", Fixture, NULL, setup, + test_repo_finder_config_no_configs, teardown); + g_test_add ("/repo-finder-config/mixed-configs", Fixture, NULL, setup, + test_repo_finder_config_mixed_configs, teardown); + + return g_test_run(); +} diff --git a/tests/test-repo-finder-mount.c b/tests/test-repo-finder-mount.c new file mode 100644 index 00000000..c84feb52 --- /dev/null +++ b/tests/test-repo-finder-mount.c @@ -0,0 +1,497 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright © 2017 Endless Mobile, Inc. + * + * 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. + * + * Authors: + * - Philip Withnall + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "libostreetest.h" +#include "ostree-autocleanups.h" +#include "ostree-remote-private.h" +#include "ostree-repo-finder.h" +#include "ostree-repo-finder-mount.h" +#include "ostree-types.h" +#include "test-mock-gio.h" + +/* Test fixture. Creates a temporary directory and repository. */ +typedef struct +{ + OstreeRepo *parent_repo; + int working_dfd; /* owned */ + GFile *working_dir; /* owned */ +} Fixture; + +static void +setup (Fixture *fixture, + gconstpointer test_data) +{ + g_autofree gchar *tmp_name = NULL; + g_autoptr(GError) error = NULL; + + tmp_name = g_strdup ("test-repo-finder-mount-XXXXXX"); + glnx_mkdtempat_open_in_system (tmp_name, 0700, &fixture->working_dfd, &error); + g_assert_no_error (error); + + g_test_message ("Using temporary directory: %s", tmp_name); + + glnx_shutil_mkdir_p_at (fixture->working_dfd, "repo", 0700, NULL, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + g_clear_error (&error); + g_assert_no_error (error); + + g_autoptr(GFile) tmp_dir = g_file_new_for_path (g_get_tmp_dir ()); + fixture->working_dir = g_file_get_child (tmp_dir, tmp_name); + + fixture->parent_repo = ot_test_setup_repo (NULL, &error); + g_assert_no_error (error); +} + +static void +teardown (Fixture *fixture, + gconstpointer test_data) +{ + glnx_fd_close int parent_repo_dfd = -1; + g_autoptr(GError) error = NULL; + + /* Recursively remove the temporary directory. */ + glnx_shutil_rm_rf_at (fixture->working_dfd, ".", NULL, NULL); + + close (fixture->working_dfd); + fixture->working_dfd = -1; + + /* The repo also needs its source files to be removed. This is the inverse + * of setup_test_repository() in libtest.sh. */ + g_autofree gchar *parent_repo_path = g_file_get_path (ostree_repo_get_path (fixture->parent_repo)); + glnx_opendirat (-1, parent_repo_path, TRUE, &parent_repo_dfd, &error); + g_assert_no_error (error); + + glnx_shutil_rm_rf_at (parent_repo_dfd, "../files", NULL, NULL); + glnx_shutil_rm_rf_at (parent_repo_dfd, "../repo", NULL, NULL); + + g_clear_object (&fixture->working_dir); + g_clear_object (&fixture->parent_repo); +} + +/* Test the object constructor works at a basic level. */ +static void +test_repo_finder_mount_init (void) +{ + g_autoptr(OstreeRepoFinderMount) finder = NULL; + g_autoptr(GVolumeMonitor) monitor = NULL; + + /* Default #GVolumeMonitor. */ + finder = ostree_repo_finder_mount_new (NULL); + g_clear_object (&finder); + + /* Explicit #GVolumeMonitor. */ + monitor = ostree_mock_volume_monitor_new (NULL, NULL); + finder = ostree_repo_finder_mount_new (monitor); + g_clear_object (&finder); +} + +static void +result_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GAsyncResult **result_out = user_data; + *result_out = g_object_ref (result); +} + +/* Test that no remotes are found if the #GVolumeMonitor returns no mounts. */ +static void +test_repo_finder_mount_no_mounts (Fixture *fixture, + gconstpointer test_data) +{ + g_autoptr(OstreeRepoFinderMount) finder = NULL; + g_autoptr(GVolumeMonitor) monitor = NULL; + g_autoptr(GMainContext) context = NULL; + g_autoptr(GAsyncResult) result = NULL; + g_autoptr(GPtrArray) results = NULL; /* (element-type OstreeRepoFinderResult) */ + g_autoptr(GError) error = NULL; + const OstreeCollectionRef ref1 = { "org.example.Collection1", "exampleos/x86_64/standard" }; + const OstreeCollectionRef ref2 = { "org.example.Collection1", "exampleos/x86_64/buildmaster/standard" }; + const OstreeCollectionRef ref3 = { "org.example.Collection2", "exampleos/x86_64/standard" }; + const OstreeCollectionRef ref4 = { "org.example.Collection2", "exampleos/arm64/standard" }; + const OstreeCollectionRef * const refs[] = { &ref1, &ref2, &ref3, &ref4, NULL }; + + context = g_main_context_new (); + g_main_context_push_thread_default (context); + + monitor = ostree_mock_volume_monitor_new (NULL, NULL); + finder = ostree_repo_finder_mount_new (monitor); + + ostree_repo_finder_resolve_async (OSTREE_REPO_FINDER (finder), refs, + fixture->parent_repo, + NULL, result_cb, &result); + + while (result == NULL) + g_main_context_iteration (context, TRUE); + + results = ostree_repo_finder_resolve_finish (OSTREE_REPO_FINDER (finder), + result, &error); + g_assert_no_error (error); + g_assert_nonnull (results); + g_assert_cmpuint (results->len, ==, 0); + + g_main_context_pop_thread_default (context); +} + +/* Create a .ostree/repos directory under the given @mount_root, or abort. */ +static gboolean +assert_create_repos_dir (Fixture *fixture, + const gchar *mount_root_name, + int *out_repos_dfd, + GMount **out_mount) +{ + glnx_fd_close int repos_dfd = -1; + g_autoptr(GError) error = NULL; + + g_autofree gchar *path = g_build_filename (mount_root_name, ".ostree", "repos", NULL); + glnx_shutil_mkdir_p_at_open (fixture->working_dfd, path, 0700, &repos_dfd, NULL, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + g_clear_error (&error); + g_assert_no_error (error); + + *out_repos_dfd = glnx_steal_fd (&repos_dfd); + g_autoptr(GFile) mount_root = g_file_get_child (fixture->working_dir, mount_root_name); + *out_mount = G_MOUNT (ostree_mock_mount_new (mount_root_name, mount_root)); + + return TRUE; +} + +/* Create a new repository in @repo_dir with its collection ID unset, and + * containing the refs given in @... (which must be %NULL-terminated). Each + * #OstreeCollectionRef in @... is followed by a gchar** return address for the + * checksum committed for that ref. Return the new repository. */ +static OstreeRepo * +assert_create_remote_va (Fixture *fixture, + GFile *repo_dir, + va_list args) +{ + g_autoptr(GError) error = NULL; + + g_autoptr(OstreeRepo) repo = ostree_repo_new (repo_dir); + ostree_repo_create (repo, OSTREE_REPO_MODE_ARCHIVE_Z2, NULL, &error); + g_assert_no_error (error); + + /* Set up the refs from @.... */ + for (const OstreeCollectionRef *ref = va_arg (args, const OstreeCollectionRef *); + ref != NULL; + ref = va_arg (args, const OstreeCollectionRef *)) + { + g_autofree gchar *checksum = NULL; + g_autoptr(OstreeMutableTree) mtree = NULL; + g_autoptr(OstreeRepoFile) repo_file = NULL; + gchar **out_checksum = va_arg (args, gchar **); + + mtree = ostree_mutable_tree_new (); + ostree_repo_write_dfd_to_mtree (repo, AT_FDCWD, ".", mtree, NULL, NULL, &error); + g_assert_no_error (error); + ostree_repo_write_mtree (repo, mtree, (GFile **) &repo_file, NULL, &error); + g_assert_no_error (error); + + ostree_repo_write_commit (repo, NULL /* no parent */, ref->ref_name, ref->ref_name, + NULL /* no metadata */, repo_file, &checksum, + NULL, &error); + g_assert_no_error (error); + + if (ref->collection_id != NULL) + ostree_repo_set_collection_ref_immediate (repo, ref, checksum, NULL, &error); + else + ostree_repo_set_ref_immediate (repo, NULL, ref->ref_name, checksum, NULL, &error); + g_assert_no_error (error); + + if (out_checksum != NULL) + *out_checksum = g_steal_pointer (&checksum); + } + + /* Update the summary. */ + ostree_repo_regenerate_summary (repo, NULL /* no metadata */, NULL, &error); + g_assert_no_error (error); + + return g_steal_pointer (&repo); +} + +static OstreeRepo * +assert_create_repo_dir (Fixture *fixture, + int repos_dfd, + GMount *repos_mount, + const OstreeCollectionRef *ref, + gchar **out_uri, + ...) G_GNUC_NULL_TERMINATED; + +/* Create a @ref directory under the given @repos_dfd, or abort. Create a new + * repository in it with the refs given in @..., as per assert_create_remote_va(). + * Return the URI of the repository. */ +static OstreeRepo * +assert_create_repo_dir (Fixture *fixture, + int repos_dfd, + GMount *repos_mount, + const OstreeCollectionRef *ref, + gchar **out_uri, + ...) +{ + glnx_fd_close int ref_dfd = -1; + g_autoptr(OstreeRepo) repo = NULL; + g_autoptr(GError) error = NULL; + va_list args; + + g_autofree gchar *path = g_build_filename (ref->collection_id, ref->ref_name, NULL); + glnx_shutil_mkdir_p_at_open (repos_dfd, path, 0700, &ref_dfd, NULL, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + g_clear_error (&error); + g_assert_no_error (error); + + g_autoptr(GFile) mount_root = g_mount_get_root (repos_mount); + g_autoptr(GFile) repos_dir = g_file_get_child (mount_root, ".ostree/repos"); + g_autoptr(GFile) repo_dir = g_file_get_child (repos_dir, path); + + va_start (args, out_uri); + repo = assert_create_remote_va (fixture, repo_dir, args); + va_end (args); + + *out_uri = g_file_get_uri (repo_dir); + + return g_steal_pointer (&repo); +} + +/* Create a @ref symlink under the given @repos_dfd, pointing to + * @symlink_target, or abort. */ +static int +assert_create_repo_symlink (int repos_dfd, + const OstreeCollectionRef *ref, + const gchar *symlink_target_path) +{ + glnx_fd_close int symlink_target_dfd = -1; + g_autoptr(GError) error = NULL; + + /* The @ref_parent_dir is not necessarily @collection_dir, since @ref may + * contain slashes. */ + g_autofree gchar *path = g_build_filename (ref->collection_id, ref->ref_name, NULL); + g_autofree gchar *path_parent = g_path_get_dirname (path); + + glnx_shutil_mkdir_p_at (repos_dfd, path_parent, 0700, NULL, &error); + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) + g_clear_error (&error); + g_assert_no_error (error); + + if (TEMP_FAILURE_RETRY (symlinkat (symlink_target_path, repos_dfd, path)) != 0) + { + g_autoptr(GError) error = NULL; + glnx_throw_errno_prefix (&error, "symlinkat"); + g_assert_no_error (error); + } + + /* Return a dir FD for the symlink target. */ + glnx_opendirat (repos_dfd, path, TRUE, &symlink_target_dfd, &error); + g_assert_no_error (error); + + return glnx_steal_fd (&symlink_target_dfd); +} + +/* Add configuration for a remote named @remote_name, at @remote_uri, with a + * remote collection ID of @collection_id, to the given @repo. */ +static void +assert_create_remote_config (OstreeRepo *repo, + const gchar *remote_name, + const gchar *remote_uri, + const gchar *collection_id) +{ + g_autoptr(GError) error = NULL; + g_autoptr(GVariant) options = NULL; + + if (collection_id != NULL) + options = g_variant_new_parsed ("@a{sv} { 'collection-id': <%s> }", + collection_id); + + ostree_repo_remote_add (repo, remote_name, remote_uri, options, NULL, &error); + g_assert_no_error (error); +} + +/* Test resolving the refs against a collection of mock volumes, some of which + * are mounted, some of which are removable, some of which contain valid or + * invalid repo information on the file system, etc. */ +static void +test_repo_finder_mount_mixed_mounts (Fixture *fixture, + gconstpointer test_data) +{ + g_autoptr(OstreeRepoFinderMount) finder = NULL; + g_autoptr(GVolumeMonitor) monitor = NULL; + g_autoptr(GMainContext) context = NULL; + g_autoptr(GAsyncResult) result = NULL; + g_autoptr(GPtrArray) results = NULL; /* (element-type OstreeRepoFinderResult) */ + g_autoptr(GError) error = NULL; + g_autoptr(GList) mounts = NULL; /* (element-type OstreeMockMount) */ + g_autoptr(GMount) non_removable_mount = NULL; + g_autoptr(GMount) no_repos_mount = NULL; + g_autoptr(GMount) repo1_mount = NULL; + g_autoptr(GMount) repo2_mount = NULL; + g_autoptr(GFile) non_removable_root = NULL; + glnx_fd_close int no_repos_repos = -1; + glnx_fd_close int repo1_repos = -1; + glnx_fd_close int repo2_repos = -1; + g_autoptr(OstreeRepo) repo1_repo_a = NULL, repo1_repo_b = NULL; + g_autoptr(OstreeRepo) repo2_repo_a = NULL; + g_autofree gchar *repo1_repo_a_uri = NULL, *repo1_repo_b_uri = NULL; + g_autofree gchar *repo2_repo_a_uri = NULL; + g_autofree gchar *repo1_ref0_checksum = NULL, *repo1_ref1_checksum = NULL, *repo1_ref2_checksum = NULL; + g_autofree gchar *repo2_ref0_checksum = NULL, *repo2_ref1_checksum = NULL, *repo2_ref2_checksum = NULL; + g_autofree gchar *repo1_ref5_checksum = NULL; + gsize i; + const OstreeCollectionRef ref0 = { "org.example.Collection1", "exampleos/x86_64/ref0" }; + const OstreeCollectionRef ref1 = { "org.example.Collection1", "exampleos/x86_64/ref1" }; + const OstreeCollectionRef ref2 = { "org.example.Collection1", "exampleos/x86_64/ref2" }; + const OstreeCollectionRef ref3 = { "org.example.Collection1", "exampleos/x86_64/ref3" }; + const OstreeCollectionRef ref4 = { "org.example.UnconfiguredCollection", "exampleos/x86_64/ref4" }; + const OstreeCollectionRef ref5 = { "org.example.Collection3", "exampleos/x86_64/ref0" }; + const OstreeCollectionRef * const refs[] = { &ref0, &ref1, &ref2, &ref3, &ref4, &ref5, NULL }; + + context = g_main_context_new (); + g_main_context_push_thread_default (context); + + /* Build the various mock drives/volumes/mounts, and some repositories with + * refs within them. We use "/" under the assumption that it’s on a separate + * file system from /tmp, so it’s an example of a symlink pointing outside + * its mount point. */ + non_removable_root = g_file_get_child (fixture->working_dir, "non-removable-mount"); + non_removable_mount = G_MOUNT (ostree_mock_mount_new ("non-removable", non_removable_root)); + + assert_create_repos_dir (fixture, "no-repos-mount", &no_repos_repos, &no_repos_mount); + + assert_create_repos_dir (fixture, "repo1-mount", &repo1_repos, &repo1_mount); + repo1_repo_a = assert_create_repo_dir (fixture, repo1_repos, repo1_mount, refs[0], &repo1_repo_a_uri, + refs[0], &repo1_ref0_checksum, + refs[2], &repo1_ref2_checksum, + refs[5], &repo1_ref5_checksum, + NULL); + repo1_repo_b = assert_create_repo_dir (fixture, repo1_repos, repo1_mount, refs[1], &repo1_repo_b_uri, + refs[1], &repo1_ref1_checksum, + NULL); + assert_create_repo_symlink (repo1_repos, refs[2], "ref0"); /* repo1_repo_a */ + assert_create_repo_symlink (repo1_repos, refs[5], "../../../org.example.Collection1/exampleos/x86_64/ref0"); /* repo1_repo_a */ + + assert_create_repos_dir (fixture, "repo2-mount", &repo2_repos, &repo2_mount); + repo2_repo_a = assert_create_repo_dir (fixture, repo2_repos, repo2_mount, refs[0], &repo2_repo_a_uri, + refs[0], &repo2_ref0_checksum, + refs[1], &repo2_ref1_checksum, + refs[2], &repo2_ref2_checksum, + refs[3], NULL, + NULL); + assert_create_repo_symlink (repo2_repos, refs[1], "ref0"); /* repo2_repo_a */ + assert_create_repo_symlink (repo2_repos, refs[2], "ref1"); /* repo2_repo_b */ + assert_create_repo_symlink (repo2_repos, refs[3], "/"); + + mounts = g_list_prepend (mounts, non_removable_mount); + mounts = g_list_prepend (mounts, no_repos_mount); + mounts = g_list_prepend (mounts, repo1_mount); + mounts = g_list_prepend (mounts, repo2_mount); + + monitor = ostree_mock_volume_monitor_new (mounts, NULL); + finder = ostree_repo_finder_mount_new (monitor); + + assert_create_remote_config (fixture->parent_repo, "remote1", "https://nope1", "org.example.Collection1"); + assert_create_remote_config (fixture->parent_repo, "remote2", "https://nope2", "org.example.Collection2"); + /* don’t configure org.example.UnconfiguredCollection */ + assert_create_remote_config (fixture->parent_repo, "remote3", "https://nope3", "org.example.Collection3"); + + /* Resolve the refs. */ + ostree_repo_finder_resolve_async (OSTREE_REPO_FINDER (finder), refs, + fixture->parent_repo, + NULL, result_cb, &result); + + while (result == NULL) + g_main_context_iteration (context, TRUE); + + results = ostree_repo_finder_resolve_finish (OSTREE_REPO_FINDER (finder), + result, &error); + g_assert_no_error (error); + g_assert_nonnull (results); + g_assert_cmpuint (results->len, ==, 4); + + /* Check that the results are correct: the invalid refs should have been + * ignored, and the valid results canonicalised and deduplicated. */ + for (i = 0; i < results->len; i++) + { + g_autofree gchar *uri = NULL; + const gchar *keyring; + const OstreeRepoFinderResult *result = g_ptr_array_index (results, i); + + uri = g_key_file_get_string (result->remote->options, result->remote->group, "url", &error); + g_assert_no_error (error); + keyring = result->remote->keyring; + + if (g_strcmp0 (uri, repo1_repo_a_uri) == 0 && + g_strcmp0 (keyring, "remote1.trustedkeys.gpg") == 0) + { + g_assert_cmpuint (g_hash_table_size (result->ref_to_checksum), ==, 2); + g_assert_cmpstr (g_hash_table_lookup (result->ref_to_checksum, refs[0]), ==, repo1_ref0_checksum); + g_assert_cmpstr (g_hash_table_lookup (result->ref_to_checksum, refs[2]), ==, repo1_ref2_checksum); + } + else if (g_strcmp0 (uri, repo1_repo_a_uri) == 0 && + g_strcmp0 (keyring, "remote3.trustedkeys.gpg") == 0) + { + g_assert_cmpuint (g_hash_table_size (result->ref_to_checksum), ==, 1); + g_assert_cmpstr (g_hash_table_lookup (result->ref_to_checksum, refs[5]), ==, repo1_ref5_checksum); + } + else if (g_strcmp0 (uri, repo1_repo_b_uri) == 0 && + g_strcmp0 (keyring, "remote1.trustedkeys.gpg") == 0) + { + g_assert_cmpuint (g_hash_table_size (result->ref_to_checksum), ==, 1); + g_assert_cmpstr (g_hash_table_lookup (result->ref_to_checksum, refs[1]), ==, repo1_ref1_checksum); + } + else if (g_strcmp0 (uri, repo2_repo_a_uri) == 0 && + g_strcmp0 (keyring, "remote1.trustedkeys.gpg") == 0) + { + g_assert_cmpuint (g_hash_table_size (result->ref_to_checksum), ==, 3); + g_assert_cmpstr (g_hash_table_lookup (result->ref_to_checksum, refs[0]), ==, repo2_ref0_checksum); + g_assert_cmpstr (g_hash_table_lookup (result->ref_to_checksum, refs[1]), ==, repo2_ref1_checksum); + g_assert_cmpstr (g_hash_table_lookup (result->ref_to_checksum, refs[2]), ==, repo2_ref2_checksum); + } + else + { + g_test_message ("Unknown result ‘%s’ with keyring ‘%s’.", + result->remote->name, result->remote->keyring); + g_assert_not_reached (); + } + } + + g_main_context_pop_thread_default (context); +} + +int main (int argc, char **argv) +{ + setlocale (LC_ALL, ""); + g_test_init (&argc, &argv, NULL); + + g_test_add_func ("/repo-finder-mount/init", test_repo_finder_mount_init); + g_test_add ("/repo-finder-mount/no-mounts", Fixture, NULL, setup, + test_repo_finder_mount_no_mounts, teardown); + g_test_add ("/repo-finder-mount/mixed-mounts", Fixture, NULL, setup, + test_repo_finder_mount_mixed_mounts, teardown); + + return g_test_run(); +} diff --git a/tests/test-summary-update.sh b/tests/test-summary-update.sh new file mode 100755 index 00000000..457debbe --- /dev/null +++ b/tests/test-summary-update.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# +# Copyright © 2017 Endless Mobile, Inc. +# +# 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. +# +# Authors: +# - Philip Withnall + +set -euo pipefail + +. $(dirname $0)/libtest.sh + +echo "1..2" + +cd ${test_tmpdir} +mkdir repo +ostree_repo_init repo + +mkdir -p tree/root +touch tree/root/a + +# Add a few commits +seq 5 | while read i; do + echo a >> tree/root/a + ${CMD_PREFIX} ostree --repo=repo commit --branch=test-$i -m test -s test tree +done + +# Generate a plain summary file. +${CMD_PREFIX} ostree --repo=repo summary --update + +# Generate a signed summary file. +${CMD_PREFIX} ostree --repo=repo summary --update --gpg-homedir=${TEST_GPG_KEYHOME} --gpg-sign=${TEST_GPG_KEYID_1} + +# Try various ways of adding additional data. +${CMD_PREFIX} ostree --repo=repo summary --update --add-metadata key="'value'" --add-metadata=key2=true +${CMD_PREFIX} ostree --repo=repo summary --update -m some-int='@t 123' +${CMD_PREFIX} ostree --repo=repo summary --update --add-metadata=map='@a{sv} {}' + +# Check the additional metadata turns up in the output. +${CMD_PREFIX} ostree --repo=repo summary --view > summary +assert_file_has_content summary "^map: {}$" + +echo "ok 1 update summary" + +# Test again, but with collections enabled in the repository (if supported). +if ! ostree --version | grep -q -e '- experimental'; then + echo "ok 2 # skip No experimental API is compiled in" + exit 0 +fi + +cd ${test_tmpdir} +rm -rf repo +ostree_repo_init repo --collection-id org.example.Collection1 + +mkdir -p tree/root +touch tree/root/a + +# Add a few commits +seq 5 | while read i; do + echo a >> tree/root/a + ${CMD_PREFIX} ostree --repo=repo commit --branch=test-$i -m test -s test tree + ${CMD_PREFIX} ostree --repo=repo refs --collections --create=org.example.Collection2:test-$i test-$i +done + +# Generate a plain summary file. +${CMD_PREFIX} ostree --repo=repo summary --update + +# Generate a signed summary file. +${CMD_PREFIX} ostree --repo=repo summary --update --gpg-homedir=${TEST_GPG_KEYHOME} --gpg-sign=${TEST_GPG_KEYID_1} + +# Try various ways of adding additional data. +${CMD_PREFIX} ostree --repo=repo summary --update --add-metadata key="'value'" --add-metadata=key2=true +${CMD_PREFIX} ostree --repo=repo summary --update -m some-int='@t 123' +${CMD_PREFIX} ostree --repo=repo summary --update --add-metadata=map='@a{sv} {}' + +# Check the additional metadata turns up in the output. +${CMD_PREFIX} ostree --repo=repo summary --view > summary +assert_file_has_content summary "^map: {}$" + +echo "ok 2 update summary with collections" diff --git a/tests/test-switchroot.sh b/tests/test-switchroot.sh index fd5a30d0..bc3ec38b 100755 --- a/tests/test-switchroot.sh +++ b/tests/test-switchroot.sh @@ -127,6 +127,8 @@ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then . $(dirname $0)/libtest.sh unshare -m true || \ skip "this test needs to set up mount namespaces, rerun as root" + [ -f /bin/busybox ] || \ + skip "this test needs busybox" echo "1..3" test_that_prepare_root_sets_sysroot_up_correctly_with_initrd diff --git a/tests/test-symbols.sh b/tests/test-symbols.sh index d22231d0..fcc345f0 100755 --- a/tests/test-symbols.sh +++ b/tests/test-symbols.sh @@ -22,7 +22,11 @@ set -xeuo pipefail echo '1..3' released_syms=${G_TEST_SRCDIR}/src/libostree/libostree-released.sym -devel_syms=${G_TEST_SRCDIR}/src/libostree/libostree-devel.sym +if echo "$OSTREE_FEATURES" | grep --quiet --no-messages "devel"; then + devel_syms=${G_TEST_SRCDIR}/src/libostree/libostree-devel.sym +else + devel_syms= +fi if echo "$OSTREE_FEATURES" | grep --quiet --no-messages "experimental"; then experimental_sym="${G_TEST_SRCDIR}/src/libostree/libostree-experimental.sym" experimental_sections="${G_TEST_SRCDIR}/apidoc/ostree-experimental-sections.txt" @@ -48,7 +52,7 @@ echo 'ok documented symbols' # ONLY update this checksum in release commits! cat > released-sha256.txt <