Makefile.am                                |    4 
 android/lib/src/main/cpp/CMakeLists.txt.in |    1 
 common/Common.hpp                          |    2 
 common/FileUtil.cpp                        |   52 +++-
 common/FileUtil.hpp                        |   43 +++
 common/JailUtil.cpp                        |  364 +++++++++++++++++++++++++++++
 common/JailUtil.hpp                        |   70 +++++
 common/Log.hpp                             |    1 
 common/Session.cpp                         |    1 
 common/SigUtil.cpp                         |    1 
 common/Util.cpp                            |   26 +-
 common/Util.hpp                            |   23 +
 common/security.h                          |    1 
 configure.ac                               |    2 
 debian/loolwsd.postinst.in                 |    1 
 docker/Ubuntu                              |    1 
 kit/ForKit.cpp                             |   21 +
 kit/Kit.cpp                                |  199 ++++++---------
 loolwsd-systemplate-setup                  |    5 
 loolwsd.spec.in                            |    2 
 loolwsd.xml.in                             |    1 
 net/Socket.cpp                             |    1 
 net/Socket.hpp                             |    1 
 test/run_unit.sh.in                        |    2 
 tools/mount.cpp                            |  124 +++++++++
 wsd/LOOLWSD.cpp                            |   89 +++----
 wsd/Storage.hpp                            |    4 
 27 files changed, 836 insertions(+), 206 deletions(-)

New commits:
commit 5c9988f2e345ca82e7bb5f5e9bf66a30b82a0446
Author:     Ashod Nakashian <ashod.nakash...@collabora.co.uk>
AuthorDate: Thu Apr 9 09:02:58 2020 -0400
Commit:     Ashod Nakashian <ashnak...@gmail.com>
CommitDate: Wed Jul 1 05:42:43 2020 +0200

    wsd: faster jail setup via bind-mount
    
    loolmount now works and supports mounting and
    unmounting, plus numerous improvements,
    refactoring, logging, etc..  When enabled,
    binding improves the jail setup time by anywhere
    from 2x to orders of magnitude (in docker, f.e.).
    
    A new config entry mount_jail_tree controls
    whether mounting is used or the old method of
    linking/copying of jail contents. It is set to
    true by default and falls back to linking/copying.
    A test mount is done when the setting is enabled,
    and if mounting fails, it's disabled to avoid noise.
    
    Temporarily disabled for unit-tests until we can
    cleanup lingering mounts after Jenkins aborts our
    build job. In a future patch we will have mount/jail
    cleanup as part of make.
    
    The network/system files in /etc that need frequent
    refreshing are now updated in systemplate to make
    their most recent version available in the jails.
    These files can change during the course of loolwsd
    lifetime, and are unlikely to be updated in
    systemplate after installation at all. We link to
    them in the systemplate/etc directory, and if that
    fails, we copy them before forking each kit
    instance to have the latest.
    
    This reworks the approach used to bind-mount the
    jails and the templates such that the total is
    now down to only three mounts: systemplate, lo, tmp.
    
    As now systemplate and lotemplate are shared, they
    must be mounted as readonly, this means that user/
    must now be moved into tmp/user/ which is writable.
    
    The mount-points must be recursive, because we mount
    lo/ within the mount-point of systemplate (which is
    the root of the jail). But because we (re)bind
    recursively, and because both systemplate and
    lotemplate are mounted for each jails, we need to
    make them unbindable, so they wouldn't multiply the
    mount-points for each jails (an explosive growth!)
    Contrarywise, we don't want the mount-points to
    be shared, because we don't expect to add/remove
    mounts after a jail is created.
    
    The random temp directory is now created and set
    correctly, plus many logging and other improvements.
    
    Change-Id: Iae3fda5e876cf47d2cae6669a87b5b826a8748df
    Reviewed-on: https://gerrit.libreoffice.org/c/online/+/92829
    Tested-by: Jenkins
    Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com>
    Reviewed-by: Ashod Nakashian <ashnak...@gmail.com>

diff --git a/Makefile.am b/Makefile.am
index 171a07e36..d3f18e6a3 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -16,6 +16,7 @@ export ENABLE_DEBUG
 
 bin_PROGRAMS = \
        loolforkit \
+       loolmount \
        loolconvert loolconfig
 
 if ENABLE_LIBFUZZER
@@ -87,6 +88,7 @@ AM_ETAGSFLAGS = --c++-kinds=+p --fields=+iaS --extra=+q -R 
--totals=yes --exclud
 AM_CTAGSFLAGS = $(AM_ETAGSFLAGS)
 
 shared_sources = common/FileUtil.cpp \
+                 common/JailUtil.cpp \
                  common/Log.cpp \
                  common/Protocol.cpp \
                  common/StringVector.cpp \
@@ -131,7 +133,6 @@ noinst_PROGRAMS = clientnb \
                   lokitclient \
                   loolmap \
                   loolstress \
-                  loolmount \
                   loolsocketdump
 
 if ENABLE_LIBFUZZER
@@ -239,6 +240,7 @@ shared_headers = common/Common.hpp \
                  common/Crypto.hpp \
                  common/JsonUtil.hpp \
                  common/FileUtil.hpp \
+                 common/JailUtil.hpp \
                  common/Log.hpp \
                  common/LOOLWebSocket.hpp \
                  common/Protocol.hpp \
diff --git a/android/lib/src/main/cpp/CMakeLists.txt.in 
b/android/lib/src/main/cpp/CMakeLists.txt.in
index 7b1dc9202..56b5a8a52 100644
--- a/android/lib/src/main/cpp/CMakeLists.txt.in
+++ b/android/lib/src/main/cpp/CMakeLists.txt.in
@@ -4,6 +4,7 @@ cmake_minimum_required(VERSION 3.4.1)
 add_library(androidapp SHARED
             androidapp.cpp
             ../../../../../common/FileUtil.cpp
+            ../../../../../common/JailUtil.cpp
             ../../../../../common/Log.cpp
             ../../../../../common/MessageQueue.cpp
             ../../../../../common/Protocol.cpp
diff --git a/common/Common.hpp b/common/Common.hpp
index b12cc67b5..d2cc933d6 100644
--- a/common/Common.hpp
+++ b/common/Common.hpp
@@ -32,7 +32,7 @@ constexpr long READ_BUFFER_SIZE = 64 * 1024;
 /// or as intentionally flooding the server.
 constexpr int MAX_MESSAGE_SIZE = 2 * 1024 * READ_BUFFER_SIZE;
 
-constexpr const char JAILED_DOCUMENT_ROOT[] = "/user/docs/";
+constexpr const char JAILED_DOCUMENT_ROOT[] = "/tmp/user/docs/";
 constexpr const char CHILD_URI[] = "/loolws/child?";
 constexpr const char NEW_CHILD_URI[] = "/loolws/newchild";
 constexpr const char LO_JAIL_SUBPATH[] = "lo";
diff --git a/common/FileUtil.cpp b/common/FileUtil.cpp
index 4d8ac0a56..ac980b5f3 100644
--- a/common/FileUtil.cpp
+++ b/common/FileUtil.cpp
@@ -11,8 +11,8 @@
 
 #include "FileUtil.hpp"
 
+#include <dirent.h>
 #include <ftw.h>
-#include <sys/stat.h>
 #ifdef __linux
 #include <sys/vfs.h>
 #elif defined IOS
@@ -39,6 +39,9 @@ namespace filesystem = ::std::filesystem;
 # include <Poco/TemporaryFile.h>
 #endif
 
+#include <Poco/File.h>
+#include <Poco/Path.h>
+
 #include "Log.hpp"
 #include "Util.hpp"
 #include "Unit.hpp"
@@ -196,6 +199,8 @@ namespace FileUtil
 
     void removeFile(const std::string& path, const bool recursive)
     {
+        LOG_DBG("Removing [" << path << "] " << (recursive ? "recursively." : 
"only."));
+
 // Amazingly filesystem::remove_all silently fails to work on some
 // systems. No real need to be using experimental API here either.
 #if 0 // HAVE_STD_FILESYSTEM
@@ -211,10 +216,12 @@ namespace FileUtil
         try
         {
             struct stat sb;
+            errno = 0;
             if (!recursive || stat(path.c_str(), &sb) == -1 || 
S_ISREG(sb.st_mode))
             {
-                // Non-recursive directories, and files.
-                Poco::File(path).remove(recursive);
+                // Non-recursive directories and files that exist.
+                if (errno != ENOENT)
+                    Poco::File(path).remove(recursive);
             }
             else
             {
@@ -225,12 +232,12 @@ namespace FileUtil
         catch (const std::exception&e)
         {
             // Already removed or we don't care about failures.
-            LOG_DBG("Exception removing " << path << ' ' << recursive << " : " 
<< e.what());
+            LOG_DBG("Failed to remove [" << path << "] " << (recursive ? 
"recursively: " : "only: ")
+                                         << e.what());
         }
 #endif
     }
 
-
 } // namespace FileUtil
 
 namespace
@@ -385,6 +392,41 @@ namespace FileUtil
         return AnonymizeUserData ? Util::anonymize(username, 
AnonymizationSalt) : username;
     }
 
+    bool isEmptyDirectory(const char* path)
+    {
+        DIR* dir = opendir(path);
+        if (dir == nullptr)
+            return errno != EACCES; // Assume it's not empty when EACCES.
+
+        int count = 0;
+        while (readdir(dir) && ++count < 3)
+            ;
+
+        closedir(dir);
+        return count <= 2; // Discounting . and ..
+    }
+
+    bool linkOrCopyFile(const char* source, const char* target)
+    {
+        if (link(source, target) == -1)
+        {
+            LOG_INF("link(\"" << source << "\", \"" << target << "\") failed: 
" << strerror(errno)
+                              << ". Will copy.");
+            try
+            {
+                Poco::File(source).copyTo(target);
+            }
+            catch (const std::exception& exc)
+            {
+                LOG_ERR("Copying of [" << source << "] to [" << target
+                                       << "] failed: " << exc.what());
+                return false;
+            }
+        }
+
+        return true;
+    }
+
 } // namespace FileUtil
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/common/FileUtil.hpp b/common/FileUtil.hpp
index a64ff9758..1ef60a843 100644
--- a/common/FileUtil.hpp
+++ b/common/FileUtil.hpp
@@ -9,9 +9,10 @@
 
 #pragma once
 
