diff --git a/Makefile-tests.am b/Makefile-tests.am index c6973339..bf584240 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -45,6 +45,7 @@ testfiles = test-basic \ test-admin-deploy-uboot \ test-admin-instutil-set-kargs \ test-admin-upgrade-not-backwards \ + test-admin-locking \ test-repo-checkout-subpath \ test-reset-nonlinear \ test-setuid \ diff --git a/doc/ostree-sections.txt b/doc/ostree-sections.txt index d26d37a4..9e2ef6c3 100644 --- a/doc/ostree-sections.txt +++ b/doc/ostree-sections.txt @@ -365,6 +365,8 @@ ostree_sysroot_new ostree_sysroot_new_default ostree_sysroot_get_path ostree_sysroot_load +ostree_sysroot_lock +ostree_sysroot_unlock ostree_sysroot_get_fd ostree_sysroot_ensure_initialized ostree_sysroot_get_bootversion diff --git a/libglnx b/libglnx index dfe77be2..2558e0ea 160000 --- a/libglnx +++ b/libglnx @@ -1 +1 @@ -Subproject commit dfe77be2d558373c4b01189e2048d79be9c9c453 +Subproject commit 2558e0ea35712aed27234fd1ad4a5bac4fae3817 diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h index 69310c3f..fa7b0866 100644 --- a/src/libostree/ostree-sysroot-private.h +++ b/src/libostree/ostree-sysroot-private.h @@ -20,6 +20,7 @@ #pragma once +#include "libglnx.h" #include "ostree.h" #include "ostree-kernel-args.h" #include "ostree-bootloader.h" @@ -31,6 +32,7 @@ struct OstreeSysroot { GFile *path; int sysroot_fd; + GLnxLockFile lock; gboolean loaded; @@ -43,8 +45,11 @@ struct OstreeSysroot { /* Only access through ostree_sysroot_get_repo() */ OstreeRepo *repo; + }; +#define OSTREE_SYSROOT_LOCKFILE "ostree/lock" + gboolean _ostree_sysroot_read_boot_loader_configs (OstreeSysroot *self, int bootversion, diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c index 62558037..8f2b2496 100644 --- a/src/libostree/ostree-sysroot.c +++ b/src/libostree/ostree-sysroot.c @@ -44,6 +44,10 @@ find_booted_deployment (OstreeSysroot *self, * which in particular should contain a toplevel /ostree directory. * Inside this directory is an #OstreeRepo in /ostree/repo, plus a set * of deployments in /ostree/deploy. + * + * This class is not by default safe against concurrent use by threads + * or external processes. You can use ostree_sysroot_lock() to + * perform locking externally. */ typedef struct { GObjectClass parent_class; @@ -66,6 +70,8 @@ ostree_sysroot_finalize (GObject *object) g_clear_object (&self->sepolicy); g_clear_object (&self->repo); + glnx_release_lock_file (&self->lock); + if (self->sysroot_fd != -1) (void) close (self->sysroot_fd); @@ -148,6 +154,7 @@ static void ostree_sysroot_init (OstreeSysroot *self) { self->sysroot_fd = -1; + self->lock = (GLnxLockFile)GLNX_LOCK_FILE_INIT; } /** @@ -1142,6 +1149,43 @@ ostree_sysroot_origin_new_from_refspec (OstreeSysroot *sysroot, return ret; } +/** + * ostree_sysroot_lock: + * @self: Self + * @error: Error + * + * Acquire an exclusive multi-process write lock for @self. This call + * blocks until the lock has been acquired. The lock is not + * reentrant. + * + * Release the lock with ostree_sysroot_unlock(). The lock will also + * be released if @self is deallocated. + */ +gboolean +ostree_sysroot_lock (OstreeSysroot *self, + GError **error) +{ + if (!ensure_sysroot_fd (self, error)) + return FALSE; + return glnx_make_lock_file (self->sysroot_fd, OSTREE_SYSROOT_LOCKFILE, + LOCK_EX, &self->lock, error); +} + +/** + * ostree_sysroot_unlock: + * @self: Self + * @error: Error + * + * Clear the lock previously acquired with ostree_sysroot_lock(). It + * is safe to call this function if the lock has not been previously + * acquired. + */ +void +ostree_sysroot_unlock (OstreeSysroot *self) +{ + glnx_release_lock_file (&self->lock); +} + /** * ostree_sysroot_simple_write_deployment: * @sysroot: Sysroot diff --git a/src/libostree/ostree-sysroot.h b/src/libostree/ostree-sysroot.h index fe6441fb..e7f6e482 100644 --- a/src/libostree/ostree-sysroot.h +++ b/src/libostree/ostree-sysroot.h @@ -62,6 +62,9 @@ char *ostree_sysroot_get_deployment_dirpath (OstreeSysroot *self, GFile * ostree_sysroot_get_deployment_origin_path (GFile *deployment_path); +gboolean ostree_sysroot_lock (OstreeSysroot *self, GError **error); +void ostree_sysroot_unlock (OstreeSysroot *self); + gboolean ostree_sysroot_cleanup (OstreeSysroot *self, GCancellable *cancellable, GError **error); diff --git a/src/ostree/ot-admin-builtin-deploy.c b/src/ostree/ot-admin-builtin-deploy.c index 091a7eac..1872894b 100644 --- a/src/ostree/ot-admin-builtin-deploy.c +++ b/src/ostree/ot-admin-builtin-deploy.c @@ -79,6 +79,9 @@ ot_admin_builtin_deploy (int argc, char **argv, GCancellable *cancellable, GErro refspec = argv[1]; + if (!ostree_sysroot_lock (sysroot, error)) + goto out; + if (!ostree_sysroot_load (sysroot, cancellable, error)) goto out; @@ -172,6 +175,8 @@ ot_admin_builtin_deploy (int argc, char **argv, GCancellable *cancellable, GErro ret = TRUE; out: + if (sysroot) + ostree_sysroot_unlock (sysroot); if (origin) g_key_file_unref (origin); if (context) diff --git a/tests/test-admin-locking.sh b/tests/test-admin-locking.sh new file mode 100644 index 00000000..b6cd7bd9 --- /dev/null +++ b/tests/test-admin-locking.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# +# Copyright (C) 2015 Colin Walters +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +set -e + +. $(dirname $0)/libtest.sh + +echo "1..1" + +setup_os_repository "archive-z2" "syslinux" + +echo "ok setup" + +echo "1..1" + +cd ${test_tmpdir} +${CMD_PREFIX} ostree --repo=sysroot/ostree/repo remote add --set=gpg-verify=false testos $(cat httpd-address)/ostree/testos-repo +${CMD_PREFIX} ostree --repo=sysroot/ostree/repo pull testos testos/buildmaster/x86_64-runtime +rev=$(${CMD_PREFIX} 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 +${CMD_PREFIX} 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} + +count=$(($(getconf _NPROCESSORS_ONLN) * 2)) +seq "${count}" | parallel --no-notice -n0 ${CMD_PREFIX} ostree admin --sysroot=sysroot deploy --retain --os=testos testos:testos/buildmaster/x86_64-runtime + +${CMD_PREFIX} ostree admin --sysroot=sysroot status > status.txt +grep "testos ${rev}" status.txt | wc -l > status-matches.txt +assert_file_has_content status-matches.txt $((${count} + 1)) + +echo 'ok deploy locking'