diff --git a/Makefile-tests.am b/Makefile-tests.am index a9b866bf8b..5544a6bf05 100644 --- a/Makefile-tests.am +++ b/Makefile-tests.am @@ -114,6 +114,7 @@ _installed_or_uninstalled_test_scripts = \ tests/test-admin-deploy-none.sh \ tests/test-admin-deploy-bootid-gc.sh \ tests/test-admin-deploy-whiteouts.sh \ + tests/test-admin-deploy-emptyetc.sh \ tests/test-osupdate-dtb.sh \ tests/test-admin-instutil-set-kargs.sh \ tests/test-admin-upgrade-not-backwards.sh \ diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 974d23362f..72d729c3da 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -872,26 +872,80 @@ prepare_deployment_etc (OstreeSysroot *sysroot, OstreeRepo *repo, OstreeDeployme { GLNX_AUTO_PREFIX_ERROR ("Preparing /etc", error); + enum DirectoryState + { + DIRSTATE_NONEXISTENT, + DIRSTATE_EMPTY, + DIRSTATE_POPULATED, + }; + + enum DirectoryState etc_state; + { + gboolean exists = FALSE; + g_auto (GLnxDirFdIterator) dfd_iter = { + 0, + }; + if (!ot_dfd_iter_init_allow_noent (deployment_dfd, "etc", &dfd_iter, &exists, error)) + return glnx_prefix_error (error, "Failed to stat etc in deployment"); + if (!exists) + { + etc_state = DIRSTATE_NONEXISTENT; + } + else + { + struct dirent *dent; + if (!glnx_dirfd_iterator_next_dent (&dfd_iter, &dent, NULL, error)) + return FALSE; + if (dent) + etc_state = DIRSTATE_POPULATED; + else + etc_state = DIRSTATE_EMPTY; + } + } struct stat stbuf; - if (!glnx_fstatat_allow_noent (deployment_dfd, "etc", &stbuf, AT_SYMLINK_NOFOLLOW, error)) - return FALSE; - gboolean etc_exists = (errno == 0); if (!glnx_fstatat_allow_noent (deployment_dfd, "usr/etc", &stbuf, AT_SYMLINK_NOFOLLOW, error)) return FALSE; gboolean usretc_exists = (errno == 0); - if (etc_exists) + switch (etc_state) { - if (usretc_exists) - return glnx_throw (error, "Tree contains both /etc and /usr/etc"); - /* Compatibility hack */ - if (!glnx_renameat (deployment_dfd, "etc", deployment_dfd, "usr/etc", error)) - return FALSE; - usretc_exists = TRUE; + case DIRSTATE_NONEXISTENT: + break; + case DIRSTATE_EMPTY: + { + if (usretc_exists) + { + /* For now it's actually simpler to just remove the empty directory + * and have a symmetrical code path. + */ + if (unlinkat (deployment_dfd, "etc", AT_REMOVEDIR) < 0) + return glnx_throw_errno_prefix (error, "Failed to remove empty etc"); + etc_state = DIRSTATE_NONEXISTENT; + } + /* Otherwise, there's no /etc or /usr/etc, we'll assume they know what they're doing... */ + } + break; + case DIRSTATE_POPULATED: + { + if (usretc_exists) + { + return glnx_throw (error, "Tree contains both /etc and /usr/etc"); + } + else + { + /* Compatibility hack */ + if (!glnx_renameat (deployment_dfd, "etc", deployment_dfd, "usr/etc", error)) + return FALSE; + etc_state = DIRSTATE_NONEXISTENT; + usretc_exists = TRUE; + } + } + break; } if (usretc_exists) { + g_assert (etc_state == DIRSTATE_NONEXISTENT); /* We need copies of /etc from /usr/etc (so admins can use vi), and if * SELinux is enabled, we need to relabel. */ diff --git a/tests/test-admin-deploy-emptyetc.sh b/tests/test-admin-deploy-emptyetc.sh new file mode 100755 index 0000000000..8c96207b89 --- /dev/null +++ b/tests/test-admin-deploy-emptyetc.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# +# SPDX-License-Identifier: LGPL-2.0+ +# +# 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, see . + +set -euo pipefail + +. $(dirname $0)/libtest.sh + +setup_os_repository "archive" "syslinux" + +echo "1..1" +cd ${test_tmpdir}/osdata +mkdir etc +${CMD_PREFIX} ostree --repo=${test_tmpdir}/testos-repo commit --add-metadata-string "version=42.etc" -b testos/buildmain/x86_64-runtime +cd - +${CMD_PREFIX} ostree --repo=sysroot/ostree/repo pull-local --remote=testos testos-repo testos/buildmain/x86_64-runtime +${CMD_PREFIX} ostree admin deploy --os=testos testos:testos/buildmain/x86_64-runtime +origdeployment=$(${CMD_PREFIX} ostree admin --sysroot=sysroot --print-current-dir) +assert_file_has_content ${origdeployment}/etc/NetworkManager/nm.conf "a default daemon file" +echo "ok empty etc"