+#include <cerrno>
 #include <string>
+#include <sys/stat.h>
 
-#include <Poco/File.h>
 #include <Poco/Path.h>
 
 namespace FileUtil
@@ -65,6 +66,10 @@ namespace FileUtil
         removeFile(path.toString(), recursive);
     }
 
+    /// Returns true iff the directory is empty (or doesn't exist).
+    bool isEmptyDirectory(const char* path);
+    inline bool isEmptyDirectory(const std::string& path) { return 
isEmptyDirectory(path.c_str()); }
+
     /// Copy a file from @fromPath to @toPath, throws on failure.
     void copyFileTo(const std::string &fromPath, const std::string &toPath);
 
@@ -81,6 +86,42 @@ namespace FileUtil
         return getTempFilePath(srcDir, srcFilename, std::string());
     }
 
+    /// Link source to target, and copy if linking fails.
+    bool linkOrCopyFile(const char* source, const char* target);
+
+    /// File/Directory stat helper.
+    class Stat
+    {
+    public:
+        /// Stat the given path. Symbolic links are stats when @link is true.
+        Stat(const std::string& file, bool link = false)
+            : _path(file)
+            , _res(link ? lstat(file.c_str(), &_sb) : stat(file.c_str(), &_sb))
+            , _errno(errno)
+        {
+        }
+
+        bool good() const { return _res == 0; }
+        bool bad() const { return !good(); }
+        bool erno() const { return _errno; }
+        const struct ::stat& sb() const { return _sb; }
+
+        const std::string path() const { return _path; }
+
+        bool isDirectory() const { return S_ISDIR(_sb.st_mode); }
+        bool isFile() const { return S_ISREG(_sb.st_mode); }
+        bool isLink() const { return S_ISLNK(_sb.st_mode); }
+
+        /// Returns true iff the path exists, regarlesss of access permission.
+        bool exists() const { return good() || (_errno != ENOENT && _errno != 
ENOTDIR); }
+
+    private:
+        const std::string _path;
+        struct ::stat _sb;
+        const int _res;
+        const int _errno;
+    };
+
 } // end namespace FileUtil
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/common/JailUtil.cpp b/common/JailUtil.cpp
new file mode 100644
index 000000000..95927c476
--- /dev/null
+++ b/common/JailUtil.cpp
@@ -0,0 +1,364 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * 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 loolmount(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(), 
"loolmount").toString() + ' '
+                            + arg + ' ' + source + ' ' + target;
+    LOG_TRC("Executing loolmount command: " << cmd);
+    return !system(cmd.c_str());
+}
+
+bool bind(const std::string& source, const std::string& target)
+{
+    Poco::File(target).createDirectory();
+    const bool res = loolmount("-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)
+{
+    Poco::File(target).createDirectory();
+    const bool res = loolmount("-r", source, target);
+    if (res)
+        LOG_TRC("Mounted [" << source << "] -> [" << target << "].");
+    else
+        LOG_ERR("Failed to mount [" << source << "] -> [" << target << "].");
+    return res;
+}
+
+bool unmount(const std::string& target)
+{
+    LOG_DBG("Unmounting [" << target << "].");
+    const bool res = loolmount("-u", "", target);
+    if (res)
+        LOG_TRC("Unmounted [" << target << "] successfully.");
+    else
+        LOG_ERR("Failed to unmount [" << target << "].");
+    return res;
+}
+
+bool safeRemoveDir(const std::string& path)
+{
+    unmount(path);
+
+    static const bool bind = std::getenv("LOOL_BIND_MOUNT");
+
+    // We must be empty if we had mounted.
+    if (bind && !FileUtil::isEmptyDirectory(path))
+    {
+        LOG_WRN("Path [" << path << "] is not empty. Will not remove it.");
+        return false;
+    }
+
+    // Recursively remove if link/copied.
+    FileUtil::removeFile(path, !bind);
+    return true;
+}
+
+void removeJail(const std::string& path)
+{
+    LOG_INF("Removing jail [" << path << "].");
+
+    // Unmount the tmp directory. Don't care if we fail.
+    const std::string tmpPath = Poco::Path(path, "tmp").toString();
+    FileUtil::removeFile(tmpPath, true); // Delete tmp contents with prejeduce.
+    unmount(tmpPath);
+
+    // Unmount the loTemplate directory.
+    unmount(Poco::Path(path, "lo").toString());
+
+    // Unmount the jail (sysTemplate).
+    safeRemoveDir(path);
+}
+
+/// 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;
+    }
+
+    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);
+            if (jail == "tmp") // Delete tmp with prejeduce.
+                FileUtil::removeFile(path.toString(), true);
+            else
+                cleanupJails(path.toString());
+        }
+    }
+
+    // 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).createDirectories();
+
+    unsetenv("LOOL_BIND_MOUNT"); // Clear to avoid surprises.
+    if (bindMount)
+    {
+        // Test mounting to verify it actually works,
+        // as it might not function in some systems.
+        const std::string target = Poco::Path(jailRoot, 
"lool_test_mount").toString();
+        if (bind(sysTemplate, target))
+        {
+            safeRemoveDir(target);
+            setenv("LOOL_BIND_MOUNT", "1", 1);
+            LOG_INF("Enabling Bind-Mounting of jail contents for better 
performance per "
+                    "mount_jail_tree config in loolwsd.xml.");
+        }
+        else
+            LOG_ERR("Bind-Mounting fails and will be disabled for this run. To 
disable permanently "
+                    "set mount_jail_tree config entry in loolwsd.xml to 
false.");
+    }
+    else
+        LOG_INF("Disabling Bind-Mounting of jail contents per "
+                "mount_jail_tree config in loolwsd.xml.");
+}
+
+void symlinkPathToJail(const std::string& sysTemplate, const std::string& 
loTemplate,
+                       const std::string& loSubPath)
+{
+    std::string symlinkTarget;
+    for (int i = 0; i < Poco::Path(loTemplate).depth(); i++)
+        symlinkTarget += "../";
+    symlinkTarget += loSubPath;
+
+    const Poco::Path symlinkSourcePath(sysTemplate + '/' + loTemplate);
+    const std::string symlinkSource = symlinkSourcePath.toString();
+    Poco::File(symlinkSourcePath.parent()).createDirectories();
+
+    LOG_DBG("Linking symbolically [" << symlinkSource << "] to [" << 
symlinkTarget << "].");
+
+    const FileUtil::Stat stLink(symlinkSource, true); // The file is a link.
+    if (stLink.exists())
+    {
+        if (!stLink.isLink())
+            LOG_WRN("Link [" << symlinkSource << "] already exists but isn't a 
link.");
+        else
+            LOG_TRC("Link [" << symlinkSource << "] already exists, skipping 
linking.");
+
+        return;
+    }
+
+    if (symlink(symlinkTarget.c_str(), symlinkSource.c_str()) == -1)
+        LOG_SYS("Failed to symlink(\"" << symlinkTarget << "\", \"" << 
symlinkSource << "\")");
+}
+
+// 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)
+{
+    // Create the urandom and random devices
+    Poco::File(Poco::Path(root, "/dev")).createDirectory();
+    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.");
+        }
+    }
+}
+
+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" };
+
+/// Copy by default for KIT_IN_PROCESS.
+static bool LinkDynamicFiles = false;
+
+void setupDynamicFiles(const std::string& sysTemplate)
+{
+    LOG_INF("Setting up dynamic files in sysTemplate.");
+
+    const std::string etcSysTemplatePath = Poco::Path(sysTemplate, 
"etc").toString();
+    LinkDynamicFiles = true;
+    for (const auto& srcFilename : DynamicFilePaths)
+    {
+        const Poco::File srcFilePath(srcFilename);
+        if (!srcFilePath.exists())
+            continue;
+
+        // Remove the file to create a symlink.
+        const Poco::Path dstFilePath(sysTemplate, srcFilename);
+        if (LinkDynamicFiles)
+        {
+            LOG_INF("Linking [" << srcFilename << "] -> [" << 
dstFilePath.toString() << "].");
+            FileUtil::removeFile(dstFilePath);
+
+            // Link or copy.
+            if (link(srcFilename, dstFilePath.toString().c_str()) != -1)
+                continue;
+
+            // Failed to link a file. Disable linking and copy instead.
+            LOG_WRN("Failed to link [" << srcFilename << "] -> [" << 
dstFilePath.toString() << "] ("
+                                       << strerror(errno) << "). Will copy.");
+            LinkDynamicFiles = false;
+        }
+
+        // Linking fails, just copy.
+        LOG_INF("Copying [" << srcFilename << "] -> [" << 
dstFilePath.toString() << "].");
+        srcFilePath.copyTo(etcSysTemplatePath);
+    }
+}
+
+void updateDynamicFiles(const std::string& sysTemplate)
+{
+    if (!LinkDynamicFiles)
+    {
+        LOG_INF("Updating dynamic files in sysTemplate.");
+
+        const std::string etcSysTemplatePath = Poco::Path(sysTemplate, 
"etc").toString();
+        for (const auto& srcFilename : DynamicFilePaths)
+        {
+            const Poco::File srcFilePath(srcFilename);
+            if (!srcFilePath.exists())
+                continue;
+
+            const Poco::Path dstFilePath(sysTemplate, srcFilename);
+            LOG_DBG("Copying [" << srcFilename << "] -> [" << 
dstFilePath.toString() << "].");
+            srcFilePath.copyTo(etcSysTemplatePath);
+        }
+    }
+}
+
+void setupLoSymlink(const std::string& sysTemplate, const std::string& 
loTemplate,
+                    const std::string& loSubPath)
+{
+    symlinkPathToJail(sysTemplate, loTemplate, loSubPath);
+
+    // Font paths can end up as realpaths so match that too.
+    char* resolved = realpath(loTemplate.c_str(), nullptr);
+    if (resolved)
+    {
+        if (strcmp(loTemplate.c_str(), resolved) != 0)
+            symlinkPathToJail(sysTemplate, std::string(resolved), loSubPath);
+        free(resolved);
+    }
+}
+
+void setupRandomDeviceLink(const std::string& sysTemplate, const std::string& 
name)
+{
+    const std::string path = sysTemplate + "/dev/";
+    Poco::File(path).createDirectories();
+
+    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: */
diff --git a/common/JailUtil.hpp b/common/JailUtil.hpp
new file mode 100644
index 000000000..d783b53d3
--- /dev/null
+++ b/common/JailUtil.hpp
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; 
fill-column: 100 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * 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/.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <Poco/File.h>
+#include <Poco/Path.h>
+
+namespace JailUtil
+{
+/// Bind mount a jail directory.
+bool bind(const std::string& source, const std::string& target);
+
+/// Remount a bound mount point as readonly.
+bool remountReadonly(const std::string& source, const std::string& target);
+
+/// Unmount a bind-mounted jail directory.
+bool unmount(const std::string& target);
+
+/// Remove the jail directory and all its contents.
+void removeJail(const std::string& path);
+
+/// Remove all the jails given their paths.
+inline void removeJails(const std::vector<std::string>& jails)
+{
+    for (const auto& path : jails)
+    {
+        removeJail(path);
+    }
+}
+
+/// Remove all jails.
+void cleanupJails(const std::string& jailRoot);
+
+/// Setup the jails.
+void setupJails(bool bindMount, const std::string& jailRoot, const 
std::string& sysTemplate);
+
+/// Setup /dev/random and /dev/urandom in the given jail path.
+void setupJailDevNodes(const std::string& root);
+
+namespace SysTemplate
+{
+/// Create a symlink inside the jailPath so that the absolute pathname 
loTemplate, when
+/// interpreted inside a chroot at jailPath, points to loSubPath (relative to 
the chroot).
+void setupLoSymlink(const std::string& sysTemplate, const std::string& 
loTemplate,
+                    const std::string& loSubPath);
+
+/// Setup links for /dev/random and /dev/urandom in systemplate.
+void setupRandomDeviceLinks(const std::string& root);
+
+/// Setup of the dynamic files within the sysTemplate by either
+/// copying or linking. See updateJail_DynamicFilesInSysTemplate.
+void setupDynamicFiles(const std::string& sysTemplate);
+
+/// Update the dynamic files within the sysTemplate before each child fork.
+void updateDynamicFiles(const std::string& sysTemplate);
+
+} // namespace SysTemplate
+
+} // end namespace JailUtil
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/common/Log.hpp b/common/Log.hpp
index 604c66ac7..b2bb43860 100644
--- a/common/Log.hpp
+++ b/common/Log.hpp
@@ -12,6 +12,7 @@
 #include <sys/syscall.h>
 #include <unistd.h>
 
