diff --git a/Makefile-libostree.am b/Makefile-libostree.am index 86ba0414..7f2e2a4a 100644 --- a/Makefile-libostree.am +++ b/Makefile-libostree.am @@ -115,6 +115,7 @@ libostree_1_la_SOURCES = \ src/libostree/ostree-sysroot-cleanup.c \ src/libostree/ostree-sysroot-deploy.c \ src/libostree/ostree-sysroot-upgrader.c \ + src/libostree/ostree-impl-system-generator.c \ src/libostree/ostree-bootconfig-parser.c \ src/libostree/ostree-deployment.c \ src/libostree/ostree-bootloader.h \ diff --git a/Makefile-switchroot.am b/Makefile-switchroot.am index 6fd2f820..dd24010e 100644 --- a/Makefile-switchroot.am +++ b/Makefile-switchroot.am @@ -27,6 +27,7 @@ ostree_prepare_root_SOURCES = \ src/switchroot/ostree-mount-util.h \ src/switchroot/ostree-prepare-root.c \ $(NULL) +ostree_prepare_root_CPPFLAGS = $(AM_CPPFLAGS) if BUILDOPT_USE_STATIC_COMPILER # ostree-prepare-root can be used as init in a system without a populated /lib. @@ -45,7 +46,6 @@ ostree-prepare-root : $(ostree_prepare_root_SOURCES) $(STATIC_COMPILER) -o $@ -static $(ostree_prepare_root_SOURCES) $(AM_CPPFLAGS) $(AM_CFLAGS) $(DEFAULT_INCLUDES) else ostree_boot_PROGRAMS += ostree-prepare-root - ostree_prepare_root_CFLAGS = $(AM_CFLAGS) -Isrc/switchroot endif @@ -53,4 +53,19 @@ ostree_remount_SOURCES = \ src/switchroot/ostree-mount-util.h \ src/switchroot/ostree-remount.c \ $(NULL) -ostree_remount_CFLAGS = $(AM_CFLAGS) -Isrc/switchroot +ostree_remount_CPPFLAGS = $(AM_CPPFLAGS) -Isrc/switchroot + +# This is the "new mode" of using a generator for /var; see +# https://github.com/ostreedev/ostree/issues/855 +if BUILDOPT_SYSTEMD_AND_LIBMOUNT +ostree_prepare_root_CPPFLAGS += -DHAVE_SYSTEMD_AND_LIBMOUNT=1 +ostree_remount_CPPFLAGS += -DHAVE_SYSTEMD_AND_LIBMOUNT=1 + +systemdsystemgenerator_PROGRAMS = ostree-system-generator +GITIGNOREFILES += $(systemdsystemgenerator_PROGRAMS) +ostree_system_generator_SOURCES = src/switchroot/ostree-mount-util.h \ + src/switchroot/ostree-system-generator.c +ostree_system_generator_CPPFLAGS = $(AM_CPPFLAGS) -I$(srcdir)/libglnx -I$(srcdir)/src/libostree +ostree_system_generator_CFLAGS = $(AM_CFLAGS) $(OT_INTERNAL_GIO_UNIX_CFLAGS) +ostree_system_generator_LDADD = $(AM_LDFLAGS) libglnx.la libostree-1.la $(OT_INTERNAL_GIO_UNIX_LIBS) +endif diff --git a/configure.ac b/configure.ac index 6acd0b3c..06d39126 100644 --- a/configure.ac +++ b/configure.ac @@ -389,8 +389,19 @@ AS_IF([test "x$have_libsystemd" = "xyes"], [ AS_IF([test "x$with_systemdsystemunitdir" != "xno"], [ AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir]) ]) + AC_ARG_WITH([systemdsystemgeneratordir], + AS_HELP_STRING([--with-systemdsystemgeneratordir=DIR], [Directory for systemd generators]), + [], + [with_systemdsystemgeneratordir=$($PKG_CONFIG --variable=systemdsystemgeneratordir systemd)]) + AS_IF([test "x$with_systemdsystemgeneratordir" != "xno"], [ + AC_SUBST([systemdsystemgeneratordir], [$with_systemdsystemgeneratordir]) + ]) ]) AM_CONDITIONAL(BUILDOPT_SYSTEMD, test x$with_systemd = xyes) +dnl If we have both, we use the "new /var" model with ostree-system-generator +AM_CONDITIONAL(BUILDOPT_SYSTEMD_AND_LIBMOUNT,[test x$with_systemd = xyes && test x$with_libmount = xyes]) +AM_COND_IF(BUILDOPT_SYSTEMD_AND_LIBMOUNT, + AC_DEFINE([BUILDOPT_LIBSYSTEMD_AND_LIBMOUNT], 1, [Define if systemd and libmount])) AC_ARG_WITH(builtin-grub2-mkconfig, AS_HELP_STRING([--with-builtin-grub2-mkconfig], diff --git a/src/libostree/ostree-cmdprivate.c b/src/libostree/ostree-cmdprivate.c index 4367b497..bade7431 100644 --- a/src/libostree/ostree-cmdprivate.c +++ b/src/libostree/ostree-cmdprivate.c @@ -45,6 +45,7 @@ const OstreeCmdPrivateVTable * ostree_cmd__private__ (void) { static OstreeCmdPrivateVTable table = { + _ostree_impl_system_generator, impl_ostree_generate_grub2_config, _ostree_repo_static_delta_dump, _ostree_repo_static_delta_query_exists, diff --git a/src/libostree/ostree-cmdprivate.h b/src/libostree/ostree-cmdprivate.h index 8d1c653e..63c427cd 100644 --- a/src/libostree/ostree-cmdprivate.h +++ b/src/libostree/ostree-cmdprivate.h @@ -24,7 +24,10 @@ G_BEGIN_DECLS +gboolean _ostree_impl_system_generator (const char *ostree_cmdline, const char *normal_dir, const char *early_dir, const char *late_dir, GError **error); + typedef struct { + gboolean (* ostree_system_generator) (const char *ostree_cmdline, const char *normal_dir, const char *early_dir, const char *late_dir, 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); gboolean (* ostree_static_delta_query_exists) (OstreeRepo *repo, const char *delta_id, gboolean *out_exists, GCancellable *cancellable, GError **error); diff --git a/src/libostree/ostree-impl-system-generator.c b/src/libostree/ostree-impl-system-generator.c new file mode 100644 index 00000000..7c4d49df --- /dev/null +++ b/src/libostree/ostree-impl-system-generator.c @@ -0,0 +1,219 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2017 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. + */ + +#include "config.h" + +#include +#include +#include +#include +#ifdef HAVE_LIBMOUNT +#include +#endif +#include +#include "otutil.h" + +#include "ostree.h" +#include "ostree-core-private.h" +#include "ostree-cmdprivate.h" + +#ifdef HAVE_LIBMOUNT +typedef FILE OtLibMountFile; +G_DEFINE_AUTOPTR_CLEANUP_FUNC(OtLibMountFile, endmntent); + +/* Taken from systemd path-util.c */ +static bool +is_path (const char *p) +{ + return !!strchr (p, '/'); +} + +/* Taken from systemd path-util.c */ +static char* +path_kill_slashes (char *path) +{ + char *f, *t; + bool slash = false; + + /* Removes redundant inner and trailing slashes. Modifies the + * passed string in-place. + * + * For example: ///foo///bar/ becomes /foo/bar + */ + + for (f = path, t = path; *f; f++) + { + if (*f == '/') + { + slash = true; + continue; + } + + if (slash) + { + slash = false; + *(t++) = '/'; + } + + *(t++) = *f; + } + + /* Special rule, if we are talking of the root directory, a + trailing slash is good */ + + if (t == path && slash) + *(t++) = '/'; + + *t = 0; + return path; +} + +/* Written by ostree-sysroot-deploy.c. We parse out the stateroot here since we + * need to know it to mount /var. Unfortunately we can't easily use the + * libostree API to find the booted deployment since /boot might not have been + * mounted yet. + */ +static char * +stateroot_from_ostree_cmdline (const char *ostree_cmdline, + GError **error) +{ + static GRegex *regex; + static gsize regex_initialized; + if (g_once_init_enter (®ex_initialized)) + { + regex = g_regex_new ("^/ostree/boot.[01]/([^/]+)/", 0, 0, NULL); + g_assert (regex); + g_once_init_leave (®ex_initialized, 1); + } + + g_autoptr(GMatchInfo) match = NULL; + if (!g_regex_match (regex, ostree_cmdline, 0, &match)) + return glnx_null_throw (error, "Failed to parse %s", ostree_cmdline); + + return g_match_info_fetch (match, 1); +} +#endif + +/* Implementation of ostree-system-generator */ +gboolean +_ostree_impl_system_generator (const char *ostree_cmdline, + const char *normal_dir, + const char *early_dir, + const char *late_dir, + GError **error) +{ +#ifdef HAVE_LIBMOUNT + /* Not currently cancellable, but define a var in case we care later */ + GCancellable *cancellable = NULL; + /* Some path constants to avoid typos */ + static const char fstab_path[] = "/etc/fstab"; + static const char var_path[] = "/var"; + + /* ostree-prepare-root was patched to write the stateroot to this file */ + g_autofree char *stateroot = stateroot_from_ostree_cmdline (ostree_cmdline, error); + if (!stateroot) + return FALSE; + + /* Load /etc/fstab if it exists, and look for a /var mount */ + g_autoptr(OtLibMountFile) fstab = setmntent (fstab_path, "re"); + gboolean found_var_mnt = FALSE; + if (!fstab) + { + if (errno != ENOENT) + return glnx_throw_errno_prefix (error, "Reading %s", fstab_path); + } + else + { + /* Parse it */ + struct mntent *me; + while ((me = getmntent (fstab))) + { + g_autofree char *where = g_strdup (me->mnt_dir); + if (is_path (where)) + path_kill_slashes (where); + + /* We're only looking for /var here */ + if (strcmp (where, var_path) != 0) + continue; + + found_var_mnt = TRUE; + break; + } + } + + /* If we found /var, we're done */ + if (found_var_mnt) + return TRUE; + + /* Prepare to write to the output unit dir; we use the "normal" dir + * that overrides /usr, but not /etc. + */ + glnx_fd_close int normal_dir_dfd = -1; + if (!glnx_opendirat (AT_FDCWD, normal_dir, TRUE, &normal_dir_dfd, error)) + return FALSE; + + /* Generate our bind mount unit */ + const char *stateroot_var_path = glnx_strjoina ("/sysroot/ostree/deploy/", stateroot, "/var"); + + glnx_fd_close int tmpfd = -1; + g_autofree char *tmppath = NULL; + if (!glnx_open_tmpfile_linkable_at (normal_dir_dfd, ".", O_WRONLY, + &tmpfd, &tmppath, error)) + return FALSE; + g_autoptr(GOutputStream) outstream = g_unix_output_stream_new (tmpfd, FALSE); + gsize bytes_written; + /* This code is inspired by systemd's fstab-generator.c. + * + * Note that our unit doesn't run if systemd.volatile is enabled; + * see https://github.com/ostreedev/ostree/pull/856 + */ + if (!g_output_stream_printf (outstream, &bytes_written, cancellable, error, + "##\n# Automatically generated by ostree-system-generator\n##\n\n" + "[Unit]\n" + "Documentation=man:ostree(1)\n" + "ConditionKernelCommandLine=!systemd.volatile\n" + /* We need /sysroot mounted writable first */ + "After=ostree-remount.service\n" + "Before=local-fs.target\n" + "\n" + "[Mount]\n" + "Where=%s\n" + "What=%s\n" + "Options=bind\n", + var_path, + stateroot_var_path)) + return FALSE; + if (!g_output_stream_flush (outstream, cancellable, error)) + return FALSE; + g_clear_object (&outstream); + /* It should be readable */ + if (fchmod (tmpfd, 0644) < 0) + return glnx_throw_errno_prefix (error, "fchmod"); + /* Error out if somehow it already exists, that'll help us debug conflicts */ + if (!glnx_link_tmpfile_at (normal_dir_dfd, GLNX_LINK_TMPFILE_NOREPLACE, + tmpfd, tmppath, normal_dir_dfd, + "var.mount", error)) + return FALSE; + + return TRUE; +#else + return glnx_throw (error, "Not implemented"); +#endif +} diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index b4a8ec0c..257a058b 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -1379,6 +1379,7 @@ install_deployment_kernel (OstreeSysroot *sysroot, val = ostree_bootconfig_parser_get (bootconfig, "options"); + /* Note this is parsed in ostree-impl-system-generator.c */ g_autofree char *ostree_kernel_arg = g_strdup_printf ("ostree=/ostree/boot.%d/%s/%s/%d", new_bootversion, osname, bootcsum, ostree_deployment_get_bootserial (deployment)); diff --git a/src/switchroot/ostree-mount-util.h b/src/switchroot/ostree-mount-util.h index b24aa44d..1e7253d2 100644 --- a/src/switchroot/ostree-mount-util.h +++ b/src/switchroot/ostree-mount-util.h @@ -25,6 +25,10 @@ #include #include #include +#include +#include +#include +#include static inline int path_is_on_readonly_fs (char *path) @@ -37,4 +41,66 @@ path_is_on_readonly_fs (char *path) return (stvfsbuf.f_flag & ST_RDONLY) != 0; } +static inline char * +read_proc_cmdline (void) +{ + FILE *f = fopen("/proc/cmdline", "r"); + char *cmdline = NULL; + size_t len; + + if (!f) + goto out; + + /* Note that /proc/cmdline will not end in a newline, so getline + * will fail unelss we provide a length. + */ + if (getline (&cmdline, &len, f) < 0) + goto out; + /* ... but the length will be the size of the malloc buffer, not + * strlen(). Fix that. + */ + len = strlen (cmdline); + + if (cmdline[len-1] == '\n') + cmdline[len-1] = '\0'; +out: + if (f) + fclose (f); + return cmdline; +} + +static inline char * +read_proc_cmdline_ostree (void) +{ + char *cmdline = NULL; + const char *iter; + char *ret = NULL; + + cmdline = read_proc_cmdline (); + if (!cmdline) + err (EXIT_FAILURE, "failed to read /proc/cmdline"); + + iter = cmdline; + while (iter != NULL) + { + const char *next = strchr (iter, ' '); + const char *next_nonspc = next; + while (next_nonspc && *next_nonspc == ' ') + next_nonspc += 1; + if (strncmp (iter, "ostree=", strlen ("ostree=")) == 0) + { + const char *start = iter + strlen ("ostree="); + if (next) + ret = strndup (start, next - start); + else + ret = strdup (start); + break; + } + iter = next_nonspc; + } + + free (cmdline); + return ret; +} + #endif /* __OSTREE_MOUNT_UTIL_H_ */ diff --git a/src/switchroot/ostree-prepare-root.c b/src/switchroot/ostree-prepare-root.c index 1dac984d..15dbafdf 100644 --- a/src/switchroot/ostree-prepare-root.c +++ b/src/switchroot/ostree-prepare-root.c @@ -46,68 +46,6 @@ #include "ostree-mount-util.h" -static char * -read_proc_cmdline (void) -{ - FILE *f = fopen("/proc/cmdline", "r"); - char *cmdline = NULL; - size_t len; - - if (!f) - goto out; - - /* Note that /proc/cmdline will not end in a newline, so getline - * will fail unelss we provide a length. - */ - if (getline (&cmdline, &len, f) < 0) - goto out; - /* ... but the length will be the size of the malloc buffer, not - * strlen(). Fix that. - */ - len = strlen (cmdline); - - if (cmdline[len-1] == '\n') - cmdline[len-1] = '\0'; -out: - if (f) - fclose (f); - return cmdline; -} - -static char * -parse_ostree_cmdline (void) -{ - char *cmdline = NULL; - const char *iter; - char *ret = NULL; - - cmdline = read_proc_cmdline (); - if (!cmdline) - err (EXIT_FAILURE, "failed to read /proc/cmdline"); - - iter = cmdline; - while (iter != NULL) - { - const char *next = strchr (iter, ' '); - const char *next_nonspc = next; - while (next_nonspc && *next_nonspc == ' ') - next_nonspc += 1; - if (strncmp (iter, "ostree=", strlen ("ostree=")) == 0) - { - const char *start = iter + strlen ("ostree="); - if (next) - ret = strndup (start, next - start); - else - ret = strdup (start); - break; - } - iter = next_nonspc; - } - - free (cmdline); - return ret; -} - /* This is an API for other projects to determine whether or not the * currently running system is ostree-controlled. */ @@ -132,7 +70,7 @@ resolve_deploy_path (const char * root_mountpoint) struct stat stbuf; char *ostree_target, *deploy_path; - ostree_target = parse_ostree_cmdline (); + ostree_target = read_proc_cmdline_ostree (); if (!ostree_target) errx (EXIT_FAILURE, "No OSTree target; expected ostree=/ostree/boot.N/..."); @@ -211,9 +149,12 @@ main(int argc, char *argv[]) if (chdir (deploy_path) < 0) err (EXIT_FAILURE, "failed to chdir to deploy_path"); + /* In the systemd case, this is handled by ostree-system-generator */ +#ifndef HAVE_SYSTEMD_AND_LIBMOUNT /* Link to the deployment's /var */ if (mount ("../../var", "var", NULL, MS_MGC_VAL|MS_BIND, NULL) < 0) err (EXIT_FAILURE, "failed to bind mount ../../var to var"); +#endif /* If /boot is on the same partition, use a bind mount to make it visible * at /boot inside the deployment. */ diff --git a/src/switchroot/ostree-system-generator.c b/src/switchroot/ostree-system-generator.c new file mode 100644 index 00000000..e7205b5a --- /dev/null +++ b/src/switchroot/ostree-system-generator.c @@ -0,0 +1,67 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- + * + * Copyright (C) 2017 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. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "ostree-cmdprivate.h" +#include "ostree-mount-util.h" + +static const char *arg_dest = "/tmp"; +static const char *arg_dest_late = "/tmp"; + +/* This program is a simple stub that calls the implementation that + * lives inside libostree. + */ +int +main(int argc, char *argv[]) +{ + /* Important: if this isn't an ostree-booted system, do nothing; people could + * have the package installed as a dependency for flatpak or whatever. + */ + { struct stat stbuf; + if (fstatat (AT_FDCWD, "/run/ostree-booted", &stbuf, 0) < 0) + exit (EXIT_SUCCESS); + } + + if (argc > 1 && argc != 4) + errx (EXIT_FAILURE, "This program takes three or no arguments"); + + if (argc > 1) + arg_dest = argv[1]; + if (argc > 3) + arg_dest_late = argv[3]; + + char *ostree_cmdline = read_proc_cmdline_ostree (); + if (!ostree_cmdline) + errx (EXIT_FAILURE, "Failed to find ostree= kernel argument"); + + { g_autoptr(GError) local_error = NULL; + if (!ostree_cmd__private__()->ostree_system_generator (ostree_cmdline, arg_dest, NULL, arg_dest_late, &local_error)) + errx (EXIT_FAILURE, "%s", local_error->message); + } + + exit (EXIT_SUCCESS); +}