/* * Copyright (C) 2017 Colin Walters * * 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 . */ #include "config.h" #include #include #include #include #ifdef HAVE_LIBMOUNT #include #endif #include #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 /* Forcibly enable our internal units, since we detected ostree= on the kernel cmdline */ static gboolean require_internal_units (const char *normal_dir, const char *early_dir, const char *late_dir, GError **error) { #ifdef SYSTEM_DATA_UNIT_PATH GCancellable *cancellable = NULL; glnx_autofd int normal_dir_dfd = -1; if (!glnx_opendirat (AT_FDCWD, normal_dir, TRUE, &normal_dir_dfd, error)) return FALSE; if (!glnx_shutil_mkdir_p_at (normal_dir_dfd, "local-fs.target.requires", 0755, cancellable, error)) return FALSE; if (symlinkat (SYSTEM_DATA_UNIT_PATH "/ostree-remount.service", normal_dir_dfd, "local-fs.target.requires/ostree-remount.service") < 0) return glnx_throw_errno_prefix (error, "symlinkat"); if (!glnx_shutil_mkdir_p_at (normal_dir_dfd, "multi-user.target.wants", 0755, cancellable, error)) return FALSE; if (symlinkat (SYSTEM_DATA_UNIT_PATH "/ostree-finalize-staged.path", normal_dir_dfd, "multi-user.target.wants/ostree-finalize-staged.path") < 0) return glnx_throw_errno_prefix (error, "symlinkat"); return TRUE; #else return glnx_throw (error, "Not implemented"); #endif } /* Generate var.mount */ static gboolean fstab_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_autofd 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"); g_auto(GLnxTmpfile) tmpf = { 0, }; if (!glnx_open_tmpfile_linkable_at (normal_dir_dfd, ".", O_WRONLY | O_CLOEXEC, &tmpf, error)) return FALSE; g_autoptr(GOutputStream) outstream = g_unix_output_stream_new (tmpf.fd, 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" "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 (!glnx_fchmod (tmpf.fd, 0644, error)) return FALSE; /* Error out if somehow it already exists, that'll help us debug conflicts */ if (!glnx_link_tmpfile_at (&tmpf, GLNX_LINK_TMPFILE_NOREPLACE, normal_dir_dfd, "var.mount", error)) return FALSE; /* And ensure it's required; newer systemd will auto-inject fs dependencies * via RequiresMountsFor and the like, but on older versions (e.g. CentOS) we * need this. It's what the fstab generator does. And my mother always said, * listen to the fstab generator. */ if (!glnx_shutil_mkdir_p_at (normal_dir_dfd, "local-fs.target.requires", 0755, cancellable, error)) return FALSE; if (symlinkat ("../var.mount", normal_dir_dfd, "local-fs.target.requires/var.mount") < 0) return glnx_throw_errno_prefix (error, "symlinkat"); return TRUE; #else return glnx_throw (error, "Not implemented"); #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) { if (!require_internal_units (normal_dir, early_dir, late_dir, error)) return FALSE; if (!fstab_generator (ostree_cmdline, normal_dir, early_dir, late_dir, error)) return FALSE; return TRUE; }