+#include <cerrno>
 #include <cstddef>
 #include <functional>
 #include <iostream>
diff --git a/common/Session.cpp b/common/Session.cpp
index 204573e0f..cdccea9d6 100644
--- a/common/Session.cpp
+++ b/common/Session.cpp
@@ -11,7 +11,6 @@
 
 #include "Session.hpp"
 
-#include <sys/stat.h>
 #include <sys/types.h>
 #include <ftw.h>
 #include <utime.h>
diff --git a/common/SigUtil.cpp b/common/SigUtil.cpp
index dff88b8a6..2bc3d5aa7 100644
--- a/common/SigUtil.cpp
+++ b/common/SigUtil.cpp
@@ -16,7 +16,6 @@
 #endif
 #include <csignal>
 #include <sys/poll.h>
-#include <sys/stat.h>
 #include <sys/uio.h>
 #include <unistd.h>
 
diff --git a/common/Util.cpp b/common/Util.cpp
index ee1aa19b1..16681cc56 100644
--- a/common/Util.cpp
+++ b/common/Util.cpp
@@ -166,14 +166,15 @@ namespace Util
         return tmp;
     }
 
-    std::string createRandomTmpDir()
+    std::string createRandomTmpDir(std::string root)
     {
-        std::string defaultTmp = getDefaultTmpDir();
-        std::string newTmp =
-            defaultTmp + "/lool-" + rng::getFilename(16);
-        if (::mkdir(newTmp.c_str(), S_IRWXU) < 0) {
-            LOG_ERR("Failed to create random temp directory");
-            return defaultTmp;
+        if (root.empty())
+            root = getDefaultTmpDir();
+        const std::string newTmp = root + "/lool-" + rng::getFilename(16);
+        if (::mkdir(newTmp.c_str(), S_IRWXU) < 0)
+        {
+            LOG_SYS("Failed to create random temp directory [" << newTmp << 
"]");
+            return root;
         }
         return newTmp;
     }
@@ -966,6 +967,17 @@ namespace Util
         return result;
     }
 
