commit
56b6994e39
121
CONTRIBUTING.md
121
CONTRIBUTING.md
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
docs/CONTRIBUTING.md
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
44
README.md
44
README.md
|
|
@ -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
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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"/>
|
||||||
|
|
@ -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
|
||||||
56
configure.ac
56
configure.ac
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
155
doc/overview.xml
155
doc/overview.xml
|
|
@ -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>
|
|
||||||
127
doc/repo.xml
127
doc/repo.xml
|
|
@ -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>
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
../README.md
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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 \
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
@ -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'
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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 *
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Reference in New Issue