Merge tag 'upstream/2016.3'

Upstream version 2016.3
This commit is contained in:
Simon McVittie 2016-03-11 08:08:06 +00:00
commit 56b6994e39
166 changed files with 4714 additions and 1869 deletions

View File

@ -1,121 +0,0 @@
Submitting patches
------------------
You can:
1. Send mail to ostree-list@gnome.org, with the patch attached
1. Submit a pull request against https://github.com/GNOME/ostree
1. Attach them to https://bugzilla.gnome.org/
Please look at "git log" and match the commit log style.
Running the test suite
----------------------
Currently, ostree uses https://wiki.gnome.org/GnomeGoals/InstalledTests
To run just ostree's tests:
./configure ... --enable-installed-tests
gnome-desktop-testing-runner -p 0 ostree/
Also, there is a regular:
make check
That runs a different set of tests.
Coding style
------------
Indentation is GNU. Files should start with the appropriate mode lines.
Use GCC `__attribute__((cleanup))` wherever possible. If interacting
with a third party library, try defining local cleanup macros.
Use GError and GCancellable where appropriate.
Prefer returning `gboolean` to signal success/failure, and have output
values as parameters.
Prefer linear control flow inside functions (aside from standard
loops). In other words, avoid "early exits" or use of `goto` besides
`goto out;`.
This is an example of an "early exit":
static gboolean
myfunc (...)
{
gboolean ret = FALSE;
/* some code */
/* some more code */
if (condition)
return FALSE;
/* some more code */
ret = TRUE;
out:
return ret;
}
If you must shortcut, use:
if (condition)
{
ret = TRUE;
goto out;
}
A consequence of this restriction is that you are encouraged to avoid
deep nesting of loops or conditionals. Create internal static helper
functions, particularly inside loops. For example, rather than:
while (condition)
{
/* some code */
if (condition)
{
for (i = 0; i < somevalue; i++)
{
if (condition)
{
/* deeply nested code */
}
/* more nested code */
}
}
}
Instead do this:
static gboolean
helperfunc (..., GError **error)
{
if (condition)
{
/* deeply nested code */
}
/* more nested code */
return ret;
}
while (condition)
{
/* some code */
if (!condition)
continue;
for (i = 0; i < somevalue; i++)
{
if (!helperfunc (..., i, error))
goto out;
}
}

1
CONTRIBUTING.md Symbolic link
View File

@ -0,0 +1 @@
docs/CONTRIBUTING.md

View File

@ -182,7 +182,10 @@ pkgconfig_DATA += src/libostree/ostree-1.pc
gpgreadme_DATA = src/libostree/README-gpg gpgreadme_DATA = src/libostree/README-gpg
gpgreadmedir = $(pkgdatadir)/trusted.gpg.d gpgreadmedir = $(pkgdatadir)/trusted.gpg.d
EXTRA_DIST += src/libostree/README-gpg src/libostree/bupsplit.h EXTRA_DIST += src/libostree/README-gpg src/libostree/bupsplit.h \
src/libostree/ostree-enumtypes.h.template \
src/libostree/ostree-enumtypes.c.template \
src/libostree/ostree-deployment-private.h
install-mkdir-remotes-d-hook: install-mkdir-remotes-d-hook:
mkdir -p $(DESTDIR)$(sysconfdir)/ostree/remotes.d mkdir -p $(DESTDIR)$(sysconfdir)/ostree/remotes.d

58
Makefile-man.am Normal file
View File

@ -0,0 +1,58 @@
# Makefile for man/
#
# Copyright (C) 2016 Colin Walters <walters@verbum.org>
#
# 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.
if ENABLE_MAN
man1_files = ostree.1 ostree-admin-cleanup.1 ostree-admin-config-diff.1 ostree-admin-deploy.1 ostree-admin-init-fs.1 ostree-admin-instutil.1 ostree-admin-os-init.1 ostree-admin-status.1 ostree-admin-set-origin.1 ostree-admin-switch.1 ostree-admin-undeploy.1 ostree-admin-upgrade.1 ostree-admin.1 ostree-cat.1 ostree-checkout.1 ostree-checksum.1 ostree-commit.1 ostree-export.1 ostree-gpg-sign.1 ostree-config.1 ostree-diff.1 ostree-fsck.1 ostree-init.1 ostree-log.1 ostree-ls.1 ostree-prune.1 ostree-pull-local.1 ostree-pull.1 ostree-refs.1 ostree-remote.1 ostree-reset.1 ostree-rev-parse.1 ostree-show.1 ostree-summary.1 ostree-static-delta.1 ostree-trivial-httpd.1
if BUILDOPT_FUSE
man1_files += rofiles-fuse.1
endif
man5_files = ostree.repo.5 ostree.repo-config.5
man1_MANS = $(addprefix man/,$(man1_files))
man5_MANS = $(addprefix man/,$(man5_files))
EXTRA_DIST += $(man1_MANS) $(man5_MANS)
XSLT_STYLESHEET = http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl
XSLTPROC_FLAGS = \
--nonet \
--stringparam man.output.quietly 1 \
--stringparam funcsynopsis.style ansi \
--stringparam man.th.extra1.suppress 1 \
--stringparam man.authors.section.enabled 0 \
--stringparam man.copyright.section.enabled 0
XSLTPROC_MAN = $(XSLTPROC) $(XSLTPROC_FLAGS)
%.1: %.xml
$(AM_V_GEN) $(XSLTPROC_MAN) --output $@ $(XSLT_STYLESHEET) $<
%.5: %.xml
$(AM_V_GEN) $(XSLTPROC_MAN) --output $@ $(XSLT_STYLESHEET) $<
CLEANFILES += \
$(man1_MANS) \
$(man5_MANS) \
$(NULL)
endif

View File

@ -28,6 +28,7 @@ ostree_SOURCES = src/ostree/main.c \
src/ostree/ot-builtin-checksum.c \ src/ostree/ot-builtin-checksum.c \
src/ostree/ot-builtin-commit.c \ src/ostree/ot-builtin-commit.c \
src/ostree/ot-builtin-diff.c \ src/ostree/ot-builtin-diff.c \
src/ostree/ot-builtin-export.c \
src/ostree/ot-builtin-fsck.c \ src/ostree/ot-builtin-fsck.c \
src/ostree/ot-builtin-gpg-sign.c \ src/ostree/ot-builtin-gpg-sign.c \
src/ostree/ot-builtin-init.c \ src/ostree/ot-builtin-init.c \
@ -89,6 +90,8 @@ ostree_SOURCES += \
src/ostree/parse-datetime.c: src/ostree/parse-datetime.y Makefile src/ostree/parse-datetime.c: src/ostree/parse-datetime.y Makefile
$(AM_V_GEN) $(YACC) $< -o $@ $(AM_V_GEN) $(YACC) $< -o $@
EXTRA_DIST += src/ostree/parse-datetime.y
CLEANFILES += src/ostree/parse-datetime.c
ostree_bin_shared_cflags = $(AM_CFLAGS) -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree -I$(srcdir)/src/ostree \ ostree_bin_shared_cflags = $(AM_CFLAGS) -I$(srcdir)/src/libotutil -I$(srcdir)/src/libostree -I$(srcdir)/src/ostree \
$(NULL) $(NULL)
@ -105,3 +108,8 @@ ostree_SOURCES += \
ostree_CFLAGS += $(OT_INTERNAL_SOUP_CFLAGS) ostree_CFLAGS += $(OT_INTERNAL_SOUP_CFLAGS)
ostree_LDADD += $(OT_INTERNAL_SOUP_LIBS) ostree_LDADD += $(OT_INTERNAL_SOUP_LIBS)
endif endif
if USE_LIBARCHIVE
ostree_CFLAGS += $(OT_DEP_LIBARCHIVE_CFLAGS)
ostree_LDADD += $(OT_DEP_LIBARCHIVE_LIBS)
endif

View File

@ -27,6 +27,7 @@ testfiles = test-basic \
test-remote-add \ test-remote-add \
test-remote-gpg-import \ test-remote-gpg-import \
test-commit-sign \ test-commit-sign \
test-export \
test-help \ test-help \
test-libarchive \ test-libarchive \
test-pull-archive-z \ test-pull-archive-z \
@ -61,6 +62,11 @@ testfiles = test-basic \
test-auto-summary \ test-auto-summary \
test-prune \ test-prune \
$(NULL) $(NULL)
if BUILDOPT_FUSE
testfiles += test-rofiles-fuse
endif
insttest_SCRIPTS = $(addprefix tests/,$(testfiles:=.sh)) insttest_SCRIPTS = $(addprefix tests/,$(testfiles:=.sh))
# This one uses corrupt-repo-ref.js # This one uses corrupt-repo-ref.js
@ -79,6 +85,8 @@ insttest_DATA = tests/archive-test.sh \
tests/test-basic-user.sh \ tests/test-basic-user.sh \
tests/test-local-pull.sh \ tests/test-local-pull.sh \
tests/corrupt-repo-ref.js \ tests/corrupt-repo-ref.js \
tests/pre-endian-deltas-repo-big.tar.xz \
tests/pre-endian-deltas-repo-little.tar.xz \
$(NULL) $(NULL)
insttest_SCRIPTS += \ insttest_SCRIPTS += \
@ -134,6 +142,10 @@ TESTS = tests/test-varint tests/test-ot-unix-utils tests/test-bsdiff tests/test-
tests/test-keyfile-utils tests/test-ot-opt-utils tests/test-ot-tool-util \ 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-gpg-verify-result tests/test-checksum tests/test-lzma tests/test-rollsum
if USE_LIBARCHIVE
TESTS += tests/test-libarchive-import
endif
check_PROGRAMS = $(TESTS) check_PROGRAMS = $(TESTS)
TESTS_ENVIRONMENT = \ TESTS_ENVIRONMENT = \
G_TEST_SRCDIR=$(abs_srcdir)/tests \ G_TEST_SRCDIR=$(abs_srcdir)/tests \
@ -166,6 +178,10 @@ tests_test_checksum_SOURCES = src/libostree/ostree-core.c tests/test-checksum.c
tests_test_checksum_CFLAGS = $(TESTS_CFLAGS) $(libglnx_cflags) tests_test_checksum_CFLAGS = $(TESTS_CFLAGS) $(libglnx_cflags)
tests_test_checksum_LDADD = $(TESTS_LDADD) tests_test_checksum_LDADD = $(TESTS_LDADD)
tests_test_libarchive_import_SOURCES = tests/test-libarchive-import.c
tests_test_libarchive_import_CFLAGS = $(TESTS_CFLAGS) $(libglnx_cflags)
tests_test_libarchive_import_LDADD = $(TESTS_LDADD)
tests_test_keyfile_utils_CFLAGS = $(TESTS_CFLAGS) tests_test_keyfile_utils_CFLAGS = $(TESTS_CFLAGS)
tests_test_keyfile_utils_LDADD = $(TESTS_LDADD) tests_test_keyfile_utils_LDADD = $(TESTS_LDADD)

View File

@ -32,7 +32,7 @@ DISTCHECK_CONFIGURE_FLAGS += --enable-gtk-doc --disable-maintainer-mode
SUBDIRS += . SUBDIRS += .
if ENABLE_GTK_DOC if ENABLE_GTK_DOC
SUBDIRS += doc SUBDIRS += apidoc
endif endif
EXTRA_DIST += autogen.sh COPYING README.md EXTRA_DIST += autogen.sh COPYING README.md
@ -43,8 +43,8 @@ OT_INTERNAL_SOUP_CFLAGS = $(OT_DEP_SOUP_CFLAGS)
OT_INTERNAL_SOUP_LIBS = $(OT_DEP_SOUP_LIBS) OT_INTERNAL_SOUP_LIBS = $(OT_DEP_SOUP_LIBS)
# This canonicalizes the PKG_CHECK_MODULES or AM_PATH_GPGME results # This canonicalizes the PKG_CHECK_MODULES or AM_PATH_GPGME results
OT_INTERNAL_GPGME_CFLAGS = $(OT_DEP_GPGME_CFLAGS) $(GPGME_CFLAGS) OT_INTERNAL_GPGME_CFLAGS = $(OT_DEP_GPGME_CFLAGS) $(GPGME_PTHREAD_CFLAGS)
OT_INTERNAL_GPGME_LIBS = $(OT_DEP_GPGME_LIBS) $(GPGME_LIBS) OT_INTERNAL_GPGME_LIBS = $(OT_DEP_GPGME_LIBS) $(GPGME_PTHREAD_LIBS)
if BUILDOPT_INTROSPECTION if BUILDOPT_INTROSPECTION
include $(INTROSPECTION_MAKEFILE) include $(INTROSPECTION_MAKEFILE)
@ -68,8 +68,10 @@ include Makefile-otutil.am
include Makefile-libostree.am include Makefile-libostree.am
include Makefile-ostree.am include Makefile-ostree.am
include Makefile-switchroot.am include Makefile-switchroot.am
include src/rofiles-fuse/Makefile-inc.am
include Makefile-tests.am include Makefile-tests.am
include Makefile-boot.am include Makefile-boot.am
include Makefile-man.am
release-tag: release-tag:
git tag -m "Release $(VERSION)" v$(VERSION) git tag -m "Release $(VERSION)" v$(VERSION)

View File