+    static std::string ApplicationPath;
+    void setApplicationPath(const std::string& path)
+    {
+        ApplicationPath = Poco::Path(path).absolute().toString();
+    }
+
+    std::string getApplicationPath()
+    {
+        return ApplicationPath;
+    }
+
     #if !MOBILEAPP
         // If OS is not mobile, it must be Linux.
         std::string getLinuxVersion(){
diff --git a/common/Util.hpp b/common/Util.hpp
index f6ca50825..ba70cba47 100644
--- a/common/Util.hpp
+++ b/common/Util.hpp
@@ -60,8 +60,9 @@ namespace Util
         std::string getFilename(const size_t length);
     }
 
-    /// Create randomized temporary directory
-    std::string createRandomTmpDir();
+    /// Create randomized temporary directory in the root provided.
+    /// If root is empty, the current temp directory is used.
+    std::string createRandomTmpDir(std::string root = std::string());
 
 #if !MOBILEAPP
     /// Get number of threads in this process or -1 on error
@@ -319,6 +320,21 @@ namespace Util
         return s;
     }
 
+    inline std::string& trim(std::string& s, const char ch)
+    {
+        const size_t last = s.find_last_not_of(ch);
+        if (last != std::string::npos)
+        {
+            s = s.substr(0, last + 1);
+        }
+        else
+        {
+            s.clear();
+        }
+
+        return s;
+    }
+
     /// Trim spaces from both left and right. Just spaces.
     inline std::string& trim(std::string& s)
     {
@@ -1104,6 +1120,9 @@ int main(int argc, char**argv)
         return result;
     }
 
