diff --git a/Makefile-tests.am b/Makefile-tests.am index d1a8803d..13c5d60d 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -37,6 +37,7 @@ testfiles = test-basic \ test-admin-deploy-switch \ test-admin-deploy-etcmerge-cornercases \ test-admin-deploy-uboot \ + test-admin-upgrade-not-backwards \ test-setuid \ test-delta \ test-xattrs \ diff --git a/src/libostree/ostree-core.c b/src/libostree/ostree-core.c index 9b7a2379..b8fa114a 100644 --- a/src/libostree/ostree-core.c +++ b/src/libostree/ostree-core.c @@ -1755,3 +1755,11 @@ ostree_commit_get_parent (GVariant *commit_variant) return NULL; return ostree_checksum_from_bytes_v (bytes); } + +guint64 +ostree_commit_get_timestamp (GVariant *commit_variant) +{ + guint64 ret; + g_variant_get_child (commit_variant, 5, "t", &ret); + return GUINT64_FROM_BE (ret); +} diff --git a/src/libostree/ostree-core.h b/src/libostree/ostree-core.h index bd8e68fc..de1698d2 100644 --- a/src/libostree/ostree-core.h +++ b/src/libostree/ostree-core.h @@ -250,5 +250,6 @@ gboolean ostree_validate_structureof_dirmeta (GVariant *dirmeta, GError **error); gchar * ostree_commit_get_parent (GVariant *commit_variant); +guint64 ostree_commit_get_timestamp (GVariant *commit_variant); G_END_DECLS diff --git a/src/ostree/ot-admin-builtin-upgrade.c b/src/ostree/ot-admin-builtin-upgrade.c index 1b86cddc..9fcae678 100644 --- a/src/ostree/ot-admin-builtin-upgrade.c +++ b/src/ostree/ot-admin-builtin-upgrade.c @@ -34,11 +34,13 @@ #include static gboolean opt_reboot; +static gboolean opt_allow_downgrade; static char *opt_osname; static GOptionEntry options[] = { { "os", 0, 0, G_OPTION_ARG_STRING, &opt_osname, "Specify operating system root to use", NULL }, { "reboot", 'r', 0, G_OPTION_ARG_NONE, &opt_reboot, "Reboot after a successful upgrade", NULL }, + { "allow-downgrade", 0, 0, G_OPTION_ARG_NONE, &opt_allow_downgrade, "Permit deployment of chronologically older trees", NULL }, { NULL } }; @@ -109,6 +111,44 @@ ot_admin_builtin_upgrade (int argc, char **argv, OstreeSysroot *sysroot, GCancel else { gs_unref_object GFile *real_sysroot = g_file_new_for_path ("/"); + + if (!opt_allow_downgrade) + { + const char *old_revision = ostree_deployment_get_csum (merge_deployment); + gs_unref_variant GVariant *old_commit = NULL; + gs_unref_variant GVariant *new_commit = NULL; + + if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, + old_revision, + &old_commit, + error)) + goto out; + + if (!ostree_repo_load_variant (repo, OSTREE_OBJECT_TYPE_COMMIT, + new_revision, &new_commit, + error)) + goto out; + + if (ostree_commit_get_timestamp (old_commit) > ostree_commit_get_timestamp (new_commit)) + { + GDateTime *old_ts = g_date_time_new_from_unix_utc (ostree_commit_get_timestamp (old_commit)); + GDateTime *new_ts = g_date_time_new_from_unix_utc (ostree_commit_get_timestamp (new_commit)); + gs_free char *old_ts_str = NULL; + gs_free char *new_ts_str = NULL; + + g_assert (old_ts); + g_assert (new_ts); + old_ts_str = g_date_time_format (old_ts, "%c"); + new_ts_str = g_date_time_format (new_ts, "%c"); + g_date_time_unref (old_ts); + g_date_time_unref (new_ts); + + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Upgrade target revision '%s' with timestamp '%s' is chronologically older than current revision '%s' with timestamp '%s'; use --allow-downgrade to permit", + new_revision, new_ts_str, old_revision, old_ts_str); + goto out; + } + } /* Here we perform cleanup of any leftover data from previous * partial failures. This avoids having to call gs_shutil_rm_rf() diff --git a/tests/libtest.sh b/tests/libtest.sh index da85c1c8..7d726a5c 100644 --- a/tests/libtest.sh +++ b/tests/libtest.sh @@ -223,6 +223,8 @@ EOF ostree --repo=${test_tmpdir}/testos-repo commit -b testos/buildmaster/x86_64-runtime -s "Build" + # Ensure these commits have distinct second timestamps + sleep 2 echo "a new executable" > usr/bin/sh ostree --repo=${test_tmpdir}/testos-repo commit -b testos/buildmaster/x86_64-runtime -s "Build" @@ -248,6 +250,15 @@ EOF setup_os_boot_uboot ;; esac + + cd ${test_tmpdir} + mkdir ${test_tmpdir}/httpd + cd httpd + ln -s ${test_tmpdir} ostree + ostree trivial-httpd --daemonize -p ${test_tmpdir}/httpd-port $args + port=$(cat ${test_tmpdir}/httpd-port) + echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address + cd ${oldpwd} } os_repository_new_commit () diff --git a/tests/test-admin-upgrade-not-backwards.sh b/tests/test-admin-upgrade-not-backwards.sh new file mode 100644 index 00000000..6ea988b8 --- /dev/null +++ b/tests/test-admin-upgrade-not-backwards.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# +# Copyright (C) 2014 Colin Walters +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +set -e + +. $(dirname $0)/libtest.sh + +echo "1..1" + +setup_os_repository "archive-z2" "syslinux" + +echo "ok setup" + +echo "1..2" + +cd ${test_tmpdir} +ostree --repo=sysroot/ostree/repo remote add --set=gpg-verify=false testos $(cat httpd-address)/ostree/testos-repo +ostree --repo=sysroot/ostree/repo pull testos testos/buildmaster/x86_64-runtime +rev=$(ostree --repo=sysroot/ostree/repo rev-parse testos/buildmaster/x86_64-runtime) +export rev +echo "rev=${rev}" +# This initial deployment gets kicked off with some kernel arguments +ostree admin --sysroot=sysroot deploy --karg=root=LABEL=MOO --karg=quiet --os=testos testos:testos/buildmaster/x86_64-runtime +assert_has_dir sysroot/boot/ostree/testos-${bootcsum} + +# This should be a no-op +ostree admin --sysroot=sysroot upgrade --os=testos + +# Now reset to an older revision +ostree --repo=${test_tmpdir}/testos-repo reset testos/buildmaster/x86_64-runtime{,^} + +if ostree admin --sysroot=sysroot upgrade --os=testos 2>upgrade-err.txt; then + assert_not_reached 'upgrade unexpectedly succeeded' +fi +assert_file_has_content upgrade-err.txt 'chronologically older' + +echo 'ok upgrade will not go backwards' + +ostree admin --sysroot=sysroot upgrade --os=testos --allow-downgrade + +echo 'ok upgrade backwards'