Skip to content

Commit

Permalink
livefs: Update /usr/lib/{passwd,group}, run systemd-tmpfiles
Browse files Browse the repository at this point in the history
This gets us to supporting live installs of `httpd` and many
other services.  I also tried `postgresql-server` for example.

Closes: coreos#962
  • Loading branch information
cgwalters committed Sep 22, 2017
1 parent f089b8d commit 50a5abe
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 23 deletions.
146 changes: 126 additions & 20 deletions src/daemon/rpmostreed-transaction-livefs.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ typedef struct {
guint refcount;
CommitDiffFlags flags;
guint n_usretc;
guint n_tmpfilesd;

char *from;
char *to;
Expand Down Expand Up @@ -162,6 +163,8 @@ diff_one_path (CommitDiff *diff,
diff->flags |= COMMIT_DIFF_FLAGS_ETC;
diff->n_usretc++;
}
else if (g_str_has_prefix (path, "/usr/lib/tmpfiles.d"))
diff->n_tmpfilesd++;
else if (path_is_boot (path))
diff->flags |= COMMIT_DIFF_FLAGS_BOOT;
else if (path_is_rootfs (path))
Expand Down Expand Up @@ -449,6 +452,76 @@ checkout_add_usr (OstreeRepo *repo,
return TRUE;
}

/* Even when doing a pure "add" there are some things we need to actually
* replace:
*
* - rpm database
* - /usr/lib/{passwd,group}
*
* This function can swap in a new file/directory.
*/
static gboolean
replace_subpath (OstreeRepo *repo,
int deployment_dfd,
GLnxTmpDir *tmpdir,
const char *target_csum,
const char *subpath,
GCancellable *cancellable,
GError **error)
{
/* The subpath for ostree_repo_checkout_at() must be absolute, but
* our real filesystem paths must be relative.
*/
g_assert_cmpint (*subpath, ==, '/');
const char *relsubpath = subpath += strspn (subpath, "/");
const char *bname = glnx_basename (relsubpath);

/* See if it exists, if it does gather stat info; we need to handle
* directories differently from non-dirs.
*/
struct stat stbuf;
if (!glnx_fstatat_allow_noent (deployment_dfd, relsubpath, &stbuf, AT_SYMLINK_NOFOLLOW, error))
return FALSE;
if (errno == ENOENT)
return TRUE; /* Do nothing if the path doesn't exist */

OstreeRepoCheckoutAtOptions replace_checkout_opts = { .mode = OSTREE_REPO_CHECKOUT_MODE_NONE,
.no_copy_fallback = TRUE,
.subpath = subpath };
/* We need to differentiate nondirs vs dirs; for two reasons. First,
* see the /etc path code - ostree_repo_checkout_at() has
* some legacy bits around checking out individual files.
*
* Second, for directories we want RENAME_EXCHANGE if at all possible, but for
* non-dirs there's no reason not to just use plain old renameat() which will
* also work atomically even on old kernels (e.g. CentOS7). For some critical
* files like /usr/lib/passwd we really do want atomicity.
*/
const gboolean target_is_nondirectory = !S_ISDIR (stbuf.st_mode);
if (target_is_nondirectory)
{
if (!ostree_repo_checkout_at (repo, &replace_checkout_opts, tmpdir->fd, ".",
target_csum, cancellable, error))
return FALSE;
if (!glnx_renameat (tmpdir->fd, bname, deployment_dfd, relsubpath, error))
return FALSE;
}
else
{
if (!ostree_repo_checkout_at (repo, &replace_checkout_opts, tmpdir->fd, bname,
target_csum, cancellable, error))
return FALSE;

if (glnx_renameat2_exchange (tmpdir->fd, bname, deployment_dfd, relsubpath) < 0)
return glnx_throw_errno_prefix (error, "rename(..., RENAME_EXCHANGE) for %s", subpath);
/* And nuke the old one */
if (!glnx_shutil_rm_rf_at (tmpdir->fd, bname, cancellable, error))
return FALSE;
}

return TRUE;
}

/* Update the origin for @booted with new livefs state */
static gboolean
write_livefs_state (OstreeSysroot *sysroot,
Expand All @@ -473,8 +546,6 @@ livefs_transaction_execute_inner (LiveFsTransaction *self,
GCancellable *cancellable,
GError **error)
{
static const char orig_rpmdb_path[] = "usr/share/rpm.rpmostree-orig";

/* Initial setup - load sysroot, repo, and booted deployment */
glnx_unref_object OstreeRepo *repo = NULL;
if (!ostree_sysroot_get_repo (sysroot, &repo, cancellable, error))
Expand Down Expand Up @@ -628,29 +699,64 @@ livefs_transaction_execute_inner (LiveFsTransaction *self,
if (!checkout_add_usr (repo, deployment_dfd, diff, target_csum, cancellable, error))
return FALSE;

/* Start replacing the rpmdb. First, ensure the temporary dir for the new
version doesn't exist */
if (!glnx_shutil_rm_rf_at (deployment_dfd, orig_rpmdb_path, cancellable, error))
return FALSE;
/* Check out the new rpmdb */
{ OstreeRepoCheckoutAtOptions rpmdb_checkout_opts = { .mode = OSTREE_REPO_CHECKOUT_MODE_NONE,
.no_copy_fallback = TRUE,
.subpath = "/usr/share/rpm" };


if (!ostree_repo_checkout_at (repo, &rpmdb_checkout_opts, deployment_dfd, orig_rpmdb_path,
target_csum, cancellable, error))
/* And files that we always need to replace; the rpmdb, /usr/lib/passwd. We
* make a tmpdir just for this since it's a more convenient place to put
* temporary files/dirs without generating tempnames.
*/
{ g_auto(GLnxTmpDir) replace_tmpdir = { 0, };
if (!glnx_mkdtempat (ostree_repo_get_dfd (repo), "tmp/rpmostree-livefs.XXXXXX", 0700,
&replace_tmpdir, error))
return FALSE;

const char *replace_paths[] = { "/usr/share/rpm", "/usr/lib/passwd", "/usr/lib/group" };
for (guint i = 0; i < G_N_ELEMENTS(replace_paths); i++)
{
const char *replace_path = replace_paths[i];
if (!replace_subpath (repo, deployment_dfd, &replace_tmpdir,
target_csum, replace_path,
cancellable, error))
return FALSE;
}
}
/* Now, RENAME_EXCHANGE the two */
if (glnx_renameat2_exchange (deployment_dfd, "usr/share/rpm", deployment_dfd, orig_rpmdb_path) < 0)
return glnx_throw_errno_prefix (error, "%s", "rename(..., RENAME_EXCHANGE) for rpmdb");
/* And nuke the old one */
if (!glnx_shutil_rm_rf_at (deployment_dfd, orig_rpmdb_path, cancellable, error))
return FALSE;

rpmostree_output_task_end ("done");

if (diff->n_tmpfilesd > 0)
{
const char *tmpfiles_prefixes[] = { "/run/", "/var/"};
const char *tmpfiles_argv[] = { "systemd-tmpfiles", "--create",
"--prefix", NULL, NULL };

rpmostree_output_task_begin ("Running systemd-tmpfiles for /run,/var");

for (guint i = 0; i < G_N_ELEMENTS (tmpfiles_prefixes); i++)
{
const char *prefix = tmpfiles_prefixes[i];
GLNX_AUTO_PREFIX_ERROR ("Executing systemd-tmpfiles", error);
tmpfiles_argv[G_N_ELEMENTS(tmpfiles_argv)-2] = prefix;
g_autoptr(GSubprocess) subproc = g_subprocess_newv ((const char *const*)tmpfiles_argv,
G_SUBPROCESS_FLAGS_STDERR_PIPE |
G_SUBPROCESS_FLAGS_STDOUT_SILENCE,
error);
if (!subproc)
return FALSE;
g_autofree char *stderr_buf = NULL;
if (!g_subprocess_communicate_utf8 (subproc, NULL, cancellable,
NULL, &stderr_buf, error))
return FALSE;
if (!g_subprocess_get_successful (subproc))
{
/* Only dump stderr if it actually failed; otherwise we know
* there are (harmless) warnings, no need to bother everyone.
*/
sd_journal_print (LOG_ERR, "systemd-tmpfiles failed: %s", stderr_buf);
return FALSE;
}
}

rpmostree_output_task_end ("done");
}

if (requires_etc_merge)
{
if (!copy_new_config_files (repo, origin_merge_deployment,
Expand Down
23 changes: 20 additions & 3 deletions tests/vmcheck/test-livefs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,26 @@ vm_build_rpm test-livefs-with-etc \
/etc/%{name}/*
/etc/opt/%{name}*"

# Simulate a service that adds a user and has data in tmpfiles.d
vm_build_rpm test-livefs-service \
build "echo test-livefs-service > test-livefs-service.txt" \
install "mkdir -p %{buildroot}/{usr/share,var/lib/%{name}}
install test-livefs-service.txt %{buildroot}/usr/share" \
pre "groupadd -r livefs-group
useradd -r livefs-user -g livefs-group -s /sbin/nologin" \
files "/usr/share/%{name}.txt
/var/lib/%{name}"

# make sure there are no config files already present
vm_cmd rm -rf /etc/test-livefs-with-etc \
/etc/test-livefs-with-etc.conf \
/etc/opt/test-livefs-with-etc-opt.conf

vm_rpmostree install /tmp/vmcheck/yumrepo/packages/x86_64/test-livefs-with-etc-1.0-1.x86_64.rpm
vm_rpmostree install /tmp/vmcheck/yumrepo/packages/x86_64/test-livefs-{with-etc,service}-1.0-1.x86_64.rpm
assert_livefs_ok
vm_rpmostree ex livefs
vm_cmd rpm -q foo test-livefs-with-etc > rpmq.txt
assert_file_has_content rpmq.txt foo-1.0-1 test-livefs-with-etc-1.0-1
vm_cmd rpm -q foo test-livefs-{with-etc,service} > rpmq.txt
assert_file_has_content rpmq.txt foo-1.0-1 test-livefs-{with-etc,service}-1.0-1
vm_cmd cat /etc/test-livefs-with-etc.conf > test-livefs-with-etc.conf
assert_file_has_content test-livefs-with-etc.conf "A config file for test-livefs-with-etc"
for v in subconfig-one subconfig-two subdir/subconfig-three; do
Expand All @@ -83,6 +93,13 @@ for v in subconfig-one subconfig-two subdir/subconfig-three; do
done
vm_cmd cat /etc/opt/test-livefs-with-etc-opt.conf > test-livefs-with-etc.conf
assert_file_has_content test-livefs-with-etc.conf "file-in-opt-subdir"
# Test /usr/lib/{passwd,group} bits
vm_cmd getent passwd livefs-user > test-livefs-user.txt
assert_file_has_content test-livefs-user.txt livefs-user
vm_cmd getent group livefs-group > test-livefs-group.txt
assert_file_has_content test-livefs-group.txt livefs-group
# Test systemd-tmpfiles
vm_cmd test -d /var/lib/test-livefs-service

echo "ok livefs stage2"

Expand Down

0 comments on commit 50a5abe

Please sign in to comment.