+    void setApplicationPath(const std::string& path);
+    std::string getApplicationPath();
+
     /**
      * Converts vector of strings to map. Strings should have formed like 
this: key + delimiter + value.
      * In case of a misformed string or zero length vector, passes that item 
and warns the developer.
diff --git a/common/security.h b/common/security.h
index 0fd0a691b..0bd794d2b 100644
--- a/common/security.h
+++ b/common/security.h
@@ -12,7 +12,6 @@
 
 #pragma once
 
-#include <sys/mount.h>
 #include <sys/types.h>
 
 #include <pwd.h>
diff --git a/configure.ac b/configure.ac
index 7a8177e91..740dc2e3c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1140,7 +1140,7 @@ if test "$enable_androidapp" = "yes"; then
                  
$srcdir/android/lib/src/main/cpp/CMakeLists.txt:android/lib/src/main/cpp/CMakeLists.txt.in])
 fi
 
-AC_CONFIG_FILES([test/run_unit.sh],[chmod +x test/run_unit.sh])
+AC_CONFIG_FILES([test/run_unit.sh:test/run_unit.sh.in],[chmod +x 
test/run_unit.sh])
 
 AC_OUTPUT
 
diff --git a/debian/loolwsd.postinst.in b/debian/loolwsd.postinst.in
index 41198e75a..ac38e2ab1 100644
--- a/debian/loolwsd.postinst.in
+++ b/debian/loolwsd.postinst.in
@@ -5,6 +5,7 @@ set -e
 case "$1" in
     configure)
        setcap cap_fowner,cap_mknod,cap_sys_chroot=ep /usr/bin/loolforkit || 
true
+       setcap cap_sys_admin=ep /usr/bin/loolmount || true
 
        adduser --quiet --system --group --home /opt/lool lool
        mkdir -p /var/cache/loolwsd && chown lool: /var/cache/loolwsd
diff --git a/docker/Ubuntu b/docker/Ubuntu
index 4d00a16bb..a41d38bba 100644
--- a/docker/Ubuntu
+++ b/docker/Ubuntu
@@ -29,6 +29,7 @@ COPY /scripts/run-lool.sh /
 # set up LibreOffice Online (normally done by postinstall script of package)
 # Fix permissions
 RUN setcap cap_fowner,cap_mknod,cap_sys_chroot=ep /usr/bin/loolforkit && \
+    setcap cap_sys_admin=ep /usr/bin/loolmount && \
     adduser --quiet --system --group --home /opt/lool lool && \
     mkdir -p /var/cache/loolwsd && chown lool: /var/cache/loolwsd && \
     rm -rf /var/cache/loolwsd/* && \
diff --git a/kit/ForKit.cpp b/kit/ForKit.cpp
index b69e2593b..d1ecb1cdd 100644
--- a/kit/ForKit.cpp
+++ b/kit/ForKit.cpp
@@ -14,7 +14,6 @@
 #include <config.h>
 
 #include <sys/capability.h>
-#include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <sysexits.h>
@@ -40,6 +39,7 @@
 #endif
 
 #include <common/FileUtil.hpp>
+#include <common/JailUtil.hpp>
 #include <common/Seccomp.hpp>
 #include <common/SigUtil.hpp>
 #include <security.h>
@@ -280,11 +280,7 @@ static void cleanupChildren()
     }
 
     // Now delete the jails.
-    for (const auto& path : jails)
-    {
-        LOG_INF("Removing jail [" << path << "].");
-        FileUtil::removeFile(path, true);
-    }
+    JailUtil::removeJails(jails);
 }
 
 static int createLibreOfficeKit(const std::string& childRoot,
@@ -296,6 +292,9 @@ static int createLibreOfficeKit(const std::string& 
childRoot,
     // Generate a jail ID to be used for in the jail path.
     const std::string jailId = Util::rng::getFilename(16);
 
+    // Update the dynamic files as necessary.
+    JailUtil::SysTemplate::updateDynamicFiles(sysTemplate);
+
     // Used to label the spare kit instances
     static size_t spareKitId = 0;
     ++spareKitId;
@@ -433,6 +432,7 @@ int main(int argc, char** argv)
 #endif
 
     Util::setThreadName("forkit");
+    Util::setApplicationPath(Poco::Path(argv[0]).parent().toString());
 
     // Initialization
     const bool logToFile = std::getenv("LOOL_LOGFILE");
@@ -587,6 +587,15 @@ int main(int argc, char** argv)
     if (Util::getProcessThreadCount() != 1)
         LOG_ERR("Error: forkit has more than a single thread after pre-init");
 
+    // Link the network and system files in sysTemplate.
+    JailUtil::SysTemplate::setupDynamicFiles(sysTemplate);
+
+    // Make the real lo path in the chroot point to the chroot lo/.
+    JailUtil::SysTemplate::setupLoSymlink(sysTemplate, loTemplate, loSubPath);
+
+    // Make dev/[u]random point to the writable devices in tmp/dev/.
+    JailUtil::SysTemplate::setupRandomDeviceLinks(sysTemplate);
+
     LOG_INF("Preinit stage OK.");
 
     // We must have at least one child, more are created dynamically.
diff --git a/kit/Kit.cpp b/kit/Kit.cpp
index b50ae65a4..94241417a 100644
--- a/kit/Kit.cpp
+++ b/kit/Kit.cpp
@@ -54,6 +54,7 @@
 #include <Common.hpp>
 #include <MobileApp.hpp>
 #include <FileUtil.hpp>
+#include <common/JailUtil.hpp>
 #include "KitHelper.hpp"
 #include "Kit.hpp"
 #include <Protocol.hpp>
@@ -123,7 +124,11 @@ static LokHookFunction2* initFunction = nullptr;
 namespace
 {
 #ifndef BUILDING_TESTS
-    enum class LinkOrCopyType { All, LO, NoUsr };
+    enum class LinkOrCopyType
+    {
+        All,
+        LO
+    };
     LinkOrCopyType linkOrCopyType;
     std::string sourceForLinkOrCopy;
     Path destinationForLinkOrCopy;
@@ -137,15 +142,13 @@ namespace
     {
         switch (type)
         {
-        case LinkOrCopyType::NoUsr:
-            return "non-user";
-        case LinkOrCopyType::LO:
-            return "LibreOffice";
-        case LinkOrCopyType::All:
-            return "all";
-        default:
-            assert(!"Unknown LinkOrCopyType.");
-            return "unknown";
+            case LinkOrCopyType::LO:
+                return "LibreOffice";
+            case LinkOrCopyType::All:
+                return "all";
+            default:
+                assert(!"Unknown LinkOrCopyType.");
+                return "unknown";
         }
     }
 
@@ -153,9 +156,6 @@ namespace
     {
         switch (linkOrCopyType)
         {
-        case LinkOrCopyType::NoUsr:
-            // bind mounted.
-            return strcmp(path,"usr") != 0;
         case LinkOrCopyType::LO:
             return
                 strcmp(path, "program/wizards") != 0 &&
@@ -164,7 +164,8 @@ namespace
                 strcmp(path, "share/basic") != 0 &&
                 strcmp(path, "share/Scripts/java") != 0 &&
                 strcmp(path, "share/Scripts/javascript") != 0 &&
-                strcmp(path, "share/config/wizard") != 0;
+                strcmp(path, "share/config/wizard") != 0 &&
+                strcmp(path, "readmes") != 0;
         default: // LinkOrCopyType::All
             return true;
         }
@@ -215,33 +216,22 @@ namespace
             }
             return true;
         }
-        case LinkOrCopyType::NoUsr:
         default: // LinkOrCopyType::All
             return true;
         }
     }
 
-    void linkOrCopyFile(const char *fpath, const Path& newPath)
+    void linkOrCopyFile(const char* fpath, const std::string& newPath)
     {
         ++linkOrCopyFileCount;
         if (linkOrCopyVerboseLogging)
-            LOG_INF("Linking file \"" << fpath << "\" to \"" << 
newPath.toString() << '"');
+            LOG_INF("Linking file \"" << fpath << "\" to \"" << newPath << 
'"');
 
-        if (link(fpath, newPath.toString().c_str()) == -1)
+        if (!FileUtil::linkOrCopyFile(fpath, newPath.c_str()))
         {
-            LOG_INF("link(\"" << fpath << "\", \"" <<
-                    newPath.toString() << "\") failed: " << strerror(errno) << 
". Will copy.");
-            try
-            {
-                File(fpath).copyTo(newPath.toString());
-            }
-            catch (const std::exception& exc)
-            {
-                LOG_FTL("Copying of '" << fpath << "' to " << 
newPath.toString() <<
-                        " failed: " << exc.what() << ". Exiting.");
-                Log::shutdown();
-                std::_Exit(EX_SOFTWARE);
-            }
+            LOG_FTL("Failed to copy or link [" << fpath << "] to [" << newPath 
<< "]. Exiting.");
+            Log::shutdown();
+            std::_Exit(EX_SOFTWARE);
         }
     }
 
@@ -271,7 +261,7 @@ namespace
 
         assert(fpath[strlen(sourceForLinkOrCopy.c_str())] == '/');
         const char *relativeOldPath = fpath + 
strlen(sourceForLinkOrCopy.c_str()) + 1;
-        Path newPath(destinationForLinkOrCopy, Path(relativeOldPath));
+        const Path newPath(destinationForLinkOrCopy, Path(relativeOldPath));
 
         switch (typeflag)
         {
@@ -280,7 +270,7 @@ namespace
             File(newPath.parent()).createDirectories();
 
             if (shouldLinkFile(relativeOldPath))
-                linkOrCopyFile(fpath, newPath);
+                linkOrCopyFile(fpath, newPath.toString());
             break;
         case FTW_D:
             {
@@ -424,26 +414,6 @@ namespace
 
         cap_free(caps);
     }
-
-    void symlinkPathToJail(const Path& jailPath, const std::string &loTemplate,
-                           const std::string &loSubPath)
-    {
-        Path symlinkSource(jailPath, Path(loTemplate.substr(1)));
-        File(symlinkSource.parent()).createDirectories();
-
-        std::string symlinkTarget;
-        for (int i = 0; i < Path(loTemplate).depth(); i++)
-            symlinkTarget += "../";
-        symlinkTarget += loSubPath;
-
-        LOG_DBG("symlink(\"" << symlinkTarget << "\", \"" << 
symlinkSource.toString() << "\")");
-        if (symlink(symlinkTarget.c_str(), symlinkSource.toString().c_str()) 
== -1)
-        {
-            LOG_SYS("symlink(\"" << symlinkTarget << "\", \"" << 
symlinkSource.toString()
-                                 << "\") failed");
-            throw Exception("symlink() failed");
-        }
-    }
 #endif
 }
 
@@ -2618,89 +2588,82 @@ void lokit_main(
     // framework/source/services/modulemanager.cxx:198
     // So we insure it lives until std::_Exit is called.
     std::shared_ptr<lok::Office> loKit;
-    Path jailPath;
     ChildSession::NoCapsForKit = noCapabilities;
 #endif // MOBILEAPP
 
     try
     {
 #if !MOBILEAPP
-        jailPath = Path::forDirectory(childRoot + '/' + jailId);
+        const Path jailPath = Path::forDirectory(childRoot + '/' + jailId);
         LOG_INF("Jail path: " << jailPath.toString());
         File(jailPath).createDirectories();
         chmod(jailPath.toString().c_str(), S_IXUSR | S_IWUSR | S_IRUSR);
 
         if (!ChildSession::NoCapsForKit)
         {
-            userdir_url = "file:///user";
-            instdir_path = '/' + loSubPath + "/program";
+            std::chrono::time_point<std::chrono::steady_clock> 
jailSetupStartTime
+                = std::chrono::steady_clock::now();
 
-            // Create a symlink inside the jailPath so that the absolute 
pathname loTemplate, when
-            // interpreted inside a chroot at jailPath, points to loSubPath 
(relative to the chroot).
-            symlinkPathToJail(jailPath, loTemplate, loSubPath);
-
-            // Font paths can end up as realpaths so match that too.
-            char *resolved = realpath(loTemplate.c_str(), nullptr);
-            if (resolved)
-            {
-                if (strcmp(loTemplate.c_str(), resolved) != 0)
-                    symlinkPathToJail(jailPath, std::string(resolved), 
loSubPath);
-                free (resolved);
-            }
-
-            Path jailLOInstallation(jailPath, loSubPath);
-            jailLOInstallation.makeDirectory();
-            File(jailLOInstallation).createDirectory();
+            userdir_url = "file:///tmp/user";
+            instdir_path = '/' + loSubPath + "/program";
 
             // Copy (link) LO installation and other necessary files into it 
from the template.
-            bool bLoopMounted = false;
             if (std::getenv("LOOL_BIND_MOUNT"))
             {
-                Path usrSrcPath(sysTemplate, "usr");
-                Path usrDestPath(jailPath, "usr");
-                File(usrDestPath).createDirectory();
-                std::string mountCommand =
-                    std::string("loolmount ") +
-                    usrSrcPath.toString() +
-                    std::string(" ") +
-                    usrDestPath.toString();
-                LOG_DBG("Initializing jail bind mount.");
-                bLoopMounted = !system(mountCommand.c_str());
-                LOG_DBG("Initialized jail bind mount.");
-            }
+                const std::string destPath = jailPath.toString();
+                LOG_DBG("Mounting " << sysTemplate << " -> " << destPath);
+                if (!JailUtil::bind(sysTemplate, destPath))
+                {
+                    LOG_INF("Failed to mount [" << sysTemplate << "] -> [" << 
destPath
+                                                << "], will link/copy 
contents.");
+                    linkOrCopy(sysTemplate, destPath, LinkOrCopyType::All);
+                }
+                else
+                    JailUtil::remountReadonly(sysTemplate, 
jailPath.toString());
 
-            linkOrCopy(sysTemplate, jailPath,
-                       bLoopMounted ? LinkOrCopyType::NoUsr : 
LinkOrCopyType::All);
-            linkOrCopy(loTemplate, jailLOInstallation, LinkOrCopyType::LO);
+                // Mount lotemplate inside it.
+                const std::string loDestPath = Poco::Path(jailPath, 
"lo").toString();
+                LOG_DBG("Mounting " << loTemplate << " -> " << loDestPath);
+                Poco::File(loDestPath).createDirectories();
+                if (!JailUtil::bind(loTemplate, loDestPath))
+                {
+                    LOG_INF("Failed to mount [" << loTemplate << "] -> [" << 
loDestPath
+                                                << "], will link/copy 
contents.");
+                    linkOrCopy(sysTemplate, loDestPath, LinkOrCopyType::LO);
+                }
+                else
+                    JailUtil::remountReadonly(loTemplate, loDestPath);
+
+                // hard-random tmpdir inside the jail / root
+                const std::string tempRoot = Poco::Path(childRoot, 
"tmp").toString();
+                Poco::File(tempRoot).createDirectories();
+                const std::string tmpSubDir = 
Util::createRandomTmpDir(tempRoot);
+                const std::string jailTmpDir = Poco::Path(jailPath, 
"tmp").toString();
+                if (!JailUtil::bind(tmpSubDir, jailTmpDir))
+                {
+                    LOG_ERR("Failed to bind tmp dir in jail.");
+                }
 
-            // Copy some needed files - makes the networking work in the
-            // chroot
-            const std::initializer_list<const char*> files = {"/etc/passwd", 
"/etc/group", "/etc/host.conf", "/etc/hosts", "/etc/nsswitch.conf", 
"/etc/resolv.conf"};
-            for (const auto& filename : files)
-            {
-                const Poco::Path etcPath = Path(jailPath, filename);
-                const std::string etcPathString = etcPath.toString();
-                if (File(filename).exists() && !File(etcPathString).exists() )
-                    linkOrCopyFile(filename, etcPath);
+                JailUtil::setupJailDevNodes(destPath + "/tmp");
             }
+            else
+            {
+                linkOrCopy(sysTemplate, jailPath, LinkOrCopyType::All);
 
-            LOG_DBG("Initialized jail files.");
+                Poco::Path jailLOInstallation(jailPath, loSubPath);
+                jailLOInstallation.makeDirectory();
+                Poco::File(jailLOInstallation).createDirectory();
+                linkOrCopy(loTemplate, jailLOInstallation, LinkOrCopyType::LO);
 
-            // Create the urandom and random devices
-            File(Path(jailPath, "/dev")).createDirectory();
-            if (mknod((jailPath.toString() + "/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(" << jailPath.toString() << "/dev/random) 
failed. Mount must not use nodev flag.");
+                JailUtil::setupJailDevNodes(Poco::Path(jailPath, 
"/tmp").toString());
             }
 
-            if (mknod((jailPath.toString() + "/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(" << jailPath.toString() << "/dev/random) 
failed. Mount must not use nodev flag.");
-            }
+            ::setenv("TMPDIR", "/tmp", 1);
+
+            const auto ms = 
std::chrono::duration_cast<std::chrono::milliseconds>(
+                                std::chrono::steady_clock::now() - 
jailSetupStartTime)
+                                .count();
+            LOG_DBG("Initialized jail files in " << ms << " ms.");
 
             ProcSMapsFile = open("/proc/self/smaps", O_RDONLY);
             if (ProcSMapsFile < 0)
@@ -2730,14 +2693,10 @@ void lokit_main(
         else // noCapabilities set
         {
             LOG_ERR("Security warning - using template " << loTemplate << " as 
install subpath - skipping chroot jail setup");
-            userdir_url = "file:///" + jailPath.toString() + "/user";
+            userdir_url = "file:///" + jailPath.toString() + "/tmp/user";
             instdir_path = '/' + loTemplate + "/program";
         }
 
-        // hard-random tmpdir inside the jail / root
-        std::string tmpSubdir = Util::createRandomTmpDir();
-        ::setenv("TMPDIR", tmpSubdir.c_str(), 1);
-
         LibreOfficeKit *kit;
         {
             const char *instdir = instdir_path.c_str();
@@ -3032,15 +2991,15 @@ bool globalPreinit(const std::string &loTemplate)
              "javaloader javavm jdbc rpt rptui rptxml ",
              0 /* no overwrite */);
 