@ -1,31 +1,48 @@
OSTree OSTree
====== ======
New! See the docs online at [Read The Docs (OSTree)](https://ostree.readthedocs.org/en/latest/ )
-----
OSTree is a tool that combines a "git-like" model for committing and OSTree is a tool that combines a "git-like" model for committing and
downloading bootable filesystem trees, along with a layer for downloading bootable filesystem trees, along with a layer for
deploying them and managing the bootloader configuration. deploying them and managing the bootloader configuration.
Traditional package managers (dpkg/rpm) build filesystem trees on the OSTree is like git in that it checksums individual files and has a
client side. In contrast, the primary focus of OSTree is on content-addressed-object store. It's unlike git in that it "checks
replicating trees composed on a server. out" the files via hardlinks, and they should thus be immutable.
Therefore, another way to think of OSTree is that it's just a more
polished version of
[Linux VServer hardlinks](http://linux-vserver.org/index.php?title=util-vserver:Vhashify&oldid=2285).
**Features:** **Features:**
- Atomic upgrades and rollback - Atomic upgrades and rollback for the system
- GPG signatures and "pinned TLS" support - Replicating content incrementally over HTTP via GPG signatures and "pinned TLS" support
- Support for parallel installing more than just 2 bootable roots - Support for parallel installing more than just 2 bootable roots
- Binary history on the server side - Binary history on the server side (and client)
- Introspectable shared library API for build and deployment systems - Introspectable shared library API for build and deployment systems
This last point is important - you should think of the OSTree command
line as effectively a "demo" for the shared library. The intent is that
package managers, system upgrade tools, container build tools and the like
use OSTree as a "deduplicating hardlink store".
Projects using OSTree Projects using OSTree
--------------------- ---------------------
[rpm-ostree](https://github.com/projectatomic/rpm-ostree) is a tool [rpm-ostree](https://github.com/projectatomic/rpm-ostree) is a tool
that uses OSTree as a shared library, and supports committing RPMs that uses OSTree as a shared library, and supports committing RPMs
into an OSTree repository, and deploying them on the client. into an OSTree repository, and deploying them on the client. This is
appropriate for "fixed purpose" systems. There is in progress work
for more sophisticated hybrid models, deeply integrating the RPM
packaging with OSTree.
[Project Atomic](http://www.projectatomic.io/) uses rpm-ostree [Project Atomic](http://www.projectatomic.io/) uses rpm-ostree to
to provide a minimal host for Docker formatted Linux containers. 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 [xdg-app](https://github.com/alexlarsson/xdg-app) uses OSTree
for desktop application containers. for desktop application containers.
@ -46,8 +63,8 @@ versions support extended validation using
However, in order to build from a git clone, you must update the However, in order to build from a git clone, you must update the
submodules. If you're packaging OSTree and want a tarball, I submodules. If you're packaging OSTree and want a tarball, I
recommend using a "recursive git archive" script. There are several recommend using a "recursive git archive" script. There are several
available online; [this available online;
code](https://git.gnome.org/browse/ostree/tree/packaging/Makefile.dist-packaging#n11) [this code](https://git.gnome.org/browse/ostree/tree/packaging/Makefile.dist-packaging#n11)
in OSTree is an example. in OSTree is an example.
Once you have a git clone or recursive archive, building is the Once you have a git clone or recursive archive, building is the
@ -63,12 +80,11 @@ make install DESTDIR=/path/to/dest
More documentation More documentation
------------------ ------------------
New! See the docs online at [Read The Docs (OSTree)](https://ostree.readthedocs.org/en/latest/ )
Some more information is available on the old wiki page: Some more information is available on the old wiki page:
https://wiki.gnome.org/Projects/OSTree https://wiki.gnome.org/Projects/OSTree
The intent is for that wiki page content to be migrated into Markdown
in this git repository.
Contributing Contributing
------------ ------------

View File

View File

@ -95,11 +95,6 @@ HTML_IMAGES=
# Extra SGML files that are included by $(DOC_MAIN_SGML_FILE). # Extra SGML files that are included by $(DOC_MAIN_SGML_FILE).
# e.g. content_files=running.sgml building.sgml changes-2.0.sgml # e.g. content_files=running.sgml building.sgml changes-2.0.sgml
content_files= \ content_files= \
overview.xml \
repo.xml \
deployment.xml \
atomic-upgrades.xml \
adapting-existing.xml \
$(NULL) $(NULL)
# SGML files where gtk-doc abbrevations (#GtkWidget) are expanded # SGML files where gtk-doc abbrevations (#GtkWidget) are expanded
@ -116,53 +111,13 @@ expand_content_files= \
# e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib) # e.g. GTKDOC_LIBS=$(top_builddir)/gtk/$(gtktargetlib)
GTKDOC_LIBS= GTKDOC_LIBS=
# Hacks around gtk-doc brokenness for out of tree builds
ostree-sections.txt: $(srcdir)/ostree-sections.txt
cp $< $@
version.xml: version.xml:
echo -n $(VERSION) > "$@" echo -n $(VERSION) > "$@"
# This includes the standard gtk-doc make rules, copied by gtkdocize. # This includes the standard gtk-doc make rules, copied by gtkdocize.
include $(top_srcdir)/gtk-doc.make include $(top_srcdir)/gtk-doc.make
man1_MANS = ostree.1 ostree-admin-cleanup.1 ostree-admin-config-diff.1 ostree-admin-deploy.1 ostree-admin-init-fs.1 ostree-admin-instutil.1 ostree-admin-os-init.1 ostree-admin-status.1 ostree-admin-set-origin.1 ostree-admin-switch.1 ostree-admin-undeploy.1 ostree-admin-upgrade.1 ostree-admin.1 ostree-cat.1 ostree-checkout.1 ostree-checksum.1 ostree-commit.1 ostree-gpg-sign.1 ostree-config.1 ostree-diff.1 ostree-fsck.1 ostree-init.1 ostree-log.1 ostree-ls.1 ostree-prune.1 ostree-pull-local.1 ostree-pull.1 ostree-refs.1 ostree-remote.1 ostree-reset.1 ostree-rev-parse.1 ostree-show.1 ostree-summary.1 ostree-static-delta.1 ostree-trivial-httpd.1
man5_MANS = ostree.repo.5 ostree.repo-config.5
if ENABLE_GTK_DOC
XSLTPROC_FLAGS = \
--nonet \
--stringparam man.output.quietly 1 \
--stringparam funcsynopsis.style ansi \
--stringparam man.th.extra1.suppress 1 \
--stringparam man.authors.section.enabled 0 \
--stringparam man.copyright.section.enabled 0
XSLTPROC_MAN = \
$(XSLTPROC) $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl
.xml.1:
$(AM_V_GEN) $(XSLTPROC_MAN) $<
.xml.5:
$(AM_V_GEN) $(XSLTPROC_MAN) $<
CLEANFILES += \
$(man1_MANS) \
$(man5_MANS) \
$(NULL)
endif # ENABLE_GTK_DOC
MAN_IN_FILES = \
$(man1_MANS:.1=.xml) \
$(man5_MANS:.5=.xml) \
$(NULL)
EXTRA_DIST += \ EXTRA_DIST += \
$(MAN_IN_FILES) \
version.xml \ version.xml \
ostree.xml \
ostree-sections.txt \ ostree-sections.txt \
$(NULL) $(NULL)

View File

@ -7,16 +7,10 @@
]> ]>
<book id="index"> <book id="index">
<bookinfo> <bookinfo>
<title>OSTree Manual</title> <title>OSTree API references</title>
<releaseinfo>for OSTree &version;</releaseinfo> <releaseinfo>for OSTree &version;</releaseinfo>
</bookinfo> </bookinfo>
<xi:include href="overview.xml"/>
<xi:include href="repo.xml"/>
<xi:include href="deployment.xml"/>
<xi:include href="atomic-upgrades.xml"/>
<xi:include href="adapting-existing.xml"/>
<chapter xml:id="reference"> <chapter xml:id="reference">
<title>API Reference</title> <title>API Reference</title>
<xi:include href="xml/libostree-core.xml"/> <xi:include href="xml/libostree-core.xml"/>

View File

@ -220,6 +220,7 @@ ostree_repo_new_for_sysroot_path
ostree_repo_new_default ostree_repo_new_default
ostree_repo_open ostree_repo_open
ostree_repo_set_disable_fsync ostree_repo_set_disable_fsync
ostree_repo_get_disable_fsync
ostree_repo_is_system ostree_repo_is_system
ostree_repo_is_writable ostree_repo_is_writable
ostree_repo_create ostree_repo_create

View File

@ -1,5 +1,5 @@
AC_PREREQ([2.63]) AC_PREREQ([2.63])
AC_INIT([ostree], [2016.1], [walters@verbum.org]) AC_INIT([ostree], [2016.3], [walters@verbum.org])
AC_CONFIG_HEADER([config.h]) AC_CONFIG_HEADER([config.h])
AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_AUX_DIR([build-aux])
@ -106,17 +106,19 @@ AM_CONDITIONAL(BUILDOPT_INTROSPECTION, test "x$found_introspection" = xyes)
LIBGPGME_DEPENDENCY="1.1.8" LIBGPGME_DEPENDENCY="1.1.8"
PKG_CHECK_MODULES(OT_DEP_GPGME, gpgme >= $LIBGPGME_DEPENDENCY, have_gpgme=yes, [ PKG_CHECK_MODULES(OT_DEP_GPGME, gpgme-pthread >= $LIBGPGME_DEPENDENCY, have_gpgme=yes, [
m4_ifdef([AM_PATH_GPGME], [ m4_ifdef([AM_PATH_GPGME_PTHREAD], [
AM_PATH_GPGME($LIBGPGME_DEPENDENCY, have_gpgme=yes, have_gpgme=no) AM_PATH_GPGME_PTHREAD($LIBGPGME_DEPENDENCY, have_gpgme=yes, have_gpgme=no)
],[ have_gpgme=no ]) ],[ have_gpgme=no ])
]) ])
AS_IF([ test x$have_gpgme = xno ], [ AS_IF([ test x$have_gpgme = xno ], [
AC_MSG_ERROR([Need GPGME version $LIBGPGME_DEPENDENCY or later]) AC_MSG_ERROR([Need GPGME_PTHREAD version $LIBGPGME_DEPENDENCY or later])
]) ])
OSTREE_FEATURES="$OSTREE_FEATURES +gpgme" OSTREE_FEATURES="$OSTREE_FEATURES +gpgme"
LIBARCHIVE_DEPENDENCY="libarchive >= 2.8.0" LIBARCHIVE_DEPENDENCY="libarchive >= 2.8.0"
# What's in RHEL7.2.
FUSE_DEPENDENCY="fuse >= 2.9.2"
# check for gtk-doc # check for gtk-doc
m4_ifdef([GTK_DOC_CHECK], [ m4_ifdef([GTK_DOC_CHECK], [
@ -126,7 +128,22 @@ enable_gtk_doc=no
AM_CONDITIONAL([ENABLE_GTK_DOC], false) AM_CONDITIONAL([ENABLE_GTK_DOC], false)
]) ])
AC_PATH_PROG([XSLTPROC], [xsltproc]) AC_ARG_ENABLE(man,
[AS_HELP_STRING([--enable-man],
[generate man pages [default=auto]])],,
enable_man=maybe)
AS_IF([test "$enable_man" != no], [
AC_PATH_PROG([XSLTPROC], [xsltproc])
AS_IF([test -z "$XSLTPROC"], [
AS_IF([test "$enable_man" = yes], [
AC_MSG_ERROR([xsltproc is required for --enable-man])
])
enable_man=no
])
enable_man=yes
])
AM_CONDITIONAL(ENABLE_MAN, test "$enable_man" != no)
AC_ARG_WITH(libarchive, AC_ARG_WITH(libarchive,
AS_HELP_STRING([--without-libarchive], [Do not use libarchive]), AS_HELP_STRING([--without-libarchive], [Do not use libarchive]),
@ -179,6 +196,16 @@ AS_IF([ test x$with_selinux != xno ], [
if test x$with_selinux != xno; then OSTREE_FEATURES="$OSTREE_FEATURES +selinux"; fi if test x$with_selinux != xno; then OSTREE_FEATURES="$OSTREE_FEATURES +selinux"; fi
AM_CONDITIONAL(USE_SELINUX, test $with_selinux != no) AM_CONDITIONAL(USE_SELINUX, test $with_selinux != no)
# Enabled by default because I think people should use it.
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 ], [
PKG_CHECK_MODULES(BUILDOPT_FUSE, $FUSE_DEPENDENCY)
], [enable_rofiles_fuse=no])
AM_CONDITIONAL(BUILDOPT_FUSE, test x$enable_rofiles_fuse = xyes)
AC_ARG_WITH(dracut, AC_ARG_WITH(dracut,
AS_HELP_STRING([--with-dracut], AS_HELP_STRING([--with-dracut],
[Install dracut module (default: no)]),, [Install dracut module (default: no)]),,
@ -220,18 +247,9 @@ AS_IF([test "x$found_introspection" = xyes], [
], [have_gjs=no]) ], [have_gjs=no])
AM_CONDITIONAL(BUILDOPT_GJS, test x$have_gjs = xyes) AM_CONDITIONAL(BUILDOPT_GJS, test x$have_gjs = xyes)
AC_ARG_ENABLE(static_deltas,
AS_HELP_STRING([--enable-static-deltas],
[Enable static delta code (default: yes)]),,
[enable_static_deltas=yes])
AS_IF([test x$enable_static_deltas = xyes], [
AC_DEFINE([BUILDOPT_STATIC_DELTAS], 1, [Define if static deltas are enabled])
])
AM_CONDITIONAL(BUILDOPT_STATIC_DELTAS, test x$enable_static_deltas = xyes)
AC_CONFIG_FILES([ AC_CONFIG_FILES([
Makefile Makefile
doc/Makefile apidoc/Makefile
src/libostree/ostree-1.pc src/libostree/ostree-1.pc
]) ])
AC_OUTPUT AC_OUTPUT
@ -242,12 +260,14 @@ echo "
introspection: $found_introspection introspection: $found_introspection
rofiles-fuse: $enable_rofiles_fuse
libsoup (retrieve remote HTTP repositories): $with_soup libsoup (retrieve remote HTTP repositories): $with_soup
libsoup TLS client certs: $have_libsoup_client_certs libsoup TLS client certs: $have_libsoup_client_certs
SELinux: $with_selinux SELinux: $with_selinux
libarchive (parse tar files directly): $with_libarchive libarchive (parse tar files directly): $with_libarchive
static deltas: $enable_static_deltas static deltas: yes (always enabled now)
documentation: $enable_gtk_doc man pages (xsltproc): $enable_man
api docs (gtk-doc): $enable_gtk_doc
gjs-based tests: $have_gjs gjs-based tests: $have_gjs
dracut: $with_dracut dracut: $with_dracut
mkinitcpio: $with_mkinitcpio" mkinitcpio: $with_mkinitcpio"

View File

@ -1,267 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
<!ENTITY version SYSTEM "../version.xml">
]>
<part id="adapting-existing">
<title>Adapting existing mainstream distributions</title>
<chapter id="layout">
<title>System layout</title>
<para>
First, OSTree encourages systems to implement <ulink
url="http://www.freedesktop.org/wiki/Software/systemd/TheCaseForTheUsrMerge/">UsrMove</ulink>.
This is simply to avoid the need for more bind mounts. By
default OSTree's dracut hook creates a read-only bind mount over
<filename class='directory'>/usr</filename>; you can of course
generate individual bind-mounts for <filename
class='directory'>/bin</filename>, all the <filename
class='directory'>/lib</filename> variants, etc. So it is not
intended to be a hard requirement.
</para>
<para>
Remember, because by default the system is booted into a
<literal>chroot</literal> equivalent, there has to be some way
to refer to the actual physical root filesystem. Therefore,
your operating system tree should contain an empty <filename
class='directory'>/sysroot</filename> directory; at boot time,
OSTree will make this a bind mount to the physical / root
directory. There is precedent for this name in the initramfs
context. You should furthermore make a toplevel symbolic link
<filename class='directory'>/ostree</filename> which points to
<filename class='directory'>/sysroot/ostree</filename>, so that
the OSTree tool at runtime can consistently find the system data
regardless of whether it's operating on a physical root or
inside a deployment.
</para>
<para>
Because OSTree only preserves <filename
class='directory'>/var</filename> across upgrades (each
deployment's chroot directory will be garbage collected
eventually), you will need to choose how to handle other
toplevel writable directories specified by the <ulink
url="http://www.pathname.com/fhs/">Filesystem Hierarchy
Standard</ulink>. Your operating system may of course choose
not to support some of these such as <filename
class='directory'>/usr/local</filename>, but following is the
recommended set:
<itemizedlist>
<listitem>
<para>
<filename class='directory'>/home</filename> to <filename class='directory'>/var/home</filename>
</para>
</listitem>
<listitem>
<para>
<filename class='directory'>/opt</filename> to <filename class='directory'>/var/opt</filename>
</para>
</listitem>
<listitem>
<para>
<filename class='directory'>/srv</filename> to <filename class='directory'>/var/srv</filename>
</para>
</listitem>
<listitem>
<para>
<filename class='directory'>/root</filename> to <filename class='directory'>/var/roothome</filename>
</para>
</listitem>
<listitem>
<para>
<filename class='directory'>/usr/local</filename> to <filename class='directory'>/var/local</filename>
</para>
</listitem>
<listitem>
<para>
<filename class='directory'>/mnt</filename> to <filename class='directory'>/var/mnt</filename>
</para>
</listitem>
<listitem>
<para>
<filename class='directory'>/tmp</filename> to <filename class='directory'>/sysroot/tmp</filename>
</para>
</listitem>
</itemizedlist>
</para>
<para>
Furthermore, since <filename class='directory'>/var</filename>
is empty by default, your operating system will need to
dynamically create the <emphasis>targets</emphasis> of these at
boot. A good way to do this is using
<command>systemd-tmpfiles</command>, if your OS uses systemd.
For example:
</para>
<programlisting>
<![CDATA[
d /var/log/journal 0755 root root -
L /var/home - - - - ../sysroot/home
d /var/opt 0755 root root -
d /var/srv 0755 root root -
d /var/roothome 0700 root root -
d /var/usrlocal 0755 root root -
d /var/usrlocal/bin 0755 root root -
d /var/usrlocal/etc 0755 root root -
d /var/usrlocal/games 0755 root root -
d /var/usrlocal/include 0755 root root -
d /var/usrlocal/lib 0755 root root -
d /var/usrlocal/man 0755 root root -
d /var/usrlocal/sbin 0755 root root -
d /var/usrlocal/share 0755 root root -
d /var/usrlocal/src 0755 root root -
d /var/mnt 0755 root root -
d /run/media 0755 root root -
]]>
</programlisting>
<para>
Particularly note here the double indirection of <filename
class='directory'>/home</filename>. By default, each
deployment will share the global toplevel <filename
class='directory'>/home</filename> directory on the physical
root filesystem. It is then up to higher levels of management
tools to keep <filename>/etc/passwd</filename> or equivalent
synchronized between operating systems.
</para>
<para>
Each deployment can easily be reconfigured to have its own home
directory set simply by making <filename
class='directory'>/var/home</filename> a real directory.
</para>
</chapter>
<chapter id="booting">
<title>Booting and initramfs technology</title>
<para>
OSTree comes with optional dracut+systemd integration code that
parses the <literal>ostree=</literal> kernel command line
argument in the initramfs, and then sets up the read-only bind
mount on <filename class='directory'>/usr</filename>, a bind
mount on the deployment's <filename
class='directory'>/sysroot</filename> to the physical <filename
class='directory'>/</filename>, and then finally uses
<literal>mount(MS_MOVE)</literal> to make the deployment root appear to be the
root filesystem before telling systemd to switch root.
</para>
<para>
If you are not using dracut or systemd, using OSTree should still
be possible, but you will have to write the integration code. Patches
to support other initramfs technologies and init systems, if sufficiently
clean, will likely be accepted upstream.
</para>
<para>
A further specific note regarding <command>sysvinit</command>:
OSTree used to support recording device files such the
<filename>/dev/initctl</filename> FIFO, but no longer does.
It's recommended to just patch your initramfs to create this at
boot.
</para>
</chapter>
<chapter id="lib-passwd">
<title>/usr/lib/passwd</title>
<para>
Unlike traditional package systems, OSTree trees contain
<emphasis>numeric</emphasis> uid and gids. Furthermore, it does
not have a <literal>%post</literal> type mechanism where
<filename>useradd</filename> could be invoked. In order to ship
an OS that contains both system users and users dynamically
created on client machines, you will need to choose a solution
for <filename>/etc/passwd</filename>. The core problem is that
if you add a user to the system for a daemon, the OSTree upgrade
process for <filename class='directory'>/etc</filename> will
simply notice that because <filename>/etc/passwd</filename>
differs from the previous default, it will keep the modified
config file, and your new OS user will not be visible.
</para>
<para>
The solution chosen for the <ulink
url="https://live.gnome.org/Projects/GnomeContinuous">gnome-continuous</ulink>
operating system is to create
<filename>/usr/lib/passwd</filename>, and to include a NSS
module <ulink
url="https://github.com/aperezdc/nss-altfiles">nss-altfiles</ulink>
which instructs glibc to read from it. Then, the build system
places all system users there, freeing up
<filename>/etc/passwd</filename> to be purely a database of
local users. See also a more recent effort from <ulink
url="http://0pointer.de/blog/projects/stateless.html">Systemd
stateless</ulink>.
</para>
</chapter>
<chapter id="adapting-package-manager">
<title>Adapting existing package managers</title>
<para>
The largest endeavor is likely to be redesigning your
distribution's package manager to be on top of OSTree,
particularly if you want to keep compatibility with the "old
way" of installing into the physical <filename
class='directory'>/</filename>. This section will use examples
from both <command>dpkg</command> and <command>rpm</command> as
the author has familiarity with both; but the abstract concepts
should apply to most traditional package managers.
</para>
<para>
There are many levels of possible integration; initially, we
will describe the most naive implementation which is the
simplest but also the least efficient. We will assume here that
the admin is booted into an OSTree-enabled system, and wants to
add a set of packages.
</para>
<para>
Many package managers store their state in <filename
class='directory'>/var</filename>; but since in the OSTree model
that directory is shared between independent versions, the
package database must first be found in the per-deployment
<filename class='directory'>/usr</filename> directory. It
becomes read-only; remember, all upgrades involve constructing a
new filesystem tree, so your package manager will also need to
create a copy of its database. Most likely, if you want to
continue supporting non-OSTree deployments, simply have your
package manager fall back to the legacy <filename
class='directory'>/var</filename> location if the one in
<filename class='directory'>/usr</filename> is not found.
</para>
<para>
To install a set of new packages (without removing any existing
ones), enumerate the set of packages in the currently booted
deployment, and perform dependency resolution to compute the
complete set of new packages. Download and unpack these new
packages to a temporary directory.
</para>
<para>
Now, because we are merely installing new packages and not
removing anything, we can make the major optimization of reusing
our existing filesystem tree, and merely
<emphasis>layering</emphasis> the composed filesystem tree of
these new packages on top. A command lke this: <command>ostree
commit -b osname/releasename/description
--tree=ref=<replaceable>osname/releasenamename/description</replaceable>
--tree=dir=/var/tmp/newpackages.13A8D0/</command> will create a
new commit in the
<replaceable>osname/releasename/description</replaceable>
branch. The OSTree SHA256 checksum of all the files in
/var/tmp/newpackages.13A8D0/ will be computed, but we will not
re-checksum the present existing tree. In this layering model,
earlier directories will take precedence, but files in later
layers will silently override earlier layers.
</para>
<para>
Then to actually deploy this tree for the next boot:
<command>ostree admin deploy
<replaceable>osname/releasenamename/description</replaceable></command>
</para>
</chapter>
</part>

View File

@ -1,181 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
<!ENTITY version SYSTEM "../version.xml">
]>
<part id="atomic-upgrades">
<title>Atomic Upgrades</title>
<chapter id="upgrades-intro">
<title>You can turn off the power anytime you want...</title>
<para>
OSTree is designed to implement fully atomic and safe upgrades;
more generally, atomic transitions between lists of bootable
deployments. If the system crashes or you pull the power, you
will have either the old system, or the new one.
</para>
</chapter>
<chapter id="simple-http">
<title>Simple upgrades via HTTP</title>
<para>
First, the most basic model OSTree supports is one where it
replicates pre-generated filesystem trees from a server over
HTTP, tracking exactly one ref, which is stored in the <filename
class='extension'>.origin</filename> file for the deployment.
The command <command>ostree admin upgrade</command> implements
this.
</para>
<para>
To begin a simple upgrade, OSTree fetches the contents of the
ref from the remote server. Suppose we're tracking a ref named
<literal>exampleos/buildmaster/x86_64-runtime</literal>.
OSTree fetches the URL
<literal>http://<replaceable>example.com</replaceable>/repo/refs/exampleos/buildmaster/x86_64-runtime</literal>,
which contains a SHA256 checksum. This determines the tree to
deploy, and <filename class='directory'>/etc</filename> will be
merged from currently booted tree.
</para>
<para>
If we do not have this commit, then, then we perform a pull
process. At present (without static deltas), this involves
quite simply just fetching each individual object that we do not
have, asynchronously. Put in other words, we only download
changed files (zlib-compressed). Each object has its checksum
validated and is stored in <filename
class='directory'>/ostree/repo/objects/</filename>.
</para>
<para>
Once the pull is complete, we have all the objects locally
we need to perform a deployment.
</para>
</chapter>
<chapter id="package-manager">
<title>Upgrades via external tools (e.g. package managers)</title>
<para>
As mentioned in the introduction, OSTree is also designed to
allow a model where filesystem trees are computed on the client.
It is completely agnostic as to how those trees are generated;
they could be computed with traditional packages, packages with
post-deployment scripts on top, or built by developers directly
from revision control locally, etc.
</para>
<para>
At a practical level, most package managers today
(<command>dpkg</command> and <command>rpm</command>) 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: <xref linkend="adapting-existing"/>.
</para>
<para>
For the purposes of this section, let's assume that we have a
newly generated filesystem tree stored in the repo (which shares
storage with the existing booted tree). We can then move on to
checking it back out of the repo into a deployment.
</para>
</chapter>
<chapter id="deployment-dir">
<title>Assembling a new deployment directory</title>
<para>
Given a commit to deploy, OSTree first allocates a directory for
it. This is of the form <filename
class='directory'>/boot/loader/entries/ostree-<replaceable>osname</replaceable>-<replaceable>checksum</replaceable>.<replaceable>serial</replaceable>.conf</filename>.
The <replaceable>serial</replaceable> 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 <filename class='directory'>/etc</filename>
that we do not want to use or overwrite.
</para>
<para>
Now that we have a deployment directory, a 3-way merge is
performed between the (by default) currently booted deployment's
<filename class='directory'>/etc</filename>, its default
configuration, and the new deployment (based on its <filename
class='directory'>/usr/etc</filename>).
</para>
</chapter>
<chapter id="swapping-boot">
<title>Atomically swapping boot configuration</title>
<para>
At this point, a new deployment directory has been created as a
hardlink farm; the running system is untouched, and the
bootloader configuration is untouched. We want to add this deployment
to the "deployment list".
</para>
<para>
To support a more general case, OSTree supports atomic
transitioning between arbitrary sets of deployments, with the
restriction that the currently booted deployment must always be
in the new set. In the normal case, we have exactly one
deployment, which is the booted one, and we want to add the new
deployment to the list. A more complex command might allow
creating 100 deployments as part of one atomic transaction, so
that one can set up an automated system to bisect across them.
</para>
<simplesect id="bootversion">
<title>The bootversion</title>
<para>
OSTree allows swapping between boot configurations by
implementing the "swapped directory pattern" in <filename
class='directory'>/boot</filename>. This means it is a
symbolic link to one of two directories <filename
class='directory'>/ostree/boot.<replaceable>[0|1]</replaceable></filename>.
To swap the contents atomically, if the current version is
<literal>0</literal>, we create <filename
class='directory'>/ostree/boot.1</filename>, populate it with
the new contents, then atomically swap the symbolic link. Finally,
the old contents can be garbage collected at any point.
</para>
</simplesect>
<simplesect id="ostree-bootversion">
<title>The /ostree/boot directory</title>
<para>
However, we want to optimize for the case where we the set of
kernel/initramfs pairs is the same between both the old and
new deployment lists. This happens when doing an upgrade that
does not include the kernel; think of a simple translation
update. OSTree optimizes for this case because on some
systems <filename class='directory'>/boot</filename> may be on
a separate medium such as flash storage not optimized for
significant amounts of write traffic.
</para>
<para>
To implement this, OSTree also maintains the directory
<filename
class='directory'>/ostree/boot.<replaceable>bootversion</replaceable></filename>,
which is a set of symbolic links to the deployment
directories. The <replaceable>bootversion</replaceable> here
must match the version of <filename
class='directory'>/boot</filename>. However, in order to
allow atomic transitions of <emphasis>this</emphasis>
directory, this is also a swapped directory, so just like
<filename class='directory'>/boot</filename>, it has a version
of <literal>0</literal> or <literal>1</literal> appended.
</para>
<para>
Each bootloader entry has a special <literal>ostree=</literal>
argument which refers to one of these symbolic links. This is
parsed at runtime in the initramfs.
</para>
</simplesect>
</chapter>
</part>

View File

@ -1,158 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
<!ENTITY version SYSTEM "../version.xml">
]>
<part id="deployment">
<title>Deployments</title>
<chapter id="deployment-intro">
<title>Overview</title>
<para>
Built on top of the OSTree versioning filesystem core is a layer
that knows how to deploy, parallel install, and manage Unix-like
operating systems (accessible via <command>ostree
admin</command>). The core content of these operating systems
are treated as read-only, but they transparently share storage.
</para>
<para>
A deployment is physically located at a path of the form
<filename
class='directory'>/ostree/deploy/<replaceable>osname</replaceable>/deploy/<replaceable>checksum</replaceable></filename>.
OSTree is designed to boot directly into exactly one deployment
at a time; each deployment is intended to be a target for
<literal>chroot()</literal> or equivalent.
</para>
</chapter>
<chapter id="deployment-osname">
<title>"osname": Group of deployments that share /var</title>
<para>
Each deployment is grouped in exactly one "osname". From
above, you can see that an osname is physically represented in
the <filename
class='directory'>/ostree/deploy/<replaceable>osname</replaceable></filename>
directory. For example, OSTree can allow parallel installing
Debian in <filename
class='directory'>/ostree/deploy/debian</filename> and Red Hat
Enterprise Linux in <filename
class='directory'>/ostree/deploy/rhel</filename> (subject to
operating system support, present released versions of these
operating systems may not support this).
</para>
<para>
Each osname has exactly one copy of the traditional Unix
<filename class='directory'>/var</filename>, stored physically
in <filename
class='directory'>/ostree/deploy/<replaceable>osname</replaceable>/var</filename>.
OSTree provides support tools for <command>systemd</command>
to create a Linux bind mount that ensures the booted
deployment sees the shared copy of <filename
class='directory'>/var</filename>.
</para>
<para>
OSTree does not touch the contents of <filename
class='directory'>/var</filename>. Operating system components
such as daemon services are required to create any directories
they require there at runtime (e.g. <filename
class='directory'>/var/cache/<replaceable>daemonname</replaceable></filename>),
and to manage upgrading data formats inside those directories.
</para>
</chapter>
<chapter id="deployment-contents">
<title>Contents of a deployment</title>
<para>
A deployment begins with a specific commit (represented as a
SHA256 hash) in the OSTree repository in <filename
class='directory'>/ostree/repo</filename>. This commit refers
to a filesystem tree that represents the underlying basis of a
deployment. For short, we will call this the "tree", to
distinguish it from the concept of a deployment.
</para>
<para>
First, the tree must include a kernel stored as <filename
class='directory'>/boot/vmlinuz-<replaceable>checksum</replaceable></filename>.
The checksum should be a SHA256 hash of the kernel contents;
it must be pre-computed before storing the kernel in the
repository. Optionally, the tree can contain an initramfs,
stored as <filename
class='directory'>/boot/initramfs-<replaceable>checksum</replaceable></filename>.
If this exists, the checksum must include both the kernel and
initramfs contents. OSTree will use this to determine which
kernels are shared. The rationale for this is to avoid
computing checksums on the client by default.
</para>
<para>
The deployment should not have a traditional UNIX <filename
class='directory'>/etc</filename>; instead, it should include
<filename class='directory'>/usr/etc</filename>. This is the
"default configuration". When OSTree creates a deployment, it
performs a 3-way merge using the <emphasis>old</emphasis>
default configuration, the active system's <filename
class='directory'>/etc</filename>, and the new default
configuration. In the final filesystem tree for a deployment
then, <filename class='directory'>/etc</filename> is a regular
writable directory.
</para>
<para>
Besides the exceptions of <filename
class='directory'>/var</filename> and <filename
class='directory'>/etc</filename> then, the rest of the
contents of the tree are checked out as hard links into the
repository. It's strongly recommended that operating systems
ship all of their content in <filename
class='directory'>/usr</filename>, but this is not a hard
requirement.
</para>
<para>
Finally, a deployment may have a <filename
class='extension'>.origin</filename> file, stored next to its
directory. This file tells <command>ostree admin
upgrade</command> how to upgrade it. At the moment, OSTree only
supports upgrading a single refspec. However, in the future
OSTree may support a syntax for composing layers of trees, for
example.
</para>
</chapter>
<chapter id="managing-boot">
<title>The system /boot</title>
<para>
While OSTree parallel installs deployments cleanly inside the
<filename class='directory'>/ostree</filename> directory,
ultimately it has to control the system's <filename
class='directory'>/boot</filename> directory. The way this
works is via the <ulink
url="http://www.freedesktop.org/wiki/Specifications/BootLoaderSpec/">boot
loader specification</ulink>, which is a standard for
bootloader-independent drop-in configuration files.
</para>
<para>
When a tree is deployed, it will have a configuration file
generated of the form <filename
class='directory'>/boot/loader/entries/ostree-<replaceable>osname</replaceable>-<replaceable>checksum</replaceable>.<replaceable>serial</replaceable>.conf</filename>.
This configuration file will include a special
<literal>ostree=</literal> kernel argument that allows the
initramfs to find (and <literal>chroot()</literal> into) the
specified deployment.
</para>
<para>
At present, not all bootloaders implement the BootLoaderSpec,
so OSTree contains code for some of these to regenerate native
config files (such as
<filename>/boot/syslinux/syslinux.conf</filename> based on the
entries.
</para>
</chapter>
</part>

View File

@ -1,155 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
<!ENTITY version SYSTEM "../version.xml">
]>
<part id="overview">
<title>OSTree Overview</title>
<chapter id="ostree-intro">
<title>Introduction</title>
<para>
OSTree an upgrade system for Linux-based operating systems that
performs atomic upgrades of complete filesystem trees. It is
not a package system; rather, it is intended to complement them.
A primary model is composing packages on a server, and then
replicating them to clients.
</para>
<para>
The underlying architecture might be summarized as "git for
operating system binaries". It operates in userspace, and will
work on top of any Linux filesystem. At its core is a git-like
content-addressed object store, and layered on top of that is
bootloader configuration, management of
<filename>/etc</filename>, and other functions to perform an
upgrade beyond just replicating files.
</para>
<para>
You can use OSTree standalone in the pure replication model,
but another approach is to add a package manager on top,
thus creating a hybrid tree/package system.
</para>
</chapter>
<chapter id="ostree-package-comparison">
<title>Comparison with "package managers"</title>
<para>
Because OSTree is designed for deploying core operating
systems, a comparison with traditional "package managers" such
as dpkg and rpm is illustrative. Packages are traditionally
composed of partial filesystem trees with metadata and scripts
attached, and these are dynamically assembled on the client
machine, after a process of dependency resolution.
</para>
<para>
In contrast, OSTree only supports recording and deploying
<emphasis>complete</emphasis> (bootable) filesystem trees. It
has no built-in knowledge of how a given filesystem tree was
generated or the origin of individual files, or dependencies,
descriptions of individual components. Put another way, OSTree
only handles delivery and deployment; you will likely still want
to include inside each tree metadata about the individual
components that went into the tree. For example, a system
administrator may want to know what version of OpenSSL was
included in your tree, so you should support the equivalent of
<command>rpm -q</command> or <command>dpkg -L</command>.
</para>
<para>
The OSTree core emphasizes replicating read-only OS trees via
HTTP, and where the OS includes (if desired) an entirely
separate mechanism to install applications, stored in <filename
class='directory'>/var</filename> if they're system global, or
<filename class='directory'>/home</filename> for per-user
application installation. An example application mechanism is
<ulink url="http://docker.io/">Docker</ulink>.
</para>
<para>
However, it is entirely possible to use OSTree underneath a
package system, where the contents of <filename
class='directory'>/usr</filename> are computed on the client.
For example, when installing a package, rather than changing the
currently running filesystem, the package manager could assemble
a new filesystem tree that layers the new packages on top of a
base tree, record it in the local OSTree repository, and then
set it up for the next boot. To support this model, OSTree
provides an (introspectable) C shared library.
</para>
</chapter>
<chapter id="ostree-block-comparison">
<title>Comparison with block/image replication</title>
<para>
OSTree shares some similarity with "dumb" replication and
stateless deployments, such as the model common in "cloud"
deployments where nodes are booted from an (effectively)
readonly disk, and user data is kept on a different volumes.
The advantage of "dumb" replication, shared by both OSTree and
the cloud model, is that it's <emphasis>reliable</emphasis>
and <emphasis>predictable</emphasis>.
</para>
<para>
But unlike many default image-based deployments, OSTree supports
exactly two persistent writable directories that are preserved
across upgrades: <literal>/etc</literal> and
<literal>/var</literal>.
</para>
<para>
Because OSTree operates at the Unix filesystem layer, it works
on top of any filesystem or block storage layout; it's possible
to replicate a given filesystem tree from an OSTree repository
into plain ext4, BTRFS, XFS, or in general any Unix-compatible
filesystem that supports hard links. Note: OSTree will
transparently take advantage of some BTRFS features if deployed
on it.
</para>
</chapter>
<chapter id="ostree-atomic-parallel-installation">
<title>Atomic transitions between parallel-installable read-only filesystem trees</title>
<para>
Another deeply fundamental difference between both package
managers and image-based replication is that OSTree is
designed to parallel-install <emphasis>multiple
versions</emphasis> of multiple
<emphasis>independent</emphasis> operating systems. OSTree
relies on a new toplevel <filename
class='directory'>ostree</filename> directory; it can in fact
parallel install inside an existing OS or distribution
occupying the physical <filename
class='directory'>/</filename> root.
</para>
<para>
On each client machine, there is an OSTree repository stored
in <filename class='directory'>/ostree/repo</filename>, and a
set of "deployments" stored in <filename
class='directory'>/ostree/deploy/<replaceable>OSNAME</replaceable>/<replaceable>CHECKSUM</replaceable></filename>.
Each deployment is primarily composed of a set of hardlinks
into the repository. This means each version is deduplicated;
an upgrade process only costs disk space proportional to the
new files, plus some constant overhead.
</para>
<para>
The model OSTree emphasizes is that the OS read-only content
is kept in the classic Unix <filename
class='directory'>/usr</filename>; it comes with code to
create a Linux read-only bind mount to prevent inadvertent
corruption. There is exactly one <filename
class='directory'>/var</filename> writable directory shared
between each deployment for a given OS. The OSTree core code
does not touch content in this directory; it is up to the code
in each operating system for how to manage and upgrade state.
</para>
<para>
Finally, each deployment has its own writable copy of the
configuration store <filename
class='directory'>/etc</filename>. On upgrade, OSTree will
perform a basic 3-way diff, and apply any local changes to the
new copy, while leaving the old untouched.
</para>
</chapter>
</part>

View File

@ -1,127 +0,0 @@
<?xml version="1.0"?>
<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [
<!ENTITY version SYSTEM "../version.xml">
]>
<part id="repository">
<title>Anatomy of an OSTree repository</title>
<chapter id="repository-intro">
<title>Core object types and data model</title>
<para>
OSTree is deeply inspired by git; the core layer is a userspace
content-addressed versioning filesystem. It is worth taking
some time to familiarize yourself with <ulink
url="http://git-scm.com/book/en/Git-Internals">Git
Internals</ulink>, as this section will assume some knowledge of
how git works.
</para>
<para>
Its object types are similar to git; it has commit objects and
content objects. Git has "tree" objects, whereas OSTree splits
them into "dirtree" and "dirmeta" objects. But unlike git,
OSTree's checksums are SHA256. And most crucially, its content
objects include uid, gid, and extended attributes (but still no
timestamps).
</para>
<simplesect id="commits">
<title>Commit objects</title>
<para>
A commit object contains metadata such as a timestamp, a log
message, and most importantly, a reference to a
dirtree/dirmeta pair of checksums which describe the root
directory of the filesystem.
</para>
<para>
Also like git, each commit in OSTree can have a parent. It is
designed to store a history of your binary builds, just like git
stores a history of source control. However, OSTree also makes
it easy to delete data, under the assumption that you can
regenerate it from source code.
</para>
</simplesect>
<simplesect id="dirtree">
<title>Dirtree objects</title>
<para>
A dirtree contains a sorted array of (filename, checksum)
pairs for content objects, and a second sorted array of
(filename, dirtree checksum, dirmeta checksum), which are
subdirectories.
</para>
</simplesect>
<simplesect id="dirmeta">
<title>Dirmeta objects</title>
<para>
In git, tree objects contain the metadata such as permissions
for their children. But OSTree splits this into a separate
object to avoid duplicating extended attribute listings.
</para>
</simplesect>
<simplesect id="content">
<title>Content objects</title>
<para>
Unlike the first three object types which are metadata,
designed to be <literal>mmap()ed</literal>, the content object
has a separate internal header and 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.
</para>
</simplesect>
</chapter>
<chapter id="repository-types">
<title>Repository types and locations</title>
<para>
Also unlike git, an OSTree repository can be in one of two
separate modes: <literal>bare</literal> and
<literal>archive-z2</literal>. A bare repository is one where
content files are just stored as regular files; it's designed to
be the source of a "hardlink farm", where each operating system
checkout is merely links into it. If you want to store files
owned by e.g. root in this mode, you must run OSTree as root.
In contrast, the <literal>archive-z2</literal> mode is designed
for serving via plain HTTP. Like tar files, it can be
read/written by non-root users.
</para>
<para>
On an OSTree-deployed system, the "system repository" is
<filename class='directory'>/ostree/repo</filename>. It can be
read by any uid, but only written by root. Unless the
<literal>--repo</literal> argument is given to the
<command>ostree</command> command, it will operate on the system
repository.
</para>
</chapter>
<chapter id="refs">
<title>Refs</title>
<para>
Like git, OSTree uses "refs" to which are text files that point
to particular commits (i.e. filesystem trees). For example, the
gnome-ostree operating system creates trees named like
<literal>gnome-ostree/buildmaster/x86_64-runtime</literal> and
<literal>gnome-ostree/buildmaster/x86_64-devel-debug</literal>.
These two refs point to two different generated filesystem
trees. In this example, the "runtime" tree contains just enough
to run a basic GNOME system, and "devel-debug" contains all of
the developer tools.
</para>
<para>
The <command>ostree</command> supports a simple syntax using the
carat <literal>^</literal> to refer to the parent of a given
commit. For example,
<literal>gnome-ostree/buildmaster/x86_64-runtime^</literal>
refers to the previous build, and
<literal>gnome-ostree/buildmaster/x86_64-runtime^^</literal>
refers to the one before that.
</para>
</chapter>
</part>

View File

@ -1,12 +0,0 @@
Atomic upgrades and rollback
----------------------------
Traditional package managers operate "live" on the running system.
This means it's possible for interrupted updates to result in a
half-updated system. This model also makes it significantly harder to
support rollbacks when updates fail.
In contrast, OSTree always creates a *new* root whenever it's
performing an update. This new root shares storage via hardlinks with
the current system. Upon success, the bootloader configuration will
be updated.

121
docs/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,121 @@
Submitting patches
------------------
You can:
1. Send mail to ostree-list@gnome.org, with the patch attached
1. Submit a pull request against https://github.com/GNOME/ostree
1. Attach them to https://bugzilla.gnome.org/
Please look at "git log" and match the commit log style.
Running the test suite
----------------------
Currently, ostree uses https://wiki.gnome.org/GnomeGoals/InstalledTests
To run just ostree's tests:
./configure ... --enable-installed-tests
gnome-desktop-testing-runner -p 0 ostree/
Also, there is a regular:
make check
That runs a different set of tests.
Coding style
------------
Indentation is GNU. Files should start with the appropriate mode lines.
Use GCC `__attribute__((cleanup))` wherever possible. If interacting
with a third party library, try defining local cleanup macros.
Use GError and GCancellable where appropriate.
Prefer returning `gboolean` to signal success/failure, and have output
values as parameters.
Prefer linear control flow inside functions (aside from standard
loops). In other words, avoid "early exits" or use of `goto` besides
`goto out;`.
This is an example of an "early exit":
static gboolean
myfunc (...)
{
gboolean ret = FALSE;
/* some code */
/* some more code */
if (condition)
return FALSE;
/* some more code */
ret = TRUE;
out:
return ret;
}
If you must shortcut, use:
if (condition)
{
ret = TRUE;
goto out;
}
A consequence of this restriction is that you are encouraged to avoid
deep nesting of loops or conditionals. Create internal static helper
functions, particularly inside loops. For example, rather than:
while (condition)
{
/* some code */
if (condition)
{
for (i = 0; i < somevalue; i++)
{
if (condition)
{
/* deeply nested code */
}
/* more nested code */
}
}
}
Instead do this:
static gboolean
helperfunc (..., GError **error)
{
if (condition)
{
/* deeply nested code */
}
/* more nested code */
return ret;
}
while (condition)
{
/* some code */
if (!condition)
continue;
for (i = 0; i < somevalue; i++)
{
if (!helperfunc (..., i, error))
goto out;
}
}

1
docs/index.md Symbolic link
View File

@ -0,0 +1 @@
../README.md

View File

@ -0,0 +1,159 @@
# Adapting existing mainstream distributions
## System layout
First, OSTree encourages systems to implement
[UsrMove](http://www.freedesktop.org/wiki/Software/systemd/TheCaseForTheUsrMerge/)
This is simply to avoid the need for more bind mounts. By default
OSTree's dracut hook creates a read-only bind mount over `/usr`; you
can of course generate individual bind-mounts for `/bin`, all the
`/lib` variants, etc. So it is not intended to be a hard requirement.
Remember, because by default the system is booted into a `chroot`
equivalent, there has to be some way to refer to the actual physical
root filesystem. Therefore, your operating system tree should contain
an empty `/sysroot` directory; at boot time, OSTree will make this a
bind mount to the physical / root directory. There is precedent for
this name in the initramfs context. You should furthermore make a
toplevel symbolic link `/ostree` which points to `/sysroot/ostree`, so
that the OSTree tool at runtime can consistently find the system data
regardless of whether it's operating on a physical root or inside a
deployment.
Because OSTree only preserves `/var` across upgrades (each
deployment's chroot directory will be garbage collected
eventually), you will need to choose how to handle other
toplevel writable directories specified by the [Filesystem Hierarchy Standard](http://www.pathname.com/fhs/")
Your operating system may of course choose
not to support some of these such as `/usr/local`, but following is the
recommended set:
- `/home``/var/home`
- `/opt``/var/opt`
- `/srv``/var/srv`
- `/root``/var/roothome`
- `/usr/local``/var/local`
- `/mnt``/var/mnt`
- `/tmp``/sysroot/tmp`
Furthermore, since `/var` is empty by default, your operating system
will need to dynamically create the <emphasis>targets</emphasis> of
these at boot. A good way to do this is using `systemd-tmpfiles`, if
your OS uses systemd. For example:
```
d /var/log/journal 0755 root root -
L /var/home - - - - ../sysroot/home
d /var/opt 0755 root root -
d /var/srv 0755 root root -
d /var/roothome 0700 root root -
d /var/usrlocal 0755 root root -
d /var/usrlocal/bin 0755 root root -
d /var/usrlocal/etc 0755 root root -
d /var/usrlocal/games 0755 root root -
d /var/usrlocal/include 0755 root root -
d /var/usrlocal/lib 0755 root root -
d /var/usrlocal/man 0755 root root -
d /var/usrlocal/sbin 0755 root root -
d /var/usrlocal/share 0755 root root -
d /var/usrlocal/src 0755 root root -
d /var/mnt 0755 root root -
d /run/media 0755 root root -
```
Particularly note here the double indirection of `/home`. By default,
each deployment will share the global toplevel `/home` directory on
the physical root filesystem. It is then up to higher levels of
management tools to keep <filename>/etc/passwd</filename> or
equivalent synchronized between operating systems. Each deployment
can easily be reconfigured to have its own home directory set simply
by making `/var/home` a real directory. </chapter>
## Booting and initramfs technology
OSTree comes with optional dracut+systemd integration code that parses
the `ostree=` kernel command line argument in the initramfs, and then
sets up the read-only bind mount on `/usr`, a bind mount on the
deployment's `/sysroot` to the physical `/`, and then finally uses
`mount(MS_MOVE)` to make the deployment root appear to be the root
filesystem before telling systemd to switch root.
If you are not using dracut or systemd, using OSTree should still be
possible, but you will have to write the integration code. Patches to
support other initramfs technologies and init systems, if sufficiently
clean, will likely be accepted upstream.
A further specific note regarding `sysvinit`: OSTree used to support
recording device files such the `/dev/initctl` FIFO, but no longer
does. It's recommended to just patch your initramfs to create this at
boot.
## /usr/lib/passwd
Unlike traditional package systems, OSTree trees contain *numeric* uid
and gids. Furthermore, it does not have a `%post` type mechanism
where `useradd` could be invoked. In order to ship an OS that
contains both system users and users dynamically created on client
machines, you will need to choose a solution for `/etc/passwd`. The
core problem is that if you add a user to the system for a daemon, the
OSTree upgrade process for `/etc` will simply notice that because
`/etc/passwd` differs from the previous default, it will keep the
modified config file, and your new OS user will not be visible. The
solution chosen for the [Gnome Continuous](https://live.gnome.org/Projects/GnomeContinuous) operating
system is to create `/usr/lib/passwd`, and to include a NSS module
[nss-altfiles](https://github.com/aperezdc/nss-altfiles) which
instructs glibc to read from it. Then, the build system places all
system users there, freeing up `/etc/passwd` to be purely a database
of local users. See also a more recent effort from [Systemd Stateless](http://0pointer.de/blog/projects/stateless.html)
## Adapting existing package managers
The largest endeavor is likely to be redesigning your distribution's
package manager to be on top of OSTree, particularly if you want to
keep compatibility with the "old way" of installing into the physical
`/`. This section will use examples from both `dpkg` and `rpm` as the
author has familiarity with both; but the abstract concepts should
apply to most traditional package managers.
There are many levels of possible integration; initially, we will
describe the most naive implementation which is the simplest but also
the least efficient. We will assume here that the admin is booted
into an OSTree-enabled system, and wants to add a set of packages.
Many package managers store their state in `/var`; but since in the
OSTree model that directory is shared between independent versions,
the package database must first be found in the per-deployment `/usr`
directory. It becomes read-only; remember, all upgrades involve
constructing a new filesystem tree, so your package manager will also
need to create a copy of its database. Most likely, if you want to
continue supporting non-OSTree deployments, simply have your package
manager fall back to the legacy `/var` location if the one in `/usr`
is not found.
To install a set of new packages (without removing any existing ones),
enumerate the set of packages in the currently booted deployment, and
perform dependency resolution to compute the complete set of new
packages. Download and unpack these new packages to a temporary
directory.
Now, because we are merely installing new packages and not
removing anything, we can make the major optimization of reusing
our existing filesystem tree, and merely
*layering* the composed filesystem tree of
these new packages on top. A command like this:
```
ostree commit -b osname/releasename/description
--tree=ref=$osname/releasename/description
--tree=dir=/var/tmp/newpackages.13A8D0/
```
will create a new commit in the `$osname/releasename/description`
branch. The OSTree SHA256 checksum of all the files in
`/var/tmp/newpackages.13A8D0/` will be computed, but we will not
re-checksum the present existing tree. In this layering model,
earlier directories will take precedence, but files in later layers
will silently override earlier layers.
Then to actually deploy this tree for the next boot:
`ostree admin deploy <replaceable>osname/releasename/description`

View File

@ -0,0 +1,116 @@
# Atomic Upgrades
## You can turn off the power anytime you want...
OSTree is designed to implement fully atomic and safe upgrades;
more generally, atomic transitions between lists of bootable
deployments. If the system crashes or you pull the power, you
will have either the old system, or the new one.
## Simple upgrades via HTTP
First, the most basic model OSTree supports is one where it replicates
pre-generated filesystem trees from a server over HTTP, tracking
exactly one ref, which is stored in the `.origin` file for the
deployment. The command `ostree admin upgrade`
implements this.
o begin a simple upgrade, OSTree fetches the contents of the ref from
the remote server. Suppose we're tracking a ref named
`exampleos/buildmaster/x86_64-runtime`. OSTree fetches the URL
`http://$example.com/repo/refs/exampleos/buildmaster/x86_64-runtime`,
which contains a SHA256 checksum. This determines the tree to deploy,
and `/etc` will be merged from currently booted tree.
If we do not have this commit, then, then we perform a pull process.
At present (without static deltas), this involves quite simply just
fetching each individual object that we do not have, asynchronously.
Put in other words, we only download changed files (zlib-compressed).
Each object has its checksum validated and is stored in `/ostree/repo/objects/`.
Once the pull is complete, we have all the objects locally
we need to perform a deployment.
## Upgrades via external tools (e.g. package managers)
As mentioned in the introduction, OSTree is also designed to allow a
model where filesystem trees are computed on the client. It is
completely agnostic as to how those trees are generated; hey could be
computed with traditional packages, packages with post-deployment
scripts on top, or built by developers directly from revision control
locally, etc.
At a practical level, most package managers today (`dpkg` and `rpm`)
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).
For the purposes of this section, let's assume that we have a
newly generated filesystem tree stored in the repo (which shares
storage with the existing booted tree). We can then move on to
checking it back out of the repo into a deployment.
## Assembling a new deployment directory
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`.
he $serial is normally 0, but if a
given commit is deployed more than once, it will be incremented.
his is supported because the previous deployment may have
configuration in `/etc`
hat we do not want to use or overwrite.
Now that we have a deployment directory, a 3-way merge is
performed between the (by default) currently booted deployment's
`/etc`, its default
configuration, and the new deployment (based on its `/usr/etc`).
## Atomically swapping boot configuration
At this point, a new deployment directory has been created as a
hardlink farm; the running system is untouched, and the bootloader
configuration is untouched. We want to add this deployment o the
"deployment list".
To support a more general case, OSTree supports atomic ransitioning
between arbitrary sets of deployments, with the restriction that the
currently booted deployment must always be in the new set. In the
normal case, we have exactly one deployment, which is the booted one,
and we want to add the new deployment to the list. A more complex
command might allow creating 100 deployments as part of one atomic
transaction, so that one can set up an automated system to bisect
across them.
## The bootversion
OSTree allows swapping between boot configurations by implementing the
"swapped directory pattern" in `/boot`. This means it is a symbolic
link to one of two directories `/ostree/boot.[0|1]`. To swap the
contents atomically, if the current version is `0`, we create
`/ostree/boot.1`, populate it with the new contents, then atomically
swap the symbolic link. Finally, the old contents can be garbage
collected at any point.
## The /ostree/boot directory
However, we want to optimize for the case where we the set of
kernel/initramfs pairs is the same between both the old and new
deployment lists. This happens when doing an upgrade that does not
include the kernel; think of a simple translation update. OSTree
optimizes for this case because on some systems `/boot` may be on a
separate medium such as flash storage not optimized for significant
amounts of write traffic.
To implement this, OSTree also maintains the directory
`/ostree/boot.<replaceable>bootversion</replaceable>`, which is a set
of symbolic links to the deployment directories. The
<replaceable>bootversion</replaceable> here must match the version of
`/boot`. However, in order to allow atomic transitions of
<emphasis>this</emphasis> directory, this is also a swapped directory,
so just like `/boot`, it has a version of `0` or `1` appended.
Each bootloader entry has a special `ostree=` argument which refers to
one of these symbolic links. This is parsed at runtime in the
initramfs.

90
docs/manual/deployment.md Normal file
View File

@ -0,0 +1,90 @@
# Deployments
## Overview
Built on top of the OSTree versioning filesystem core is a layer
that knows how to deploy, parallel install, and manage Unix-like
operating systems (accessible via `ostree admin`). The core content of these operating systems
are treated as read-only, but they transparently share storage.
A deployment is physically located at a path of the form
`/ostree/deploy/$osname/deploy/$checksum`.
OSTree is designed to boot directly into exactly one deployment
at a time; each deployment is intended to be a target for
`chroot()` or equivalent.
### "osname": Group of deployments that share /var</title>
Each deployment is grouped in exactly one "osname". From above, you
can see that an osname is physically represented in the
`/ostree/deploy/$osname` directory. For example, OSTree can allow
parallel installing Debian in `/ostree/deploy/debian` and Red Hat
Enterprise Linux in `/ostree/deploy/rhel` (subject to operating system
support, present released versions of these operating systems may not
support this).
Each osname has exactly one copy of the traditional Unix `/var`,
stored physically in `/ostree/deploy/$osname/var`. OSTree provides
support tools for `systemd` to create a Linux bind mount that ensures
the booted deployment sees the shared copy of `/var`.
OSTree does not touch the contents of `/var`. Operating system
components such as daemon services are required to create any
directories they require there at runtime
(e.g. `/var/cache/$daemonname`), and to manage upgrading data formats
inside those directories.
### Contents of a deployment
A deployment begins with a specific commit (represented as a
SHA256 hash) in the OSTree repository in `/ostree/repo`. This commit refers
to a filesystem tree that represents the underlying basis of a
deployment. For short, we will call this the "tree", to
distinguish it from the concept of a deployment.
First, the tree must include a kernel stored as
`/boot/vmlinuz-$checksum`. The checksum should be a SHA256 hash of
the kernel contents; it must be pre-computed before storing the kernel
in the repository. Optionally, the tree can contain an initramfs,
stored as `/boot/initramfs-$checksum`. If this exists, the checksum
must include both the kernel and initramfs contents. OSTree will use
this to determine which kernels are shared. The rationale for this is
to avoid computing checksums on the client by default.
The deployment should not have a traditional UNIX `/etc`; instead, it
should include `/usr/etc`. This is the "default configuration". When
OSTree creates a deployment, it performs a 3-way merge using the
<emphasis>old</emphasis> default configuration, the active system's
`/etc`, and the new default configuration. In the final filesystem
tree for a deployment then, `/etc` is a regular writable directory.
Besides the exceptions of `/var` and `/etc` then, the rest of the
contents of the tree are checked out as hard links into the
repository. It's strongly recommended that operating systems ship all
of their content in `/usr`, but this is not a hard requirement.
Finally, a deployment may have a `.origin` file, stored next to its
directory. This file tells `ostree admin upgrade` how to upgrade it.
At the moment, OSTree only supports upgrading a single refspec.
However, in the future OSTree may support a syntax for composing
layers of trees, for example.
### The system /boot
While OSTree parallel installs deployments cleanly inside the
`/ostree` directory, ultimately it has to control the system's `/boot`
directory. The way this works is via the
[Boot Loader Specification](http://www.freedesktop.org/wiki/Specifications/BootLoaderSpec),
which is a standard for bootloader-independent drop-in configuration
files.
When a tree is deployed, it will have a configuration file generated
of the form
`/boot/loader/entries/ostree-$osname-$checksum.$serial.conf`. This
configuration file will include a special `ostree=` kernel argument
that allows the initramfs to find (and `chroot()` into) the specified
deployment.
At present, not all bootloaders implement the BootLoaderSpec, so
OSTree contains code for some of these to regenerate native config
files (such as `/boot/syslinux/syslinux.conf` based on the entries.

181
docs/manual/formats.md Normal file
View File

@ -0,0 +1,181 @@
# OSTree data formats
## On the topic of "smart servers"
One really crucial difference between OSTree and git is that git has a
"smart server". Even when fetching over `https://`, it isn't just a
static webserver, but one that e.g. dynamically computes and
compresses pack files for each client.
In contrast, the author of OSTree feels that for operating system
updates, many deployments will want to use simple static webservers,
the same target most package systems were designed to use. The
primary advantages are security and compute efficiency. Services like
Amazon S3 and CDNs are a canonical target, as well as a stock static
nginx server.
## The archive-z2 format
In the [repo](repo) section, the concept of objects was introduced,
where file/content objects are checksummed and managed individually.
(Unlike a package system, which operates on compressed aggregates).
The archive-z2 format simply gzip-compresses each content object.
Metadata objects are stored uncompressed. This means that it's easy
to serve via static HTTP.
When you commit new content, you will see new `.filez` files appearing
in `objects/`.
## archive-z2 efficiency
The advantages of `archive-z2`:
- It's easy to understand and implement
- Can be served directly over plain HTTP by a static webserver
- Clients can download/unpack updates incrementally
- Space efficient on the server
The biggest disadvantage of this format is that for a client to
perform an update, one HTTP request per changed file is required. In
some scenarios, this actually isn't bad at all, particularly with
techniques to reduce HTTP overhead, such as
[HTTP/2](https://en.wikipedia.org/wiki/HTTP/2).
In order to make this format work well, you should design your content
such that large data that changes infrequently (e.g. graphic images)
are stored separately from small frequently changing data (application
code).
Other disadvantages of `archive-z2`:
- It's quite bad when clients are performing an initial pull (without HTTP/2),
- One doesn't know the total size (compressed or uncompressed) of content
before downloading everything
## Aside: the bare and bare-user formats
The most common operation is to pull from an `archive-z2` repository
into a `bare` or `bare-user` formatted repository. These latter two
are not compressed on disk. In other words, pulling to them is
similar to unpacking (but not installing) an RPM/deb package.
The `bare-user` format is a bit special in that the uid/gid and xattrs
from the content are ignored. This is primarily useful if you want to
have the same OSTree-managed content that can be run on a host system
or an unprivileged container.
## Static deltas
OSTree itself was originally focused on a continous delivery model, where
client systems are expected to update regularly. However, many OS vendors
would like to supply content that's updated e.g. once a month or less often.
For this model, we can do a lot better to support batched updates than
a basic `archive-z2` repo. However, we still want to preserve the
model of "static webserver only". Given this, OSTree has gained the
concept of a "static delta".
These deltas are targeted to be a delta between two specific commit
objects, including "bsdiff" and "rsync-style" deltas within a content
object. Static deltas also support `from NULL`, where the client can
more efficiently download a commit object from scratch.
Effectively, we're spending server-side storage (and one-time compute
cost), and gaining efficiency in client network bandwith.
## Static delta repository layout
Since static deltas may not exist, the client first needs to attempt
to locate one. Suppose a client wants to retrieve commit `${new}`
while currently running `${current}`.
The first thing to understand is that in order to save space, these
two commits are "modified base64" - the `/` character is replaced with
`_`.
Like the commit objects, a "prefix directory" is used to make
management easier for filesystem tools
A delta is named `$(mbase64 $from)-$(mbase64 $to)`, for example
`GpTyZaVut2jXFPWnO4LJiKEdRTvOw_mFUCtIKW1NIX0-L8f+VVDkEBKNc1Ncd+mDUrSVR4EyybQGCkuKtkDnTwk`,
which in sha256 format is
`1a94f265a56eb768d714f5a73b82c988a11d453bcec3f985502b48296d4d217d-2fc7fe5550e410128d73535c77e98352b495478132c9b4060a4b8ab640e74f09`.
Finally, the actual content can be found in
`deltas/$fromprefix/$fromsuffix-$to`.
## Static delta internal structure
A delta is itself a directory. Inside, there is a file called
`superblock` which contains metadata. The rest of the files will be
integers bearing packs of content.
The file format of static deltas should be currently considered an
OSTree implementation detail. Obviously, nothing stops one from
writing code which is compatible with OSTree today. However, we would
like the flexibility to expand and change things, and having multiple
codebases makes that more problematic. Please contact the authors
with any requests.
That said, one critical thing to understand about the design is that
delta payloads are a bit more like "restricted programs" than they are
raw data. There's a "compilation" phase which generates output that
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).
### The delta superblock
The superblock contains:
- arbitrary metadata
- delta generation timestamp
- the new commit object
- An array of recursive deltas to apply
- An array of per-part metadata, including total object sizes (compressed and uncompressed),
- An array of fallback objects
Let's define a delta part, then return to discuss details:
## A delta part
A delta part is a combination of a raw blob of data, plus a very
restricted bytecode that operates on it. Say for example two files
happen to share a common section. It's possible for the delta
compilation to include that section once in the delta data blob, then
generate instructions to write out that blob twice when generating
both objects.
Realistically though, it's very common for most of a delta to just be
"stream of new objects" - if one considers it, it doesn't make sense
to have too much duplication inside operating system content at this
level.
So then, what's more interesting is that OSTree static deltas support
a per-file delta algorithm called
[bsdiff](https://github.com/mendsley/bsdiff) that most notably works
well on executable code.
The current delta compiler scans for files with maching basenamesin
each commit that have a similar size, and attempts a bsdiff between
them. (It would make sense later to have a build system provide a
hint for this - for example, files within a same package).
A generated bsdiff is included in the payload blob, and applying it is
an instruction.
## Fallback objects
It's possible for there to be large-ish files which might be resistant
to bsdiff. A good example is that it's common for operating systems
to use an "initramfs", which is itself a compressed filesystem. This
"internal compression" defeats bsdiff analysis.
For these types of objects, the delta superblock contains an array of
"fallback objects". These objects aren't included in the delta
parts - the client simply fetches them from the underlying `.filez`
object.

110
docs/manual/introduction.md Normal file
View File

@ -0,0 +1,110 @@
# OSTree Overview
## Introduction
OSTree an upgrade system for Linux-based operating systems that
performs atomic upgrades of complete filesystem trees. It is
not a package system; rather, it is intended to complement them.
A primary model is composing packages on a server, and then
replicating them to clients.
The underlying architecture might be summarized as "git for
operating system binaries". It operates in userspace, and will
work on top of any Linux filesystem. At its core is a git-like
content-addressed object store, and layered on top of that is
bootloader configuration, management of
`/etc`, and other functions to perform an
upgrade beyond just replicating files.
You can use OSTree standalone in the pure replication model,
but another approach is to add a package manager on top,
thus creating a hybrid tree/package system.
## Comparison with "package managers"
Because OSTree is designed for deploying core operating
systems, a comparison with traditional "package managers" such
as dpkg and rpm is illustrative. Packages are traditionally
composed of partial filesystem trees with metadata and scripts
attached, and these are dynamically assembled on the client
machine, after a process of dependency resolution.
In contrast, OSTree only supports recording and deploying
*complete* (bootable) filesystem trees. It
has no built-in knowledge of how a given filesystem tree was
generated or the origin of individual files, or dependencies,
descriptions of individual components. Put another way, OSTree
only handles delivery and deployment; you will likely still want
to include inside each tree metadata about the individual
components that went into the tree. For example, a system
administrator may want to know what version of OpenSSL was
included in your tree, so you should support the equivalent of
`rpm -q` or `dpkg -L`.
The OSTree core emphasizes replicating read-only OS trees via
HTTP, and where the OS includes (if desired) an entirely
separate mechanism to install applications, stored in `/var` if they're system global, or
`/home` for per-user
application installation. An example application mechanism is
http://docker.io/
However, it is entirely possible to use OSTree underneath a
package system, where the contents of `/usr` are computed on the client.
For example, when installing a package, rather than changing the
currently running filesystem, the package manager could assemble
a new filesystem tree that layers the new packages on top of a
base tree, record it in the local OSTree repository, and then
set it up for the next boot. To support this model, OSTree
provides an (introspectable) C shared library.
## Comparison with block/image replication
OSTree shares some similarity with "dumb" replication and
stateless deployments, such as the model common in "cloud"
deployments where nodes are booted from an (effectively)
readonly disk, and user data is kept on a different volumes.
The advantage of "dumb" replication, shared by both OSTree and
the cloud model, is that it's *reliable*
and *predictable*.
But unlike many default image-based deployments, OSTree supports
exactly two persistent writable directories that are preserved across
upgrades: `/etc` and `/var`.
Because OSTree operates at the Unix filesystem layer, it works
on top of any filesystem or block storage layout; it's possible
to replicate a given filesystem tree from an OSTree repository
into plain ext4, BTRFS, XFS, or in general any Unix-compatible
filesystem that supports hard links. Note: OSTree will
transparently take advantage of some BTRFS features if deployed
on it.
## Atomic transitions between parallel-installable read-only filesystem trees
Another deeply fundamental difference between both package
managers and image-based replication is that OSTree is
designed to parallel-install *multiple versions* of multiple
*independent* operating systems. OSTree
relies on a new toplevel `ostree` directory; it can in fact
parallel install inside an existing OS or distribution
occupying the physical `/` root.
On each client machine, there is an OSTree repository stored
in `/ostree/repo`, and a set of "deployments" stored in `/ostree/deploy/$OSNAME/$CHECKSUM`.
Each deployment is primarily composed of a set of hardlinks
into the repository. This means each version is deduplicated;
an upgrade process only costs disk space proportional to the
new files, plus some constant overhead.
The model OSTree emphasizes is that the OS read-only content
is kept in the classic Unix `/usr`; it comes with code to
create a Linux read-only bind mount to prevent inadvertent
corruption. There is exactly one `/var` writable directory shared
between each deployment for a given OS. The OSTree core code
does not touch content in this directory; it is up to the code
in each operating system for how to manage and upgrade state.
Finally, each deployment has its own writable copy of the
configuration store `/etc`. On upgrade, OSTree will
perform a basic 3-way diff, and apply any local changes to the
new copy, while leaving the old untouched.

90
docs/manual/repo.md Normal file
View File

@ -0,0 +1,90 @@
# Anatomy of an OSTree repository
## Core object types and data model
OSTree is deeply inspired by git; the core layer is a userspace
content-addressed versioning filesystem. It is worth taking some time
to familiarize yourself with
[Git Internals](http://git-scm.com/book/en/Git-Internals), as this
section will assume some knowledge of how git works.
Its object types are similar to git; it has commit objects and content
objects. Git has "tree" objects, whereas OSTree splits them into
"dirtree" and "dirmeta" objects. But unlike git, OSTree's checksums
are SHA256. And most crucially, its content objects include uid, gid,
and extended attributes (but still no timestamps).
### Commit objects
A commit object contains metadata such as a timestamp, a log
message, and most importantly, a reference to a
dirtree/dirmeta pair of checksums which describe the root
directory of the filesystem.
Also like git, each commit in OSTree can have a parent. It is
designed to store a history of your binary builds, just like git
stores a history of source control. However, OSTree also makes
it easy to delete data, under the assumption that you can
regenerate it from source code.
### Dirtree objects
A dirtree contains a sorted array of (filename, checksum)
pairs for content objects, and a second sorted array of
(filename, dirtree checksum, dirmeta checksum), which are
subdirectories.
### Dirmeta objects
In git, tree objects contain the metadata such as permissions
for their children. But OSTree splits this into a separate
object to avoid duplicating extended attribute listings.
### Content objects
Unlike the first three object types which are metadata, designed to be
`mmap()`ed, the content object has a separate internal header and
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.
# Repository types and locations
Also unlike git, an OSTree repository can be in one of three separate
modes: `bare`, `bare-user`, and `archive-z2`. A bare repository is
one where content files are just stored as regular files; it's
designed to be the source of a "hardlink farm", where each operating
system checkout is merely links into it. If you want to store files
owned by e.g. root in this mode, you must run OSTree as root.
The `bare-user` is a later addition that is like `bare` in that files
are unpacked, but it can (and should generally) be created as
non-root. In this mode, extended metadata such as owner uid, gid, and
extended attributes are stored but not actually applied.
The `bare-user` mode is useful for build systems that run as non-root
but want to generate root-owned content, as well as non-root container
systems.
In contrast, the `archive-z2` mode is designed for serving via plain
HTTP. Like tar files, it can be read/written by non-root users.
On an OSTree-deployed system, the "system repository" is
`/ostree/repo`. It can be read by any uid, but only written by root.
Unless the `--repo` argument is given to the <command>ostree</command>
command, it will operate on the system repository.
## Refs
Like git, OSTree uses "refs" to which are text files that point to
particular commits (i.e. filesystem trees). For example, the
gnome-ostree operating system creates trees named like
`exampleos/buildmaster/x86_64-runtime` and
`exampleos/buildmaster/x86_64-devel-debug`. These two refs point to
two different generated filesystem trees. In this example, the
"runtime" tree contains just enough to run a basic system, and
"devel-debug" contains all of the developer tools and debuginfo.
The `ostree` supports a simple syntax using the carat `^` to refer to
the parent of a given commit. For example,
`exampleos/buildmaster/x86_64-runtime^` refers to the previous build,
and `exampleos/buildmaster/x86_64-runtime^^` refers to the one before
that.

View File

@ -18,6 +18,7 @@
EXTRA_DIST += $(libglnx_srcpath)/README.md $(libglnx_srcpath)/COPYING EXTRA_DIST += $(libglnx_srcpath)/README.md $(libglnx_srcpath)/COPYING
libglnx_la_SOURCES = \ libglnx_la_SOURCES = \
$(libglnx_srcpath)/glnx-alloca.h \
$(libglnx_srcpath)/glnx-backport-autocleanups.h \ $(libglnx_srcpath)/glnx-backport-autocleanups.h \
$(libglnx_srcpath)/glnx-backport-autoptr.h \ $(libglnx_srcpath)/glnx-backport-autoptr.h \
$(libglnx_srcpath)/glnx-backports.h \ $(libglnx_srcpath)/glnx-backports.h \

47
libglnx/glnx-alloca.h Normal file
View File

@ -0,0 +1,47 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2014,2015 Colin Walters <walters@verbum.org>.
*
* 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 <stdlib.h>
#include <glib.h>
G_BEGIN_DECLS
/* Taken from https://github.com/systemd/systemd/src/basic/string-util.h
* at revision v228-666-gcf6c8c4
*/
#define glnx_strjoina(a, ...) \
({ \
const char *_appendees_[] = { a, __VA_ARGS__ }; \
char *_d_, *_p_; \
int _len_ = 0; \
unsigned _i_; \
for (_i_ = 0; _i_ < G_N_ELEMENTS(_appendees_) && _appendees_[_i_]; _i_++) \
_len_ += strlen(_appendees_[_i_]); \
_p_ = _d_ = alloca(_len_ + 1); \
for (_i_ = 0; _i_ < G_N_ELEMENTS(_appendees_) && _appendees_[_i_]; _i_++) \
_p_ = stpcpy(_p_, _appendees_[_i_]); \
*_p_ = 0; \
_d_; \
})
G_END_DECLS

View File

@ -31,13 +31,6 @@ g_autoptr_cleanup_generic_gfree (void *p)
g_free (*pp); g_free (*pp);
} }
static inline void
g_autoptr_cleanup_gstring_free (GString *string)
{
if (string)
g_string_free (string, TRUE);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GAsyncQueue, g_async_queue_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GAsyncQueue, g_async_queue_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GBookmarkFile, g_bookmark_file_free) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GBookmarkFile, g_bookmark_file_free)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GBytes, g_bytes_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GBytes, g_bytes_unref)
@ -55,7 +48,6 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GPtrArray, g_ptr_array_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GMainContext, g_main_context_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GMainContext, g_main_context_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GMainLoop, g_main_loop_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GMainLoop, g_main_loop_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GSource, g_source_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GSource, g_source_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GString, g_autoptr_cleanup_gstring_free)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GMappedFile, g_mapped_file_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GMappedFile, g_mapped_file_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GMarkupParseContext, g_markup_parse_context_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GMarkupParseContext, g_markup_parse_context_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(gchar, g_free) G_DEFINE_AUTOPTR_CLEANUP_FUNC(gchar, g_free)
@ -113,5 +105,20 @@ G_DEFINE_AUTOPTR_CLEANUP_FUNC(GTlsDatabase, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GTlsInteraction, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GTlsInteraction, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GDBusConnection, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GDBusConnection, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GDBusMessage, g_object_unref) G_DEFINE_AUTOPTR_CLEANUP_FUNC(GDBusMessage, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GZlibCompressor, g_object_unref)
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GZlibDecompressor, g_object_unref)
#endif
#if !GLIB_CHECK_VERSION(2, 45, 8)
static inline void
g_autoptr_cleanup_gstring_free (GString *string)
{
if (string)
g_string_free (string, TRUE);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GString, g_autoptr_cleanup_gstring_free)
#endif #endif

View File

@ -241,7 +241,7 @@ glnx_console_progress_text_percent (const char *text,
if (textlen > 0) if (textlen > 0)
{ {
fwrite (text, 1, textlen - 1, stdout); fwrite (text, 1, textlen, stdout);
fputc (' ', stdout); fputc (' ', stdout);
} }
@ -285,5 +285,5 @@ glnx_console_unlock (GLnxConsoleRef *console)
if (console->is_tty) if (console->is_tty)
fputc ('\n', stdout); fputc ('\n', stdout);
locked = FALSE; locked = console->locked = FALSE;
} }

View File

@ -45,7 +45,8 @@ guint glnx_console_columns (void);
static inline void static inline void
glnx_console_ref_cleanup (GLnxConsoleRef *p) glnx_console_ref_cleanup (GLnxConsoleRef *p)
{ {
glnx_console_unlock (p); if (p->locked)
glnx_console_unlock (p);
} }
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GLnxConsoleRef, glnx_console_ref_cleanup) G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GLnxConsoleRef, glnx_console_ref_cleanup)

