b53d353779
In 'debug' log-level we expect a detailed, but still readable output. Having one area with disproportionately large number of logs reduces the overall utility of the log output. This patch reduces a number of redundant log entries, including errors that are already logged. It also reduces the level of some others from 'information' to 'debug' and from 'debug' to 'trace'. The goal is to make 'debug' level as useful as possible to read the progress and be able to understand what was going on, such that one is able to decide which area to dig deeper into. Then, trace level could be used to get more insight into that area, if necessary. For example, when investigating a test failure, one first enables 'debug' logs and reads through. Once a section between two debug entries is identified as being of interest, enabling 'trace' level logs becomes more productive as it's now possible to easily reach the first DBG entry and read through until the second one. It's unfortunate that we don't have per-area control for enabling/disabling logs, so it is common to see more and more 'debug' log entries added all around, making logs less and less readable. It is also a limitation of the levels we have that we really only have 3 usable levels: one, two, many. That is, 'information' for the most important events, 'debug' for technical details needed to investigate issues, and 'trace' for everything else. ('warning' and 'error' aren't really 'levels'; they have semantics that makes them special-cases.) So we have to avoid degrading one into the other, or have differences without distinction. If any of these entries are needed to be displayed more frequently, changing them back to 'debug' or even 'information' should be done. Though for me they seem special cases that don't benefit most log readings. Change-Id: Id2c6a9dc027483b81a066b0b4b50a298c5eff449 Signed-off-by: Ashod Nakashian <ashod.nakashian@collabora.co.uk>
509 lines
17 KiB
C++
509 lines
17 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
/*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include "FileUtil.hpp"
|
|
#include "JailUtil.hpp"
|
|
|
|
#include <sys/types.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#ifdef __linux__
|
|
#include <sys/sysmacros.h>
|
|
#endif
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <string>
|
|
|
|
#include "Log.hpp"
|
|
|
|
namespace JailUtil
|
|
{
|
|
bool coolmount(const std::string& arg, std::string source, std::string target)
|
|
{
|
|
source = Util::trim(source, '/');
|
|
target = Util::trim(target, '/');
|
|
const std::string cmd = Poco::Path(Util::getApplicationPath(), "coolmount").toString() + ' '
|
|
+ arg + ' ' + source + ' ' + target;
|
|
LOG_TRC("Executing coolmount command: " << cmd);
|
|
return !system(cmd.c_str());
|
|
}
|
|
|
|
bool bind(const std::string& source, const std::string& target)
|
|
{
|
|
LOG_DBG("Mounting [" << source << "] -> [" << target << "].");
|
|
Poco::File(target).createDirectory();
|
|
const bool res = coolmount("-b", source, target);
|
|
if (res)
|
|
LOG_TRC("Bind-mounted [" << source << "] -> [" << target << "].");
|
|
else
|
|
LOG_ERR("Failed to bind-mount [" << source << "] -> [" << target << "].");
|
|
return res;
|
|
}
|
|
|
|
bool remountReadonly(const std::string& source, const std::string& target)
|
|
{
|
|
LOG_DBG("Remounting [" << source << "] -> [" << target << "].");
|
|
Poco::File(target).createDirectory();
|
|
const bool res = coolmount("-r", source, target);
|
|
if (res)
|
|
LOG_TRC("Mounted [" << source << "] -> [" << target << "] readonly.");
|
|
else
|
|
LOG_ERR("Failed to mount [" << source << "] -> [" << target << "] readonly.");
|
|
return res;
|
|
}
|
|
|
|
bool unmount(const std::string& target)
|
|
{
|
|
LOG_DBG("Unmounting [" << target << ']');
|
|
const bool res = coolmount("-u", "", target);
|
|
if (res)
|
|
LOG_TRC("Unmounted [" << target << "] successfully.");
|
|
else
|
|
LOG_ERR("Failed to unmount [" << target << ']');
|
|
return res;
|
|
}
|
|
|
|
// This file signifies that we copied instead of mounted.
|
|
// NOTE: jail cleanup helpers are called from forkit and
|
|
// coolwsd, and they may have bind-mounting enabled, but the
|
|
// kit could have had it removed when falling back to copying.
|
|
// In such cases, we cannot safely know whether the jail was
|
|
// copied or not, since the bind envar will be present and
|
|
// assuming it was mounted, would leak them.
|
|
// Alternatively, if we remove the files when mounted
|
|
// we could destroy systemplate if remounting read-only had
|
|
// failed (and it wasn't owned by root).
|
|
constexpr const char* COPIED_JAIL_MARKER_FILE = "delete.me";
|
|
|
|
void markJailCopied(const std::string& root)
|
|
{
|
|
// The reason we should be able to create this file
|
|
// is because the jail must be writable.
|
|
// Failing this will cause an exception, signaling an error.
|
|
Poco::File(root + '/' + COPIED_JAIL_MARKER_FILE).createFile();
|
|
}
|
|
|
|
bool isJailCopied(const std::string& root)
|
|
{
|
|
// If the marker file exists, the jail was copied.
|
|
FileUtil::Stat delFileStat(root + '/' + COPIED_JAIL_MARKER_FILE);
|
|
return delFileStat.exists();
|
|
}
|
|
|
|
bool safeRemoveDir(const std::string& path)
|
|
{
|
|
// Always unmount, just in case.
|
|
unmount(path);
|
|
|
|
// Regardless of the bind flag, check if the jail is marked as copied.
|
|
const bool copied = isJailCopied(path);
|
|
|
|
// We must be empty if we had mounted.
|
|
if (!copied && JailUtil::isBindMountingEnabled() && !FileUtil::isEmptyDirectory(path))
|
|
{
|
|
LOG_WRN("Path [" << path << "] is not empty. Will not remove it.");
|
|
return false;
|
|
}
|
|
|
|
// Recursively remove if link/copied.
|
|
const bool recursive = copied;
|
|
FileUtil::removeFile(path, recursive);
|
|
return true;
|
|
}
|
|
|
|
void removeJail(const std::string& root)
|
|
{
|
|
LOG_INF("Removing jail [" << root << "].");
|
|
|
|
// Unmount the tmp directory. Don't care if we fail.
|
|
const std::string tmpPath = Poco::Path(root, "tmp").toString();
|
|
#ifdef __FreeBSD__
|
|
unmount(tmpPath + "/dev");
|
|
#endif
|
|
FileUtil::removeFile(tmpPath, true); // Delete tmp contents with prejudice.
|
|
unmount(tmpPath);
|
|
|
|
// Unmount the loTemplate directory.
|
|
//FIXME: technically, the loTemplate directory may have any name.
|
|
unmount(Poco::Path(root, "lo").toString());
|
|
|
|
// Unmount/delete the jail (sysTemplate).
|
|
safeRemoveDir(root);
|
|
}
|
|
|
|
/// This cleans up the jails directories.
|
|
/// Note that we assume the templates are mounted
|
|
/// and we unmount first. This is critical, because
|
|
/// otherwise when mounting is disabled we may
|
|
/// inadvertently delete the contents of the mount-points.
|
|
void cleanupJails(const std::string& root)
|
|
{
|
|
LOG_INF("Cleaning up childroot directory [" << root << "].");
|
|
|
|
FileUtil::Stat stRoot(root);
|
|
if (!stRoot.exists() || !stRoot.isDirectory())
|
|
{
|
|
LOG_TRC("Directory [" << root << "] is not a directory or doesn't exist.");
|
|
return;
|
|
}
|
|
|
|
// FIXME: technically, the loTemplate directory may have any name.
|
|
if (FileUtil::Stat(root + "/lo").exists())
|
|
{
|
|
// This is a jail.
|
|
removeJail(root);
|
|
}
|
|
else
|
|
{
|
|
// Not a jail, recurse. UnitTest creates sub-directories.
|
|
LOG_TRC("Directory [" << root << "] is not a jail, recursing.");
|
|
|
|
std::vector<std::string> jails;
|
|
Poco::File(root).list(jails);
|
|
for (const auto& jail : jails)
|
|
{
|
|
const Poco::Path path(root, jail);
|
|
// Postpone deleting "tmp" directory until we clean all the jails
|
|
// On FreeBSD the "tmp" dir contains a devfs moint point. Normally,
|
|
// it gets unmounted by coolmount during shutdown, but coolmount
|
|
// does nothing if it is called on the non-existing path.
|
|
// Removing this dir there prevents clean unmounting of devfs later.
|
|
if (jail == "tmp")
|
|
continue;
|
|
// Delete tmp and link cache with prejudice.
|
|
if (jail == "linkable")
|
|
FileUtil::removeFile(path.toString(), true);
|
|
else
|
|
cleanupJails(path.toString());
|
|
}
|
|
const Poco::Path tmpPath(root, "tmp");
|
|
FileUtil::removeFile(tmpPath.toString(), true);
|
|
}
|
|
|
|
// Remove empty directories.
|
|
if (FileUtil::isEmptyDirectory(root))
|
|
safeRemoveDir(root);
|
|
else
|
|
LOG_WRN("Jails root directory [" << root << "] is not empty. Will not remove it.");
|
|
}
|
|
|
|
void setupJails(bool bindMount, const std::string& jailRoot, const std::string& sysTemplate)
|
|
{
|
|
// Start with a clean slate.
|
|
cleanupJails(jailRoot);
|
|
Poco::File(jailRoot + JAIL_TMP_INCOMING_PATH).createDirectories();
|
|
|
|
disableBindMounting(); // Clear to avoid surprises.
|
|
|
|
// Try to enable bind-mounting if requested (via config).
|
|
if (bindMount)
|
|
{
|
|
// Test mounting to verify it actually works,
|
|
// as it might not function in some systems.
|
|
const std::string target = Poco::Path(jailRoot, "cool_test_mount").toString();
|
|
if (bind(sysTemplate, target))
|
|
{
|
|
enableBindMounting();
|
|
safeRemoveDir(target);
|
|
LOG_INF("Enabling Bind-Mounting of jail contents for better performance per "
|
|
"mount_jail_tree config in coolwsd.xml.");
|
|
}
|
|
else
|
|
LOG_ERR("Bind-Mounting fails and will be disabled for this run. To disable permanently "
|
|
"set mount_jail_tree config entry in coolwsd.xml to false.");
|
|
}
|
|
else
|
|
LOG_INF("Disabling Bind-Mounting of jail contents per "
|
|
"mount_jail_tree config in coolwsd.xml.");
|
|
}
|
|
|
|
// This is the second stage of setting up /dev/[u]random
|
|
// in the jails. Here we create the random devices in
|
|
// /tmp/dev/ in the jail chroot. See setupRandomDeviceLinks().
|
|
void setupJailDevNodes(const std::string& root)
|
|
{
|
|
if (!FileUtil::isWritable(root))
|
|
{
|
|
LOG_WRN("Path [" << root << "] is read-only. Will not create the random device nodes.");
|
|
return;
|
|
}
|
|
|
|
const auto pathDev = Poco::Path(root, "/dev");
|
|
|
|
try
|
|
{
|
|
// Create the path first.
|
|
Poco::File(pathDev).createDirectory();
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
LOG_ERR("Failed to create [" << pathDev.toString() << "]: " << ex.what());
|
|
return;
|
|
}
|
|
|
|
#ifndef __FreeBSD__
|
|
// Create the urandom and random devices.
|
|
if (!Poco::File(root + "/dev/random").exists())
|
|
{
|
|
LOG_DBG("Making /dev/random node in [" << root << "/dev].");
|
|
if (mknod((root + "/dev/random").c_str(),
|
|
S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH,
|
|
makedev(1, 8))
|
|
!= 0)
|
|
{
|
|
LOG_SYS("mknod(" << root << "/dev/random) failed. Mount must not use nodev flag.");
|
|
}
|
|
}
|
|
|
|
if (!Poco::File(root + "/dev/urandom").exists())
|
|
{
|
|
LOG_DBG("Making /dev/urandom node in [" << root << "/dev].");
|
|
if (mknod((root + "/dev/urandom").c_str(),
|
|
S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH,
|
|
makedev(1, 9))
|
|
!= 0)
|
|
{
|
|
LOG_SYS("mknod(" << root << "/dev/urandom) failed. Mount must not use nodev flag.");
|
|
}
|
|
}
|
|
#else
|
|
if (!Poco::File(root + "/dev/random").exists())
|
|
{
|
|
const bool res = coolmount("-d", "", root + "/dev");
|
|
if (res)
|
|
LOG_TRC("Mounted devfs hierarchy -> [" << root << "/dev].");
|
|
else
|
|
LOG_ERR("Failed to mount devfs -> [" << root << "/dev].");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// The envar name used to control bind-mounting of systemplate/jails.
|
|
constexpr const char* BIND_MOUNTING_ENVAR_NAME = "COOL_BIND_MOUNT";
|
|
|
|
void enableBindMounting()
|
|
{
|
|
// Set the envar to enable.
|
|
setenv(BIND_MOUNTING_ENVAR_NAME, "1", 1);
|
|
}
|
|
|
|
void disableBindMounting()
|
|
{
|
|
// Remove the envar to disable.
|
|
unsetenv(BIND_MOUNTING_ENVAR_NAME);
|
|
}
|
|
|
|
bool isBindMountingEnabled()
|
|
{
|
|
// Check if we have a valid envar set.
|
|
return std::getenv(BIND_MOUNTING_ENVAR_NAME) != nullptr;
|
|
}
|
|
|
|
namespace SysTemplate
|
|
{
|
|
/// The network and other system files we need to keep up-to-date in jails.
|
|
/// These must be up-to-date, as they can change during
|
|
/// the long lifetime of our process. Also, it's unlikely
|
|
/// that systemplate will get re-generated after installation.
|
|
static const auto DynamicFilePaths
|
|
= { "/etc/passwd", "/etc/group", "/etc/host.conf", "/etc/hosts",
|
|
"/etc/nsswitch.conf", "/etc/resolv.conf", "/etc/timezone", "/etc/localtime" };
|
|
|
|
/// Copy (false) by default for KIT_IN_PROCESS.
|
|
static bool LinkDynamicFiles = false;
|
|
|
|
static bool updateDynamicFilesImpl(const std::string& sysTemplate);
|
|
|
|
void setupDynamicFiles(const std::string& sysTemplate)
|
|
{
|
|
LOG_INF("Setting up systemplate dynamic files in [" << sysTemplate << "].");
|
|
|
|
const std::string etcSysTemplatePath = Poco::Path(sysTemplate, "etc").toString();
|
|
LinkDynamicFiles = true; // Prefer linking, unless it fails.
|
|
|
|
if (!updateDynamicFilesImpl(sysTemplate))
|
|
{
|
|
// Can't copy!
|
|
LOG_WRN("Failed to update the dynamic files in ["
|
|
<< sysTemplate
|
|
<< "]. Will disable bind-mounting in this run and clone systemplate into the "
|
|
"jails, which is more resource intensive.");
|
|
disableBindMounting(); // We can't mount from incomplete systemplate.
|
|
LinkDynamicFiles = false;
|
|
}
|
|
|
|
FileUtil::Stat copiedFileStat(Poco::Path(sysTemplate, "etc/copied").toString());
|
|
if (copiedFileStat.exists())
|
|
{
|
|
// At least one file is copied, we must check for changes before each jail setup.
|
|
LinkDynamicFiles = false;
|
|
}
|
|
|
|
LOG_INF("Systemplate dynamic files in ["
|
|
<< sysTemplate << "] "
|
|
<< (LinkDynamicFiles ? "are linked and will remain" : "will be copied to keep them")
|
|
<< " up-to-date.");
|
|
}
|
|
|
|
bool updateDynamicFilesImpl(const std::string& sysTemplate)
|
|
{
|
|
LOG_INF("Updating systemplate dynamic files in [" << sysTemplate << "].");
|
|
for (const auto& dynFilename : DynamicFilePaths)
|
|
{
|
|
if (!FileUtil::Stat(dynFilename).exists())
|
|
{
|
|
LOG_INF("Dynamic file [" << dynFilename
|
|
<< "] does not exist. Some functionality may be affected.");
|
|
continue;
|
|
}
|
|
|
|
const std::string srcFilename = FileUtil::realpath(dynFilename);
|
|
if (srcFilename != dynFilename)
|
|
{
|
|
LOG_TRC("Dynamic file [" << dynFilename << "] points to real path [" << srcFilename
|
|
<< "], which will be used instead.");
|
|
}
|
|
|
|
const Poco::File srcFilePath(srcFilename);
|
|
FileUtil::Stat srcStat(srcFilename);
|
|
if (!srcStat.exists())
|
|
continue;
|
|
|
|
const std::string dstFilename = Poco::Path(sysTemplate, dynFilename).toString();
|
|
FileUtil::Stat dstStat(dstFilename);
|
|
|
|
// Is it outdated?
|
|
if (dstStat.isUpToDate(srcStat))
|
|
{
|
|
LOG_TRC("File [" << dstFilename << "] is already up-to-date.");
|
|
continue;
|
|
}
|
|
|
|
// Check that sysTemplate is in fact writable to avoid predictable errors.
|
|
if (!FileUtil::isWritable(sysTemplate))
|
|
{
|
|
disableBindMounting(); // We can't mount from incomplete systemplate that can't be updated.
|
|
LinkDynamicFiles = false;
|
|
LOG_WRN("The systemplate directory ["
|
|
<< sysTemplate << "] is read-only, and at least [" << dstFilename
|
|
<< "] is out-of-date. Will have to copy sysTemplate to jails. To restore "
|
|
"optimal performance, make sure the files in ["
|
|
<< sysTemplate << "/etc] are up-to-date.");
|
|
return false;
|
|
}
|
|
|
|
LOG_INF("File [" << dstFilename << "] needs to be updated.");
|
|
if (LinkDynamicFiles)
|
|
{
|
|
LOG_INF("Linking [" << srcFilename << "] -> [" << dstFilename << "].");
|
|
|
|
// Link or copy.
|
|
if (link(srcFilename.c_str(), dstFilename.c_str()) == 0)
|
|
continue;
|
|
|
|
// Hard-linking failed, try symbolic linking.
|
|
if (symlink(srcFilename.c_str(), dstFilename.c_str()) == 0)
|
|
continue;
|
|
|
|
const int linkerr = errno;
|
|
|
|
// With parallel tests, another test might have linked already.
|
|
FileUtil::Stat dstStat2(dstFilename);
|
|
if (dstStat2.isUpToDate(srcStat))
|
|
{
|
|
LOG_INF("File [" << dstFilename << "] now seems to be up-to-date.");
|
|
continue;
|
|
}
|
|
|
|
// Failed to link a file. Disable linking and copy instead.
|
|
LOG_WRN("Failed to link ["
|
|
<< srcFilename << "] -> [" << dstFilename << "] (" << strerror(linkerr)
|
|
<< "). Will copy and disable linking dynamic system files in this run.");
|
|
LinkDynamicFiles = false;
|
|
}
|
|
|
|
// Linking failed, just copy.
|
|
if (!LinkDynamicFiles)
|
|
{
|
|
LOG_INF("Copying [" << srcFilename << "] -> [" << dstFilename << "].");
|
|
if (!FileUtil::copyAtomic(srcFilename, dstFilename, true))
|
|
{
|
|
FileUtil::Stat dstStat2(dstFilename); // Stat again.
|
|
if (!dstStat2.isUpToDate(srcStat))
|
|
{
|
|
return false; // No point in trying the remaining files.
|
|
}
|
|
}
|
|
|
|
// Create the 'copied' file so we keep the files up-to-date.
|
|
Poco::File(Poco::Path(sysTemplate, "etc/copied").toString()).createFile();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool updateDynamicFiles(const std::string& sysTemplate)
|
|
{
|
|
// If the files are linked, they are always up-to-date.
|
|
return LinkDynamicFiles ? true : updateDynamicFilesImpl(sysTemplate);
|
|
}
|
|
|
|
void setupRandomDeviceLink(const std::string& sysTemplate, const std::string& name)
|
|
{
|
|
const std::string path = sysTemplate + "/dev/";
|
|
try
|
|
{
|
|
// Create the path first.
|
|
Poco::File(path).createDirectories();
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
LOG_ERR("Failed to create [" << path << "]: " << ex.what());
|
|
return;
|
|
}
|
|
|
|
const std::string linkpath = path + name;
|
|
const std::string target = "../tmp/dev/" + name;
|
|
LOG_DBG("Linking symbolically [" << linkpath << "] to [" << target << "].");
|
|
|
|
const FileUtil::Stat stLink(linkpath, true); // The file is a link.
|
|
if (stLink.exists())
|
|
{
|
|
if (!stLink.isLink())
|
|
LOG_WRN("Random device link [" << linkpath << "] exists but isn't a link.");
|
|
else
|
|
LOG_TRC("Random device link [" << linkpath << "] already exists.");
|
|
|
|
return;
|
|
}
|
|
|
|
if (symlink(target.c_str(), linkpath.c_str()) == -1)
|
|
LOG_SYS("Failed to symlink(\"" << target << "\", \"" << linkpath << "\")");
|
|
}
|
|
|
|
// The random devices are setup in two stages.
|
|
// This is the first stage, where we create symbolic links
|
|
// in sysTemplate/dev/[u]random pointing to ../tmp/dev/[u]random
|
|
// when we setup sysTemplate in forkit.
|
|
// In the second stage, during jail creation, we create the dev
|
|
// nodes in /tmp/dev/[u]random inside the jail chroot.
|
|
void setupRandomDeviceLinks(const std::string& sysTemplate)
|
|
{
|
|
setupRandomDeviceLink(sysTemplate, "random");
|
|
setupRandomDeviceLink(sysTemplate, "urandom");
|
|
}
|
|
|
|
} // namespace SysTemplate
|
|
|
|
} // namespace JailUtil
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|