-    LOG_TRC("Invoking lok_preinit(" << loTemplate << "/program\", 
\"file:///user\")");
+    LOG_TRC("Invoking lok_preinit(" << loTemplate << "/program\", 
\"file:///tmp/user\")");
     const auto start = std::chrono::steady_clock::now();
-    if (preInit((loTemplate + "/program").c_str(), "file:///user") != 0)
+    if (preInit((loTemplate + "/program").c_str(), "file:///tmp/user") != 0)
     {
         LOG_FTL("lok_preinit() in " << loadedLibrary << " failed");
         return false;
     }
 
-    LOG_TRC("Finished lok_preinit(" << loTemplate << "/program\", 
\"file:///user\") in " <<
+    LOG_TRC("Finished lok_preinit(" << loTemplate << "/program\", 
\"file:///tmp/user\") in " <<
             
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()
 - start).count() <<
             " ms.");
     return true;
diff --git a/loolwsd-systemplate-setup b/loolwsd-systemplate-setup
index 2500beac4..5b7bda6e8 100755
--- a/loolwsd-systemplate-setup
+++ b/loolwsd-systemplate-setup
@@ -22,6 +22,7 @@ cd / || exit 1
 
 # First essential files and shared objects
 find etc/hosts etc/nsswitch.conf etc/resolv.conf \