View File

@ -168,6 +168,8 @@ glnx_dirfd_iterator_next_dent (GLnxDirFdIterator *dfd_iter,
gboolean ret = FALSE; gboolean ret = FALSE;
GLnxRealDirfdIterator *real_dfd_iter = (GLnxRealDirfdIterator*) dfd_iter; GLnxRealDirfdIterator *real_dfd_iter = (GLnxRealDirfdIterator*) dfd_iter;
g_return_val_if_fail (out_dent, FALSE);
if (g_cancellable_set_error_if_cancelled (cancellable, error)) if (g_cancellable_set_error_if_cancelled (cancellable, error))
goto out; goto out;
@ -189,6 +191,53 @@ glnx_dirfd_iterator_next_dent (GLnxDirFdIterator *dfd_iter,
return ret; return ret;
} }
/**
* glnx_dirfd_iterator_next_dent_ensure_dtype:
* @dfd_iter: A directory iterator
* @out_dent: (out) (transfer none): Pointer to dirent; do not free
* @cancellable: Cancellable
* @error: Error
*
* A variant of @glnx_dirfd_iterator_next_dent, which will ensure the
* `dent->d_type` member is filled in by calling `fstatat`
* automatically if the underlying filesystem type sets `DT_UNKNOWN`.
*/
gboolean
glnx_dirfd_iterator_next_dent_ensure_dtype (GLnxDirFdIterator *dfd_iter,
struct dirent **out_dent,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
struct dirent *ret_dent;
g_return_val_if_fail (out_dent, FALSE);
if (!glnx_dirfd_iterator_next_dent (dfd_iter, out_dent, cancellable, error))
goto out;
ret_dent = *out_dent;
if (ret_dent)
{
if (ret_dent->d_type == DT_UNKNOWN)
{
struct stat stbuf;
if (TEMP_FAILURE_RETRY (fstatat (dfd_iter->fd, ret_dent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW)) != 0)
{
glnx_set_error_from_errno (error);
goto out;
}
ret_dent->d_type = IFTODT (stbuf.st_mode);
}
}
ret = TRUE;
out:
return ret;
}
/** /**
* glnx_dirfd_iterator_clear: * glnx_dirfd_iterator_clear:
* @dfd_iter: Iterator, will be de-initialized * @dfd_iter: Iterator, will be de-initialized

View File

@ -60,6 +60,10 @@ gboolean glnx_dirfd_iterator_next_dent (GLnxDirFdIterator *dfd_iter,
struct dirent **out_dent, struct dirent **out_dent,
GCancellable *cancellable, GCancellable *cancellable,
GError **error); GError **error);
gboolean glnx_dirfd_iterator_next_dent_ensure_dtype (GLnxDirFdIterator *dfd_iter,
struct dirent **out_dent,
GCancellable *cancellable,
GError **error);
void glnx_dirfd_iterator_clear (GLnxDirFdIterator *dfd_iter); void glnx_dirfd_iterator_clear (GLnxDirFdIterator *dfd_iter);
G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GLnxDirFdIterator, glnx_dirfd_iterator_clear) G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GLnxDirFdIterator, glnx_dirfd_iterator_clear)

View File

@ -354,7 +354,7 @@ static int btrfs_reflink(int infd, int outfd) {
return 0; return 0;
} }
static int loop_write(int fd, const void *buf, size_t nbytes) { int glnx_loop_write(int fd, const void *buf, size_t nbytes) {
const uint8_t *p = buf; const uint8_t *p = buf;
g_return_val_if_fail(fd >= 0, -1); g_return_val_if_fail(fd >= 0, -1);
@ -437,7 +437,7 @@ static int copy_bytes(int fdf, int fdt, off_t max_bytes, bool try_reflink) {
if (n == 0) /* EOF */ if (n == 0) /* EOF */
break; break;
r = loop_write(fdt, buf, (size_t) n); r = glnx_loop_write(fdt, buf, (size_t) n);
if (r < 0) if (r < 0)
return r; return r;
} }
@ -685,7 +685,7 @@ glnx_file_replace_contents_with_perms_at (int dfd,
goto out; goto out;
} }
if ((r = loop_write (fd, buf, len)) != 0) if ((r = glnx_loop_write (fd, buf, len)) != 0)
{ {
errno = -r; errno = -r;
glnx_set_error_from_errno (error); glnx_set_error_from_errno (error);

View File

@ -102,6 +102,9 @@ glnx_readlinkat_malloc (int dfd,
GCancellable *cancellable, GCancellable *cancellable,
GError **error); GError **error);
int
glnx_loop_write (int fd, const void *buf, size_t nbytes);
typedef enum { typedef enum {
GLNX_FILE_COPY_OVERWRITE, GLNX_FILE_COPY_OVERWRITE,
GLNX_FILE_COPY_NOXATTRS, GLNX_FILE_COPY_NOXATTRS,

View File

@ -24,6 +24,7 @@
G_BEGIN_DECLS G_BEGIN_DECLS
#include <glnx-alloca.h>
#include <glnx-local-alloc.h> #include <glnx-local-alloc.h>
#include <glnx-backport-autocleanups.h> #include <glnx-backport-autocleanups.h>
#include <glnx-backports.h> #include <glnx-backports.h>

70
man/ostree-export.xml Normal file
View File

@ -0,0 +1,70 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!--
Copyright 2016 Colin Walters <walters@verbum.org>
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.
-->
<refentry id="ostree">
<refentryinfo>
<title>ostree export</title>
<productname>OSTree</productname>
<authorgroup>
<author>
<contrib>Developer</contrib>
<firstname>Colin</firstname>
<surname>Walters</surname>
<email>walters@verbum.org</email>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>ostree export</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>ostree-export</refname>
<refpurpose>Generate a tar archive from an OSTree commit</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>ostree export</command> <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="req">BRANCH</arg>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
This command generates a GNU tar formatted archive from an
OSTree commit. This is useful for cases like backups,
converting OSTree commits into Docker images, and the like.
</para>
</refsect1>
<refsect1>
<title>Example</title>
<para><command>$ ostree export exampleos/x86_64/standard | gzip > exampleos-standard.tar.gz</command></para>
</refsect1>
</refentry>

View File

@ -111,14 +111,37 @@ Boston, MA 02111-1307, USA.
<title>Description</title> <title>Description</title>
<para> <para>
Downloads all content corresponding to the provided branch or commit from the given remote. This command can retrieve just a specific commit, or go all
the way to performing a full mirror of the remote
repository. If no <literal>BRANCH</literal> is specified,
all branches are retrieved.
</para> </para>
<para>
A special syntax in the <literal>@</literal> character allows
specifying a specific commit to retrieve from a branch. This
</para>
</refsect1> </refsect1>
<refsect1> <refsect1>
<title>Example</title> <title>Example</title>
<para><command>$ ostree pull remote_name</command></para> <para><command>$ ostree --repo=repo pull --depth=-1 --mirror remote_name</command></para>
<para>Perform a complete mirror of the remote. (This is
likely most useful if your repository is also
<literal>archive-z2</literal> mode)</para>
<para><command>$ ostree --repo=repo pull remote_name exampleos/x86_64/standard</command></para>
<para>Fetch the most recent commit to <literal>exampleos/x86_64/standard</literal>.</para>
<para><command>$ ostree --repo=repo pull remote_name exampleos/x86_64/standard@98ea6e4f216f2fb4b69fff9b3a44842c38686ca685f3f55dc48c5d3fb1107be4</command></para>
<para>Download the specific commit starting with
<literal>98ea6e</literal> as if it was the latest commit for
<literal>exampleos/x86_64/standard</literal>.</para>
</refsect1> </refsect1>
</refentry> </refentry>

104
man/rofiles-fuse.xml Normal file
View File

@ -0,0 +1,104 @@
<?xml version='1.0'?> <!--*-nxml-*-->
<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<!--
Copyright 2016 Colin Walters <walters@verbum.org>
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.
-->
<refentry id="ostree">
<refentryinfo>
<title>rofiles-fuse</title>
<productname>rofiles-fuse</productname>
<authorgroup>
<author>
<contrib>Developer</contrib>
<firstname>Colin</firstname>
<surname>Walters</surname>
<email>walters@verbum.org</email>
</author>
</authorgroup>
</refentryinfo>
<refmeta>
<refentrytitle>rofiles-fuse</refentrytitle>
<manvolnum>1</manvolnum>
</refmeta>
<refnamediv>
<refname>rofiles-fuse</refname>
<refpurpose>Use FUSE to create a view where directories are writable, files are immutable</refpurpose>
</refnamediv>
<refsynopsisdiv>
<cmdsynopsis>
<command>rofiles-fuse SRCDIR MNTPOINT</command>
</cmdsynopsis>
</refsynopsisdiv>
<refsect1>
<title>Description</title>
<para>
Creating a checkout from an OSTree repository by default
uses hard links, which means an in-place mutation to any
file corrupts the repository and all checkouts. This can be
problematic if one wishes to run arbitrary programs against
such a checkout. For example, RPM <literal>%post</literal>
scripts or equivalent.
</para>
<para>
In the case where one wants to create a tree commit derived
from other content, using <command>rofiles-fuse</command> in
concert with <command>ostree commit
--link-checkout-speedup</command> (or the underlying API)
can ensure that only new files are checksummed.
</para>
</refsect1>
<refsect1>
<title>Example: Update an OSTree commit</title>
<programlisting>
# Initialize a checkout and mount
$ ostree --repo=repo checkout somebranch branch-checkout
$ mkdir mnt
$ rofiles-fuse branch-checkout mnt
# Now, arbitrary changes to mnt/ are reflected in branch-checkout
$ echo somenewcontent > mnt/anewfile
$ mkdir mnt/anewdir
$ rm mnt/someoriginalcontent -rf
# Commit and cleanup
$ fusermount -u mnt
$ ostree --repo=repo commit --link-checkout-speedup -b somebranch -s 'Commit new content' --tree=dir=branch-checkout
$ rm mnt branch-checkout -rf
</programlisting>
</refsect1>
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>ostree</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>
</refentry>

View File

@ -0,0 +1,62 @@
#!/usr/bin/env bash
#
# Generate multiple variants of static deltas in a repo, then use them
# to upgrade in a test repository, verifying that fsck works.
#
# This test is manual so it's easy to run against arbitrary content.
set -euo pipefail
repo=$(pwd)/${1}
shift
branch=${1}
shift
from=$(ostree --repo=${repo} rev-parse "${branch}"^)
to=$(ostree --repo=${repo} rev-parse ${branch})
tmpdir=$(mktemp -d /var/tmp/ostree-delta-check.XXXXXX)
cd ${tmpdir}
touch ${tmpdir}/.tmp
echo "Using tmpdir ${tmpdir}"
cleanup_tmpdir() {
if test -f ${tmpdir}/.tmp; then
rm -rf ${tmpdir}
fi
}
if test -z "${PRESERVE_TMP:-}"; then
trap cleanup_tmpdir EXIT
fi
fatal() {
echo "$@"
exit 1
}
assert_streq() {
if ! test $1 = $2; then
fatal "assertion failed: $1 = $2"
fi
}
validate_delta_options() {
mkdir testrepo
ostree --repo=testrepo init --mode=bare-user
ostree --repo=testrepo remote add --set=gpg-verify=false local file://${repo}
ostree --repo=${repo} static-delta generate $@ ${from} ${to}
ostree --repo=testrepo pull local ${branch}@${from}
assert_streq $(ostree --repo=testrepo rev-parse ${branch}) ${from}
ostree --repo=testrepo pull local ${branch}
assert_streq $(ostree --repo=testrepo rev-parse ${branch}) ${to}
ostree --repo=testrepo fsck
rm testrepo -rf
}
set -x
validate_delta_options
validate_delta_options --inline
validate_delta_options --disable-bsdiff

11
mkdocs.yml Normal file
View File

@ -0,0 +1,11 @@
site_name: My Docs
pages:
- Home: 'index.md'
- Contributing: 'CONTRIBUTING.md'
- Manual:
- Introduction: 'manual/introduction.md'
- Repository: 'manual/repo.md'
- Deployments: 'manual/deployment.md'
- Atomic Upgrades: 'manual/atomic-upgrades.md'
- Adapting Existing Systems: 'manual/adapting-existing.md'
- Formats: 'manual/formats.md'

View File

@ -19,11 +19,14 @@ BuildRequires: pkgconfig(libgsystem)
BuildRequires: pkgconfig(e2p) BuildRequires: pkgconfig(e2p)
# Extras # Extras
BuildRequires: pkgconfig(libarchive) BuildRequires: pkgconfig(libarchive)
BuildRequires: pkgconfig(fuse)
BuildRequires: pkgconfig(libselinux) BuildRequires: pkgconfig(libselinux)
BuildRequires: libcap-devel
BuildRequires: gpgme-devel BuildRequires: gpgme-devel
BuildRequires: pkgconfig(systemd) BuildRequires: pkgconfig(systemd)
BuildRequires: /usr/bin/g-ir-scanner BuildRequires: /usr/bin/g-ir-scanner
BuildRequires: dracut BuildRequires: dracut
BuildRequires: bison
# Runtime requirements # Runtime requirements
Requires: dracut Requires: dracut
@ -53,6 +56,14 @@ Requires: grub2
%description grub2 %description grub2
GRUB2 integration for OSTree GRUB2 integration for OSTree
%package fuse
Summary: FUSE utilities for OSTree
Group: Development/Libraries
Requires: fuse
%description fuse
%{summary}
%prep %prep
%setup -q -n ostree-%{version} %setup -q -n ostree-%{version}
@ -106,3 +117,6 @@ rm -rf $RPM_BUILD_ROOT
%files grub2 %files grub2
%{_sysconfdir}/grub.d/*ostree %{_sysconfdir}/grub.d/*ostree
%{_libexecdir}/ostree/grub2* %{_libexecdir}/ostree/grub2*
%files fuse
%{_bindir}/rofiles-fuse

View File

@ -1,158 +0,0 @@
OSTree Static Object Deltas
===========================
Currently, OSTree's "archive-z2" mode stores both metadata and content
objects as individual files in the filesystem. Content objects are
zlib-compressed.
The advantage of this is model are:
0) It's easy to understand and implement
1) Can be served directly over plain HTTP by a static webserver
2) Space efficient on the server
However, it can be inefficient both for large updates and small ones:
0) For large tree changes (such as going from -runtime to
-devel-debug, or major version upgrades), this can mean thousands
and thousands of HTTP requests. The overhead for that is very
large (until SPDY/HTTP2.0), and will be catastrophically bad if the
webserver is not configured with KeepAlive.
1) Small changes (typo in gnome-shell .js file) still require around
5 metadata HTTP requests, plus a redownload of the whole file.
Why not smart servers?
======================
Smart servers (custom daemons, or just CGI scripts) as git has are not
under consideration for this proposal. OSTree is designed for the
same use case as GNU/Linux distribution package systems are, where
content is served by a network of volunteer mirrors that will
generally not run custom code.
In particular, Amazon S3 style dumb content servers is a very
important use case, as is being able to apply updates from static
media like DVD-ROM.
Finding Static Deltas
=====================
Since static deltas may not exist, the client first needs to attempt
to locate one. Suppose a client wants to retrieve commit ${new} while
currently running ${current}. The first thing to fetch is the delta
metadata, called "meta". It can be found at
${repo}/deltas/${current}-${new}/meta.
FIXME: GPG signatures (.metameta?) Or include commit object in meta?
But we would then be forced to verify the commit only after processing
the entirety of the delta, which is dangerous. I think we need to
require signing deltas.
Delta Bytecode Format
=====================
A delta-part has the following form:
byte compression-type (0 = none, 'g' = gzip')
REPEAT[(varint size, delta-part-content)]
delta-part-content:
byte[] payload
ARRAY[operation]
The rationale for having delta-part is that it allows easy incremental
resumption of downloads. The client can look at the delta descriptor
and skip downloading delta-parts for which it already has the
contained objects. This is better than simply resuming a gigantic
file because if the client decides to fetch a slightly newer version,
it's very probable that some of the downloading we've already done is
still useful.
For the actual delta payload, it comes as a stream of pair of
(payload, operation) so that it can be processed while being
decompressed.
Finally, the delta-part-content is effectively a high level bytecode
for a stack-oriented machine. It iterates on the array of objects in
order. The following operations are available:
FETCH
Fall back to fetching the current object individually. Move
to the next object.
WRITE(array[(varint offset, varint length)])
Write from current input target (default payload) to output.
GUNZIP(array[(varint offset, varint length)])
gunzip from current input target (default payload) to output.
CLOSE
Close the current output target, and proceed to the next; if the
output object was a temporary, the output resets to the current
object.
# Change the input source to an object
READOBJECT(csum object)
Set object as current input target
# Change the input source to payload
READPAYLOAD
Set payload as current input target
Compiling Deltas
================
After reading the above, you may be wondering how we actually *make*
these deltas. I envison a strategy similar to that employed by
Chromium autoupdate:
http://www.chromium.org/chromium-os/chromiumos-design-docs/autoupdate-details
Something like this would be a useful initial algorithm:
1) Compute the set of added objects NEW
2) For each object in NEW:
- Look for a the set of "superficially similar" objects in the
previous tree, using heuristics based first on filename (including
prefix), then on size. Call this set CANDIDATES.
For each entry in CANDIDATES:
- Try doing a bup/librsync style rolling checksum, and compute the
list of changed blocks.
- Try gzip-compressing it
3) Choose the lowest cost method for each NEW object, and partition
the program for each method into deltapart-sized chunks.
However, there are many other possibilities, that could be used in a
hybrid mode with the above. For example, we could try to find similar
objects, and gzip them together. This would be a *very* useful
strategy for things like the 9000 Boost headers which have massive
amounts of redundant data.
Notice too that the delta format supports falling back to retrieving
individual objects. For cases like the initramfs which is compressed
inside the tree with gzip, we're not going to find an efficient way to
sync it, so the delta compiler should just fall back to fetching it
individually.
Which Deltas To Create?
=======================
Going back to the start, there are two cases to optimize for:
1) Incremental upgrades between builds
2) Major version upgrades
A command line operation would look something like this:
$ ostree --repo=/path/to/repo gendelta --ref-prefix=gnome-ostree/buildmaster/ --strategy=latest --depth=5
This would tell ostree to generate deltas from each of the last 4
commits to each ref (e.g. gnome-ostree/buildmaster/x86_64-runtime) to
the latest commit. It might also be possible of course to have
--strategy=incremental where we generate a delta between each commit.
I suspect that'd be something to do if one has a *lot* of disk space
to spend, and there's a reason for clients to be fetching individual
refs.
$ ostree --repo=/path/to/repo gendelta --from=gnome-ostree/3.10/x86_64-runtime --to=gnome-ostree/buildmaster/x86_64-runtime
This is an obvious one - generate a delta from the last stable release
to the current development head.

View File

@ -388,8 +388,6 @@ rm -f ${grub_cfg}.new
if (!g_file_copy (self->config_path_efi, config_path_efi_old, if (!g_file_copy (self->config_path_efi, config_path_efi_old,
G_FILE_COPY_OVERWRITE, cancellable, NULL, NULL, error)) G_FILE_COPY_OVERWRITE, cancellable, NULL, NULL, error))
goto out; goto out;
if (!ot_gfile_ensure_unlinked (config_path_efi_old, cancellable, error))
goto out;
/* NOTE: NON-ATOMIC REPLACEMENT; WE can't do anything else on FAT; /* NOTE: NON-ATOMIC REPLACEMENT; WE can't do anything else on FAT;
* see https://bugzilla.gnome.org/show_bug.cgi?id=724246 * see https://bugzilla.gnome.org/show_bug.cgi?id=724246

View File

@ -23,6 +23,7 @@
#include "ostree-cmdprivate.h" #include "ostree-cmdprivate.h"
#include "ostree-repo-private.h" #include "ostree-repo-private.h"
#include "ostree-core-private.h" #include "ostree-core-private.h"
#include "ostree-repo-static-delta-private.h"
#include "ostree-sysroot.h" #include "ostree-sysroot.h"
#include "ostree-bootloader-grub2.h" #include "ostree-bootloader-grub2.h"
@ -44,7 +45,8 @@ const OstreeCmdPrivateVTable *
ostree_cmd__private__ (void) ostree_cmd__private__ (void)
{ {
static OstreeCmdPrivateVTable table = { static OstreeCmdPrivateVTable table = {
impl_ostree_generate_grub2_config impl_ostree_generate_grub2_config,
_ostree_repo_static_delta_dump
}; };
return &table; return &table;

View File

@ -26,6 +26,7 @@ G_BEGIN_DECLS
typedef struct { typedef struct {
gboolean (* ostree_generate_grub2_config) (OstreeSysroot *sysroot, int bootversion, int target_fd, GCancellable *cancellable, GError **error); gboolean (* ostree_generate_grub2_config) (OstreeSysroot *sysroot, int bootversion, int target_fd, GCancellable *cancellable, GError **error);
gboolean (* ostree_static_delta_dump) (OstreeRepo *repo, const char *delta_id, GCancellable *cancellable, GError **error);
} OstreeCmdPrivateVTable; } OstreeCmdPrivateVTable;
const OstreeCmdPrivateVTable * const OstreeCmdPrivateVTable *

View File

@ -1069,7 +1069,7 @@ int
ostree_cmp_checksum_bytes (const guchar *a, ostree_cmp_checksum_bytes (const guchar *a,
const guchar *b) const guchar *b)
{ {
return memcmp (a, b, 32); return memcmp (a, b, OSTREE_SHA256_DIGEST_LEN);
} }
/** /**
@ -1151,7 +1151,7 @@ ostree_checksum_inplace_to_bytes (const char *checksum,
guint i; guint i;
guint j; guint j;
for (i = 0, j = 0; i < 32; i += 1, j += 2) for (i = 0, j = 0; i < OSTREE_SHA256_DIGEST_LEN; i += 1, j += 2)
{ {
gint big, little; gint big, little;
@ -1177,7 +1177,7 @@ ostree_checksum_inplace_to_bytes (const char *checksum,
guchar * guchar *
ostree_checksum_to_bytes (const char *checksum) ostree_checksum_to_bytes (const char *checksum)
{ {
guchar *ret = g_malloc (32); guchar *ret = g_malloc (OSTREE_SHA256_DIGEST_LEN);
ostree_checksum_inplace_to_bytes (checksum, ret); ostree_checksum_inplace_to_bytes (checksum, ret);
return ret; return ret;
} }
@ -1191,9 +1191,9 @@ ostree_checksum_to_bytes (const char *checksum)
GVariant * GVariant *
ostree_checksum_to_bytes_v (const char *checksum) ostree_checksum_to_bytes_v (const char *checksum)
{ {
guchar result[32]; guchar result[OSTREE_SHA256_DIGEST_LEN];
ostree_checksum_inplace_to_bytes (checksum, result); ostree_checksum_inplace_to_bytes (checksum, result);
return ot_gvariant_new_bytearray ((guchar*)result, 32); return ot_gvariant_new_bytearray ((guchar*)result, OSTREE_SHA256_DIGEST_LEN);
} }
/** /**
@ -1210,7 +1210,7 @@ ostree_checksum_inplace_from_bytes (const guchar *csum,
static const gchar hexchars[] = "0123456789abcdef"; static const gchar hexchars[] = "0123456789abcdef";
guint i, j; guint i, j;
for (i = 0, j = 0; i < 32; i++, j += 2) for (i = 0, j = 0; i < OSTREE_SHA256_DIGEST_LEN; i++, j += 2)
{ {
guchar byte = csum[i]; guchar byte = csum[i];
buf[j] = hexchars[byte >> 4]; buf[j] = hexchars[byte >> 4];
@ -1242,7 +1242,7 @@ ostree_checksum_b64_inplace_from_bytes (const guchar *csum,
* a lot easier to reuse GLib's base64 encoder and postprocess it * a lot easier to reuse GLib's base64 encoder and postprocess it
* to replace the '/' with '_'. * to replace the '/' with '_'.
*/ */
outlen = g_base64_encode_step (csum, 32, FALSE, tmpbuf, &state, &save); outlen = g_base64_encode_step (csum, OSTREE_SHA256_DIGEST_LEN, FALSE, tmpbuf, &state, &save);
outlen += g_base64_encode_close (FALSE, tmpbuf+outlen, &state, &save); outlen += g_base64_encode_close (FALSE, tmpbuf+outlen, &state, &save);
g_assert (outlen == 44); g_assert (outlen == 44);
@ -1299,7 +1299,7 @@ ostree_checksum_bytes_peek (GVariant *bytes)
gsize n_elts; gsize n_elts;
const guchar *ret; const guchar *ret;
ret = g_variant_get_fixed_array (bytes, &n_elts, 1); ret = g_variant_get_fixed_array (bytes, &n_elts, 1);
if (G_UNLIKELY (n_elts != 32)) if (G_UNLIKELY (n_elts != OSTREE_SHA256_DIGEST_LEN))
return NULL; return NULL;
return ret; return ret;
} }
@ -1434,20 +1434,20 @@ _ostree_get_relative_static_delta_path (const char *from,
const char *to, const char *to,
const char *target) const char *target)
{ {
guint8 csum_to[32]; guint8 csum_to[OSTREE_SHA256_DIGEST_LEN];
char to_b64[44]; char to_b64[44];
guint8 csum_to_copy[32]; guint8 csum_to_copy[OSTREE_SHA256_DIGEST_LEN];
GString *ret = g_string_new ("deltas/"); GString *ret = g_string_new ("deltas/");
ostree_checksum_inplace_to_bytes (to, csum_to); ostree_checksum_inplace_to_bytes (to, csum_to);
ostree_checksum_b64_inplace_from_bytes (csum_to, to_b64); ostree_checksum_b64_inplace_from_bytes (csum_to, to_b64);
ostree_checksum_b64_inplace_to_bytes (to_b64, csum_to_copy); ostree_checksum_b64_inplace_to_bytes (to_b64, csum_to_copy);
g_assert (memcmp (csum_to, csum_to_copy, 32) == 0); g_assert (memcmp (csum_to, csum_to_copy, OSTREE_SHA256_DIGEST_LEN) == 0);
if (from != NULL) if (from != NULL)
{ {
guint8 csum_from[32]; guint8 csum_from[OSTREE_SHA256_DIGEST_LEN];
char from_b64[44]; char from_b64[44];
ostree_checksum_inplace_to_bytes (from, csum_from); ostree_checksum_inplace_to_bytes (from, csum_from);

View File

@ -50,6 +50,8 @@ G_BEGIN_DECLS
*/ */
#define OSTREE_MAX_RECURSION (256) #define OSTREE_MAX_RECURSION (256)
#define OSTREE_SHA256_DIGEST_LEN (32)
/** /**
* OstreeObjectType: * OstreeObjectType:
* @OSTREE_OBJECT_TYPE_FILE: Content; regular file, symbolic link * @OSTREE_OBJECT_TYPE_FILE: Content; regular file, symbolic link

View File

@ -277,7 +277,12 @@ static void
session_thread_set_tls_interaction_cb (ThreadClosure *thread_closure, session_thread_set_tls_interaction_cb (ThreadClosure *thread_closure,
gpointer data) gpointer data)
{ {
GTlsInteraction *interaction = data; GTlsCertificate *cert = data;
glnx_unref_object OstreeTlsCertInteraction *interaction = NULL;
/* The GTlsInteraction instance must be created in the
* session thread so it uses the correct GMainContext. */
interaction = _ostree_tls_cert_interaction_new (cert);
g_object_set (thread_closure->session, g_object_set (thread_closure->session,
SOUP_SESSION_TLS_INTERACTION, SOUP_SESSION_TLS_INTERACTION,
@ -645,7 +650,7 @@ _ostree_fetcher_set_client_cert (OstreeFetcher *self,
#ifdef HAVE_LIBSOUP_CLIENT_CERTS #ifdef HAVE_LIBSOUP_CLIENT_CERTS
session_thread_idle_add (self->thread_closure, session_thread_idle_add (self->thread_closure,
session_thread_set_tls_interaction_cb, session_thread_set_tls_interaction_cb,
_ostree_tls_cert_interaction_new (cert), g_object_ref (cert),
(GDestroyNotify) g_object_unref); (GDestroyNotify) g_object_unref);
#else #else
g_warning ("This version of OSTree is compiled without client side certificate support"); g_warning ("This version of OSTree is compiled without client side certificate support");

View File

@ -32,6 +32,8 @@
#include "ostree-core-private.h" #include "ostree-core-private.h"
#include "ostree-repo-private.h" #include "ostree-repo-private.h"
#define WHITEOUT_PREFIX ".wh."
static gboolean static gboolean
checkout_object_for_uncompressed_cache (OstreeRepo *self, checkout_object_for_uncompressed_cache (OstreeRepo *self,
const char *loose_path, const char *loose_path,
@ -102,9 +104,16 @@ checkout_object_for_uncompressed_cache (OstreeRepo *self,
return ret; return ret;
} }
static gboolean
fsync_is_enabled (OstreeRepo *self,
OstreeRepoCheckoutOptions *options)
{
return !(self->disable_fsync || options->disable_fsync);
}
static gboolean static gboolean
write_regular_file_content (OstreeRepo *self, write_regular_file_content (OstreeRepo *self,
OstreeRepoCheckoutMode mode, OstreeRepoCheckoutOptions *options,
GOutputStream *output, GOutputStream *output,
GFileInfo *file_info, GFileInfo *file_info,
GVariant *xattrs, GVariant *xattrs,
@ -113,6 +122,7 @@ write_regular_file_content (OstreeRepo *self,
GError **error) GError **error)
{ {
gboolean ret = FALSE; gboolean ret = FALSE;
const OstreeRepoCheckoutMode mode = options->mode;
int fd; int fd;
int res; int res;
@ -154,12 +164,12 @@ write_regular_file_content (OstreeRepo *self,
} }
} }
if (!self->disable_fsync) if (fsync_is_enabled (self, options))
{ {
if (fsync (fd) == -1) if (fsync (fd) == -1)
{ {
gs_set_error_from_errno (error, errno); gs_set_error_from_errno (error, errno);
goto out; goto out;
} }
} }
@ -238,7 +248,7 @@ checkout_file_from_input_at (OstreeRepo *self,
temp_out = g_unix_output_stream_new (fd, TRUE); temp_out = g_unix_output_stream_new (fd, TRUE);
fd = -1; /* Transfer ownership */ fd = -1; /* Transfer ownership */
if (!write_regular_file_content (self, options->mode, temp_out, file_info, xattrs, input, if (!write_regular_file_content (self, options, temp_out, file_info, xattrs, input,
cancellable, error)) cancellable, error))
goto out; goto out;
} }
@ -298,7 +308,7 @@ checkout_file_unioning_from_input_at (OstreeRepo *repo,
cancellable, error)) cancellable, error))
goto out; goto out;
if (!write_regular_file_content (repo, options->mode, temp_out, file_info, xattrs, input, if (!write_regular_file_content (repo, options, temp_out, file_info, xattrs, input,
cancellable, error)) cancellable, error))
goto out; goto out;
} }
@ -388,20 +398,46 @@ checkout_one_file_at (OstreeRepo *repo,
const char *checksum; const char *checksum;
gboolean is_symlink; gboolean is_symlink;
gboolean can_cache; gboolean can_cache;
gboolean did_hardlink = FALSE; gboolean need_copy = TRUE;
char loose_path_buf[_OSTREE_LOOSE_PATH_MAX]; char loose_path_buf[_OSTREE_LOOSE_PATH_MAX];
g_autoptr(GInputStream) input = NULL; g_autoptr(GInputStream) input = NULL;
g_autoptr(GVariant) xattrs = NULL; g_autoptr(GVariant) xattrs = NULL;
gboolean is_whiteout;
is_symlink = g_file_info_get_file_type (source_info) == G_FILE_TYPE_SYMBOLIC_LINK; is_symlink = g_file_info_get_file_type (source_info) == G_FILE_TYPE_SYMBOLIC_LINK;
checksum = ostree_repo_file_get_checksum ((OstreeRepoFile*)source); checksum = ostree_repo_file_get_checksum ((OstreeRepoFile*)source);
/* Try to do a hardlink first, if it's a regular file. This also is_whiteout = !is_symlink && options->process_whiteouts &&
* traverses all parent repos. g_str_has_prefix (destination_name, WHITEOUT_PREFIX);
/* First, see if it's a Docker whiteout,
* https://github.com/docker/docker/blob/1a714e76a2cb9008cd19609059e9988ff1660b78/pkg/archive/whiteouts.go
*/ */
if (!is_symlink) if (is_whiteout)
{ {
const char *name = destination_name + (sizeof (WHITEOUT_PREFIX) - 1);
if (!name[0])
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid empty whiteout '%s'", name);
goto out;
}
g_assert (name[0] != '/'); /* Sanity */
if (!glnx_shutil_rm_rf_at (destination_dfd, name, cancellable, error))
goto out;
need_copy = FALSE;
}
else if (!is_symlink)
{
gboolean did_hardlink = FALSE;
/* Try to do a hardlink first, if it's a regular file. This also
* traverses all parent repos.
*/
OstreeRepo *current_repo = repo; OstreeRepo *current_repo = repo;
while (current_repo) while (current_repo)
@ -454,6 +490,8 @@ checkout_one_file_at (OstreeRepo *repo,
} }
current_repo = current_repo->parent_repo; current_repo = current_repo->parent_repo;
} }
need_copy = !did_hardlink;
} }
can_cache = (options->enable_uncompressed_cache can_cache = (options->enable_uncompressed_cache
@ -463,11 +501,14 @@ checkout_one_file_at (OstreeRepo *repo,
* it now, stick it in the cache, and then hardlink to that. * it now, stick it in the cache, and then hardlink to that.
*/ */
if (can_cache if (can_cache
&& !is_whiteout
&& !is_symlink && !is_symlink
&& !did_hardlink && need_copy
&& repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2 && repo->mode == OSTREE_REPO_MODE_ARCHIVE_Z2
&& options->mode == OSTREE_REPO_CHECKOUT_MODE_USER) && options->mode == OSTREE_REPO_CHECKOUT_MODE_USER)
{ {
gboolean did_hardlink;
if (!ostree_repo_load_file (repo, checksum, &input, NULL, NULL, if (!ostree_repo_load_file (repo, checksum, &input, NULL, NULL,
cancellable, error)) cancellable, error))
goto out; goto out;
@ -518,10 +559,12 @@ checkout_one_file_at (OstreeRepo *repo,
g_prefix_error (error, "Using new cached uncompressed hardlink of %s to %s: ", checksum, destination_name); g_prefix_error (error, "Using new cached uncompressed hardlink of %s to %s: ", checksum, destination_name);
goto out; goto out;
} }
need_copy = !did_hardlink;
} }
/* Fall back to copy if we couldn't hardlink */ /* Fall back to copy if we couldn't hardlink */
if (!did_hardlink) if (need_copy)
{ {
if (!ostree_repo_load_file (repo, checksum, &input, NULL, &xattrs, if (!ostree_repo_load_file (repo, checksum, &input, NULL, &xattrs,
cancellable, error)) cancellable, error))
@ -726,17 +769,13 @@ checkout_tree_at (OstreeRepo *self,
} }
} }
/* Finally, fsync to ensure all entries are on disk. Ultimately if (fsync_is_enabled (self, options))
* this should be configurable for the case where we're constructing
* buildroots.
*/
if (!self->disable_fsync)
{ {
if (fsync (destination_dfd) == -1) if (fsync (destination_dfd) == -1)
{ {
gs_set_error_from_errno (error, errno); gs_set_error_from_errno (error, errno);
goto out; goto out;
} }
} }
ret = TRUE; ret = TRUE;

View File

@ -412,12 +412,12 @@ add_size_index_to_metadata (OstreeRepo *self,
for (i = 0; i < sorted_keys->len; i++) for (i = 0; i < sorted_keys->len; i++)
{ {
guint8 csum[32]; guint8 csum[OSTREE_SHA256_DIGEST_LEN];
const char *e_checksum = sorted_keys->pdata[i]; const char *e_checksum = sorted_keys->pdata[i];
GString *buffer = g_string_new (NULL); GString *buffer = g_string_new (NULL);
ostree_checksum_inplace_to_bytes (e_checksum, csum); ostree_checksum_inplace_to_bytes (e_checksum, csum);
g_string_append_len (buffer, (char*)csum, 32); g_string_append_len (buffer, (char*)csum, sizeof (csum));
e_size = g_hash_table_lookup (self->object_sizes, e_checksum); e_size = g_hash_table_lookup (self->object_sizes, e_checksum);
_ostree_write_varuint64 (buffer, e_size->archived); _ostree_write_varuint64 (buffer, e_size->archived);
@ -1144,7 +1144,8 @@ ostree_repo_scan_hardlinks (OstreeRepo *self,
* ostree_repo_prepare_transaction: * ostree_repo_prepare_transaction:
* @self: An #OstreeRepo * @self: An #OstreeRepo
* @out_transaction_resume: (allow-none) (out): Whether this transaction * @out_transaction_resume: (allow-none) (out): Whether this transaction
* is resuming from a previous one. * is resuming from a previous one. This is a legacy state, now OSTree
* pulls use per-commit `state/.commitpartial` files.
* @cancellable: Cancellable * @cancellable: Cancellable
* @error: Error * @error: Error
* *

View File

@ -24,6 +24,7 @@
#include "ostree-core-private.h" #include "ostree-core-private.h"
#include "ostree-repo-private.h" #include "ostree-repo-private.h"
#include "ostree-repo-file.h"
#include "ostree-mutable-tree.h" #include "ostree-mutable-tree.h"
#ifdef HAVE_LIBARCHIVE #ifdef HAVE_LIBARCHIVE
@ -77,6 +78,7 @@ file_info_from_archive_entry_and_modifier (OstreeRepo *repo,
static gboolean static gboolean
import_libarchive_entry_file (OstreeRepo *self, import_libarchive_entry_file (OstreeRepo *self,
OstreeRepoImportArchiveOptions *opts,
struct archive *a, struct archive *a,
struct archive_entry *entry, struct archive_entry *entry,
GFileInfo *file_info, GFileInfo *file_info,
@ -92,8 +94,27 @@ import_libarchive_entry_file (OstreeRepo *self,
if (g_cancellable_set_error_if_cancelled (cancellable, error)) if (g_cancellable_set_error_if_cancelled (cancellable, error))
return FALSE; return FALSE;
if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR) switch (g_file_info_get_file_type (file_info))
archive_stream = _ostree_libarchive_input_stream_new (a); {
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, if (!ostree_raw_file_to_content_stream (archive_stream, file_info, NULL,
&file_object_input, &length, cancellable, error)) &file_object_input, &length, cancellable, error))
@ -110,6 +131,7 @@ import_libarchive_entry_file (OstreeRepo *self,
static gboolean static gboolean
write_libarchive_entry_to_mtree (OstreeRepo *self, write_libarchive_entry_to_mtree (OstreeRepo *self,
OstreeRepoImportArchiveOptions *opts,
OstreeMutableTree *root, OstreeMutableTree *root,
struct archive *a, struct archive *a,
struct archive_entry *entry, struct archive_entry *entry,
@ -248,16 +270,19 @@ write_libarchive_entry_to_mtree (OstreeRepo *self,
goto out; goto out;
} }
if (!import_libarchive_entry_file (self, a, entry, file_info, &tmp_csum, if (!import_libarchive_entry_file (self, opts, a, entry, file_info, &tmp_csum,
cancellable, error)) cancellable, error))
goto out; goto out;
g_free (tmp_checksum); if (tmp_csum)
tmp_checksum = ostree_checksum_from_bytes (tmp_csum); {
if (!ostree_mutable_tree_replace_file (parent, basename, g_free (tmp_checksum);
tmp_checksum, tmp_checksum = ostree_checksum_from_bytes (tmp_csum);
error)) if (!ostree_mutable_tree_replace_file (parent, basename,
goto out; tmp_checksum,
error))
goto out;
}
} }
} }
@ -266,6 +291,109 @@ write_libarchive_entry_to_mtree (OstreeRepo *self,
return ret; return ret;
} }
#endif #endif
static gboolean
create_empty_dir_with_uidgid (OstreeRepo *self,
guint32 uid,
guint32 gid,
guint8 **out_csum,
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);
}
/**
* ostree_repo_import_archive_to_mtree:
* @self: An #OstreeRepo
* @opts: Options structure, ensure this is zeroed, then set specific variables
* @archive: Really this is "struct archive*"
* @mtree: The #OstreeMutableTree to write to
* @modifier: (allow-none): Optional commit modifier
* @cancellable: Cancellable
* @error: Error
*
* Import an archive file @archive into the repository, and write its
* file structure to @mtree.
*/
gboolean
ostree_repo_import_archive_to_mtree (OstreeRepo *self,
OstreeRepoImportArchiveOptions *opts,
void *archive,
OstreeMutableTree *mtree,
OstreeRepoCommitModifier *modifier,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
struct archive *a = archive;
struct archive_entry *entry;
g_autofree guchar *tmp_csum = NULL;
int r;
while (TRUE)
{
r = archive_read_next_header (a, &entry);
if (r == ARCHIVE_EOF)
break;
else 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 (!write_libarchive_entry_to_mtree (self, opts, mtree, a,
entry, modifier, tmp_csum,
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)
{
char tmp_checksum[65];
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);
}
ret = TRUE;
out:
return ret;
}
/** /**
* ostree_repo_write_archive_to_mtree: * ostree_repo_write_archive_to_mtree:
@ -292,10 +420,8 @@ ostree_repo_write_archive_to_mtree (OstreeRepo *self,
#ifdef HAVE_LIBARCHIVE #ifdef HAVE_LIBARCHIVE
gboolean ret = FALSE; gboolean ret = FALSE;
struct archive *a = NULL; struct archive *a = NULL;
struct archive_entry *entry;
int r;
g_autoptr(GFileInfo) tmp_dir_info = NULL; g_autoptr(GFileInfo) tmp_dir_info = NULL;
g_autofree guchar *tmp_csum = NULL; OstreeRepoImportArchiveOptions opts = { 0, };
a = archive_read_new (); a = archive_read_new ();
#ifdef HAVE_ARCHIVE_READ_SUPPORT_FILTER_ALL #ifdef HAVE_ARCHIVE_READ_SUPPORT_FILTER_ALL
@ -310,35 +436,11 @@ ostree_repo_write_archive_to_mtree (OstreeRepo *self,
goto out; goto out;
} }
while (TRUE) opts.autocreate_parents = !!autocreate_parents;
{
r = archive_read_next_header (a, &entry);
if (r == ARCHIVE_EOF)
break;
else if (r != ARCHIVE_OK)
{
propagate_libarchive_error (error, a);
goto out;
}
if (autocreate_parents && !tmp_csum) if (!ostree_repo_import_archive_to_mtree (self, &opts, a, mtree, modifier, cancellable, error))
{ goto out;
tmp_dir_info = g_file_info_new ();
g_file_info_set_attribute_uint32 (tmp_dir_info, "unix::uid", archive_entry_uid (entry));
g_file_info_set_attribute_uint32 (tmp_dir_info, "unix::gid", archive_entry_gid (entry));
g_file_info_set_attribute_uint32 (tmp_dir_info, "unix::mode", 0755 | S_IFDIR);
if (!_ostree_repo_write_directory_meta (self, tmp_dir_info, NULL, &tmp_csum, cancellable, error))
goto out;
}
if (!write_libarchive_entry_to_mtree (self, mtree, a,
entry, modifier,
autocreate_parents ? tmp_csum : NULL,
cancellable, error))
goto out;
}
if (archive_read_close (a) != ARCHIVE_OK) if (archive_read_close (a) != ARCHIVE_OK)
{ {
propagate_libarchive_error (error, a); propagate_libarchive_error (error, a);
@ -356,3 +458,251 @@ ostree_repo_write_archive_to_mtree (OstreeRepo *self,
return FALSE; return FALSE;
#endif #endif
} }
#ifdef HAVE_LIBARCHIVE
static gboolean
file_to_archive_entry_common (GFile *root,
OstreeRepoExportArchiveOptions *opts,
GFile *path,
GFileInfo *file_info,
struct archive_entry *entry,
GError **error)
{
gboolean ret = FALSE;
g_autofree char *pathstr = g_file_get_relative_path (root, path);
g_autoptr(GVariant) xattrs = NULL;
time_t ts = (time_t) opts->timestamp_secs;
if (pathstr && !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_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"));
if (!ostree_repo_file_get_xattrs ((OstreeRepoFile*)path, &xattrs, NULL, error))
goto out;
if (!opts->disable_xattrs)
{
int i, n;
n = g_variant_n_children (xattrs);
for (i = 0; i < n; i++)
{
const guint8* name;
g_autoptr(GVariant) value = NULL;
const guint8* value_data;
gsize value_len;
g_variant_get_child (xattrs, i, "(^&ay@ay)", &name, &value);
value_data = g_variant_get_fixed_array (value, &value_len, 1);
archive_entry_xattr_add_entry (entry, (char*)name,
(char*) value_data, value_len);
}
}
ret = TRUE;
out:
return ret;
}
static gboolean
write_header_free_entry (struct archive *a,
struct archive_entry **entryp,
GError **error)
{
struct archive_entry *entry = *entryp;
gboolean ret = FALSE;
if (archive_write_header (a, entry) != ARCHIVE_OK)
{
propagate_libarchive_error (error, a);
goto out;
}
ret = TRUE;
out:
archive_entry_free (entry);
*entryp = NULL;
return ret;
}
static gboolean
write_directory_to_libarchive_recurse (OstreeRepo *self,
OstreeRepoExportArchiveOptions *opts,
GFile *root,
GFile *dir,
struct archive *a,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
g_autoptr(GFileInfo) dir_info = NULL;
g_autoptr(GFileEnumerator) dir_enum = NULL;
struct archive_entry *entry = NULL;
dir_info = g_file_query_info (dir, OSTREE_GIO_FAST_QUERYINFO,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable, error);
if (!dir_info)
goto out;
entry = archive_entry_new2 (a);
if (!file_to_archive_entry_common (root, opts, dir, dir_info, entry, error))
goto out;
if (!write_header_free_entry (a, &entry, error))
goto out;
dir_enum = g_file_enumerate_children (dir, OSTREE_GIO_FAST_QUERYINFO,
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable, error);
if (!dir_enum)
goto out;
while (TRUE)
{
GFileInfo *file_info;
GFile *path;
if (!gs_file_enumerator_iterate (dir_enum, &file_info, &path,
cancellable, error))
goto out;
if (file_info == NULL)
break;
/* First, handle directories recursively */
if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY)
{
if (!write_directory_to_libarchive_recurse (self, opts, root, path, a,
cancellable, error))
goto out;
/* Go to the next entry */
continue;
}
/* Past here, should be a regular file or a symlink */
entry = archive_entry_new2 (a);
if (!file_to_archive_entry_common (root, opts, path, file_info, entry, error))
goto out;
switch (g_file_info_get_file_type (file_info))
{
case G_FILE_TYPE_SYMBOLIC_LINK:
{
archive_entry_set_symlink (entry, g_file_info_get_symlink_target (file_info));
if (!write_header_free_entry (a, &entry, error))
goto out;
}
break;
case G_FILE_TYPE_REGULAR:
{
guint8 buf[8192];
g_autoptr(GInputStream) file_in = NULL;
g_autoptr(GFileInfo) file_info = NULL;
const char *checksum;
checksum = ostree_repo_file_get_checksum ((OstreeRepoFile*)path);
if (!ostree_repo_load_file (self, checksum, &file_in, &file_info, NULL,
cancellable, error))
goto out;
archive_entry_set_size (entry, g_file_info_get_size (file_info));
if (archive_write_header (a, entry) != ARCHIVE_OK)
{
propagate_libarchive_error (error, a);
goto out;
}
while (TRUE)
{
gssize bytes_read = g_input_stream_read (file_in, buf, sizeof (buf),
cancellable, error);
if (bytes_read < 0)
goto out;
if (bytes_read == 0)
break;
{ ssize_t r = archive_write_data (a, buf, bytes_read);
if (r != bytes_read)
{
propagate_libarchive_error (error, a);
g_prefix_error (error, "Failed to write %" G_GUINT64_FORMAT " bytes (code %" G_GUINT64_FORMAT"): ", (guint64)bytes_read, (guint64)r);
goto out;
}
}
}
if (archive_write_finish_entry (a) != ARCHIVE_OK)
{
propagate_libarchive_error (error, a);
goto out;
}
archive_entry_free (entry);
entry = NULL;
}
break;
default:
g_assert_not_reached ();
}
}
ret = TRUE;
out:
if (entry)
archive_entry_free (entry);
return ret;
}
#endif
/**
* ostree_repo_export_tree_to_archive:
* @self: An #OstreeRepo
* @opts: Options controlling conversion
* @root: An #OstreeRepoFile for the base directory
* @archive: A `struct archive`, but specified as void to avoid a dependency on the libarchive headers
* @cancellable: Cancellable
* @error: Error
*
* Import an archive file @archive into the repository, and write its
* file structure to @mtree.
*/
gboolean
ostree_repo_export_tree_to_archive (OstreeRepo *self,
OstreeRepoExportArchiveOptions *opts,
OstreeRepoFile *root,
void *archive,
GCancellable *cancellable,
GError **error)
{
#ifdef HAVE_LIBARCHIVE
gboolean ret = FALSE;
struct archive *a = archive;
if (!write_directory_to_libarchive_recurse (self, opts, (GFile*)root, (GFile*)root,
a, cancellable, error))
goto out;
ret = TRUE;
out:
return ret;
#else
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"This version of ostree is not compiled with libarchive support");
return FALSE;
#endif
}

