diff --git a/Makefile-boot.am b/Makefile-boot.am index 7ec54fa3..8b62d1ba 100644 --- a/Makefile-boot.am +++ b/Makefile-boot.am @@ -21,7 +21,8 @@ if BUILDOPT_DRACUT # Not using $(libdir) here is intentional, dracut modules go in prefix/lib dracutmoddir = $(prefix)/lib/dracut/modules.d/98ostree dracutmod_SCRIPTS = src/boot/dracut/module-setup.sh - +endif +if BUILDOPT_DRACUT_CONF dracutconfdir = $(sysconfdir)/dracut.conf.d dracutconf_DATA = src/boot/dracut/ostree.conf endif diff --git a/Makefile-decls.am b/Makefile-decls.am index d8ec5ab4..f7ebd422 100644 --- a/Makefile-decls.am +++ b/Makefile-decls.am @@ -18,7 +18,7 @@ # Common variables AM_CPPFLAGS = AM_CFLAGS = -DISTCHECK_CONFIGURE_FLAGS = +AM_DISTCHECK_CONFIGURE_FLAGS = SUBDIRS = NULL = BUILT_SOURCES = diff --git a/Makefile-libostree-defines.am b/Makefile-libostree-defines.am index f623900c..2d478bb6 100644 --- a/Makefile-libostree-defines.am +++ b/Makefile-libostree-defines.am @@ -21,7 +21,9 @@ libostree_public_headers = \ src/libostree/ostree.h \ src/libostree/ostree-async-progress.h \ + src/libostree/ostree-autocleanups.h \ src/libostree/ostree-core.h \ + src/libostree/ostree-dummy-enumtypes.h \ src/libostree/ostree-mutable-tree.h \ src/libostree/ostree-repo.h \ src/libostree/ostree-types.h \ diff --git a/Makefile-libostree.am b/Makefile-libostree.am index a50b2b9d..d6b83528 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -33,9 +33,11 @@ lib_LTLIBRARIES += libostree-1.la libostreeincludedir = $(includedir)/ostree-1 libostreeinclude_HEADERS = $(libostree_public_headers) -ENUM_TYPES = \ - $(srcdir)/src/libostree/ostree-fetcher.h \ - $(NULL) +ENUM_TYPES = $(NULL) + +if USE_LIBSOUP +ENUM_TYPES += $(srcdir)/src/libostree/ostree-fetcher.h +endif src/libostree/ostree-enumtypes.h: src/libostree/ostree-enumtypes.h.template $(ENUM_TYPES) $(AM_V_GEN) $(GLIB_MKENUMS) \ @@ -48,12 +50,14 @@ src/libostree/ostree-enumtypes.c: src/libostree/ostree-enumtypes.c.template $(EN --fhead "#include \"ostree-enumtypes.h\"" \ $(ENUM_TYPES) > $@.tmp && mv $@.tmp $@ +if USE_LIBSOUP ENUM_GENERATED = \ src/libostree/ostree-enumtypes.h \ src/libostree/ostree-enumtypes.c \ $(NULL) BUILT_SOURCES += $(ENUM_GENERATED) +endif CLEANFILES += $(BUILT_SOURCES) @@ -69,6 +73,7 @@ libostree_1_la_SOURCES = \ src/libostree/ostree-cmdprivate.c \ src/libostree/ostree-core-private.h \ src/libostree/ostree-core.c \ + src/libostree/ostree-dummy-enumtypes.c \ src/libostree/ostree-checksum-input-stream.c \ src/libostree/ostree-checksum-input-stream.h \ src/libostree/ostree-chain-input-stream.c \ @@ -90,6 +95,7 @@ libostree_1_la_SOURCES = \ src/libostree/ostree-repo.c \ src/libostree/ostree-repo-checkout.c \ src/libostree/ostree-repo-commit.c \ + src/libostree/ostree-repo-pull.c \ src/libostree/ostree-repo-libarchive.c \ src/libostree/ostree-repo-prune.c \ src/libostree/ostree-repo-refs.c \ @@ -123,6 +129,7 @@ libostree_1_la_SOURCES = \ src/libostree/ostree-gpg-verifier.h \ src/libostree/ostree-gpg-verify-result.c \ src/libostree/ostree-gpg-verify-result-private.h \ + src/libostree/ostree-autocleanups.h \ $(NULL) if USE_LIBARCHIVE libostree_1_la_SOURCES += src/libostree/ostree-libarchive-input-stream.h \ @@ -142,6 +149,8 @@ libostree_1_la_CFLAGS = $(AM_CFLAGS) -I$(srcdir)/bsdiff -I$(srcdir)/libglnx -I$( libostree_1_la_LDFLAGS = -version-number 1:0:0 -Bsymbolic-functions -Wl,--version-script=$(top_srcdir)/src/libostree/libostree.sym libostree_1_la_LIBADD = libotutil.la libbupsplit.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) +EXTRA_DIST += src/libostree/libostree.sym + if USE_LIBARCHIVE libostree_1_la_CFLAGS += $(OT_DEP_LIBARCHIVE_CFLAGS) libostree_1_la_LIBADD += $(OT_DEP_LIBARCHIVE_LIBS) @@ -153,7 +162,6 @@ libostree_1_la_SOURCES += \ src/libostree/ostree-fetcher.c \ src/libostree/ostree-metalink.h \ src/libostree/ostree-metalink.c \ - src/libostree/ostree-repo-pull.c \ $(NULL) libostree_1_la_CFLAGS += $(OT_INTERNAL_SOUP_CFLAGS) libostree_1_la_LIBADD += $(OT_INTERNAL_SOUP_LIBS) diff --git a/Makefile-tests.am b/Makefile-tests.am index b3d75142..80903071 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -19,18 +19,25 @@ include $(top_srcdir)/buildutil/glib-tap.mk +EXTRA_DIST += \ + buildutil/tap-driver.sh \ + buildutil/tap-test \ + $(NULL) + # We should probably consider flipping the default for DEBUG. Also, # include the builddir in $PATH so we find our just-built ostree # binary. TESTS_ENVIRONMENT += OT_TESTS_DEBUG=1 \ GI_TYPELIB_PATH=$$(cd $(top_builddir) && pwd) \ LD_LIBRARY_PATH=$$(cd $(top_builddir)/.libs && pwd) \ - PATH=$$(cd $(top_builddir) && pwd):$${PATH} \ + PATH=$$(cd $(top_builddir)/tests && pwd):$${PATH} \ $(NULL) -uninstalled_test_scripts = tests/test-abi.sh +uninstalled_test_data = tests/ostree-symlink-stamp -test_scripts = \ +dist_uninstalled_test_scripts = tests/test-symbols.sh + +dist_test_scripts = \ tests/test-basic.sh \ tests/test-pull-subpath.sh \ tests/test-archivez.sh \ @@ -51,6 +58,7 @@ test_scripts = \ tests/test-pull-summary-sigs.sh \ tests/test-pull-resume.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 \ @@ -62,6 +70,7 @@ test_scripts = \ tests/test-admin-deploy-etcmerge-cornercases.sh \ tests/test-admin-deploy-uboot.sh \ tests/test-admin-deploy-grub2.sh \ + tests/test-admin-deploy-bootid-gc.sh \ tests/test-admin-instutil-set-kargs.sh \ tests/test-admin-upgrade-not-backwards.sh \ tests/test-admin-pull-deploy-commit.sh \ @@ -70,7 +79,6 @@ test_scripts = \ tests/test-repo-checkout-subpath.sh \ tests/test-reset-nonlinear.sh \ tests/test-oldstyle-partial.sh \ - tests/test-setuid.sh \ tests/test-delta.sh \ tests/test-xattrs.sh \ tests/test-auto-summary.sh \ @@ -80,15 +88,15 @@ test_scripts = \ $(NULL) if BUILDOPT_FUSE -test_scripts += tests/test-rofiles-fuse.sh +dist_test_scripts += tests/test-rofiles-fuse.sh endif # This one uses corrupt-repo-ref.js if BUILDOPT_GJS -test_scripts += tests/test-corruption.sh +dist_test_scripts += tests/test-corruption.sh endif -installed_test_data = tests/archive-test.sh \ +dist_installed_test_data = tests/archive-test.sh \ tests/pull-test.sh \ tests/libtest.sh \ tests/admin-test.sh \ @@ -99,29 +107,29 @@ installed_test_data = tests/archive-test.sh \ tests/pre-endian-deltas-repo-little.tar.xz \ $(NULL) -test_extra_scripts = tests/bootloader-entries-crosscheck.py \ +dist_test_extra_scripts = tests/bootloader-entries-crosscheck.py \ tests/ostree-grub-generator # We can't use nobase_ as we need to strip off the tests/, can't # use plain installed_ as we do need the gpghome/ prefix. if ENABLE_INSTALLED_TESTS gpginsttestdir = $(installed_testdir)/gpghome -gpginsttest_DATA = tests/gpghome/secring.gpg \ +dist_gpginsttest_DATA = tests/gpghome/secring.gpg \ tests/gpghome/pubring.gpg \ tests/gpghome/trustdb.gpg \ tests/gpghome/key1.asc \ tests/gpghome/key2.asc \ tests/gpghome/key3.asc gpginsttest_trusteddir = $(installed_testdir)/gpghome/trusted -gpginsttest_trusted_DATA = tests/gpghome/trusted/pubring.gpg +dist_gpginsttest_trusted_DATA = tests/gpghome/trusted/pubring.gpg gpgvinsttestdir = $(installed_testdir)/gpg-verify-data -gpgvinsttest_DATA = $(addprefix tests/gpg-verify-data/, \ +dist_gpgvinsttest_DATA = $(addprefix tests/gpg-verify-data/, \ gpg.conf lgpl2 lgpl2.sig pubring.gpg secring.gpg trustdb.gpg) endif if BUILDOPT_GJS -installed_test_scripts = tests/test-core.js \ +dist_installed_test_scripts = tests/test-core.js \ tests/test-sizes.js \ tests/test-sysroot.js \ $(NULL) @@ -129,9 +137,12 @@ endif test_ltlibraries = libreaddir-rand.la libreaddir_rand_la_SOURCES = tests/readdir-rand.c -libreaddir_rand_la_CFLAGS = $(OT_INTERNAL_GIO_UNIX_CFLAGS) -libreaddir_rand_la_LIBADD = $(OT_INTERNAL_GIO_UNIX_LIBS) -libreaddir_rand_la_LDFLAGS = -avoid-version +libreaddir_rand_la_CFLAGS = $(AM_CFLAGS) $(OT_INTERNAL_GIO_UNIX_CFLAGS) +libreaddir_rand_la_LIBADD = \ + -ldl \ + $(OT_INTERNAL_GIO_UNIX_LIBS) \ + $(NULL) +libreaddir_rand_la_LDFLAGS = $(AM_LDFLAGS) -avoid-version if !ENABLE_INSTALLED_TESTS libreaddir_rand_la_LDFLAGS += -rpath $(abs_builddir) endif @@ -139,7 +150,7 @@ endif 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-basic-c tests/test-sysroot-c tests/test-pull-c # An interactive tool noinst_PROGRAMS += tests/test-rollsum-cli @@ -176,6 +187,9 @@ tests_test_basic_c_LDADD = $(TESTS_LDADD) tests_test_sysroot_c_CFLAGS = $(TESTS_CFLAGS) tests_test_sysroot_c_LDADD = $(TESTS_LDADD) +tests_test_pull_c_CFLAGS = $(TESTS_CFLAGS) +tests_test_pull_c_LDADD = $(TESTS_LDADD) + tests_test_ot_unix_utils_CFLAGS = $(TESTS_CFLAGS) tests_test_ot_unix_utils_LDADD = $(TESTS_LDADD) @@ -216,6 +230,7 @@ tests_test_gpg_verify_result_LDADD = $(TESTS_LDADD) $(OT_INTERNAL_GPGME_LIBS) EXTRA_DIST += \ tests/libostreetest.h \ + tests/libtest.sh \ tests/gpg-verify-data/README.md \ tests/gpg-verify-data/lgpl2 \ tests/gpg-verify-data/lgpl2.sig \ @@ -227,7 +242,12 @@ EXTRA_DIST += \ tests/libreaddir-rand.so: Makefile $(AM_V_GEN) ln -fns ../.libs/libreaddir-rand.so tests ALL_LOCAL_RULES += tests/libreaddir-rand.so -CLEANFILES += tests/libreaddir-rand.so +CLEANFILES += tests/libreaddir-rand.so tests/ostree-symlink-stamp tests/ostree + +tests/ostree-symlink-stamp: Makefile + @real_bin=`cd $(top_builddir) && libtool --mode=execute echo ostree`; \ + ln -sf "$${real_bin}" tests/ostree; \ + touch $@ # Unfortunately the glib test data APIs don't actually handle # non-recursive Automake, so we change our code to canonically look diff --git a/Makefile.am b/Makefile.am index 488d4b6d..1de31544 100644 --- a/Makefile.am +++ b/Makefile.am @@ -24,11 +24,12 @@ AM_CPPFLAGS += -DDATADIR='"$(datadir)"' -DLIBEXECDIR='"$(libexecdir)"' \ -DLOCALEDIR=\"$(datadir)/locale\" -DSYSCONFDIR=\"$(sysconfdir)\" \ -DSHORTENED_SYSCONFDIR=\"$(shortened_sysconfdir)\" \ -DOSTREE_FEATURES='"$(OSTREE_FEATURES)"' \ + -DOSTREE_COMPILATION \ -DG_LOG_DOMAIN=\"OSTree\" \ -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_40 -DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_40 \ -DSOUP_VERSION_MIN_REQUIRED=SOUP_VERSION_2_40 -DSOUP_VERSION_MAX_ALLOWED=SOUP_VERSION_2_48 -AM_CFLAGS += $(WARN_CFLAGS) -DISTCHECK_CONFIGURE_FLAGS += --enable-gtk-doc --disable-maintainer-mode +AM_CFLAGS += -std=gnu99 $(WARN_CFLAGS) +AM_DISTCHECK_CONFIGURE_FLAGS += --enable-gtk-doc --disable-maintainer-mode GITIGNOREFILES = aclocal.m4 build-aux/ buildutil/*.m4 config.h.in gtk-doc.make @@ -71,7 +72,9 @@ include Makefile-otutil.am include Makefile-libostree.am include Makefile-ostree.am include Makefile-switchroot.am +if BUILDOPT_FUSE include src/rofiles-fuse/Makefile-inc.am +endif include Makefile-tests.am include Makefile-boot.am include Makefile-man.am diff --git a/README.md b/README.md index 15cba7ab..caa8503d 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ provide a minimal host for Docker formatted Linux containers. Replicating a base immutable OS, then using Docker for applications meshes together two different tools with different tradeoffs. -[xdg-app](https://github.com/alexlarsson/xdg-app) uses OSTree +[flatpak](https://github.com/alexlarsson/xdg-app) uses OSTree for desktop application containers. [GNOME Continuous](https://wiki.gnome.org/Projects/GnomeContinuous) is diff --git a/apidoc/.gitignore b/apidoc/.gitignore index 77cacd4d..80d2c112 100644 --- a/apidoc/.gitignore +++ b/apidoc/.gitignore @@ -32,6 +32,7 @@ /ostree.args /ostree.hierarchy /ostree.interfaces +/ostree-overrides.txt /ostree.pdf /ostree.prerequisites /ostree.signals @@ -48,4 +49,5 @@ /tmpl/*.bak /tmpl/ostree-unused.sgml /xml +/version.xml _libs diff --git a/apidoc/ostree-docs.xml b/apidoc/ostree-docs.xml index c5ea28e6..8721ffa8 100644 --- a/apidoc/ostree-docs.xml +++ b/apidoc/ostree-docs.xml @@ -13,25 +13,23 @@ API Reference - - - - - - - - - - - - - - - + + + + + + + + + + + + + + API Index - diff --git a/apidoc/ostree-sections.txt b/apidoc/ostree-sections.txt index 1eef5da7..6dca9ecc 100644 --- a/apidoc/ostree-sections.txt +++ b/apidoc/ostree-sections.txt @@ -1,5 +1,5 @@
-libostree-async-progress +ostree-async-progress OstreeAsyncProgress ostree_async_progress_new ostree_async_progress_new_and_connect @@ -22,12 +22,14 @@ ostree_async_progress_get_type
-libostree-bootconfig-parser +ostree-bootconfig-parser OstreeBootconfigParser ostree_bootconfig_parser_new ostree_bootconfig_parser_clone ostree_bootconfig_parser_parse +ostree_bootconfig_parser_parse_at ostree_bootconfig_parser_write +ostree_bootconfig_parser_write_at ostree_bootconfig_parser_set ostree_bootconfig_parser_get @@ -38,7 +40,7 @@ ostree_bootconfig_parser_get_type
-libostree-chain-input-stream +ostree-chain-input-stream OstreeChainInputStream ostree_chain_input_stream_new @@ -54,7 +56,7 @@ ostree_chain_input_stream_get_type
-libostree-checksum-input-stream +ostree-checksum-input-stream OstreeChecksumInputStream ostree_checksum_input_stream_new @@ -70,7 +72,7 @@ ostree_checksum_input_stream_get_type
-libostree-core +ostree-core OSTREE_MAX_METADATA_SIZE OSTREE_MAX_METADATA_WARN_SIZE OSTREE_MAX_RECURSION @@ -97,10 +99,11 @@ ostree_checksum_inplace_from_bytes ostree_checksum_inplace_to_bytes ostree_checksum_bytes_peek ostree_checksum_bytes_peek_validate +ostree_checksum_b64_inplace_from_bytes +ostree_checksum_b64_inplace_to_bytes ostree_cmp_checksum_bytes ostree_validate_rev ostree_parse_refspec -ostree_checksum_update_meta ostree_object_type_to_string ostree_object_type_from_string ostree_hash_object_name @@ -110,6 +113,8 @@ ostree_object_to_string ostree_object_from_string ostree_content_stream_parse ostree_content_file_parse +ostree_content_file_parse_at +ostree_raw_file_to_archive_z2_stream ostree_raw_file_to_content_stream ostree_checksum_file_from_input ostree_checksum_file @@ -128,7 +133,7 @@ ostree_commit_get_timestamp
-libostree-deployment +ostree-deployment OstreeDeployment ostree_deployment_hash ostree_deployment_equal @@ -141,11 +146,14 @@ ostree_deployment_get_bootcsum ostree_deployment_get_bootserial ostree_deployment_get_bootconfig ostree_deployment_get_origin +ostree_deployment_get_origin_relpath +ostree_deployment_get_unlocked ostree_deployment_set_index ostree_deployment_set_bootserial ostree_deployment_set_bootconfig ostree_deployment_set_origin ostree_deployment_clone +ostree_deployment_unlocked_state_to_string OSTREE_DEPLOYMENT OSTREE_IS_DEPLOYMENT @@ -154,7 +162,7 @@ ostree_deployment_get_type
-libostree-diff +ostree-diff OstreeDiffFlags OstreeDiffItem ostree_diff_item_ref @@ -166,7 +174,7 @@ ostree_diff_item_get_type
-libostree-gpg-verify-result +ostree-gpg-verify-result OstreeGpgVerifyResult OstreeGpgSignatureAttr ostree_gpg_verify_result_count_all @@ -177,6 +185,7 @@ ostree_gpg_verify_result_get_all OstreeGpgSignatureFormatFlags ostree_gpg_verify_result_describe ostree_gpg_verify_result_describe_variant +ostree_gpg_verify_result_require_valid_signature OSTREE_GPG_VERIFY_RESULT OSTREE_IS_GPG_VERIFY_RESULT @@ -184,8 +193,33 @@ OSTREE_TYPE_GPG_VERIFY_RESULT ostree_gpg_verify_result_get_type
+ostree-lzma-compressor + +OSTREE_IS_LZMA_COMPRESSOR +OSTREE_IS_LZMA_COMPRESSOR_CLASS +OSTREE_LZMA_COMPRESSOR +OSTREE_LZMA_COMPRESSOR_CLASS +OSTREE_LZMA_COMPRESSOR_GET_CLASS +OSTREE_TYPE_LZMA_COMPRESSOR +OstreeLzmaCompressor +OstreeLzmaCompressorClass + +
-libostree-mutable-tree +ostree-lzma-decompressor + +OSTREE_IS_LZMA_DECOMPRESSOR +OSTREE_IS_LZMA_DECOMPRESSOR_CLASS +OSTREE_LZMA_DECOMPRESSOR +OSTREE_LZMA_DECOMPRESSOR_CLASS +OSTREE_LZMA_DECOMPRESSOR_GET_CLASS +OSTREE_TYPE_LZMA_DECOMPRESSOR +OstreeLzmaDecompressor +OstreeLzmaDecompressorClass +
+ +
+ostree-mutable-tree OstreeMutableTree ostree_mutable_tree_new ostree_mutable_tree_set_metadata_checksum @@ -211,7 +245,7 @@ ostree_mutable_tree_get_type
-libostree-repo +ostree-repo OstreeRepo OstreeRepoMode ostree_repo_mode_from_string @@ -227,6 +261,7 @@ ostree_repo_create ostree_repo_get_path ostree_repo_get_mode ostree_repo_get_config +ostree_repo_get_dfd ostree_repo_copy_config ostree_repo_remote_add ostree_repo_remote_delete @@ -238,6 +273,10 @@ ostree_repo_remote_get_gpg_verify ostree_repo_remote_get_gpg_verify_summary ostree_repo_remote_gpg_import ostree_repo_remote_fetch_summary +ostree_repo_remote_fetch_summary_with_options +ostree_repo_get_remote_boolean_option +ostree_repo_get_remote_list_option +ostree_repo_get_remote_option ostree_repo_get_parent ostree_repo_write_config OstreeRepoTransactionStats @@ -248,6 +287,8 @@ ostree_repo_abort_transaction ostree_repo_transaction_set_refspec ostree_repo_transaction_set_ref ostree_repo_set_ref_immediate +ostree_repo_set_cache_dir +ostree_repo_sign_delta ostree_repo_has_object ostree_repo_write_metadata ostree_repo_write_metadata_async @@ -270,6 +311,8 @@ ostree_repo_load_object_stream ostree_repo_query_object_storage_size ostree_repo_import_object_from ostree_repo_import_object_from_with_trust +ostree_repo_import_archive_to_mtree +ostree_repo_export_tree_to_archive ostree_repo_delete_object OstreeRepoCommitFilterResult OstreeRepoCommitFilter @@ -285,6 +328,7 @@ ostree_repo_commit_modifier_unref ostree_repo_devino_cache_new ostree_repo_devino_cache_ref ostree_repo_devino_cache_unref +ostree_repo_devino_cache_get_type ostree_repo_write_directory_to_mtree ostree_repo_write_dfd_to_mtree ostree_repo_write_archive_to_mtree @@ -310,8 +354,16 @@ ostree_repo_static_delta_execute_offline ostree_repo_traverse_new_reachable ostree_repo_traverse_commit ostree_repo_traverse_commit_union +ostree_repo_commit_traverse_iter_cleanup +ostree_repo_commit_traverse_iter_clear +ostree_repo_commit_traverse_iter_get_dir +ostree_repo_commit_traverse_iter_get_file +ostree_repo_commit_traverse_iter_init_commit +ostree_repo_commit_traverse_iter_init_dirtree +ostree_repo_commit_traverse_iter_next OstreeRepoPruneFlags ostree_repo_prune +ostree_repo_prune_static_deltas OstreeRepoPullFlags ostree_repo_pull ostree_repo_pull_one_dir @@ -319,6 +371,8 @@ ostree_repo_pull_with_options ostree_repo_pull_default_console_progress_changed ostree_repo_sign_commit ostree_repo_append_gpg_signature +ostree_repo_add_gpg_signature_summary +ostree_repo_gpg_verify_data ostree_repo_verify_commit ostree_repo_verify_commit_ext ostree_repo_verify_summary @@ -333,13 +387,12 @@ ostree_repo_transaction_stats_get_type
-libostree-repo-file +ostree-repo-file OstreeRepoFile ostree_repo_file_ensure_resolved ostree_repo_file_get_xattrs ostree_repo_file_get_repo ostree_repo_file_get_root -ostree_repo_file_make_empty_tree ostree_repo_file_tree_set_metadata ostree_repo_file_tree_get_contents_checksum ostree_repo_file_tree_get_metadata_checksum @@ -360,14 +413,17 @@ ostree_repo_file_get_type
-libostree-sepolicy +ostree-sepolicy OstreeSePolicy ostree_sepolicy_new ostree_sepolicy_get_path ostree_sepolicy_get_name ostree_sepolicy_get_label +ostree_sepolicy_get_csum OstreeSePolicyRestoreconFlags ostree_sepolicy_restorecon +ostree_sepolicy_setfscreatecon +ostree_sepolicy_fscreatecon_cleanup OSTREE_SEPOLICY OSTREE_IS_SEPOLICY @@ -376,7 +432,7 @@ ostree_sepolicy_get_type
-libostree-sysroot +ostree-sysroot OstreeSysroot ostree_sysroot_new ostree_sysroot_new_default @@ -388,6 +444,7 @@ ostree_sysroot_try_lock ostree_sysroot_lock_async ostree_sysroot_lock_finish ostree_sysroot_unlock +ostree_sysroot_unload ostree_sysroot_get_fd ostree_sysroot_ensure_initialized ostree_sysroot_get_bootversion @@ -395,13 +452,17 @@ ostree_sysroot_get_subbootversion ostree_sysroot_get_deployments ostree_sysroot_get_booted_deployment ostree_sysroot_get_deployment_directory +ostree_sysroot_get_deployment_dirpath ostree_sysroot_get_deployment_origin_path ostree_sysroot_cleanup ostree_sysroot_prepare_cleanup ostree_sysroot_get_repo ostree_sysroot_init_osname ostree_sysroot_deployment_set_kargs +ostree_sysroot_deployment_set_mutable +ostree_sysroot_deployment_unlock ostree_sysroot_write_deployments +ostree_sysroot_write_origin_file ostree_sysroot_deploy_tree ostree_sysroot_get_merge_deployment ostree_sysroot_origin_new_from_refspec @@ -415,7 +476,7 @@ ostree_sysroot_get_type
-libostree-sysroot-upgrader +ostree-sysroot-upgrader OstreeSysrootUpgrader ostree_sysroot_upgrader_new ostree_sysroot_upgrader_new_for_os diff --git a/autogen.sh b/autogen.sh index 0eb65550..581f3dee 100755 --- a/autogen.sh +++ b/autogen.sh @@ -33,8 +33,8 @@ if ! test -f libglnx/README.md || ! test -f bsdiff/README.md; then git submodule update --init fi # Workaround automake bug with subdir-objects and computed paths -sed -e 's,$(libglnx_srcpath),'${srcdir}/libglnx,g < libglnx/Makefile-libglnx.am >libglnx/Makefile-libglnx.am.inc -sed -e 's,$(libbsdiff_srcpath),'${srcdir}/bsdiff,g < bsdiff/Makefile-bsdiff.am >bsdiff/Makefile-bsdiff.am.inc +sed -e 's,$(libglnx_srcpath),libglnx,g' < libglnx/Makefile-libglnx.am >libglnx/Makefile-libglnx.am.inc +sed -e 's,$(libbsdiff_srcpath),bsdiff,g' < bsdiff/Makefile-bsdiff.am >bsdiff/Makefile-bsdiff.am.inc autoreconf --force --install --verbose diff --git a/configure.ac b/configure.ac index dca9f536..18f9f277 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ([2.63]) -AC_INIT([ostree], [2016.5], [walters@verbum.org]) +AC_INIT([ostree], [2016.6], [walters@verbum.org]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([buildutil]) AC_CONFIG_AUX_DIR([build-aux]) @@ -38,13 +38,15 @@ GLIB_TESTS AC_CHECK_HEADER([sys/xattr.h],,[AC_MSG_ERROR([You must have sys/xattr.h from glibc])]) -AC_CHECK_PROGS(YACC, 'bison -y', :) -AS_IF([test "$YACC" = :], [AC_MSG_ERROR([bison not found but required])]) +AS_IF([test "$YACC" != "bison -y"], [AC_MSG_ERROR([bison not found but required])]) PKG_PROG_PKG_CONFIG AM_PATH_GLIB_2_0 +dnl When bumping the gio-unix-2.0 dependency (or glib-2.0 in general), +dnl remember to bump GLIB_VERSION_MIN_REQUIRED and +dnl GLIB_VERSION_MAX_ALLOWED in Makefile.am GIO_DEPENDENCY="gio-unix-2.0 >= 2.40.0 libgsystem >= 2015.1" PKG_CHECK_MODULES(OT_DEP_GIO_UNIX, $GIO_DEPENDENCY) @@ -57,6 +59,9 @@ PKG_CHECK_MODULES(OT_DEP_ZLIB, zlib) dnl We're not actually linking to this, just using the header PKG_CHECK_MODULES(OT_DEP_E2P, e2p) +dnl When bumping the libsoup-2.4 dependency, remember to bump +dnl SOUP_VERSION_MIN_REQUIRED and SOUP_VERSION_MAX_ALLOWED in +dnl Makefile.am SOUP_DEPENDENCY="libsoup-2.4 >= 2.39.1" AC_ARG_WITH(soup, AS_HELP_STRING([--with-soup], [Use libsoup @<:@default=yes@:>@]), @@ -222,7 +227,7 @@ AC_ARG_ENABLE(rofiles-fuse, [AS_HELP_STRING([--enable-rofiles-fuse], [generate rofiles-fuse helper [default=yes]])],, enable_rofiles_fuse=yes) -AS_IF([ test $enable_rofiles_fuse != xno ], [ +AS_IF([ test x$enable_rofiles_fuse != xno ], [ PKG_CHECK_MODULES(BUILDOPT_FUSE, $FUSE_DEPENDENCY) ], [enable_rofiles_fuse=no]) AM_CONDITIONAL(BUILDOPT_FUSE, test x$enable_rofiles_fuse = xyes) @@ -231,7 +236,14 @@ AC_ARG_WITH(dracut, AS_HELP_STRING([--with-dracut], [Install dracut module (default: no)]),, [with_dracut=no]) -AM_CONDITIONAL(BUILDOPT_DRACUT, test x$with_dracut = xyes) +case x$with_dracut in + xno) ;; + xyes) ;; + xyesbutnoconf) ;; + *) AC_MSG_ERROR([Unknown --with-dracut value $with_dracut]) +esac +AM_CONDITIONAL(BUILDOPT_DRACUT, test x$with_dracut = xyes || test x$with_dracut = xyesbutnoconf) +AM_CONDITIONAL(BUILDOPT_DRACUT_CONF, test x$with_dracut = xyes) AC_ARG_WITH(mkinitcpio, AS_HELP_STRING([--with-mkinitcpio], @@ -239,7 +251,7 @@ AC_ARG_WITH(mkinitcpio, [with_mkinitcpio=no]) AM_CONDITIONAL(BUILDOPT_MKINITCPIO, test x$with_mkinitcpio = xyes) -AS_IF([test "x$with_dracut" = "xyes" || test "x$with_mkinitcpio" = "xyes"], [ +AS_IF([test "x$with_dracut" = "xyes" || test "x$with_dracut" = "xyesbutnoconf" || test "x$with_mkinitcpio" = "xyes"], [ with_systemd=yes AC_ARG_WITH([systemdsystemunitdir], AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files]), diff --git a/docs/manual/atomic-upgrades.md b/docs/manual/atomic-upgrades.md index b5f398d6..40515b83 100644 --- a/docs/manual/atomic-upgrades.md +++ b/docs/manual/atomic-upgrades.md @@ -45,7 +45,7 @@ operate "live" on the currently booted filesystem. The way they could work with OSTree is instead to take the list of installed packages in the currently booted tree, and compute a new filesystem from that. A later chapter describes in more details how this could work: -[adapting-existing.md](Adapting Existing Systems). +[Adapting Existing Systems](adapting-existing.md). For the purposes of this section, let's assume that we have a newly generated filesystem tree stored in the repo (which shares @@ -56,7 +56,7 @@ checking it back out of the repo into a deployment. Given a commit to deploy, OSTree first allocates a directory for it. This is of the form `/boot/loader/entries/ostree-$osname-$checksum.$serial.conf`. -The `$serial` is normally 0, but if a +The `$serial` is normally `0`, but if a given commit is deployed more than once, it will be incremented. This is supported because the previous deployment may have configuration in `/etc` that we do not want to use or overwrite. diff --git a/docs/manual/formats.md b/docs/manual/formats.md index e689f8a8..87d0005f 100644 --- a/docs/manual/formats.md +++ b/docs/manual/formats.md @@ -125,8 +125,7 @@ the client executes. This "updates as code" model allows for multiple content generation strategies. The design of this was inspired by that of Chromium: -[http://dev.chromium.org/chromium-os/chromiumos-design-docs/filesystem-autoupdate](ChromiumOS -autoupdate). +[ChromiumOS Autoupdate](http://dev.chromium.org/chromium-os/chromiumos-design-docs/filesystem-autoupdate). ### The delta superblock diff --git a/docs/manual/related-projects.md b/docs/manual/related-projects.md index 896c7655..d37e2cfe 100644 --- a/docs/manual/related-projects.md +++ b/docs/manual/related-projects.md @@ -78,6 +78,10 @@ available. All of the above also applies if one replaces "BTRFS" with "LVM snapshots" except for the reflinks. +OSTree supports using "bare-user" repositories, which do not require +root to use. Using a filesystem-level layer without root is more +difficult and would likely require a setuid helper or privileged service. + Finally, see the next portion around ChromiumOS for why a hybrid but integrated package/image system improves on this. @@ -130,6 +134,11 @@ believes that at the moment, the "CL updater" is not truly atomic in the sense that because it applies updates live, there is a window where the OS root may be inconsistent. +## Mender.io + +[Mender.io](https://mender.io/) is another implementation of the dual +partition approach. + ## OLPC update OSTree is basically a generalization of olpc-update, except using @@ -147,37 +156,54 @@ See [this comment](http://blog.verbum.org/2013/08/26/ostree-v2013-6-released/#comment-1169) for a comparison. -## NixOS +## NixOS / Nix -See [NixOS](http://nixos.org/). It was a very influential project for -OSTree. NixOS and OSTree both support the idea of independent "roots" -that are bootable. +See [NixOS](http://nixos.org/). It was a very influential project for OSTree. +NixOS and OSTree both support the idea of independent "roots" that are bootable. -In NixOS, the entire system is based on checksums of package inputs -(build dependencies) - see [Nix store](http://nixos.org/nix/manual/#chap-package-management/). A both -positive and negative of the Nix model is that a change in the build -dependencies (e.g. being built with a newer gcc), requires a cascading -rebuild of everything. +In NixOS, files in a package are accessed by a path depending on the checksums +of package inputs (build dependencies) - see +[Nix store](http://nixos.org/nix/manual/#chap-package-management/). +However, OSTree uses a commit/deploy model - it isn't tied to any particular +directory layout, and you can put whatever data you want inside an OSTree, for +example the standard FHS layout. A both positive and negative of the Nix model +is that a change in the build dependencies (e.g. being built with a newer gcc), +requires a cascading rebuild of everything. It's good because it makes it easy +to do massive system-wide changes such as gcc upgrades, and allows installing +multiple versions of packages at once. However, a security update to e.g. glibc +forces a rebuild of everything from scratch, and so Nix is not practical at +scale. OSTree supports using a build system that just rebuilds individual +components (packages) as they change, without forcing a rebuild of their +dependencies. -In OSTree, the checksums are of object *content* (including extended -attributes). This means that any data that's identical is -transparently, automatically shared on disk. It's possible to ask the -Nix store to deduplicate, (via hard links and immutable bit), but this -is significantly less efficient than the OSTree approach. The Nix use -of the ext immutable bit is racy, since it has to be briefly removed -to make a hard link. +Nix automatically detects runtime package dependencies by scanning content for +hashes. OSTree only supports only system-level images, and doesn't do dependency +management. Nix can store arbitrary files, using nix-store --add, but, more +commonly, paths are added as the result of running a derivation file generated +using the Nix language. OSTree is build-system agnostic; filesystem trees are +committed using a simple C API, and this is the only way to commit files. -At the lowest level, OSTree is just "git for binaries" - it isn't tied -strongly to any particular build system. You can put whatever data you -want inside an OSTree repository, built however you like. So for -example, while one could make a build system that did the "purely -functional" approach of Nix, it also works to have a build system that -just rebuilds individual components (packages) as they change, without -forcing a rebuild of their dependencies. +OSTree automatically shares the storage of identical data using hard links into +a content-addressed store. Nix can deduplicate using hard links as well, using +the auto-optimise-store option, but this is not on by default, and Nix does not +guarantee that all of its files are in the content-addressed store. OSTree +provides a git-like command line interface for browsing the content-addressed +store, while Nix does not have this functionality. -The author of OSTree believes that while Nix has some good ideas, -forcing a rebuild of everything for a security update to e.g. glibc is -not practical at scale. +Nix used to use the immutable bit to prevent modifications to /nix/store, but +now it uses a read-only bind mount. The bind mount can be privately remounted, +allowing per-process privileged write access. OSTree uses the immutable +bit on the root of the deployment, and mounts /usr as read-only. + +NixOS supports switching OS images on-the-fly, by maintaining both booted-system +and current-system roots. It is not clear how well this approach works. OSTree +currently requries a reboot to switch images. + +Finally, NixOS supports installing user-specific packages from trusted +repositories without requiring root, using a trusted daemon. +[Flatpak](https://lwn.net/Articles/687909/), based on OSTree, similarly has a +policykit-based system helper that allows you to authenticate via polkit to +install into the system repository. ## Solaris IPS @@ -205,3 +231,40 @@ See [bmap](https://source.tizen.org/documentation/reference/bmaptool/introduction). A tool for optimized copying of disk images. Intended for offline use, so not directly comparable. + +## Git + +Although OSTree has been called "Git for Binaries", and the two share the idea +of a hashed content store, the implementation details are quite different. +OSTree supports extended attributes and uses SHA256 instead of Git's SHA1. It +"checks out" files via hardlinks, rather than copying, and thus requires the +checkout to be immutable. At the moment, OSTree commits may have at most one +parent, as opposed to Git which allows an arbitrary number. Git uses a +smart-delta protocol for updates, while OSTree uses 1 HTTP request per changed +file, or can generate static deltas. + +## Conda + +[Conda](http://conda.pydata.org/docs/) is an "OS-agnostic, system-level binary +package manager and ecosystem"; although most well-known for its accompanying +Python distribution anaconda, its scope has been expanding quickly. The package +format is very similar to well-known ones such as RPM. However, unlike typical +RPMs, the packages are built to be relocatable. Also, the package manager runs +natively on Windows. Conda's main advantage is its ability to install +collections of packages into "environments" by unpacking them all to the same +directory. Conda reduces duplication across environments using hardlinks, +similar to OSTree's sharing between deployments (although Conda uses package / +file path instead of file hash). Overall, it is quite similar to rpm-ostree in +functionality and scope. + +## rpm-ostree + +This builds on top of ostree to support building RPMs into OSTree images, and +even composing RPMs on-the-fly using an overlay filesystem. It is being +developed by Fedora, Red Hat, and CentOS as part of Project Atomic. + +## GNOME Continuous + +This is a service that incrementally rebuilds and tests GNOME on every commit. +The need to make and distribute snapshots for this system was the original +inspiration for ostree. diff --git a/docs/manual/repo.md b/docs/manual/repo.md index d3be549c..bce7e0c9 100644 --- a/docs/manual/repo.md +++ b/docs/manual/repo.md @@ -47,6 +47,22 @@ payload sections. The header contains uid, gid, mode, and symbolic link target (for symlinks), as well as extended attributes. After the header, for regular files, the content follows. +The OSTree data format intentionally does not contain timestamps. The reasoning +is that data files may be downloaded at different times, and by different build +systems, and so will have different timestamps but identical physical content. +These files may be large, so most users would like them to be shared, both in +the repository and between the repository and deployments. + +This could cause problems with programs that check if files are out-of-date by +comparing timestamps. For Git, the logical choice is to not mess with +timestamps, because unnecessary rebuilding is better than a broken tree. +However, OSTree has to hardlink files to check them out, and commits are assumed +to be internally consistent with no build steps needed. For this reason, OSTree +acts as though all timestamps are set to time_t 1, so that comparisons will be +considered up-to-date. 1 is a better choice than 0 because some programs use 0 +as a special value; for example, GNU Tar warns of an "implausibly old time +stamp" with 0. + # Repository types and locations Also unlike git, an OSTree repository can be in one of three separate @@ -98,7 +114,7 @@ that. A later addition to OSTree is the concept of a "summary" file, created via the `ostree summary -u` command. This was introduced for a few reasons. A primary use case is to be a target a -(Metalink)[https://en.wikipedia.org/wiki/Metalink], which requires a +[Metalink](https://en.wikipedia.org/wiki/Metalink), which requires a single file with a known checksum as a target. The summary file primarily contains two mappings: diff --git a/docs/manual/repository-management.md b/docs/manual/repository-management.md index b83f6c15..b6da9629 100644 --- a/docs/manual/repository-management.md +++ b/docs/manual/repository-management.md @@ -4,11 +4,12 @@ Once you have a build system going, if you actually want client systems to retrieve the content, you will quickly feel a need for "repository management". -OSTree itself does not currently come with tools to do this. One -reason is that how content is delivered and managed has concerns very -specific to the organization. For example, some operating system -content vendors may want integration with a specific errata -notification system. +The command line tool `ostree` does cover some core functionality, but +doesn't include very high level workflows. One reason is that how +content is delivered and managed has concerns very specific to the +organization. For example, some operating system content vendors may +want integration with a specific errata notification system when +generating commits. In this section, we will describe some high level ideas and methods for managing content in OSTree repositories, mostly independent of any @@ -21,6 +22,27 @@ repositories today is the [Pulp Project](http://www.pulpproject.org/), which has a [Pulp OSTree plugin](https://pulp-ostree.readthedocs.org/en/latest/). +## Mirroring repositories + +It's very common to want to perform a full or partial mirror, in +particular across organizational boundaries (e.g. an upstream OS +provider, and a user that wants offline and faster access to the +content). OSTree supports both full and partial mirroring of the base +`archive-z2` content, although not yet of static deltas. + +To create a mirror, first create an `archive-z2` repository (you don't +need to run this as root), then add the upstream as a remote, then use +`pull --mirror`. + +``` +ostree --repo=repo init --mode=archive-z2 +ostree --repo=repo remote add exampleos https://exampleos.com/ostree/repo +ostree --repo=repo pull --mirror exampleos:exampleos/x86_64/standard +``` + +You can use the `--depth=-1` option to retrieve all history, or a +positive integer like `3` to retrieve just the last 3 commits. + ## Separate development vs release repositories By default, OSTree accumulates server side history. This is actually diff --git a/libglnx/glnx-console.c b/libglnx/glnx-console.c index 39733118..d40702ed 100644 --- a/libglnx/glnx-console.c +++ b/libglnx/glnx-console.c @@ -177,22 +177,9 @@ printpad (const char *padbuf, fwrite (padbuf, 1, r, stdout); } -/** - * glnx_console_progress_text_percent: - * @text: Show this text before the progress bar - * @percentage: An integer in the range of 0 to 100 - * - * On a tty, print to the console @text followed by an ASCII art - * progress bar whose percentage is @percentage. If stdout is not a - * tty, a more basic line by line change will be printed. - * - * You must have called glnx_console_lock() before invoking this - * function. - * - */ -void -glnx_console_progress_text_percent (const char *text, - guint percentage) +static void +text_percent_internal (const char *text, + int percentage) { static const char equals[] = "===================="; const guint n_equals = sizeof (equals) - 1; @@ -201,10 +188,6 @@ glnx_console_progress_text_percent (const char *text, const guint ncolumns = glnx_console_columns (); const guint bar_min = 10; const guint input_textlen = text ? strlen (text) : 0; - guint textlen; - guint barlen; - - g_return_if_fail (percentage >= 0 && percentage <= 100); if (text && !*text) text = NULL; @@ -236,36 +219,69 @@ glnx_console_progress_text_percent (const char *text, (void) fwrite (beginbuf, 1, sizeof (beginbuf), stdout); } - textlen = MIN (input_textlen, ncolumns - bar_min); - barlen = ncolumns - textlen; - - if (textlen > 0) + if (percentage == -1) { - fwrite (text, 1, textlen, stdout); - fputc (' ', stdout); + const guint spacelen = ncolumns - input_textlen; + fwrite (text, 1, input_textlen, stdout); + printpad (spaces, n_spaces, spacelen); } - - { - const guint nbraces = 2; - const guint textpercent_len = 5; - const guint bar_internal_len = barlen - nbraces - textpercent_len; - const guint eqlen = bar_internal_len * (percentage / 100.0); - const guint spacelen = bar_internal_len - eqlen; + else + { + const guint textlen = MIN (input_textlen, ncolumns - bar_min); + const guint barlen = ncolumns - (textlen + 1);; - fputc ('[', stdout); - printpad (equals, n_equals, eqlen); - printpad (spaces, n_spaces, spacelen); - fputc (']', stdout); - fprintf (stdout, " %3d%%", percentage); - } + if (textlen > 0) + { + fwrite (text, 1, textlen, stdout); + fputc (' ', stdout); + } - { const guint spacelen = ncolumns - textlen - barlen; - printpad (spaces, n_spaces, spacelen); - } + { + const guint nbraces = 2; + const guint textpercent_len = 5; + const guint bar_internal_len = barlen - nbraces - textpercent_len; + const guint eqlen = bar_internal_len * (percentage / 100.0); + const guint spacelen = bar_internal_len - eqlen; + + fputc ('[', stdout); + printpad (equals, n_equals, eqlen); + printpad (spaces, n_spaces, spacelen); + fputc (']', stdout); + fprintf (stdout, " %3d%%", percentage); + } + } fflush (stdout); } +/** + * glnx_console_progress_text_percent: + * @text: Show this text before the progress bar + * @percentage: An integer in the range of 0 to 100 + * + * On a tty, print to the console @text followed by an ASCII art + * progress bar whose percentage is @percentage. If stdout is not a + * tty, a more basic line by line change will be printed. + * + * You must have called glnx_console_lock() before invoking this + * function. + * + */ +void +glnx_console_progress_text_percent (const char *text, + guint percentage) +{ + g_return_if_fail (percentage >= 0 && percentage <= 100); + + text_percent_internal (text, percentage); +} + +void +glnx_console_text (const char *text) +{ + text_percent_internal (text, -1); +} + /** * glnx_console_unlock: * diff --git a/libglnx/glnx-console.h b/libglnx/glnx-console.h index 8fc38656..8c1d8115 100644 --- a/libglnx/glnx-console.h +++ b/libglnx/glnx-console.h @@ -33,6 +33,8 @@ typedef struct GLnxConsoleRef GLnxConsoleRef; void glnx_console_lock (GLnxConsoleRef *ref); +void glnx_console_text (const char *text); + void glnx_console_progress_text_percent (const char *text, guint percentage); diff --git a/libglnx/glnx-dirfd.c b/libglnx/glnx-dirfd.c index 4861ccfe..95d7fe17 100644 --- a/libglnx/glnx-dirfd.c +++ b/libglnx/glnx-dirfd.c @@ -277,20 +277,17 @@ glnx_fdrel_abspath (int dfd, } /** - * glnx_mkdtempat: - * @dfd: Directory fd - * @tmpl: (type filename): template directory name - * @mode: permissions to create the temporary directory with - * @error: Error + * glnx_gen_temp_name: + * @tmpl: (type filename): template directory name, the last 6 characters will be replaced * - * Similar to g_mkdtemp_full, but using openat. + * Replace the last 6 characters of @tmpl with random ASCII. You must + * use this in combination with a mechanism to ensure race-free file + * creation such as `O_EXCL`. */ -gboolean -glnx_mkdtempat (int dfd, - gchar *tmpl, - int mode, - GError **error) +void +glnx_gen_temp_name (gchar *tmpl) { + size_t len; char *XXXXXX; int count; static const char letters[] = @@ -300,17 +297,11 @@ glnx_mkdtempat (int dfd, GTimeVal tv; static int counter = 0; - g_return_val_if_fail (tmpl != NULL, -1); + g_return_if_fail (tmpl != NULL); + len = strlen (tmpl); + g_return_if_fail (len >= 6); - /* find the last occurrence of "XXXXXX" */ - XXXXXX = g_strrstr (tmpl, "XXXXXX"); - - if (!XXXXXX || strncmp (XXXXXX, "XXXXXX", 6)) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, - "Invalid temporary directory template '%s'", tmpl); - return FALSE; - } + XXXXXX = tmpl + (len - 6); /* Get some more or less random data. */ g_get_current_time (&tv); @@ -332,6 +323,31 @@ glnx_mkdtempat (int dfd, XXXXXX[4] = letters[v % NLETTERS]; v /= NLETTERS; XXXXXX[5] = letters[v % NLETTERS]; + } +} + +/** + * glnx_mkdtempat: + * @dfd: Directory fd + * @tmpl: (type filename): template directory name, last 6 characters will be replaced + * @mode: permissions to create the temporary directory with + * @error: Error + * + * Similar to g_mkdtemp_full, but using openat. + */ +gboolean +glnx_mkdtempat (int dfd, + gchar *tmpl, + int mode, + GError **error) +{ + int count; + + g_return_val_if_fail (tmpl != NULL, -1); + + for (count = 0; count < 100; count++) + { + glnx_gen_temp_name (tmpl); if (mkdirat (dfd, tmpl, mode) == -1) { diff --git a/libglnx/glnx-dirfd.h b/libglnx/glnx-dirfd.h index c13e3dc5..3a766952 100644 --- a/libglnx/glnx-dirfd.h +++ b/libglnx/glnx-dirfd.h @@ -81,6 +81,8 @@ gboolean glnx_opendirat (int dfd, char *glnx_fdrel_abspath (int dfd, const char *path); +void glnx_gen_temp_name (gchar *tmpl); + gboolean glnx_mkdtempat (int dfd, gchar *tmpl, int mode, diff --git a/libglnx/glnx-fdio.c b/libglnx/glnx-fdio.c index 466cbc4e..cdbb69fe 100644 --- a/libglnx/glnx-fdio.c +++ b/libglnx/glnx-fdio.c @@ -746,5 +746,35 @@ glnx_file_replace_contents_with_perms_at (int dfd, ret = TRUE; out: + if (!ret) + (void) unlink (tmppath); return ret; } + +/** + * glnx_stream_fstat: + * @stream: A stream containing a Unix file descriptor + * @stbuf: Memory location to write stat buffer + * @error: + * + * Some streams created via libgsystem are #GUnixInputStream; these do + * not support e.g. g_file_input_stream_query_info(). This function + * allows dropping to the raw unix fstat() call for these types of + * streams, while still conveniently wrapped with the normal GLib + * handling of @error. + */ +gboolean +glnx_stream_fstat (GFileDescriptorBased *stream, + struct stat *stbuf, + GError **error) +{ + int fd = g_file_descriptor_based_get_fd (stream); + + if (fstat (fd, stbuf) == -1) + { + glnx_set_prefix_error_from_errno (error, "%s", "fstat"); + return FALSE; + } + + return TRUE; +} diff --git a/libglnx/glnx-fdio.h b/libglnx/glnx-fdio.h index c0fd4e48..3ca1a660 100644 --- a/libglnx/glnx-fdio.h +++ b/libglnx/glnx-fdio.h @@ -21,6 +21,7 @@ #pragma once #include +#include #include #include #include @@ -121,4 +122,9 @@ glnx_file_copy_at (int src_dfd, GCancellable *cancellable, GError **error); +gboolean +glnx_stream_fstat (GFileDescriptorBased *stream, + struct stat *stbuf, + GError **error); + G_END_DECLS diff --git a/libglnx/glnx-libcontainer.c b/libglnx/glnx-libcontainer.c index 8c0f3407..38c1937f 100644 --- a/libglnx/glnx-libcontainer.c +++ b/libglnx/glnx-libcontainer.c @@ -274,6 +274,14 @@ glnx_libcontainer_run_chroot_private (const char *dest, if (chdir ("/") != 0) _perror_fatal ("chdir: "); + /* Environment variables like PATH in the end are distribution + * specific. The most correct thing would be to run through PAM, + * but that's a huge level of pain. We'd like to drive towards a + * standard /usr/bin (i.e. unified sbin too), but for now this is + * pretty compatible. + */ + setenv ("PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 1); + if (binary[0] == '/') { if (execv (binary, argv) != 0) @@ -281,9 +289,6 @@ glnx_libcontainer_run_chroot_private (const char *dest, } else { - /* Set PATH to something sane. */ - setenv ("PATH", "/usr/sbin:/usr/bin", 1); - if (execvp (binary, argv) != 0) _perror_fatal ("execvp: "); } diff --git a/libglnx/glnx-local-alloc.h b/libglnx/glnx-local-alloc.h index af5af4b8..f628b61c 100644 --- a/libglnx/glnx-local-alloc.h +++ b/libglnx/glnx-local-alloc.h @@ -21,6 +21,7 @@ #pragma once #include +#include G_BEGIN_DECLS @@ -195,13 +196,17 @@ GLNX_DEFINE_CLEANUP_FUNCTION0(GKeyFile*, glnx_local_keyfile_unref, g_key_file_un static inline void glnx_cleanup_close_fdp (int *fdp) { - int fd; + int fd, errsv; g_assert (fdp); fd = *fdp; if (fd != -1) - (void) close (fd); + { + errsv = errno; + (void) close (fd); + errno = errsv; + } } /** diff --git a/man/ostree-commit.xml b/man/ostree-commit.xml index 4a86c1d2..8f0037f6 100644 --- a/man/ostree-commit.xml +++ b/man/ostree-commit.xml @@ -57,7 +57,7 @@ Boston, MA 02111-1307, USA. Description - This allows you to commit changes to a branch. The specification of the branch is required. If no commit message is specified with then a text editor will be opened. The commit will be aborted if the commit subject is left empty. The command will print the checksum of a successful commit. + This allows you to commit changes to a branch. The specification of the branch is required. The command will print the checksum of a successful commit. @@ -68,7 +68,7 @@ Boston, MA 02111-1307, USA. , ="SUBJECT" - One line subject. + One line subject. (optional) @@ -76,7 +76,15 @@ Boston, MA 02111-1307, USA. , ="BODY" - Full description. + Full description. (optional) + + + + + , + + + Open a text editor for the commit description. It will use OSTREE_EDITOR, VISUAL, EDITOR, or vi, in descending order of preference. The commit will be aborted if the message is left empty. @@ -84,7 +92,7 @@ Boston, MA 02111-1307, USA. , ="BRANCH" - Branch. + Branch. Required, unless --orphan is given. @@ -163,7 +171,15 @@ Boston, MA 02111-1307, USA. ="PATH" - File containing list of modifications to make permissions. + File containing list of modifications to make permissions (file mode, followed by space, followed by file path). + + + + + ="PATH" + + + File containing list of file paths to skip (one path per line). @@ -206,6 +222,22 @@ Boston, MA 02111-1307, USA. Override the timestamp of the commit to TIMESTAMP. + + + + + + Create a commit without writing to a ref (branch) + + + + + ="POLICY" + + + POLICY is a boolean which specifies whether fsync should be used or not. Default to true. + + diff --git a/packaging/ostree.spec.in b/packaging/ostree.spec.in index 2d6a2660..2f07f0a8 100644 --- a/packaging/ostree.spec.in +++ b/packaging/ostree.spec.in @@ -72,7 +72,7 @@ env NOCONFIGURE=1 ./autogen.sh %configure --disable-silent-rules \ --enable-gtk-doc \ --with-selinux \ - --with-dracut + --with-dracut=yesbutnoconf make %{?_smp_mflags} %install @@ -94,7 +94,6 @@ rm -rf $RPM_BUILD_ROOT %{_bindir}/ostree %{_sbindir}/ostree-prepare-root %{_sbindir}/ostree-remount -%{_sysconfdir}/dracut.conf.d/ostree.conf %dir %{_prefix}/lib/dracut/modules.d/98ostree %{_prefix}/lib/systemd/system/ostree*.service %{_prefix}/lib/dracut/modules.d/98ostree/* diff --git a/src/libostree/libostree.sym b/src/libostree/libostree.sym index 89a1457e..e8cca9d8 100644 --- a/src/libostree/libostree.sym +++ b/src/libostree/libostree.sym @@ -323,11 +323,6 @@ global: ostree_deployment_unlocked_state_to_string; } LIBOSTREE_2016.3; -/* NOTE NOTE NOTE - * Versions above here are released. Only add symbols below this line. - * NOTE NOTE NOTE - */ - LIBOSTREE_2016.5 { global: ostree_repo_import_object_from_with_trust; @@ -337,3 +332,24 @@ global: ostree_repo_get_remote_boolean_option; ostree_repo_set_cache_dir; } LIBOSTREE_2016.4; + +LIBOSTREE_2016.6 { +global: + ostree_gpg_verify_result_require_valid_signature; + ostree_raw_file_to_archive_z2_stream; + ostree_repo_gpg_verify_data; + ostree_repo_remote_fetch_summary_with_options; +} LIBOSTREE_2016.5; + +/* NOTE NOTE NOTE + * Versions above here are released. Only add symbols below this line. + * NOTE NOTE NOTE + */ + +/* Uncomment this when adding a new symbol */ +/* +LIBOSTREE_2016.7 { +global: + ostree_some_new_symbol; +} LIBOSTREE_2016.6; +*/ diff --git a/src/libostree/ostree-async-progress.c b/src/libostree/ostree-async-progress.c index 59656fc9..0851fd86 100644 --- a/src/libostree/ostree-async-progress.c +++ b/src/libostree/ostree-async-progress.c @@ -23,7 +23,7 @@ #include "ostree-async-progress.h" /** - * SECTION:libostree-async-progress + * SECTION:ostree-async-progress * @title: Progress notification system for asynchronous operations * @short_description: Values representing progress * diff --git a/src/libostree/ostree-autocleanups.h b/src/libostree/ostree-autocleanups.h new file mode 100644 index 00000000..7301ef1d --- /dev/null +++ b/src/libostree/ostree-autocleanups.h @@ -0,0 +1,68 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2016 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. + * + * Author: Krzesimir Nowak + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#ifndef OSTREE_WITH_AUTOCLEANUPS +#define OSTREE_WITH_AUTOCLEANUPS 0 +#endif + +/* ostree can use g_autoptr backports from libglnx when glib is too + * old, but still avoid exposing them to users that also have an old + * glib */ +#if defined(OSTREE_COMPILATION) || (OSTREE_WITH_AUTOCLEANUPS && GLIB_CHECK_VERSION(2, 44, 0)) + +/* + * The following types have no specific clear/free/unref functions, so + * they can be used as the stack-allocated variables or as the + * g_autofree heap-allocated variables. + * + * OstreeRepoTransactionStats + * OstreeRepoImportArchiveOptions + * OstreeRepoExportArchiveOptions + * OstreeRepoCheckoutOptions + */ + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeDiffItem, ostree_diff_item_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoCommitModifier, ostree_repo_commit_modifier_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoDevInoCache, ostree_repo_devino_cache_unref) + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeAsyncProgress, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeBootconfigParser, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeDeployment, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeGpgVerifyResult, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeMutableTree, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepo, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeRepoFile, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeSePolicy, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeSysroot, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeSysrootUpgrader, g_object_unref) + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (OstreeRepoCommitTraverseIter, ostree_repo_commit_traverse_iter_clear) + +#endif + +G_END_DECLS diff --git a/src/libostree/ostree-bootloader-syslinux.c b/src/libostree/ostree-bootloader-syslinux.c index 16951629..05cb173e 100644 --- a/src/libostree/ostree-bootloader-syslinux.c +++ b/src/libostree/ostree-bootloader-syslinux.c @@ -138,7 +138,8 @@ _ostree_bootloader_syslinux_write_config (OstreeBootloader *bootloader, bootversion); /* This should follow the symbolic link to the current bootversion. */ - config_contents = gs_file_load_contents_utf8 (self->config_path, cancellable, error); + config_contents = glnx_file_get_contents_utf8_at (AT_FDCWD, gs_file_get_path_cached (self->config_path), NULL, + cancellable, error); if (!config_contents) goto out; diff --git a/src/libostree/ostree-bootloader-uboot.c b/src/libostree/ostree-bootloader-uboot.c index f67e9bdb..f95ea843 100644 --- a/src/libostree/ostree-bootloader-uboot.c +++ b/src/libostree/ostree-bootloader-uboot.c @@ -113,7 +113,8 @@ _ostree_bootloader_uboot_write_config (OstreeBootloader *bootloader, g_autoptr(GPtrArray) new_lines = NULL; /* This should follow the symbolic link to the current bootversion. */ - config_contents = gs_file_load_contents_utf8 (self->config_path, cancellable, error); + config_contents = glnx_file_get_contents_utf8_at (AT_FDCWD, gs_file_get_path_cached (self->config_path), NULL, + cancellable, error); if (!config_contents) return FALSE; diff --git a/src/libostree/ostree-core-private.h b/src/libostree/ostree-core-private.h index a92aa4c7..91d52f1b 100644 --- a/src/libostree/ostree-core-private.h +++ b/src/libostree/ostree-core-private.h @@ -83,7 +83,7 @@ _ostree_make_temporary_symlink_at (int tmp_dirfd, GFileInfo * _ostree_header_gfile_info_new (mode_t mode, uid_t uid, gid_t gid); -/* XX + / + checksum-2 + . + extension, but let's just use 256 for a +/* XX/checksum-2.extension, but let's just use 256 for a * bit of overkill. */ #define _OSTREE_LOOSE_PATH_MAX (256) @@ -112,8 +112,7 @@ _ostree_get_relative_static_delta_part_path (const char *from, const char *to, guint i); -static inline char * -_ostree_get_commitpartial_path (const char *checksum) +static inline char * _ostree_get_commitpartial_path (const char *checksum) { return g_strconcat ("state/", checksum, ".commitpartial", NULL); } diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c index ded5e976..8e8424fc 100644 --- a/src/libostree/ostree-core.c +++ b/src/libostree/ostree-core.c @@ -26,11 +26,11 @@ #include #include #include +#include "libglnx.h" #include "ostree.h" #include "ostree-core-private.h" #include "ostree-chain-input-stream.h" #include "otutil.h" -#include "libglnx.h" #define ALIGN_VALUE(this, boundary) \ (( ((unsigned long)(this)) + (((unsigned long)(boundary)) -1)) & (~(((unsigned long)(boundary))-1))) @@ -47,7 +47,7 @@ zlib_file_header_parse (GVariant *metadata, GError **error); /** - * SECTION:libostree-core + * SECTION:ostree-core * @title: Core repository-independent functions * @short_description: Create, validate, and convert core data types * @@ -400,6 +400,98 @@ write_file_header_update_checksum (GOutputStream *out, return ret; } +/* + * header_and_input_to_stream: + * @file_header: A file header + * @input: File raw content stream + * @out_input: (out): Serialized object stream + * @out_header_size: (out): Length of the header + * @cancellable: Cancellable + * @error: Error + * + * Combines @file_header and @input into a single stream. + */ +static gboolean +header_and_input_to_stream (GVariant *file_header, + GInputStream *input, + GInputStream **out_input, + guint64 *out_header_size, + GCancellable *cancellable, + GError **error) +{ + gpointer header_data; + gsize header_size; + g_autoptr(GInputStream) ret_input = NULL; + g_autoptr(GPtrArray) streams = NULL; + g_autoptr(GOutputStream) header_out_stream = NULL; + g_autoptr(GInputStream) header_in_stream = NULL; + + header_out_stream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); + + if (!_ostree_write_variant_with_size (header_out_stream, file_header, 0, NULL, NULL, + cancellable, error)) + return FALSE; + + if (!g_output_stream_close (header_out_stream, cancellable, error)) + return FALSE; + + header_size = g_memory_output_stream_get_data_size ((GMemoryOutputStream*) header_out_stream); + header_data = g_memory_output_stream_steal_data ((GMemoryOutputStream*) header_out_stream); + header_in_stream = g_memory_input_stream_new_from_data (header_data, header_size, g_free); + + streams = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); + + g_ptr_array_add (streams, g_object_ref (header_in_stream)); + if (input) + g_ptr_array_add (streams, g_object_ref (input)); + + ret_input = (GInputStream*)ostree_chain_input_stream_new (streams); + ot_transfer_out_value (out_input, &ret_input); + if (out_header_size) + *out_header_size = header_size; + + return TRUE; +} + +/** + * ostree_raw_file_to_archive_z2_stream: + * @input: File raw content stream + * @file_info: A file info + * @xattrs: (allow-none): Optional extended attributes + * @out_input: (out): Serialized object stream + * @cancellable: Cancellable + * @error: Error + * + * Convert from a "bare" file representation into an + * OSTREE_OBJECT_TYPE_FILE stream suitable for ostree pull. + */ +gboolean +ostree_raw_file_to_archive_z2_stream (GInputStream *input, + GFileInfo *file_info, + GVariant *xattrs, + GInputStream **out_input, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GVariant) file_header = NULL; + g_autoptr(GInputStream) zlib_input = NULL; + + file_header = _ostree_zlib_file_header_new (file_info, xattrs); + if (input != NULL) + { + g_autoptr(GConverter) zlib_compressor = NULL; + + zlib_compressor = G_CONVERTER (g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW, 9)); + zlib_input = g_converter_input_stream_new (input, zlib_compressor); + } + return header_and_input_to_stream (file_header, + zlib_input, + out_input, + NULL, + cancellable, + error); +} + /** * ostree_raw_file_to_content_stream: * @input: File raw content stream @@ -423,44 +515,20 @@ ostree_raw_file_to_content_stream (GInputStream *input, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - gpointer header_data; - gsize header_size; - g_autoptr(GInputStream) ret_input = NULL; g_autoptr(GVariant) file_header = NULL; - g_autoptr(GPtrArray) streams = NULL; - g_autoptr(GOutputStream) header_out_stream = NULL; - g_autoptr(GInputStream) header_in_stream = NULL; + guint64 header_size; file_header = file_header_new (file_info, xattrs); - - header_out_stream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); - - if (!_ostree_write_variant_with_size (header_out_stream, file_header, 0, NULL, NULL, - cancellable, error)) - goto out; - - if (!g_output_stream_close (header_out_stream, cancellable, error)) - goto out; - - header_size = g_memory_output_stream_get_data_size ((GMemoryOutputStream*) header_out_stream); - header_data = g_memory_output_stream_steal_data ((GMemoryOutputStream*) header_out_stream); - header_in_stream = g_memory_input_stream_new_from_data (header_data, header_size, g_free); - - streams = g_ptr_array_new_with_free_func ((GDestroyNotify)g_object_unref); - - g_ptr_array_add (streams, g_object_ref (header_in_stream)); - if (input) - g_ptr_array_add (streams, g_object_ref (input)); - - ret_input = (GInputStream*)ostree_chain_input_stream_new (streams); - - ret = TRUE; - ot_transfer_out_value (out_input, &ret_input); + if (!header_and_input_to_stream (file_header, + input, + out_input, + &header_size, + cancellable, + error)) + return FALSE; if (out_length) *out_length = header_size + g_file_info_get_size (file_info); - out: - return ret; + return TRUE; } /** @@ -613,7 +681,7 @@ ostree_content_file_parse_at (gboolean compressed, cancellable, error)) goto out; - if (!gs_stream_fstat ((GFileDescriptorBased*)file_input, &stbuf, cancellable, error)) + if (!glnx_stream_fstat ((GFileDescriptorBased*)file_input, &stbuf, error)) goto out; if (!ostree_content_stream_parse (compressed, file_input, stbuf.st_size, trusted, @@ -921,14 +989,13 @@ _ostree_make_temporary_symlink_at (int tmp_dirfd, GError **error) { gboolean ret = FALSE; - g_autofree char *tmpname = NULL; + char *tmpname = g_strdup ("tmplink.XXXXXX"); guint i; const int max_attempts = 128; for (i = 0; i < max_attempts; i++) { - g_free (tmpname); - tmpname = gs_fileutil_gen_tmp_name (NULL, NULL); + glnx_gen_temp_name (tmpname); if (symlinkat (target, tmp_dirfd, tmpname) < 0) { if (errno == EEXIST) diff --git a/src/libostree/ostree-core.h b/src/libostree/ostree-core.h index 29ef7b28..4a0e60e9 100644 --- a/src/libostree/ostree-core.h +++ b/src/libostree/ostree-core.h @@ -59,7 +59,7 @@ G_BEGIN_DECLS * @OSTREE_OBJECT_TYPE_DIR_TREE: List of children (trees or files), and metadata * @OSTREE_OBJECT_TYPE_DIR_META: Directory metadata * @OSTREE_OBJECT_TYPE_COMMIT: Toplevel object, refers to tree and dirmeta for root - * @OSTREE_OBJECT_TYPE_COMMIT_TOMBSTONE: Toplevel object, refers to a deleted commit + * @OSTREE_OBJECT_TYPE_TOMBSTONE_COMMIT: Toplevel object, refers to a deleted commit * * Enumeration for core object types; %OSTREE_OBJECT_TYPE_FILE is for * content, the other types are metadata. @@ -90,10 +90,10 @@ typedef enum { /** * OSTREE_DIRMETA_GVARIANT_FORMAT: * - * u - uid - * u - gid - * u - mode - * a(ayay) - xattrs + * - u - uid + * - u - gid + * - u - mode + * - a(ayay) - xattrs */ #define OSTREE_DIRMETA_GVARIANT_STRING "(uuua(ayay))" #define OSTREE_DIRMETA_GVARIANT_FORMAT G_VARIANT_TYPE (OSTREE_DIRMETA_GVARIANT_STRING) @@ -106,10 +106,10 @@ typedef enum { * can't store in the real filesystem but we can still use a regular .file object * that we can hardlink to in the case of a user-mode checkout. * - * u - uid - * u - gid - * u - mode - * a(ayay) - xattrs + * - u - uid + * - u - gid + * - u - mode + * - a(ayay) - xattrs */ #define OSTREE_FILEMETA_GVARIANT_STRING "(uuua(ayay))" #define OSTREE_FILEMETA_GVARIANT_FORMAT G_VARIANT_TYPE (OSTREE_FILEMETA_GVARIANT_STRING) @@ -117,8 +117,8 @@ typedef enum { /** * OSTREE_TREE_GVARIANT_FORMAT: * - * a(say) - array of (filename, checksum) for files - * a(sayay) - array of (dirname, tree_checksum, meta_checksum) for directories + * - a(say) - array of (filename, checksum) for files + * - a(sayay) - array of (dirname, tree_checksum, meta_checksum) for directories */ #define OSTREE_TREE_GVARIANT_STRING "(a(say)a(sayay))" #define OSTREE_TREE_GVARIANT_FORMAT G_VARIANT_TYPE (OSTREE_TREE_GVARIANT_STRING) @@ -126,14 +126,14 @@ typedef enum { /** * OSTREE_COMMIT_GVARIANT_FORMAT: * - * a{sv} - Metadata - * ay - parent checksum (empty string for initial) - * a(say) - Related objects - * s - subject - * s - body - * t - Timestamp in seconds since the epoch (UTC) - * ay - Root tree contents - * ay - Root tree metadata + * - a{sv} - Metadata + * - ay - parent checksum (empty string for initial) + * - a(say) - Related objects + * - s - subject + * - s - body + * - t - Timestamp in seconds since the epoch (UTC) + * - ay - Root tree contents + * - ay - Root tree metadata */ #define OSTREE_COMMIT_GVARIANT_STRING "(a{sv}aya(say)sstayay)" #define OSTREE_COMMIT_GVARIANT_FORMAT G_VARIANT_TYPE (OSTREE_COMMIT_GVARIANT_STRING) @@ -141,8 +141,9 @@ typedef enum { /** * OSTREE_SUMMARY_GVARIANT_FORMAT: * - * refs: a(s(taya{sv})) - Map of ref name -> (latest commit size, latest commit checksum, additional metadata), sorted by ref name - * extensions: a{sv} - Additional metadata, none defined at the current time + * - a(s(taya{sv})) - Map of ref name -> (latest commit size, latest commit checksum, additional metadata), sorted by ref name + * - a{sv} - Additional metadata, at the current time the following are defined: + * - key: "ostree.static-deltas", value: a{sv}, static delta name -> 32 bytes of checksum */ #define OSTREE_SUMMARY_GVARIANT_STRING "(a(s(taya{sv}))a{sv})" #define OSTREE_SUMMARY_GVARIANT_FORMAT G_VARIANT_TYPE (OSTREE_SUMMARY_GVARIANT_STRING) @@ -152,9 +153,9 @@ typedef enum { /** * OstreeRepoMode: - * @OSTREE_REPO_MODE_BARE: Files are stored as themselves; can only be written as root + * @OSTREE_REPO_MODE_BARE: Files are stored as themselves; checkouts are hardlinks; can only be written as root * @OSTREE_REPO_MODE_ARCHIVE_Z2: Files are compressed, should be owned by non-root. Can be served via HTTP - * @OSTREE_REPO_MODE_BARE_USER: Files are stored as themselves, except ownership; can be written by user + * @OSTREE_REPO_MODE_BARE_USER: Files are stored as themselves, except ownership; can be written by user. Hardlinks work only in user checkouts. * * See the documentation of #OstreeRepo for more information about the * possible modes. @@ -165,8 +166,8 @@ typedef enum { OSTREE_REPO_MODE_BARE_USER } OstreeRepoMode; -const _OSTREE_PUBLIC -GVariantType *ostree_metadata_variant_type (OstreeObjectType objtype); +_OSTREE_PUBLIC +const GVariantType *ostree_metadata_variant_type (OstreeObjectType objtype); _OSTREE_PUBLIC gboolean ostree_validate_checksum_string (const char *sha256, @@ -214,9 +215,6 @@ gboolean ostree_parse_refspec (const char *refspec, char **out_ref, GError **error); -_OSTREE_PUBLIC -void ostree_checksum_update_meta (GChecksum *checksum, GFileInfo *file_info, GVariant *xattrs); - _OSTREE_PUBLIC const char * ostree_object_type_to_string (OstreeObjectType objtype); @@ -244,7 +242,8 @@ void ostree_object_from_string (const char *str, gchar **out_checksum, OstreeObjectType *out_objtype); -_OSTREE_PUBLIC gboolean +_OSTREE_PUBLIC +gboolean ostree_content_stream_parse (gboolean compressed, GInputStream *input, guint64 input_length, @@ -276,6 +275,15 @@ gboolean ostree_content_file_parse_at (gboolean compressed, GCancellable *cancellable, GError **error); +_OSTREE_PUBLIC +gboolean +ostree_raw_file_to_archive_z2_stream (GInputStream *input, + GFileInfo *file_info, + GVariant *xattrs, + GInputStream **out_input, + GCancellable *cancellable, + GError **error); + _OSTREE_PUBLIC gboolean ostree_raw_file_to_content_stream (GInputStream *input, GFileInfo *file_info, diff --git a/src/libostree/ostree-deployment-private.h b/src/libostree/ostree-deployment-private.h index 856a3987..01fdf1bf 100644 --- a/src/libostree/ostree-deployment-private.h +++ b/src/libostree/ostree-deployment-private.h @@ -24,19 +24,33 @@ G_BEGIN_DECLS + +/** + * OstreeDeployment: + * @parent_instance: + * @index: Global offset + * @osname: + * @csum: OSTree checksum of tree + * @deployserial: How many times this particular csum appears in deployment list + * @bootcsum: Checksum of kernel+initramfs + * @bootserial: An integer assigned to this tree per its ${bootcsum} + * @bootconfig: Bootloader configuration + * @origin: How to construct an upgraded version of this tree + * @unlocked: The unlocked state + */ struct _OstreeDeployment { GObject parent_instance; - int index; /* Global offset */ - char *osname; /* osname */ - char *csum; /* OSTree checksum of tree */ - int deployserial; /* How many times this particular csum appears in deployment list */ - char *bootcsum; /* Checksum of kernel+initramfs */ - int bootserial; /* An integer assigned to this tree per its ${bootcsum} */ - OstreeBootconfigParser *bootconfig; /* Bootloader configuration */ - GKeyFile *origin; /* How to construct an upgraded version of this tree */ - OstreeDeploymentUnlockedState unlocked; /* The unlocked state */ + int index; + char *osname; + char *csum; + int deployserial; + char *bootcsum; + int bootserial; + OstreeBootconfigParser *bootconfig; + GKeyFile *origin; + OstreeDeploymentUnlockedState unlocked; }; void _ostree_deployment_set_bootcsum (OstreeDeployment *self, const char *bootcsum); diff --git a/src/libostree/ostree-diff.c b/src/libostree/ostree-diff.c index 4b733461..69a67b76 100644 --- a/src/libostree/ostree-diff.c +++ b/src/libostree/ostree-diff.c @@ -22,6 +22,7 @@ #include "config.h" +#include "libglnx.h" #include "ostree.h" #include "otutil.h" @@ -209,6 +210,8 @@ diff_add_dir_recurse (GFile *d, * @modified: (element-type OstreeDiffItem): Modified files * @removed: (element-type Gio.File): Removed files * @added: (element-type Gio.File): Added files + * @cancellable: Cancellable + * @error: Error * * Compute the difference between directory @a and @b as 3 separate * sets of #OstreeDiffItem in @modified, @removed, and @added. diff --git a/src/libostree/ostree-diff.h b/src/libostree/ostree-diff.h index f4db23ef..781bf768 100644 --- a/src/libostree/ostree-diff.h +++ b/src/libostree/ostree-diff.h @@ -27,11 +27,17 @@ G_BEGIN_DECLS +/** + * OstreeDiffFlags: + */ typedef enum { OSTREE_DIFF_FLAGS_NONE = 0, OSTREE_DIFF_FLAGS_IGNORE_XATTRS = (1 << 0) } OstreeDiffFlags; +/** + * OstreeDiffItem: + */ typedef struct _OstreeDiffItem OstreeDiffItem; struct _OstreeDiffItem { diff --git a/src/libostree/ostree-dummy-enumtypes.c b/src/libostree/ostree-dummy-enumtypes.c new file mode 100644 index 00000000..259273b5 --- /dev/null +++ b/src/libostree/ostree-dummy-enumtypes.c @@ -0,0 +1,31 @@ +/* This file declares a stub function that is only exported + * to pacify ABI checkers - no one could really have used it. + * + * Copyright (C) 2016 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 "ostree-dummy-enumtypes.h" + +/* Exported for backwards compat - see + * https://bugzilla.gnome.org/show_bug.cgi?id=764131 + */ +GType +ostree_fetcher_config_flags_get_type (void) +{ + return G_TYPE_INVALID; +} diff --git a/src/libostree/ostree-dummy-enumtypes.h b/src/libostree/ostree-dummy-enumtypes.h new file mode 100644 index 00000000..017ed28e --- /dev/null +++ b/src/libostree/ostree-dummy-enumtypes.h @@ -0,0 +1,29 @@ +/* This file declares a stub function that is only exported + * to pacify ABI checkers - no one could really have used it. + * + * Copyright (C) 2016 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. + */ + +#pragma once + +#include + +#ifndef __GI_SCANNER__ +_OSTREE_PUBLIC GType +ostree_fetcher_config_flags_get_type (void); +#endif diff --git a/src/libostree/ostree-enumtypes.c.template b/src/libostree/ostree-enumtypes.c.template index fe8807ae..ab1b2aec 100644 --- a/src/libostree/ostree-enumtypes.c.template +++ b/src/libostree/ostree-enumtypes.c.template @@ -28,7 +28,7 @@ /*** BEGIN value-header ***/ GType -@enum_name@_get_type (void) +_@enum_name@_get_type (void) { static volatile gsize the_type__volatile = 0; diff --git a/src/libostree/ostree-enumtypes.h.template b/src/libostree/ostree-enumtypes.h.template index 40899d7f..ec20fe0d 100644 --- a/src/libostree/ostree-enumtypes.h.template +++ b/src/libostree/ostree-enumtypes.h.template @@ -32,9 +32,8 @@ G_BEGIN_DECLS /*** END file-production ***/ /*** BEGIN enumeration-production ***/ -#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (@enum_name@_get_type ()) -_OSTREE_PUBLIC -GType @enum_name@_get_type (void) G_GNUC_CONST; +#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (_@enum_name@_get_type ()) +GType _@enum_name@_get_type (void) G_GNUC_CONST; /*** END enumeration-production ***/ diff --git a/src/libostree/ostree-fetcher.c b/src/libostree/ostree-fetcher.c index d7915ba6..313df6a8 100644 --- a/src/libostree/ostree-fetcher.c +++ b/src/libostree/ostree-fetcher.c @@ -25,6 +25,7 @@ #include #include +#include "libglnx.h" #include "ostree-fetcher.h" #ifdef HAVE_LIBSOUP_CLIENT_CERTS #include "ostree-tls-cert-interaction.h" @@ -66,7 +67,12 @@ typedef struct { guint64 total_downloaded; } ThreadClosure; +static void +session_thread_process_pending_queue (ThreadClosure *thread_closure); + typedef struct { + volatile int ref_count; + ThreadClosure *thread_closure; SoupURI *uri; @@ -185,10 +191,22 @@ pending_task_compare (gconstpointer a, (priority_a < priority_b) ? -1 : 1; } -static void -pending_uri_free (OstreeFetcherPendingURI *pending) +static OstreeFetcherPendingURI * +pending_uri_ref (OstreeFetcherPendingURI *pending) { - g_hash_table_remove (pending->thread_closure->outstanding, pending); + g_return_val_if_fail (pending != NULL, NULL); + g_return_val_if_fail (pending->ref_count > 0, NULL); + + g_atomic_int_inc (&pending->ref_count); + + return pending; +} + +static void +pending_uri_unref (OstreeFetcherPendingURI *pending) +{ + if (!g_atomic_int_dec_and_test (&pending->ref_count)) + return; g_clear_pointer (&pending->thread_closure, thread_closure_unref); @@ -273,6 +291,7 @@ session_thread_set_proxy_cb (ThreadClosure *thread_closure, proxy_uri, NULL); } +#ifdef HAVE_LIBSOUP_CLIENT_CERTS static void session_thread_set_tls_interaction_cb (ThreadClosure *thread_closure, gpointer data) @@ -288,6 +307,7 @@ session_thread_set_tls_interaction_cb (ThreadClosure *thread_closure, SOUP_SESSION_TLS_INTERACTION, interaction, NULL); } +#endif static void session_thread_set_tls_database_cb (ThreadClosure *thread_closure, @@ -328,8 +348,7 @@ session_thread_process_pending_queue (ThreadClosure *thread_closure) pending = g_task_get_task_data (task); cancellable = g_task_get_cancellable (task); - /* pending_uri_free() removes this. */ - g_hash_table_add (thread_closure->outstanding, pending); + g_hash_table_add (thread_closure->outstanding, pending_uri_ref (pending)); soup_request_send_async (pending->request, cancellable, @@ -383,7 +402,7 @@ session_thread_request_uri (ThreadClosure *thread_closure, if (thread_closure->tmpdir_name == NULL) { if (!_ostree_repo_allocate_tmpdir (thread_closure->base_tmpdir_dfd, - "fetcher-", + OSTREE_REPO_TMPDIR_FETCHER, &thread_closure->tmpdir_name, &thread_closure->tmpdir_dfd, &thread_closure->tmpdir_lock, @@ -537,7 +556,7 @@ _ostree_fetcher_constructed (GObject *object) self->thread_closure->tmpdir_dfd = -1; self->thread_closure->tmpdir_lock = empty_lockfile; - self->thread_closure->outstanding = g_hash_table_new (NULL, NULL); + self->thread_closure->outstanding = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)pending_uri_unref); self->thread_closure->output_stream_set = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) NULL, (GDestroyNotify) g_object_unref); @@ -739,6 +758,18 @@ on_stream_read (GObject *object, GAsyncResult *result, gpointer user_data); +static void +remove_pending_rerun_queue (OstreeFetcherPendingURI *pending) +{ + /* Hold a temporary ref to ensure the reference to + * pending->thread_closure is valid. + */ + pending_uri_ref (pending); + g_hash_table_remove (pending->thread_closure->outstanding, pending); + session_thread_process_pending_queue (pending->thread_closure); + pending_uri_unref (pending); +} + static void on_out_splice_complete (GObject *object, GAsyncResult *result, @@ -767,7 +798,10 @@ on_out_splice_complete (GObject *object, out: if (local_error) - g_task_return_error (task, local_error); + { + g_task_return_error (task, local_error); + remove_pending_rerun_queue (pending); + } g_object_unref (task); } @@ -799,6 +833,7 @@ on_stream_read (GObject *object, g_task_return_pointer (task, g_strdup (pending->out_tmpfile), (GDestroyNotify) g_free); + remove_pending_rerun_queue (pending); } else { @@ -834,7 +869,10 @@ on_stream_read (GObject *object, out: if (local_error) - g_task_return_error (task, local_error); + { + g_task_return_error (task, local_error); + remove_pending_rerun_queue (pending); + } g_object_unref (task); } @@ -880,6 +918,7 @@ on_request_sent (GObject *object, g_strdup (pending->out_tmpfile), (GDestroyNotify) g_free); } + remove_pending_rerun_queue (pending); goto out; } else if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) @@ -944,6 +983,7 @@ on_request_sent (GObject *object, g_task_return_pointer (task, g_object_ref (pending->request_body), (GDestroyNotify) g_object_unref); + remove_pending_rerun_queue (pending); } out: @@ -952,6 +992,7 @@ on_request_sent (GObject *object, if (pending->request_body) (void) g_input_stream_close (pending->request_body, NULL, NULL); g_task_return_error (task, local_error); + remove_pending_rerun_queue (pending); } g_object_unref (task); @@ -976,6 +1017,7 @@ ostree_fetcher_request_uri_internal (OstreeFetcher *self, /* SoupRequest is created in session thread. */ pending = g_new0 (OstreeFetcherPendingURI, 1); + pending->ref_count = 1; pending->thread_closure = thread_closure_ref (self->thread_closure); pending->uri = soup_uri_copy (uri); pending->max_size = max_size; @@ -983,7 +1025,7 @@ ostree_fetcher_request_uri_internal (OstreeFetcher *self, task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, source_tag); - g_task_set_task_data (task, pending, (GDestroyNotify) pending_uri_free); + g_task_set_task_data (task, pending, (GDestroyNotify) pending_uri_unref); /* We'll use the GTask priority for our own priority queue. */ g_task_set_priority (task, priority); @@ -1067,7 +1109,7 @@ _ostree_fetcher_bytes_transferred (OstreeFetcher *self) if (G_IS_FILE_DESCRIPTOR_BASED (stream)) { - if (gs_stream_fstat ((GFileDescriptorBased*)stream, &stbuf, NULL, NULL)) + if (glnx_stream_fstat ((GFileDescriptorBased*)stream, &stbuf, NULL)) ret += stbuf.st_size; } } diff --git a/src/libostree/ostree-gpg-verifier.h b/src/libostree/ostree-gpg-verifier.h index 209f7342..2db39f3b 100644 --- a/src/libostree/ostree-gpg-verifier.h +++ b/src/libostree/ostree-gpg-verifier.h @@ -35,6 +35,11 @@ G_BEGIN_DECLS typedef struct OstreeGpgVerifier OstreeGpgVerifier; +/* If this type becomes public in future, move this autoptr cleanup + * definition to the ostree-autocleanups.h header file. Right now it + * relies on glnx's fallback definition of the macro. */ +G_DEFINE_AUTOPTR_CLEANUP_FUNC (OstreeGpgVerifier, g_object_unref) + GType _ostree_gpg_verifier_get_type (void); OstreeGpgVerifier *_ostree_gpg_verifier_new (void); diff --git a/src/libostree/ostree-gpg-verify-result.c b/src/libostree/ostree-gpg-verify-result.c index 37fbfb5c..fa4614d1 100644 --- a/src/libostree/ostree-gpg-verify-result.c +++ b/src/libostree/ostree-gpg-verify-result.c @@ -27,7 +27,7 @@ #include "ostree-gpg-verify-result-private.h" /** - * SECTION: libostree-gpg-verify-result + * SECTION: ostree-gpg-verify-result * @title: GPG signature verification results * @short_description: Inspect detached GPG signatures * @@ -622,3 +622,33 @@ ostree_gpg_verify_result_describe_variant (GVariant *variant, } } } + +/** + * ostree_gpg_verify_result_require_valid_signature: + * @result: (nullable): an #OstreeGpgVerifyResult + * @error: A #GError + * + * Checks if the result contains at least one signature from the + * trusted keyring. You can call this function immediately after + * ostree_repo_verify_summary() or ostree_repo_verify_commit_ext() - + * it will handle the %NULL @result and filled @error too. + * + * Returns: %TRUE if @result was not %NULL and had at least one + * signature from trusted keyring, otherwise %FALSE + */ +gboolean +ostree_gpg_verify_result_require_valid_signature (OstreeGpgVerifyResult *result, + GError **error) +{ + if (result == NULL) + return FALSE; + + if (ostree_gpg_verify_result_count_valid (result) == 0) + { + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "GPG signatures found, but none are in trusted keyring"); + return FALSE; + } + + return TRUE; +} diff --git a/src/libostree/ostree-gpg-verify-result.h b/src/libostree/ostree-gpg-verify-result.h index 8894afdf..f9512538 100644 --- a/src/libostree/ostree-gpg-verify-result.h +++ b/src/libostree/ostree-gpg-verify-result.h @@ -133,4 +133,8 @@ void ostree_gpg_verify_result_describe_variant (GVariant *variant, const gchar *line_prefix, OstreeGpgSignatureFormatFlags flags); +_OSTREE_PUBLIC +gboolean ostree_gpg_verify_result_require_valid_signature (OstreeGpgVerifyResult *result, + GError **error); + G_END_DECLS diff --git a/src/libostree/ostree-linuxfsutil.c b/src/libostree/ostree-linuxfsutil.c index 4151fa34..b270a94e 100644 --- a/src/libostree/ostree-linuxfsutil.c +++ b/src/libostree/ostree-linuxfsutil.c @@ -107,7 +107,7 @@ _ostree_linuxfs_alter_immutable_flag (GFile *path, GError **error) { gboolean ret = FALSE; - int fd = -1; + glnx_fd_close int fd = -1; if (g_cancellable_set_error_if_cancelled (cancellable, error)) return FALSE; @@ -129,7 +129,5 @@ _ostree_linuxfs_alter_immutable_flag (GFile *path, ret = TRUE; out: - if (fd != -1) - (void) close (fd); return ret; } diff --git a/src/libostree/ostree-lzma-compressor.c b/src/libostree/ostree-lzma-compressor.c index 1ec03c41..8d9dcbc9 100644 --- a/src/libostree/ostree-lzma-compressor.c +++ b/src/libostree/ostree-lzma-compressor.c @@ -32,7 +32,7 @@ enum { /** * SECTION:ostree-lzma-compressor - * @short_description: LZMA compressor + * @title: LZMA compressor * * An implementation of #GConverter that compresses data using * LZMA. diff --git a/src/libostree/ostree-lzma-decompressor.c b/src/libostree/ostree-lzma-decompressor.c index b46e8fb2..6376e389 100644 --- a/src/libostree/ostree-lzma-decompressor.c +++ b/src/libostree/ostree-lzma-decompressor.c @@ -29,6 +29,14 @@ enum { PROP_0, }; +/** + * SECTION:ostree-lzma-decompressor + * @title: LZMA decompressor + * + * An implementation of #GConverter that decompresses data using + * LZMA. + */ + static void _ostree_lzma_decompressor_iface_init (GConverterIface *iface); struct _OstreeLzmaDecompressor diff --git a/src/libostree/ostree-mutable-tree.c b/src/libostree/ostree-mutable-tree.c index bc4f4250..5540cc7c 100644 --- a/src/libostree/ostree-mutable-tree.c +++ b/src/libostree/ostree-mutable-tree.c @@ -27,7 +27,7 @@ #include "ostree-core.h" /** - * SECTION:libostree-mutable-tree + * SECTION:ostree-mutable-tree * @title: In-memory modifiable filesystem tree * @short_description: Modifiable filesystem tree * @@ -159,6 +159,11 @@ ostree_mutable_tree_replace_file (OstreeMutableTree *self, { gboolean ret = FALSE; + g_return_val_if_fail (name != NULL, FALSE); + + if (!ot_util_filename_validate (name, error)) + goto out; + if (g_hash_table_lookup (self->subdirs, name)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, @@ -187,6 +192,9 @@ ostree_mutable_tree_ensure_dir (OstreeMutableTree *self, g_return_val_if_fail (name != NULL, FALSE); + if (!ot_util_filename_validate (name, error)) + goto out; + if (g_hash_table_lookup (self->files, name)) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, diff --git a/src/libostree/ostree-mutable-tree.h b/src/libostree/ostree-mutable-tree.h index 30425d8d..1b642c42 100644 --- a/src/libostree/ostree-mutable-tree.h +++ b/src/libostree/ostree-mutable-tree.h @@ -84,7 +84,8 @@ gboolean ostree_mutable_tree_lookup (OstreeMutableTree *self, OstreeMutableTree **out_subdir, GError **error); -_OSTREE_PUBLIC gboolean +_OSTREE_PUBLIC +gboolean ostree_mutable_tree_ensure_parent_dirs (OstreeMutableTree *self, GPtrArray *split_path, const char *metadata_checksum, diff --git a/src/libostree/ostree-repo-checkout.c b/src/libostree/ostree-repo-checkout.c index 0a77da02..5f7cf0bd 100644 --- a/src/libostree/ostree-repo-checkout.c +++ b/src/libostree/ostree-repo-checkout.c @@ -632,7 +632,7 @@ checkout_tree_at (OstreeRepo *self, { gboolean ret = FALSE; gboolean did_exist = FALSE; - int destination_dfd = -1; + glnx_fd_close int destination_dfd = -1; int res; g_autoptr(GVariant) xattrs = NULL; g_autoptr(GFileEnumerator) dir_enum = NULL; @@ -752,12 +752,12 @@ checkout_tree_at (OstreeRepo *self, } } - /* Set directory mtime to 0, so that it is constant for all checkouts. + /* Set directory mtime to OSTREE_TIMESTAMP, so that it is constant for all checkouts. * Must be done after setting permissions and creating all children. */ if (!did_exist) { - const struct timespec times[2] = { { 0, UTIME_OMIT }, { 0, } }; + const struct timespec times[2] = { { OSTREE_TIMESTAMP, UTIME_OMIT }, { OSTREE_TIMESTAMP, 0} }; do res = futimens (destination_dfd, times); while (G_UNLIKELY (res == -1 && errno == EINTR)); @@ -779,8 +779,6 @@ checkout_tree_at (OstreeRepo *self, ret = TRUE; out: - if (destination_dfd != -1) - (void) close (destination_dfd); return ret; } @@ -957,38 +955,36 @@ ostree_repo_checkout_gc (OstreeRepo *self, g_hash_table_iter_init (&iter, to_clean_dirs); while (to_clean_dirs && g_hash_table_iter_next (&iter, &key, &value)) { - g_autoptr(GFile) objdir = NULL; - g_autoptr(GFileEnumerator) enumerator = NULL; - g_autofree char *objdir_name = NULL; + g_autofree char *objdir_name = g_strdup_printf ("%02x", GPOINTER_TO_UINT (key)); + g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; - objdir_name = g_strdup_printf ("%02x", GPOINTER_TO_UINT (key)); - objdir = g_file_get_child (self->uncompressed_objects_dir, objdir_name); - - enumerator = g_file_enumerate_children (objdir, "standard::name,standard::type,unix::inode,unix::nlink", - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, - error); - if (!enumerator) + if (!glnx_dirfd_iterator_init_at (self->uncompressed_objects_dir_fd, objdir_name, FALSE, + &dfd_iter, error)) goto out; - + while (TRUE) { - GFileInfo *file_info; - guint32 nlinks; + struct dirent *dent; + struct stat stbuf; - if (!gs_file_enumerator_iterate (enumerator, &file_info, NULL, - cancellable, error)) + if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error)) goto out; - if (file_info == NULL) + if (dent == NULL) break; - - nlinks = g_file_info_get_attribute_uint32 (file_info, "unix::nlink"); - if (nlinks == 1) + + if (fstatat (dfd_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) != 0) { - g_autoptr(GFile) objpath = NULL; - objpath = g_file_get_child (objdir, g_file_info_get_name (file_info)); - if (!gs_file_unlink (objpath, cancellable, error)) - goto out; + glnx_set_error_from_errno (error); + goto out; + } + + if (stbuf.st_nlink == 1) + { + if (unlinkat (dfd_iter.fd, dent->d_name, 0) != 0) + { + glnx_set_error_from_errno (error); + goto out; + } } } } diff --git a/src/libostree/ostree-repo-commit.c b/src/libostree/ostree-repo-commit.c index 7f03e11d..685eadd0 100644 --- a/src/libostree/ostree-repo-commit.c +++ b/src/libostree/ostree-repo-commit.c @@ -36,22 +36,6 @@ #include #include -struct OstreeRepoCommitModifier { - volatile gint refcount; - - OstreeRepoCommitModifierFlags flags; - OstreeRepoCommitFilter filter; - gpointer user_data; - GDestroyNotify destroy_notify; - - OstreeRepoCommitModifierXattrCallback xattr_callback; - GDestroyNotify xattr_destroy; - gpointer xattr_user_data; - - OstreeSePolicy *sepolicy; - GHashTable *devino_cache; -}; - gboolean _ostree_repo_ensure_loose_objdir_at (int dfd, const char *loose_path, @@ -226,7 +210,6 @@ commit_loose_object_trusted (OstreeRepo *self, else { int res; - struct timespec times[2]; if (objtype == OSTREE_OBJECT_TYPE_FILE && self->mode == OSTREE_REPO_MODE_BARE) { @@ -282,12 +265,9 @@ commit_loose_object_trusted (OstreeRepo *self, { /* 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 0. + * set the modification time to OSTREE_TIMESTAMP. */ - times[0].tv_sec = 0; /* atime */ - times[0].tv_nsec = UTIME_OMIT; - times[1].tv_sec = 0; /* mtime */ - times[1].tv_nsec = 0; + const struct timespec times[2] = { { OSTREE_TIMESTAMP, UTIME_OMIT }, { OSTREE_TIMESTAMP, 0} }; do res = futimens (fd, times); while (G_UNLIKELY (res == -1 && errno == EINTR)); @@ -573,11 +553,8 @@ _ostree_repo_open_trusted_content_bare (OstreeRepo *self, g_autofree char *temp_filename = NULL; g_autoptr(GOutputStream) ret_stream = NULL; gboolean have_obj; - char loose_objpath[_OSTREE_LOOSE_PATH_MAX]; - if (!_ostree_repo_has_loose_object (self, checksum, OSTREE_OBJECT_TYPE_FILE, - &have_obj, loose_objpath, - NULL, + if (!_ostree_repo_has_loose_object (self, checksum, OSTREE_OBJECT_TYPE_FILE, &have_obj, cancellable, error)) goto out; @@ -662,7 +639,6 @@ write_object (OstreeRepo *self, gboolean temp_file_is_regular; gboolean temp_file_is_symlink; gboolean object_is_symlink = FALSE; - char loose_objpath[_OSTREE_LOOSE_PATH_MAX]; gssize unpacked_size = 0; gboolean indexable = FALSE; @@ -673,9 +649,8 @@ write_object (OstreeRepo *self, if (expected_checksum) { - if (!_ostree_repo_has_loose_object (self, expected_checksum, objtype, - &have_obj, loose_objpath, - NULL, cancellable, error)) + if (!_ostree_repo_has_loose_object (self, expected_checksum, objtype, &have_obj, + cancellable, error)) goto out; if (have_obj) { @@ -852,8 +827,7 @@ write_object (OstreeRepo *self, repo_store_size_entry (self, actual_checksum, unpacked_size, stbuf.st_size); } - if (!_ostree_repo_has_loose_object (self, actual_checksum, objtype, - &have_obj, loose_objpath, NULL, + if (!_ostree_repo_has_loose_object (self, actual_checksum, objtype, &have_obj, cancellable, error)) goto out; @@ -1167,7 +1141,6 @@ ostree_repo_prepare_transaction (OstreeRepo *self, { gboolean ret = FALSE; gboolean ret_transaction_resume = FALSE; - g_autofree char *stagedir_boot_id_prefix = NULL; g_autofree char *stagedir_name = NULL; glnx_fd_close int stagedir_fd = -1; g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; @@ -1178,10 +1151,8 @@ ostree_repo_prepare_transaction (OstreeRepo *self, self->in_transaction = TRUE; - stagedir_boot_id_prefix = g_strconcat ("staging-", self->boot_id, "-", NULL); - if (!_ostree_repo_allocate_tmpdir (self->tmp_dir_fd, - stagedir_boot_id_prefix, + self->stagedir_prefix, &self->commit_stagedir_name, &self->commit_stagedir_fd, &self->commit_stagedir_lock, @@ -1287,44 +1258,87 @@ cleanup_tmpdir (OstreeRepo *self, GError **error) { gboolean ret = FALSE; - g_autoptr(GFileEnumerator) enumerator = NULL; + g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; guint64 curtime_secs; - enumerator = g_file_enumerate_children (self->tmp_dir, "standard::name,time::modified", - G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, - cancellable, - error); - if (!enumerator) - goto out; - curtime_secs = g_get_real_time () / 1000000; + if (!glnx_dirfd_iterator_init_at (self->tmp_dir_fd, ".", TRUE, &dfd_iter, error)) + goto out; + while (TRUE) { - GFileInfo *file_info; - GFile *path; - guint64 mtime; guint64 delta; + struct dirent *dent; + struct stat stbuf; + g_auto(GLnxLockFile) lockfile = GLNX_LOCK_FILE_INIT; + gboolean did_lock; - if (!gs_file_enumerator_iterate (enumerator, &file_info, &path, - cancellable, error)) + if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error)) goto out; - if (file_info == NULL) + + if (dent == NULL) break; - mtime = g_file_info_get_attribute_uint64 (file_info, "time::modified"); - if (mtime > curtime_secs) - continue; - /* Only delete files older than a day. To do better, we would - * need to coordinate between multiple processes in a reliable - * fashion. See - * https://bugzilla.gnome.org/show_bug.cgi?id=709115 - */ - delta = curtime_secs - mtime; - if (delta > 60*60*24) + if (TEMP_FAILURE_RETRY (fstatat (dfd_iter.fd, dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW)) < 0) { - if (!glnx_shutil_rm_rf_at (AT_FDCWD, gs_file_get_path_cached (path), cancellable, error)) + if (errno == ENOENT) /* Did another cleanup win? */ + continue; + glnx_set_error_from_errno (error); + goto out; + } + + /* First, if it's a directory which needs locking, but it's + * busy, skip it. + */ + if (_ostree_repo_is_locked_tmpdir (dent->d_name)) + { + if (!_ostree_repo_try_lock_tmpdir (dfd_iter.fd, dent->d_name, + &lockfile, &did_lock, error)) goto out; + if (!did_lock) + continue; + } + + /* If however this is the staging directory for the *current* + * boot, then don't delete it now - we may end up reusing it, as + * is the point. + */ + if (g_str_has_prefix (dent->d_name, self->stagedir_prefix)) + continue; + else if (g_str_has_prefix (dent->d_name, OSTREE_REPO_TMPDIR_STAGING)) + { + /* But, crucially we can now clean up staging directories + * from *other* boots + */ + if (!glnx_shutil_rm_rf_at (dfd_iter.fd, dent->d_name, cancellable, error)) + goto out; + } + /* FIXME - move OSTREE_REPO_TMPDIR_FETCHER underneath the + * staging/boot-id scheme as well, since all of the "did it get + * fsync'd" concerns apply to that as well. Then we can skip + * this special case. + */ + else if (g_str_has_prefix (dent->d_name, OSTREE_REPO_TMPDIR_FETCHER)) + continue; + else + { + /* Now we do time-based cleanup. Ignore it if it's somehow + * in the future... + */ + if (stbuf.st_mtime > curtime_secs) + continue; + + /* Now, we're pruning content based on the expiry, which + * defaults to a day. That's what we were doing before we + * had locking...but in future we can be smarter here. + */ + delta = curtime_secs - stbuf.st_mtime; + if (delta > self->tmp_expiry_seconds) + { + if (!glnx_shutil_rm_rf_at (dfd_iter.fd, dent->d_name, cancellable, error)) + goto out; + } } } @@ -1448,12 +1462,25 @@ ostree_repo_commit_transaction (OstreeRepo *self, g_return_val_if_fail (self->in_transaction == TRUE, FALSE); - if (syncfs (self->tmp_dir_fd) < 0) + if ((self->test_error_flags & OSTREE_REPO_TEST_ERROR_PRE_COMMIT) > 0) { - glnx_set_error_from_errno (error); + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "OSTREE_REPO_TEST_ERROR_PRE_COMMIT specified"); goto out; } + /* FIXME: Added since valgrind in el7 doesn't know about + * `syncfs`...we should delete this later. + */ + if (g_getenv ("OSTREE_SUPPRESS_SYNCFS") == NULL) + { + if (syncfs (self->tmp_dir_fd) < 0) + { + glnx_set_error_from_errno (error); + goto out; + } + } + if (!rename_pending_loose_objects (self, cancellable, error)) goto out; @@ -1937,7 +1964,7 @@ create_empty_gvariant_dict (void) * ostree_repo_write_commit: * @self: Repo * @parent: (allow-none): ASCII SHA256 checksum for parent, or %NULL for none - * @subject: Subject + * @subject: (allow-none): Subject * @body: (allow-none): Body * @metadata: (allow-none): GVariant of type a{sv}, or %NULL for none * @root: The tree to point the commit to @@ -1981,10 +2008,11 @@ ostree_repo_write_commit (OstreeRepo *self, * ostree_repo_write_commit_with_time: * @self: Repo * @parent: (allow-none): ASCII SHA256 checksum for parent, or %NULL for none - * @subject: Subject + * @subject: (allow-none): Subject * @body: (allow-none): Body * @metadata: (allow-none): GVariant of type a{sv}, or %NULL for none * @root: The tree to point the commit to + * @time: The time to use to stamp the commit * @out_commit: (out): Resulting ASCII SHA256 checksum for commit * @cancellable: Cancellable * @error: Error @@ -2011,8 +2039,6 @@ ostree_repo_write_commit_with_time (OstreeRepo *self, g_autofree guchar *commit_csum = NULL; OstreeRepoFile *repo_root = OSTREE_REPO_FILE (root); - g_return_val_if_fail (subject != NULL, FALSE); - /* Add sizes information to our metadata object */ if (!add_size_index_to_metadata (self, metadata, &new_metadata, cancellable, error)) @@ -2022,7 +2048,7 @@ ostree_repo_write_commit_with_time (OstreeRepo *self, new_metadata ? new_metadata : create_empty_gvariant_dict (), parent ? ostree_checksum_to_bytes_v (parent) : ot_gvariant_new_bytearray (NULL, 0), g_variant_new_array (G_VARIANT_TYPE ("(say)"), NULL, 0), - subject, body ? body : "", + subject ? subject : "", body ? body : "", GUINT64_TO_BE (time), ostree_checksum_to_bytes_v (ostree_repo_file_tree_get_contents_checksum (repo_root)), ostree_checksum_to_bytes_v (ostree_repo_file_tree_get_metadata_checksum (repo_root))); @@ -2073,21 +2099,13 @@ ostree_repo_read_commit_detached_metadata (OstreeRepo *self, g_autoptr(GFile) metadata_path = _ostree_repo_get_commit_metadata_loose_path (self, checksum); g_autoptr(GVariant) ret_metadata = NULL; - GError *temp_error = NULL; - if (!ot_util_variant_map (metadata_path, G_VARIANT_TYPE ("a{sv}"), - TRUE, &ret_metadata, &temp_error)) + if (!ot_util_variant_map_at (AT_FDCWD, gs_file_get_path_cached (metadata_path), + G_VARIANT_TYPE ("a{sv}"), + OT_VARIANT_MAP_ALLOW_NOENT | OT_VARIANT_MAP_TRUSTED, &ret_metadata, error)) { - if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) - { - g_clear_error (&temp_error); - } - else - { - g_prefix_error (error, "Unable to read existing detached metadata: "); - g_propagate_error (error, temp_error); - goto out; - } + g_prefix_error (error, "Unable to read existing detached metadata: "); + goto out; } ret = TRUE; @@ -2184,6 +2202,10 @@ create_tree_variant_from_hashes (GHashTable *file_checksums, while (g_hash_table_iter_next (&hash_iter, &key, &value)) { const char *name = key; + + /* Should have been validated earlier, but be paranoid */ + g_assert (ot_util_filename_validate (name, NULL)); + sorted_filenames = g_slist_prepend (sorted_filenames, (char*)name); } @@ -2355,7 +2377,7 @@ get_modified_xattrs (OstreeRepo *self, if (label) { - GVariantBuilder *builder; + g_autoptr(GVariantBuilder) builder = NULL; /* ret_xattrs may be NULL */ builder = ot_util_variant_builder_from_variant (ret_xattrs, diff --git a/src/libostree/ostree-repo-file.h b/src/libostree/ostree-repo-file.h index fb651645..6f9b79fe 100644 --- a/src/libostree/ostree-repo-file.h +++ b/src/libostree/ostree-repo-file.h @@ -58,9 +58,6 @@ OstreeRepo * ostree_repo_file_get_repo (OstreeRepoFile *self); _OSTREE_PUBLIC OstreeRepoFile * ostree_repo_file_get_root (OstreeRepoFile *self); -_OSTREE_PUBLIC -void ostree_repo_file_make_empty_tree (OstreeRepoFile *self); - _OSTREE_PUBLIC void ostree_repo_file_tree_set_metadata (OstreeRepoFile *self, const char *checksum, diff --git a/src/libostree/ostree-repo-libarchive.c b/src/libostree/ostree-repo-libarchive.c index 7a30192c..45427ef7 100644 --- a/src/libostree/ostree-repo-libarchive.c +++ b/src/libostree/ostree-repo-libarchive.c @@ -37,6 +37,8 @@ #ifdef HAVE_LIBARCHIVE +#define DEFAULT_DIRMODE (0755 | S_IFDIR) + static void propagate_libarchive_error (GError **error, struct archive *a) @@ -45,269 +47,761 @@ propagate_libarchive_error (GError **error, "%s", archive_error_string (a)); } +static const char * +path_relative (const char *src, + GError **error) +{ + /* One issue here is that some archives almost record the pathname as just a + * string and don't need to actually encode parent/child relationships in the + * archive. For us however, this will be important. So we do our best to deal + * with non-conventional paths. We also validate the path at the end to make + * sure there are no illegal components. Also important, we relativize the + * path. */ + + /* relativize first (and make /../../ --> /) */ + while (src[0] == '/') + { + src += 1; + if (src[0] == '.' && src[1] == '.' && src[2] == '/') + src += 2; /* keep trailing / so we continue */ + } + + /* now let's skip . and empty components */ + while (TRUE) + { + if (src[0] == '.' && src[1] == '/') + src += 2; + else if (src[0] == '/') + src += 1; + else + break; + } + + /* assume a single '.' means the root dir itself, which we handle as the empty + * string in our code */ + if (src[0] == '.' && src[1] == '\0') + src += 1; + + /* make sure that the final path is valid (no . or ..) */ + if (!ot_util_path_split_validate (src, NULL, error)) + { + g_prefix_error (error, "While making relative path \"%s\":", src); + return NULL; + } + + return src; +} + +static char * +path_relative_ostree (const char *path, + GError **error) +{ + path = path_relative (path, error); + if (path == NULL) + return NULL; + if (g_str_has_prefix (path, "etc/")) + return g_strconcat ("usr/", path, NULL); + else if (strcmp (path, "etc") == 0) + return g_strdup ("usr/etc"); + return g_strdup (path); +} + +static void +append_path_component (char **path_builder, + const char *component) +{ + g_autofree char *s = g_steal_pointer (path_builder); + *path_builder = g_build_filename (s ?: "/", component, NULL); +} + +/* inplace trailing slash squashing */ +static void +squash_trailing_slashes (char *path) +{ + char *endp = path + strlen (path) - 1; + for (; endp > path && *endp == '/'; endp--) + *endp = '\0'; +} + static GFileInfo * -file_info_from_archive_entry_and_modifier (OstreeRepo *repo, - struct archive_entry *entry, - OstreeRepoCommitModifier *modifier) +file_info_from_archive_entry (struct archive_entry *entry) { g_autoptr(GFileInfo) info = NULL; - GFileInfo *modified_info = NULL; - const struct stat *st; + const struct stat *st = NULL; guint32 file_type; + mode_t mode; st = archive_entry_stat (entry); + mode = st->st_mode; - info = _ostree_header_gfile_info_new (st->st_mode, st->st_uid, st->st_gid); - file_type = ot_gfile_type_for_mode (st->st_mode); + /* 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)); + g_file_info_set_attribute_byte_string (info, "standard::symlink-target", + archive_entry_symlink (entry)); } - _ostree_repo_commit_modifier_apply (repo, modifier, - archive_entry_pathname (entry), - info, &modified_info); - - return modified_info; + return g_steal_pointer (&info); } static gboolean -import_libarchive_entry_file (OstreeRepo *self, - OstreeRepoImportArchiveOptions *opts, - struct archive *a, - struct archive_entry *entry, - GFileInfo *file_info, - guchar **out_csum, - GCancellable *cancellable, - GError **error) +builder_add_label (GVariantBuilder *builder, + OstreeSePolicy *sepolicy, + const char *path, + mode_t mode, + GCancellable *cancellable, + GError **error) { - gboolean ret = FALSE; - g_autoptr(GInputStream) file_object_input = NULL; - g_autoptr(GInputStream) archive_stream = NULL; - guint64 length; - - if (g_cancellable_set_error_if_cancelled (cancellable, error)) + g_autofree char *label = NULL; + + if (!sepolicy) + return TRUE; + + if (!ostree_sepolicy_get_label (sepolicy, path, mode, &label, + cancellable, error)) return FALSE; - switch (g_file_info_get_file_type (file_info)) - { - case G_FILE_TYPE_REGULAR: - archive_stream = _ostree_libarchive_input_stream_new (a); - break; - case G_FILE_TYPE_SYMBOLIC_LINK: - break; - default: - if (opts->ignore_unsupported_content) - { - ret = TRUE; - goto out; - } - else - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Unable to import non-regular/non-symlink file '%s'", - archive_entry_pathname (entry)); - goto out; - } - } - - if (!ostree_raw_file_to_content_stream (archive_stream, file_info, NULL, - &file_object_input, &length, cancellable, error)) - goto out; - - if (!ostree_repo_write_content (self, NULL, file_object_input, length, out_csum, - cancellable, error)) - goto out; - - ret = TRUE; - out: - return ret; + if (label) + g_variant_builder_add (builder, "(@ay@ay)", + g_variant_new_bytestring ("security.selinux"), + g_variant_new_bytestring (label)); + return TRUE; } + +/* Like ostree_mutable_tree_ensure_dir(), but also creates and sets dirmeta if + * the dir has to be created. */ static gboolean -write_libarchive_entry_to_mtree (OstreeRepo *self, - OstreeRepoImportArchiveOptions *opts, - OstreeMutableTree *root, - struct archive *a, - struct archive_entry *entry, - OstreeRepoCommitModifier *modifier, - const guchar *tmp_dir_csum, - GCancellable *cancellable, - GError **error) +mtree_ensure_dir_with_meta (OstreeRepo *repo, + OstreeMutableTree *parent, + const char *name, + GFileInfo *file_info, + GVariant *xattrs, + gboolean error_if_exist, /* XXX: remove if not needed */ + OstreeMutableTree **out_dir, + GCancellable *cancellable, + GError **error) { - gboolean ret = FALSE; - const char *pathname; - const char *hardlink; - const char *basename; + glnx_unref_object OstreeMutableTree *dir = NULL; + g_autofree guchar *csum_raw = NULL; + g_autofree char *csum = NULL; + + if (name[0] == '\0') /* root? */ + dir = g_object_ref (parent); + else if (ostree_mutable_tree_lookup (parent, name, NULL, &dir, error)) + { + if (error_if_exist) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Directory \"%s\" already exists", name); + return FALSE; + } + } + + if (dir == NULL) + { + if (!g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + return FALSE; + + g_clear_error (error); + + if (!ostree_mutable_tree_ensure_dir (parent, name, &dir, error)) + return FALSE; + } + + if (!_ostree_repo_write_directory_meta (repo, file_info, xattrs, + &csum_raw, cancellable, error)) + return FALSE; + + csum = ostree_checksum_from_bytes (csum_raw); + + ostree_mutable_tree_set_metadata_checksum (dir, csum); + + if (out_dir) + *out_dir = g_steal_pointer (&dir); + + return TRUE; +} + +typedef struct { + OstreeRepo *repo; + OstreeRepoImportArchiveOptions *opts; + OstreeMutableTree *root; + struct archive *archive; + struct archive_entry *entry; + GHashTable *deferred_hardlinks; + OstreeRepoCommitModifier *modifier; +} OstreeRepoArchiveImportContext; + +typedef struct { + OstreeMutableTree *parent; + char *path; + guint64 size; +} DeferredHardlink; + +static inline char* +aic_get_final_path (OstreeRepoArchiveImportContext *ctx, + const char *path, + GError **error) +{ + if (ctx->opts->use_ostree_convention) + return path_relative_ostree (path, error); + return g_strdup (path_relative (path, error)); +} + +static inline char* +aic_get_final_entry_pathname (OstreeRepoArchiveImportContext *ctx, + GError **error) +{ + const char *pathname = archive_entry_pathname (ctx->entry); + g_autofree char *final = aic_get_final_path (ctx, pathname, error); + + if (final == NULL) + return NULL; + + /* get rid of trailing slashes some archives put on dirs */ + squash_trailing_slashes (final); + return g_steal_pointer (&final); +} + +static inline char* +aic_get_final_entry_hardlink (OstreeRepoArchiveImportContext *ctx) +{ + GError *local_error = NULL; + const char *hardlink = archive_entry_hardlink (ctx->entry); + g_autofree char *final = NULL; + + if (hardlink != NULL) + { + final = aic_get_final_path (ctx, hardlink, &local_error); + + /* hardlinks always point to a preceding entry, so if there were an error + * it would have failed then */ + g_assert_no_error (local_error); + } + + return g_steal_pointer (&final); +} + +static OstreeRepoCommitFilterResult +aic_apply_modifier_filter (OstreeRepoArchiveImportContext *ctx, + const char *relpath, + GFileInfo **out_file_info) +{ + g_autofree char *hardlink = aic_get_final_entry_hardlink (ctx); g_autoptr(GFileInfo) file_info = NULL; - g_autoptr(GPtrArray) split_path = NULL; - g_autoptr(GPtrArray) hardlink_split_path = NULL; - glnx_unref_object OstreeMutableTree *subdir = NULL; - glnx_unref_object OstreeMutableTree *parent = NULL; - glnx_unref_object OstreeMutableTree *hardlink_source_parent = NULL; - g_autofree char *hardlink_source_checksum = NULL; - glnx_unref_object OstreeMutableTree *hardlink_source_subdir = NULL; - g_autofree guchar *tmp_csum = NULL; - g_autofree char *tmp_checksum = NULL; + g_autofree char *abspath = NULL; + const char *cb_path = NULL; - pathname = archive_entry_pathname (entry); - - if (!ot_util_path_split_validate (pathname, &split_path, error)) - goto out; - - if (split_path->len == 0) - { - parent = NULL; - basename = NULL; - } + if (ctx->opts->callback_with_entry_pathname) + cb_path = archive_entry_pathname (ctx->entry); else { - if (tmp_dir_csum) - { - g_free (tmp_checksum); - tmp_checksum = ostree_checksum_from_bytes (tmp_dir_csum); - if (!ostree_mutable_tree_ensure_parent_dirs (root, split_path, - tmp_checksum, - &parent, - error)) - goto out; - } - else - { - if (!ostree_mutable_tree_walk (root, split_path, 0, &parent, error)) - goto out; - } - basename = (char*)split_path->pdata[split_path->len-1]; + /* the user expects an abspath (where the dir to commit represents /) */ + abspath = g_build_filename ("/", relpath, NULL); + cb_path = abspath; } - hardlink = archive_entry_hardlink (entry); - if (hardlink) - { - const char *hardlink_basename; - - g_assert (parent != NULL); + file_info = file_info_from_archive_entry (ctx->entry); - if (!ot_util_path_split_validate (hardlink, &hardlink_split_path, error)) - goto out; - if (hardlink_split_path->len == 0) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Invalid hardlink path %s", hardlink); - goto out; - } - - hardlink_basename = hardlink_split_path->pdata[hardlink_split_path->len - 1]; - - if (!ostree_mutable_tree_walk (root, hardlink_split_path, 0, &hardlink_source_parent, error)) - goto out; - - if (!ostree_mutable_tree_lookup (hardlink_source_parent, hardlink_basename, - &hardlink_source_checksum, - &hardlink_source_subdir, - error)) - { - g_prefix_error (error, "While resolving hardlink target: "); - goto out; - } - - if (hardlink_source_subdir) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Hardlink %s refers to directory %s", - pathname, hardlink); - goto out; - } - g_assert (hardlink_source_checksum); - - if (!ostree_mutable_tree_replace_file (parent, - basename, - hardlink_source_checksum, - error)) - goto out; - } - else - { - file_info = file_info_from_archive_entry_and_modifier (self, entry, modifier); - - if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_UNKNOWN) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Unsupported file for import: %s", pathname); - goto out; - } - - if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) - { - - if (!_ostree_repo_write_directory_meta (self, file_info, NULL, &tmp_csum, cancellable, error)) - goto out; - - if (parent == NULL) - { - subdir = g_object_ref (root); - } - else - { - if (!ostree_mutable_tree_ensure_dir (parent, basename, &subdir, error)) - goto out; - } - - g_free (tmp_checksum); - tmp_checksum = ostree_checksum_from_bytes (tmp_csum); - ostree_mutable_tree_set_metadata_checksum (subdir, tmp_checksum); - } - else - { - if (parent == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Can't import file as root"); - goto out; - } - - if (!import_libarchive_entry_file (self, opts, a, entry, file_info, &tmp_csum, - cancellable, error)) - goto out; - - if (tmp_csum) - { - g_free (tmp_checksum); - tmp_checksum = ostree_checksum_from_bytes (tmp_csum); - if (!ostree_mutable_tree_replace_file (parent, basename, - tmp_checksum, - error)) - goto out; - } - } - } - - ret = TRUE; - out: - return ret; + return _ostree_repo_commit_modifier_apply (ctx->repo, ctx->modifier, cb_path, + file_info, out_file_info); } static gboolean -create_empty_dir_with_uidgid (OstreeRepo *self, - guint32 uid, - guint32 gid, - guint8 **out_csum, - GCancellable *cancellable, - GError **error) +aic_ensure_parent_dir_with_file_info (OstreeRepoArchiveImportContext *ctx, + OstreeMutableTree *parent, + const char *fullpath, + GFileInfo *file_info, + OstreeMutableTree **out_dir, + GCancellable *cancellable, + GError **error) { - g_autoptr(GFileInfo) tmp_dir_info = g_file_info_new (); - - g_file_info_set_attribute_uint32 (tmp_dir_info, "unix::uid", uid); - g_file_info_set_attribute_uint32 (tmp_dir_info, "unix::gid", gid); - g_file_info_set_attribute_uint32 (tmp_dir_info, "unix::mode", 0755 | S_IFDIR); - - return _ostree_repo_write_directory_meta (self, tmp_dir_info, NULL, out_csum, cancellable, error); + const char *name = glnx_basename (fullpath); + g_auto(GVariantBuilder) xattrs_builder; + g_autoptr(GVariant) xattrs = NULL; + + /* is this the root directory itself? transform into empty string */ + if (name[0] == '/' && name[1] == '\0') + name++; + + g_variant_builder_init (&xattrs_builder, (GVariantType*)"a(ayay)"); + + if (ctx->modifier && ctx->modifier->sepolicy) + if (!builder_add_label (&xattrs_builder, ctx->modifier->sepolicy, fullpath, + DEFAULT_DIRMODE, cancellable, error)) + return FALSE; + + xattrs = g_variant_ref_sink (g_variant_builder_end (&xattrs_builder)); + return mtree_ensure_dir_with_meta (ctx->repo, parent, name, file_info, + xattrs, + FALSE /* error_if_exist */, out_dir, + cancellable, error); } -#endif + +static gboolean +aic_ensure_parent_dir (OstreeRepoArchiveImportContext *ctx, + OstreeMutableTree *parent, + const char *fullpath, + OstreeMutableTree **out_dir, + GCancellable *cancellable, + GError **error) +{ + /* Who should own the parent dir? Since it's not in the archive, it's up to + * us. Here, we use the heuristic of simply creating it as the same user as + * the owner of the archive entry for which we're creating the dir. This is OK + * since any nontrivial dir perms should have explicit archive entries. */ + + guint32 uid = archive_entry_uid (ctx->entry); + guint32 gid = archive_entry_gid (ctx->entry); + glnx_unref_object GFileInfo *file_info = g_file_info_new (); + + g_file_info_set_attribute_uint32 (file_info, "unix::uid", uid); + g_file_info_set_attribute_uint32 (file_info, "unix::gid", gid); + g_file_info_set_attribute_uint32 (file_info, "unix::mode", DEFAULT_DIRMODE); + + return aic_ensure_parent_dir_with_file_info (ctx, parent, fullpath, file_info, + out_dir, cancellable, error); +} + +static gboolean +aic_create_parent_dirs (OstreeRepoArchiveImportContext *ctx, + GPtrArray *components, + OstreeMutableTree **out_subdir, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *fullpath = NULL; + glnx_unref_object OstreeMutableTree *dir = NULL; + + /* start with the root itself */ + if (!aic_ensure_parent_dir (ctx, ctx->root, "/", &dir, cancellable, error)) + return FALSE; + + for (guint i = 0; i < components->len-1; i++) + { + glnx_unref_object OstreeMutableTree *subdir = NULL; + append_path_component (&fullpath, components->pdata[i]); + + if (!aic_ensure_parent_dir (ctx, dir, fullpath, &subdir, + cancellable, error)) + return FALSE; + + g_set_object (&dir, subdir); + } + + *out_subdir = g_steal_pointer (&dir); + return TRUE; +} + +static gboolean +aic_get_parent_dir (OstreeRepoArchiveImportContext *ctx, + const char *path, + OstreeMutableTree **out_dir, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GPtrArray) components = NULL; + if (!ot_util_path_split_validate (path, &components, error)) + return FALSE; + + if (components->len == 0) /* root dir? */ + { + *out_dir = g_object_ref (ctx->root); + return TRUE; + } + + if (ostree_mutable_tree_walk (ctx->root, components, 0, out_dir, error)) + return TRUE; /* already exists, nice! */ + + if (!g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + return FALSE; /* some other error occurred */ + + if (ctx->opts->autocreate_parents) + { + g_clear_error (error); + return aic_create_parent_dirs (ctx, components, out_dir, + cancellable, error); + } + + return FALSE; +} + +static gboolean +aic_get_xattrs (OstreeRepoArchiveImportContext *ctx, + const char *path, + GFileInfo *file_info, + GVariant **out_xattrs, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *abspath = g_build_filename ("/", path, NULL); + g_autoptr(GVariant) xattrs = NULL; + const char *cb_path = abspath; + + if (ctx->opts->callback_with_entry_pathname) + cb_path = archive_entry_pathname (ctx->entry); + + if (ctx->modifier && ctx->modifier->xattr_callback) + xattrs = ctx->modifier->xattr_callback (ctx->repo, cb_path, file_info, + ctx->modifier->xattr_user_data); + + if (ctx->modifier && ctx->modifier->sepolicy) + { + mode_t mode = g_file_info_get_attribute_uint32 (file_info, "unix::mode"); + g_autoptr(GVariantBuilder) builder = + ot_util_variant_builder_from_variant (xattrs, G_VARIANT_TYPE + ("a(ayay)")); + + if (!builder_add_label (builder, ctx->modifier->sepolicy, abspath, mode, + cancellable, error)) + return FALSE; + + if (xattrs) + g_variant_unref (xattrs); + + xattrs = g_variant_builder_end (builder); + g_variant_ref_sink (xattrs); + } + + *out_xattrs = g_steal_pointer (&xattrs); + return TRUE; +} + +/* XXX: add option in ctx->opts to disallow already existing dirs? see + * error_if_exist */ +static gboolean +aic_handle_dir (OstreeRepoArchiveImportContext *ctx, + OstreeMutableTree *parent, + const char *path, + GFileInfo *fi, + GCancellable *cancellable, + GError **error) +{ + const char *name = glnx_basename (path); + g_autoptr(GVariant) xattrs = NULL; + + if (!aic_get_xattrs (ctx, path, fi, &xattrs, cancellable, error)) + return FALSE; + + return mtree_ensure_dir_with_meta (ctx->repo, parent, name, fi, xattrs, + FALSE /* error_if_exist */, NULL, + cancellable, error); +} + +static gboolean +aic_write_file (OstreeRepoArchiveImportContext *ctx, + GFileInfo *fi, + GVariant *xattrs, + char **out_csum, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GInputStream) archive_stream = NULL; + g_autoptr(GInputStream) file_object_input = NULL; + guint64 length; + + g_autofree guchar *csum_raw = NULL; + + if (g_file_info_get_file_type (fi) == G_FILE_TYPE_REGULAR) + archive_stream = _ostree_libarchive_input_stream_new (ctx->archive); + + if (!ostree_raw_file_to_content_stream (archive_stream, fi, xattrs, + &file_object_input, &length, + cancellable, error)) + return FALSE; + + if (!ostree_repo_write_content (ctx->repo, NULL, file_object_input, length, + &csum_raw, cancellable, error)) + return FALSE; + + *out_csum = ostree_checksum_from_bytes (csum_raw); + return TRUE; +} + +static gboolean +aic_import_file (OstreeRepoArchiveImportContext *ctx, + OstreeMutableTree *parent, + const char *path, + GFileInfo *fi, + GCancellable *cancellable, + GError **error) +{ + const char *name = glnx_basename (path); + g_autoptr(GVariant) xattrs = NULL; + g_autofree char *csum = NULL; + + if (!aic_get_xattrs (ctx, path, fi, &xattrs, cancellable, error)) + return FALSE; + + if (!aic_write_file (ctx, fi, xattrs, &csum, cancellable, error)) + return FALSE; + + if (!ostree_mutable_tree_replace_file (parent, name, csum, error)) + return FALSE; + + return TRUE; +} + +static void +aic_add_deferred_hardlink (OstreeRepoArchiveImportContext *ctx, + const char *hardlink, + DeferredHardlink *dh) +{ + gboolean new_slist; + GSList *slist; + + slist = g_hash_table_lookup (ctx->deferred_hardlinks, hardlink); + new_slist = (slist == NULL); + + slist = g_slist_append (slist, dh); + + if (new_slist) + g_hash_table_insert (ctx->deferred_hardlinks, g_strdup (hardlink), slist); +} + +static void +aic_defer_hardlink (OstreeRepoArchiveImportContext *ctx, + OstreeMutableTree *parent, + const char *path, + guint64 size, + const char *hardlink) +{ + DeferredHardlink *dh = g_slice_new (DeferredHardlink); + dh->parent = g_object_ref (parent); + dh->path = g_strdup (path); + dh->size = size; + + aic_add_deferred_hardlink (ctx, hardlink, dh); +} + +static gboolean +aic_handle_file (OstreeRepoArchiveImportContext *ctx, + OstreeMutableTree *parent, + const char *path, + GFileInfo *fi, + GCancellable *cancellable, + GError **error) +{ + /* The wonderful world of hardlinks and archives. We have to be very careful + * here. Do not assume that if a file is a hardlink, it will have size 0 (e.g. + * cpio). Do not assume that if a file will have hardlinks to it, it will have + * size > 0. Also do not assume that its nlink param is present (tar) or even + * accurate (cpio). Also do not assume that hardlinks follow each other in + * order of entries. + * + * These archives were made to be extracted onto a filesystem, not directly + * hashed into an object store. So to be careful, we defer all hardlink + * imports until the very end. Nonzero files have to be imported, hardlink or + * not, since we can't easily seek back to this position later on. + * */ + + g_autofree char *hardlink = aic_get_final_entry_hardlink (ctx); + guint64 size = g_file_info_get_attribute_uint64 (fi, "standard::size"); + + if (hardlink == NULL || size > 0) + if (!aic_import_file (ctx, parent, path, fi, cancellable, error)) + return FALSE; + + if (hardlink) + aic_defer_hardlink (ctx, parent, path, size, hardlink); + + return TRUE; +} + +static gboolean +aic_handle_entry (OstreeRepoArchiveImportContext *ctx, + OstreeMutableTree *parent, + const char *path, + GFileInfo *fi, + GCancellable *cancellable, + GError **error) +{ + switch (g_file_info_get_file_type (fi)) + { + case G_FILE_TYPE_DIRECTORY: + return aic_handle_dir (ctx, parent, path, fi, cancellable, error); + case G_FILE_TYPE_REGULAR: + case G_FILE_TYPE_SYMBOLIC_LINK: + return aic_handle_file (ctx, parent, path, fi, cancellable, error); + default: + if (ctx->opts->ignore_unsupported_content) + return TRUE; + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unsupported file type for path \"%s\"", path); + return FALSE; + } + } +} + +static gboolean +aic_import_entry (OstreeRepoArchiveImportContext *ctx, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GFileInfo) fi = NULL; + glnx_unref_object OstreeMutableTree *parent = NULL; + g_autofree char *path = aic_get_final_entry_pathname (ctx, error); + + if (path == NULL) + return FALSE; + + if (aic_apply_modifier_filter (ctx, path, &fi) + == OSTREE_REPO_COMMIT_FILTER_SKIP) + return TRUE; + + if (!aic_get_parent_dir (ctx, path, &parent, cancellable, error)) + return FALSE; + + return aic_handle_entry (ctx, parent, path, fi, cancellable, error); +} + +static gboolean +aic_import_from_hardlink (OstreeRepoArchiveImportContext *ctx, + const char *target, + DeferredHardlink *dh, + GError **error) +{ + g_autofree char *csum = NULL; + const char *name = glnx_basename (target); + const char *name_dh = glnx_basename (dh->path); + g_autoptr(GPtrArray) components = NULL; + glnx_unref_object OstreeMutableTree *parent = NULL; + + if (!ostree_mutable_tree_lookup (dh->parent, name_dh, &csum, NULL, error)) + return FALSE; + + g_assert (csum); + + if (!ot_util_path_split_validate (target, &components, error)) + return FALSE; + + if (!ostree_mutable_tree_walk (ctx->root, components, 0, &parent, error)) + return FALSE; + + if (!ostree_mutable_tree_replace_file (parent, name, csum, error)) + return FALSE; + + return TRUE; +} + +static gboolean +aic_lookup_file_csum (OstreeRepoArchiveImportContext *ctx, + const char *target, + char **out_csum, + GError **error) +{ + g_autofree char *csum = NULL; + const char *name = glnx_basename (target); + glnx_unref_object OstreeMutableTree *parent = NULL; + glnx_unref_object OstreeMutableTree *subdir = NULL; + g_autoptr(GPtrArray) components = NULL; + + if (!ot_util_path_split_validate (target, &components, error)) + return FALSE; + + if (!ostree_mutable_tree_walk (ctx->root, components, 0, &parent, error)) + return FALSE; + + if (!ostree_mutable_tree_lookup (parent, name, &csum, &subdir, error)) + return FALSE; + + if (subdir != NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Expected hardlink file target at \"%s\" but found a " + "directory", target); + return FALSE; + } + + *out_csum = g_steal_pointer (&csum); + return TRUE; +} + +static gboolean +aic_import_deferred_hardlinks_for (OstreeRepoArchiveImportContext *ctx, + const char *target, + GSList *hardlinks, + GError **error) +{ + GSList *payload = hardlinks; + g_autofree char *csum = NULL; + + /* find node with the payload, if any (if none, then they're all hardlinks to + * a zero sized target, and there's no rewrite required) */ + while (payload && ((DeferredHardlink*)payload->data)->size == 0) + payload = g_slist_next (payload); + + /* rewrite the target so it points to the csum of the payload hardlink */ + if (payload) + if (!aic_import_from_hardlink (ctx, target, payload->data, error)) + return FALSE; + + if (!aic_lookup_file_csum (ctx, target, &csum, error)) + return FALSE; + + /* import all the hardlinks */ + for (GSList *hl = hardlinks; hl != NULL; hl = g_slist_next (hl)) + { + DeferredHardlink *df = hl->data; + const char *name = glnx_basename (df->path); + + if (hl == payload) + continue; /* small optimization; no need to redo this one */ + + if (!ostree_mutable_tree_replace_file (df->parent, name, csum, error)) + return FALSE; + } + + return TRUE; +} + +static gboolean +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; + + return TRUE; +} + +static void +deferred_hardlink_free (void *data) +{ + DeferredHardlink *dh = data; + g_object_unref (dh->parent); + g_free (dh->path); + g_slice_free (DeferredHardlink, dh); +} + +static void +deferred_hardlinks_list_free (void *data) +{ + GSList *slist = data; + g_slist_free_full (slist, deferred_hardlink_free); +} +#endif /* HAVE_LIBARCHIVE */ /** * ostree_repo_import_archive_to_mtree: @@ -334,61 +828,56 @@ ostree_repo_import_archive_to_mtree (OstreeRepo *self, #ifdef HAVE_LIBARCHIVE gboolean ret = FALSE; struct archive *a = archive; - struct archive_entry *entry; - g_autofree guchar *tmp_csum = NULL; - int r; + g_autoptr(GHashTable) deferred_hardlinks = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + deferred_hardlinks_list_free); + OstreeRepoArchiveImportContext aictx = { + .repo = self, + .opts = opts, + .root = mtree, + .archive = archive, + .deferred_hardlinks = deferred_hardlinks, + .modifier = modifier + }; while (TRUE) { - r = archive_read_next_header (a, &entry); + int r = archive_read_next_header (a, &aictx.entry); if (r == ARCHIVE_EOF) break; - else if (r != ARCHIVE_OK) + if (r != ARCHIVE_OK) { propagate_libarchive_error (error, a); goto out; } - /* TODO - refactor this to only create the metadata on demand - * (i.e. if there is a missing parent dir) - */ - if (opts->autocreate_parents && !tmp_csum) - { - /* Here, we auto-pick the first uid/gid we find in the - * archive. Realistically this is probably always going to - * be root, but eh, at least we try to match. - */ - if (!create_empty_dir_with_uidgid (self, archive_entry_uid (entry), - archive_entry_gid (entry), - &tmp_csum, cancellable, error)) - goto out; - } + if (g_cancellable_set_error_if_cancelled (cancellable, error)) + goto out; - if (!write_libarchive_entry_to_mtree (self, opts, mtree, a, - entry, modifier, tmp_csum, - cancellable, error)) + if (!aic_import_entry (&aictx, cancellable, error)) goto out; } + if (!aic_import_deferred_hardlinks (&aictx, cancellable, error)) + goto out; + /* If we didn't import anything at all, and autocreation of parents * is enabled, automatically create a root directory. This is * useful primarily when importing Docker image layers, which can * just be metadata. */ - if (!ostree_mutable_tree_get_metadata_checksum (mtree) && opts->autocreate_parents) + if (opts->autocreate_parents && + ostree_mutable_tree_get_metadata_checksum (mtree) == NULL) { - char tmp_checksum[65]; + glnx_unref_object GFileInfo *fi = g_file_info_new (); + g_file_info_set_attribute_uint32 (fi, "unix::uid", 0); + g_file_info_set_attribute_uint32 (fi, "unix::gid", 0); + g_file_info_set_attribute_uint32 (fi, "unix::mode", DEFAULT_DIRMODE); - if (!tmp_csum) - { - /* We didn't have any archive entries to match, so pick uid 0, gid 0. */ - if (!create_empty_dir_with_uidgid (self, 0, 0, &tmp_csum, cancellable, error)) - goto out; - } - - ostree_checksum_inplace_from_bytes (tmp_csum, tmp_checksum); - ostree_mutable_tree_set_metadata_checksum (mtree, tmp_checksum); + if (!aic_ensure_parent_dir_with_file_info (&aictx, mtree, "/", fi, NULL, + cancellable, error)) + goto out; } ret = TRUE; @@ -400,7 +889,7 @@ ostree_repo_import_archive_to_mtree (OstreeRepo *self, return FALSE; #endif } - + /** * ostree_repo_write_archive_to_mtree: * @self: An #OstreeRepo @@ -420,8 +909,8 @@ ostree_repo_write_archive_to_mtree (OstreeRepo *self, OstreeMutableTree *mtree, OstreeRepoCommitModifier *modifier, gboolean autocreate_parents, - GCancellable *cancellable, - GError **error) + GCancellable *cancellable, + GError **error) { #ifdef HAVE_LIBARCHIVE gboolean ret = FALSE; @@ -480,16 +969,22 @@ file_to_archive_entry_common (GFile *root, g_autoptr(GVariant) xattrs = NULL; time_t ts = (time_t) opts->timestamp_secs; - if (pathstr && !pathstr[0]) + if (opts->path_prefix && opts->path_prefix[0]) + { + g_autofree char *old_pathstr = pathstr; + pathstr = g_strconcat (opts->path_prefix, old_pathstr, NULL); + } + + if (pathstr == NULL || !pathstr[0]) { g_free (pathstr); pathstr = g_strdup ("."); } archive_entry_update_pathname_utf8 (entry, pathstr); - archive_entry_set_ctime (entry, ts, 0); - archive_entry_set_mtime (entry, ts, 0); - archive_entry_set_atime (entry, ts, 0); + archive_entry_set_ctime (entry, ts, OSTREE_TIMESTAMP); + archive_entry_set_mtime (entry, ts, OSTREE_TIMESTAMP); + archive_entry_set_atime (entry, ts, OSTREE_TIMESTAMP); archive_entry_set_uid (entry, g_file_info_get_attribute_uint32 (file_info, "unix::uid")); archive_entry_set_gid (entry, g_file_info_get_attribute_uint32 (file_info, "unix::gid")); archive_entry_set_mode (entry, g_file_info_get_attribute_uint32 (file_info, "unix::mode")); diff --git a/src/libostree/ostree-repo-private.h b/src/libostree/ostree-repo-private.h index 6a9092e9..f330d169 100644 --- a/src/libostree/ostree-repo-private.h +++ b/src/libostree/ostree-repo-private.h @@ -23,10 +23,6 @@ #include "ostree-repo.h" #include "libglnx.h" -#ifdef HAVE_LIBSOUP -#include "ostree-fetcher.h" -#endif - G_BEGIN_DECLS #define OSTREE_DELTAPART_VERSION (0) @@ -36,6 +32,28 @@ G_BEGIN_DECLS #define _OSTREE_SUMMARY_CACHE_DIR "summaries" #define _OSTREE_CACHE_DIR "cache" +#define OSTREE_TIMESTAMP (1) + +typedef enum { + OSTREE_REPO_TEST_ERROR_PRE_COMMIT = (1 << 0) +} OstreeRepoTestErrorFlags; + +struct OstreeRepoCommitModifier { + volatile gint refcount; + + OstreeRepoCommitModifierFlags flags; + OstreeRepoCommitFilter filter; + gpointer user_data; + GDestroyNotify destroy_notify; + + OstreeRepoCommitModifierXattrCallback xattr_callback; + GDestroyNotify xattr_destroy; + gpointer xattr_user_data; + + OstreeSePolicy *sepolicy; + GHashTable *devino_cache; +}; + /** * OstreeRepo: * @@ -44,7 +62,7 @@ G_BEGIN_DECLS struct OstreeRepo { GObject parent; - char *boot_id; + char *stagedir_prefix; int commit_stagedir_fd; char *commit_stagedir_name; GLnxLockFile commit_stagedir_lock; @@ -86,12 +104,15 @@ struct OstreeRepo { uid_t target_owner_uid; gid_t target_owner_gid; + guint test_error_flags; /* OstreeRepoTestErrorFlags */ + GKeyFile *config; GHashTable *remotes; GMutex remotes_lock; OstreeRepoMode mode; gboolean enable_uncompressed_cache; gboolean generate_sizes; + guint64 tmp_expiry_seconds; OstreeRepo *parent_repo; }; @@ -102,6 +123,9 @@ typedef struct { char checksum[65]; } OstreeDevIno; +#define OSTREE_REPO_TMPDIR_STAGING "staging-" +#define OSTREE_REPO_TMPDIR_FETCHER "fetcher-" + gboolean _ostree_repo_allocate_tmpdir (int tmpdir_dfd, const char *tmpdir_prefix, @@ -112,6 +136,16 @@ _ostree_repo_allocate_tmpdir (int tmpdir_dfd, GCancellable *cancellable, GError **error); +gboolean +_ostree_repo_is_locked_tmpdir (const char *filename); + +gboolean +_ostree_repo_try_lock_tmpdir (int tmpdir_dfd, + const char *tmpdir_name, + GLnxLockFile *file_lock_out, + gboolean *out_did_lock, + GError **error); + gboolean _ostree_repo_ensure_loose_objdir_at (int dfd, const char *loose_path, @@ -135,8 +169,6 @@ _ostree_repo_has_loose_object (OstreeRepo *self, const char *checksum, OstreeObjectType objtype, gboolean *out_is_stored, - char *loose_path_buf, - GFile **out_stored_path, GCancellable *cancellable, GError **error); @@ -199,13 +231,6 @@ _ostree_repo_commit_modifier_apply (OstreeRepo *self, gboolean _ostree_repo_remote_name_is_file (const char *remote_name); -#ifdef HAVE_LIBSOUP -OstreeFetcher * -_ostree_repo_remote_new_fetcher (OstreeRepo *self, - const char *remote_name, - GError **error); -#endif - OstreeGpgVerifyResult * _ostree_repo_gpg_verify_with_metadata (OstreeRepo *self, GBytes *signed_data, @@ -293,22 +318,4 @@ gboolean _ostree_repo_update_mtime (OstreeRepo *self, GError **error); -/* Load the summary from the cache if the provided .sig file is the same as the - cached version. */ -gboolean -_ostree_repo_load_cache_summary_if_same_sig (OstreeRepo *self, - const char *remote, - GBytes *summary_sig, - GBytes **summary, - GCancellable *cancellable, - GError **error); - -gboolean -_ostree_repo_cache_summary (OstreeRepo *self, - const char *remote, - GBytes *summary, - GBytes *summary_sig, - GCancellable *cancellable, - GError **error); - G_END_DECLS diff --git a/src/libostree/ostree-repo-pull.c b/src/libostree/ostree-repo-pull.c index eef5f039..53847340 100644 --- a/src/libostree/ostree-repo-pull.c +++ b/src/libostree/ostree-repo-pull.c @@ -23,11 +23,15 @@ #include "config.h" #include "ostree.h" +#include "otutil.h" + +#ifdef HAVE_LIBSOUP + +#include "libglnx.h" #include "ostree-core-private.h" #include "ostree-repo-private.h" #include "ostree-repo-static-delta-private.h" #include "ostree-metalink.h" -#include "otutil.h" #include "ot-fs-utils.h" #include @@ -629,6 +633,7 @@ content_fetch_on_write_complete (GObject *object, const char *expected_checksum; g_autofree guchar *csum = NULL; g_autofree char *checksum = NULL; + g_autofree char *checksum_obj = NULL; if (!ostree_repo_write_content_finish ((OstreeRepo*)object, result, &csum, error)) @@ -639,7 +644,8 @@ content_fetch_on_write_complete (GObject *object, ostree_object_name_deserialize (fetch_data->object, &expected_checksum, &objtype); g_assert (objtype == OSTREE_OBJECT_TYPE_FILE); - g_debug ("write of %s complete", ostree_object_to_string (checksum, objtype)); + checksum_obj = ostree_object_to_string (checksum, objtype); + g_debug ("write of %s complete", checksum_obj); if (strcmp (checksum, expected_checksum) != 0) { @@ -675,6 +681,7 @@ content_fetch_on_complete (GObject *object, g_autoptr(GInputStream) object_input = NULL; g_autofree char *temp_path = NULL; const char *checksum; + g_autofree char *checksum_obj = NULL; OstreeObjectType objtype; temp_path = _ostree_fetcher_request_uri_with_partial_finish (fetcher, result, error); @@ -684,7 +691,8 @@ content_fetch_on_complete (GObject *object, ostree_object_name_deserialize (fetch_data->object, &checksum, &objtype); g_assert (objtype == OSTREE_OBJECT_TYPE_FILE); - g_debug ("fetch of %s complete", ostree_object_to_string (checksum, objtype)); + checksum_obj = ostree_object_to_string (checksum, objtype); + g_debug ("fetch of %s complete", checksum_obj); if (pull_data->is_mirror && pull_data->repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2) { @@ -796,13 +804,16 @@ meta_fetch_on_complete (GObject *object, g_autoptr(GVariant) metadata = NULL; g_autofree char *temp_path = NULL; const char *checksum; + g_autofree char *checksum_obj = NULL; OstreeObjectType objtype; GError *local_error = NULL; GError **error = &local_error; glnx_fd_close int fd = -1; + gboolean free_fetch_data = FALSE; ostree_object_name_deserialize (fetch_data->object, &checksum, &objtype); - g_debug ("fetch of %s%s complete", ostree_object_to_string (checksum, objtype), + checksum_obj = ostree_object_to_string (checksum, objtype); + g_debug ("fetch of %s%s complete", checksum_obj, fetch_data->is_detached_meta ? " (detached)" : ""); temp_path = _ostree_fetcher_request_uri_with_partial_finish (fetcher, result, error); @@ -864,6 +875,8 @@ meta_fetch_on_complete (GObject *object, if (!fetch_data->object_is_stored) enqueue_one_object_request (pull_data, checksum, objtype, FALSE, FALSE); + + free_fetch_data = TRUE; } else { @@ -901,7 +914,7 @@ meta_fetch_on_complete (GObject *object, pull_data->n_outstanding_metadata_fetches--; pull_data->n_fetched_metadata++; check_outstanding_requests_handle_error (pull_data, local_error); - if (local_error) + if (local_error || free_fetch_data) { g_variant_unref (fetch_data->object); g_free (fetch_data); @@ -1445,6 +1458,7 @@ request_static_delta_superblock_sync (OtPullData *pull_data, if (out_delta_superblock) *out_delta_superblock = g_steal_pointer (&ret_delta_superblock); out: + g_clear_pointer (&target_uri, (GDestroyNotify) soup_uri_free); return ret; } @@ -1734,47 +1748,9 @@ validate_variant_is_csum (GVariant *csum, return ret; } -/* documented in ostree-repo.c */ -gboolean -ostree_repo_pull (OstreeRepo *self, - const char *remote_name, - char **refs_to_fetch, - OstreeRepoPullFlags flags, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error) -{ - return ostree_repo_pull_one_dir (self, remote_name, NULL, refs_to_fetch, flags, progress, cancellable, error); -} - -/* Documented in ostree-repo.c */ -gboolean -ostree_repo_pull_one_dir (OstreeRepo *self, - const char *remote_name, - const char *dir_to_pull, - char **refs_to_fetch, - OstreeRepoPullFlags flags, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error) -{ - GVariantBuilder builder; - g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); - - if (dir_to_pull) - g_variant_builder_add (&builder, "{s@v}", "subdir", - g_variant_new_variant (g_variant_new_string (dir_to_pull))); - g_variant_builder_add (&builder, "{s@v}", "flags", - g_variant_new_variant (g_variant_new_int32 (flags))); - if (refs_to_fetch) - g_variant_builder_add (&builder, "{s@v}", "refs", - g_variant_new_variant (g_variant_new_strv ((const char *const*) refs_to_fetch, -1))); - - return ostree_repo_pull_with_options (self, remote_name, g_variant_builder_end (&builder), - progress, cancellable, error); -} - -gboolean +/* Load the summary from the cache if the provided .sig file is the same as the + cached version. */ +static gboolean _ostree_repo_load_cache_summary_if_same_sig (OstreeRepo *self, const char *remote, GBytes *summary_sig, @@ -1836,7 +1812,7 @@ _ostree_repo_load_cache_summary_if_same_sig (OstreeRepo *self, return ret; } -gboolean +static gboolean _ostree_repo_cache_summary (OstreeRepo *self, const char *remote, GBytes *summary, @@ -1876,7 +1852,311 @@ _ostree_repo_cache_summary (OstreeRepo *self, } -/* Documented in ostree-repo.c */ +static OstreeFetcher * +_ostree_repo_remote_new_fetcher (OstreeRepo *self, + const char *remote_name, + GError **error) +{ + OstreeFetcher *fetcher = NULL; + OstreeFetcherConfigFlags fetcher_flags = 0; + gboolean tls_permissive = FALSE; + gboolean success = FALSE; + + g_return_val_if_fail (OSTREE_IS_REPO (self), NULL); + g_return_val_if_fail (remote_name != NULL, NULL); + + if (!ostree_repo_get_remote_boolean_option (self, remote_name, + "tls-permissive", FALSE, + &tls_permissive, error)) + goto out; + + if (tls_permissive) + fetcher_flags |= OSTREE_FETCHER_FLAGS_TLS_PERMISSIVE; + + fetcher = _ostree_fetcher_new (self->tmp_dir_fd, fetcher_flags); + + { + g_autofree char *tls_client_cert_path = NULL; + g_autofree char *tls_client_key_path = NULL; + + if (!ostree_repo_get_remote_option (self, remote_name, + "tls-client-cert-path", NULL, + &tls_client_cert_path, error)) + goto out; + if (!ostree_repo_get_remote_option (self, remote_name, + "tls-client-key-path", NULL, + &tls_client_key_path, error)) + goto out; + + if ((tls_client_cert_path != NULL) != (tls_client_key_path != NULL)) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Remote \"%s\" must specify both " + "\"tls-client-cert-path\" and \"tls-client-key-path\"", + remote_name); + goto out; + } + else if (tls_client_cert_path != NULL) + { + g_autoptr(GTlsCertificate) client_cert = NULL; + + g_assert (tls_client_key_path != NULL); + + client_cert = g_tls_certificate_new_from_files (tls_client_cert_path, + tls_client_key_path, + error); + if (client_cert == NULL) + goto out; + + _ostree_fetcher_set_client_cert (fetcher, client_cert); + } + } + + { + g_autofree char *tls_ca_path = NULL; + + if (!ostree_repo_get_remote_option (self, remote_name, + "tls-ca-path", NULL, + &tls_ca_path, error)) + goto out; + + if (tls_ca_path != NULL) + { + g_autoptr(GTlsDatabase) db = NULL; + + db = g_tls_file_database_new (tls_ca_path, error); + if (db == NULL) + goto out; + + _ostree_fetcher_set_tls_database (fetcher, db); + } + } + + { + g_autofree char *http_proxy = NULL; + + if (!ostree_repo_get_remote_option (self, remote_name, + "proxy", NULL, + &http_proxy, error)) + goto out; + + if (http_proxy != NULL) + _ostree_fetcher_set_proxy (fetcher, http_proxy); + } + + success = TRUE; + +out: + if (!success) + g_clear_object (&fetcher); + + return fetcher; +} + +static gboolean +_ostree_preload_metadata_file (OstreeRepo *self, + OstreeFetcher *fetcher, + SoupURI *base_uri, + const char *filename, + gboolean is_metalink, + GBytes **out_bytes, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + if (is_metalink) + { + glnx_unref_object OstreeMetalink *metalink = NULL; + GError *local_error = NULL; + + metalink = _ostree_metalink_new (fetcher, filename, + OSTREE_MAX_METADATA_SIZE, + base_uri); + + _ostree_metalink_request_sync (metalink, NULL, out_bytes, NULL, + cancellable, &local_error); + + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) + { + g_clear_error (&local_error); + *out_bytes = NULL; + } + else if (local_error != NULL) + { + g_propagate_error (error, local_error); + goto out; + } + } + else + { + SoupURI *uri; + const char *base_path; + g_autofree char *path = NULL; + + base_path = soup_uri_get_path (base_uri); + path = g_build_filename (base_path, filename, NULL); + uri = soup_uri_new_with_base (base_uri, path); + + ret = _ostree_fetcher_request_uri_to_membuf (fetcher, uri, + FALSE, TRUE, + out_bytes, + OSTREE_MAX_METADATA_SIZE, + cancellable, error); + soup_uri_free (uri); + + if (!ret) + goto out; + } + + ret = TRUE; +out: + return ret; +} + +static gboolean +repo_remote_fetch_summary (OstreeRepo *self, + const char *name, + const char *metalink_url_string, + GVariant *options, + GBytes **out_summary, + GBytes **out_signatures, + GCancellable *cancellable, + GError **error) +{ + glnx_unref_object OstreeFetcher *fetcher = NULL; + g_autoptr(GMainContext) mainctx = NULL; + gboolean ret = FALSE; + SoupURI *base_uri = NULL; + gboolean from_cache = FALSE; + g_autofree char *url_override = NULL; + + if (options) + (void) g_variant_lookup (options, "override-url", "&s", &url_override); + + mainctx = g_main_context_new (); + g_main_context_push_thread_default (mainctx); + + fetcher = _ostree_repo_remote_new_fetcher (self, name, error); + if (fetcher == NULL) + goto out; + + { + g_autofree char *url_string = NULL; + if (metalink_url_string) + url_string = g_strdup (metalink_url_string); + else if (url_override) + url_string = g_strdup (url_override); + else if (!ostree_repo_remote_get_url (self, name, &url_string, error)) + goto out; + + base_uri = soup_uri_new (url_string); + if (base_uri == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid URL '%s'", url_string); + goto out; + } + } + + if (!_ostree_preload_metadata_file (self, + fetcher, + base_uri, + "summary.sig", + metalink_url_string ? TRUE : FALSE, + out_signatures, + cancellable, + error)) + goto out; + + if (*out_signatures) + { + if (!_ostree_repo_load_cache_summary_if_same_sig (self, + name, + *out_signatures, + out_summary, + cancellable, + error)) + goto out; + } + + if (*out_summary) + from_cache = TRUE; + else + { + if (!_ostree_preload_metadata_file (self, + fetcher, + base_uri, + "summary", + metalink_url_string ? TRUE : FALSE, + out_summary, + cancellable, + error)) + goto out; + } + + if (!from_cache && *out_summary && *out_signatures) + { + g_autoptr(GError) temp_error = NULL; + + if (!_ostree_repo_cache_summary (self, + name, + *out_summary, + *out_signatures, + cancellable, + &temp_error)) + { + if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) + g_debug ("No permissions to save summary cache"); + else + { + g_propagate_error (error, g_steal_pointer (&temp_error)); + goto out; + } + } + } + + ret = TRUE; + + out: + if (mainctx) + g_main_context_pop_thread_default (mainctx); + if (base_uri != NULL) + soup_uri_free (base_uri); + return ret; +} + +/* ------------------------------------------------------------------------------------------ + * Below is the libsoup-invariant API; these should match + * the stub functions in the #else clause + * ------------------------------------------------------------------------------------------ + */ + +/** + * ostree_repo_pull_with_options: + * @self: Repo + * @remote_name: Name of remote + * @options: A GVariant a{sv} with an extensible set of flags. + * @progress: (allow-none): Progress + * @cancellable: Cancellable + * @error: Error + * + * Like ostree_repo_pull(), but supports an extensible set of flags. + * The following are currently defined: + * + * * refs (as): Array of string refs + * * flags (i): An instance of #OstreeRepoPullFlags + * * subdir (s): Pull just this subdirectory + * * override-remote-name (s): If local, add this remote to refspec + * * gpg-verify (b): GPG verify commits + * * gpg-verify-summary (b): GPG verify summary + * * depth (i): How far in the history to traverse; default is 0, -1 means infinite + * * disable-static-deltas (b): Do not use static deltas + * * require-static-deltas (b): Require static deltas + * * override-commit-ids (as): Array of specific commit IDs to fetch for refs + * * dry-run (b): Only print information on what will be downloaded (requires static deltas) + * * override-url (s): Fetch objects from this URL if remote specifies no metalink in options + */ gboolean ostree_repo_pull_with_options (OstreeRepo *self, const char *remote_name_or_baseurl, @@ -1889,8 +2169,6 @@ ostree_repo_pull_with_options (OstreeRepo *self, GHashTableIter hash_iter; gpointer key, value; g_autoptr(GBytes) bytes_summary = NULL; - g_autofree char *remote_key = NULL; - g_autofree char *path = NULL; g_autofree char *metalink_url_str = NULL; g_autoptr(GHashTable) requested_refs_to_fetch = NULL; g_autoptr(GHashTable) commits_to_fetch = NULL; @@ -1904,13 +2182,14 @@ ostree_repo_pull_with_options (OstreeRepo *self, guint64 end_time; OstreeRepoPullFlags flags = 0; const char *dir_to_pull = NULL; - char **refs_to_fetch = NULL; + g_autofree char **refs_to_fetch = NULL; char **override_commit_ids = NULL; GSource *update_timeout = NULL; gboolean disable_static_deltas = FALSE; gboolean require_static_deltas = FALSE; gboolean opt_gpg_verify = FALSE; gboolean opt_gpg_verify_summary = FALSE; + const char *url_override = NULL; if (options) { @@ -1928,6 +2207,7 @@ ostree_repo_pull_with_options (OstreeRepo *self, (void) g_variant_lookup (options, "require-static-deltas", "b", &require_static_deltas); (void) g_variant_lookup (options, "override-commit-ids", "^a&s", &override_commit_ids); (void) g_variant_lookup (options, "dry-run", "b", &pull_data->dry_run); + (void) g_variant_lookup (options, "override-url", "&s", &url_override); } g_return_val_if_fail (pull_data->maxdepth >= -1, FALSE); @@ -2023,7 +2303,9 @@ ostree_repo_pull_with_options (OstreeRepo *self, { g_autofree char *baseurl = NULL; - if (!ostree_repo_remote_get_url (self, remote_name_or_baseurl, &baseurl, error)) + if (url_override != NULL) + baseurl = g_strdup (url_override); + else if (!ostree_repo_remote_get_url (self, remote_name_or_baseurl, &baseurl, error)) goto out; pull_data->base_uri = soup_uri_new (baseurl); @@ -2587,3 +2869,121 @@ ostree_repo_pull_with_options (OstreeRepo *self, g_clear_pointer (&remote_config, (GDestroyNotify) g_key_file_unref); return ret; } + +/** + * ostree_repo_remote_fetch_summary_with_options: + * @self: Self + * @name: name of a remote + * @options: (nullable): A GVariant a{sv} with an extensible set of flags + * @out_summary: (nullable): return location for raw summary data, or %NULL + * @out_signatures: (nullable): return location for raw summary signature + * data, or %NULL + * @cancellable: a #GCancellable + * @error: a #GError + * + * Like ostree_repo_remote_fetch_summary(), but supports an extensible set of flags. + * The following are currently defined: + * + * - override-url (s): Fetch summary from this URL if remote specifies no metalink in options + * + * Returns: %TRUE on success, %FALSE on failure + */ +gboolean +ostree_repo_remote_fetch_summary_with_options (OstreeRepo *self, + const char *name, + GVariant *options, + GBytes **out_summary, + GBytes **out_signatures, + GCancellable *cancellable, + GError **error) +{ + g_autofree char *metalink_url_string = NULL; + g_autoptr(GBytes) summary = NULL; + g_autoptr(GBytes) signatures = NULL; + gboolean ret = FALSE; + gboolean gpg_verify_summary; + + g_return_val_if_fail (OSTREE_REPO (self), FALSE); + g_return_val_if_fail (name != NULL, FALSE); + + if (!ostree_repo_get_remote_option (self, name, "metalink", NULL, + &metalink_url_string, error)) + goto out; + + if (!repo_remote_fetch_summary (self, + name, + metalink_url_string, + options, + &summary, + &signatures, + cancellable, + error)) + goto out; + + if (!ostree_repo_remote_get_gpg_verify_summary (self, name, &gpg_verify_summary, error)) + goto out; + + if (gpg_verify_summary && signatures == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "GPG verification enabled, but no summary signatures found (use gpg-verify-summary=false in remote config to disable)"); + goto out; + } + + /* Verify any summary signatures. */ + if (gpg_verify_summary && summary != NULL && signatures != NULL) + { + glnx_unref_object OstreeGpgVerifyResult *result = NULL; + + result = ostree_repo_verify_summary (self, + name, + summary, + signatures, + cancellable, + error); + if (!ostree_gpg_verify_result_require_valid_signature (result, error)) + goto out; + } + + if (out_summary != NULL) + *out_summary = g_steal_pointer (&summary); + + if (out_signatures != NULL) + *out_signatures = g_steal_pointer (&signatures); + + ret = TRUE; + +out: + return ret; +} + +#else /* HAVE_LIBSOUP */ + +gboolean +ostree_repo_pull_with_options (OstreeRepo *self, + const char *remote_name, + GVariant *options, + OstreeAsyncProgress *progress, + GCancellable *cancellable, + GError **error) +{ + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "This version of ostree was built without libsoup, and cannot fetch over HTTP"); + return FALSE; +} + +gboolean +ostree_repo_remote_fetch_summary_with_options (OstreeRepo *self, + const char *name, + GVariant *options, + GBytes **out_summary, + GBytes **out_signatures, + GCancellable *cancellable, + GError **error) +{ + g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, + "This version of ostree was built without libsoup, and cannot fetch over HTTP"); + return FALSE; +} + +#endif /* HAVE_LIBSOUP */ diff --git a/src/libostree/ostree-repo-static-delta-compilation.c b/src/libostree/ostree-repo-static-delta-compilation.c index b3989234..4e4e7f9c 100644 --- a/src/libostree/ostree-repo-static-delta-compilation.c +++ b/src/libostree/ostree-repo-static-delta-compilation.c @@ -1226,7 +1226,7 @@ get_fallback_headers (OstreeRepo *self, * * The @params argument should be an a{sv}. The following attributes * are known: - * - min-fallback-size: u: Minimume uncompressed size in megabytes to use fallback, 0 to disable fallbacks + * - min-fallback-size: u: Minimum uncompressed size in megabytes to use fallback, 0 to disable fallbacks * - max-chunk-size: u: Maximum size in megabytes of a delta part * - max-bsdiff-size: u: Maximum size in megabytes to consider bsdiff compression * for input files @@ -1253,7 +1253,7 @@ ostree_repo_static_delta_generate (OstreeRepo *self, guint min_fallback_size; guint max_bsdiff_size; guint max_chunk_size; - GVariantBuilder metadata_builder; + g_auto(GVariantBuilder) metadata_builder = {{0,}}; DeltaOpts delta_opts = DELTAOPT_FLAG_NONE; guint64 total_compressed_size = 0; guint64 total_uncompressed_size = 0; @@ -1384,16 +1384,18 @@ ostree_repo_static_delta_generate (OstreeRepo *self, g_autoptr(GVariant) delta_part_content = NULL; g_autoptr(GVariant) delta_part = NULL; g_autoptr(GVariant) delta_part_header = NULL; - GVariantBuilder *mode_builder = g_variant_builder_new (G_VARIANT_TYPE ("a(uuu)")); - GVariantBuilder *xattr_builder = g_variant_builder_new (G_VARIANT_TYPE ("aa(ayay)")); + g_auto(GVariantBuilder) mode_builder = {{0,}}; + g_auto(GVariantBuilder) xattr_builder = {{0,}}; guint8 compression_type_char; + g_variant_builder_init (&mode_builder, G_VARIANT_TYPE ("a(uuu)")); + g_variant_builder_init (&xattr_builder, G_VARIANT_TYPE ("aa(ayay)")); { guint j; for (j = 0; j < part_builder->modes->len; j++) - g_variant_builder_add_value (mode_builder, part_builder->modes->pdata[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]); + g_variant_builder_add_value (&xattr_builder, part_builder->xattrs->pdata[j]); } payload_b = g_string_free_to_bytes (part_builder->payload); @@ -1403,7 +1405,7 @@ ostree_repo_static_delta_generate (OstreeRepo *self, part_builder->operations = NULL; /* FIXME - avoid duplicating memory here */ delta_part_content = g_variant_new ("(a(uuu)aa(ayay)@ay@ay)", - mode_builder, xattr_builder, + &mode_builder, &xattr_builder, ot_gvariant_new_ay_bytes (payload_b), ot_gvariant_new_ay_bytes (operations_b)); g_variant_ref_sink (delta_part_content); @@ -1481,7 +1483,8 @@ ostree_repo_static_delta_generate (OstreeRepo *self, descriptor_dir = g_file_get_parent (descriptor_path); - if (!gs_file_ensure_directory (descriptor_dir, TRUE, cancellable, error)) + if (!glnx_shutil_mkdir_p_at (AT_FDCWD, gs_file_get_path_cached (descriptor_dir), 0755, + cancellable, error)) goto out; for (i = 0; i < part_tempfiles->len; i++) diff --git a/src/libostree/ostree-repo-static-delta-core.c b/src/libostree/ostree-repo-static-delta-core.c index 9e3ed09f..de9e6c7a 100644 --- a/src/libostree/ostree-repo-static-delta-core.c +++ b/src/libostree/ostree-repo-static-delta-core.c @@ -126,8 +126,8 @@ ostree_repo_list_static_delta_names (OstreeRepo *self, if (g_file_info_get_file_type (file_info2) != G_FILE_TYPE_DIRECTORY) continue; - name1 = gs_file_get_basename_cached (child); - name2 = gs_file_get_basename_cached (child2); + name1 = g_file_info_get_name (file_info); + name2 = g_file_info_get_name (file_info2); { g_autoptr(GFile) meta_path = g_file_get_child (child2, "superblock"); @@ -159,7 +159,8 @@ ostree_repo_list_static_delta_names (OstreeRepo *self, } ret = TRUE; - gs_transfer_out_value (out_deltas, &ret_deltas); + if (out_deltas) + *out_deltas = g_steal_pointer (&ret_deltas); out: return ret; } @@ -512,6 +513,7 @@ _ostree_static_delta_part_open (GInputStream *part_in, g_bytes_get_size (inline_part_bytes) - 1); ret_part = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0), content_bytes, trusted); + g_variant_ref_sink (ret_part); } if (!skip_checksum) @@ -840,7 +842,7 @@ _ostree_repo_static_delta_dump (OstreeRepo *self, if (!ot_util_variant_map_at (self->repo_dir_fd, superblock_path, (GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT, - TRUE, &delta_superblock, error)) + OT_VARIANT_MAP_TRUSTED, &delta_superblock, error)) goto out; g_print ("%s\n", g_variant_print (delta_superblock, 1)); diff --git a/src/libostree/ostree-repo-traverse.c b/src/libostree/ostree-repo-traverse.c index bb437c38..503ab329 100644 --- a/src/libostree/ostree-repo-traverse.c +++ b/src/libostree/ostree-repo-traverse.c @@ -22,6 +22,7 @@ #include "config.h" +#include "libglnx.h" #include "ostree.h" #include "otutil.h" diff --git a/src/libostree/ostree-repo.c b/src/libostree/ostree-repo.c index 08e6a48f..e86685bf 100644 --- a/src/libostree/ostree-repo.c +++ b/src/libostree/ostree-repo.c @@ -26,7 +26,9 @@ #include #include #include +#include "libglnx.h" #include "otutil.h" +#include #include "ostree-core-private.h" #include "ostree-repo-private.h" @@ -35,16 +37,13 @@ #include "ostree-gpg-verifier.h" #include "ostree-repo-static-delta-private.h" #include "ot-fs-utils.h" - -#ifdef HAVE_LIBSOUP -#include "ostree-metalink.h" -#endif +#include "ostree-autocleanups.h" #include #include /** - * SECTION:libostree-repo + * SECTION:ostree-repo * @title: Content-addressed object store * @short_description: A git-like storage system for operating system binaries * @@ -131,7 +130,7 @@ static OstreeRemote * ost_remote_new_from_keyfile (GKeyFile *keyfile, const gchar *group) { - GMatchInfo *match = NULL; + g_autoptr(GMatchInfo) match = NULL; OstreeRemote *remote; static gsize regex_initialized; @@ -158,8 +157,6 @@ ost_remote_new_from_keyfile (GKeyFile *keyfile, ot_keyfile_copy_group (keyfile, remote->options, group); - g_match_info_unref (match); - return remote; } @@ -322,12 +319,16 @@ ostree_repo_get_remote_option (OstreeRepo *self, { if (g_error_matches (temp_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) { - if (self->parent_repo != NULL) - return ostree_repo_get_remote_option (self->parent_repo, - remote_name, option_name, - default_value, - out_value, - error); + /* Note: We ignore errors on the parent because the parent config may not + specify this remote, causing a "remote not found" error, but we found + the remote at some point, so we need to instead return the default */ + if (self->parent_repo != NULL && + ostree_repo_get_remote_option (self->parent_repo, + remote_name, option_name, + default_value, + out_value, + NULL)) + return TRUE; value = g_strdup (default_value); ret = TRUE; @@ -397,11 +398,16 @@ ostree_repo_get_remote_list_option (OstreeRepo *self, /* Default value if key not found is always NULL. */ if (g_error_matches (temp_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) { - if (self->parent_repo != NULL) - return ostree_repo_get_remote_list_option (self->parent_repo, - remote_name, option_name, - out_value, - error); + /* Note: We ignore errors on the parent because the parent config may not + specify this remote, causing a "remote not found" error, but we found + the remote at some point, so we need to instead return the default */ + if (self->parent_repo != NULL && + ostree_repo_get_remote_list_option (self->parent_repo, + remote_name, option_name, + out_value, + NULL)) + return TRUE; + ret = TRUE; } else if (temp_error) @@ -426,7 +432,7 @@ ostree_repo_get_remote_list_option (OstreeRepo *self, * @self: A OstreeRepo * @remote_name: Name * @option_name: Option - * @default_value: (allow-none): Value returned if @option_name is not present + * @default_value: Value returned if @option_name is not present * @out_value: (out) : location to store the result. * @error: Error * @@ -464,12 +470,16 @@ ostree_repo_get_remote_boolean_option (OstreeRepo *self, { if (g_error_matches (temp_error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_KEY_NOT_FOUND)) { - if (self->parent_repo != NULL) - return ostree_repo_get_remote_boolean_option (self->parent_repo, - remote_name, option_name, - default_value, - out_value, - error); + /* Note: We ignore errors on the parent because the parent config may not + specify this remote, causing a "remote not found" error, but we found + the remote at some point, so we need to instead return the default */ + if (self->parent_repo != NULL && + ostree_repo_get_remote_boolean_option (self->parent_repo, + remote_name, option_name, + default_value, + out_value, + NULL)) + return TRUE; value = default_value; ret = TRUE; @@ -493,109 +503,6 @@ ostree_repo_get_remote_boolean_option (OstreeRepo *self, return ret; } -#ifdef HAVE_LIBSOUP -OstreeFetcher * -_ostree_repo_remote_new_fetcher (OstreeRepo *self, - const char *remote_name, - GError **error) -{ - OstreeFetcher *fetcher = NULL; - OstreeFetcherConfigFlags fetcher_flags = 0; - gboolean tls_permissive = FALSE; - gboolean success = FALSE; - - g_return_val_if_fail (OSTREE_IS_REPO (self), NULL); - g_return_val_if_fail (remote_name != NULL, NULL); - - if (!ostree_repo_get_remote_boolean_option (self, remote_name, - "tls-permissive", FALSE, - &tls_permissive, error)) - goto out; - - if (tls_permissive) - fetcher_flags |= OSTREE_FETCHER_FLAGS_TLS_PERMISSIVE; - - fetcher = _ostree_fetcher_new (self->tmp_dir_fd, fetcher_flags); - - { - g_autofree char *tls_client_cert_path = NULL; - g_autofree char *tls_client_key_path = NULL; - - if (!ostree_repo_get_remote_option (self, remote_name, - "tls-client-cert-path", NULL, - &tls_client_cert_path, error)) - goto out; - if (!ostree_repo_get_remote_option (self, remote_name, - "tls-client-key-path", NULL, - &tls_client_key_path, error)) - goto out; - - if ((tls_client_cert_path != NULL) != (tls_client_key_path != NULL)) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Remote \"%s\" must specify both " - "\"tls-client-cert-path\" and \"tls-client-key-path\"", - remote_name); - goto out; - } - else if (tls_client_cert_path != NULL) - { - g_autoptr(GTlsCertificate) client_cert = NULL; - - g_assert (tls_client_key_path != NULL); - - client_cert = g_tls_certificate_new_from_files (tls_client_cert_path, - tls_client_key_path, - error); - if (client_cert == NULL) - goto out; - - _ostree_fetcher_set_client_cert (fetcher, client_cert); - } - } - - { - g_autofree char *tls_ca_path = NULL; - - if (!ostree_repo_get_remote_option (self, remote_name, - "tls-ca-path", NULL, - &tls_ca_path, error)) - goto out; - - if (tls_ca_path != NULL) - { - g_autoptr(GTlsDatabase) db = NULL; - - db = g_tls_file_database_new (tls_ca_path, error); - if (db == NULL) - goto out; - - _ostree_fetcher_set_tls_database (fetcher, db); - } - } - - { - g_autofree char *http_proxy = NULL; - - if (!ostree_repo_get_remote_option (self, remote_name, - "proxy", NULL, - &http_proxy, error)) - goto out; - - if (http_proxy != NULL) - _ostree_fetcher_set_proxy (fetcher, http_proxy); - } - - success = TRUE; - -out: - if (!success) - g_clear_object (&fetcher); - - return fetcher; -} -#endif - static void ostree_repo_finalize (GObject *object) { @@ -603,7 +510,7 @@ ostree_repo_finalize (GObject *object) g_clear_object (&self->parent_repo); - g_free (self->boot_id); + g_free (self->stagedir_prefix); g_clear_object (&self->repodir); if (self->repo_dir_fd != -1) (void) close (self->repo_dir_fd); @@ -784,6 +691,9 @@ ostree_repo_init (OstreeRepo *self) { static gsize gpgme_initialized; GLnxLockFile empty_lockfile = GLNX_LOCK_FILE_INIT; + const GDebugKey test_error_keys[] = { + { "pre-commit", OSTREE_REPO_TEST_ERROR_PRE_COMMIT }, + }; if (g_once_init_enter (&gpgme_initialized)) { @@ -792,6 +702,9 @@ ostree_repo_init (OstreeRepo *self) g_once_init_leave (&gpgme_initialized, 1); } + self->test_error_flags = g_parse_debug_string (g_getenv ("OSTREE_REPO_TEST_ERROR"), + test_error_keys, G_N_ELEMENTS (test_error_keys)); + g_mutex_init (&self->cache_lock); g_mutex_init (&self->txn_stats_lock); @@ -920,6 +833,14 @@ ostree_repo_is_writable (OstreeRepo *self, return self->writable; } +/** + * _ostree_repo_update_mtime: + * @self: Repo + * @error: a #GError + * + * Bump the mtime of the repository so that programs + * can detect that the refs have updated. + */ gboolean _ostree_repo_update_mtime (OstreeRepo *self, GError **error) @@ -1233,8 +1154,11 @@ impl_repo_remote_delete (OstreeRepo *self, if (remote->file != NULL) { - if (!gs_file_unlink (remote->file, cancellable, error)) - goto out; + if (unlink (gs_file_get_path_cached (remote->file)) != 0) + { + glnx_set_error_from_errno (error); + goto out; + } } else { @@ -1809,185 +1733,12 @@ out: return ret; } -#ifdef HAVE_LIBSOUP -static gboolean -_ostree_preload_metadata_file (OstreeRepo *self, - OstreeFetcher *fetcher, - SoupURI *base_uri, - const char *filename, - gboolean is_metalink, - GBytes **out_bytes, - GCancellable *cancellable, - GError **error) -{ - gboolean ret = FALSE; - - if (is_metalink) - { - glnx_unref_object OstreeMetalink *metalink = NULL; - GError *local_error = NULL; - - metalink = _ostree_metalink_new (fetcher, filename, - OSTREE_MAX_METADATA_SIZE, - base_uri); - - _ostree_metalink_request_sync (metalink, NULL, out_bytes, NULL, - cancellable, &local_error); - - if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) - { - g_clear_error (&local_error); - *out_bytes = NULL; - } - else if (local_error != NULL) - { - g_propagate_error (error, local_error); - goto out; - } - } - else - { - SoupURI *uri; - const char *base_path; - g_autofree char *path = NULL; - - base_path = soup_uri_get_path (base_uri); - path = g_build_filename (base_path, filename, NULL); - uri = soup_uri_new_with_base (base_uri, path); - - ret = _ostree_fetcher_request_uri_to_membuf (fetcher, uri, - FALSE, TRUE, - out_bytes, - OSTREE_MAX_METADATA_SIZE, - cancellable, error); - soup_uri_free (uri); - - if (!ret) - goto out; - } - - ret = TRUE; -out: - return ret; -} - -static gboolean -repo_remote_fetch_summary (OstreeRepo *self, - const char *name, - const char *metalink_url_string, - GBytes **out_summary, - GBytes **out_signatures, - GCancellable *cancellable, - GError **error) -{ - glnx_unref_object OstreeFetcher *fetcher = NULL; - g_autoptr(GMainContext) mainctx = NULL; - gboolean ret = FALSE; - SoupURI *base_uri = NULL; - gboolean from_cache = FALSE; - - mainctx = g_main_context_new (); - g_main_context_push_thread_default (mainctx); - - fetcher = _ostree_repo_remote_new_fetcher (self, name, error); - if (fetcher == NULL) - goto out; - - base_uri = soup_uri_new (metalink_url_string); - - { - g_autofree char *url_string = NULL; - if (metalink_url_string) - url_string = g_strdup (metalink_url_string); - else - { - if (!ostree_repo_remote_get_url (self, name, &url_string, error)) - goto out; - } - - base_uri = soup_uri_new (url_string); - if (base_uri == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Invalid URL '%s'", url_string); - goto out; - } - } - - if (!_ostree_preload_metadata_file (self, - fetcher, - base_uri, - "summary.sig", - metalink_url_string ? TRUE : FALSE, - out_signatures, - cancellable, - error)) - goto out; - - if (*out_signatures) - { - if (!_ostree_repo_load_cache_summary_if_same_sig (self, - name, - *out_signatures, - out_summary, - cancellable, - error)) - goto out; - } - - if (*out_summary) - from_cache = TRUE; - else - { - if (!_ostree_preload_metadata_file (self, - fetcher, - base_uri, - "summary", - metalink_url_string ? TRUE : FALSE, - out_summary, - cancellable, - error)) - goto out; - } - - if (!from_cache && *out_summary && *out_signatures) - { - g_autoptr(GError) temp_error = NULL; - - if (!_ostree_repo_cache_summary (self, - name, - *out_summary, - *out_signatures, - cancellable, - &temp_error)) - { - if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) - g_debug ("No permissions to save summary cache"); - else - { - g_propagate_error (error, g_steal_pointer (&temp_error)); - goto out; - } - } - } - - ret = TRUE; - - out: - if (mainctx) - g_main_context_pop_thread_default (mainctx); - if (base_uri != NULL) - soup_uri_free (base_uri); - return ret; -} -#endif - /** * ostree_repo_remote_fetch_summary: * @self: Self * @name: name of a remote - * @out_summary: (allow-none): return location for raw summary data, or %NULL - * @out_signatures: (allow-none): return location for raw summary signature + * @out_summary: (nullable): return location for raw summary data, or %NULL + * @out_signatures: (nullable): return location for raw summary signature * data, or %NULL * @cancellable: a #GCancellable * @error: a #GError @@ -2013,76 +1764,13 @@ ostree_repo_remote_fetch_summary (OstreeRepo *self, GCancellable *cancellable, GError **error) { -#ifdef HAVE_LIBSOUP - g_autofree char *metalink_url_string = NULL; - g_autoptr(GBytes) summary = NULL; - g_autoptr(GBytes) signatures = NULL; - gboolean ret = FALSE; - gboolean gpg_verify_summary; - - g_return_val_if_fail (OSTREE_REPO (self), FALSE); - g_return_val_if_fail (name != NULL, FALSE); - - if (!ostree_repo_get_remote_option (self, name, "metalink", NULL, - &metalink_url_string, error)) - goto out; - - if (!repo_remote_fetch_summary (self, - name, - metalink_url_string, - &summary, - &signatures, - cancellable, - error)) - goto out; - - if (!ostree_repo_remote_get_gpg_verify_summary (self, name, &gpg_verify_summary, error)) - goto out; - - if (gpg_verify_summary && signatures == NULL) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "GPG verification enabled, but no summary signatures found (use gpg-verify-summary=false in remote config to disable)"); - goto out; - } - - /* Verify any summary signatures. */ - if (gpg_verify_summary && summary != NULL && signatures != NULL) - { - glnx_unref_object OstreeGpgVerifyResult *result = NULL; - - result = ostree_repo_verify_summary (self, - name, - summary, - signatures, - cancellable, - error); - if (result == NULL) - goto out; - - if (ostree_gpg_verify_result_count_valid (result) == 0) - { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "GPG signatures found, but none are in trusted keyring"); - goto out; - } - } - - if (out_summary != NULL) - *out_summary = g_steal_pointer (&summary); - - if (out_signatures != NULL) - *out_signatures = g_steal_pointer (&signatures); - - ret = TRUE; - -out: - return ret; -#else - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - "This version of ostree was built without libsoup, and cannot fetch over HTTP"); - return FALSE; -#endif + return ostree_repo_remote_fetch_summary_with_options (self, + name, + NULL, + out_summary, + out_signatures, + cancellable, + error); } static gboolean @@ -2128,7 +1816,8 @@ ostree_repo_mode_from_string (const char *mode, ret_mode = OSTREE_REPO_MODE_BARE; else if (strcmp (mode, "bare-user") == 0) ret_mode = OSTREE_REPO_MODE_BARE_USER; - else if (strcmp (mode, "archive-z2") == 0) + else if (strcmp (mode, "archive-z2") == 0 || + strcmp (mode, "archive") == 0) ret_mode = OSTREE_REPO_MODE_ARCHIVE_Z2; else { @@ -2171,8 +1860,14 @@ ostree_repo_create (OstreeRepo *self, if (!ostree_repo_mode_to_string (mode, &mode_str, error)) goto out; - if (!gs_file_ensure_directory (self->repodir, FALSE, cancellable, error)) - goto out; + if (mkdir (gs_file_get_path_cached (self->repodir), 0755) != 0) + { + if (errno != EEXIST) + { + glnx_set_error_from_errno (error); + goto out; + } + } config_data = g_string_new (DEFAULT_CONFIG_CONTENTS); g_string_append_printf (config_data, "mode=%s\n", mode_str); @@ -2190,6 +1885,13 @@ ostree_repo_create (OstreeRepo *self, if (!g_file_make_directory (self->tmp_dir, cancellable, error)) goto out; + { + g_autoptr(GFile) extensions_dir = + g_file_resolve_relative_path (self->repodir, "extensions"); + if (!g_file_make_directory (extensions_dir, cancellable, error)) + goto out; + } + g_clear_object (&child); child = g_file_get_child (self->repodir, "refs"); if (!g_file_make_directory (child, cancellable, error)) @@ -2402,22 +2104,33 @@ ostree_repo_open (OstreeRepo *self, g_autofree char *version = NULL; g_autofree char *mode = NULL; g_autofree char *parent_repo_path = NULL; - g_autoptr(GError) temp_error = NULL; g_return_val_if_fail (error == NULL || *error == NULL, FALSE); if (self->inited) return TRUE; - /* We use a per-boot identifier to keep track of which file contents - * possibly haven't been sync'd to disk. + /* We use a directory of the form `staging-${BOOT_ID}-${RANDOM}` + * where if the ${BOOT_ID} doesn't match, we know file contents + * possibly haven't been sync'd to disk and need to be discarded. */ - if (!g_file_get_contents ("/proc/sys/kernel/random/boot_id", - &self->boot_id, - NULL, - error)) - goto out; - g_strdelimit (self->boot_id, "\n", '\0'); + { const char *env_bootid = getenv ("OSTREE_BOOTID"); + g_autofree char *boot_id = NULL; + + if (env_bootid != NULL) + boot_id = g_strdup (env_bootid); + else + { + if (!g_file_get_contents ("/proc/sys/kernel/random/boot_id", + &boot_id, + NULL, + error)) + goto out; + g_strdelimit (boot_id, "\n", '\0'); + } + + self->stagedir_prefix = g_strconcat (OSTREE_REPO_TMPDIR_STAGING, boot_id, "-", NULL); + } if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (self->repodir), TRUE, &self->repo_dir_fd, error)) @@ -2530,43 +2243,35 @@ ostree_repo_open (OstreeRepo *self, ostree_repo_set_disable_fsync (self, TRUE); } + { g_autofree char *tmp_expiry_seconds = NULL; + + /* 86400 secs = one day */ + if (!ot_keyfile_get_value_with_default (self->config, "core", "tmp-expiry-secs", "86400", + &tmp_expiry_seconds, error)) + goto out; + + self->tmp_expiry_seconds = g_ascii_strtoull (tmp_expiry_seconds, NULL, 10); + } + if (!append_remotes_d (self, cancellable, error)) goto out; if (!glnx_opendirat (self->repo_dir_fd, "tmp", TRUE, &self->tmp_dir_fd, error)) goto out; - if (!glnx_shutil_mkdir_p_at (self->tmp_dir_fd, _OSTREE_CACHE_DIR, 0775, cancellable, &temp_error)) + if (self->writable) { - if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED)) - { - g_clear_error (&temp_error); - g_debug ("No permissions to create cache dir"); - } - else - { - g_propagate_error (error, g_steal_pointer (&temp_error)); - goto out; - } - } + if (!glnx_shutil_mkdir_p_at (self->tmp_dir_fd, _OSTREE_CACHE_DIR, 0775, cancellable, error)) + goto out; - if (!glnx_opendirat (self->tmp_dir_fd, _OSTREE_CACHE_DIR, TRUE, &self->cache_dir_fd, &temp_error)) - { - if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) - { - g_clear_error (&temp_error); - g_debug ("No cache dir"); - } - else - { - g_propagate_error (error, g_steal_pointer (&temp_error)); - goto out; - } + if (!glnx_opendirat (self->tmp_dir_fd, _OSTREE_CACHE_DIR, TRUE, &self->cache_dir_fd, error)) + goto out; } if (self->mode == OSTREE_REPO_MODE_ARCHIVE_Z2 && self->enable_uncompressed_cache) { - if (!gs_file_ensure_directory (self->uncompressed_objects_dir, TRUE, cancellable, error)) + if (!glnx_shutil_mkdir_p_at (self->repo_dir_fd, "uncompressed-objects-cache", 0755, + cancellable, error)) goto out; if (!glnx_opendirat (self->repo_dir_fd, "uncompressed-objects-cache", TRUE, &self->uncompressed_objects_dir_fd, @@ -2603,6 +2308,8 @@ ostree_repo_set_disable_fsync (OstreeRepo *self, * @self: An #OstreeRepo * @dfd: directory fd * @path: subpath in @dfd + * @cancellable: a #GCancellable + * @error: a #GError * * Set a custom location for the cache directory used for e.g. * per-remote summary caches. Setting this manually is useful when @@ -2852,7 +2559,7 @@ load_metadata_internal (OstreeRepo *self, { gboolean ret = FALSE; char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; - int fd = -1; + glnx_fd_close int fd = -1; g_autoptr(GInputStream) ret_stream = NULL; g_autoptr(GVariant) ret_variant = NULL; @@ -2880,8 +2587,6 @@ load_metadata_internal (OstreeRepo *self, mfile = g_mapped_file_new_from_fd (fd, FALSE, error); if (!mfile) goto out; - (void) close (fd); /* Ignore errors, we have it mapped */ - fd = -1; ret_variant = g_variant_new_from_data (ostree_metadata_variant_type (objtype), g_mapped_file_get_contents (mfile), g_mapped_file_get_length (mfile), @@ -2903,7 +2608,7 @@ load_metadata_internal (OstreeRepo *self, { struct stat stbuf; - if (!gs_stream_fstat ((GFileDescriptorBased*)ret_stream, &stbuf, cancellable, error)) + if (!glnx_stream_fstat ((GFileDescriptorBased*)ret_stream, &stbuf, error)) goto out; *out_size = stbuf.st_size; } @@ -2926,8 +2631,6 @@ load_metadata_internal (OstreeRepo *self, ot_transfer_out_value (out_variant, &ret_variant); ot_transfer_out_value (out_stream, &ret_stream); out: - if (fd != -1) - (void) close (fd); return ret; } @@ -3018,8 +2721,13 @@ _ostree_repo_read_bare_fd (OstreeRepo *self, _ostree_loose_path (loose_path_buf, checksum, OSTREE_OBJECT_TYPE_FILE, self->mode); - return gs_file_openat_noatime (self->objects_dir_fd, loose_path_buf, out_fd, - cancellable, error); + *out_fd = openat (self->objects_dir_fd, loose_path_buf, O_RDONLY | O_CLOEXEC); + if (*out_fd < 0) + { + glnx_set_error_from_errno (error); + return FALSE; + } + return TRUE; } /** @@ -3066,13 +2774,20 @@ ostree_repo_load_file (OstreeRepo *self, error)) goto out; + if (fd < 0 && self->commit_stagedir_fd != -1) + { + if (!ot_openat_ignore_enoent (self->commit_stagedir_fd, loose_path_buf, &fd, + error)) + goto out; + } + if (fd != -1) { tmp_stream = g_unix_input_stream_new (fd, TRUE); fd = -1; /* Transfer ownership */ - if (!gs_stream_fstat ((GFileDescriptorBased*) tmp_stream, &stbuf, - cancellable, error)) + if (!glnx_stream_fstat ((GFileDescriptorBased*) tmp_stream, &stbuf, + error)) goto out; if (!ostree_content_stream_parse (TRUE, tmp_stream, stbuf.st_size, TRUE, @@ -3285,15 +3000,13 @@ _ostree_repo_has_loose_object (OstreeRepo *self, const char *checksum, OstreeObjectType objtype, gboolean *out_is_stored, - char *loose_path_buf, - GFile **out_stored_path, GCancellable *cancellable, GError **error) { gboolean ret = FALSE; struct stat stbuf; int res = -1; - gboolean tmp_file = FALSE; + char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; _ostree_loose_path (loose_path_buf, checksum, objtype, self->mode); @@ -3309,9 +3022,7 @@ _ostree_repo_has_loose_object (OstreeRepo *self, } } - if (res == 0) - tmp_file = TRUE; - else + if (res < 0) { do res = fstatat (self->objects_dir_fd, loose_path_buf, &stbuf, AT_SYMLINK_NOFOLLOW); @@ -3325,32 +3036,10 @@ _ostree_repo_has_loose_object (OstreeRepo *self, ret = TRUE; *out_is_stored = (res != -1); - - if (out_stored_path) - { - if (res != -1) - *out_stored_path = g_file_resolve_relative_path (tmp_file ? self->tmp_dir : self->objects_dir, loose_path_buf); - else - *out_stored_path = NULL; - } out: return ret; } -gboolean -_ostree_repo_find_object (OstreeRepo *self, - OstreeObjectType objtype, - const char *checksum, - GFile **out_stored_path, - GCancellable *cancellable, - GError **error) -{ - gboolean has_object; - char loose_path[_OSTREE_LOOSE_PATH_MAX]; - return _ostree_repo_has_loose_object (self, checksum, objtype, &has_object, loose_path, - out_stored_path, cancellable, error); -} - /** * ostree_repo_has_object: * @self: Repo @@ -3375,13 +3064,12 @@ ostree_repo_has_object (OstreeRepo *self, { gboolean ret = FALSE; gboolean ret_have_object; - g_autoptr(GFile) loose_path = NULL; - if (!_ostree_repo_find_object (self, objtype, checksum, &loose_path, - cancellable, error)) + if (!_ostree_repo_has_loose_object (self, checksum, objtype, &ret_have_object, + cancellable, error)) goto out; - ret_have_object = (loose_path != NULL); + /* In the future, here is where we would also look up in metadata pack files */ if (!ret_have_object && self->parent_repo) { @@ -3462,13 +3150,16 @@ ostree_repo_delete_object (OstreeRepo *self, if (tombstone_commits) { - g_autoptr(GVariantBuilder) builder = NULL; - builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); - g_variant_builder_add (builder, "{sv}", "commit", g_variant_new_bytestring (sha256)); + g_auto(GVariantBuilder) builder = {{0,}}; + g_autoptr(GVariant) variant = NULL; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + g_variant_builder_add (&builder, "{sv}", "commit", g_variant_new_bytestring (sha256)); + variant = g_variant_ref_sink (g_variant_builder_end (&builder)); if (!ostree_repo_write_metadata_trusted (self, OSTREE_OBJECT_TYPE_TOMBSTONE_COMMIT, sha256, - g_variant_builder_end (builder), + variant, cancellable, error)) goto out; @@ -4003,7 +3694,6 @@ ostree_repo_read_commit (OstreeRepo *self, return ret; } -#ifndef HAVE_LIBSOUP /** * ostree_repo_pull: * @self: Repo @@ -4040,9 +3730,7 @@ ostree_repo_pull (OstreeRepo *self, GCancellable *cancellable, GError **error) { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - "This version of ostree was built without libsoup, and cannot fetch over HTTP"); - return FALSE; + return ostree_repo_pull_one_dir (self, remote_name, NULL, refs_to_fetch, flags, progress, cancellable, error); } /** @@ -4069,49 +3757,55 @@ ostree_repo_pull_one_dir (OstreeRepo *self, GCancellable *cancellable, GError **error) { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - "This version of ostree was built without libsoup, and cannot fetch over HTTP"); - return FALSE; + GVariantBuilder builder; + g_autoptr(GVariant) options = NULL; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + + if (dir_to_pull) + g_variant_builder_add (&builder, "{s@v}", "subdir", + g_variant_new_variant (g_variant_new_string (dir_to_pull))); + g_variant_builder_add (&builder, "{s@v}", "flags", + g_variant_new_variant (g_variant_new_int32 (flags))); + if (refs_to_fetch) + g_variant_builder_add (&builder, "{s@v}", "refs", + g_variant_new_variant (g_variant_new_strv ((const char *const*) refs_to_fetch, -1))); + + options = g_variant_ref_sink (g_variant_builder_end (&builder)); + return ostree_repo_pull_with_options (self, remote_name, options, + progress, cancellable, error); } /** - * ostree_repo_pull_with_options: - * @self: Repo - * @remote_name: Name of remote - * @options: A GVariant a{sv} with an extensible set of flags. - * @progress: (allow-none): Progress - * @cancellable: Cancellable - * @error: Error + * _formatted_time_remaining_from_seconds + * @seconds_remaining: Estimated number of seconds remaining. * - * Like ostree_repo_pull(), but supports an extensible set of flags. - * The following are currently defined: - * - * * refs (as): Array of string refs - * * flags (i): An instance of #OstreeRepoPullFlags - * * subdir (s): Pull just this subdirectory - * * override-remote-name (s): If local, add this remote to refspec - * * gpg-verify (b): GPG verify commits - * * gpg-verify-summary (b): GPG verify summary - * * depth (i): How far in the history to traverse; default is 0, -1 means infinite - * * disable-static-deltas (b): Do not use static deltas - * * require-static-deltas (b): Require static deltas - * * override-commit-ids (as): Array of specific commit IDs to fetch for refs - * * dry-run (b): Only print information on what will be downloaded (requires static deltas) - */ -gboolean -ostree_repo_pull_with_options (OstreeRepo *self, - const char *remote_name, - GVariant *options, - OstreeAsyncProgress *progress, - GCancellable *cancellable, - GError **error) + * Returns a strings showing the number of days, hours, minutes + * and seconds remaining. + **/ +static char * +_formatted_time_remaining_from_seconds (guint64 seconds_remaining) { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, - "This version of ostree was built without libsoup, and cannot fetch over HTTP"); - return FALSE; -} + guint64 minutes_remaining = seconds_remaining / 60; + guint64 hours_remaining = minutes_remaining / 60; + guint64 days_remaining = hours_remaining / 24; -#endif + GString *description = g_string_new (NULL); + + if (days_remaining) + g_string_append_printf (description, "%" G_GUINT64_FORMAT " days ", days_remaining); + + if (hours_remaining) + g_string_append_printf (description, "%" G_GUINT64_FORMAT " hours ", hours_remaining % 24); + + if (minutes_remaining) + g_string_append_printf (description, "%" G_GUINT64_FORMAT " minutes ", minutes_remaining % 60); + + if (seconds_remaining) + g_string_append_printf (description, "%" G_GUINT64_FORMAT " seconds ", seconds_remaining % 60); + + return g_string_free (description, FALSE); +} /** * ostree_repo_pull_default_console_progress_changed: @@ -4126,12 +3820,15 @@ ostree_repo_pull_with_options (OstreeRepo *self, * custom status message, or else outstanding fetch progress in bytes/sec, * or else outstanding content or metadata writes to the repository in * number of objects. + * + * Compatibility note: this function previously assumed that @user_data + * was a pointer to a #GSConsole instance. This is no longer the case, + * and @user_data is ignored. **/ void ostree_repo_pull_default_console_progress_changed (OstreeAsyncProgress *progress, gpointer user_data) { - GSConsole *console = user_data; GString *buf; g_autofree char *status = NULL; guint outstanding_fetches; @@ -4141,7 +3838,8 @@ ostree_repo_pull_default_console_progress_changed (OstreeAsyncProgress *progress guint fetched_delta_parts; guint total_delta_parts; - if (!console) + /* Historical note; we used to treat this as a GSConsole instance */ + if (user_data == NULL) return; buf = g_string_new (""); @@ -4164,28 +3862,39 @@ ostree_repo_pull_default_console_progress_changed (OstreeAsyncProgress *progress guint fetched = ostree_async_progress_get_uint (progress, "fetched"); guint metadata_fetched = ostree_async_progress_get_uint (progress, "metadata-fetched"); guint requested = ostree_async_progress_get_uint (progress, "requested"); - guint64 bytes_sec = (g_get_monotonic_time () - ostree_async_progress_get_uint64 (progress, "start-time")) / G_USEC_PER_SEC; + guint64 start_time = ostree_async_progress_get_uint64 (progress, "start-time"); + guint64 total_delta_part_size = ostree_async_progress_get_uint64 (progress, "total-delta-part-size"); + guint64 current_time = g_get_monotonic_time (); g_autofree char *formatted_bytes_transferred = g_format_size_full (bytes_transferred, 0); g_autofree char *formatted_bytes_sec = NULL; + g_autofree char *formatted_est_time_remaining = NULL; - if (!bytes_sec) // Ignore first second - formatted_bytes_sec = g_strdup ("-"); + /* Ignore the first second, or when we haven't transferred any + * data, since those could cause divide by zero below. + */ + if ((current_time - start_time) < G_USEC_PER_SEC || bytes_transferred == 0) + { + formatted_bytes_sec = g_strdup ("-"); + formatted_est_time_remaining = g_strdup ("- "); + } else { - bytes_sec = bytes_transferred / bytes_sec; + guint64 bytes_sec = bytes_transferred / ((current_time - start_time) / G_USEC_PER_SEC); + guint64 est_time_remaining = (total_delta_part_size - bytes_transferred) / bytes_sec; formatted_bytes_sec = g_format_size (bytes_sec); + formatted_est_time_remaining = _formatted_time_remaining_from_seconds (est_time_remaining); } if (total_delta_parts > 0) { - guint64 total_delta_part_size = ostree_async_progress_get_uint64 (progress, "total-delta-part-size"); g_autofree char *formatted_total = g_format_size (total_delta_part_size); - g_string_append_printf (buf, "Receiving delta parts: %u/%u %s/s %s/%s", + /* No space between %s and remaining, since formatted_est_time_remaining has a trailing space */ + g_string_append_printf (buf, "Receiving delta parts: %u/%u %s/s %s/%s %sremaining", fetched_delta_parts, total_delta_parts, formatted_bytes_sec, formatted_bytes_transferred, - formatted_total); + formatted_total, formatted_est_time_remaining); } else if (outstanding_metadata_fetches) { @@ -4208,7 +3917,7 @@ ostree_repo_pull_default_console_progress_changed (OstreeAsyncProgress *progress g_string_append_printf (buf, "Scanning metadata: %u", n_scanned_metadata); } - gs_console_begin_status_line (console, buf->str, NULL, NULL); + glnx_console_text (buf->str); g_string_free (buf, TRUE); } @@ -4233,7 +3942,6 @@ ostree_repo_append_gpg_signature (OstreeRepo *self, gboolean ret = FALSE; g_autoptr(GVariant) metadata = NULL; g_autoptr(GVariant) new_metadata = NULL; - g_autoptr(GVariantBuilder) builder = NULL; if (!ostree_repo_read_commit_detached_metadata (self, commit_checksum, @@ -4359,7 +4067,7 @@ sign_data (OstreeRepo *self, if (!g_output_stream_close (tmp_signature_output, cancellable, error)) goto out; - signature_file = gs_file_map_noatime (tmp_signature_file, cancellable, error); + signature_file = g_mapped_file_new (gs_file_get_path_cached (tmp_signature_file), FALSE, error); if (!signature_file) goto out; ret_signature = g_mapped_file_get_bytes (signature_file); @@ -4475,9 +4183,7 @@ out: /** * ostree_repo_sign_delta: - * @self: Self - * @from_commit: SHA256 of starting commit to sign, or %NULL - * @to_commit: SHA256 of target commit to sign + * * This function is deprecated, sign the summary file instead. * Add a GPG signature to a static delta. */ @@ -4513,33 +4219,19 @@ ostree_repo_add_gpg_signature_summary (OstreeRepo *self, { gboolean ret = FALSE; g_autoptr(GBytes) summary_data = NULL; - g_autoptr(GFile) summary_file = NULL; - g_autoptr(GFile) signature_path = NULL; - GError *temp_error = NULL; g_autoptr(GVariant) existing_signatures = NULL; g_autoptr(GVariant) new_metadata = NULL; g_autoptr(GVariant) normalized = NULL; guint i; - signature_path = g_file_resolve_relative_path (self->repodir, "summary.sig"); - summary_file = g_file_resolve_relative_path (self->repodir, "summary"); - summary_data = gs_file_map_readonly (summary_file, cancellable, error); + summary_data = ot_file_mapat_bytes (self->repo_dir_fd, "summary", error); if (!summary_data) goto out; - if (!ot_util_variant_map (signature_path, G_VARIANT_TYPE (OSTREE_SUMMARY_SIG_GVARIANT_STRING), - TRUE, &existing_signatures, &temp_error)) - { - if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) - { - g_clear_error (&temp_error); - } - else - { - g_propagate_error (error, temp_error); - goto out; - } - } + if (!ot_util_variant_map_at (self->repo_dir_fd, "summary.sig", + G_VARIANT_TYPE (OSTREE_SUMMARY_SIG_GVARIANT_STRING), + OT_VARIANT_MAP_ALLOW_NOENT, &existing_signatures, error)) + goto out; for (i = 0; key_id[i]; i++) { @@ -4599,23 +4291,17 @@ find_keyring (OstreeRepo *self, return NULL; } -OstreeGpgVerifyResult * -_ostree_repo_gpg_verify_with_metadata (OstreeRepo *self, - GBytes *signed_data, - GVariant *metadata, - const char *remote_name, - GFile *keyringdir, - GFile *extra_keyring, - GCancellable *cancellable, - GError **error) +static OstreeGpgVerifyResult * +_ostree_repo_gpg_verify_data_internal (OstreeRepo *self, + const gchar *remote_name, + GBytes *data, + GBytes *signatures, + GFile *keyringdir, + GFile *extra_keyring, + GCancellable *cancellable, + GError **error) { - OstreeGpgVerifyResult *result = NULL; glnx_unref_object OstreeGpgVerifier *verifier = NULL; - g_autoptr(GVariant) signaturedata = NULL; - GByteArray *buffer; - GVariantIter iter; - GVariant *child; - g_autoptr (GBytes) signatures = NULL; gboolean add_global_keyring_dir = TRUE; verifier = _ostree_gpg_verifier_new (); @@ -4626,7 +4312,7 @@ _ostree_repo_gpg_verify_with_metadata (OstreeRepo *self, if (!_ostree_gpg_verifier_add_keyring_dir (verifier, self->repodir, cancellable, error)) - goto out; + return NULL; } else if (remote_name != NULL) { @@ -4637,7 +4323,7 @@ _ostree_repo_gpg_verify_with_metadata (OstreeRepo *self, remote = ost_repo_get_remote_inherited (self, remote_name, error); if (remote == NULL) - goto out; + return NULL; file = find_keyring (self, remote, cancellable); @@ -4654,20 +4340,43 @@ _ostree_repo_gpg_verify_with_metadata (OstreeRepo *self, { /* Use the deprecated global keyring directory. */ if (!_ostree_gpg_verifier_add_global_keyring_dir (verifier, cancellable, error)) - goto out; + return NULL; } if (keyringdir) { if (!_ostree_gpg_verifier_add_keyring_dir (verifier, keyringdir, cancellable, error)) - goto out; + return NULL; } if (extra_keyring != NULL) { _ostree_gpg_verifier_add_keyring (verifier, extra_keyring); } + return _ostree_gpg_verifier_check_signature (verifier, + data, + signatures, + cancellable, + error); +} + +OstreeGpgVerifyResult * +_ostree_repo_gpg_verify_with_metadata (OstreeRepo *self, + GBytes *signed_data, + GVariant *metadata, + const char *remote_name, + GFile *keyringdir, + GFile *extra_keyring, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GVariant) signaturedata = NULL; + GByteArray *buffer; + GVariantIter iter; + GVariant *child; + g_autoptr (GBytes) signatures = NULL; + if (metadata) signaturedata = g_variant_lookup_value (metadata, _OSTREE_METADATA_GPGSIGS_NAME, @@ -4676,7 +4385,7 @@ _ostree_repo_gpg_verify_with_metadata (OstreeRepo *self, { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "GPG verification enabled, but no signatures found (use gpg-verify=false in remote config to disable)"); - goto out; + return NULL; } /* OpenPGP data is organized into binary records called packets. RFC 4880 @@ -4698,12 +4407,14 @@ _ostree_repo_gpg_verify_with_metadata (OstreeRepo *self, } signatures = g_byte_array_free_to_bytes (buffer); - result = _ostree_gpg_verifier_check_signature (verifier, - signed_data, signatures, - cancellable, error); - - out: - return result; + return _ostree_repo_gpg_verify_data_internal (self, + remote_name, + signed_data, + signatures, + keyringdir, + extra_keyring, + cancellable, + error); } /* Needed an internal version for the remote_name parameter. */ @@ -4773,6 +4484,8 @@ out: * * Check for a valid GPG signature on commit named by the ASCII * checksum @commit_checksum. + * + * Returns: %TRUE if there was a GPG signature from a trusted keyring, otherwise %FALSE */ gboolean ostree_repo_verify_commit (OstreeRepo *self, @@ -4783,25 +4496,12 @@ ostree_repo_verify_commit (OstreeRepo *self, GError **error) { glnx_unref_object OstreeGpgVerifyResult *result = NULL; - gboolean ret = FALSE; result = ostree_repo_verify_commit_ext (self, commit_checksum, keyringdir, extra_keyring, cancellable, error); - if (result == NULL) - goto out; - if (ostree_gpg_verify_result_count_valid (result) == 0) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "GPG signatures found, but none are in trusted keyring"); - goto out; - } - - ret = TRUE; - - out: - return ret; + return ostree_gpg_verify_result_require_valid_signature (result, error); } /** @@ -4835,6 +4535,49 @@ ostree_repo_verify_commit_ext (OstreeRepo *self, error); } +/** + * ostree_repo_gpg_verify_data: + * @self: Repository + * @remote_name: (nullable): Name of remote + * @data: Data as a #GBytes + * @signatures: Signatures as a #GBytes + * @keyringdir: (nullable): Path to directory GPG keyrings; overrides built-in default if given + * @extra_keyring: (nullable): Path to additional keyring file (not a directory) + * @cancellable: Cancellable + * @error: Error + * + * Verify @signatures for @data using GPG keys in the keyring for + * @remote_name, and return an #OstreeGpgVerifyResult. + * + * The @remote_name parameter can be %NULL. In that case it will do + * the verifications using GPG keys in the keyrings of all remotes. + * + * Returns: (transfer full): an #OstreeGpgVerifyResult, or %NULL on error + */ +OstreeGpgVerifyResult * +ostree_repo_gpg_verify_data (OstreeRepo *self, + const gchar *remote_name, + GBytes *data, + GBytes *signatures, + GFile *keyringdir, + GFile *extra_keyring, + GCancellable *cancellable, + GError **error) +{ + g_return_val_if_fail (OSTREE_IS_REPO (self), NULL); + g_return_val_if_fail (data != NULL, NULL); + g_return_val_if_fail (signatures != NULL, NULL); + + return _ostree_repo_gpg_verify_data_internal (self, + (remote_name != NULL) ? remote_name : OSTREE_ALL_REMOTES, + data, + signatures, + keyringdir, + extra_keyring, + cancellable, + error); +} + /** * ostree_repo_verify_summary: * @self: Repo @@ -4901,7 +4644,7 @@ ostree_repo_regenerate_summary (OstreeRepo *self, g_autoptr(GVariant) summary = NULL; GList *ordered_keys = NULL; GList *iter = NULL; - GVariantDict additional_metadata_builder; + g_auto(GVariantDict) additional_metadata_builder = {{0,}}; if (!ostree_repo_list_refs (self, NULL, &refs, cancellable, error)) goto out; @@ -4934,7 +4677,7 @@ ostree_repo_regenerate_summary (OstreeRepo *self, { guint i; g_autoptr(GPtrArray) delta_names = NULL; - GVariantDict deltas_builder; + g_auto(GVariantDict) deltas_builder = {{0,}}; g_autoptr(GVariant) deltas = NULL; if (!ostree_repo_list_static_delta_names (self, &delta_names, cancellable, error)) @@ -5010,6 +4753,50 @@ ostree_repo_regenerate_summary (OstreeRepo *self, return ret; } +gboolean +_ostree_repo_is_locked_tmpdir (const char *filename) +{ + return g_str_has_prefix (filename, OSTREE_REPO_TMPDIR_STAGING) || + g_str_has_prefix (filename, OSTREE_REPO_TMPDIR_FETCHER); +} + +gboolean +_ostree_repo_try_lock_tmpdir (int tmpdir_dfd, + const char *tmpdir_name, + GLnxLockFile *file_lock_out, + gboolean *out_did_lock, + GError **error) +{ + gboolean ret = FALSE; + g_autofree char *lock_name = g_strconcat (tmpdir_name, "-lock", NULL); + gboolean did_lock = FALSE; + g_autoptr(GError) local_error = NULL; + + /* We put the lock outside the dir, so we can hold the lock + * until the directory is fully removed */ + if (!glnx_make_lock_file (tmpdir_dfd, lock_name, LOCK_EX | LOCK_NB, + file_lock_out, &local_error)) + { + if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) + { + did_lock = FALSE; + } + else + { + g_propagate_error (error, g_steal_pointer (&local_error)); + goto out; + } + } + else + { + did_lock = TRUE; + } + + ret = TRUE; + *out_did_lock = did_lock; + out: + return ret; +} /* This allocates and locks a subdir of the repo tmp dir, using an existing * one with the same prefix if it is not in use already. */ @@ -5023,25 +4810,28 @@ _ostree_repo_allocate_tmpdir (int tmpdir_dfd, GCancellable *cancellable, GError **error) { + gboolean ret = FALSE; gboolean reusing_dir = FALSE; + gboolean did_lock; g_autofree char *tmpdir_name = NULL; glnx_fd_close int tmpdir_fd = -1; g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; + g_return_val_if_fail (_ostree_repo_is_locked_tmpdir (tmpdir_prefix), FALSE); + /* Look for existing tmpdir (with same prefix) to reuse */ if (!glnx_dirfd_iterator_init_at (tmpdir_dfd, ".", FALSE, &dfd_iter, error)) - return FALSE; + goto out; - while (TRUE) + while (tmpdir_name == NULL) { gs_dirfd_iterator_cleanup GSDirFdIterator child_dfd_iter = { 0, }; struct dirent *dent; glnx_fd_close int existing_tmpdir_fd = -1; g_autoptr(GError) local_error = NULL; - g_autofree char *lock_name = NULL; if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, cancellable, error)) - return FALSE; + goto out; if (dent == NULL) break; @@ -5062,25 +4852,18 @@ _ostree_repo_allocate_tmpdir (int tmpdir_dfd, else { g_propagate_error (error, g_steal_pointer (&local_error)); - return FALSE; + goto out; } } - lock_name = g_strconcat (dent->d_name, "-lock", NULL); - /* We put the lock outside the dir, so we can hold the lock * until the directory is fully removed */ - if (!glnx_make_lock_file (dfd_iter.fd, lock_name, LOCK_EX | LOCK_NB, - file_lock_out, &local_error)) - { - if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) - continue; - else - { - g_propagate_error (error, g_steal_pointer (&local_error)); - return FALSE; - } - } + if (!_ostree_repo_try_lock_tmpdir (dfd_iter.fd, dent->d_name, + file_lock_out, &did_lock, + error)) + goto out; + if (!did_lock) + continue; /* Touch the reused directory so that we don't accidentally * remove it due to being old when cleaning up the tmpdir @@ -5098,32 +4881,24 @@ _ostree_repo_allocate_tmpdir (int tmpdir_dfd, g_autofree char *tmpdir_name_template = g_strconcat (tmpdir_prefix, "XXXXXX", NULL); glnx_fd_close int new_tmpdir_fd = -1; g_autoptr(GError) local_error = NULL; - g_autofree char *lock_name = NULL; /* No existing tmpdir found, create a new */ if (!glnx_mkdtempat (tmpdir_dfd, tmpdir_name_template, 0777, error)) - return FALSE; + goto out; if (!glnx_opendirat (tmpdir_dfd, tmpdir_name_template, FALSE, &new_tmpdir_fd, error)) - return FALSE; - - lock_name = g_strconcat (tmpdir_name_template, "-lock", NULL); + goto out; /* Note, at this point we can race with another process that picks up this * new directory. If that happens we need to retry, making a new directory. */ - if (!glnx_make_lock_file (tmpdir_dfd, lock_name, LOCK_EX | LOCK_NB, - file_lock_out, &local_error)) - { - if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) - continue; - else - { - g_propagate_error (error, g_steal_pointer (&local_error)); - return FALSE; - } - } + if (!_ostree_repo_try_lock_tmpdir (tmpdir_dfd, tmpdir_name_template, + file_lock_out, &did_lock, + error)) + goto out; + if (!did_lock) + continue; tmpdir_name = g_steal_pointer (&tmpdir_name_template); tmpdir_fd = glnx_steal_fd (&new_tmpdir_fd); @@ -5138,5 +4913,7 @@ _ostree_repo_allocate_tmpdir (int tmpdir_dfd, if (reusing_dir_out) *reusing_dir_out = reusing_dir; - return TRUE; + ret = TRUE; + out: + return ret; } diff --git a/src/libostree/ostree-repo.h b/src/libostree/ostree-repo.h index 8a04e8e5..ce280ee6 100644 --- a/src/libostree/ostree-repo.h +++ b/src/libostree/ostree-repo.h @@ -115,6 +115,10 @@ gboolean ostree_repo_remote_delete (OstreeRepo *self, GCancellable *cancellable, GError **error); +/** + * OstreeRepoRemoteChange: + * The remote change operation. + */ typedef enum { OSTREE_REPO_REMOTE_CHANGE_ADD, OSTREE_REPO_REMOTE_CHANGE_ADD_IF_NOT_EXISTS, @@ -194,6 +198,15 @@ gboolean ostree_repo_remote_fetch_summary (OstreeRepo *self, GCancellable *cancellable, GError **error); +_OSTREE_PUBLIC +gboolean ostree_repo_remote_fetch_summary_with_options (OstreeRepo *self, + const char *name, + GVariant *options, + GBytes **out_summary, + GBytes **out_signatures, + GCancellable *cancellable, + GError **error); + _OSTREE_PUBLIC OstreeRepo * ostree_repo_get_parent (OstreeRepo *self); @@ -214,6 +227,10 @@ gboolean ostree_repo_write_config (OstreeRepo *self, * were written to the repository in this transaction. * @content_bytes_written: The amount of data added to the repository, * in bytes, counting only content objects. + * @padding1: reserved + * @padding2: reserved + * @padding3: reserved + * @padding4: reserved * * A list of statistics for each transaction that may be * interesting for reporting purposes. @@ -582,7 +599,9 @@ gboolean ostree_repo_write_archive_to_mtree (OstreeRepo * typedef struct { guint ignore_unsupported_content : 1; guint autocreate_parents : 1; - guint reserved : 30; + guint use_ostree_convention : 1; + guint callback_with_entry_pathname : 1; + guint reserved : 28; guint unused_uint[8]; gpointer unused_ptrs[8]; @@ -610,7 +629,10 @@ typedef struct { guint64 timestamp_secs; guint unused_uint[8]; - gpointer unused_ptrs[8]; + + char *path_prefix; + + gpointer unused_ptrs[7]; } OstreeRepoExportArchiveOptions; _OSTREE_PUBLIC @@ -685,7 +707,8 @@ typedef enum { OSTREE_REPO_CHECKOUT_OVERWRITE_UNION_FILES = 1 } OstreeRepoCheckoutOverwriteMode; -_OSTREE_PUBLIC gboolean +_OSTREE_PUBLIC +gboolean ostree_repo_checkout_tree (OstreeRepo *self, OstreeRepoCheckoutMode mode, OstreeRepoCheckoutOverwriteMode overwrite_mode, @@ -852,14 +875,16 @@ typedef enum { OSTREE_REPO_COMMIT_TRAVERSE_FLAG_NONE = (1 << 0) } OstreeRepoCommitTraverseFlags; -_OSTREE_PUBLIC gboolean +_OSTREE_PUBLIC +gboolean ostree_repo_commit_traverse_iter_init_commit (OstreeRepoCommitTraverseIter *iter, OstreeRepo *repo, GVariant *commit, OstreeRepoCommitTraverseFlags flags, GError **error); -_OSTREE_PUBLIC gboolean +_OSTREE_PUBLIC +gboolean ostree_repo_commit_traverse_iter_init_dirtree (OstreeRepoCommitTraverseIter *iter, OstreeRepo *repo, GVariant *dirtree, @@ -889,8 +914,8 @@ void ostree_repo_commit_traverse_iter_get_dir (OstreeRepoCommitTraverseIter *ite char **out_content_checksum, char **out_meta_checksum); -_OSTREE_PUBLIC void -ostree_repo_commit_traverse_iter_clear (OstreeRepoCommitTraverseIter *iter); +_OSTREE_PUBLIC +void ostree_repo_commit_traverse_iter_clear (OstreeRepoCommitTraverseIter *iter); _OSTREE_PUBLIC void ostree_repo_commit_traverse_iter_cleanup (void *p); @@ -909,7 +934,8 @@ typedef enum { OSTREE_REPO_PRUNE_FLAGS_REFS_ONLY } OstreeRepoPruneFlags; -_OSTREE_PUBLIC gboolean +_OSTREE_PUBLIC +gboolean ostree_repo_prune_static_deltas (OstreeRepo *self, const char *commit, GCancellable *cancellable, GError **error); @@ -947,7 +973,8 @@ gboolean ostree_repo_pull (OstreeRepo *self, GCancellable *cancellable, GError **error); -_OSTREE_PUBLIC gboolean +_OSTREE_PUBLIC +gboolean ostree_repo_pull_one_dir (OstreeRepo *self, const char *remote_name, const char *dir_to_pull, @@ -986,7 +1013,8 @@ gboolean ostree_repo_sign_delta (OstreeRepo *self, GCancellable *cancellable, GError **error); -_OSTREE_PUBLIC gboolean +_OSTREE_PUBLIC +gboolean ostree_repo_add_gpg_signature_summary (OstreeRepo *self, const gchar **key_id, const gchar *homedir, @@ -1016,6 +1044,16 @@ OstreeGpgVerifyResult * ostree_repo_verify_commit_ext (OstreeRepo *self, GCancellable *cancellable, GError **error); +_OSTREE_PUBLIC +OstreeGpgVerifyResult * ostree_repo_gpg_verify_data (OstreeRepo *self, + const gchar *remote_name, + GBytes *data, + GBytes *signatures, + GFile *keyringdir, + GFile *extra_keyring, + GCancellable *cancellable, + GError **error); + _OSTREE_PUBLIC OstreeGpgVerifyResult * ostree_repo_verify_summary (OstreeRepo *self, const char *remote_name, diff --git a/src/libostree/ostree-sepolicy.c b/src/libostree/ostree-sepolicy.c index 3b1a391b..8e49428f 100644 --- a/src/libostree/ostree-sepolicy.c +++ b/src/libostree/ostree-sepolicy.c @@ -32,7 +32,7 @@ #include "ostree-bootloader-syslinux.h" /** - * SECTION:libostree-sepolicy + * SECTION:ostree-sepolicy * @title: SELinux policy management * @short_description: Read SELinux policy and manage filesystem labels * diff --git a/src/libostree/ostree-sysroot-cleanup.c b/src/libostree/ostree-sysroot-cleanup.c index 5c370507..64c1389e 100644 --- a/src/libostree/ostree-sysroot-cleanup.c +++ b/src/libostree/ostree-sysroot-cleanup.c @@ -32,7 +32,7 @@ _ostree_sysroot_list_deployment_dirs_for_os (GFile *osdir, GError **error) { gboolean ret = FALSE; - const char *osname = gs_file_get_basename_cached (osdir); + const char *osname = glnx_basename (gs_file_get_path_cached (osdir)); g_autoptr(GFileEnumerator) dir_enum = NULL; g_autoptr(GFile) osdeploy_dir = NULL; GError *temp_error = NULL; @@ -370,7 +370,7 @@ cleanup_old_deployments (OstreeSysroot *self, g_autofree char *osname = NULL; g_autofree char *bootcsum = NULL; - if (!parse_bootdir_name (gs_file_get_basename_cached (bootdir), + if (!parse_bootdir_name (glnx_basename (gs_file_get_path_cached (bootdir)), &osname, &bootcsum)) g_assert_not_reached (); diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 77ecb9af..4616bab1 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -87,6 +87,39 @@ symlink_at_replace (const char *oldpath, return ret; } +/* Try a hardlink if we can, otherwise fall back to copying. Used + * right now for kernels/initramfs in /boot, where we can just + * hardlink if we're on the same partition. + */ +static gboolean +hardlink_or_copy_at (int src_dfd, + const char *src_subpath, + int dest_dfd, + const char *dest_subpath, + GCancellable *cancellable, + GError **error) +{ + gboolean ret = FALSE; + + if (linkat (src_dfd, src_subpath, dest_dfd, dest_subpath, 0) != 0) + { + if (errno == EMLINK || errno == EXDEV) + { + return glnx_file_copy_at (src_dfd, src_subpath, NULL, dest_dfd, dest_subpath, 0, + cancellable, error); + } + else + { + glnx_set_error_from_errno (error); + goto out; + } + } + + ret = TRUE; + out: + return ret; +} + static gboolean dirfd_copy_attributes_and_xattrs (int src_parent_dfd, const char *src_name, @@ -139,8 +172,8 @@ copy_dir_recurse (int src_parent_dfd, GError **error) { gboolean ret = FALSE; - int src_dfd = -1; - int dest_dfd = -1; + glnx_fd_close int src_dfd = -1; + glnx_fd_close int dest_dfd = -1; DIR *srcd = NULL; struct dirent *dent; @@ -207,10 +240,6 @@ copy_dir_recurse (int src_parent_dfd, /* Note the srcd owns src_dfd */ src_dfd = -1; } - if (src_dfd != -1) - (void) close (src_dfd); - if (dest_dfd != -1) - (void) close (dest_dfd); return ret; } @@ -224,8 +253,8 @@ ensure_directory_from_template (int orig_etc_fd, GError **error) { gboolean ret = FALSE; - int src_dfd = -1; - int target_dfd = -1; + glnx_fd_close int src_dfd = -1; + glnx_fd_close int target_dfd = -1; g_assert (path != NULL); g_assert (*path != '/' && *path != '\0'); @@ -283,10 +312,6 @@ ensure_directory_from_template (int orig_etc_fd, target_dfd = -1; } out: - if (src_dfd != -1) - (void) close (src_dfd); - if (target_dfd != -1) - (void) close (target_dfd); return ret; } @@ -308,7 +333,7 @@ copy_modified_config_file (int orig_etc_fd, gboolean ret = FALSE; struct stat modified_stbuf; struct stat new_stbuf; - int dest_parent_dfd = -1; + glnx_fd_close int dest_parent_dfd = -1; if (fstatat (modified_etc_fd, path, &modified_stbuf, AT_SYMLINK_NOFOLLOW) < 0) { @@ -398,8 +423,6 @@ copy_modified_config_file (int orig_etc_fd, ret = TRUE; out: - if (dest_parent_dfd != -1) - (void) close (dest_parent_dfd); return ret; } @@ -426,9 +449,9 @@ merge_etc_changes (GFile *orig_etc, g_autoptr(GPtrArray) removed = NULL; g_autoptr(GPtrArray) added = NULL; guint i; - int orig_etc_fd = -1; - int modified_etc_fd = -1; - int new_etc_fd = -1; + glnx_fd_close int orig_etc_fd = -1; + glnx_fd_close int modified_etc_fd = -1; + glnx_fd_close int new_etc_fd = -1; modified = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_diff_item_unref); removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref); @@ -505,12 +528,6 @@ merge_etc_changes (GFile *orig_etc, ret = TRUE; out: - if (orig_etc_fd != -1) - (void) close (orig_etc_fd); - if (modified_etc_fd != -1) - (void) close (modified_etc_fd); - if (new_etc_fd != -1) - (void) close (new_etc_fd); return ret; } @@ -651,7 +668,7 @@ relabel_recursively (OstreeSysroot *sysroot, if (file_info == NULL) break; - g_ptr_array_add (path_parts, (char*)gs_file_get_basename_cached (child)); + g_ptr_array_add (path_parts, (char*)g_file_info_get_name (file_info)); ftype = g_file_info_get_file_type (file_info); if (ftype == G_FILE_TYPE_DIRECTORY) @@ -723,12 +740,12 @@ selinux_relabel_file (OstreeSysroot *sysroot, goto out; g_ptr_array_add (path_parts, (char*)prefix); - g_ptr_array_add (path_parts, (char*)gs_file_get_basename_cached (path)); + g_ptr_array_add (path_parts, (char*)g_file_info_get_name (file_info)); if (!relabel_one_path (sysroot, sepolicy, path, file_info, path_parts, cancellable, error)) { g_prefix_error (error, "Relabeling /%s/%s: ", prefix, - gs_file_get_basename_cached (path)); + g_file_info_get_name (file_info)); goto out; } @@ -1277,7 +1294,6 @@ install_deployment_kernel (OstreeSysroot *sysroot, struct stat stbuf; const char *osname = ostree_deployment_get_osname (deployment); const char *bootcsum = ostree_deployment_get_bootcsum (deployment); - g_autoptr(GFile) bootdir = NULL; g_autofree char *bootcsumdir = NULL; g_autofree char *bootconfdir = NULL; g_autofree char *bootconf_name = NULL; @@ -1293,10 +1309,6 @@ install_deployment_kernel (OstreeSysroot *sysroot, g_autofree char *contents = NULL; g_autofree char *deployment_version = NULL; g_autoptr(GHashTable) osrelease_values = NULL; - g_autofree char *linux_relpath = NULL; - g_autofree char *linux_key = NULL; - g_autofree char *initramfs_relpath = NULL; - g_autofree char *initrd_key = NULL; g_autofree char *version_key = NULL; g_autofree char *ostree_kernel_arg = NULL; g_autofree char *options_key = NULL; @@ -1342,9 +1354,9 @@ install_deployment_kernel (OstreeSysroot *sysroot, glnx_set_prefix_error_from_errno (error, "fstat %s", dest_kernel_name); goto out; } - if (!glnx_file_copy_at (tree_boot_dfd, tree_kernel_name, NULL, - bootcsum_dfd, dest_kernel_name, 0, - cancellable, error)) + if (!hardlink_or_copy_at (tree_boot_dfd, tree_kernel_name, + bootcsum_dfd, dest_kernel_name, + cancellable, error)) goto out; } @@ -1359,9 +1371,9 @@ install_deployment_kernel (OstreeSysroot *sysroot, glnx_set_prefix_error_from_errno (error, "fstat %s", dest_initramfs_name); goto out; } - if (!glnx_file_copy_at (tree_boot_dfd, tree_initramfs_name, NULL, - bootcsum_dfd, dest_initramfs_name, 0, - cancellable, error)) + if (!hardlink_or_copy_at (tree_boot_dfd, tree_initramfs_name, + bootcsum_dfd, dest_initramfs_name, + cancellable, error)) goto out; } } diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h index d210a36f..1fa8e83c 100644 --- a/src/libostree/ostree-sysroot-private.h +++ b/src/libostree/ostree-sysroot-private.h @@ -34,6 +34,10 @@ typedef enum { } OstreeSysrootDebugFlags; +/** + * OstreeSysroot: + * Internal struct + */ struct OstreeSysroot { GObject parent; diff --git a/src/libostree/ostree-sysroot-upgrader.c b/src/libostree/ostree-sysroot-upgrader.c index 5c8f9d1d..92b8dc83 100644 --- a/src/libostree/ostree-sysroot-upgrader.c +++ b/src/libostree/ostree-sysroot-upgrader.c @@ -25,7 +25,7 @@ #include "ostree-sysroot-upgrader.h" /** - * SECTION:libostree-sysroot-upgrader + * SECTION:ostree-sysroot-upgrader * @title: Simple upgrade class * @short_description: Upgrade OSTree systems * diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c index b114a901..7cc4c9f5 100644 --- a/src/libostree/ostree-sysroot.c +++ b/src/libostree/ostree-sysroot.c @@ -37,7 +37,7 @@ find_booted_deployment (OstreeSysroot *self, GError **error); /** - * SECTION:libostree-sysroot + * SECTION:ostree-sysroot * @title: Root partition mount point * @short_description: Manage physical root filesystem * @@ -70,10 +70,12 @@ ostree_sysroot_finalize (GObject *object) g_clear_object (&self->path); g_clear_object (&self->sepolicy); g_clear_object (&self->repo); + g_clear_pointer (&self->deployments, g_ptr_array_unref); + g_clear_object (&self->booted_deployment); glnx_release_lock_file (&self->lock); - (void) ostree_sysroot_unload (self); + ostree_sysroot_unload (self); G_OBJECT_CLASS (ostree_sysroot_parent_class)->finalize (object); } @@ -276,28 +278,34 @@ ostree_sysroot_ensure_initialized (OstreeSysroot *self, GError **error) { gboolean ret = FALSE; - g_autoptr(GFile) dir = NULL; - g_autoptr(GFile) ostree_dir = NULL; - g_autoptr(GFile) repo_dir = NULL; + struct stat stbuf; - ostree_dir = g_file_get_child (self->path, "ostree"); - repo_dir = g_file_get_child (ostree_dir, "repo"); - if (!gs_file_ensure_directory (repo_dir, TRUE, cancellable, error)) + if (!ensure_sysroot_fd (self, error)) goto out; - g_clear_object (&dir); - dir = g_file_get_child (ostree_dir, "deploy"); - if (!gs_file_ensure_directory (dir, TRUE, cancellable, error)) - goto out; - - g_clear_object (&dir); - dir = ot_gfile_get_child_build_path (ostree_dir, "repo", "objects", NULL); - if (!g_file_query_exists (dir, NULL)) - { - glnx_unref_object OstreeRepo *repo = ostree_repo_new (repo_dir); - if (!ostree_repo_create (repo, OSTREE_REPO_MODE_BARE, + if (!glnx_shutil_mkdir_p_at (self->sysroot_fd, "ostree/repo", 0755, cancellable, error)) - goto out; + goto out; + + if (!glnx_shutil_mkdir_p_at (self->sysroot_fd, "ostree/deploy", 0755, + cancellable, error)) + goto out; + + if (fstatat (self->sysroot_fd, "ostree/repo/objects", &stbuf, 0) != 0) + { + if (errno != ENOENT) + { + glnx_set_prefix_error_from_errno (error, "stat %s", "ostree/repo/objects"); + goto out; + } + else + { + g_autoptr(GFile) repo_dir = g_file_resolve_relative_path (self->path, "ostree/repo"); + glnx_unref_object OstreeRepo *repo = ostree_repo_new (repo_dir); + if (!ostree_repo_create (repo, OSTREE_REPO_MODE_BARE, + cancellable, error)) + goto out; + } } ret = TRUE; @@ -873,7 +881,7 @@ ostree_sysroot_load_if_changed (OstreeSysroot *self, } g_clear_pointer (&self->deployments, g_ptr_array_unref); - g_clear_pointer (&self->booted_deployment, g_object_unref); + g_clear_object (&self->booted_deployment); self->bootversion = -1; self->subbootversion = -1; diff --git a/src/libostree/ostree.h b/src/libostree/ostree.h index f6bb6adb..9846a7e3 100644 --- a/src/libostree/ostree.h +++ b/src/libostree/ostree.h @@ -31,3 +31,5 @@ #include #include #include + +#include diff --git a/src/libotutil/ot-fs-utils.c b/src/libotutil/ot-fs-utils.c index 4d45cd06..46a0405b 100644 --- a/src/libotutil/ot-fs-utils.c +++ b/src/libotutil/ot-fs-utils.c @@ -230,3 +230,24 @@ ot_openat_ignore_enoent (int dfd, out: return ret; } + +GBytes * +ot_file_mapat_bytes (int dfd, + const char *path, + GError **error) +{ + glnx_fd_close int fd = openat (dfd, path, O_RDONLY | O_CLOEXEC); + g_autoptr(GMappedFile) mfile = NULL; + + if (fd < 0) + { + glnx_set_error_from_errno (error); + return FALSE; + } + + mfile = g_mapped_file_new_from_fd (fd, FALSE, error); + if (!mfile) + return FALSE; + + return g_mapped_file_get_bytes (mfile); +} diff --git a/src/libotutil/ot-fs-utils.h b/src/libotutil/ot-fs-utils.h index cfeea74d..27f0f38e 100644 --- a/src/libotutil/ot-fs-utils.h +++ b/src/libotutil/ot-fs-utils.h @@ -66,4 +66,8 @@ gboolean ot_openat_ignore_enoent (int dfd, int *out_fd, GError **error); +GBytes *ot_file_mapat_bytes (int dfd, + const char *path, + GError **error); + G_END_DECLS diff --git a/src/libotutil/ot-gio-utils.c b/src/libotutil/ot-gio-utils.c index 9b8b0dce..eb9b94f5 100644 --- a/src/libotutil/ot-gio-utils.c +++ b/src/libotutil/ot-gio-utils.c @@ -254,7 +254,8 @@ ot_gfile_load_contents_utf8_allow_noent (GFile *path, GError *temp_error = NULL; g_autofree char *ret_contents = NULL; - ret_contents = gs_file_load_contents_utf8 (path, cancellable, &temp_error); + ret_contents = glnx_file_get_contents_utf8_at (AT_FDCWD, gs_file_get_path_cached (path), NULL, + cancellable, &temp_error); if (!ret_contents) { if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) @@ -356,8 +357,8 @@ ot_gfile_replace_contents_fsync (GFile *path, GError **error) { gboolean ret = FALSE; - int parent_dfd; - const char *target_basename = gs_file_get_basename_cached (path); + glnx_fd_close int parent_dfd = -1; + const char *target_basename = glnx_basename (gs_file_get_path_cached (path)); g_autoptr(GFile) parent = NULL; parent = g_file_get_parent (path); @@ -373,8 +374,6 @@ ot_gfile_replace_contents_fsync (GFile *path, ret = TRUE; out: - if (parent_dfd != -1) - (void) close (parent_dfd); return ret; } @@ -389,25 +388,12 @@ ot_gfile_ensure_unlinked (GFile *path, GCancellable *cancellable, GError **error) { - gboolean ret = FALSE; - GError *temp_error = NULL; - - if (!gs_file_unlink (path, cancellable, &temp_error)) + if (unlink (gs_file_get_path_cached (path)) != 0) { - if (g_error_matches (temp_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) - { - g_clear_error (&temp_error); - } - else - { - g_propagate_error (error, temp_error); - goto out; - } + if (errno != ENOENT) + return FALSE; } - - ret = TRUE; - out: - return ret; + return TRUE; } /** @@ -424,7 +410,7 @@ ot_util_fsync_directory (GFile *dir, GError **error) { gboolean ret = FALSE; - int dfd = -1; + glnx_fd_close int dfd = -1; if (!glnx_opendirat (AT_FDCWD, gs_file_get_path_cached (dir), TRUE, &dfd, error)) @@ -438,8 +424,6 @@ ot_util_fsync_directory (GFile *dir, ret = TRUE; out: - if (dfd != -1) - (void) close (dfd); return ret; } @@ -458,8 +442,8 @@ ot_util_ensure_directory_and_fsync (GFile *dir, GError **error) { gboolean ret = FALSE; - int parentfd = -1; - const char *basename = gs_file_get_basename_cached (dir); + glnx_fd_close int parentfd = -1; + const char *basename = glnx_basename (gs_file_get_path_cached (dir)); g_autoptr(GFile) parent = g_file_get_parent (dir); again: @@ -507,7 +491,5 @@ ot_util_ensure_directory_and_fsync (GFile *dir, ret = TRUE; out: - if (parentfd != -1) - (void) close (parentfd); return ret; } diff --git a/src/libotutil/ot-variant-utils.c b/src/libotutil/ot-variant-utils.c index 2dc07582..315bbeb2 100644 --- a/src/libotutil/ot-variant-utils.c +++ b/src/libotutil/ot-variant-utils.c @@ -33,7 +33,10 @@ GVariant * ot_gvariant_new_empty_string_dict (void) { - return g_variant_builder_end (g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"))); + g_auto(GVariantBuilder) builder = {{0,}}; + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + return g_variant_builder_end (&builder); } GVariant * @@ -114,63 +117,31 @@ ot_util_variant_take_ref (GVariant *variant) return g_variant_take_ref (variant); } -/** - * ot_util_variant_map: - * @src: a #GFile - * @type: Use this for variant - * @trusted: See documentation of g_variant_new_from_data() - * @out_variant: (out): Return location for new variant - * @error: - * - * Memory-map @src, and store a new #GVariant referring to this memory - * in @out_variant. Note the returned @out_variant is not floating. - */ -gboolean -ot_util_variant_map (GFile *src, - const GVariantType *type, - gboolean trusted, - GVariant **out_variant, - GError **error) -{ - gboolean ret = FALSE; - g_autoptr(GVariant) ret_variant = NULL; - GMappedFile *mfile = NULL; - - mfile = gs_file_map_noatime (src, NULL, error); - if (!mfile) - goto out; - - ret_variant = g_variant_new_from_data (type, - g_mapped_file_get_contents (mfile), - g_mapped_file_get_length (mfile), - trusted, - (GDestroyNotify) g_mapped_file_unref, - mfile); - g_variant_ref_sink (ret_variant); - - ret = TRUE; - ot_transfer_out_value(out_variant, &ret_variant); - out: - return ret; -} - gboolean ot_util_variant_map_at (int dfd, const char *path, const GVariantType *type, - gboolean trusted, + OtVariantMapFlags flags, GVariant **out_variant, GError **error) { glnx_fd_close int fd = -1; - g_autoptr(GVariant) ret_variant = NULL; + const gboolean trusted = (flags & OT_VARIANT_MAP_TRUSTED) > 0; fd = openat (dfd, path, O_RDONLY | O_CLOEXEC); if (fd < 0) { - glnx_set_error_from_errno (error); - g_prefix_error (error, "Opening %s: ", path); - return FALSE; + if (errno == ENOENT && (flags & OT_VARIANT_MAP_ALLOW_NOENT) > 0) + { + *out_variant = NULL; + return TRUE; + } + else + { + glnx_set_error_from_errno (error); + g_prefix_error (error, "Opening %s: ", path); + return FALSE; + } } return ot_util_variant_map_fd (fd, 0, type, trusted, out_variant, error); @@ -186,6 +157,7 @@ variant_map_data_destroy (gpointer data) { VariantMapData *mdata = data; (void) munmap (mdata->addr, mdata->len); + g_free (mdata); } gboolean @@ -221,8 +193,8 @@ ot_util_variant_map_fd (int fd, mdata->len = len; ret = TRUE; - *out_variant = g_variant_new_from_data (type, map, len, trusted, - variant_map_data_destroy, mdata); + *out_variant = g_variant_ref_sink (g_variant_new_from_data (type, map, len, trusted, + variant_map_data_destroy, mdata)); out: return ret; } diff --git a/src/libotutil/ot-variant-utils.h b/src/libotutil/ot-variant-utils.h index 1a7abe0e..8a33cf60 100644 --- a/src/libotutil/ot-variant-utils.h +++ b/src/libotutil/ot-variant-utils.h @@ -42,16 +42,15 @@ gboolean ot_util_variant_save (GFile *dest, GCancellable *cancellable, GError **error); -gboolean ot_util_variant_map (GFile *src, - const GVariantType *type, - gboolean trusted, - GVariant **out_variant, - GError **error); +typedef enum { + OT_VARIANT_MAP_TRUSTED = (1 << 0), + OT_VARIANT_MAP_ALLOW_NOENT = (1 << 1) +} OtVariantMapFlags; gboolean ot_util_variant_map_at (int dfd, const char *path, const GVariantType *type, - gboolean trusted, + OtVariantMapFlags flags, GVariant **out_variant, GError **error); diff --git a/src/ostree/ot-admin-builtin-init-fs.c b/src/ostree/ot-admin-builtin-init-fs.c index 0172f01c..1f122553 100644 --- a/src/ostree/ot-admin-builtin-init-fs.c +++ b/src/ostree/ot-admin-builtin-init-fs.c @@ -39,8 +39,7 @@ ot_admin_builtin_init_fs (int argc, char **argv, GCancellable *cancellable, GErr GOptionContext *context; glnx_unref_object OstreeSysroot *sysroot = NULL; gboolean ret = FALSE; - g_autoptr(GFile) dir = NULL; - g_autoptr(GFile) child = NULL; + glnx_fd_close int root_dfd = -1; glnx_unref_object OstreeSysroot *target_sysroot = NULL; guint i; const char *normal_toplevels[] = {"boot", "dev", "home", "proc", "run", "sys"}; @@ -58,36 +57,31 @@ ot_admin_builtin_init_fs (int argc, char **argv, GCancellable *cancellable, GErr goto out; } - dir = g_file_new_for_path (argv[1]); - target_sysroot = ostree_sysroot_new (dir); + if (!glnx_opendirat (AT_FDCWD, argv[1], TRUE, &root_dfd, error)) + goto out; + { g_autoptr(GFile) dir = g_file_new_for_path (argv[1]); + target_sysroot = ostree_sysroot_new (dir); + } for (i = 0; i < G_N_ELEMENTS(normal_toplevels); i++) { - child = g_file_get_child (dir, normal_toplevels[i]); - if (!gs_file_ensure_directory_mode (child, 0755, cancellable, error)) + if (!glnx_shutil_mkdir_p_at (root_dfd, normal_toplevels[i], 0755, + cancellable, error)) goto out; - g_clear_object (&child); } - - child = g_file_get_child (dir, "root"); - if (!gs_file_ensure_directory_mode (child, 0700, cancellable, error)) + + if (!glnx_shutil_mkdir_p_at (root_dfd, "root", 0700, + cancellable, error)) goto out; - g_clear_object (&child); - child = g_file_get_child (dir, "tmp"); - if (!gs_file_ensure_directory_mode (child, 01777, cancellable, error)) + if (!glnx_shutil_mkdir_p_at (root_dfd, "tmp", 01777, + cancellable, error)) goto out; - /* FIXME - we should be using an API that explicitly ignores umask; - */ - { - const char *path = gs_file_get_path_cached (child); - if (chmod (path, 01777) == -1) - { - gs_set_prefix_error_from_errno (error, errno, "chmod"); - goto out; - } - } - g_clear_object (&child); + if (fchmodat (root_dfd, "tmp", 01777, 0) == -1) + { + glnx_set_prefix_error_from_errno (error, "chmod: %s", "tmp"); + goto out; + } if (!ostree_sysroot_ensure_initialized (target_sysroot, cancellable, error)) goto out; diff --git a/src/ostree/ot-admin-builtin-instutil.c b/src/ostree/ot-admin-builtin-instutil.c index 1087b381..88894a6a 100644 --- a/src/ostree/ot-admin-builtin-instutil.c +++ b/src/ostree/ot-admin-builtin-instutil.c @@ -20,11 +20,11 @@ #include "config.h" +#include "ot-main.h" #include "ot-builtins.h" #include "ot-admin-instutil-builtins.h" #include "ot-admin-builtins.h" #include "ot-admin-functions.h" -#include "ot-main.h" #include "ostree.h" #include diff --git a/src/ostree/ot-admin-builtin-switch.c b/src/ostree/ot-admin-builtin-switch.c index 0e0101a9..b313e830 100644 --- a/src/ostree/ot-admin-builtin-switch.c +++ b/src/ostree/ot-admin-builtin-switch.c @@ -61,8 +61,6 @@ ot_admin_builtin_switch (int argc, char **argv, GCancellable *cancellable, GErro glnx_unref_object OstreeSysrootUpgrader *upgrader = NULL; glnx_unref_object OstreeAsyncProgress *progress = NULL; gboolean changed; - GSConsole *console = NULL; - gboolean in_status_line = FALSE; GKeyFile *old_origin; GKeyFile *new_origin = NULL; @@ -125,28 +123,24 @@ ot_admin_builtin_switch (int argc, char **argv, GCancellable *cancellable, GErro if (!ostree_sysroot_upgrader_set_origin (upgrader, new_origin, cancellable, error)) goto out; - console = gs_console_get (); - if (console) - { - gs_console_begin_status_line (console, "", NULL, NULL); - in_status_line = TRUE; - progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, console); - } + { g_auto(GLnxConsoleRef) console = { 0, }; + glnx_console_lock (&console); - /* Always allow older...there's not going to be a chronological - * relationship necessarily. - */ - if (!ostree_sysroot_upgrader_pull (upgrader, 0, - OSTREE_SYSROOT_UPGRADER_PULL_FLAGS_ALLOW_OLDER, - progress, &changed, - cancellable, error)) - goto out; + if (console.is_tty) + progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, &console); - if (in_status_line) - { - gs_console_end_status_line (console, NULL, NULL); - in_status_line = FALSE; - } + /* Always allow older...there's not going to be a chronological + * relationship necessarily. + */ + if (!ostree_sysroot_upgrader_pull (upgrader, 0, + OSTREE_SYSROOT_UPGRADER_PULL_FLAGS_ALLOW_OLDER, + progress, &changed, + cancellable, error)) + goto out; + + if (progress) + ostree_async_progress_finish (progress); + } if (!ostree_sysroot_upgrader_deploy (upgrader, cancellable, error)) goto out; @@ -171,8 +165,6 @@ ot_admin_builtin_switch (int argc, char **argv, GCancellable *cancellable, GErro ret = TRUE; out: - if (in_status_line) - gs_console_end_status_line (console, NULL, NULL); if (new_origin) g_key_file_unref (new_origin); if (context) diff --git a/src/ostree/ot-admin-builtin-upgrade.c b/src/ostree/ot-admin-builtin-upgrade.c index b3b531c0..81f9bb6f 100644 --- a/src/ostree/ot-admin-builtin-upgrade.c +++ b/src/ostree/ot-admin-builtin-upgrade.c @@ -55,8 +55,6 @@ ot_admin_builtin_upgrade (int argc, char **argv, GCancellable *cancellable, GErr g_autoptr(GFile) deployment_path = NULL; g_autoptr(GFile) deployment_origin_path = NULL; g_autoptr(GKeyFile) origin = NULL; - GSConsole *console = NULL; - gboolean in_status_line = FALSE; glnx_unref_object OstreeAsyncProgress *progress = NULL; gboolean changed; OstreeSysrootUpgraderPullFlags upgraderpullflags = 0; @@ -108,27 +106,23 @@ ot_admin_builtin_upgrade (int argc, char **argv, GCancellable *cancellable, GErr } } - console = gs_console_get (); - if (console) - { - gs_console_begin_status_line (console, "", NULL, NULL); - in_status_line = TRUE; - progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, console); - } + { g_auto(GLnxConsoleRef) console = { 0, }; + glnx_console_lock (&console); - if (opt_allow_downgrade) - upgraderpullflags |= OSTREE_SYSROOT_UPGRADER_PULL_FLAGS_ALLOW_OLDER; + if (console.is_tty) + progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, &console); - if (!ostree_sysroot_upgrader_pull (upgrader, 0, upgraderpullflags, - progress, &changed, - cancellable, error)) - goto out; + if (opt_allow_downgrade) + upgraderpullflags |= OSTREE_SYSROOT_UPGRADER_PULL_FLAGS_ALLOW_OLDER; + + if (!ostree_sysroot_upgrader_pull (upgrader, 0, upgraderpullflags, + progress, &changed, + cancellable, error)) + goto out; - if (in_status_line) - { - gs_console_end_status_line (console, NULL, NULL); - in_status_line = FALSE; - } + if (progress) + ostree_async_progress_finish (progress); + } if (!changed) { @@ -148,8 +142,6 @@ ot_admin_builtin_upgrade (int argc, char **argv, GCancellable *cancellable, GErr ret = TRUE; out: - if (in_status_line) - gs_console_end_status_line (console, NULL, NULL); if (context) g_option_context_free (context); return ret; diff --git a/src/ostree/ot-admin-functions.c b/src/ostree/ot-admin-functions.c index bc9034e1..ed4dfdfb 100644 --- a/src/ostree/ot-admin-functions.c +++ b/src/ostree/ot-admin-functions.c @@ -22,6 +22,7 @@ #include "config.h" +#include "libglnx.h" #include "ot-admin-functions.h" #include "otutil.h" #include "ostree.h" diff --git a/src/ostree/ot-admin-instutil-builtin-selinux-ensure-labeled.c b/src/ostree/ot-admin-instutil-builtin-selinux-ensure-labeled.c index 1f0e91e3..776ab54f 100644 --- a/src/ostree/ot-admin-instutil-builtin-selinux-ensure-labeled.c +++ b/src/ostree/ot-admin-instutil-builtin-selinux-ensure-labeled.c @@ -120,7 +120,7 @@ relabel_recursively (OstreeSePolicy *sepolicy, if (file_info == NULL) break; - g_ptr_array_add (path_parts, (char*)gs_file_get_basename_cached (child)); + g_ptr_array_add (path_parts, (char*)g_file_info_get_name (file_info)); ftype = g_file_info_get_file_type (file_info); if (ftype == G_FILE_TYPE_DIRECTORY) diff --git a/src/ostree/ot-builtin-admin.c b/src/ostree/ot-builtin-admin.c index 8b866170..7e7b04b5 100644 --- a/src/ostree/ot-builtin-admin.c +++ b/src/ostree/ot-builtin-admin.c @@ -22,10 +22,10 @@ #include "config.h" +#include "ot-main.h" #include "ot-builtins.h" #include "ot-admin-builtins.h" #include "ot-admin-functions.h" -#include "ot-main.h" #include "ostree.h" #include "ostree-repo-file.h" diff --git a/src/ostree/ot-builtin-commit.c b/src/ostree/ot-builtin-commit.c index 1d96855d..85c4c65d 100644 --- a/src/ostree/ot-builtin-commit.c +++ b/src/ostree/ot-builtin-commit.c @@ -32,10 +32,12 @@ static char *opt_subject; static char *opt_body; +static gboolean opt_editor; static char *opt_parent; static gboolean opt_orphan; static char *opt_branch; static char *opt_statoverride_file; +static char *opt_skiplist_file; static char **opt_metadata_strings; static char **opt_detached_metadata_strings; static gboolean opt_link_checkout_speedup; @@ -72,6 +74,7 @@ static GOptionEntry options[] = { { "parent", 0, 0, G_OPTION_ARG_STRING, &opt_parent, "Parent ref, or \"none\"", "REF" }, { "subject", 's', 0, G_OPTION_ARG_STRING, &opt_subject, "One line subject", "SUBJECT" }, { "body", 'm', 0, G_OPTION_ARG_STRING, &opt_body, "Full description", "BODY" }, + { "editor", 'e', 0, G_OPTION_ARG_NONE, &opt_editor, "Use an editor to write the commit message", NULL }, { "branch", 'b', 0, G_OPTION_ARG_STRING, &opt_branch, "Branch", "BRANCH" }, { "orphan", 0, 0, G_OPTION_ARG_NONE, &opt_orphan, "Create a commit without writing a ref", NULL }, { "tree", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_trees, "Overlay the given argument as a tree", "dir=PATH or tar=TARFILE or ref=COMMIT" }, @@ -84,6 +87,7 @@ static GOptionEntry options[] = { { "tar-autocreate-parents", 0, 0, G_OPTION_ARG_NONE, &opt_tar_autocreate_parents, "When loading tar archives, automatically create parent directories as needed", NULL }, { "skip-if-unchanged", 0, 0, G_OPTION_ARG_NONE, &opt_skip_if_unchanged, "If the contents are unchanged from previous commit, do nothing", NULL }, { "statoverride", 0, 0, G_OPTION_ARG_FILENAME, &opt_statoverride_file, "File containing list of modifications to make to permissions", "PATH" }, + { "skip-list", 0, 0, G_OPTION_ARG_FILENAME, &opt_skiplist_file, "File containing list of files to skip", "PATH" }, { "table-output", 0, 0, G_OPTION_ARG_NONE, &opt_table_output, "Output more information in a KEY: VALUE format", NULL }, { "gpg-sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_key_ids, "GPG Key ID to sign the commit with", "KEY-ID"}, { "gpg-homedir", 0, 0, G_OPTION_ARG_STRING, &opt_gpg_homedir, "GPG Homedir to use when looking for keyrings", "HOMEDIR"}, @@ -95,65 +99,85 @@ static GOptionEntry options[] = { }; static gboolean -parse_statoverride_file (GHashTable **out_mode_add, - GCancellable *cancellable, - GError **error) +parse_file_by_line (const char *path, + gboolean (*cb)(const char*, void*, GError**), + void *cbdata, + GCancellable *cancellable, + GError **error) { gboolean ret = FALSE; - gsize len; - char **iter = NULL; /* nofree */ - g_autoptr(GHashTable) ret_hash = NULL; - g_autoptr(GFile) path = NULL; g_autofree char *contents = NULL; + g_autoptr(GFile) file = NULL; char **lines = NULL; - path = g_file_new_for_path (opt_statoverride_file); - - if (!g_file_load_contents (path, cancellable, &contents, &len, NULL, - error)) + file = g_file_new_for_path (path); + if (!g_file_load_contents (file, cancellable, &contents, NULL, NULL, error)) goto out; - - ret_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + lines = g_strsplit (contents, "\n", -1); - - for (iter = lines; iter && *iter; iter++) + for (char **iter = lines; iter && *iter; iter++) { - const char *line = *iter; + /* skip empty lines at least */ + if (**iter == '\0') + continue; - if (*line == '+') - { - const char *spc; - guint mode_add; - - spc = strchr (line + 1, ' '); - if (!spc) - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Malformed statoverride file"); - goto out; - } - - mode_add = (guint32)(gint32)g_ascii_strtod (line + 1, NULL); - g_hash_table_insert (ret_hash, - g_strdup (spc + 1), - GUINT_TO_POINTER((gint32)mode_add)); - } + if (!cb (*iter, cbdata, error)) + goto out; } ret = TRUE; - ot_transfer_out_value (out_mode_add, &ret_hash); - out: +out: g_strfreev (lines); return ret; } +static gboolean +handle_statoverride_line (const char *line, + void *data, + GError **error) +{ + GHashTable *files = data; + const char *spc; + guint mode_add; + + spc = strchr (line, ' '); + if (spc == NULL) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Malformed statoverride file (no space found)"); + return FALSE; + } + + mode_add = (guint32)(gint32)g_ascii_strtod (line, NULL); + g_hash_table_insert (files, g_strdup (spc + 1), + GUINT_TO_POINTER((gint32)mode_add)); + return TRUE; +} + +static gboolean +handle_skiplist_line (const char *line, + void *data, + GError **error) +{ + GHashTable *files = data; + g_hash_table_add (files, g_strdup (line)); + return TRUE; +} + +struct CommitFilterData { + GHashTable *mode_adds; + GHashTable *skip_list; +}; + static OstreeRepoCommitFilterResult commit_filter (OstreeRepo *self, const char *path, GFileInfo *file_info, gpointer user_data) { - GHashTable *mode_adds = user_data; + struct CommitFilterData *data = user_data; + GHashTable *mode_adds = data->mode_adds; + GHashTable *skip_list = data->skip_list; gpointer value; if (opt_owner_uid >= 0) @@ -169,7 +193,13 @@ commit_filter (OstreeRepo *self, current_mode | mode_add); g_hash_table_remove (mode_adds, path); } - + + if (skip_list && g_hash_table_contains (skip_list, path)) + { + g_hash_table_remove (skip_list, path); + return OSTREE_REPO_COMMIT_FILTER_SKIP; + } + return OSTREE_REPO_COMMIT_FILTER_ALLOW; } @@ -188,14 +218,18 @@ commit_editor (OstreeRepo *repo, char **lines = NULL; int i; - *subject = NULL; - *body = NULL; - 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." - "%s%s\n", branch ? "\n#\n# Branch: " : "", branch ?: ""); + "%s%s%s%s%s%s\n" + , branch ? "\n#\n# Branch: " : "", branch ? branch : "" + , *subject ? "\n" : "", *subject ? *subject : "" + , *body ? "\n" : "", *body ? *body : "" + ); + + *subject = NULL; + *body = NULL; output = ot_editor_prompt (repo, input, cancellable, error); if (output == NULL) @@ -310,9 +344,11 @@ 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) skip_list = NULL; OstreeRepoCommitModifierFlags flags = 0; OstreeRepoCommitModifier *modifier = NULL; OstreeRepoTransactionStats stats; + struct CommitFilterData filter_data = { 0, }; context = g_option_context_new ("[PATH] - Commit a new revision"); @@ -324,7 +360,17 @@ ostree_builtin_commit (int argc, char **argv, GCancellable *cancellable, GError if (opt_statoverride_file) { - if (!parse_statoverride_file (&mode_adds, cancellable, error)) + mode_adds = 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)) + goto out; + } + + if (opt_skiplist_file) + { + skip_list = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + if (!parse_file_by_line (opt_skiplist_file, handle_skiplist_line, + skip_list, cancellable, error)) goto out; } @@ -359,9 +405,13 @@ ostree_builtin_commit (int argc, char **argv, GCancellable *cancellable, GError || opt_owner_uid >= 0 || opt_owner_gid >= 0 || opt_statoverride_file != NULL + || opt_skiplist_file != NULL || opt_no_xattrs) { - modifier = ostree_repo_commit_modifier_new (flags, commit_filter, mode_adds, NULL); + filter_data.mode_adds = mode_adds; + filter_data.skip_list = skip_list; + modifier = ostree_repo_commit_modifier_new (flags, commit_filter, + &filter_data, NULL); } if (opt_parent) @@ -381,19 +431,12 @@ ostree_builtin_commit (int argc, char **argv, GCancellable *cancellable, GError goto out; } - if (!opt_subject && !opt_body) + if (opt_editor) { if (!commit_editor (repo, opt_branch, &opt_subject, &opt_body, cancellable, error)) goto out; } - if (!opt_subject) - { - g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "A subject must be specified with --subject"); - goto out; - } - if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error)) goto out; @@ -491,6 +534,22 @@ ostree_builtin_commit (int argc, char **argv, GCancellable *cancellable, GError goto out; } + if (skip_list && g_hash_table_size (skip_list) > 0) + { + GHashTableIter hash_iter; + gpointer key; + + g_hash_table_iter_init (&hash_iter, skip_list); + + while (g_hash_table_iter_next (&hash_iter, &key, NULL)) + { + g_printerr ("Unmatched skip-list path: %s\n", (char*)key); + } + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Unmatched skip-list paths"); + goto out; + } + if (!ostree_repo_write_mtree (repo, mtree, &root, cancellable, error)) goto out; diff --git a/src/ostree/ot-builtin-export.c b/src/ostree/ot-builtin-export.c index cccb50e7..5b84d1ab 100644 --- a/src/ostree/ot-builtin-export.c +++ b/src/ostree/ot-builtin-export.c @@ -32,10 +32,14 @@ #endif static char *opt_output_path; +static char *opt_subpath; +static char *opt_prefix; static gboolean opt_no_xattrs; static GOptionEntry options[] = { { "no-xattrs", 0, 0, G_OPTION_ARG_NONE, &opt_no_xattrs, "Skip output of extended attributes", NULL }, + { "subpath", 0, 0, G_OPTION_ARG_STRING, &opt_subpath, "Checkout sub-directory PATH", "PATH" }, + { "prefix", 0, 0, G_OPTION_ARG_STRING, &opt_prefix, "Add PATH as prefix to archive pathnames", "PATH" }, { "output", 'o', 0, G_OPTION_ARG_STRING, &opt_output_path, "Output to PATH ", "PATH" }, { NULL } }; @@ -60,6 +64,7 @@ ostree_builtin_export (int argc, char **argv, GCancellable *cancellable, GError gboolean ret = FALSE; const char *rev; g_autoptr(GFile) root = NULL; + g_autoptr(GFile) subtree = NULL; g_autofree char *commit = NULL; g_autoptr(GVariant) commit_data = NULL; struct archive *a; @@ -124,7 +129,14 @@ ostree_builtin_export (int argc, char **argv, GCancellable *cancellable, GError opts.timestamp_secs = ostree_commit_get_timestamp (commit_data); - if (!ostree_repo_export_tree_to_archive (repo, &opts, (OstreeRepoFile*)root, a, + if (opt_subpath) + subtree = g_file_resolve_relative_path (root, opt_subpath); + else + subtree = g_object_ref (root); + + opts.path_prefix = opt_prefix; + + if (!ostree_repo_export_tree_to_archive (repo, &opts, (OstreeRepoFile*)subtree, a, cancellable, error)) goto out; diff --git a/src/ostree/ot-builtin-init.c b/src/ostree/ot-builtin-init.c index 9e7c8a2b..a250b793 100644 --- a/src/ostree/ot-builtin-init.c +++ b/src/ostree/ot-builtin-init.c @@ -37,7 +37,7 @@ gboolean ostree_builtin_init (int argc, char **argv, GCancellable *cancellable, GError **error) { GOptionContext *context = NULL; - glnx_unref_object OstreeRepo *repo = NULL; + g_autoptr(OstreeRepo) repo = NULL; gboolean ret = FALSE; OstreeRepoMode mode; diff --git a/src/ostree/ot-builtin-pull-local.c b/src/ostree/ot-builtin-pull-local.c index 36057ec6..7fb6f03a 100644 --- a/src/ostree/ot-builtin-pull-local.c +++ b/src/ostree/ot-builtin-pull-local.c @@ -55,7 +55,6 @@ ostree_builtin_pull_local (int argc, char **argv, GCancellable *cancellable, GEr glnx_unref_object OstreeRepo *repo = NULL; int i; const char *src_repo_arg; - GSConsole *console = NULL; g_autofree char *src_repo_uri = NULL; glnx_unref_object OstreeAsyncProgress *progress = NULL; g_autoptr(GPtrArray) refs_to_fetch = NULL; @@ -132,14 +131,11 @@ ostree_builtin_pull_local (int argc, char **argv, GCancellable *cancellable, GEr g_ptr_array_add (refs_to_fetch, NULL); } - console = gs_console_get (); - if (console) - { - gs_console_begin_status_line (console, "", NULL, NULL); - progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, console); - } - { GVariantBuilder builder; + g_auto(GLnxConsoleRef) console = { 0, }; + + glnx_console_lock (&console); + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_add (&builder, "{s@v}", "flags", @@ -158,17 +154,21 @@ ostree_builtin_pull_local (int argc, char **argv, GCancellable *cancellable, GEr g_variant_builder_add (&builder, "{s@v}", "depth", g_variant_new_variant (g_variant_new_int32 (opt_depth))); + if (console.is_tty) + progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, &console); + if (!ostree_repo_pull_with_options (repo, src_repo_uri, g_variant_builder_end (&builder), progress, cancellable, error)) goto out; + + if (progress) + ostree_async_progress_finish (progress); } ret = TRUE; out: - if (progress) - ostree_async_progress_finish (progress); if (context) g_option_context_free (context); if (repo) diff --git a/src/ostree/ot-builtin-pull.c b/src/ostree/ot-builtin-pull.c index 734f7440..99b25937 100644 --- a/src/ostree/ot-builtin-pull.c +++ b/src/ostree/ot-builtin-pull.c @@ -37,7 +37,8 @@ static gboolean opt_untrusted; static char* opt_subpath; static char* opt_cache_dir; static int opt_depth = 0; - +static char* opt_url; + static GOptionEntry options[] = { { "commit-metadata-only", 0, 0, G_OPTION_ARG_NONE, &opt_commit_only, "Fetch only the commit metadata", NULL }, { "cache-dir", 0, 0, G_OPTION_ARG_STRING, &opt_cache_dir, "Use custom cache dir", NULL }, @@ -49,6 +50,7 @@ static GOptionEntry options[] = { { "untrusted", 0, 0, G_OPTION_ARG_NONE, &opt_untrusted, "Do not trust (local) sources", NULL }, { "dry-run", 0, 0, G_OPTION_ARG_NONE, &opt_dry_run, "Only print information on what will be downloaded (requires static deltas)", NULL }, { "depth", 0, 0, G_OPTION_ARG_INT, &opt_depth, "Traverse DEPTH parents (-1=infinite) (default: 0)", "DEPTH" }, + { "url", 0, 0, G_OPTION_ARG_STRING, &opt_url, "Pull objects from this URL instead of the one from the remote config", NULL }, { NULL } }; @@ -56,16 +58,17 @@ static void gpg_verify_result_cb (OstreeRepo *repo, const char *checksum, OstreeGpgVerifyResult *result, - GSConsole *console) + GLnxConsoleRef *console) { - /* Temporarily place the GSConsole stream (which is just stdout) - * back in normal mode before printing GPG verification results. */ - gs_console_end_status_line (console, NULL, NULL); + /* Temporarily place the tty back in normal mode before printing GPG + * verification results. + */ + glnx_console_unlock (console); g_print ("\n"); ostree_print_gpg_verify_result (result); - gs_console_begin_status_line (console, "", NULL, NULL); + glnx_console_lock (console); } static gboolean printed_console_progress; @@ -109,7 +112,6 @@ ostree_builtin_pull (int argc, char **argv, GCancellable *cancellable, GError ** gboolean ret = FALSE; g_autofree char *remote = NULL; OstreeRepoPullFlags pullflags = 0; - GSConsole *console = NULL; g_autoptr(GPtrArray) refs_to_fetch = NULL; g_autoptr(GPtrArray) override_commit_ids = NULL; glnx_unref_object OstreeAsyncProgress *progress = NULL; @@ -204,30 +206,16 @@ ostree_builtin_pull (int argc, char **argv, GCancellable *cancellable, GError ** g_ptr_array_add (refs_to_fetch, NULL); } - if (!opt_dry_run) - { - console = gs_console_get (); - if (console) - { - gs_console_begin_status_line (console, "", NULL, NULL); - progress = ostree_async_progress_new_and_connect (ostree_repo_pull_default_console_progress_changed, console); - signal_handler_id = g_signal_connect (repo, "gpg-verify-result", - G_CALLBACK (gpg_verify_result_cb), - console); - } - } - else - { - progress = ostree_async_progress_new_and_connect (dry_run_console_progress_changed, console); - signal_handler_id = g_signal_connect (repo, "gpg-verify-result", - G_CALLBACK (gpg_verify_result_cb), - console); - } - { GVariantBuilder builder; + g_auto(GLnxConsoleRef) console = { 0, }; g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + glnx_console_lock (&console); + + if (opt_url) + g_variant_builder_add (&builder, "{s@v}", "override-url", + g_variant_new_variant (g_variant_new_string (opt_url))); if (opt_subpath) g_variant_builder_add (&builder, "{s@v}", "subdir", g_variant_new_variant (g_variant_new_string (opt_subpath))); @@ -252,25 +240,38 @@ ostree_builtin_pull (int argc, char **argv, GCancellable *cancellable, GError ** 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_dry_run) + { + 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 (dry_run_console_progress_changed, NULL); + } + + if (console.is_tty) + { + signal_handler_id = g_signal_connect (repo, "gpg-verify-result", + G_CALLBACK (gpg_verify_result_cb), + &console); + } + if (!ostree_repo_pull_with_options (repo, remote, g_variant_builder_end (&builder), progress, cancellable, error)) goto out; + + if (progress) + ostree_async_progress_finish (progress); + + if (opt_dry_run) + g_assert (printed_console_progress); } - if (progress) - ostree_async_progress_finish (progress); - - if (opt_dry_run) - g_assert (printed_console_progress); - ret = TRUE; out: if (signal_handler_id > 0) g_signal_handler_disconnect (repo, signal_handler_id); - - if (console) - gs_console_end_status_line (console, NULL, NULL); - if (context) g_option_context_free (context); return ret; diff --git a/src/ostree/ot-builtin-refs.c b/src/ostree/ot-builtin-refs.c index af90c841..10647ec6 100644 --- a/src/ostree/ot-builtin-refs.c +++ b/src/ostree/ot-builtin-refs.c @@ -28,10 +28,12 @@ static gboolean opt_delete; static gboolean opt_list; +static char *opt_create; 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" }, { NULL } }; @@ -48,10 +50,16 @@ static gboolean do_ref (OstreeRepo *repo, const char *refspec_prefix, GCancellab cancellable, error)) goto out; } + else if (opt_create) + { + if (!ostree_repo_list_refs_ext (repo, NULL, &refs, OSTREE_REPO_LIST_REFS_EXT_NONE, + cancellable, error)) + goto out; + } else if (!ostree_repo_list_refs (repo, refspec_prefix, &refs, cancellable, error)) goto out; - if (!opt_delete) + if (!opt_delete && !opt_create) { g_hash_table_iter_init (&hashiter, refs); while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue)) @@ -60,7 +68,30 @@ static gboolean do_ref (OstreeRepo *repo, const char *refspec_prefix, GCancellab g_print ("%s\n", ref); } } + else if (opt_create) + { + g_autofree char *checksum = NULL; + g_autofree char *checksum_existing = NULL; + + if (!ostree_repo_resolve_rev (repo, opt_create, TRUE, &checksum_existing, error)) + 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; + + if (!ostree_repo_set_ref_immediate (repo, NULL, opt_create, checksum, + cancellable, error)) + goto out; + } else + /* delete */ { g_hash_table_iter_init (&hashiter, refs); while (g_hash_table_iter_next (&hashiter, &hashkey, &hashvalue)) @@ -97,6 +128,12 @@ ostree_builtin_refs (int argc, char **argv, GCancellable *cancellable, GError ** if (argc >= 2) { + if (opt_create && argc > 2) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "You must specify only 1 existing ref when creating a new ref"); + goto out; + } for (i = 1; i < argc; i++) if (!do_ref (repo, argv[i], cancellable, error)) goto out; @@ -110,6 +147,13 @@ ostree_builtin_refs (int argc, char **argv, GCancellable *cancellable, GError ** "At least one PREFIX is required when deleting refs"); goto out; } + else if (opt_create) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "You must specify an existing ref when creating a new ref"); + goto out; + } + ret = do_ref (repo, NULL, cancellable, error); } diff --git a/src/ostree/ot-builtin-show.c b/src/ostree/ot-builtin-show.c index a1b4db5f..ef541c2a 100644 --- a/src/ostree/ot-builtin-show.c +++ b/src/ostree/ot-builtin-show.c @@ -51,12 +51,9 @@ do_print_variant_generic (const GVariantType *type, GError **error) { gboolean ret = FALSE; - g_autoptr(GFile) f = NULL; g_autoptr(GVariant) variant = NULL; - f = g_file_new_for_path (filename); - - if (!ot_util_variant_map (f, type, TRUE, &variant, error)) + if (!ot_util_variant_map_at (AT_FDCWD, filename, type, TRUE, &variant, error)) goto out; ot_dump_variant (variant); diff --git a/src/ostree/ot-builtin-summary.c b/src/ostree/ot-builtin-summary.c index 4622ceef..2ff1c965 100644 --- a/src/ostree/ot-builtin-summary.c +++ b/src/ostree/ot-builtin-summary.c @@ -31,7 +31,7 @@ static char *opt_gpg_homedir; static GOptionEntry options[] = { { "update", 'u', 0, G_OPTION_ARG_NONE, &opt_update, "Update the summary", NULL }, - { "gpg-sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_key_ids, "GPG Key ID to sign the commit with", "KEY-ID"}, + { "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_STRING, &opt_gpg_homedir, "GPG Homedir to use when looking for keyrings", "HOMEDIR"}, { NULL } }; diff --git a/src/ostree/ot-builtin-trivial-httpd.c b/src/ostree/ot-builtin-trivial-httpd.c index 038559bb..811e8924 100644 --- a/src/ostree/ot-builtin-trivial-httpd.c +++ b/src/ostree/ot-builtin-trivial-httpd.c @@ -22,6 +22,8 @@ #include +#include + #include "ot-main.h" #include "ot-builtins.h" #include "ostree.h" @@ -32,6 +34,7 @@ #include static char *opt_port_file = NULL; +static char *opt_log = NULL; static gboolean opt_daemonize; static gboolean opt_autoexit; static gboolean opt_force_ranges; @@ -40,6 +43,7 @@ static gint opt_port = 0; typedef struct { GFile *root; gboolean running; + GOutputStream *log; } OtTrivialHttpd; static GOptionEntry options[] = { @@ -48,9 +52,31 @@ static GOptionEntry options[] = { { "port", 'P', 0, G_OPTION_ARG_INT, &opt_port, "Use the specified TCP port", NULL }, { "port-file", 'p', 0, G_OPTION_ARG_FILENAME, &opt_port_file, "Write port number to PATH (- for standard output)", "PATH" }, { "force-range-requests", 0, 0, G_OPTION_ARG_NONE, &opt_force_ranges, "Force range requests by only serving half of files", NULL }, + { "log-file", 0, 0, G_OPTION_ARG_FILENAME, &opt_log, "Put logs here", "PATH" }, { NULL } }; +static void +httpd_log (OtTrivialHttpd *httpd, const gchar *format, ...) __attribute__ ((format(printf, 2, 3))); + +static void +httpd_log (OtTrivialHttpd *httpd, const gchar *format, ...) +{ + g_autoptr(GString) str = NULL; + va_list args; + gsize written; + + if (!httpd->log) + return; + + str = g_string_new (NULL); + va_start (args, format); + g_string_vprintf (str, format, args); + va_end (args); + + g_output_stream_write_all (httpd->log, str->str, str->len, &written, NULL, NULL); +} + static int compare_strings (gconstpointer a, gconstpointer b) { @@ -154,6 +180,7 @@ do_get (OtTrivialHttpd *self, struct stat stbuf; g_autofree char *safepath = NULL; + httpd_log (self, "serving %s\n", path); if (strstr (path, "../") != NULL) { soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN); @@ -296,6 +323,16 @@ do_get (OtTrivialHttpd *self, soup_message_set_status (msg, SOUP_STATUS_OK); } out: + { + guint status = 0; + g_autofree gchar *reason = NULL; + + g_object_get (msg, + "status-code", &status, + "reason-phrase", &reason, + NULL); + httpd_log (self, " status: %s (%u)\n", reason, status); + } return; } @@ -305,9 +342,6 @@ httpd_callback (SoupServer *server, SoupMessage *msg, SoupClientContext *context, gpointer data) { OtTrivialHttpd *self = data; - SoupMessageHeadersIter iter; - - soup_message_headers_iter_init (&iter, msg->request_headers); if (msg->method == SOUP_METHOD_GET || msg->method == SOUP_METHOD_HEAD) do_get (self, server, msg, path, context); @@ -354,6 +388,37 @@ ostree_builtin_trivial_httpd (int argc, char **argv, GCancellable *cancellable, app->root = g_file_new_for_path (dirpath); + if (opt_log) + { + GOutputStream *stream = NULL; + + if (g_strcmp0 (opt_log, "-") == 0) + { + if (opt_daemonize) + { + ot_util_usage_error (context, "Cannot use --log-file=- and --daemonize at the same time", error); + goto out; + } + stream = G_OUTPUT_STREAM (g_unix_output_stream_new (STDOUT_FILENO, FALSE)); + } + else + { + g_autoptr(GFile) log_file; + GFileOutputStream* log_stream; + + log_file = g_file_new_for_path (opt_log); + log_stream = g_file_create (log_file, + G_FILE_CREATE_PRIVATE, + cancellable, + error); + if (!log_stream) + goto out; + stream = G_OUTPUT_STREAM (log_stream); + } + + app->log = stream; + } + #if SOUP_CHECK_VERSION(2, 48, 0) server = soup_server_new (SOUP_SERVER_SERVER_HEADER, "ostree-httpd ", NULL); if (!soup_server_listen_all (server, opt_port, 0, error)) @@ -459,13 +524,17 @@ ostree_builtin_trivial_httpd (int argc, char **argv, GCancellable *cancellable, goto out; g_signal_connect (dirmon, "changed", G_CALLBACK (on_dir_changed), app); } - + { + g_autofree gchar *path = g_file_get_path (app->root); + httpd_log (app, "serving at root %s\n", path); + } while (app->running) g_main_context_iteration (NULL, TRUE); - + ret = TRUE; out: g_clear_object (&app->root); + g_clear_object (&app->log); if (context) g_option_context_free (context); return ret; diff --git a/src/ostree/ot-dump.c b/src/ostree/ot-dump.c index 670ccd6a..48b087af 100644 --- a/src/ostree/ot-dump.c +++ b/src/ostree/ot-dump.c @@ -118,8 +118,15 @@ dump_commit (GVariant *variant, g_print ("Version: %s\n", version); } - g_print ("\n"); - dump_indented_lines (subject); + if (subject[0]) + { + g_print ("\n"); + dump_indented_lines (subject); + } + else + { + g_print ("(no subject)\n"); + } if (body[0]) { diff --git a/src/ostree/ot-editor.c b/src/ostree/ot-editor.c index 4c29c81e..66c6e7be 100644 --- a/src/ostree/ot-editor.c +++ b/src/ostree/ot-editor.c @@ -22,9 +22,9 @@ #include "config.h" +#include "libglnx.h" #include "ot-editor.h" #include "libgsystem.h" -#include "libglnx.h" #include #include @@ -101,7 +101,8 @@ ot_editor_prompt (OstreeRepo *repo, goto out; } - ret = gs_file_load_contents_utf8 (file, cancellable, error); + ret = glnx_file_get_contents_utf8_at (AT_FDCWD, gs_file_get_path_cached (file), NULL, + cancellable, error); out: if (file) diff --git a/src/ostree/ot-main.c b/src/ostree/ot-main.c index 93f841dc..18c13239 100644 --- a/src/ostree/ot-main.c +++ b/src/ostree/ot-main.c @@ -27,9 +27,9 @@ #include #include +#include "ot-main.h" #include "ostree.h" #include "ot-admin-functions.h" -#include "ot-main.h" #include "otutil.h" static char *opt_repo; diff --git a/src/ostree/ot-main.h b/src/ostree/ot-main.h index 32620c52..3bb75243 100644 --- a/src/ostree/ot-main.h +++ b/src/ostree/ot-main.h @@ -22,8 +22,8 @@ #pragma once -#include "ostree.h" #include "libglnx.h" +#include "ostree.h" typedef enum { OSTREE_BUILTIN_FLAG_NONE = 0, diff --git a/tests/basic-test.sh b/tests/basic-test.sh index 5519764a..003df893 100755 --- a/tests/basic-test.sh +++ b/tests/basic-test.sh @@ -19,7 +19,7 @@ set -euo pipefail -echo "1..53" +echo "1..57" $OSTREE checkout test2 checkout-test2 echo "ok checkout" @@ -103,6 +103,12 @@ $OSTREE commit -b test2-no-parent -s '' --parent=none $test_tmpdir/checkout-test assert_streq $($OSTREE log test2-no-parent |grep '^commit' | wc -l) "1" echo "ok commit no parent" +cd ${test_tmpdir} +empty_rev=$($OSTREE commit -b test2-no-subject -s '' --timestamp="2005-10-29 12:43:29 +0000" $test_tmpdir/checkout-test2-4) +omitted_rev=$($OSTREE commit -b test2-no-subject-2 --timestamp="2005-10-29 12:43:29 +0000" $test_tmpdir/checkout-test2-4) +assert_streq $empty_rev $omitted_rev +echo "ok commit no subject" + cd ${test_tmpdir} $OSTREE commit -b test2-custom-parent -s '' $test_tmpdir/checkout-test2-4 $OSTREE commit -b test2-custom-parent -s '' $test_tmpdir/checkout-test2-4 @@ -179,13 +185,31 @@ cd ${test_tmpdir}/checkout-test2-4 $OSTREE commit -b test2 -s "no xattrs" --no-xattrs echo "ok commit with no xattrs" +# NB: The + is optional, but we need to make sure we support it cd ${test_tmpdir} cat > test-statoverride.txt < test-skiplist.txt < cow-mtime -assert_file_has_content cow-mtime 0 +assert_file_has_content cow-mtime 1 stat '--format=%Y' test2-checkout/baz/deeper > deeper-mtime -assert_file_has_content deeper-mtime 0 +assert_file_has_content deeper-mtime 1 echo "ok content mtime" cd ${test_tmpdir} @@ -381,7 +405,7 @@ echo "ok commit of fifo was rejected" cd ${test_tmpdir} rm repo2 -rf mkdir repo2 -${CMD_PREFIX} ostree --repo=repo2 init --mode=archive-z2 +${CMD_PREFIX} ostree --repo=repo2 init --mode=archive ${CMD_PREFIX} ostree --repo=repo2 pull-local repo rm -rf test2-checkout ${CMD_PREFIX} ostree --repo=repo2 checkout -U --disable-cache test2 test2-checkout @@ -397,6 +421,28 @@ assert_file_has_content test2-checkout/baz/cow moo assert_has_dir repo2/uncompressed-objects-cache echo "ok disable cache checkout" +cd ${test_tmpdir} +rm checkout-test2 -rf +$OSTREE checkout test2 checkout-test2 +date > checkout-test2/date.txt +rm repo/tmp/* -rf +export TEST_BOOTID=3072029c-8b10-60d1-d31b-8422eeff9b42 +if env OSTREE_REPO_TEST_ERROR=pre-commit OSTREE_BOOTID=${TEST_BOOTID} \ + $OSTREE commit -b test2 -s '' $test_tmpdir/checkout-test2 2>err.txt; then + assert_not_reached "Should have hit OSTREE_REPO_TEST_ERROR_PRE_COMMIT" +fi +assert_file_has_content err.txt OSTREE_REPO_TEST_ERROR_PRE_COMMIT +found_staging=0 +for d in $(find repo/tmp/ -maxdepth 1 -type d); do + bn=$(basename $d) + if test ${bn##staging-} != ${bn}; then + assert_str_match "${bn}" "^staging-${TEST_BOOTID}-" + found_staging=1 + fi +done +assert_streq "${found_staging}" 1 +echo "ok test error pre commit/bootid" + # Whiteouts cd ${test_tmpdir} mkdir -p overlay/baz/ @@ -468,5 +514,9 @@ cd .. if cmp timestamp-{orig,new}.txt; then assert_not_reached "failed to update mtime on repo" fi - echo "ok mtime updated" + +cd ${test_tmpdir} +$OSTREE init --mode=bare --repo=repo-extensions +assert_has_dir repo-extensions/extensions +echo "ok extensions dir" diff --git a/tests/glib.supp b/tests/glib.supp new file mode 100644 index 00000000..33449070 --- /dev/null +++ b/tests/glib.supp @@ -0,0 +1,535 @@ +# This GLib suppressions file is known to be used at least by: +# +# - rpm-software-management/libhif +# +# Please use the upstream verison in libhif for changes. +{ + gobject_init_1 + Memcheck:Leak + ... + fun:gobject_init +} +{ + g_type_register_static_1 + Memcheck:Leak + ... + fun:g_type_register_static +} +{ + g_type_register_fundamental + Memcheck:Leak + ... + fun:g_type_register_fundamental +} +{ + g_type_init_with_debug_flags + Memcheck:Leak + ... + fun:g_type_init_with_debug_flags +} +{ + g_type_class_ref_1 + Memcheck:Leak + ... + fun:type_iface_vtable_base_init_Wm + ... + fun:g_type_class_ref +} +{ + g_type_class_ref_2 + Memcheck:Leak + ... + fun:type_class_init_Wm + ... + fun:g_type_class_ref +} +{ + g_type_add_interface_static + Memcheck:Leak + ... + fun:g_type_add_interface_static +} +{ + g_param_spec_internal + Memcheck:Leak + ... + fun:g_type_class_ref + fun:g_type_create_instance + fun:g_param_spec_internal +} +{ + g_param_spec_enum + Memcheck:Leak + ... + fun:g_type_class_ref + fun:g_param_spec_enum +} +{ + g_param_spec_flags + Memcheck:Leak + ... + fun:g_type_class_ref + fun:g_param_spec_flags +} +{ + g_quark_from_static_string + Memcheck:Leak + ... + fun:g_quark_from_static_string +} +{ + g_quark_from_string + Memcheck:Leak + ... + fun:g_quark_from_string +} +{ + g_value_register_transform_func + Memcheck:Leak + ... + fun:g_value_register_transform_func +} +{ + test_run_seed + Memcheck:Leak + ... + fun:g_rand_new_with_seed_array + fun:test_run_seed + ... + fun:g_test_run_suite +} +{ + g_test_init + Memcheck:Leak + ... + fun:g_rand_new_with_seed_array + ... + fun:g_test_init +} +{ + g_intern_static_string + Memcheck:Leak + ... + fun:g_intern_static_string +} +{ + g_main_context_push_thread_default + Memcheck:Leak + ... + fun:g_queue_new + fun:g_main_context_push_thread_default +} +{ + g_main_context_push_thread_default_inlined + Memcheck:Leak + ... + fun:g_slice_alloc0 + fun:g_main_context_push_thread_default +} +{ + g_dbus_error_register_error + Memcheck:Leak + ... + fun:g_dbus_error_register_error +} +{ + g_param_spec_pool_insert + Memcheck:Leak + ... + fun:g_param_spec_pool_insert +} +{ + g_main_context_default + Memcheck:Leak + ... + fun:g_main_context_default +} +{ + g_main_context_check + Memcheck:Leak + ... + fun:g_ptr_array_add + fun:g_main_context_check +} +{ + g_test_run_suite + Memcheck:Leak + ... + fun:g_slist_copy + fun:g_test_run_suite_internal + fun:g_test_run_suite +} +{ + g_dbus_interface_info_cache_build + Memcheck:Leak + ... + fun:g_dbus_interface_info_cache_build +} +{ + g_cancellable_push_current + Memcheck:Leak + ... + fun:thread_memory_from_self + ... + fun:g_cancellable_push_current +} +{ + _g_io_module_get_default + Memcheck:Leak + ... + fun:g_io_module_new + fun:g_io_modules_scan_all_in_directory_with_scope + fun:_g_io_modules_ensure_loaded + fun:_g_io_module_get_default +} +{ + g_io_scheduler_push_job + Memcheck:Leak + ... + fun:init_scheduler + fun:g_once_impl + fun:g_io_scheduler_push_job +} +{ + g_io_scheduler_push_job_2 + Memcheck:Leak + ... + fun:g_system_thread_new + ... + fun:g_io_scheduler_push_job +} +{ + g_bus_get_sync__available_connections + Memcheck:Leak + ... + fun:g_hash_table_new + fun:initable_init + fun:g_initable_init + fun:g_bus_get_sync +} +{ + g_socket_connection_factory_register_type + Memcheck:Leak + ... + fun:g_socket_connection_factory_register_type +} +{ + g_test_add_vtable + Memcheck:Leak + ... + fun:g_test_add_vtable +} +{ + g_mutex_lock + Memcheck:Leak + ... + fun:g_mutex_impl_new + fun:g_mutex_get_impl + fun:g_mutex_lock +} +{ + g_thread_self + Memcheck:Leak + ... + fun:g_thread_self +} +{ + g_rec_mutex_lock + Memcheck:Leak + ... + fun:g_rec_mutex_impl_new + fun:g_rec_mutex_get_impl + fun:g_rec_mutex_lock +} +{ + test_case_run + Memcheck:Leak + ... + fun:g_malloc0 + fun:test_case_run + ... + fun:g_test_run_suite +} +{ + g_get_charset + Memcheck:Leak + ... + fun:g_get_charset +} +{ + g_test_run_suite__timer_new + Memcheck:Leak + ... + fun:g_timer_new + fun:test_case_run + ... + fun:g_test_run_suite +} +{ + g_test_run_suite__timer_new2 + Memcheck:Leak + ... + fun:g_timer_new + fun:test_case_run_suite_internal + ... + fun:g_test_run_suite +} +{ + g_test_run_suite__strconcat + Memcheck:Leak + ... + fun:g_strconcat + fun:test_case_run + ... + fun:g_test_run_suite + fun:g_test_run +} +{ + g_type_interface_add_prerequisite + Memcheck:Leak + ... + fun:g_type_interface_add_prerequisite +} +{ + + Memcheck:Leak + ... + fun:g_slist_copy + fun:g_test_run_suite_internal + ... + fun:g_test_run_suite +} +{ + g_set_prgname + Memcheck:Leak + ... + fun:g_set_prgname +} +{ + g_test_run_suite__strconcat_2 + Memcheck:Leak + ... + fun:g_strconcat + fun:g_test_run_suite_internal +} +{ + g_test_run_suite__strdup + Memcheck:Leak + ... + fun:g_strdup + fun:g_test_run_suite_internal +} +{ + g_private_get + Memcheck:Leak + ... + fun:g_private_get +} +{ + g_private_set + Memcheck:Leak + ... + fun:g_private_set +} +{ + g_static_mutex_get_mutex_impl + Memcheck:Leak + ... + fun:g_static_mutex_get_mutex_impl +} +{ + g_variant_type_info_unref + Memcheck:Leak + ... + fun:g_hash_table_remove + fun:g_variant_type_info_unref +} +{ + g_rw_lock_reader_lock + Memcheck:Leak + ... + fun:g_rw_lock_impl_new + fun:g_rw_lock_get_impl + fun:g_rw_lock_reader_lock +} +{ + g_child_watch_finalize__rt_sigaction + Memcheck:Param + rt_sigaction(act->sa_flags) + fun:__libc_sigaction + ... + fun:g_child_watch_finalize +} +{ + g_dbus_worker_new + Memcheck:Leak + fun:calloc + ... + fun:_g_dbus_worker_new +} +{ + gdbus_shared_thread_func + Memcheck:Leak + match-leak-kinds: definite + ... + fun:g_malloc + ... + fun:gdbus_shared_thread_func +} +{ + g_task_start_task_thread + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:g_malloc + fun:g_slice_alloc + fun:g_slice_alloc0 + ... + fun:g_thread_pool_push + fun:g_task_start_task_thread +} +{ + g_get_language_names + Memcheck:Leak + match-leak-kinds: definite + fun:calloc + fun:g_malloc0 + fun:g_get_language_names +} +{ + g_get_filename_charsets + Memcheck:Leak + match-leak-kinds: definite + ... + fun:g_get_filename_charsets + fun:g_filename_display_name +} +{ + g_main_current_source + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:g_malloc + ... + fun:g_main_current_source + fun:g_task_return + fun:g_task_thread_pool_thread +} +{ + g_once_init_enter + Memcheck:Leak + match-leak-kinds: definite + ... + fun:g_once_init_enter +} +{ + g_child_watch_source_new + Memcheck:Leak + match-leak-kinds: definite + ... + fun:g_thread_new + ... + fun:g_child_watch_source_new +} +{ + continue_writing_in_idle_cb + Memcheck:Leak + match-leak-kinds: definite + ... + fun:g_task_new + ... + fun:continue_writing_in_idle_cb + fun:g_main_context_dispatch +} +{ + g_main_current_source + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + ... + fun:g_main_current_source +} +{ + g_thread_pool_push + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + ... + fun:g_thread_pool_push +} +{ + leak_test_dbus_dispose + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + ... + fun:g_main_loop_run + fun:g_test_dbus_down +} +{ + leak_test_dbus_down + Memcheck:Leak + match-leak-kinds: definite + fun:calloc + fun:g_malloc0 + fun:g_main_loop_new + fun:g_test_dbus_down +} +{ + leak_socket_client_connect + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:g_malloc + fun:g_slice_alloc + fun:g_slice_alloc0 + fun:g_socket_client_connect_async + fun:g_socket_client_connect_to_uri_async +} +{ + leak_signal_handlers_disconnect_matched + Memcheck:Leak + match-leak-kinds: definite + fun:calloc + fun:g_malloc0 + ... + fun:g_slice_alloc + ... + fun:g_signal_handlers_disconnect_matched +} +{ + g_tls_connection_gnutls_init_priorities + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:g_malloc + fun:g_strdup + fun:g_tls_connection_gnutls_init_priorities +} +{ + g_tls_connection_gnutls_heisenbug_likely_same_as_above + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:g_malloc + fun:g_strdup + ... + fun:g_tls_client_connection_new +} +{ + g_unix_signal_add_full + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:g_malloc + ... + fun:g_thread_new + ... + fun:g_unix_signal_add_full +} +{ + glib_worker_1 + Memcheck:Leak + ... + fun:glib_worker_main +} diff --git a/tests/libostreetest.c b/tests/libostreetest.c index 58283368..81f743e9 100644 --- a/tests/libostreetest.c +++ b/tests/libostreetest.c @@ -28,11 +28,11 @@ /* This function hovers in a quantum superposition of horrifying and * beautiful. Future generations may interpret it as modern art. */ -static gboolean -run_libtest (const char *cmd, GError **error) +gboolean +ot_test_run_libtest (const char *cmd, GError **error) { gboolean ret = FALSE; - const char *builddir = g_getenv ("G_TEST_BUILDDIR"); + const char *srcdir = g_getenv ("G_TEST_SRCDIR"); int estatus; g_autoptr(GPtrArray) argv = g_ptr_array_new (); g_autoptr(GString) cmdstr = g_string_new (""); @@ -40,8 +40,8 @@ run_libtest (const char *cmd, GError **error) g_ptr_array_add (argv, "bash"); g_ptr_array_add (argv, "-c"); - g_string_append (cmdstr, ". "); - g_string_append (cmdstr, builddir); + g_string_append (cmdstr, "set -xeuo pipefail; . "); + g_string_append (cmdstr, srcdir); g_string_append (cmdstr, "/tests/libtest.sh; "); g_string_append (cmdstr, cmd); @@ -68,7 +68,7 @@ ot_test_setup_repo (GCancellable *cancellable, g_autoptr(GFile) repo_path = g_file_new_for_path ("repo"); glnx_unref_object OstreeRepo* ret_repo = NULL; - if (!run_libtest ("setup_test_repository", error)) + if (!ot_test_run_libtest ("setup_test_repository archive-z2", error)) goto out; ret_repo = ostree_repo_new (repo_path); @@ -91,7 +91,7 @@ ot_test_setup_sysroot (GCancellable *cancellable, g_autoptr(GFile) sysroot_path = g_file_new_for_path ("sysroot"); glnx_unref_object OstreeSysroot *ret_sysroot = NULL; - if (!run_libtest ("setup_os_repository \"archive-z2\" \"syslinux\"", error)) + if (!ot_test_run_libtest ("setup_os_repository \"archive-z2\" \"syslinux\"", error)) goto out; ret_sysroot = ostree_sysroot_new (sysroot_path); diff --git a/tests/libostreetest.h b/tests/libostreetest.h index eb9bb0b2..f777545e 100644 --- a/tests/libostreetest.h +++ b/tests/libostreetest.h @@ -27,6 +27,7 @@ G_BEGIN_DECLS +gboolean ot_test_run_libtest (const char *cmd, GError **error); OstreeRepo *ot_test_setup_repo (GCancellable *cancellable, GError **error); diff --git a/tests/libtest.sh b/tests/libtest.sh index 572f023c..2d064299 100755 --- a/tests/libtest.sh +++ b/tests/libtest.sh @@ -69,6 +69,7 @@ export TEST_GPG_KEYID_3="DF444D67" # this by copying locally. echo "Copying gpghome to ${test_tmpdir}" cp -a "${test_srcdir}/gpghome" ${test_tmpdir} +chmod -R u+w "${test_tmpdir}" export TEST_GPG_KEYHOME=${test_tmpdir}/gpghome export OSTREE_GPG_HOME=${test_tmpdir}/gpghome/trusted @@ -77,15 +78,28 @@ if test -n "${OT_TESTS_DEBUG:-}"; then fi if test -n "${OT_TESTS_VALGRIND:-}"; then - CMD_PREFIX="env G_SLICE=always-malloc valgrind -q --leak-check=full --num-callers=30 --suppressions=${test_srcdir}/ostree-valgrind.supp" + CMD_PREFIX="env G_SLICE=always-malloc OSTREE_SUPPRESS_SYNCFS=1 valgrind -q --error-exitcode=1 --leak-check=full --num-callers=30 --suppressions=${test_srcdir}/glib.supp --suppressions=${test_srcdir}/ostree.supp" else - CMD_PREFIX="env LD_PRELOAD=${test_builddir}/libreaddir-rand.so" + # In some cases the LD_PRELOAD may cause obscure problems, + # e.g. right now it breaks for me with -fsanitize=address, so + # let's allow users to skip it. + if test -z "${OT_SKIP_READDIR_RAND:-}"; then + CMD_PREFIX="env LD_PRELOAD=${test_builddir}/libreaddir-rand.so" + else + CMD_PREFIX="" + fi fi assert_streq () { test "$1" = "$2" || (echo 1>&2 "$1 != $2"; exit 1) } +assert_str_match () { + if ! echo "$1" | grep -E -q "$2"; then + (echo 1>&2 "$1 does not match regexp $2"; exit 1) + fi +} + assert_not_streq () { (! test "$1" = "$2") || (echo 1>&2 "$1 == $2"; exit 1) } @@ -100,13 +114,17 @@ assert_has_dir () { assert_not_has_file () { if test -f "$1"; then - echo 1>&2 "File '$1' exists"; exit 1 + sed -e 's/^/# /' < "$1" >&2 + echo 1>&2 "File '$1' exists" + exit 1 fi } assert_not_file_has_content () { if grep -q -e "$2" "$1"; then - echo 1>&2 "File '$1' incorrectly matches regexp '$2'"; exit 1 + sed -e 's/^/# /' < "$1" >&2 + echo 1>&2 "File '$1' incorrectly matches regexp '$2'" + exit 1 fi } @@ -118,13 +136,38 @@ assert_not_has_dir () { assert_file_has_content () { if ! grep -q -e "$2" "$1"; then - echo 1>&2 "File '$1' doesn't match regexp '$2'"; exit 1 + sed -e 's/^/# /' < "$1" >&2 + echo 1>&2 "File '$1' doesn't match regexp '$2'" + exit 1 + fi +} + +assert_symlink_has_content () { + if ! test -L "$1"; then + echo 1>&2 "File '$1' is not a symbolic link" + exit 1 + fi + if ! readlink "$1" | grep -q -e "$2"; then + sed -e 's/^/# /' < "$1" >&2 + echo 1>&2 "Symbolic link '$1' doesn't match regexp '$2'" + exit 1 fi } assert_file_empty() { if test -s "$1"; then - echo 1>&2 "File '$1' is not empty"; exit 1 + sed -e 's/^/# /' < "$1" >&2 + echo 1>&2 "File '$1' is not empty" + exit 1 + fi +} + +assert_files_hardlinked() { + f1=$(stat -c %i $1) + f2=$(stat -c %i $2) + if [ "$f1" != "$f2" ]; then + echo 1>&2 "Files '$1' and '$2' are not hardlinked" + exit 1 fi } @@ -370,6 +413,11 @@ skip_without_fuse () { exit 0 fi + if ! capsh --print | grep -q 'Bounding set.*[^a-z]cap_sys_admin'; then + echo "1..0 # SKIP No cap_sys_admin in bounding set, can't use FUSE" + exit 0 + fi + if ! [ -w /dev/fuse ]; then echo "1..0 # SKIP no write access to /dev/fuse" exit 0 diff --git a/tests/ostree-valgrind.supp b/tests/ostree-valgrind.supp deleted file mode 100644 index 00efd87b..00000000 --- a/tests/ostree-valgrind.supp +++ /dev/null @@ -1,199 +0,0 @@ -{ - g_type_init_with_debug_flags calloc - Memcheck:Leak - fun:calloc - ... - fun:g_type_init_with_debug_flags - ... -} - -{ - g_type_add_interface_static malloc - Memcheck:Leak - fun:malloc - ... - fun:g_type_add_interface_static - ... -} - -{ - g_type_add_interface_dynamic malloc - Memcheck:Leak - fun:malloc - ... - fun:g_type_add_interface_dynamic - ... -} - -{ - g_type_class_ref malloc - Memcheck:Leak - fun:malloc - ... - fun:g_type_class_ref - ... -} - -{ - g_type_register_dynamic malloc - Memcheck:Leak - fun:malloc - ... - fun:g_type_register_dynamic - ... -} - -{ - g_type_init_with_debug_flags malloc - Memcheck:Leak - fun:malloc - ... - fun:g_type_init_with_debug_flags - ... -} - -{ - g_type_init_with_debug_flags realloc - Memcheck:Leak - fun:realloc - ... - fun:g_type_init_with_debug_flags - ... -} - -{ - g_test_add_vtable malloc - Memcheck:Leak - fun:malloc - ... - fun:g_test_add_vtable - ... -} - -{ - g_test_init - Memcheck:Leak - fun:malloc - ... - fun:g_test_init - ... -} - -{ - g_type_register_static malloc - Memcheck:Leak - fun:malloc - ... - fun:g_type_register_static - ... -} - -{ - g_type_register_static realloc - Memcheck:Leak - fun:realloc - ... - fun:g_type_register_static - ... -} - -{ - g_type_register_fundamental never freed - Memcheck:Leak - fun:malloc - ... - fun:g_type_register_fundamental - ... -} - -{ - g_type_class_ref never finalized - Memcheck:Leak - fun:calloc - ... - fun:g_type_class_ref - ... -} - -{ - DBusGValue qdata - Memcheck:Leak - fun:realloc - fun:g_realloc - fun:g_type_set_qdata - fun:_dbus_g_value_types_init - ... -} - -{ - gettext conditional jump - Memcheck:Cond - fun:__GI___strcasecmp_l - fun:__gconv_open - fun:_nl_find_msg - fun:__dcigettext - ... -} - -{ - gettext uninitialized value - Memcheck:Value8 - fun:__GI___strcasecmp_l - fun:__gconv_open - fun:_nl_find_msg - fun:__dcigettext - ... -} - -{ - font config invalid reads - Memcheck:Addr4 - ... - fun:FcConfigParseAndLoad - ... -} - -{ - dynamic loader conditional jump - Memcheck:Cond - fun:index - fun:expand_dynamic_string_token - fun:_dl_map_object - fun:map_doit - fun:_dl_catch_error - fun:do_preload - fun:dl_main - ... -} - -{ - g_vfs_get_local - Memcheck:Leak - ... - fun:g_vfs_get_local - ... -} - -{ - _g_io_modules_ensure_loaded - Memcheck:Leak - ... - fun:_g_io_modules_ensure_loaded - ... -} - -{ - _g_io_module_get_default - Memcheck:Leak - ... - fun:_g_io_module_get_default - ... -} - -{ - _dl_allocate_tls - Memcheck:Leak - ... - fun:_dl_allocate_tls - ... -} diff --git a/tests/ostree.supp b/tests/ostree.supp new file mode 100644 index 00000000..b81ea51b --- /dev/null +++ b/tests/ostree.supp @@ -0,0 +1 @@ +# Use this to suppress "possibly lost" for global statics diff --git a/tests/readdir-rand.c b/tests/readdir-rand.c index 527d4a3a..afef387c 100644 --- a/tests/readdir-rand.c +++ b/tests/readdir-rand.c @@ -97,7 +97,6 @@ readdir (DIR *dirp) while (cache_another) { DirEntries *de; - GSList *l; errno = 0; ret = real_readdir (dirp); @@ -110,12 +109,12 @@ readdir (DIR *dirp) { if (g_random_boolean ()) { + struct dirent *copy; if (!de) { de = dir_entries_new (); g_hash_table_insert (direntcache, dirp, de); } - struct dirent *copy; copy = g_memdup (ret, sizeof (struct dirent)); g_ptr_array_add (de->entries, copy); } diff --git a/tests/test-admin-deploy-bootid-gc.sh b/tests/test-admin-deploy-bootid-gc.sh new file mode 100755 index 00000000..ba16f336 --- /dev/null +++ b/tests/test-admin-deploy-bootid-gc.sh @@ -0,0 +1,58 @@ +#!/bin/bash +# +# Copyright (C) 2016 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. + +set -euo pipefail + +. $(dirname $0)/libtest.sh + +# Exports OSTREE_SYSROOT so --sysroot not needed. +setup_os_repository "archive-z2" "syslinux" + +echo "1..1" + +${CMD_PREFIX} ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmaster/x86_64-runtime +rev=$(${CMD_PREFIX} ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime) +export rev +${CMD_PREFIX} ostree admin deploy --karg=root=LABEL=MOO --karg=quiet --os=testos testos:testos/buildmaster/x86_64-runtime +os_repository_new_commit + +rm sysroot/ostree/repo/tmp/* -rf +export TEST_BOOTID=4072029c-8b10-60d1-d31b-8422eeff9b42 +if env OSTREE_REPO_TEST_ERROR=pre-commit OSTREE_BOOTID=${TEST_BOOTID} \ + ${CMD_PREFIX} ostree admin deploy --karg=root=LABEL=MOO --karg=quiet --os=testos testos:testos/buildmaster/x86_64-runtime 2>err.txt; then + assert_not_reached "Should have hit OSTREE_REPO_TEST_ERROR_PRE_COMMIT" +fi +stagepath=$(ls -d sysroot/ostree/repo/tmp/staging-${TEST_BOOTID}-*) +assert_has_dir "${stagepath}" + +# We have an older failed stage, now use a new boot id + +export NEW_TEST_BOOTID=5072029c-8b10-60d1-d31b-8422eeff9b42 +if env OSTREE_REPO_TEST_ERROR=pre-commit OSTREE_BOOTID=${NEW_TEST_BOOTID} \ + ${CMD_PREFIX} ostree admin deploy --karg=root=LABEL=FOO --karg=quiet --os=testos testos:testos/buildmaster/x86_64-runtime 2>err.txt; then + assert_not_reached "Should have hit OSTREE_REPO_TEST_ERROR_PRE_COMMIT" +fi +newstagepath=$(ls -d sysroot/ostree/repo/tmp/staging-${NEW_TEST_BOOTID}-*) +assert_has_dir "${newstagepath}" +env OSTREE_BOOTID=${NEW_TEST_BOOTID} ${CMD_PREFIX} ostree admin deploy --karg=root=LABEL=MOO --karg=quiet --os=testos testos:testos/buildmaster/x86_64-runtime +newstagepath=$(ls -d sysroot/ostree/repo/tmp/staging-${NEW_TEST_BOOTID}-*) +assert_not_has_dir "${stagepath}" +assert_not_has_dir "${newstagepath}" + +echo "ok admin bootid GC" diff --git a/tests/test-basic-c.c b/tests/test-basic-c.c index d5dcc811..447c46ea 100644 --- a/tests/test-basic-c.c +++ b/tests/test-basic-c.c @@ -34,6 +34,143 @@ test_repo_is_not_system (gconstpointer data) g_assert (!ostree_repo_is_system (repo)); } +static GBytes * +input_stream_to_bytes (GInputStream *input) +{ + g_autoptr(GOutputStream) mem_out_stream = NULL; + g_autoptr(GError) error = NULL; + + if (input == NULL) + return g_bytes_new (NULL, 0); + + mem_out_stream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free); + g_output_stream_splice (mem_out_stream, + input, + G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, + NULL, + &error); + g_assert_no_error (error); + + return g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (mem_out_stream)); +} + +static void +test_raw_file_to_archive_z2_stream (gconstpointer data) +{ + OstreeRepo *repo = OSTREE_REPO (data); + g_autofree gchar *commit_checksum = NULL; + g_autoptr(GHashTable) reachable = NULL; + g_autoptr(GError) error = NULL; + /* branch name of the test repository, see setup_test_repository in libtest.sh */ + const gchar *rev = "test2"; + GHashTableIter iter; + GVariant *serialized_object; + guint checks = 0; + + ostree_repo_resolve_rev (repo, + rev, + FALSE, + &commit_checksum, + &error); + g_assert_no_error (error); + reachable = ostree_repo_traverse_new_reachable (); + ostree_repo_traverse_commit (repo, + commit_checksum, + -1, + &reachable, + NULL, + &error); + g_assert_no_error (error); + g_hash_table_iter_init (&iter, reachable); + while (g_hash_table_iter_next (&iter, (gpointer*)&serialized_object, NULL)) + { + const gchar *object_checksum; + OstreeObjectType object_type; + g_autoptr(GInputStream) input = NULL; + g_autoptr(GFileInfo) info = NULL; + g_autoptr(GVariant) xattrs = NULL; + g_autoptr(GBytes) input_bytes = NULL; + g_autoptr(GInputStream) mem_input = NULL; + g_autoptr(GInputStream) zlib_stream = NULL; + g_autoptr(GBytes) zlib_bytes = NULL; + g_autoptr(GInputStream) mem_zlib = NULL; + g_autoptr(GInputStream) input2 = NULL; + g_autoptr(GFileInfo) info2 = NULL; + g_autoptr(GVariant) xattrs2 = NULL; + g_autoptr(GBytes) input2_bytes = NULL; + + ostree_object_name_deserialize (serialized_object, &object_checksum, &object_type); + if (object_type != OSTREE_OBJECT_TYPE_FILE) + continue; + + ostree_repo_load_file (repo, + object_checksum, + &input, + &info, + &xattrs, + NULL, + &error); + g_assert_no_error (error); + + input_bytes = input_stream_to_bytes (input); + /* This is to simulate NULL input received from + * ostree_repo_load_file. Instead of creating the mem_input + * variable, I could also rewind the input stream and pass it to + * the function below, but this would assume that the input + * stream implements either the GSeekable or + * GFileDescriptorBased interface. */ + if (input != NULL) + mem_input = g_memory_input_stream_new_from_bytes (input_bytes); + ostree_raw_file_to_archive_z2_stream (mem_input, + info, + xattrs, + &zlib_stream, + NULL, + &error); + g_assert_no_error (error); + + zlib_bytes = input_stream_to_bytes (zlib_stream); + mem_zlib = g_memory_input_stream_new_from_bytes (zlib_bytes); + ostree_content_stream_parse (FALSE, + mem_zlib, + g_bytes_get_size (zlib_bytes), + FALSE, + &input2, + &info2, + &xattrs2, + NULL, + &error); + g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED); + g_clear_error (&error); + + g_seekable_seek (G_SEEKABLE (mem_zlib), + 0, + G_SEEK_SET, + NULL, + &error); + g_assert_no_error (error); + + ostree_content_stream_parse (TRUE, + mem_zlib, + g_bytes_get_size (zlib_bytes), + FALSE, + &input2, + &info2, + &xattrs2, + NULL, + &error); + g_assert_no_error (error); + + input2_bytes = input_stream_to_bytes (input2); + g_assert_true (g_bytes_equal (input_bytes, input2_bytes)); + g_assert_true (g_variant_equal (xattrs, xattrs2)); + /* TODO: Not sure how to compare fileinfos */ + ++checks; + } + /* to make sure we really tested the function */ + g_assert_cmpint (checks, >, 0); +} + int main (int argc, char **argv) { g_autoptr(GError) error = NULL; @@ -46,6 +183,7 @@ int main (int argc, char **argv) goto out; 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); return g_test_run(); out: diff --git a/tests/test-checksum.c b/tests/test-checksum.c index 25d3c37e..e7fd7be4 100644 --- a/tests/test-checksum.c +++ b/tests/test-checksum.c @@ -20,6 +20,7 @@ #include "config.h" +#include "libglnx.h" #include "libgsystem.h" #include #include diff --git a/tests/test-export.sh b/tests/test-export.sh index 1e81a2e9..856c4073 100755 --- a/tests/test-export.sh +++ b/tests/test-export.sh @@ -23,7 +23,7 @@ set -euo pipefail setup_test_repository "archive-z2" -echo '1..2' +echo '1..5' $OSTREE checkout test2 test2-co $OSTREE commit --no-xattrs -b test2-noxattrs -s "test2 without xattrs" --tree=dir=test2-co @@ -35,10 +35,39 @@ mkdir t (cd t && tar xf ../test2.tar) ${CMD_PREFIX} ostree --repo=repo diff --no-xattrs test2-noxattrs ./t > diff.txt assert_file_empty diff.txt -rm test2.tar diff.txt t -rf echo 'ok export gnutar diff (no xattrs)' +cd ${test_tmpdir} +${OSTREE} 'export' test2-noxattrs --subpath=baz -o test2-subpath.tar +mkdir t2 +(cd t2 && tar xf ../test2-subpath.tar) +${CMD_PREFIX} ostree --repo=repo diff --no-xattrs ./t2 ./t/baz > diff.txt +assert_file_empty diff.txt + +echo 'ok export --subpath gnutar diff (no xattrs)' + +cd ${test_tmpdir} +${OSTREE} 'export' test2-noxattrs --prefix=the-prefix/ -o test2-prefix.tar +mkdir t3 +(cd t3 && tar xf ../test2-prefix.tar) +${CMD_PREFIX} ostree --repo=repo diff --no-xattrs test2-noxattrs ./t3/the-prefix > diff.txt +assert_file_empty diff.txt + +echo 'ok export --prefix gnutar diff (no xattrs)' + +cd ${test_tmpdir} +${OSTREE} 'export' test2-noxattrs --subpath=baz --prefix=the-prefix/ -o test2-prefix-subpath.tar +mkdir t4 +(cd t4 && tar xf ../test2-prefix-subpath.tar) +${CMD_PREFIX} ostree --repo=repo diff --no-xattrs ./t4/the-prefix ./t/baz > diff.txt +${CMD_PREFIX} ostree --repo=repo diff --no-xattrs test2-noxattrs ./t3/the-prefix > diff.txt +assert_file_empty diff.txt + +echo 'ok export --prefix --subpath gnutar diff (no xattrs)' + +rm test2.tar test2-subpath.tar diff.txt t t2 t3 t4 -rf + cd ${test_tmpdir} ${OSTREE} 'export' test2 -o test2.tar ${OSTREE} commit -b test2-from-tar -s 'Import from tar' --tree=tar=test2.tar diff --git a/tests/test-libarchive-import.c b/tests/test-libarchive-import.c index 877fa77c..aaa3c37b 100644 --- a/tests/test-libarchive-import.c +++ b/tests/test-libarchive-import.c @@ -42,6 +42,8 @@ test_data_init (TestData *td) GError *error = NULL; struct archive *a = archive_write_new (); struct archive_entry *ae; + uid_t uid = getuid (); + gid_t gid = getgid (); td->tmpd = g_mkdtemp (g_strdup ("/var/tmp/test-libarchive-import-XXXXXX")); g_assert_cmpint (0, ==, chdir (td->tmpd)); @@ -59,12 +61,16 @@ test_data_init (TestData *td) ae = archive_entry_new (); archive_entry_set_pathname (ae, "/"); archive_entry_set_mode (ae, S_IFDIR | 0755); + archive_entry_set_uid (ae, uid); + archive_entry_set_gid (ae, gid); g_assert_cmpint (0, ==, archive_write_header (a, ae)); archive_entry_free (ae); ae = archive_entry_new (); archive_entry_set_pathname (ae, "/file"); archive_entry_set_mode (ae, S_IFREG | 0777); + archive_entry_set_uid (ae, uid); + archive_entry_set_gid (ae, gid); archive_entry_set_size (ae, 4); g_assert_cmpint (0, ==, archive_write_header (a, ae)); g_assert_cmpint (4, ==, archive_write_data (a, "foo\n", 4)); @@ -73,6 +79,8 @@ test_data_init (TestData *td) ae = archive_entry_new (); archive_entry_set_pathname (ae, "/devnull"); archive_entry_set_mode (ae, S_IFCHR | 0777); + archive_entry_set_uid (ae, uid); + archive_entry_set_gid (ae, gid); archive_entry_set_devmajor (ae, 1); archive_entry_set_devminor (ae, 3); g_assert_cmpint (0, ==, archive_write_header (a, ae)); @@ -81,6 +89,26 @@ test_data_init (TestData *td) ae = archive_entry_new (); archive_entry_set_pathname (ae, "/anotherfile"); archive_entry_set_mode (ae, S_IFREG | 0777); + archive_entry_set_uid (ae, uid); + archive_entry_set_gid (ae, gid); + archive_entry_set_size (ae, 4); + g_assert_cmpint (0, ==, archive_write_header (a, ae)); + g_assert_cmpint (4, ==, archive_write_data (a, "bar\n", 4)); + archive_entry_free (ae); + + ae = archive_entry_new (); + archive_entry_set_pathname (ae, "/etc"); + archive_entry_set_mode (ae, S_IFDIR | 0755); + archive_entry_set_uid (ae, uid); + archive_entry_set_gid (ae, gid); + g_assert_cmpint (0, ==, archive_write_header (a, ae)); + archive_entry_free (ae); + + ae = archive_entry_new (); + archive_entry_set_pathname (ae, "/etc/file"); + archive_entry_set_mode (ae, S_IFREG | 0777); + archive_entry_set_uid (ae, uid); + archive_entry_set_gid (ae, gid); archive_entry_set_size (ae, 4); g_assert_cmpint (0, ==, archive_write_header (a, ae)); g_assert_cmpint (4, ==, archive_write_data (a, "bar\n", 4)); @@ -123,6 +151,15 @@ spawn_cmdline (const char *cmd, GError **error) return TRUE; } +static void +test_archive_setup (int fd, struct archive *a) +{ + g_assert_cmpint (0, ==, lseek (fd, 0, SEEK_SET)); + g_assert_cmpint (0, ==, archive_read_support_format_all (a)); + g_assert_cmpint (0, ==, archive_read_support_filter_all (a)); + g_assert_cmpint (0, ==, archive_read_open_fd (a, fd, 8192)); +} + static void test_libarchive_noautocreate_empty (gconstpointer data) { @@ -132,10 +169,7 @@ test_libarchive_noautocreate_empty (gconstpointer data) OstreeRepoImportArchiveOptions opts = { 0, }; glnx_unref_object OstreeMutableTree *mtree = ostree_mutable_tree_new (); - g_assert_cmpint (0, ==, lseek (td->fd_empty, 0, SEEK_SET)); - g_assert_cmpint (0, ==, archive_read_support_format_all (a)); - g_assert_cmpint (0, ==, archive_read_support_filter_all (a)); - g_assert_cmpint (0, ==, archive_read_open_fd (a, td->fd_empty, 8192)); + test_archive_setup (td->fd_empty, a); (void)ostree_repo_import_archive_to_mtree (td->repo, &opts, a, mtree, NULL, NULL, &error); g_assert_no_error (error); @@ -153,10 +187,7 @@ test_libarchive_autocreate_empty (gconstpointer data) opts.autocreate_parents = 1; - g_assert_cmpint (0, ==, lseek (td->fd_empty, 0, SEEK_SET)); - g_assert_cmpint (0, ==, archive_read_support_format_all (a)); - g_assert_cmpint (0, ==, archive_read_support_filter_all (a)); - g_assert_cmpint (0, ==, archive_read_open_fd (a, td->fd_empty, 8192)); + test_archive_setup (td->fd_empty, a); (void)ostree_repo_import_archive_to_mtree (td->repo, &opts, a, mtree, NULL, NULL, &error); g_assert_no_error (error); @@ -172,68 +203,95 @@ test_libarchive_error_device_file (gconstpointer data) OstreeRepoImportArchiveOptions opts = { 0, }; glnx_unref_object OstreeMutableTree *mtree = ostree_mutable_tree_new (); - g_assert_cmpint (0, ==, lseek (td->fd, 0, SEEK_SET)); - g_assert_cmpint (0, ==, archive_read_support_format_all (a)); - g_assert_cmpint (0, ==, archive_read_support_filter_all (a)); - g_assert_cmpint (0, ==, archive_read_open_fd (a, td->fd, 8192)); + test_archive_setup (td->fd, a); (void)ostree_repo_import_archive_to_mtree (td->repo, &opts, a, mtree, NULL, NULL, &error); g_assert (error != NULL); } +static gboolean +skip_if_no_xattr (TestData *td) +{ + /* /var/tmp might actually be a tmpfs */ + if (setxattr (td->tmpd, "user.test-xattr-support", "yes", 4, 0) != 0) + { + int saved_errno = errno; + g_autofree gchar *message + = g_strdup_printf ("unable to setxattr on \"%s\": %s", + td->tmpd, g_strerror (saved_errno)); + g_test_skip (message); + return TRUE; + } + + return FALSE; +} + +static gboolean +import_write_and_ref (OstreeRepo *repo, + OstreeRepoImportArchiveOptions *opts, + struct archive *a, + const char *ref, + OstreeRepoCommitModifier *modifier, + GError **error) +{ + gboolean ret = FALSE; + glnx_unref_object GFile *root = NULL; + g_autofree char *commit_checksum = NULL; + glnx_unref_object OstreeMutableTree *mtree = ostree_mutable_tree_new (); + + if (!ostree_repo_prepare_transaction (repo, NULL, NULL, error)) + goto out; + + if (!ostree_repo_import_archive_to_mtree (repo, opts, a, mtree, modifier, + NULL, error)) + goto out; + + if (!ostree_repo_write_mtree (repo, mtree, &root, NULL, error)) + goto out; + + if (!ostree_repo_write_commit (repo, NULL, "", "", NULL, + OSTREE_REPO_FILE (root), + &commit_checksum, NULL, error)) + goto out; + + ostree_repo_transaction_set_ref (repo, NULL, ref, commit_checksum); + + if (!ostree_repo_commit_transaction (repo, NULL, NULL, error)) + goto out; + + ret = TRUE; +out: + return ret; +} + static void test_libarchive_ignore_device_file (gconstpointer data) { TestData *td = (void*)data; GError *error = NULL; - GCancellable *cancellable = NULL; struct archive *a = archive_read_new (); OstreeRepoImportArchiveOptions opts = { 0, }; - glnx_unref_object OstreeMutableTree *mtree = ostree_mutable_tree_new (); - glnx_unref_object GFile *root = NULL; - g_autofree char *commit_checksum = NULL; - if (setxattr (td->tmpd, "user.test-xattr-support", "yes", 4, 0) != 0) - { - int saved_errno = errno; - g_autofree gchar *message = g_strdup_printf ("unable to setxattr on \"%s\": %s", td->tmpd, g_strerror (saved_errno)); + if (skip_if_no_xattr (td)) + goto out; - g_test_skip (message); - goto out; - } - - g_assert_cmpint (0, ==, lseek (td->fd, 0, SEEK_SET)); - g_assert_cmpint (0, ==, archive_read_support_format_all (a)); - g_assert_cmpint (0, ==, archive_read_support_filter_all (a)); - g_assert_cmpint (0, ==, archive_read_open_fd (a, td->fd, 8192)); + test_archive_setup (td->fd, a); opts.ignore_unsupported_content = TRUE; - if (!ostree_repo_prepare_transaction (td->repo, NULL, cancellable, &error)) - goto out; - - if (!ostree_repo_import_archive_to_mtree (td->repo, &opts, a, mtree, NULL, NULL, &error)) - goto out; - - if (!ostree_repo_write_mtree (td->repo, mtree, &root, cancellable, &error)) - goto out; - - if (!ostree_repo_write_commit (td->repo, NULL, "", "", NULL, - OSTREE_REPO_FILE (root), - &commit_checksum, cancellable, &error)) - goto out; - - ostree_repo_transaction_set_ref (td->repo, NULL, "foo", commit_checksum); - - if (!ostree_repo_commit_transaction (td->repo, NULL, cancellable, &error)) + if (!import_write_and_ref (td->repo, &opts, a, "foo", NULL, &error)) goto out; + /* check contents */ if (!spawn_cmdline ("ostree --repo=repo ls foo file", &error)) goto out; if (!spawn_cmdline ("ostree --repo=repo ls foo anotherfile", &error)) goto out; + if (!spawn_cmdline ("ostree --repo=repo ls foo /etc/file", &error)) + goto out; + if (spawn_cmdline ("ostree --repo=repo ls foo devnull", &error)) g_assert_not_reached (); g_assert (error != NULL); @@ -243,6 +301,243 @@ test_libarchive_ignore_device_file (gconstpointer data) g_assert_no_error (error); } +static gboolean +check_ostree_convention (GError *error) +{ + if (!spawn_cmdline ("ostree --repo=repo ls bar file", &error)) + return FALSE; + + if (!spawn_cmdline ("ostree --repo=repo ls bar anotherfile", &error)) + return FALSE; + + if (!spawn_cmdline ("ostree --repo=repo ls bar /usr/etc/file", &error)) + return FALSE; + + if (spawn_cmdline ("ostree --repo=repo ls bar /etc/file", &error)) + g_assert_not_reached (); + g_assert (error != NULL); + g_clear_error (&error); + + if (spawn_cmdline ("ostree --repo=repo ls bar devnull", &error)) + g_assert_not_reached (); + g_assert (error != NULL); + g_clear_error (&error); + + return TRUE; +} + +static void +test_libarchive_ostree_convention (gconstpointer data) +{ + TestData *td = (void*)data; + GError *error = NULL; + struct archive *a = archive_read_new (); + OstreeRepoImportArchiveOptions opts = { 0, }; + + if (skip_if_no_xattr (td)) + goto out; + + test_archive_setup (td->fd, a); + + opts.autocreate_parents = TRUE; + opts.use_ostree_convention = TRUE; + opts.ignore_unsupported_content = TRUE; + + if (!import_write_and_ref (td->repo, &opts, a, "bar", NULL, &error)) + goto out; + + if (!check_ostree_convention (error)) + goto out; + + out: + g_assert_no_error (error); +} + +static GVariant* +xattr_cb (OstreeRepo *repo, + const char *path, + GFileInfo *file_info, + gpointer user_data) +{ + g_auto(GVariantBuilder) builder; + g_variant_builder_init (&builder, (GVariantType*)"a(ayay)"); + if (strcmp (path, "/anotherfile") == 0) + g_variant_builder_add (&builder, "(@ay@ay)", + g_variant_new_bytestring ("user.data"), + g_variant_new_bytestring ("mydata")); + return g_variant_ref_sink (g_variant_builder_end (&builder)); +} + +static void +test_libarchive_xattr_callback (gconstpointer data) +{ + TestData *td = (void*)data; + GError *error = NULL; + struct archive *a = archive_read_new (); + OstreeRepoImportArchiveOptions opts = { 0 }; + OstreeRepoCommitModifier *modifier = NULL; + char buf[7] = { 0 }; + + if (skip_if_no_xattr (td)) + goto out; + + modifier = ostree_repo_commit_modifier_new (0, NULL, NULL, NULL); + ostree_repo_commit_modifier_set_xattr_callback (modifier, xattr_cb, + NULL, NULL); + + test_archive_setup (td->fd, a); + + opts.ignore_unsupported_content = TRUE; + + if (!import_write_and_ref (td->repo, &opts, a, "baz", modifier, &error)) + goto out; + + /* check contents */ + if (!spawn_cmdline ("ostree --repo=repo checkout baz baz-checkout", &error)) + goto out; + + g_assert_cmpint (0, >, getxattr ("baz-checkout/file", "user.data", NULL, 0)); + g_assert_cmpint (ENODATA, ==, errno); + + if (getxattr ("baz-checkout/anotherfile", "user.data", buf, sizeof buf) < 0) + { + glnx_set_prefix_error_from_errno (&error, "%s", "getxattr"); + goto out; + } + + g_assert_cmpstr (buf, ==, "mydata"); + + out: + if (modifier) + ostree_repo_commit_modifier_unref (modifier); + g_assert_no_error (error); +} + +static GVariant* +path_cb (OstreeRepo *repo, + const char *path, + GFileInfo *file_info, + gpointer user_data) +{ + if (strcmp (path, "/etc/file") == 0) + *(gboolean*)user_data = TRUE; + return NULL; +} + +static void +entry_pathname_test_helper (gconstpointer data, gboolean on) +{ + TestData *td = (void*)data; GError *error = NULL; + struct archive *a = archive_read_new (); + OstreeRepoImportArchiveOptions opts = { 0, }; + OstreeRepoCommitModifier *modifier = NULL; + gboolean met_etc_file = FALSE; + + modifier = ostree_repo_commit_modifier_new (0, NULL, NULL, NULL); + ostree_repo_commit_modifier_set_xattr_callback (modifier, path_cb, + NULL, &met_etc_file); + + test_archive_setup (td->fd, a); + + opts.autocreate_parents = TRUE; + opts.use_ostree_convention = TRUE; + opts.ignore_unsupported_content = TRUE; + opts.callback_with_entry_pathname = on; + + if (!import_write_and_ref (td->repo, &opts, a, "bar", modifier, &error)) + goto out; + + /* the flag shouldn't have any effect on the final tree */ + if (!check_ostree_convention (error)) + goto out; + + if (!on && met_etc_file) + { + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Received callback with /etc/file"); + goto out; + } + + if (on && !met_etc_file) + { + g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Did not receive callback with /etc/file"); + goto out; + } + + out: + g_assert_no_error (error); +} + +static void +test_libarchive_no_use_entry_pathname (gconstpointer data) +{ + entry_pathname_test_helper (data, FALSE); +} + +static void +test_libarchive_use_entry_pathname (gconstpointer data) +{ + entry_pathname_test_helper (data, TRUE); +} + +static void +test_libarchive_selinux (gconstpointer data) +{ + TestData *td = (void*)data; + GError *error = NULL; + struct archive *a = archive_read_new (); + OstreeRepoImportArchiveOptions opts = { 0 }; + glnx_unref_object OstreeSePolicy *sepol = NULL; + OstreeRepoCommitModifier *modifier = NULL; + char buf[64] = { 0 }; + + if (skip_if_no_xattr (td)) + goto out; + + { + glnx_unref_object GFile *root = g_file_new_for_path ("/"); + + /* creation should always succeed */ + sepol = ostree_sepolicy_new (root, NULL, &error); + g_assert (sepol != NULL); + } + + if (ostree_sepolicy_get_name (sepol) == NULL) + { + g_test_skip ("SELinux disabled"); + goto out; + } + + modifier = ostree_repo_commit_modifier_new (0, NULL, NULL, NULL); + ostree_repo_commit_modifier_set_sepolicy (modifier, sepol); + + test_archive_setup (td->fd, a); + + opts.ignore_unsupported_content = TRUE; + + if (!import_write_and_ref (td->repo, &opts, a, "bob", modifier, &error)) + goto out; + + /* check contents */ + if (!spawn_cmdline ("ostree --repo=repo checkout bob bob-checkout", &error)) + goto out; + + if (getxattr ("bob-checkout/etc", "security.selinux", buf, sizeof buf) < 0) + { + glnx_set_prefix_error_from_errno (&error, "%s", "getxattr"); + goto out; + } + + buf[(sizeof buf) - 1] = '\0'; + g_assert_cmpstr (buf, ==, "system_u:object_r:etc_t:s0"); + + out: + if (modifier) + ostree_repo_commit_modifier_unref (modifier); + g_assert_no_error (error); +} + int main (int argc, char **argv) { TestData td = {NULL,}; @@ -256,10 +551,15 @@ int main (int argc, char **argv) g_test_add_data_func ("/libarchive/autocreate-empty", &td, test_libarchive_autocreate_empty); g_test_add_data_func ("/libarchive/error-device-file", &td, test_libarchive_error_device_file); g_test_add_data_func ("/libarchive/ignore-device-file", &td, test_libarchive_ignore_device_file); + g_test_add_data_func ("/libarchive/ostree-convention", &td, test_libarchive_ostree_convention); + g_test_add_data_func ("/libarchive/xattr-callback", &td, test_libarchive_xattr_callback); + g_test_add_data_func ("/libarchive/no-use-entry-pathname", &td, test_libarchive_no_use_entry_pathname); + g_test_add_data_func ("/libarchive/use-entry-pathname", &td, test_libarchive_use_entry_pathname); + g_test_add_data_func ("/libarchive/selinux", &td, test_libarchive_selinux); r = g_test_run(); - if (td.tmpd) + if (td.tmpd && g_getenv ("TEST_SKIP_CLEANUP") == NULL) (void) glnx_shutil_rm_rf_at (AT_FDCWD, td.tmpd, NULL, NULL); return r; } diff --git a/tests/test-libarchive.sh b/tests/test-libarchive.sh index 7309ffd2..0c579459 100755 --- a/tests/test-libarchive.sh +++ b/tests/test-libarchive.sh @@ -26,57 +26,95 @@ fi . $(dirname $0)/libtest.sh -echo "1..7" +echo "1..20" setup_test_repository "bare" + cd ${test_tmpdir} mkdir foo cd foo -echo hi > hi -ln -s hi hello -mkdir subdir -echo contents > subdir/more -mkdir subdir/1 -touch subdir/1/empty -mkdir subdir/2 -touch subdir/2/empty -echo not > subdir/2/notempty +mkdir -p usr/bin +echo contents > usr/bin/foo +touch usr/bin/foo0 +ln usr/bin/foo usr/bin/bar +ln usr/bin/foo0 usr/bin/bar0 +ln -s foo usr/bin/sl +mkdir -p usr/local/bin +ln usr/bin/foo usr/local/bin/baz +ln usr/bin/foo0 usr/local/bin/baz0 +ln usr/bin/sl usr/local/bin/slhl +touch usr/bin/setuidme +touch usr/bin/skipme tar -c -z -f ../foo.tar.gz . +find . | cpio -o -H newc > ../foo.cpio + cd .. -$OSTREE commit -s "from tar" -b test-tar --tree=tar=foo.tar.gz + +cat > statoverride.txt < skiplist.txt < otherfile -echo foo1 > foo -ln foo bar -tar czf ${test_tmpdir}/hardlinktest.tar.gz . -cd ${test_tmpdir} -$OSTREE commit -s 'hardlinks' -b test-hardlinks --tree=tar=hardlinktest.tar.gz -rm -rf hardlinktest -echo "ok hardlink commit" + # basic content check + assert_file_has_content usr/bin/foo contents + assert_file_has_content usr/bin/bar contents + assert_file_has_content usr/local/bin/baz contents + assert_file_empty usr/bin/foo0 + assert_file_empty usr/bin/bar0 + assert_file_empty usr/local/bin/baz0 + echo "ok $1 contents" -cd ${test_tmpdir} -$OSTREE checkout test-hardlinks test-hardlinks-checkout -cd test-hardlinks-checkout -assert_file_has_content foo foo1 -assert_file_has_content bar foo1 -echo "ok hardlink contents" + # hardlinks + assert_files_hardlinked usr/bin/foo usr/bin/bar + assert_files_hardlinked usr/bin/foo usr/local/bin/baz + echo "ok $1 hardlink" + assert_files_hardlinked usr/bin/foo0 usr/bin/bar0 + assert_files_hardlinked usr/bin/foo0 usr/local/bin/baz0 + echo "ok $1 hardlink to empty files" + + # symlinks + assert_symlink_has_content usr/bin/sl foo + assert_file_has_content usr/bin/sl contents + echo "ok $1 symlink" + # ostree checkout doesn't care if two symlinks are actually hardlinked + # together (which is fine). checking that it's also a symlink is good enough. + assert_symlink_has_content usr/local/bin/slhl foo + echo "ok $1 hardlink to symlink" + + # stat override + test -u usr/bin/setuidme + echo "ok $1 setuid" + + # skip list + test ! -f usr/bin/skipme + echo "ok $1 file skip" + + cd ${test_tmpdir} + rm -rf test-$1-checkout +} + +assert_valid_checkout tar +assert_valid_checkout cpio cd ${test_tmpdir} mkdir multicommit-files @@ -115,3 +153,4 @@ cd ${test_tmpdir} $OSTREE checkout partial partial-checkout cd partial-checkout assert_file_has_content subdir/original "original" +echo "ok tar partial commit contents" diff --git a/tests/test-parent.sh b/tests/test-parent.sh index 05b102a2..d1f5d7c9 100755 --- a/tests/test-parent.sh +++ b/tests/test-parent.sh @@ -21,6 +21,8 @@ set -euo pipefail . $(dirname $0)/libtest.sh +skip_without_user_xattrs + echo '1..2' setup_test_repository "archive-z2" diff --git a/tests/test-pull-c.c b/tests/test-pull-c.c new file mode 100644 index 00000000..d784312f --- /dev/null +++ b/tests/test-pull-c.c @@ -0,0 +1,132 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2016 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 + +#include "libostreetest.h" + +typedef struct { + OstreeRepo *repo; +} TestData; + +static void +test_data_init (TestData *td) +{ + GError *local_error = NULL; + GError **error = &local_error; + g_autofree char *http_address = NULL; + g_autofree char *repo_url = NULL; + + td->repo = ot_test_setup_repo (NULL, error); + if (!td->repo) + goto out; + + if (!ot_test_run_libtest ("setup_fake_remote_repo1 archive-z2", error)) + goto out; + + if (!g_file_get_contents ("httpd-address", &http_address, NULL, error)) + goto out; + + repo_url = g_strconcat (http_address, "/ostree/gnomerepo", NULL); + + { g_autoptr(GVariantBuilder) builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}")); + g_autoptr(GVariant) opts = NULL; + + g_variant_builder_add (builder, "{s@v}", "gpg-verify", g_variant_new_variant (g_variant_new_boolean (FALSE))); + opts = g_variant_ref_sink (g_variant_builder_end (builder)); + + if (!ostree_repo_remote_change (td->repo, NULL, OSTREE_REPO_REMOTE_CHANGE_ADD, + "origin", repo_url, opts, NULL, error)) + goto out; + } + + out: + g_assert_no_error (local_error); +} + +static void +test_pull_multi_nochange (gconstpointer data) +{ + GError *local_error = NULL; + GError **error = &local_error; + TestData *td = (void*)data; + char *refs[] = { "main", NULL }; + + if (!ostree_repo_pull (td->repo, "origin", (char**)&refs, 0, NULL, NULL, error)) + goto out; + if (!ostree_repo_pull (td->repo, "origin", (char**)&refs, 0, NULL, NULL, error)) + goto out; + if (!ostree_repo_pull (td->repo, "origin", (char**)&refs, 0, NULL, NULL, error)) + goto out; + + out: + g_assert_no_error (local_error); +} + +static void +test_pull_multi_error_then_ok (gconstpointer data) +{ + GError *local_error = NULL; + GError **error = &local_error; + + TestData *td = (void*)data; + char *ok_refs[] = { "main", NULL }; + char *bad_refs[] = { "nosuchbranch", NULL }; + + for (guint i = 0; i < 3; i++) + { + g_autoptr(GError) tmp_error = NULL; + if (!ostree_repo_pull (td->repo, "origin", (char**)&ok_refs, 0, NULL, NULL, error)) + goto out; + if (ostree_repo_pull (td->repo, "origin", (char**)&bad_refs, 0, NULL, NULL, &tmp_error)) + g_assert_not_reached (); + g_clear_error (&tmp_error); + if (ostree_repo_pull (td->repo, "origin", (char**)&bad_refs, 0, NULL, NULL, &tmp_error)) + g_assert_not_reached (); + g_clear_error (&tmp_error); + if (!ostree_repo_pull (td->repo, "origin", (char**)&ok_refs, 0, NULL, NULL, error)) + goto out; + } + + out: + g_assert_no_error (local_error); +} + +int main (int argc, char **argv) +{ + TestData td = {NULL,}; + int r; + + test_data_init (&td); + + g_test_init (&argc, &argv, NULL); + + g_test_add_data_func ("/test-pull-c/multi-nochange", &td, test_pull_multi_nochange); + g_test_add_data_func ("/test-pull-c/multi-ok-error-repeat", &td, test_pull_multi_error_then_ok); + + r = g_test_run(); + + return r; +} diff --git a/tests/test-pull-override-url.sh b/tests/test-pull-override-url.sh new file mode 100755 index 00000000..d81b3454 --- /dev/null +++ b/tests/test-pull-override-url.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# +# Copyright (C) 2016 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. + +set -euo pipefail + +. $(dirname $0)/libtest.sh + +setup_fake_remote_repo1 "archive-z2" + +echo '1..1' + +cd ${test_tmpdir} +# get a list of XX/XXXXXXX...XX.commit +find ostree-srv/gnomerepo/objects -name '*.commit' | cut -d/ -f4- | sort >${test_tmpdir}/original_commits + + +gnomerepo_url="$(cat httpd-address)/ostree/gnomerepo" +mkdir mirror-srv +cd mirror-srv +mkdir gnomerepo +${CMD_PREFIX} ostree --repo=gnomerepo init --mode "archive-z2" +${CMD_PREFIX} ostree --repo=gnomerepo remote add --set=gpg-verify=false origin ${gnomerepo_url} +${CMD_PREFIX} ostree --repo=gnomerepo pull --mirror --depth=-1 origin main + +find gnomerepo/objects -name '*.commit' | cut -d/ -f3- | sort >${test_tmpdir}/mirror_commits + +if ! cmp --quiet ${test_tmpdir}/original_commits ${test_tmpdir}/mirror_commits +then + assert_not_reached 'original repository and its mirror should have the same commits' +fi + +cd ${test_tmpdir} +mkdir mirror-httpd +cd mirror-httpd +ln -s ${test_tmpdir}/mirror-srv ostree +mirror_log="${test_tmpdir}/mirror_log" +${CMD_PREFIX} ostree trivial-httpd --log-file=${mirror_log} --autoexit --daemonize -p ${test_tmpdir}/mirror-httpd-port +port=$(cat ${test_tmpdir}/mirror-httpd-port) +echo "http://127.0.0.1:${port}" > ${test_tmpdir}/mirror-httpd-address + +cd ${test_tmpdir} +mirrorrepo_url="$(cat mirror-httpd-address)/ostree/gnomerepo" +mkdir repo +${CMD_PREFIX} ostree --repo=repo init +${CMD_PREFIX} ostree --repo=repo remote add --set=gpg-verify=false origin ${gnomerepo_url} +rm -rf ${test_tmpdir}/ostree-srv/gnomerepo +if ${CMD_PREFIX} ostree --repo=repo pull --depth=-1 origin main +then + assert_not_reached 'pulling from removed remote should have failed' +fi + +if ! ${CMD_PREFIX} ostree --repo=repo pull --depth=-1 --url=${mirrorrepo_url} origin main +then + cat ${mirror_log} + assert_not_reached 'fetching from the overridden URL should succeed' +fi +find repo/objects -name '*.commit' | cut -d/ -f3- | sort >${test_tmpdir}/repo_commits + +if ! cmp --quiet ${test_tmpdir}/original_commits ${test_tmpdir}/repo_commits +then + assert_not_reached 'original repository and its mirror should have the same commits' +fi + +echo "ok pull url override" diff --git a/tests/test-refs.sh b/tests/test-refs.sh index bcebae9e..3d229031 100755 --- a/tests/test-refs.sh +++ b/tests/test-refs.sh @@ -65,4 +65,35 @@ ${CMD_PREFIX} ostree refs --repo=repo | wc -l > refscount.delete3 assert_file_has_content refscount.delete3 "^3$" assert_not_file_has_content reflist '^test-1$' +#Add a few more commits, to test --create +${CMD_PREFIX} ostree --repo=repo commit --branch=ctest -m ctest -s ctest tree +${CMD_PREFIX} ostree --repo=repo commit --branch=foo/ctest -m ctest -s ctest tree + +${CMD_PREFIX} ostree --repo=repo refs | wc -l > refscount +assert_file_has_content refscount "^5$" + +if ${CMD_PREFIX} ostree --repo=repo refs --create=ctest-new; then + assert_not_reached "refs --create unexpectedly succeeded without specifying an existing ref!" +fi +if ${CMD_PREFIX} ostree --repo=repo refs ctest --create; then + assert_not_reached "refs --create unexpectedly succeeded without specifying the ref to create!" +fi +if ${CMD_PREFIX} ostree --repo=repo refs does-not-exist --create=ctest-new; then + assert_not_reached "refs --create unexpectedly succeeded for a prefix that doesn't exist!" +fi +if ${CMD_PREFIX} ostree --repo=repo refs ctest --create=foo; then + assert_not_reached "refs --create unexpectedly succeeded for a prefix that is already in use by a folder!" +fi +if ${CMD_PREFIX} ostree --repo=repo refs foo/ctest --create=ctest; then + assert_not_reached "refs --create unexpectedly succeeded in overwriting an existing prefix!" +fi + +#Check to see if any uncleaned tmp files were created after failed --create +${CMD_PREFIX} ostree --repo=repo refs | wc -l > refscount.create1 +assert_file_has_content refscount.create1 "^5$" + +${CMD_PREFIX} ostree --repo=repo refs ctest --create ctest-new +${CMD_PREFIX} ostree --repo=repo refs | wc -l > refscount.create2 +assert_file_has_content refscount.create2 "^6$" + echo "ok refs" diff --git a/tests/test-setuid.sh b/tests/test-setuid.sh deleted file mode 100755 index edf707df..00000000 --- a/tests/test-setuid.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash -# -# Copyright (C) 2013 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. - -set -euo pipefail - -echo "1..1" - -. $(dirname $0)/libtest.sh - -setup_test_repository "bare" - -cd ${test_tmpdir} -cat > test-statoverride.txt < expected-symbols.txt eu-readelf -a ${G_TEST_BUILDDIR}/.libs/libostree-1.so | grep 'FUNC.*GLOBAL.*DEFAULT.*@@LIBOSTREE_' | sed -e 's,^.* \(ostree_[A-Za-z0-9_]*\)@@LIBOSTREE_[0-9_.]*,\1,' |sort -u > found-symbols.txt diff -u expected-symbols.txt found-symbols.txt +echo "ok exports" -echo 'ok' +# cmd__private__ is private. The fetcher symbol should not have been made public. +grep -E -v '(ostree_cmd__private__)|(ostree_fetcher_config_flags_get_type)' found-symbols.txt > expected-documented.txt + +echo "Verifying all public symbols are documented:" +grep '^ostree_' ${G_TEST_SRCDIR}/apidoc/ostree-sections.txt |sort -u > found-documented.txt +diff -u expected-documented.txt found-documented.txt + +echo 'ok documented symbols' diff --git a/tests/test-sysroot.js b/tests/test-sysroot.js index 9468d2fb..7c31659d 100644 --- a/tests/test-sysroot.js +++ b/tests/test-sysroot.js @@ -37,7 +37,7 @@ function libtestExec(shellCode) { let testdatadir = GLib.getenv("G_TEST_SRCDIR"); let libtestPath = GLib.build_filenamev([testdatadir, 'libtest.sh']) let proc = GSystem.Subprocess.new_simple_argv(['bash', '-c', - '. ' + GLib.shell_quote(libtestPath) + '; ' + shellCode], + 'set -xeuo pipefail; . ' + GLib.shell_quote(libtestPath) + '; ' + shellCode], GSystem.SubprocessStreamDisposition.INHERIT, GSystem.SubprocessStreamDisposition.INHERIT, null);