+     etc/passwd etc/group etc/host.conf \
      etc/ld.so.* \
      lib/ld-* lib64/ld-* \
      lib/libnss_* lib64/libnss_* lib/*/libnss_* \
@@ -64,7 +65,9 @@ grep -v dynamic | cut -d " " -f 3 | grep -E '^(/lib|/usr)' | 
sort -u | sed -e 's
 # This will now copy the file a symlink points to, but whatever.
 cpio -p -d -L $CHROOT
 
-mkdir -p $CHROOT/tmp
+mkdir -p $CHROOT/lo
+mkdir -p $CHROOT/dev
+mkdir -p $CHROOT/tmp/dev
 
 # /usr/share/fonts needs to be taken care of separately because the
 # directory time stamps must be preserved for fontconfig to trust
diff --git a/loolwsd.spec.in b/loolwsd.spec.in
index e8747a5a9..fa9919e9c 100644
--- a/loolwsd.spec.in
+++ b/loolwsd.spec.in
@@ -100,6 +100,7 @@ echo "account    required     pam_unix.so" >>  
%{buildroot}/etc/pam.d/loolwsd
 /usr/bin/loolforkit
 /usr/bin/loolconvert
 /usr/bin/loolconfig
+/usr/bin/loolmount
 /usr/share/loolwsd/discovery.xml
 /usr/share/loolwsd/favicon.ico
 /usr/share/loolwsd/loleaflet
@@ -140,6 +141,7 @@ getent passwd lool >/dev/null || useradd -g lool -r lool
 
 %post
 setcap cap_fowner,cap_mknod,cap_sys_chroot=ep /usr/bin/loolforkit
+setcap cap_sys_admin=ep /usr/bin/loolmount
 
 mkdir -p /var/cache/loolwsd && chown lool:lool /var/cache/loolwsd
 rm -rf /var/cache/loolwsd/*
diff --git a/loolwsd.xml.in b/loolwsd.xml.in
index e6886b084..ceb78992e 100644
--- a/loolwsd.xml.in
+++ b/loolwsd.xml.in
@@ -7,6 +7,7 @@
 
     <sys_template_path desc="Path to a template tree with shared libraries etc 
to be used as source for chroot jails for child processes." type="path" 
relative="true" default="systemplate"></sys_template_path>
     <child_root_path desc="Path to the directory under which the chroot jails 
for the child processes will be created. Should be on the same file system as 
systemplate and lotemplate. Must be an empty directory." type="path" 
relative="true" default="jails"></child_root_path>
+    <mount_jail_tree desc="Controls whether the systemplate and lotemplate 
contents are mounted or not, which is much faster than the default of 
linking/copying each file." type="bool" default="true"></mount_jail_tree>
 
     <server_name desc="External hostname:port of the server running loolwsd. 
If empty, it's derived from the request (please set it if this doesn't work). 
Must be specified when behind a reverse-proxy or when the hostname is not 
reachable directly." type="string" default=""></server_name>
     <file_server_root_path desc="Path to the directory that should be 
considered root for the file server. This should be the directory containing 
loleaflet." type="path" relative="true" 
default="loleaflet/../"></file_server_root_path>
diff --git a/net/Socket.cpp b/net/Socket.cpp
index cc0e9cf64..2f35b5cce 100644
--- a/net/Socket.cpp
+++ b/net/Socket.cpp
@@ -16,6 +16,7 @@
 #include <iomanip>
 #include <stdio.h>
 #include <unistd.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/un.h>
 #include <zlib.h>
diff --git a/net/Socket.hpp b/net/Socket.hpp
index 55cfe05bb..9ee336950 100644
--- a/net/Socket.hpp
+++ b/net/Socket.hpp
@@ -11,7 +11,6 @@
 
 #include <poll.h>
 #include <unistd.h>
-#include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
diff --git a/test/run_unit.sh.in b/test/run_unit.sh.in
index 4b893f23c..6fbfc15e7 100755
--- a/test/run_unit.sh.in
+++ b/test/run_unit.sh.in
@@ -74,6 +74,7 @@ echo "Running $tst | $tst_log ...";
 if ${trace} \
        ${abs_top_builddir}/loolwsd --o:sys_template_path="$systemplate_path" \
        --o:child_root_path="$jails_path" \
+       --o:mount_jail_tree=false \
                                    --o:storage.filesystem[@allow]=true \
                                    --o:logging.level=trace \
                                    
--o:ssl.key_file_path="${abs_top_builddir}/etc/key.pem" \
@@ -90,6 +91,7 @@ else
         echo "Test failed on unit: $tst re-run with:"
         echo "   $ gdb --args ${abs_top_builddir}/loolwsd 
--o:sys_template_path=\"$systemplate_path\" \\"
         echo "         --o:child_root_path=\"$jails_path\" \\"
+        echo "         --o:mount_jail_tree=false \\"
         echo "         --o:storage.filesystem[@allow]=true \\"
         echo "         --o:storage.ssl.enable=false \\"
         echo "         --o:logging.level=trace \\"
diff --git a/tools/mount.cpp b/tools/mount.cpp
index 7afba1fcd..8f2d78e5a 100644
--- a/tools/mount.cpp
+++ b/tools/mount.cpp
@@ -12,26 +12,134 @@
 
 #include <config.h>
 
+#include <stdio.h>
+#include <errno.h>
 #include <sys/mount.h>
+#include <sys/stat.h>
+#include <unistd.h>
 
 #include <security.h>
 
-int main(int argc, char **argv)
+void usage(const char* program)
 {
+    fprintf(stderr, "Usage: %s <-s|-r> <source path> <target path>\n", 
program);
+    fprintf(stderr, "       %s -u <target>.\n", program);
+    fprintf(stderr, "       -b bind and mount the source to target.\n");
+    fprintf(stderr, "       -r bind and mount the source to target as 
readonly.\n");
+    fprintf(stderr, "       -u to unmount the target.\n");
+}
+
+int main(int argc, char** argv)
+{
+    const char* program = argv[0];
+
     if (!hasCorrectUID("loolmount"))
+    {
+        fprintf(stderr, "%s: incorrect UID.", program);
         return 1;
+    }
 
     if (argc < 3)
+    {
+        usage(program);
         return 1;
+    }
+
+    const char* option = argv[1];
+    if (argc == 3 && strcmp(option, "-u") == 0) // Unmount
+    {
+        const char* target = argv[2];
+
+        struct stat sb;
+        const bool target_exists = (stat(target, &sb) == 0 && 
S_ISDIR(sb.st_mode));
 
-    int retval = mount (argv[1], argv[2], nullptr, MS_BIND, nullptr);
-    if (retval)
-        return retval;
+        // Do nothing if target doesn't exist.
+        if (target_exists)
+        {
+            // Unmount the target, first by detaching. This should succeed.
+            int retval = umount2(target, MNT_DETACH);
+            if (retval != 0)
+            {
+                if (errno != EINVAL)
+                    fprintf(stderr, "%s: unmount failed to detach [%s]: 
%s.\n", program, target,
+                            strerror(errno));
+            }
+
+            // Now try to force the unmounting, which isn't supported on all 
filesystems.
+            retval = umount2(target, MNT_FORCE);
+            if (retval && errno != EINVAL)
+            {
+                fprintf(stderr, "%s: forced unmount of [%s] failed: %s.\n", 
program, target,
+                        strerror(errno));
+                return 1;
+            }
+        }
+    }
+    else if (argc == 4) // Mount
+    {
+        const char* source = argv[2];
+        struct stat sb;
+        if (stat(source, &sb) != 0 || !S_ISDIR(sb.st_mode))
+        {
+            fprintf(stderr, "%s: cannot mount from invalid source directory 
[%s].\n", program,
+                    source);
+            return 1;
+        }
+
+        const char* target = argv[3];
+        const bool target_exists = (stat(target, &sb) == 0 && 
S_ISDIR(sb.st_mode));
+        if (!target_exists)
+        {
+            fprintf(stderr, "%s: cannot mount on invalid target directory 
[%s].\n", program,
+                    target);
+            return 1;
+        }
+
+        // Mount the source path as the target path.
+        // First bind to mount an existing directory node into the chroot.
+        // MS_BIND ignores other flags.
+        if (strcmp(option, "-b") == 0) // Shared or Bind Mount.
+        {
+            const int retval
+                = mount(source, target, nullptr, (MS_MGC_VAL | MS_BIND | 
MS_REC), nullptr);
+            if (retval)
+            {
+                fprintf(stderr, "%s: mount failed to bind [%s] to [%s]: 
%s.\n", program, source,
+                        target, strerror(errno));
+                return 1;
+            }
+        }
+        else if (strcmp(option, "-r") == 0) // Readonly Mount.
+        {
+            // Now we need to set read-only and other flags with a remount.
+            int retval = mount(source, target, nullptr,
+                               (MS_BIND | MS_REC | MS_REMOUNT | MS_NOATIME | 
MS_NODEV | MS_NOSUID
+                                | MS_RDONLY | MS_SILENT),
+                               nullptr);
+            if (retval)
+            {
+                fprintf(stderr, "%s: mount failed remount [%s] readonly: 
%s.\n", program, target,
+                        strerror(errno));
+                return 1;
+            }
+
+            retval = mount(source, target, nullptr, (MS_UNBINDABLE | MS_REC), 
nullptr);
+            if (retval)
+            {
+                fprintf(stderr, "%s: mount failed make [%s] private: %s.\n", 
program, target,
+                        strerror(errno));
+                return 1;
+            }
+        }
+    }
+    else
+    {
+        usage(program);
+        return 1;
+    }
 
-    // apparently this has to be done in a 2nd pass.
-    return mount(argv[1], argv[2], nullptr,
-                 (MS_BIND | MS_REMOUNT | MS_NOATIME | MS_NODEV |
-                  MS_NOSUID | MS_RDONLY  | MS_SILENT), nullptr);
+    fflush(stderr);
+    return 0;
 }
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/wsd/LOOLWSD.cpp b/wsd/LOOLWSD.cpp
index 22f5d4847..7b99e16f4 100644
--- a/wsd/LOOLWSD.cpp
+++ b/wsd/LOOLWSD.cpp
@@ -39,7 +39,6 @@
 #include <stdlib.h>
 #include <sysexits.h>
 
-#include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 
@@ -114,7 +113,8 @@ using Poco::Net::PartHandler;
 #include "DocumentBroker.hpp"
 #include "Exceptions.hpp"
 #include "FileServer.hpp"
-#include <FileUtil.hpp>
+#include <common/FileUtil.hpp>
+#include <common/JailUtil.hpp>
 #if defined KIT_IN_PROCESS || MOBILEAPP
 #  include <Kit.hpp>
 #endif
@@ -866,6 +866,8 @@ void LOOLWSD::initialize(Application& self)
     }
 #endif
 
+    
Util::setApplicationPath(Poco::Path(Application::instance().commandPath()).parent().toString());
+
     if (!UnitWSD::init(UnitWSD::UnitType::Wsd, UnitTestLibrary))
     {
         throw std::runtime_error("Failed to load wsd unit test library.");
@@ -908,6 +910,7 @@ void LOOLWSD::initialize(Application& self)
             { "logging.lokit_sal_log", "-INFO-WARN" },
             { "loleaflet_html", "loleaflet.html" },
             { "loleaflet_logging", "false" },
+            { "mount_jail_tree", "true" },
             { "net.listen", "any" },
             { "net.proto", "all" },
             { "net.service_root", "" },
@@ -1053,6 +1056,9 @@ void LOOLWSD::initialize(Application& self)
         LOG_INF("Setting log-level to [trace] and delaying setting to 
configured [" << LogLevel << "] until after WSD initialization.");
     }
 
+    ServerName = config().getString("server_name");
+    LOG_INF("Initializing loolwsd server [" << ServerName << "].");
+
     // Get anonymization settings.
 #if LOOLWSD_ANONYMIZE_USER_DATA
     AnonymizeUserData = true;
@@ -1168,8 +1174,36 @@ void LOOLWSD::initialize(Application& self)
 #endif
 
     SysTemplate = getPathFromConfig("sys_template_path");
+    if (SysTemplate.empty())
+    {
+        LOG_FTL("Missing sys_template_path config entry.");
+        throw MissingOptionException("systemplate");
+    }
+
     ChildRoot = getPathFromConfig("child_root_path");
-    ServerName = config().getString("server_name");
+    if (ChildRoot.empty())
+    {
+        LOG_FTL("Missing child_root_path config entry.");
+        throw MissingOptionException("childroot");
+    }
+    else
+    {
+        if (ChildRoot[ChildRoot.size() - 1] != '/')
+            ChildRoot += '/';
+
+        // Create a custom sub-path for parallelized unit tests.
+        if (UnitBase::isUnitTesting())
+        {
+            ChildRoot += Util::rng::getHardRandomHexString(8) + '/';
+            LOG_INF("Creating sub-childroot: " + ChildRoot);
+        }
+        else
+            LOG_INF("Creating childroot: " + ChildRoot);
+    }
+
+    // Setup the jails.
+    JailUtil::setupJails(getConfigValue<bool>(conf, "mount_jail_tree", true), 
ChildRoot,
+                         SysTemplate);
 
     LOG_DBG("FileServerRoot before config: " << FileServerRoot);
     FileServerRoot = getPathFromConfig("file_server_root_path");
@@ -1188,6 +1222,8 @@ void LOOLWSD::initialize(Application& self)
     LOG_INF("NumPreSpawnedChildren set to " << NumPreSpawnedChildren << '.');
 
 #if !MOBILEAPP
+    FileUtil::registerFileSystemForDiskSpaceChecks(ChildRoot);
+
     const auto maxConcurrency = getConfigValue<int>(conf, 
"per_document.max_concurrency", 4);
     if (maxConcurrency > 0)
     {
@@ -3727,40 +3763,14 @@ int LOOLWSD::innerMain()
     // so must check options required in the parent (but not in the
     // child) separately now. Also check for options that are
     // meaningless for the parent.
-    if (SysTemplate.empty())
-    {
-        LOG_FTL("Missing --systemplate option");
-        throw MissingOptionException("systemplate");
-    }
-
     if (LoTemplate.empty())
     {
-        LOG_FTL("Missing --lotemplate option");
+        LOG_FTL("Missing --lo-template-path option");
         throw MissingOptionException("lotemplate");
     }
 
-    if (ChildRoot.empty())
-    {
-        LOG_FTL("Missing --childroot option");
-        throw MissingOptionException("childroot");
-    }
-    else
-    {
-        if (ChildRoot[ChildRoot.size() - 1] != '/')
-            ChildRoot += '/';
-
-        // create a custom sub-path for parallelized unit tests.
-        if (UnitBase::isUnitTesting())
-        {
-            ChildRoot += Util::rng::getHardRandomHexString(8) + "/";
-            LOG_TRC("Creating sub-childroot: of " + ChildRoot);
-        }
-    }
-
-    FileUtil::registerFileSystemForDiskSpaceChecks(ChildRoot);
-
     if (FileServerRoot.empty())
-        FileServerRoot = 
Poco::Path(Application::instance().commandPath()).parent().toString();
+        FileServerRoot = Util::getApplicationPath();
     FileServerRoot = Poco::Path(FileServerRoot).absolute().toString();
     LOG_DBG("FileServerRoot: " << FileServerRoot);
 #endif
@@ -3957,22 +3967,7 @@ int LOOLWSD::innerMain()
     ForKitProc.reset();
 #endif
 
-    // In case forkit didn't cleanup properly, don't leave jails behind.
-    LOG_INF("Cleaning up childroot directory [" << ChildRoot << "].");
-    std::vector<std::string> jails;
-    File(ChildRoot).list(jails);
-    for (auto& jail : jails)
-    {
-        const auto path = ChildRoot + jail;
-        LOG_INF("Removing jail [" << path << "].");
-        FileUtil::removeFile(path, true);
-    }
-
-    if (UnitBase::isUnitTesting())
-    {
-        LOG_TRC("Removing sub-childroot: of " + ChildRoot);
-        FileUtil::removeFile(ChildRoot, true);
-    }
+    JailUtil::cleanupJails(ChildRoot);
 #endif // !MOBILEAPP
 
     return EX_OK;
diff --git a/wsd/Storage.hpp b/wsd/Storage.hpp
index d7f6d70f9..8f07b9229 100644
--- a/wsd/Storage.hpp
+++ b/wsd/Storage.hpp
@@ -260,8 +260,8 @@ protected:
 
 private:
     const Poco::URI _uri;
-    std::string _localStorePath;
-    std::string _jailPath;
+    const std::string _localStorePath;
+    const std::string _jailPath;
     std::string _jailedFilePath;
     std::string _jailedFilePathAnonym;
     FileInfo _fileInfo;
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to