View File

@ -50,8 +50,6 @@ struct OstreeRepo {
int repo_dir_fd; int repo_dir_fd;
GFile *tmp_dir; GFile *tmp_dir;
int tmp_dir_fd; int tmp_dir_fd;
GFile *local_heads_dir;
GFile *remote_heads_dir;
GFile *objects_dir; GFile *objects_dir;
GFile *state_dir; GFile *state_dir;
int objects_dir_fd; int objects_dir_fd;

View File

@ -48,7 +48,9 @@ typedef struct {
GCancellable *cancellable; GCancellable *cancellable;
OstreeAsyncProgress *progress; OstreeAsyncProgress *progress;
gboolean transaction_resuming; gboolean dry_run;
gboolean dry_run_emitted_progress;
gboolean legacy_transaction_resuming;
enum { enum {
OSTREE_PULL_PHASE_FETCHING_REFS, OSTREE_PULL_PHASE_FETCHING_REFS,
OSTREE_PULL_PHASE_FETCHING_OBJECTS OSTREE_PULL_PHASE_FETCHING_OBJECTS
@ -78,6 +80,7 @@ typedef struct {
guint n_outstanding_deltapart_write_requests; guint n_outstanding_deltapart_write_requests;
guint n_total_deltaparts; guint n_total_deltaparts;
guint64 total_deltapart_size; guint64 total_deltapart_size;
guint64 total_deltapart_usize;
gint n_requested_metadata; gint n_requested_metadata;
gint n_requested_content; gint n_requested_content;
guint n_fetched_deltaparts; guint n_fetched_deltaparts;
@ -123,7 +126,7 @@ typedef struct {
} FetchStaticDeltaData; } FetchStaticDeltaData;
typedef struct { typedef struct {
guchar csum[32]; guchar csum[OSTREE_SHA256_DIGEST_LEN];
OstreeObjectType objtype; OstreeObjectType objtype;
guint recursion_depth; guint recursion_depth;
} ScanObjectQueueData; } ScanObjectQueueData;
@ -227,6 +230,8 @@ update_progress (gpointer user_data)
pull_data->n_total_deltaparts); pull_data->n_total_deltaparts);
ostree_async_progress_set_uint64 (pull_data->progress, "total-delta-part-size", ostree_async_progress_set_uint64 (pull_data->progress, "total-delta-part-size",
pull_data->total_deltapart_size); pull_data->total_deltapart_size);
ostree_async_progress_set_uint64 (pull_data->progress, "total-delta-part-usize",
pull_data->total_deltapart_usize);
ostree_async_progress_set_uint (pull_data->progress, "total-delta-superblocks", ostree_async_progress_set_uint (pull_data->progress, "total-delta-superblocks",
pull_data->static_delta_superblocks->len); pull_data->static_delta_superblocks->len);
@ -243,6 +248,9 @@ update_progress (gpointer user_data)
else else
ostree_async_progress_set_status (pull_data->progress, NULL); ostree_async_progress_set_status (pull_data->progress, NULL);
if (pull_data->dry_run)
pull_data->dry_run_emitted_progress = TRUE;
return TRUE; return TRUE;
} }
@ -262,6 +270,9 @@ pull_termination_condition (OtPullData *pull_data)
if (pull_data->caught_error) if (pull_data->caught_error)
return TRUE; return TRUE;
if (pull_data->dry_run)
return pull_data->dry_run_emitted_progress;
switch (pull_data->phase) switch (pull_data->phase)
{ {
case OSTREE_PULL_PHASE_FETCHING_REFS: case OSTREE_PULL_PHASE_FETCHING_REFS:
@ -935,8 +946,7 @@ static_deltapart_fetch_on_complete (GObject *object,
g_autoptr(GVariant) metadata = NULL; g_autoptr(GVariant) metadata = NULL;
g_autofree char *temp_path = NULL; g_autofree char *temp_path = NULL;
g_autoptr(GInputStream) in = NULL; g_autoptr(GInputStream) in = NULL;
g_autofree char *actual_checksum = NULL; g_autoptr(GVariant) part = NULL;
g_autofree guint8 *csum = NULL;
GError *local_error = NULL; GError *local_error = NULL;
GError **error = &local_error; GError **error = &local_error;
gs_fd_close int fd = -1; gs_fd_close int fd = -1;
@ -950,54 +960,33 @@ static_deltapart_fetch_on_complete (GObject *object,
fd = openat (_ostree_fetcher_get_dfd (fetcher), temp_path, O_RDONLY | O_CLOEXEC); fd = openat (_ostree_fetcher_get_dfd (fetcher), temp_path, O_RDONLY | O_CLOEXEC);
if (fd == -1) if (fd == -1)
{ {
gs_set_error_from_errno (error, errno); glnx_set_error_from_errno (error);
goto out; goto out;
} }
/* From here on, if we fail to apply the delta, we'll re-fetch it */
if (unlinkat (_ostree_fetcher_get_dfd (fetcher), temp_path, 0) < 0)
{
glnx_set_error_from_errno (error);
goto out;
}
in = g_unix_input_stream_new (fd, FALSE); in = g_unix_input_stream_new (fd, FALSE);
/* TODO - consider making async */ /* TODO - make async */
if (!ot_gio_checksum_stream (in, &csum, pull_data->cancellable, error)) if (!_ostree_static_delta_part_open (in, NULL, 0, fetch_data->expected_checksum,
&part, pull_data->cancellable, error))
goto out; goto out;
actual_checksum = ostree_checksum_from_bytes (csum); _ostree_static_delta_part_execute_async (pull_data->repo,
fetch_data->objects,
if (strcmp (actual_checksum, fetch_data->expected_checksum) != 0) part,
{ /* Trust checksums if summary was gpg signed */
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, pull_data->gpg_verify_summary && pull_data->summary_data_sig,
"Corrupted static delta part; checksum expected='%s' actual='%s'", pull_data->cancellable,
fetch_data->expected_checksum, actual_checksum); on_static_delta_written,
goto out; fetch_data);
} pull_data->n_outstanding_deltapart_write_requests++;
/* Might as well close the fd here */
(void) g_input_stream_close (in, NULL, NULL);
{
GMappedFile *mfile = NULL;
g_autoptr(GBytes) delta_data = NULL;
mfile = g_mapped_file_new_from_fd (fd, FALSE, error);
if (!mfile)
goto out;
delta_data = g_mapped_file_get_bytes (mfile);
g_mapped_file_unref (mfile);
/* Unlink now while we're holding an open fd, so that on success
* or error, the file will be gone. This is particularly
* important if say we hit e.g. ENOSPC.
*/
(void) unlinkat (_ostree_fetcher_get_dfd (fetcher), temp_path, 0);
_ostree_static_delta_part_execute_async (pull_data->repo,
fetch_data->objects,
delta_data,
/* Trust checksums if summary was gpg signed */
pull_data->gpg_verify_summary && pull_data->summary_data_sig,
pull_data->cancellable,
on_static_delta_written,
fetch_data);
pull_data->n_outstanding_deltapart_write_requests++;
}
out: out:
g_assert (pull_data->n_outstanding_deltapart_fetches > 0); g_assert (pull_data->n_outstanding_deltapart_fetches > 0);
@ -1150,7 +1139,7 @@ queue_scan_one_metadata_object (OtPullData *pull_data,
OstreeObjectType objtype, OstreeObjectType objtype,
guint recursion_depth) guint recursion_depth)
{ {
guchar buf[32]; guchar buf[OSTREE_SHA256_DIGEST_LEN];
ostree_checksum_inplace_to_bytes (csum, buf); ostree_checksum_inplace_to_bytes (csum, buf);
queue_scan_one_metadata_object_c (pull_data, buf, objtype, recursion_depth); queue_scan_one_metadata_object_c (pull_data, buf, objtype, recursion_depth);
} }
@ -1227,7 +1216,7 @@ scan_one_metadata_object_c (OtPullData *pull_data,
} }
else if (is_stored) else if (is_stored)
{ {
gboolean do_scan = pull_data->transaction_resuming || is_requested || pull_data->commitpartial_exists; gboolean do_scan = pull_data->legacy_transaction_resuming || is_requested || pull_data->commitpartial_exists;
/* For commits, always refetch detached metadata. */ /* For commits, always refetch detached metadata. */
if (objtype == OSTREE_OBJECT_TYPE_COMMIT) if (objtype == OSTREE_OBJECT_TYPE_COMMIT)
@ -1454,6 +1443,7 @@ request_static_delta_superblock_sync (OtPullData *pull_data,
static gboolean static gboolean
process_one_static_delta_fallback (OtPullData *pull_data, process_one_static_delta_fallback (OtPullData *pull_data,
gboolean delta_byteswap,
GVariant *fallback_object, GVariant *fallback_object,
GCancellable *cancellable, GCancellable *cancellable,
GError **error) GError **error)
@ -1473,10 +1463,20 @@ process_one_static_delta_fallback (OtPullData *pull_data,
if (!ostree_validate_structureof_csum_v (csum_v, error)) if (!ostree_validate_structureof_csum_v (csum_v, error))
goto out; goto out;
objtype = (OstreeObjectType)objtype_y; compressed_size = maybe_swap_endian_u64 (delta_byteswap, compressed_size);
checksum = ostree_checksum_from_bytes_v (csum_v); uncompressed_size = maybe_swap_endian_u64 (delta_byteswap, uncompressed_size);
pull_data->total_deltapart_size += compressed_size; pull_data->total_deltapart_size += compressed_size;
pull_data->total_deltapart_usize += uncompressed_size;
if (pull_data->dry_run)
{
ret = TRUE;
goto out;
}
objtype = (OstreeObjectType)objtype_y;
checksum = ostree_checksum_from_bytes_v (csum_v);
if (!ostree_repo_has_object (pull_data->repo, objtype, checksum, if (!ostree_repo_has_object (pull_data->repo, objtype, checksum,
&is_stored, &is_stored,
@ -1522,11 +1522,14 @@ process_one_static_delta (OtPullData *pull_data,
GError **error) GError **error)
{ {
gboolean ret = FALSE; gboolean ret = FALSE;
gboolean delta_byteswap;
g_autoptr(GVariant) metadata = NULL; g_autoptr(GVariant) metadata = NULL;
g_autoptr(GVariant) headers = NULL; g_autoptr(GVariant) headers = NULL;
g_autoptr(GVariant) fallback_objects = NULL; g_autoptr(GVariant) fallback_objects = NULL;
guint i, n; guint i, n;
delta_byteswap = _ostree_delta_needs_byteswap (delta_superblock);
/* Parsing OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT */ /* Parsing OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT */
metadata = g_variant_get_child_value (delta_superblock, 0); metadata = g_variant_get_child_value (delta_superblock, 0);
headers = g_variant_get_child_value (delta_superblock, 6); headers = g_variant_get_child_value (delta_superblock, 6);
@ -1539,13 +1542,14 @@ process_one_static_delta (OtPullData *pull_data,
g_autoptr(GVariant) fallback_object = g_autoptr(GVariant) fallback_object =
g_variant_get_child_value (fallback_objects, i); g_variant_get_child_value (fallback_objects, i);
if (!process_one_static_delta_fallback (pull_data, if (!process_one_static_delta_fallback (pull_data, delta_byteswap,
fallback_object, fallback_object,
cancellable, error)) cancellable, error))
goto out; goto out;
} }
/* Write the to-commit object */ /* Write the to-commit object */
if (!pull_data->dry_run)
{ {
g_autoptr(GVariant) to_csum_v = NULL; g_autoptr(GVariant) to_csum_v = NULL;
g_autofree char *to_checksum = NULL; g_autofree char *to_checksum = NULL;
@ -1604,14 +1608,18 @@ process_one_static_delta (OtPullData *pull_data,
FetchStaticDeltaData *fetch_data; FetchStaticDeltaData *fetch_data;
g_autoptr(GVariant) csum_v = NULL; g_autoptr(GVariant) csum_v = NULL;
g_autoptr(GVariant) objects = NULL; g_autoptr(GVariant) objects = NULL;
g_autoptr(GVariant) part_data = NULL; g_autoptr(GBytes) inline_part_bytes = NULL;
g_autoptr(GBytes) delta_data = NULL;
guint64 size, usize; guint64 size, usize;
guint32 version; guint32 version;
const gboolean trusted = pull_data->gpg_verify_summary && pull_data->summary_data_sig;
header = g_variant_get_child_value (headers, i); header = g_variant_get_child_value (headers, i);
g_variant_get (header, "(u@aytt@ay)", &version, &csum_v, &size, &usize, &objects); g_variant_get (header, "(u@aytt@ay)", &version, &csum_v, &size, &usize, &objects);
version = maybe_swap_endian_u32 (delta_byteswap, version);
size = maybe_swap_endian_u64 (delta_byteswap, size);
usize = maybe_swap_endian_u64 (delta_byteswap, usize);
if (version > OSTREE_DELTAPART_VERSION) if (version > OSTREE_DELTAPART_VERSION)
{ {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
@ -1623,31 +1631,6 @@ process_one_static_delta (OtPullData *pull_data,
if (!csum) if (!csum)
goto out; goto out;
deltapart_path = _ostree_get_relative_static_delta_part_path (from_revision, to_revision, i);
part_data = g_variant_lookup_value (metadata, deltapart_path, G_VARIANT_TYPE ("(yay)"));
if (part_data)
{
g_autofree char *actual_checksum = NULL;
g_autofree char *expected_checksum = ostree_checksum_from_bytes_v (csum_v);
delta_data = g_variant_get_data_as_bytes (part_data);
/* For inline parts we are relying on per-commit GPG, so this isn't strictly necessary for security.
* See https://github.com/GNOME/ostree/pull/139
*/
actual_checksum = g_compute_checksum_for_bytes (G_CHECKSUM_SHA256, delta_data);
if (strcmp (actual_checksum, expected_checksum) != 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Corrupted static delta part; checksum expected='%s' actual='%s'",
expected_checksum, actual_checksum);
goto out;
}
}
pull_data->total_deltapart_size += size;
if (!_ostree_repo_static_delta_part_have_all_objects (pull_data->repo, if (!_ostree_repo_static_delta_part_have_all_objects (pull_data->repo,
objects, objects,
&have_all, &have_all,
@ -1663,18 +1646,42 @@ process_one_static_delta (OtPullData *pull_data,
continue; continue;
} }
deltapart_path = _ostree_get_relative_static_delta_part_path (from_revision, to_revision, i);
{ g_autoptr(GVariant) part_datav =
g_variant_lookup_value (metadata, deltapart_path, G_VARIANT_TYPE ("(yay)"));
if (part_datav)
inline_part_bytes = g_variant_get_data_as_bytes (part_datav);
}
pull_data->total_deltapart_size += size;
pull_data->total_deltapart_usize += usize;
if (pull_data->dry_run)
continue;
fetch_data = g_new0 (FetchStaticDeltaData, 1); fetch_data = g_new0 (FetchStaticDeltaData, 1);
fetch_data->pull_data = pull_data; fetch_data->pull_data = pull_data;
fetch_data->objects = g_variant_ref (objects); fetch_data->objects = g_variant_ref (objects);
fetch_data->expected_checksum = ostree_checksum_from_bytes_v (csum_v); fetch_data->expected_checksum = ostree_checksum_from_bytes_v (csum_v);
if (delta_data != NULL) if (inline_part_bytes != NULL)
{ {
g_autoptr(GInputStream) memin = g_memory_input_stream_new_from_bytes (inline_part_bytes);
g_autoptr(GVariant) inline_delta_part = NULL;
/* For inline parts we are relying on per-commit GPG, so don't bother checksumming. */
if (!_ostree_static_delta_part_open (memin, inline_part_bytes,
OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM,
NULL, &inline_delta_part,
cancellable, error))
goto out;
_ostree_static_delta_part_execute_async (pull_data->repo, _ostree_static_delta_part_execute_async (pull_data->repo,
fetch_data->objects, fetch_data->objects,
delta_data, inline_delta_part,
/* Trust checksums if summary was gpg signed */ trusted,
pull_data->gpg_verify_summary && pull_data->summary_data_sig,
pull_data->cancellable, pull_data->cancellable,
on_static_delta_written, on_static_delta_written,
fetch_data); fetch_data);
@ -1789,8 +1796,10 @@ ostree_repo_pull_with_options (OstreeRepo *self,
OstreeRepoPullFlags flags = 0; OstreeRepoPullFlags flags = 0;
const char *dir_to_pull = NULL; const char *dir_to_pull = NULL;
char **refs_to_fetch = NULL; char **refs_to_fetch = NULL;
char **override_commit_ids = NULL;
GSource *update_timeout = NULL; GSource *update_timeout = NULL;
gboolean disable_static_deltas = FALSE; gboolean disable_static_deltas = FALSE;
gboolean require_static_deltas = FALSE;
if (options) if (options)
{ {
@ -1803,13 +1812,24 @@ ostree_repo_pull_with_options (OstreeRepo *self,
(void) g_variant_lookup (options, "override-remote-name", "s", &pull_data->remote_name); (void) g_variant_lookup (options, "override-remote-name", "s", &pull_data->remote_name);
(void) g_variant_lookup (options, "depth", "i", &pull_data->maxdepth); (void) g_variant_lookup (options, "depth", "i", &pull_data->maxdepth);
(void) g_variant_lookup (options, "disable-static-deltas", "b", &disable_static_deltas); (void) g_variant_lookup (options, "disable-static-deltas", "b", &disable_static_deltas);
(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);
} }
g_return_val_if_fail (pull_data->maxdepth >= -1, FALSE); g_return_val_if_fail (pull_data->maxdepth >= -1, FALSE);
if (refs_to_fetch && override_commit_ids)
g_return_val_if_fail (g_strv_length (refs_to_fetch) == g_strv_length (override_commit_ids), FALSE);
if (dir_to_pull) if (dir_to_pull)
g_return_val_if_fail (dir_to_pull[0] == '/', FALSE); g_return_val_if_fail (dir_to_pull[0] == '/', FALSE);
g_return_val_if_fail (!(disable_static_deltas && require_static_deltas), FALSE);
/* We only do dry runs with static deltas, because we don't really have any
* in-advance information for bare fetches.
*/
g_return_val_if_fail (!pull_data->dry_run || require_static_deltas, FALSE);
pull_data->is_mirror = (flags & OSTREE_REPO_PULL_FLAGS_MIRROR) > 0; pull_data->is_mirror = (flags & OSTREE_REPO_PULL_FLAGS_MIRROR) > 0;
pull_data->is_commit_only = (flags & OSTREE_REPO_PULL_FLAGS_COMMIT_ONLY) > 0; pull_data->is_commit_only = (flags & OSTREE_REPO_PULL_FLAGS_COMMIT_ONLY) > 0;
@ -1992,6 +2012,13 @@ ostree_repo_pull_with_options (OstreeRepo *self,
goto out; goto out;
} }
if (!bytes_summary && require_static_deltas)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Fetch configured to require static deltas, but no summary found");
goto out;
}
if (bytes_summary) if (bytes_summary)
{ {
uri = suburi_new (pull_data->base_uri, "summary.sig", NULL); uri = suburi_new (pull_data->base_uri, "summary.sig", NULL);
@ -2067,7 +2094,7 @@ ostree_repo_pull_with_options (OstreeRepo *self,
{ {
const char *delta; const char *delta;
GVariant *csum_v = NULL; GVariant *csum_v = NULL;
guchar *csum_data = g_malloc (32); guchar *csum_data = g_malloc (OSTREE_SHA256_DIGEST_LEN);
g_autoptr(GVariant) ref = g_variant_get_child_value (deltas, i); g_autoptr(GVariant) ref = g_variant_get_child_value (deltas, i);
g_variant_get_child (ref, 0, "&s", &delta); g_variant_get_child (ref, 0, "&s", &delta);
@ -2096,8 +2123,10 @@ ostree_repo_pull_with_options (OstreeRepo *self,
} }
else if (refs_to_fetch != NULL) else if (refs_to_fetch != NULL)
{ {
char **strviter; char **strviter = refs_to_fetch;
for (strviter = refs_to_fetch; *strviter; strviter++) char **commitid_strviter = override_commit_ids ? override_commit_ids : NULL;
while (*strviter)
{ {
const char *branch = *strviter; const char *branch = *strviter;
@ -2108,8 +2137,13 @@ ostree_repo_pull_with_options (OstreeRepo *self,
} }
else else
{ {
g_hash_table_insert (requested_refs_to_fetch, g_strdup (branch), NULL); char *commitid = commitid_strviter ? g_strdup (*commitid_strviter) : NULL;
g_hash_table_insert (requested_refs_to_fetch, g_strdup (branch), commitid);
} }
strviter++;
if (commitid_strviter)
commitid_strviter++;
} }
} }
else else
@ -2136,28 +2170,36 @@ ostree_repo_pull_with_options (OstreeRepo *self,
while (g_hash_table_iter_next (&hash_iter, &key, &value)) while (g_hash_table_iter_next (&hash_iter, &key, &value))
{ {
const char *branch = key; const char *branch = key;
const char *override_commitid = value;
char *contents = NULL; char *contents = NULL;
if (pull_data->summary) /* Support specifying "" for an override commitid */
if (override_commitid && *override_commitid)
{ {
gsize commit_size = 0; g_hash_table_replace (requested_refs_to_fetch, g_strdup (branch), g_strdup (override_commitid));
guint64 *malloced_size;
if (!lookup_commit_checksum_from_summary (pull_data, branch, &contents, &commit_size, error))
goto out;
malloced_size = g_new0 (guint64, 1);
*malloced_size = commit_size;
g_hash_table_insert (pull_data->expected_commit_sizes, g_strdup (contents), malloced_size);
} }
else else
{ {
if (!fetch_ref_contents (pull_data, branch, &contents, cancellable, error)) if (pull_data->summary)
goto out; {
gsize commit_size = 0;
guint64 *malloced_size;
if (!lookup_commit_checksum_from_summary (pull_data, branch, &contents, &commit_size, error))
goto out;
malloced_size = g_new0 (guint64, 1);
*malloced_size = commit_size;
g_hash_table_insert (pull_data->expected_commit_sizes, g_strdup (contents), malloced_size);
}
else
{
if (!fetch_ref_contents (pull_data, branch, &contents, cancellable, error))
goto out;
}
/* Transfer ownership of contents */
g_hash_table_replace (requested_refs_to_fetch, g_strdup (branch), contents);
} }
/* Transfer ownership of contents */
g_hash_table_replace (requested_refs_to_fetch, g_strdup (branch), contents);
} }
/* Create the state directory here - it's new with the commitpartial code, /* Create the state directory here - it's new with the commitpartial code,
@ -2182,11 +2224,12 @@ ostree_repo_pull_with_options (OstreeRepo *self,
if (pull_data->fetcher == NULL) if (pull_data->fetcher == NULL)
goto out; goto out;
if (!ostree_repo_prepare_transaction (pull_data->repo, &pull_data->transaction_resuming, if (!ostree_repo_prepare_transaction (pull_data->repo, &pull_data->legacy_transaction_resuming,
cancellable, error)) cancellable, error))
goto out; goto out;
g_debug ("resuming transaction: %s", pull_data->transaction_resuming ? "true" : " false"); if (pull_data->legacy_transaction_resuming)
g_debug ("resuming legacy transaction");
g_hash_table_iter_init (&hash_iter, commits_to_fetch); g_hash_table_iter_init (&hash_iter, commits_to_fetch);
while (g_hash_table_iter_next (&hash_iter, &key, &value)) while (g_hash_table_iter_next (&hash_iter, &key, &value))
@ -2207,17 +2250,22 @@ ostree_repo_pull_with_options (OstreeRepo *self,
&from_revision, error)) &from_revision, error))
goto out; goto out;
#ifdef BUILDOPT_STATIC_DELTAS
if (!disable_static_deltas && (from_revision == NULL || g_strcmp0 (from_revision, to_revision) != 0)) if (!disable_static_deltas && (from_revision == NULL || g_strcmp0 (from_revision, to_revision) != 0))
{ {
if (!request_static_delta_superblock_sync (pull_data, from_revision, to_revision, if (!request_static_delta_superblock_sync (pull_data, from_revision, to_revision,
&delta_superblock, cancellable, error)) &delta_superblock, cancellable, error))
goto out; goto out;
} }
#endif
if (!delta_superblock) if (!delta_superblock)
{ {
if (require_static_deltas)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Static deltas required, but none found for %s to %s",
from_revision, to_revision);
goto out;
}
g_debug ("no delta superblock for %s-%s", from_revision ? from_revision : "empty", to_revision); g_debug ("no delta superblock for %s-%s", from_revision ? from_revision : "empty", to_revision);
queue_scan_one_metadata_object (pull_data, to_revision, OSTREE_OBJECT_TYPE_COMMIT, 0); queue_scan_one_metadata_object (pull_data, to_revision, OSTREE_OBJECT_TYPE_COMMIT, 0);
} }
@ -2234,7 +2282,7 @@ ostree_repo_pull_with_options (OstreeRepo *self,
if (pull_data->progress) if (pull_data->progress)
{ {
update_timeout = g_timeout_source_new_seconds (1); update_timeout = g_timeout_source_new_seconds (pull_data->dry_run ? 0 : 1);
g_source_set_priority (update_timeout, G_PRIORITY_HIGH); g_source_set_priority (update_timeout, G_PRIORITY_HIGH);
g_source_set_callback (update_timeout, update_progress, pull_data, NULL); g_source_set_callback (update_timeout, update_progress, pull_data, NULL);
g_source_attach (update_timeout, pull_data->main_context); g_source_attach (update_timeout, pull_data->main_context);
@ -2247,6 +2295,12 @@ ostree_repo_pull_with_options (OstreeRepo *self,
if (pull_data->caught_error) if (pull_data->caught_error)
goto out; goto out;
if (pull_data->dry_run)
{
ret = TRUE;
goto out;
}
g_assert_cmpint (pull_data->n_outstanding_metadata_fetches, ==, 0); g_assert_cmpint (pull_data->n_outstanding_metadata_fetches, ==, 0);
g_assert_cmpint (pull_data->n_outstanding_metadata_write_requests, ==, 0); g_assert_cmpint (pull_data->n_outstanding_metadata_write_requests, ==, 0);

View File

@ -25,19 +25,19 @@
static gboolean static gboolean
add_ref_to_set (const char *remote, add_ref_to_set (const char *remote,
GFile *base, int base_fd,
GFile *child, const char *path,
GHashTable *refs, GHashTable *refs,
GCancellable *cancellable, GCancellable *cancellable,
GError **error) GError **error)
{ {
gboolean ret = FALSE; gboolean ret = FALSE;
char *contents; char *contents;
char *relpath;
gsize len; gsize len;
GString *refname; GString *refname;
if (!g_file_load_contents (child, cancellable, &contents, &len, NULL, error)) contents = glnx_file_get_contents_utf8_at (base_fd, path, &len, cancellable, error);
if (!contents)
goto out; goto out;
g_strchomp (contents); g_strchomp (contents);
@ -48,9 +48,7 @@ add_ref_to_set (const char *remote,
g_string_append (refname, remote); g_string_append (refname, remote);
g_string_append_c (refname, ':'); g_string_append_c (refname, ':');
} }
relpath = g_file_get_relative_path (base, child); g_string_append (refname, path);
g_string_append (refname, relpath);
g_free (relpath);
g_hash_table_insert (refs, g_string_free (refname, FALSE), contents); g_hash_table_insert (refs, g_string_free (refname, FALSE), contents);
@ -116,44 +114,69 @@ write_checksum_file_at (OstreeRepo *self,
return ret; return ret;
} }
static gboolean
openat_ignore_enoent (int dfd,
const char *path,
int *out_fd,
GError **error)
{
gboolean ret = FALSE;
int target_fd = -1;
target_fd = openat (dfd, path, O_CLOEXEC | O_RDONLY);
if (target_fd < 0)
{
if (errno != ENOENT)
{
glnx_set_error_from_errno (error);
goto out;
}
}
ret = TRUE;
*out_fd = target_fd;
out:
return ret;
}
static gboolean static gboolean
find_ref_in_remotes (OstreeRepo *self, find_ref_in_remotes (OstreeRepo *self,
const char *rev, const char *rev,
GFile **out_file, int *out_fd,
GError **error) GError **error)
{ {
gboolean ret = FALSE; gboolean ret = FALSE;
g_autoptr(GFileEnumerator) dir_enum = NULL; g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
g_autoptr(GFile) ret_file = NULL; glnx_fd_close int ret_fd = -1;
dir_enum = g_file_enumerate_children (self->remote_heads_dir, OSTREE_GIO_FAST_QUERYINFO, if (!glnx_dirfd_iterator_init_at (self->repo_dir_fd, "refs/remotes", TRUE, &dfd_iter, error))
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
NULL, error);
if (!dir_enum)
goto out; goto out;
while (TRUE) while (TRUE)
{ {
GFileInfo *file_info; struct dirent *dent = NULL;
GFile *child; glnx_fd_close int remote_dfd = -1;
if (!gs_file_enumerator_iterate (dir_enum, &file_info, &child,
NULL, error)) if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, NULL, error))
goto out; goto out;
if (file_info == NULL) if (dent == NULL)
break; break;
if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY)
if (dent->d_type != DT_DIR)
continue; continue;
g_clear_object (&ret_file); if (!glnx_opendirat (dfd_iter.fd, dent->d_name, TRUE, &remote_dfd, error))
ret_file = g_file_resolve_relative_path (child, rev); goto out;
if (!g_file_query_exists (ret_file, NULL))
g_clear_object (&ret_file); if (!openat_ignore_enoent (remote_dfd, rev, &ret_fd, error))
else goto out;
if (ret_fd != -1)
break; break;
} }
ret = TRUE; ret = TRUE;
ot_transfer_out_value (out_file, &ret_file); *out_fd = ret_fd; ret_fd = -1;
out: out:
return ret; return ret;
} }
@ -210,9 +233,8 @@ resolve_refspec (OstreeRepo *self,
{ {
gboolean ret = FALSE; gboolean ret = FALSE;
__attribute__((unused)) GCancellable *cancellable = NULL; __attribute__((unused)) GCancellable *cancellable = NULL;
GError *temp_error = NULL;
g_autofree char *ret_rev = NULL; g_autofree char *ret_rev = NULL;
g_autoptr(GFile) child = NULL; glnx_fd_close int target_fd = -1;
g_return_val_if_fail (ref != NULL, FALSE); g_return_val_if_fail (ref != NULL, FALSE);
@ -223,40 +245,42 @@ resolve_refspec (OstreeRepo *self,
} }
else if (remote != NULL) else if (remote != NULL)
{ {
child = ot_gfile_resolve_path_printf (self->remote_heads_dir, "%s/%s", const char *remote_ref = glnx_strjoina ("refs/remotes/", remote, "/", ref);
remote, ref);
if (!g_file_query_exists (child, NULL)) if (!openat_ignore_enoent (self->repo_dir_fd, remote_ref, &target_fd, error))
g_clear_object (&child); goto out;
} }
else else
{ {
child = g_file_resolve_relative_path (self->local_heads_dir, ref); const char *local_ref = glnx_strjoina ("refs/heads/", ref);
if (!g_file_query_exists (child, NULL)) if (!openat_ignore_enoent (self->repo_dir_fd, local_ref, &target_fd, error))
goto out;
if (target_fd == -1)
{ {
g_clear_object (&child); local_ref = glnx_strjoina ("refs/remotes/", ref);
child = g_file_resolve_relative_path (self->remote_heads_dir, ref); if (!openat_ignore_enoent (self->repo_dir_fd, local_ref, &target_fd, error))
goto out;
if (!g_file_query_exists (child, NULL)) if (target_fd == -1)
{ {
g_clear_object (&child); if (!find_ref_in_remotes (self, ref, &target_fd, error))
if (!find_ref_in_remotes (self, ref, &child, error))
goto out; goto out;
} }
} }
} }
if (child) if (target_fd != -1)
{ {
if ((ret_rev = gs_file_load_contents_utf8 (child, NULL, &temp_error)) == NULL) ret_rev = glnx_fd_readall_utf8 (target_fd, NULL, NULL, error);
if (!ret_rev)
{ {
g_propagate_error (error, temp_error); g_prefix_error (error, "Couldn't open ref '%s': ", ref);
g_prefix_error (error, "Couldn't open ref '%s': ", gs_file_get_path_cached (child));
goto out; goto out;
} }
g_strchomp (ret_rev); g_strchomp (ret_rev);
if (!ostree_validate_checksum_string (ret_rev, error)) if (!ostree_validate_checksum_string (ret_rev, error))
goto out; goto out;
@ -433,43 +457,50 @@ ostree_repo_resolve_rev (OstreeRepo *self,
static gboolean static gboolean
enumerate_refs_recurse (OstreeRepo *repo, enumerate_refs_recurse (OstreeRepo *repo,
const char *remote, const char *remote,
GFile *base, int base_dfd,
GFile *dir, GString *base_path,
int child_dfd,
const char *path,
GHashTable *refs, GHashTable *refs,
GCancellable *cancellable, GCancellable *cancellable,
GError **error) GError **error)
{ {
gboolean ret = FALSE; gboolean ret = FALSE;
g_autoptr(GFileEnumerator) enumerator = NULL; g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
enumerator = g_file_enumerate_children (dir, OSTREE_GIO_FAST_QUERYINFO, if (!glnx_dirfd_iterator_init_at (child_dfd, path, FALSE, &dfd_iter, error))
G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
cancellable, error);
if (!enumerator)
goto out; goto out;
while (TRUE) while (TRUE)
{ {
GFileInfo *file_info = NULL; guint len = base_path->len;
GFile *child = NULL; struct dirent *dent = NULL;
if (!gs_file_enumerator_iterate (enumerator, &file_info, &child, if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
NULL, error))
goto out; goto out;
if (file_info == NULL) if (dent == NULL)
break; break;
if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) g_string_append (base_path, dent->d_name);
if (dent->d_type == DT_DIR)
{ {
if (!enumerate_refs_recurse (repo, remote, base, child, refs, cancellable, error)) g_string_append_c (base_path, '/');
if (!enumerate_refs_recurse (repo, remote, base_dfd, base_path,
dfd_iter.fd, dent->d_name,
refs, cancellable, error))
goto out; goto out;
} }
else if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_REGULAR) else if (dent->d_type == DT_REG)
{ {
if (!add_ref_to_set (remote, base, child, refs, if (!add_ref_to_set (remote, base_dfd, base_path->str, refs,
cancellable, error)) cancellable, error))
goto out; goto out;
} }
g_string_truncate (base_path, len);
} }
ret = TRUE; ret = TRUE;
@ -505,35 +536,55 @@ ostree_repo_list_refs (OstreeRepo *self,
if (refspec_prefix) if (refspec_prefix)
{ {
g_autoptr(GFile) dir = NULL; struct stat stbuf;
g_autoptr(GFile) child = NULL; const char *prefix_path;
g_autoptr(GFileInfo) info = NULL; const char *path;
if (!ostree_parse_refspec (refspec_prefix, &remote, &ref_prefix, error)) if (!ostree_parse_refspec (refspec_prefix, &remote, &ref_prefix, error))
goto out; goto out;
if (remote) if (remote)
dir = g_file_get_child (self->remote_heads_dir, remote);
else
dir = g_object_ref (self->local_heads_dir);
child = g_file_resolve_relative_path (dir, ref_prefix);
if (!ot_gfile_query_info_allow_noent (child, OSTREE_GIO_FAST_QUERYINFO, 0,
&info, cancellable, error))
goto out;
if (info)
{ {
if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) prefix_path = glnx_strjoina ("refs/remotes/", remote, "/");
path = glnx_strjoina (prefix_path, ref_prefix);
}
else
{
prefix_path = "refs/heads/";
path = glnx_strjoina (prefix_path, ref_prefix);
}
if (fstatat (self->repo_dir_fd, path, &stbuf, 0) < 0)
{
if (errno != ENOENT)
{ {
if (!enumerate_refs_recurse (self, remote, child, child, glnx_set_error_from_errno (error);
ret_all_refs, goto out;
cancellable, error)) }
}
else
{
if (S_ISDIR (stbuf.st_mode))
{
glnx_fd_close int base_fd = -1;
g_autoptr(GString) base_path = g_string_new ("");
if (!glnx_opendirat (self->repo_dir_fd, path, TRUE, &base_fd, error))
goto out;
if (!enumerate_refs_recurse (self, remote, base_fd, base_path,
base_fd, ".",
ret_all_refs, cancellable, error))
goto out; goto out;
} }
else else
{ {
if (!add_ref_to_set (remote, dir, child, ret_all_refs, glnx_fd_close int prefix_dfd = -1;
if (!glnx_opendirat (self->repo_dir_fd, prefix_path, TRUE, &prefix_dfd, error))
goto out;
if (!add_ref_to_set (remote, prefix_dfd, ref_prefix, ret_all_refs,
cancellable, error)) cancellable, error))
goto out; goto out;
} }
@ -541,31 +592,41 @@ ostree_repo_list_refs (OstreeRepo *self,
} }
else else
{ {
g_autoptr(GFileEnumerator) remote_enumerator = NULL; g_auto(GLnxDirFdIterator) dfd_iter = { 0, };
g_autoptr(GString) base_path = g_string_new ("");
if (!enumerate_refs_recurse (self, NULL, self->local_heads_dir, self->local_heads_dir, glnx_fd_close int refs_heads_dfd = -1;
ret_all_refs,
cancellable, error)) if (!glnx_opendirat (self->repo_dir_fd, "refs/heads", TRUE, &refs_heads_dfd, error))
goto out; goto out;
remote_enumerator = g_file_enumerate_children (self->remote_heads_dir, OSTREE_GIO_FAST_QUERYINFO, if (!enumerate_refs_recurse (self, NULL, refs_heads_dfd, base_path,
0, refs_heads_dfd, ".",
cancellable, error); ret_all_refs, cancellable, error))
goto out;
g_string_truncate (base_path, 0);
if (!glnx_dirfd_iterator_init_at (self->repo_dir_fd, "refs/remotes", TRUE, &dfd_iter, error))
goto out;
while (TRUE) while (TRUE)
{ {
GFileInfo *info; struct dirent *dent;
GFile *child; glnx_fd_close int remote_dfd = -1;
const char *name;
if (!gs_file_enumerator_iterate (remote_enumerator, &info, &child, if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, cancellable, error))
cancellable, error))
goto out; goto out;
if (!info) if (!dent)
break; break;
name = g_file_info_get_name (info); if (dent->d_type != DT_DIR)
if (!enumerate_refs_recurse (self, name, child, child, continue;
if (!glnx_opendirat (dfd_iter.fd, dent->d_name, TRUE, &remote_dfd, error))
goto out;
if (!enumerate_refs_recurse (self, dent->d_name, remote_dfd, base_path,
remote_dfd, ".",
ret_all_refs, ret_all_refs,
cancellable, error)) cancellable, error))
goto out; goto out;

View File

@ -59,6 +59,7 @@ typedef struct {
guint n_rollsum; guint n_rollsum;
guint n_bsdiff; guint n_bsdiff;
guint n_fallback; guint n_fallback;
gboolean swap_endian;
} OstreeStaticDeltaBuilder; } OstreeStaticDeltaBuilder;
typedef enum { typedef enum {
@ -228,7 +229,7 @@ objtype_checksum_array_new (GPtrArray *objects)
GVariant *serialized_key = objects->pdata[i]; GVariant *serialized_key = objects->pdata[i];
OstreeObjectType objtype; OstreeObjectType objtype;
const char *checksum; const char *checksum;
guint8 csum[32]; guint8 csum[OSTREE_SHA256_DIGEST_LEN];
guint8 objtype_v; guint8 objtype_v;
ostree_object_name_deserialize (serialized_key, &checksum, &objtype); ostree_object_name_deserialize (serialized_key, &checksum, &objtype);
@ -678,7 +679,7 @@ process_one_rollsum (OstreeRepo *repo,
{ gsize mode_offset, xattr_offset, from_csum_offset; { gsize mode_offset, xattr_offset, from_csum_offset;
gboolean reading_payload = TRUE; gboolean reading_payload = TRUE;
guchar source_csum[32]; guchar source_csum[OSTREE_SHA256_DIGEST_LEN];
guint i; guint i;
write_content_mode_xattrs (repo, current_part, content_finfo, content_xattrs, write_content_mode_xattrs (repo, current_part, content_finfo, content_xattrs,
@ -799,7 +800,7 @@ process_one_bsdiff (OstreeRepo *repo,
g_ptr_array_add (current_part->objects, ostree_object_name_serialize (to_checksum, OSTREE_OBJECT_TYPE_FILE)); g_ptr_array_add (current_part->objects, ostree_object_name_serialize (to_checksum, OSTREE_OBJECT_TYPE_FILE));
{ gsize mode_offset, xattr_offset; { gsize mode_offset, xattr_offset;
guchar source_csum[32]; guchar source_csum[OSTREE_SHA256_DIGEST_LEN];
write_content_mode_xattrs (repo, current_part, content_finfo, content_xattrs, write_content_mode_xattrs (repo, current_part, content_finfo, content_xattrs,
&mode_offset, &xattr_offset); &mode_offset, &xattr_offset);
@ -1191,7 +1192,8 @@ get_fallback_headers (OstreeRepo *self,
g_variant_new ("(y@aytt)", g_variant_new ("(y@aytt)",
objtype, objtype,
ostree_checksum_to_bytes_v (checksum), ostree_checksum_to_bytes_v (checksum),
compressed_size, uncompressed_size)); maybe_swap_endian_u64 (builder->swap_endian, compressed_size),
maybe_swap_endian_u64 (builder->swap_endian, uncompressed_size)));
} }
ret_headers = g_variant_ref_sink (g_variant_builder_end (fallback_builder)); ret_headers = g_variant_ref_sink (g_variant_builder_end (fallback_builder));
@ -1228,6 +1230,7 @@ get_fallback_headers (OstreeRepo *self,
* - bsdiff-enabled: b: Enable bsdiff compression. Default TRUE. * - bsdiff-enabled: b: Enable bsdiff compression. Default TRUE.
* - inline-parts: b: Put part data in header, to get a single file delta. Default FALSE. * - inline-parts: b: Put part data in header, to get a single file delta. Default FALSE.
* - verbose: b: Print diagnostic messages. Default FALSE. * - verbose: b: Print diagnostic messages. Default FALSE.
* - endianness: b: Deltas use host byte order by default; this option allows choosing (G_BIG_ENDIAN or G_LITTLE_ENDIAN)
* - filename: ay: Save delta superblock to this filename, and parts in the same directory. Default saves to repository. * - filename: ay: Save delta superblock to this filename, and parts in the same directory. Default saves to repository.
*/ */
gboolean gboolean
@ -1262,6 +1265,7 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
g_autoptr(GVariant) fallback_headers = NULL; g_autoptr(GVariant) fallback_headers = NULL;
g_autoptr(GVariant) detached = NULL; g_autoptr(GVariant) detached = NULL;
gboolean inline_parts; gboolean inline_parts;
guint endianness = G_BYTE_ORDER;
g_autoptr(GFile) tmp_dir = NULL; g_autoptr(GFile) tmp_dir = NULL;
builder.parts = g_ptr_array_new_with_free_func ((GDestroyNotify)ostree_static_delta_part_builder_unref); builder.parts = g_ptr_array_new_with_free_func ((GDestroyNotify)ostree_static_delta_part_builder_unref);
builder.fallback_objects = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref); builder.fallback_objects = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref);
@ -1277,6 +1281,11 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
max_chunk_size = 32; max_chunk_size = 32;
builder.max_chunk_size_bytes = ((guint64)max_chunk_size) * 1000 * 1000; builder.max_chunk_size_bytes = ((guint64)max_chunk_size) * 1000 * 1000;
(void) g_variant_lookup (params, "endianness", "u", &endianness);
g_return_val_if_fail (endianness == G_BIG_ENDIAN || endianness == G_LITTLE_ENDIAN, FALSE);
builder.swap_endian = endianness != G_BYTE_ORDER;
{ gboolean use_bsdiff; { gboolean use_bsdiff;
if (!g_variant_lookup (params, "bsdiff-enabled", "b", &use_bsdiff)) if (!g_variant_lookup (params, "bsdiff-enabled", "b", &use_bsdiff))
use_bsdiff = TRUE; use_bsdiff = TRUE;
@ -1306,6 +1315,10 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
cancellable, error)) cancellable, error))
goto out; goto out;
/* NOTE: Add user-supplied metadata first. This is used by at least
* xdg-app as a way to provide MIME content sniffing, since the
* metadata appears first in the file.
*/
g_variant_builder_init (&metadata_builder, G_VARIANT_TYPE ("a{sv}")); g_variant_builder_init (&metadata_builder, G_VARIANT_TYPE ("a{sv}"));
if (metadata != NULL) if (metadata != NULL)
{ {
@ -1320,6 +1333,22 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
} }
} }
{ guint8 endianness_char;
switch (endianness)
{
case G_LITTLE_ENDIAN:
endianness_char = 'l';
break;
case G_BIG_ENDIAN:
endianness_char = 'B';
break;
default:
g_assert_not_reached ();
}
g_variant_builder_add (&metadata_builder, "{sv}", "ostree.endianness", g_variant_new_byte (endianness_char));
}
if (opt_filename) if (opt_filename)
{ {
g_autoptr(GFile) f = g_file_new_for_path (opt_filename); g_autoptr(GFile) f = g_file_new_for_path (opt_filename);
@ -1411,13 +1440,13 @@ ostree_repo_static_delta_generate (OstreeRepo *self,
cancellable, error)) cancellable, error))
goto out; goto out;
checksum_bytes = g_bytes_new (part_checksum, 32); checksum_bytes = g_bytes_new (part_checksum, OSTREE_SHA256_DIGEST_LEN);
objtype_checksum_array = objtype_checksum_array_new (part_builder->objects); objtype_checksum_array = objtype_checksum_array_new (part_builder->objects);
delta_part_header = g_variant_new ("(u@aytt@ay)", delta_part_header = g_variant_new ("(u@aytt@ay)",
OSTREE_DELTAPART_VERSION, maybe_swap_endian_u32 (builder.swap_endian, OSTREE_DELTAPART_VERSION),
ot_gvariant_new_ay_bytes (checksum_bytes), ot_gvariant_new_ay_bytes (checksum_bytes),
(guint64) g_variant_get_size (delta_part), maybe_swap_endian_u64 (builder.swap_endian, (guint64) g_variant_get_size (delta_part)),
part_builder->uncompressed_size, maybe_swap_endian_u64 (builder.swap_endian, part_builder->uncompressed_size),
ot_gvariant_new_ay_bytes (objtype_checksum_array)); ot_gvariant_new_ay_bytes (objtype_checksum_array));
g_variant_builder_add_value (part_headers, g_variant_ref (delta_part_header)); g_variant_builder_add_value (part_headers, g_variant_ref (delta_part_header));

View File

@ -20,8 +20,14 @@
#include "config.h" #include "config.h"
#include <gio/gunixinputstream.h>
#include <gio/gunixoutputstream.h>
#include <gio/gfiledescriptorbased.h>
#include "ostree-core-private.h" #include "ostree-core-private.h"
#include "ostree-repo-private.h" #include "ostree-repo-private.h"
#include "ostree-lzma-decompressor.h"
#include "ostree-cmdprivate.h"
#include "ostree-checksum-input-stream.h"
#include "ostree-repo-static-delta-private.h" #include "ostree-repo-static-delta-private.h"
#include "otutil.h" #include "otutil.h"
@ -131,7 +137,7 @@ ostree_repo_list_static_delta_names (OstreeRepo *self,
g_autofree char *buf = g_strconcat (name1, name2, NULL); g_autofree char *buf = g_strconcat (name1, name2, NULL);
GString *out = g_string_new (""); GString *out = g_string_new ("");
char checksum[65]; char checksum[65];
guchar csum[32]; guchar csum[OSTREE_SHA256_DIGEST_LEN];
const char *dash = strchr (buf, '-'); const char *dash = strchr (buf, '-');
ostree_checksum_b64_inplace_to_bytes (buf, csum); ostree_checksum_b64_inplace_to_bytes (buf, csum);
@ -225,31 +231,49 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self,
{ {
gboolean ret = FALSE; gboolean ret = FALSE;
guint i, n; guint i, n;
g_autoptr(GFile) meta_file = NULL; const char *dir_or_file_path = NULL;
g_autoptr(GFile) dir = NULL; glnx_fd_close int meta_fd = -1;
glnx_fd_close int dfd = -1;
g_autoptr(GVariant) meta = NULL; g_autoptr(GVariant) meta = NULL;
g_autoptr(GVariant) headers = NULL; g_autoptr(GVariant) headers = NULL;
g_autoptr(GVariant) metadata = NULL; g_autoptr(GVariant) metadata = NULL;
g_autoptr(GVariant) fallback = NULL; g_autoptr(GVariant) fallback = NULL;
g_autofree char *to_checksum = NULL; g_autofree char *to_checksum = NULL;
g_autofree char *from_checksum = NULL; g_autofree char *from_checksum = NULL;
GFileType file_type; g_autofree char *basename = NULL;
dir_or_file_path = gs_file_get_path_cached (dir_or_file);
file_type = g_file_query_file_type (dir_or_file, 0, cancellable); /* First, try opening it as a directory */
if (file_type == G_FILE_TYPE_DIRECTORY) dfd = glnx_opendirat_with_errno (AT_FDCWD, dir_or_file_path, TRUE);
if (dfd < 0)
{ {
dir = g_object_ref (dir_or_file); if (errno != ENOTDIR)
meta_file = g_file_get_child (dir, "superblock"); {
glnx_set_error_from_errno (error);
goto out;
}
else
{
g_autofree char *dir = dirname (g_strdup (dir_or_file_path));
basename = g_path_get_basename (dir_or_file_path);
if (!glnx_opendirat (AT_FDCWD, dir, TRUE, &dfd, error))
goto out;
}
} }
else else
{ basename = g_strdup ("superblock");
meta_file = g_object_ref (dir_or_file);
dir = g_file_get_parent (meta_file);
}
if (!ot_util_variant_map (meta_file, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT), meta_fd = openat (dfd, basename, O_RDONLY | O_CLOEXEC);
FALSE, &meta, error)) if (meta_fd < 0)
{
glnx_set_error_from_errno (error);
goto out;
}
if (!ot_util_variant_map_fd (meta_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT),
FALSE, &meta, error))
goto out; goto out;
/* Parsing OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT */ /* Parsing OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT */
@ -329,14 +353,18 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self,
guint64 size; guint64 size;
guint64 usize; guint64 usize;
const guchar *csum; const guchar *csum;
char checksum[65];
gboolean have_all; gboolean have_all;
g_autoptr(GInputStream) part_in = NULL;
g_autoptr(GBytes) delta_data = NULL; g_autoptr(GBytes) delta_data = NULL;
g_autoptr(GVariant) part_data = NULL; g_autoptr(GVariant) inline_part_data = NULL;
g_autoptr(GVariant) header = NULL; g_autoptr(GVariant) header = NULL;
g_autoptr(GVariant) csum_v = NULL; g_autoptr(GVariant) csum_v = NULL;
g_autoptr(GVariant) objects = NULL; g_autoptr(GVariant) objects = NULL;
g_autoptr(GBytes) bytes = NULL; g_autoptr(GVariant) part = NULL;
g_autofree char *deltapart_path = NULL; g_autofree char *deltapart_path = NULL;
OstreeStaticDeltaOpenFlags delta_open_flags =
skip_validation ? OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM : 0;
header = g_variant_get_child_value (headers, i); header = g_variant_get_child_value (headers, i);
g_variant_get (header, "(u@aytt@ay)", &version, &csum_v, &size, &usize, &objects); g_variant_get (header, "(u@aytt@ay)", &version, &csum_v, &size, &usize, &objects);
@ -361,41 +389,57 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self,
csum = ostree_checksum_bytes_peek_validate (csum_v, error); csum = ostree_checksum_bytes_peek_validate (csum_v, error);
if (!csum) if (!csum)
goto out; goto out;
ostree_checksum_inplace_from_bytes (csum, checksum);
deltapart_path = deltapart_path =
_ostree_get_relative_static_delta_part_path (from_checksum, to_checksum, i); _ostree_get_relative_static_delta_part_path (from_checksum, to_checksum, i);
part_data = g_variant_lookup_value (metadata, deltapart_path, G_VARIANT_TYPE("(yay)")); inline_part_data = g_variant_lookup_value (metadata, deltapart_path, G_VARIANT_TYPE("(yay)"));
if (part_data) if (inline_part_data)
{ {
bytes = g_variant_get_data_as_bytes (part_data); g_autoptr(GBytes) inline_part_bytes = g_variant_get_data_as_bytes (inline_part_data);
part_in = g_memory_input_stream_new_from_bytes (inline_part_bytes);
/* For inline parts, we don't checksum, because it's
* included with the metadata, so we're not trying to
* protect against MITM or such. Non-security related
* checksums should be done at the underlying storage layer.
*/
delta_open_flags |= OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM;
if (!_ostree_static_delta_part_open (part_in, inline_part_bytes,
delta_open_flags,
NULL,
&part,
cancellable, error))
goto out;
} }
else else
{ {
g_autoptr(GFile) part_path = ot_gfile_resolve_path_printf (dir, "%u", i); g_autofree char *relpath = g_strdup_printf ("%u", i); /* TODO avoid malloc here */
GMappedFile *mfile = gs_file_map_noatime (part_path, cancellable, error); glnx_fd_close int part_fd = openat (dfd, relpath, O_RDONLY | O_CLOEXEC);
if (!mfile) if (part_fd < 0)
goto out; {
glnx_set_error_from_errno (error);
g_prefix_error (error, "Opening deltapart '%s': ", deltapart_path);
goto out;
}
bytes = g_mapped_file_get_bytes (mfile); part_in = g_unix_input_stream_new (part_fd, FALSE);
g_mapped_file_unref (mfile);
}
if (!skip_validation) if (!_ostree_static_delta_part_open (part_in, NULL,
{ delta_open_flags,
g_autoptr(GInputStream) in = g_memory_input_stream_new_from_bytes (bytes); checksum,
&part,
g_autofree char *expected_checksum = ostree_checksum_from_bytes (csum); cancellable, error))
if (!_ostree_static_delta_part_validate (self, in, i,
expected_checksum,
cancellable, error))
goto out; goto out;
} }
if (!_ostree_static_delta_part_execute (self, objects, bytes, skip_validation, if (!_ostree_static_delta_part_execute (self, objects, part, skip_validation,
FALSE, NULL,
cancellable, error)) cancellable, error))
{ {
g_prefix_error (error, "executing delta part %i: ", i); g_prefix_error (error, "Executing delta part %i: ", i);
goto out; goto out;
} }
} }
@ -404,3 +448,462 @@ ostree_repo_static_delta_execute_offline (OstreeRepo *self,
out: out:
return ret; return ret;
} }
gboolean
_ostree_static_delta_part_open (GInputStream *part_in,
GBytes *inline_part_bytes,
OstreeStaticDeltaOpenFlags flags,
const char *expected_checksum,
GVariant **out_part,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
const gboolean trusted = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_VARIANT_TRUSTED) > 0;
const gboolean skip_checksum = (flags & OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM) > 0;
gsize bytes_read;
guint8 comptype;
g_autoptr(GChecksum) checksum = NULL;
g_autoptr(GInputStream) checksum_in = NULL;
g_autoptr(GVariant) ret_part = NULL;
GInputStream *source_in;
/* We either take a fd or a GBytes reference */
g_return_val_if_fail (G_IS_FILE_DESCRIPTOR_BASED (part_in) || inline_part_bytes != NULL, FALSE);
g_return_val_if_fail (skip_checksum || expected_checksum != NULL, FALSE);
if (!skip_checksum)
{
checksum = g_checksum_new (G_CHECKSUM_SHA256);
checksum_in = (GInputStream*)ostree_checksum_input_stream_new (part_in, checksum);
source_in = checksum_in;
}
else
{
source_in = part_in;
}
{ guint8 buf[1];
/* First byte is compression type */
if (!g_input_stream_read_all (source_in, buf, sizeof(buf), &bytes_read,
cancellable, error))
{
g_prefix_error (error, "Reading initial compression flag byte: ");
goto out;
}
comptype = buf[0];
}
switch (comptype)
{
case 0:
if (!inline_part_bytes)
{
int part_fd = g_file_descriptor_based_get_fd ((GFileDescriptorBased*)part_in);
/* No compression, no checksums - a fast path */
if (!ot_util_variant_map_fd (part_fd, 1, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0),
trusted, &ret_part, error))
goto out;
}
else
{
g_autoptr(GBytes) content_bytes = g_bytes_new_from_bytes (inline_part_bytes, 1,
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);
}
if (!skip_checksum)
g_checksum_update (checksum, g_variant_get_data (ret_part),
g_variant_get_size (ret_part));
break;
case 'x':
{
g_autofree char *tmppath = g_strdup ("/var/tmp/ostree-delta-XXXXXX");
g_autoptr(GConverter) decomp = (GConverter*) _ostree_lzma_decompressor_new ();
g_autoptr(GInputStream) convin = g_converter_input_stream_new (source_in, decomp);
g_autoptr(GOutputStream) unpacked_out = NULL;
glnx_fd_close int unpacked_fd = -1;
gssize n_bytes_written;
unpacked_fd = g_mkstemp_full (tmppath, O_RDWR | O_CLOEXEC, 0640);
if (unpacked_fd < 0)
{
glnx_set_error_from_errno (error);
goto out;
}
/* Now make it autocleanup on process exit - in the future, we
* should consider caching unpacked deltas as well.
*/
if (unlink (tmppath) < 0)
{
glnx_set_error_from_errno (error);
goto out;
}
unpacked_out = g_unix_output_stream_new (unpacked_fd, FALSE);
n_bytes_written = g_output_stream_splice (unpacked_out, convin,
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
cancellable, error);
if (n_bytes_written < 0)
goto out;
if (!ot_util_variant_map_fd (unpacked_fd, 0, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0),
trusted, &ret_part, error))
goto out;
}
break;
default:
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid compression type '%u'", comptype);
goto out;
}
if (checksum)
{
const char *actual_checksum = g_checksum_get_string (checksum);
g_assert (expected_checksum != NULL);
if (strcmp (actual_checksum, expected_checksum) != 0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Checksum mismatch in static delta part; expected=%s actual=%s",
expected_checksum, actual_checksum);
goto out;
}
}
ret = TRUE;
*out_part = g_steal_pointer (&ret_part);
out:
return ret;
}
/*
* Displaying static delta parts
*/
static gboolean
show_one_part (OstreeRepo *self,
gboolean swap_endian,
const char *from,
const char *to,
GVariant *meta_entries,
guint i,
guint64 *total_size_ref,
guint64 *total_usize_ref,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
guint32 version;
guint64 size, usize;
g_autoptr(GVariant) objects = NULL;
g_autoptr(GInputStream) part_in = NULL;
g_autoptr(GVariant) part = NULL;
g_autofree char *part_path = _ostree_get_relative_static_delta_part_path (from, to, i);
gint part_fd = -1;
g_variant_get_child (meta_entries, i, "(u@aytt@ay)", &version, NULL, &size, &usize, &objects);
size = maybe_swap_endian_u64 (swap_endian, size);
usize = maybe_swap_endian_u64 (swap_endian, usize);
*total_size_ref += size;
*total_usize_ref += usize;
g_print ("PartMeta%u: nobjects=%u size=%" G_GUINT64_FORMAT " usize=%" G_GUINT64_FORMAT "\n",
i, (guint)(g_variant_get_size (objects) / OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN), size, usize);
part_fd = openat (self->repo_dir_fd, part_path, O_RDONLY | O_CLOEXEC);
if (part_fd < 0)
{
glnx_set_error_from_errno (error);
goto out;
}
part_in = g_unix_input_stream_new (part_fd, FALSE);
if (!_ostree_static_delta_part_open (part_in, NULL,
OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM,
NULL,
&part,
cancellable, error))
goto out;
{ g_autoptr(GVariant) modes = NULL;
g_autoptr(GVariant) xattrs = NULL;
g_autoptr(GVariant) blob = NULL;
g_autoptr(GVariant) ops = NULL;
OstreeDeltaExecuteStats stats = { { 0, }, };
g_variant_get (part, "(@a(uuu)@aa(ayay)@ay@ay)",
&modes, &xattrs, &blob, &ops);
g_print ("PartPayload%u: nmodes=%" G_GUINT64_FORMAT
" nxattrs=%" G_GUINT64_FORMAT
" blobsize=%" G_GUINT64_FORMAT
" opsize=%" G_GUINT64_FORMAT
"\n",
i,
(guint64)g_variant_n_children (modes),
(guint64)g_variant_n_children (xattrs),
(guint64)g_variant_n_children (blob),
(guint64)g_variant_n_children (ops));
if (!_ostree_static_delta_part_execute (self, objects,
part, TRUE, TRUE,
&stats, cancellable, error))
goto out;
{ const guint *n_ops = stats.n_ops_executed;
g_print ("PartPayloadOps%u: openspliceclose=%u open=%u write=%u setread=%u "
"unsetread=%u close=%u bspatch=%u\n",
i, n_ops[0], n_ops[1], n_ops[2], n_ops[3], n_ops[4], n_ops[5], n_ops[6]);
}
}
ret = TRUE;
out:
return ret;
}
OstreeDeltaEndianness
_ostree_delta_get_endianness (GVariant *superblock,
gboolean *out_was_heuristic)
{
guint8 endianness_char;
g_autoptr(GVariant) delta_meta = NULL;
g_autoptr(GVariantDict) delta_metadict = NULL;
guint64 total_size = 0;
guint64 total_usize = 0;
guint total_objects = 0;
delta_meta = g_variant_get_child_value (superblock, 0);
delta_metadict = g_variant_dict_new (delta_meta);
if (out_was_heuristic)
*out_was_heuristic = FALSE;
if (g_variant_dict_lookup (delta_metadict, "ostree.endianness", "y", &endianness_char))
{
switch (endianness_char)
{
case 'l':
return OSTREE_DELTA_ENDIAN_LITTLE;
case 'B':
return OSTREE_DELTA_ENDIAN_BIG;
default:
return OSTREE_DELTA_ENDIAN_INVALID;
}
}
if (out_was_heuristic)
*out_was_heuristic = TRUE;
{ g_autoptr(GVariant) meta_entries = NULL;
guint n_parts;
guint i;
gboolean is_byteswapped = FALSE;
g_variant_get_child (superblock, 6, "@a" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT, &meta_entries);
n_parts = g_variant_n_children (meta_entries);
for (i = 0; i < n_parts; i++)
{
g_autoptr(GVariant) objects = NULL;
guint64 size, usize;
guint n_objects;
g_variant_get_child (meta_entries, i, "(u@aytt@ay)", NULL, NULL, &size, &usize, &objects);
n_objects = (guint)(g_variant_get_size (objects) / OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN);
total_objects += n_objects;
total_size += size;
total_usize += usize;
if (size > usize)
{
double ratio = ((double)size)/((double)usize);
/* This should really never happen where compressing things makes it more than 50% bigger.
*/
if (ratio > 1.2)
{
is_byteswapped = TRUE;
break;
}
}
}
if (!is_byteswapped)
{
/* If the average object size is greater than 4GiB, let's assume
* we're dealing with opposite endianness. I'm fairly confident
* no one is going to be shipping peta- or exa- byte size ostree
* deltas, period. Past the gigabyte scale you really want
* bittorrent or something.
*/
if ((total_size / total_objects) > G_MAXUINT32)
{
is_byteswapped = TRUE;
}
}
if (is_byteswapped)
{
switch (G_BYTE_ORDER)
{
case G_BIG_ENDIAN:
return OSTREE_DELTA_ENDIAN_LITTLE;
case G_LITTLE_ENDIAN:
return OSTREE_DELTA_ENDIAN_BIG;
default:
g_assert_not_reached ();
}
}
return G_BYTE_ORDER;
}
}
gboolean
_ostree_delta_needs_byteswap (GVariant *superblock)
{
switch (_ostree_delta_get_endianness (superblock, NULL))
{
case OSTREE_DELTA_ENDIAN_BIG:
return G_BYTE_ORDER == G_LITTLE_ENDIAN;
case OSTREE_DELTA_ENDIAN_LITTLE:
return G_BYTE_ORDER == G_BIG_ENDIAN;
default:
return FALSE;
}
}
gboolean
_ostree_repo_static_delta_dump (OstreeRepo *self,
const char *delta_id,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
g_autofree char *from = NULL;
g_autofree char *to = NULL;
g_autofree char *superblock_path = NULL;
glnx_fd_close int superblock_fd = -1;
g_autoptr(GVariant) delta_superblock = NULL;
guint64 total_size = 0, total_usize = 0;
guint64 total_fallback_size = 0, total_fallback_usize = 0;
guint i;
OstreeDeltaEndianness endianness;
gboolean swap_endian = FALSE;
_ostree_parse_delta_name (delta_id, &from, &to);
superblock_path = _ostree_get_relative_static_delta_superblock_path (from, to);
if (!ot_util_variant_map_at (self->repo_dir_fd, superblock_path,
(GVariantType*)OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT,
TRUE, &delta_superblock, error))
goto out;
g_print ("%s\n", g_variant_print (delta_superblock, 1));
g_print ("Delta: %s\n", delta_id);
{ const char *endianness_description;
gboolean was_heuristic;
endianness = _ostree_delta_get_endianness (delta_superblock, &was_heuristic);
switch (endianness)
{
case OSTREE_DELTA_ENDIAN_BIG:
if (was_heuristic)
endianness_description = "big (heuristic)";
else
endianness_description = "big";
if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
swap_endian = TRUE;
break;
case OSTREE_DELTA_ENDIAN_LITTLE:
if (was_heuristic)
endianness_description = "little (heuristic)";
else
endianness_description = "little";
if (G_BYTE_ORDER == G_BIG_ENDIAN)
swap_endian = TRUE;
break;
case OSTREE_DELTA_ENDIAN_INVALID:
endianness_description = "invalid";
break;
default:
g_assert_not_reached ();
}
g_print ("Endianness: %s\n", endianness_description);
}
{ guint64 ts;
g_variant_get_child (delta_superblock, 1, "t", &ts);
g_print ("Timestamp: %" G_GUINT64_FORMAT "\n", GUINT64_FROM_BE (ts));
}
{ g_autoptr(GVariant) recurse = NULL;
g_variant_get_child (delta_superblock, 5, "@ay", &recurse);
g_print ("Number of parents: %u\n", (guint)(g_variant_get_size (recurse) / (OSTREE_SHA256_DIGEST_LEN * 2)));
}
{ g_autoptr(GVariant) fallback = NULL;
guint n_fallback;
g_variant_get_child (delta_superblock, 7, "@a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT, &fallback);
n_fallback = g_variant_n_children (fallback);
g_print ("Number of fallback entries: %u\n", n_fallback);
for (i = 0; i < n_fallback; i++)
{
guint64 size, usize;
g_variant_get_child (fallback, i, "(y@aytt)", NULL, NULL, &size, &usize);
size = maybe_swap_endian_u64 (swap_endian, size);
usize = maybe_swap_endian_u64 (swap_endian, usize);
total_fallback_size += size;
total_fallback_usize += usize;
}
{ g_autofree char *sizestr = g_format_size (total_fallback_size);
g_autofree char *usizestr = g_format_size (total_fallback_usize);
g_print ("Total Fallback Size: %" G_GUINT64_FORMAT " (%s)\n", total_fallback_size, sizestr);
g_print ("Total Fallback Uncompressed Size: %" G_GUINT64_FORMAT " (%s)\n", total_fallback_usize, usizestr);
}
}
{ g_autoptr(GVariant) meta_entries = NULL;
guint n_parts;
g_variant_get_child (delta_superblock, 6, "@a" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT, &meta_entries);
n_parts = g_variant_n_children (meta_entries);
g_print ("Number of parts: %u\n", n_parts);
for (i = 0; i < n_parts; i++)
{
if (!show_one_part (self, swap_endian, from, to, meta_entries, i,
&total_size, &total_usize,
cancellable, error))
goto out;
}
}
{ g_autofree char *sizestr = g_format_size (total_size);
g_autofree char *usizestr = g_format_size (total_usize);
g_print ("Total Part Size: %" G_GUINT64_FORMAT " (%s)\n", total_size, sizestr);
g_print ("Total Part Uncompressed Size: %" G_GUINT64_FORMAT " (%s)\n", total_usize, usizestr);
}
{ guint64 overall_size = total_size + total_fallback_size;
guint64 overall_usize = total_usize + total_fallback_usize;
g_autofree char *sizestr = g_format_size (overall_size);
g_autofree char *usizestr = g_format_size (overall_usize);
g_print ("Total Size: %" G_GUINT64_FORMAT " (%s)\n", overall_size, sizestr);
g_print ("Total Uncompressed Size: %" G_GUINT64_FORMAT " (%s)\n", overall_usize, usizestr);
}
ret = TRUE;
out:
return ret;
}

View File

@ -46,10 +46,10 @@ G_BEGIN_DECLS
/** /**
* OSTREE_STATIC_DELTA_META_ENTRY_FORMAT: * OSTREE_STATIC_DELTA_META_ENTRY_FORMAT:
* *
* u: version * u: version (non-canonical endian)
* ay checksum * ay checksum
* guint64 size: Total size of delta (sum of parts) * guint64 size: Total size of delta (sum of parts) (non-canonical endian)
* guint64 usize: Uncompressed size of resulting objects on disk * guint64 usize: Uncompressed size of resulting objects on disk (non-canonical endian)
* ARRAY[(guint8 objtype, csum object)] * ARRAY[(guint8 objtype, csum object)]
* *
* The checksum is of the delta payload, and each entry in the array * The checksum is of the delta payload, and each entry in the array
@ -64,8 +64,8 @@ G_BEGIN_DECLS
* *
* y: objtype * y: objtype
* ay: checksum * ay: checksum
* t: compressed size * t: compressed size (non-canonical endian)
* t: uncompressed size * t: uncompressed size (non-canonical endian)
* *
* Object to fetch invididually; includes compressed/uncompressed size. * Object to fetch invididually; includes compressed/uncompressed size.
*/ */
@ -79,7 +79,7 @@ G_BEGIN_DECLS
* *
* delta-descriptor: * delta-descriptor:
* metadata: a{sv} * metadata: a{sv}
* t: timestamp * t: timestamp (big endian)
* from: ay checksum * from: ay checksum
* to: ay checksum * to: ay checksum
* commit: new commit object * commit: new commit object
@ -103,38 +103,11 @@ G_BEGIN_DECLS
*/ */
#define OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT "(a{sv}tayay" OSTREE_COMMIT_GVARIANT_STRING "aya" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT ")" #define OSTREE_STATIC_DELTA_SUPERBLOCK_FORMAT "(a{sv}tayay" OSTREE_COMMIT_GVARIANT_STRING "aya" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "a" OSTREE_STATIC_DELTA_FALLBACK_FORMAT ")"
gboolean _ostree_static_delta_part_validate (OstreeRepo *repo, typedef enum {
GInputStream *in, OSTREE_STATIC_DELTA_OPEN_FLAGS_NONE = 0,
guint part_offset, OSTREE_STATIC_DELTA_OPEN_FLAGS_SKIP_CHECKSUM = (1 << 0),
const char *expected_checksum, OSTREE_STATIC_DELTA_OPEN_FLAGS_VARIANT_TRUSTED = (1 << 1)
GCancellable *cancellable, } OstreeStaticDeltaOpenFlags;
GError **error);
gboolean _ostree_static_delta_part_execute (OstreeRepo *repo,
GVariant *header,
GBytes *partdata,
gboolean trusted,
GCancellable *cancellable,
GError **error);
gboolean _ostree_static_delta_part_execute_raw (OstreeRepo *repo,
GVariant *header,
GVariant *part,
gboolean trusted,
GCancellable *cancellable,
GError **error);
void _ostree_static_delta_part_execute_async (OstreeRepo *repo,
GVariant *header,
GBytes *partdata,
gboolean trusted,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean _ostree_static_delta_part_execute_finish (OstreeRepo *repo,
GAsyncResult *result,
GError **error);
typedef enum { typedef enum {
OSTREE_STATIC_DELTA_OP_OPEN_SPLICE_AND_CLOSE = 'S', OSTREE_STATIC_DELTA_OP_OPEN_SPLICE_AND_CLOSE = 'S',
@ -145,6 +118,46 @@ typedef enum {
OSTREE_STATIC_DELTA_OP_CLOSE = 'c', OSTREE_STATIC_DELTA_OP_CLOSE = 'c',
OSTREE_STATIC_DELTA_OP_BSPATCH = 'B' OSTREE_STATIC_DELTA_OP_BSPATCH = 'B'
} OstreeStaticDeltaOpCode; } OstreeStaticDeltaOpCode;
#define OSTREE_STATIC_DELTA_N_OPS 7
gboolean
_ostree_static_delta_part_open (GInputStream *part_in,
GBytes *inline_part_bytes,
OstreeStaticDeltaOpenFlags flags,
const char *expected_checksum,
GVariant **out_part,
GCancellable *cancellable,
GError **error);
gboolean _ostree_static_delta_dump (OstreeRepo *repo,
const char *delta_id,
GCancellable *cancellable,
GError **error);
typedef struct {
guint n_ops_executed[OSTREE_STATIC_DELTA_N_OPS];
} OstreeDeltaExecuteStats;
gboolean _ostree_static_delta_part_execute (OstreeRepo *repo,
GVariant *header,
GVariant *part_payload,
gboolean trusted,
gboolean stats_only,
OstreeDeltaExecuteStats *stats,
GCancellable *cancellable,
GError **error);
void _ostree_static_delta_part_execute_async (OstreeRepo *repo,
GVariant *header,
GVariant *part_payload,
gboolean trusted,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean _ostree_static_delta_part_execute_finish (OstreeRepo *repo,
GAsyncResult *result,
GError **error);
gboolean gboolean
_ostree_static_delta_parse_checksum_array (GVariant *array, _ostree_static_delta_parse_checksum_array (GVariant *array,
@ -177,4 +190,43 @@ _ostree_delta_compute_similar_objects (OstreeRepo *repo,
GCancellable *cancellable, GCancellable *cancellable,
GError **error); GError **error);
gboolean
_ostree_repo_static_delta_dump (OstreeRepo *repo,
const char *delta_id,
GCancellable *cancellable,
GError **error);
/* Used for static deltas which due to a historical mistake are
* inconsistent endian.
*
* https://bugzilla.gnome.org/show_bug.cgi?id=762515
*/
static inline guint32
maybe_swap_endian_u32 (gboolean swap,
guint32 v)
{
if (!swap)
return v;
return GUINT32_SWAP_LE_BE (v);
}
static inline guint64
maybe_swap_endian_u64 (gboolean swap,
guint64 v)
{
if (!swap)
return v;
return GUINT64_SWAP_LE_BE (v);
}
typedef enum {
OSTREE_DELTA_ENDIAN_BIG,
OSTREE_DELTA_ENDIAN_LITTLE,
OSTREE_DELTA_ENDIAN_INVALID
} OstreeDeltaEndianness;
OstreeDeltaEndianness _ostree_delta_get_endianness (GVariant *superblock, gboolean *out_was_heuristic);
gboolean _ostree_delta_needs_byteswap (GVariant *superblock);
G_END_DECLS G_END_DECLS

View File

@ -40,6 +40,7 @@ G_STATIC_ASSERT (sizeof (guint) >= sizeof (guint32));
typedef struct { typedef struct {
gboolean trusted; gboolean trusted;
gboolean stats_only;
OstreeRepo *repo; OstreeRepo *repo;
guint checksum_index; guint checksum_index;
const guint8 *checksums; const guint8 *checksums;
@ -149,43 +150,39 @@ open_output_target (StaticDeltaExecutionState *state,
return ret; return ret;
} }
gboolean static guint
_ostree_static_delta_part_validate (OstreeRepo *repo, delta_opcode_index (OstreeStaticDeltaOpCode op)
GInputStream *in,
guint part_offset,
const char *expected_checksum,
GCancellable *cancellable,
GError **error)
{ {
gboolean ret = FALSE; switch (op)
g_autofree guchar *actual_checksum_bytes = NULL;
g_autofree char *actual_checksum = NULL;
if (!ot_gio_checksum_stream (in, &actual_checksum_bytes,
cancellable, error))
goto out;
actual_checksum = ostree_checksum_from_bytes (actual_checksum_bytes);
if (strcmp (actual_checksum, expected_checksum) != 0)
{ {
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, case OSTREE_STATIC_DELTA_OP_OPEN_SPLICE_AND_CLOSE:
"Checksum mismatch in static delta part %u; expected=%s actual=%s", return 0;
part_offset, expected_checksum, actual_checksum); case OSTREE_STATIC_DELTA_OP_OPEN:
goto out; return 1;
case OSTREE_STATIC_DELTA_OP_WRITE:
return 2;
case OSTREE_STATIC_DELTA_OP_SET_READ_SOURCE:
return 3;
case OSTREE_STATIC_DELTA_OP_UNSET_READ_SOURCE:
return 4;
case OSTREE_STATIC_DELTA_OP_CLOSE:
return 5;
case OSTREE_STATIC_DELTA_OP_BSPATCH:
return 6;
default:
g_assert_not_reached ();
} }
ret = TRUE;
out:
return ret;
} }
gboolean gboolean
_ostree_static_delta_part_execute_raw (OstreeRepo *repo, _ostree_static_delta_part_execute (OstreeRepo *repo,
GVariant *objects, GVariant *objects,
GVariant *part, GVariant *part,
gboolean trusted, gboolean trusted,
GCancellable *cancellable, gboolean stats_only,
GError **error) OstreeDeltaExecuteStats *stats,
GCancellable *cancellable,
GError **error)
{ {
gboolean ret = FALSE; gboolean ret = FALSE;
guint8 *checksums_data; guint8 *checksums_data;
@ -201,6 +198,7 @@ _ostree_static_delta_part_execute_raw (OstreeRepo *repo,
state->repo = repo; state->repo = repo;
state->async_error = error; state->async_error = error;
state->trusted = trusted; state->trusted = trusted;
state->stats_only = stats_only;
if (!_ostree_static_delta_parse_checksum_array (objects, if (!_ostree_static_delta_parse_checksum_array (objects,
&checksums_data, &checksums_data,
@ -270,6 +268,8 @@ _ostree_static_delta_part_execute_raw (OstreeRepo *repo,
} }
n_executed++; n_executed++;
if (stats)
stats->n_ops_executed[delta_opcode_index(opcode)]++;
} }
if (state->caught_error) if (state->caught_error)
@ -280,99 +280,10 @@ _ostree_static_delta_part_execute_raw (OstreeRepo *repo,
return ret; return ret;
} }
static gboolean
decompress_all (GConverter *converter,
GBytes *data,
GBytes **out_uncompressed,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
g_autoptr(GMemoryInputStream) memin = (GMemoryInputStream*)g_memory_input_stream_new_from_bytes (data);
g_autoptr(GMemoryOutputStream) memout = (GMemoryOutputStream*)g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
g_autoptr(GInputStream) convin = g_converter_input_stream_new ((GInputStream*)memin, converter);
{
gssize n_bytes_written = g_output_stream_splice ((GOutputStream*)memout, convin,
G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
cancellable, error);
if (n_bytes_written < 0)
goto out;
}
ret = TRUE;
*out_uncompressed = g_memory_output_stream_steal_as_bytes (memout);
out:
return ret;
}
gboolean
_ostree_static_delta_part_execute (OstreeRepo *repo,
GVariant *header,
GBytes *part_bytes,
gboolean trusted,
GCancellable *cancellable,
GError **error)
{
gboolean ret = FALSE;
gsize partlen;
const guint8*partdata;
g_autoptr(GBytes) part_payload_bytes = NULL;
g_autoptr(GBytes) payload_data = NULL;
g_autoptr(GVariant) payload = NULL;
guint8 comptype;
partdata = g_bytes_get_data (part_bytes, &partlen);
if (partlen < 1)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Corrupted 0 length delta part");
goto out;
}
/* First byte is compression type */
comptype = partdata[0];
/* Then the rest may be compressed or uncompressed */
part_payload_bytes = g_bytes_new_from_bytes (part_bytes, 1, partlen - 1);
switch (comptype)
{
case 0:
/* No compression */
payload_data = g_bytes_ref (part_payload_bytes);
break;
case 'x':
{
g_autoptr(GConverter) decomp =
(GConverter*) _ostree_lzma_decompressor_new ();
if (!decompress_all (decomp, part_payload_bytes, &payload_data,
cancellable, error))
goto out;
}
break;
default:
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"Invalid compression type '%u'", comptype);
goto out;
}
payload = g_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT_V0),
payload_data, FALSE);
if (!_ostree_static_delta_part_execute_raw (repo, header, payload, trusted,
cancellable, error))
goto out;
ret = TRUE;
out:
return ret;
}
typedef struct { typedef struct {
OstreeRepo *repo; OstreeRepo *repo;
GVariant *header; GVariant *header;
GBytes *partdata; GVariant *part;
GCancellable *cancellable; GCancellable *cancellable;
GSimpleAsyncResult *result; GSimpleAsyncResult *result;
gboolean trusted; gboolean trusted;
@ -385,7 +296,7 @@ static_delta_part_execute_async_data_free (gpointer user_data)
g_clear_object (&data->repo); g_clear_object (&data->repo);
g_variant_unref (data->header); g_variant_unref (data->header);
g_bytes_unref (data->partdata); g_variant_unref (data->part);
g_clear_object (&data->cancellable); g_clear_object (&data->cancellable);
g_free (data); g_free (data);
} }
@ -401,8 +312,9 @@ static_delta_part_execute_thread (GSimpleAsyncResult *res,
data = g_simple_async_result_get_op_res_gpointer (res); data = g_simple_async_result_get_op_res_gpointer (res);
if (!_ostree_static_delta_part_execute (data->repo, if (!_ostree_static_delta_part_execute (data->repo,
data->header, data->header,
data->partdata, data->part,
data->trusted, data->trusted,
FALSE, NULL,
cancellable, &error)) cancellable, &error))
g_simple_async_result_take_error (res, error); g_simple_async_result_take_error (res, error);
} }
@ -410,7 +322,7 @@ static_delta_part_execute_thread (GSimpleAsyncResult *res,
void void
_ostree_static_delta_part_execute_async (OstreeRepo *repo, _ostree_static_delta_part_execute_async (OstreeRepo *repo,
GVariant *header, GVariant *header,
GBytes *partdata, GVariant *part,
gboolean trusted, gboolean trusted,
GCancellable *cancellable, GCancellable *cancellable,
GAsyncReadyCallback callback, GAsyncReadyCallback callback,
@ -421,7 +333,7 @@ _ostree_static_delta_part_execute_async (OstreeRepo *repo,
asyncdata = g_new0 (StaticDeltaPartExecuteAsyncData, 1); asyncdata = g_new0 (StaticDeltaPartExecuteAsyncData, 1);
asyncdata->repo = g_object_ref (repo); asyncdata->repo = g_object_ref (repo);
asyncdata->header = g_variant_ref (header); asyncdata->header = g_variant_ref (header);
asyncdata->partdata = g_bytes_ref (partdata); asyncdata->part = g_variant_ref (part);
asyncdata->trusted = trusted; asyncdata->trusted = trusted;
asyncdata->cancellable = cancellable ? g_object_ref (cancellable) : NULL; asyncdata->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
@ -538,6 +450,12 @@ dispatch_bspatch (OstreeRepo *repo,
if (!read_varuint64 (state, &length, error)) if (!read_varuint64 (state, &length, error))
goto out; goto out;
if (state->stats_only)
{
ret = TRUE;
goto out;
}
if (!state->have_obj) if (!state->have_obj)
{ {
input_mfile = g_mapped_file_new_from_fd (state->read_source_fd, FALSE, error); input_mfile = g_mapped_file_new_from_fd (state->read_source_fd, FALSE, error);
@ -596,6 +514,12 @@ dispatch_open_splice_and_close (OstreeRepo *repo,
goto out; goto out;
if (!validate_ofs (state, offset, length, error)) if (!validate_ofs (state, offset, length, error))
goto out; goto out;
if (state->stats_only)
{
ret = TRUE;
goto out;
}
metadata = g_variant_new_from_data (ostree_metadata_variant_type (state->output_objtype), metadata = g_variant_new_from_data (ostree_metadata_variant_type (state->output_objtype),
state->payload_data + offset, length, TRUE, NULL, NULL); state->payload_data + offset, length, TRUE, NULL, NULL);
@ -639,6 +563,12 @@ dispatch_open_splice_and_close (OstreeRepo *repo,
if (!validate_ofs (state, content_offset, state->content_size, error)) if (!validate_ofs (state, content_offset, state->content_size, error))
goto out; goto out;
if (state->stats_only)
{
ret = TRUE;
goto out;
}
/* Fast path for regular files to bare repositories */ /* Fast path for regular files to bare repositories */
if (S_ISREG (state->mode) && if (S_ISREG (state->mode) &&
(repo->mode == OSTREE_REPO_MODE_BARE || (repo->mode == OSTREE_REPO_MODE_BARE ||
@ -730,6 +660,8 @@ dispatch_open_splice_and_close (OstreeRepo *repo,
ret = TRUE; ret = TRUE;
out: out:
if (state->stats_only)
(void) dispatch_close (repo, state, cancellable, NULL);
if (!ret) if (!ret)
g_prefix_error (error, "opcode open-splice-and-close: "); g_prefix_error (error, "opcode open-splice-and-close: ");
return ret; return ret;
@ -745,8 +677,11 @@ dispatch_open (OstreeRepo *repo,
g_assert (state->output_target == NULL); g_assert (state->output_target == NULL);
/* FIXME - lift this restriction */ /* FIXME - lift this restriction */
g_assert (repo->mode == OSTREE_REPO_MODE_BARE || if (!state->stats_only)
repo->mode == OSTREE_REPO_MODE_BARE_USER); {
g_assert (repo->mode == OSTREE_REPO_MODE_BARE ||
repo->mode == OSTREE_REPO_MODE_BARE_USER);
}
if (!open_output_target (state, cancellable, error)) if (!open_output_target (state, cancellable, error))
goto out; goto out;
@ -757,6 +692,12 @@ dispatch_open (OstreeRepo *repo,
if (!read_varuint64 (state, &state->content_size, error)) if (!read_varuint64 (state, &state->content_size, error))
goto out; goto out;
if (state->stats_only)
{
ret = TRUE;
goto out;
}
if (state->trusted) if (state->trusted)
{ {
if (!_ostree_repo_open_trusted_content_bare (repo, state->checksum, if (!_ostree_repo_open_trusted_content_bare (repo, state->checksum,
@ -801,6 +742,12 @@ dispatch_write (OstreeRepo *repo,
if (!read_varuint64 (state, &content_offset, error)) if (!read_varuint64 (state, &content_offset, error))
goto out; goto out;
if (state->stats_only)
{
ret = TRUE;
goto out;
}
if (!state->have_obj) if (!state->have_obj)
{ {
if (state->read_source_fd != -1) if (state->read_source_fd != -1)
@ -881,6 +828,12 @@ dispatch_set_read_source (OstreeRepo *repo,
if (!validate_ofs (state, source_offset, 32, error)) if (!validate_ofs (state, source_offset, 32, error))
goto out; goto out;
if (state->stats_only)
{
ret = TRUE;
goto out;
}
g_free (state->read_source_object); g_free (state->read_source_object);
state->read_source_object = ostree_checksum_from_bytes (state->payload_data + source_offset); state->read_source_object = ostree_checksum_from_bytes (state->payload_data + source_offset);
@ -903,6 +856,12 @@ dispatch_unset_read_source (OstreeRepo *repo,
{ {
gboolean ret = FALSE; gboolean ret = FALSE;
if (state->stats_only)
{
ret = TRUE;
goto out;
}
if (state->read_source_fd) if (state->read_source_fd)
{ {
(void) close (state->read_source_fd); (void) close (state->read_source_fd);
@ -912,7 +871,7 @@ dispatch_unset_read_source (OstreeRepo *repo,
g_clear_pointer (&state->read_source_object, g_free); g_clear_pointer (&state->read_source_object, g_free);
ret = TRUE; ret = TRUE;
/* out: */ out:
if (!ret) if (!ret)
g_prefix_error (error, "opcode unset-read-source: "); g_prefix_error (error, "opcode unset-read-source: ");
return ret; return ret;

View File

@ -518,8 +518,6 @@ ostree_repo_finalize (GObject *object)
g_clear_object (&self->tmp_dir); g_clear_object (&self->tmp_dir);
if (self->tmp_dir_fd) if (self->tmp_dir_fd)
(void) close (self->tmp_dir_fd); (void) close (self->tmp_dir_fd);
g_clear_object (&self->local_heads_dir);
g_clear_object (&self->remote_heads_dir);
g_clear_object (&self->objects_dir); g_clear_object (&self->objects_dir);
if (self->objects_dir_fd != -1) if (self->objects_dir_fd != -1)
(void) close (self->objects_dir_fd); (void) close (self->objects_dir_fd);
@ -605,8 +603,6 @@ ostree_repo_constructed (GObject *object)
g_assert (self->repodir != NULL); g_assert (self->repodir != NULL);
self->tmp_dir = g_file_resolve_relative_path (self->repodir, "tmp"); self->tmp_dir = g_file_resolve_relative_path (self->repodir, "tmp");
self->local_heads_dir = g_file_resolve_relative_path (self->repodir, "refs/heads");
self->remote_heads_dir = g_file_resolve_relative_path (self->repodir, "refs/remotes");
self->objects_dir = g_file_get_child (self->repodir, "objects"); self->objects_dir = g_file_get_child (self->repodir, "objects");
self->deltas_dir = g_file_get_child (self->repodir, "deltas"); self->deltas_dir = g_file_get_child (self->repodir, "deltas");
@ -2384,6 +2380,19 @@ ostree_repo_set_disable_fsync (OstreeRepo *self,
self->disable_fsync = disable_fsync; self->disable_fsync = disable_fsync;
} }
/**
* ostree_repo_get_disable_fsync:
* @self: An #OstreeRepo
*
* For more information see ostree_repo_set_disable_fsync().
*
* Returns: Whether or not fsync() is enabled for this repo.
*/
gboolean
ostree_repo_get_disable_fsync (OstreeRepo *self)
{
return self->disable_fsync;
}
/* Replace the contents of a file, honoring the repository's fsync /* Replace the contents of a file, honoring the repository's fsync
* policy. * policy.
@ -3775,6 +3784,7 @@ ostree_repo_pull_one_dir (OstreeRepo *self,
* * flags (i): An instance of #OstreeRepoPullFlags * * flags (i): An instance of #OstreeRepoPullFlags
* * refs: (as): Array of string refs * * refs: (as): Array of string refs
* * depth: (i): How far in the history to traverse; default is 0, -1 means infinite * * depth: (i): How far in the history to traverse; default is 0, -1 means infinite
* * override-commit-ids: (as): Array of specific commit IDs to fetch for refs
*/ */
gboolean gboolean
ostree_repo_pull_with_options (OstreeRepo *self, ostree_repo_pull_with_options (OstreeRepo *self,
@ -4594,8 +4604,8 @@ ostree_repo_regenerate_summary (OstreeRepo *self,
gs_free char *from = NULL; gs_free char *from = NULL;
gs_free char *to = NULL; gs_free char *to = NULL;
gs_free guchar *csum = NULL; gs_free guchar *csum = NULL;
gs_free char *superblock; gs_free char *superblock = NULL;
gs_fd_close int superblock_file_fd; gs_fd_close int superblock_file_fd = -1;
g_autoptr(GInputStream) in_stream = NULL; g_autoptr(GInputStream) in_stream = NULL;
_ostree_parse_delta_name (delta_names->pdata[i], &from, &to); _ostree_parse_delta_name (delta_names->pdata[i], &from, &to);

View File

@ -56,6 +56,8 @@ gboolean ostree_repo_open (OstreeRepo *self,
void ostree_repo_set_disable_fsync (OstreeRepo *self, void ostree_repo_set_disable_fsync (OstreeRepo *self,
gboolean disable_fsync); gboolean disable_fsync);
gboolean ostree_repo_get_disable_fsync (OstreeRepo *self);
gboolean ostree_repo_is_system (OstreeRepo *repo); gboolean ostree_repo_is_system (OstreeRepo *repo);
gboolean ostree_repo_is_writable (OstreeRepo *self, gboolean ostree_repo_is_writable (OstreeRepo *self,
@ -439,6 +441,7 @@ gboolean ostree_repo_write_dfd_to_mtree (OstreeRepo *self,
GCancellable *cancellable, GCancellable *cancellable,
GError **error); GError **error);
gboolean ostree_repo_write_archive_to_mtree (OstreeRepo *self, gboolean ostree_repo_write_archive_to_mtree (OstreeRepo *self,
GFile *archive, GFile *archive,
OstreeMutableTree *mtree, OstreeMutableTree *mtree,
@ -447,6 +450,53 @@ gboolean ostree_repo_write_archive_to_mtree (OstreeRepo *
GCancellable *cancellable, GCancellable *cancellable,
GError **error); GError **error);
/**
* OstreeRepoImportArchiveOptions:
*
* An extensible options structure controlling archive import. Ensure that
* you have entirely zeroed the structure, then set just the desired
* options. This is used by ostree_repo_import_archive_to_mtree().
*/
typedef struct {
guint ignore_unsupported_content : 1;
guint autocreate_parents : 1;
guint reserved : 30;
guint unused_uint[8];
gpointer unused_ptrs[8];
} OstreeRepoImportArchiveOptions;
gboolean ostree_repo_import_archive_to_mtree (OstreeRepo *self,
OstreeRepoImportArchiveOptions *opts,
void *archive, /* Really struct archive * */
OstreeMutableTree *mtree,
OstreeRepoCommitModifier *modifier,
GCancellable *cancellable,
GError **error);
/**
* OstreeRepoExportArchiveOptions:
*
* An extensible options structure controlling archive creation. Ensure that
* you have entirely zeroed the structure, then set just the desired
* options. This is used by ostree_repo_export_tree_to_archive().
*/
typedef struct {
guint disable_xattrs : 1;
guint reserved : 31;
guint64 timestamp_secs;
guint unused_uint[8];
gpointer unused_ptrs[8];
} OstreeRepoExportArchiveOptions;
gboolean ostree_repo_export_tree_to_archive (OstreeRepo *self,
OstreeRepoExportArchiveOptions *opts,
OstreeRepoFile *root,
void *archive, /* Really struct archive * */
GCancellable *cancellable,
GError **error);
gboolean ostree_repo_write_mtree (OstreeRepo *self, gboolean ostree_repo_write_mtree (OstreeRepo *self,
OstreeMutableTree *mtree, OstreeMutableTree *mtree,
GFile **out_file, GFile **out_file,
@ -530,7 +580,9 @@ typedef struct {
OstreeRepoCheckoutOverwriteMode overwrite_mode; OstreeRepoCheckoutOverwriteMode overwrite_mode;
guint enable_uncompressed_cache : 1; guint enable_uncompressed_cache : 1;
guint unused : 31; guint disable_fsync : 1;
guint process_whiteouts : 1;
guint reserved : 29;
const char *subpath; const char *subpath;

View File

@ -528,6 +528,11 @@ checkout_deployment_tree (OstreeSysroot *sysroot,
glnx_fd_close int osdeploy_dfd = -1; glnx_fd_close int osdeploy_dfd = -1;
int ret_fd; int ret_fd;
/* We end up using syncfs for the entire filesystem, so turn off
* OstreeRepo level fsync.
*/
checkout_opts.disable_fsync = TRUE;
osdeploy_path = g_strconcat ("ostree/deploy/", ostree_deployment_get_osname (deployment), "/deploy", NULL); osdeploy_path = g_strconcat ("ostree/deploy/", ostree_deployment_get_osname (deployment), "/deploy", NULL);
checkout_target_name = g_strdup_printf ("%s.%d", csum, ostree_deployment_get_deployserial (deployment)); checkout_target_name = g_strdup_printf ("%s.%d", csum, ostree_deployment_get_deployserial (deployment));
@ -2030,9 +2035,12 @@ ostree_sysroot_deploy_tree (OstreeSysroot *self,
cancellable, error)) cancellable, error))
goto out; goto out;
if (!ostree_sysroot_deployment_set_mutable (self, new_deployment, FALSE, if (!(self->debug_flags & OSTREE_SYSROOT_DEBUG_MUTABLE_DEPLOYMENTS))
cancellable, error)) {
goto out; if (!ostree_sysroot_deployment_set_mutable (self, new_deployment, FALSE,
cancellable, error))
goto out;
}
{ ostree_cleanup_sepolicy_fscreatecon gpointer dummy = NULL; { ostree_cleanup_sepolicy_fscreatecon gpointer dummy = NULL;

View File

@ -27,6 +27,13 @@
G_BEGIN_DECLS G_BEGIN_DECLS
typedef enum {
/* Don't flag deployments as immutable. */
OSTREE_SYSROOT_DEBUG_MUTABLE_DEPLOYMENTS = 1 << 0
} OstreeSysrootDebugFlags;
struct OstreeSysroot { struct OstreeSysroot {
GObject parent; GObject parent;
@ -46,6 +53,7 @@ struct OstreeSysroot {
/* Only access through ostree_sysroot_get_repo() */ /* Only access through ostree_sysroot_get_repo() */
OstreeRepo *repo; OstreeRepo *repo;
OstreeSysrootDebugFlags debug_flags;
}; };
#define OSTREE_SYSROOT_LOCKFILE "ostree/lock" #define OSTREE_SYSROOT_LOCKFILE "ostree/lock"

Some files were not shown because too many files have changed in this diff Show More