[Libreoffice-commits] online.git: loolwsd/ChildSession.cpp loolwsd/common loolwsd/LOOLForKit.cpp loolwsd/LOOLWSD.cpp loolwsd/Makefile.am loolwsd/Storage.cpp loolwsd/test loolwsd/TileCache.cpp loolwsd/Util.cpp loolwsd/Util.hpp

Ashod Nakashian ashod.nakashian at collabora.co.uk
Mon Nov 14 05:23:20 UTC 2016


 loolwsd/ChildSession.cpp                 |    3 
 loolwsd/LOOLForKit.cpp                   |    3 
 loolwsd/LOOLWSD.cpp                      |   15 +-
 loolwsd/Makefile.am                      |    2 
 loolwsd/Storage.cpp                      |    3 
 loolwsd/TileCache.cpp                    |   11 +
 loolwsd/Util.cpp                         |  167 -------------------------
 loolwsd/Util.hpp                         |   59 ---------
 loolwsd/common/FileUtil.cpp              |  202 +++++++++++++++++++++++++++++++
 loolwsd/common/FileUtil.hpp              |   93 ++++++++++++++
 loolwsd/test/Makefile.am                 |    1 
 loolwsd/test/helpers.hpp                 |    3 
 loolwsd/test/integration-http-server.cpp |    5 
 13 files changed, 328 insertions(+), 239 deletions(-)

New commits:
commit ad70138fc9c2df3b82b881f1f99b52b015cfb7f0
Author: Ashod Nakashian <ashod.nakashian at collabora.co.uk>
Date:   Sat Nov 12 16:38:13 2016 -0500

    loolwsd: move file utilities into FileUtil files
    
    Change-Id: Ib0c0bc66adabe6885f7ac16414a3d5af13d72893
    Reviewed-on: https://gerrit.libreoffice.org/30820
    Reviewed-by: Ashod Nakashian <ashnakash at gmail.com>
    Tested-by: Ashod Nakashian <ashnakash at gmail.com>

diff --git a/loolwsd/ChildSession.cpp b/loolwsd/ChildSession.cpp
index d27c91e..4a177ac 100644
--- a/loolwsd/ChildSession.cpp
+++ b/loolwsd/ChildSession.cpp
@@ -18,6 +18,7 @@
 #include <Poco/StringTokenizer.h>
 #include <Poco/URI.h>
 
+#include "common/FileUtil.hpp"
 #include "LOKitHelper.hpp"
 #include "Log.hpp"
 #include "Png.hpp"
@@ -574,7 +575,7 @@ bool ChildSession::downloadAs(const char* /*buffer*/, int /*length*/, StringToke
     }
 
     // The file is removed upon downloading.
-    const auto tmpDir = Util::createRandomDir(JAILED_DOCUMENT_ROOT);
+    const auto tmpDir = FileUtil::createRandomDir(JAILED_DOCUMENT_ROOT);
     // Prevent user inputting anything funny here.
     // A "name" should always be a name, not a path
     const Poco::Path filenameParam(name);
diff --git a/loolwsd/LOOLForKit.cpp b/loolwsd/LOOLForKit.cpp
index 0bd55e8..9529139 100644
--- a/loolwsd/LOOLForKit.cpp
+++ b/loolwsd/LOOLForKit.cpp
@@ -32,6 +32,7 @@
 #include <Poco/Util/Application.h>
 
 #include "Common.hpp"
+#include "common/FileUtil.hpp"
 #include "IoUtil.hpp"
 #include "LOOLKit.hpp"
 #include "Log.hpp"
@@ -185,7 +186,7 @@ static void cleanupChildren()
         if (childJails.find(exitedChildPid) != childJails.end())
         {
             Log::info("Child " + std::to_string(exitedChildPid) + " has exited, removing its jail '" + childJails[exitedChildPid] + "'");
-            Util::removeFile(childJails[exitedChildPid], true);
+            FileUtil::removeFile(childJails[exitedChildPid], true);
             childJails.erase(exitedChildPid);
         }
         else
diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index 799a79c..1563d6b 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -99,6 +99,7 @@
 #include "Common.hpp"
 #include "Exceptions.hpp"
 #include "FileServer.hpp"
+#include "common/FileUtil.hpp"
 #include "IoUtil.hpp"
 #include "LOOLProtocol.hpp"
 #include "LOOLSession.hpp"
@@ -257,7 +258,7 @@ static void forkChildren(const int number)
 
     if (number > 0)
     {
-        Util::checkDiskSpaceOnRegisteredFileSystems();
+        FileUtil::checkDiskSpaceOnRegisteredFileSystems();
         const std::string aMessage = "spawn " + std::to_string(number) + "\n";
         LOG_DBG("MasterToForKit: " << aMessage.substr(0, aMessage.length() - 1));
 
@@ -585,7 +586,7 @@ private:
                 // Clean up the temporary directory the HTMLForm ctor created.
                 Path tempDirectory(fromPath);
                 tempDirectory.setFileName("");
-                Util::removeFile(tempDirectory, /*recursive=*/true);
+                FileUtil::removeFile(tempDirectory, /*recursive=*/true);
             }
 
             if (!sent)
@@ -690,7 +691,7 @@ private:
                             (exc.nested() ? " (" + exc.nested()->displayText() + ")" : ""));
                 }
 
-                Util::removeFile(File(filePath.parent()).path(), true);
+                FileUtil::removeFile(File(filePath.parent()).path(), true);
             }
             else
             {
@@ -870,7 +871,7 @@ private:
             LOG_TRC("Sending to Client [" << status << "].");
             ws->sendFrame(status.data(), status.size());
 
-            Util::checkDiskSpaceOnRegisteredFileSystems();
+            FileUtil::checkDiskSpaceOnRegisteredFileSystems();
 
             // Request the child to connect to us and add this session.
             auto sessionsCount = docBroker->addSession(session);
@@ -1899,8 +1900,8 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/)
     else if (ChildRoot[ChildRoot.size() - 1] != '/')
         ChildRoot += '/';
 
-    Util::registerFileSystemForDiskSpaceChecks(ChildRoot);
-    Util::registerFileSystemForDiskSpaceChecks(Cache + "/.");
+    FileUtil::registerFileSystemForDiskSpaceChecks(ChildRoot);
+    FileUtil::registerFileSystemForDiskSpaceChecks(Cache + "/.");
 
     if (FileServerRoot.empty())
         FileServerRoot = Poco::Path(Application::instance().commandPath()).parent().parent().toString();
@@ -2092,7 +2093,7 @@ int LOOLWSD::main(const std::vector<std::string>& /*args*/)
     {
         const auto path = ChildRoot + jail;
         LOG_INF("Removing jail [" << path << "].");
-        Util::removeFile(path, true);
+        FileUtil::removeFile(path, true);
     }
 
     if (LOOLWSD::isSSLEnabled())
diff --git a/loolwsd/Makefile.am b/loolwsd/Makefile.am
index fd0efdc..e7e69ff 100644
--- a/loolwsd/Makefile.am
+++ b/loolwsd/Makefile.am
@@ -31,6 +31,7 @@ AM_ETAGSFLAGS = --c++-kinds=+p --fields=+iaS --extra=+q -R --totals=yes *
 AM_CTAGSFLAGS = $(AM_ETAGSFLAGS)
 
 shared_sources = ChildSession.cpp \
+                 common/FileUtil.cpp \
                  IoUtil.cpp \
                  Log.cpp \
                  LOOLProtocol.cpp \
@@ -93,6 +94,7 @@ noinst_HEADERS = Admin.hpp \
                  DocumentBroker.hpp \
                  Exceptions.hpp \
                  FileServer.hpp \
+                 common/FileUtil.hpp \
                  IoUtil.hpp \
                  LibreOfficeKit.hpp \
                  Log.hpp \
diff --git a/loolwsd/Storage.cpp b/loolwsd/Storage.cpp
index d504db0..bc65efe 100644
--- a/loolwsd/Storage.cpp
+++ b/loolwsd/Storage.cpp
@@ -30,6 +30,7 @@
 #include "Auth.hpp"
 #include "Common.hpp"
 #include "Exceptions.hpp"
+#include "common/FileUtil.hpp"
 #include "LOOLWSD.hpp"
 #include "Log.hpp"
 #include "Unit.hpp"
@@ -217,7 +218,7 @@ std::string LocalStorage::loadStorageFileToLocal()
     // Despite the talk about URIs it seems that _uri is actually just a pathname here
     const auto publicFilePath = _uri.getPath();
 
-    if (!Util::checkDiskSpace(publicFilePath))
+    if (!FileUtil::checkDiskSpace(publicFilePath))
     {
         throw StorageSpaceLowException("Low disk space for " + publicFilePath);
     }
diff --git a/loolwsd/TileCache.cpp b/loolwsd/TileCache.cpp
index d26db3b..41d9bd0 100644
--- a/loolwsd/TileCache.cpp
+++ b/loolwsd/TileCache.cpp
@@ -32,6 +32,7 @@
 
 #include "ClientSession.hpp"
 #include "Common.hpp"
+#include "common/FileUtil.hpp"
 #include "LOOLProtocol.hpp"
 #include "Unit.hpp"
 #include "Util.hpp"
@@ -60,7 +61,7 @@ TileCache::TileCache(const std::string& docURL,
          getTextFile("unsaved.txt") != ""))
     {
         // Document changed externally or modifications were not saved after all. Cache not useful.
-        Util::removeFile(_cacheDir, true);
+        FileUtil::removeFile(_cacheDir, true);
         Log::info("Completely cleared tile cache: " + _cacheDir);
     }
 
@@ -157,8 +158,10 @@ void TileCache::saveTileAndNotify(const TileDesc& tile, const char *data, const
     // Ignore if we can't save the tile, things will work anyway, but slower. An error indication
     // has been supposed to be sent to all users in that case.
     const auto fileName = _cacheDir + "/" + cachedName;
-    if (Util::saveDataToFileSafely(fileName, data, size))
+    if (FileUtil::saveDataToFileSafely(fileName, data, size))
+    {
         Log::trace() << "Saved cache tile: " << fileName << Log::end;
+    }
 
     // Notify subscribers, if any.
     if (tileBeingRendered)
@@ -294,7 +297,7 @@ void TileCache::saveRendering(const std::string& name, const std::string& dir, c
 
     const std::string fileName = dirName + "/" + name;
 
-    Util::saveDataToFileSafely(fileName, data, size);
+    FileUtil::saveDataToFileSafely(fileName, data, size);
 }
 
 std::unique_ptr<std::fstream> TileCache::lookupCachedFile(const std::string& name, const std::string& dir)
@@ -332,7 +335,7 @@ void TileCache::invalidateTiles(int part, int x, int y, int width, int height)
             if (intersectsTile(fileName, part, x, y, width, height))
             {
                 Log::debug("Removing tile: " + tileIterator.path().toString());
-                Util::removeFile(tileIterator.path());
+                FileUtil::removeFile(tileIterator.path());
             }
         }
     }
diff --git a/loolwsd/Util.cpp b/loolwsd/Util.cpp
index afa23f3..7949c09 100644
--- a/loolwsd/Util.cpp
+++ b/loolwsd/Util.cpp
@@ -98,8 +98,6 @@ namespace rng
         return ss.str().substr(0, length);
     }
 
-    /// Generates a random string suitable for
-    /// file/directory names.
     std::string getFilename(const size_t length)
     {
         std::string s = getB64String(length);
@@ -109,15 +107,6 @@ namespace rng
 }
 }
 
-namespace
-{
-    void alertAllUsersAndLog(const std::string& message, const std::string& cmd, const std::string& kind)
-    {
-        Log::error(message);
-        Util::alertAllUsers(cmd, kind);
-    }
-}
-
 namespace Util
 {
     std::string encodeId(const unsigned number, const int padding)
@@ -136,171 +125,15 @@ namespace Util
         return id;
     }
 
-    /// Create a secure, random directory path.
-    std::string createRandomDir(const std::string& path)
-    {
-        const auto name = rng::getFilename(64);
-        Poco::File(Poco::Path(path, name)).createDirectories();
-        return name;
-    }
-
-    std::string getTempFilePath(const std::string& srcDir, const std::string& srcFilename)
-    {
-        const std::string srcPath = srcDir + '/' + srcFilename;
-        const std::string dstPath = Poco::Path::temp() + encodeId(rng::getNext()) + '_' + srcFilename;
-        Poco::File(srcPath).copyTo(dstPath);
-        Poco::TemporaryFile::registerForDeletion(dstPath);
-        return dstPath;
-    }
-
     bool windowingAvailable()
     {
         return std::getenv("DISPLAY") != nullptr;
     }
 
-    bool saveDataToFileSafely(const std::string& fileName, const char *data, size_t size)
-    {
-        const auto tempFileName = fileName + ".temp";
-        std::fstream outStream(tempFileName, std::ios::out);
-
-        // If we can't create the file properly, just remove it
-        if (!outStream.good())
-        {
-            alertAllUsersAndLog("Creating " + tempFileName + " failed, disk full?", "internal", "diskfull");
-            // Try removing both just in case
-            std::remove(tempFileName.c_str());
-            std::remove(fileName.c_str());
-            return false;
-        }
-        else
-        {
-            outStream.write(data, size);
-            if (!outStream.good())
-            {
-                alertAllUsersAndLog("Writing to " + tempFileName + " failed, disk full?", "internal", "diskfull");
-                outStream.close();
-                std::remove(tempFileName.c_str());
-                std::remove(fileName.c_str());
-                return false;
-            }
-            else
-            {
-                outStream.close();
-                if (!outStream.good())
-                {
-                    alertAllUsersAndLog("Closing " + tempFileName + " failed, disk full?", "internal", "diskfull");
-                    std::remove(tempFileName.c_str());
-                    std::remove(fileName.c_str());
-                    return false;
-                }
-                else
-                {
-                    // Everything OK, rename the file to its proper name
-                    if (std::rename(tempFileName.c_str(), fileName.c_str()) == 0)
-                    {
-                        Log::debug() << "Renaming " << tempFileName << " to " << fileName << " OK." << Log::end;
-                        return true;
-                    }
-                    else
-                    {
-                        alertAllUsersAndLog("Renaming " + tempFileName + " to " + fileName + " failed, disk full?", "internal", "diskfull");
-                        std::remove(tempFileName.c_str());
-                        std::remove(fileName.c_str());
-                        return false;
-                    }
-                }
-            }
-        }
-    }
-
 } // namespace Util
 
-namespace
-{
-
-    struct fs
-    {
-        fs(const std::string& p, dev_t d)
-            : path(p), dev(d)
-        {
-        }
-
-        fs(dev_t d)
-            : fs("", d)
-        {
-        }
-
-        std::string path;
-        dev_t dev;
-    };
-
-    struct fsComparator
-    {
-        bool operator() (const fs& lhs, const fs& rhs) const
-        {
-            return (lhs.dev < rhs.dev);
-        }
-    };
-
-    static std::mutex fsmutex;
-    static std::set<fs, fsComparator> filesystems;
-} // unnamed namespace
-
 namespace Util
 {
-    void registerFileSystemForDiskSpaceChecks(const std::string& path)
-    {
-        std::lock_guard<std::mutex> lock(fsmutex);
-
-        if (path != "")
-        {
-            std::string dirPath = path;
-            std::string::size_type lastSlash = dirPath.rfind('/');
-            assert(lastSlash != std::string::npos);
-            dirPath = dirPath.substr(0, lastSlash + 1) + ".";
-
-            struct stat s;
-            if (stat(dirPath.c_str(), &s) == -1)
-                return;
-            filesystems.insert(fs(dirPath, s.st_dev));
-        }
-    }
-
-    void checkDiskSpaceOnRegisteredFileSystems()
-    {
-        std::lock_guard<std::mutex> lock(fsmutex);
-
-        static std::chrono::steady_clock::time_point lastCheck;
-        std::chrono::steady_clock::time_point now(std::chrono::steady_clock::now());
-
-        // Don't check more often that once a minute
-        if (std::chrono::duration_cast<std::chrono::seconds>(now - lastCheck).count() < 60)
-            return;
-
-        lastCheck = now;
-
-        for (auto& i: filesystems)
-        {
-            if (!checkDiskSpace(i.path))
-            {
-                alertAllUsersAndLog("File system of " + i.path + " dangerously low on disk space", "internal", "diskfull");
-                break;
-            }
-        }
-    }
-
-    bool checkDiskSpace(const std::string& path)
-    {
-        assert(path != "");
-        struct statfs sfs;
-        if (statfs(path.c_str(), &sfs) == -1)
-            return true;
-
-        if (static_cast<double>(sfs.f_bavail) / sfs.f_blocks <= 0.05)
-            return false;
-        return true;
-    }
-
     const char *signalName(const int signo)
     {
         switch (signo)
diff --git a/loolwsd/Util.hpp b/loolwsd/Util.hpp
index ae4c3cd..7e4170f 100644
--- a/loolwsd/Util.hpp
+++ b/loolwsd/Util.hpp
@@ -40,6 +40,10 @@ namespace Util
     {
         void reseed();
         unsigned getNext();
+
+        /// Generates a random string suitable for
+        /// file/directory names.
+        std::string getFilename(const size_t length);
     }
 
     /// Encode an integral ID into a string, with padding support.
@@ -47,21 +51,8 @@ namespace Util
     /// Decode an integral ID from a string.
     unsigned decodeId(const std::string& str);
 
-    /// Creates a randomly name directory within path and returns the name.
-    std::string createRandomDir(const std::string& path);
-
     bool windowingAvailable();
 
-    // Save data to a file (overwriting an existing file if necessary) with checks for errors. Write
-    // to a temporary file in the same directory that is then atomically renamed to the desired name
-    // if everything goes well. In case of any error, both the destination file (if it already
-    // exists) and the temporary file (if was created, or existed already) are removed. Return true
-    // if everything succeeded.
-    bool saveDataToFileSafely(const std::string& fileName, const char* data, size_t size);
-
-    // We work around some of the mess of using the same sources both on the server side and in unit
-    // tests with conditional compilation based on BUILDING_TESTS.
-
 #ifndef BUILDING_TESTS
     // Send a 'error:' message with the specified cmd and kind parameters to all connected
     // clients. This function can be called either in loolwsd or loolkit processes, even if only
@@ -76,21 +67,6 @@ namespace Util
     }
 #endif
 
-    // Add the file system that 'path' is located on to a list of file systems that are periodically
-    // checked for available space. The list is initially empty.
-    void registerFileSystemForDiskSpaceChecks(const std::string& path);
-
-    // Perform the check. If the free space on any of the registered file systems is below 5%, call
-    // 'alertAllUsers("internal", "diskfull")'. The check will be made no more often than once a
-    // minute.
-    void checkDiskSpaceOnRegisteredFileSystems();
-
-    // Check disk space on a specific file system, the one where 'path' is located. This does not
-    // add that file system to the list used by 'registerFileSystemForDiskSpaceChecks'. If the free
-    // space on the file system is below 5%, return false, otherwise true. Note that this function
-    // does not call 'alertAllUsers'.
-    bool checkDiskSpace(const std::string& path);
-
     /// Assert that a lock is already taken.
     template <typename T>
     void assertIsLocked(const T& lock)
@@ -103,33 +79,6 @@ namespace Util
         assert(!mtx.try_lock());
     }
 
-    /// Safely remove a file or directory.
-    /// Supresses exception when the file is already removed.
-    /// This can happen when there is a race (unavoidable) or when
-    /// we don't care to check before we remove (when no race exists).
-    inline void removeFile(const std::string& path, const bool recursive = false)
-    {
-        try
-        {
-            Poco::File(path).remove(recursive);
-        }
-        catch (const std::exception&)
-        {
-            // Already removed or we don't care about failures.
-        }
-    }
-
-    inline void removeFile(const Poco::Path& path, const bool recursive = false)
-    {
-        removeFile(path.toString(), recursive);
-    }
-
-    /// Make a temp copy of a file.
-    /// Primarily used by tests to avoid tainting the originals.
-    /// srcDir shouldn't end with '/' and srcFilename shouldn't contain '/'.
-    /// Returns the created file path.
-    std::string getTempFilePath(const std::string& srcDir, const std::string& srcFilename);
-
     /// Returns the name of the signal.
     const char* signalName(int signo);
 
diff --git a/loolwsd/common/FileUtil.cpp b/loolwsd/common/FileUtil.cpp
new file mode 100644
index 0000000..461513e
--- /dev/null
+++ b/loolwsd/common/FileUtil.cpp
@@ -0,0 +1,202 @@
+/* -*- 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 "FileUtil.hpp"
+#include "config.h"
+
+#include <sys/stat.h>
+#include <sys/vfs.h>
+
+#include <chrono>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <mutex>
+#include <string>
+
+#include <Poco/TemporaryFile.h>
+
+#include "Log.hpp"
+#include "Util.hpp"
+
+namespace
+{
+    void alertAllUsersAndLog(const std::string& message, const std::string& cmd, const std::string& kind)
+    {
+        Log::error(message);
+        Util::alertAllUsers(cmd, kind);
+    }
+}
+
+namespace FileUtil
+{
+    std::string createRandomDir(const std::string& path)
+    {
+        const auto name = Util::rng::getFilename(64);
+        Poco::File(Poco::Path(path, name)).createDirectories();
+        return name;
+    }
+
+    std::string getTempFilePath(const std::string& srcDir, const std::string& srcFilename)
+    {
+        const std::string srcPath = srcDir + '/' + srcFilename;
+        const std::string dstPath = Poco::Path::temp() + Util::encodeId(Util::rng::getNext()) + '_' + srcFilename;
+        Poco::File(srcPath).copyTo(dstPath);
+        Poco::TemporaryFile::registerForDeletion(dstPath);
+        return dstPath;
+    }
+
+    bool saveDataToFileSafely(const std::string& fileName, const char *data, size_t size)
+    {
+        const auto tempFileName = fileName + ".temp";
+        std::fstream outStream(tempFileName, std::ios::out);
+
+        // If we can't create the file properly, just remove it
+        if (!outStream.good())
+        {
+            alertAllUsersAndLog("Creating " + tempFileName + " failed, disk full?", "internal", "diskfull");
+            // Try removing both just in case
+            std::remove(tempFileName.c_str());
+            std::remove(fileName.c_str());
+            return false;
+        }
+        else
+        {
+            outStream.write(data, size);
+            if (!outStream.good())
+            {
+                alertAllUsersAndLog("Writing to " + tempFileName + " failed, disk full?", "internal", "diskfull");
+                outStream.close();
+                std::remove(tempFileName.c_str());
+                std::remove(fileName.c_str());
+                return false;
+            }
+            else
+            {
+                outStream.close();
+                if (!outStream.good())
+                {
+                    alertAllUsersAndLog("Closing " + tempFileName + " failed, disk full?", "internal", "diskfull");
+                    std::remove(tempFileName.c_str());
+                    std::remove(fileName.c_str());
+                    return false;
+                }
+                else
+                {
+                    // Everything OK, rename the file to its proper name
+                    if (std::rename(tempFileName.c_str(), fileName.c_str()) == 0)
+                    {
+                        Log::debug() << "Renaming " << tempFileName << " to " << fileName << " OK." << Log::end;
+                        return true;
+                    }
+                    else
+                    {
+                        alertAllUsersAndLog("Renaming " + tempFileName + " to " + fileName + " failed, disk full?", "internal", "diskfull");
+                        std::remove(tempFileName.c_str());
+                        std::remove(fileName.c_str());
+                        return false;
+                    }
+                }
+            }
+        }
+    }
+
+} // namespace FileUtil
+
+namespace
+{
+
+    struct fs
+    {
+        fs(const std::string& p, dev_t d)
+            : path(p), dev(d)
+        {
+        }
+
+        fs(dev_t d)
+            : fs("", d)
+        {
+        }
+
+        std::string path;
+        dev_t dev;
+    };
+
+    struct fsComparator
+    {
+        bool operator() (const fs& lhs, const fs& rhs) const
+        {
+            return (lhs.dev < rhs.dev);
+        }
+    };
+
+    static std::mutex fsmutex;
+    static std::set<fs, fsComparator> filesystems;
+
+} // anonymous namespace
+
+namespace FileUtil
+{
+    void registerFileSystemForDiskSpaceChecks(const std::string& path)
+    {
+        std::lock_guard<std::mutex> lock(fsmutex);
+
+        if (path != "")
+        {
+            std::string dirPath = path;
+            std::string::size_type lastSlash = dirPath.rfind('/');
+            assert(lastSlash != std::string::npos);
+            dirPath = dirPath.substr(0, lastSlash + 1) + ".";
+
+            struct stat s;
+            if (stat(dirPath.c_str(), &s) == -1)
+                return;
+            filesystems.insert(fs(dirPath, s.st_dev));
+        }
+    }
+
+    void checkDiskSpaceOnRegisteredFileSystems()
+    {
+        std::lock_guard<std::mutex> lock(fsmutex);
+
+        static std::chrono::steady_clock::time_point lastCheck;
+        std::chrono::steady_clock::time_point now(std::chrono::steady_clock::now());
+
+        // Don't check more often that once a minute
+        if (std::chrono::duration_cast<std::chrono::seconds>(now - lastCheck).count() < 60)
+            return;
+
+        lastCheck = now;
+
+        for (auto& i: filesystems)
+        {
+            if (!checkDiskSpace(i.path))
+            {
+                alertAllUsersAndLog("File system of " + i.path + " dangerously low on disk space", "internal", "diskfull");
+                break;
+            }
+        }
+    }
+
+    bool checkDiskSpace(const std::string& path)
+    {
+        assert(path != "");
+        struct statfs sfs;
+        if (statfs(path.c_str(), &sfs) == -1)
+            return true;
+
+        if (static_cast<double>(sfs.f_bavail) / sfs.f_blocks <= 0.05)
+            return false;
+        return true;
+    }
+
+} // namespace FileUtil
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/common/FileUtil.hpp b/loolwsd/common/FileUtil.hpp
new file mode 100644
index 0000000..aa6bba6
--- /dev/null
+++ b/loolwsd/common/FileUtil.hpp
@@ -0,0 +1,93 @@
+/* -*- 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/.
+ */
+
+#ifndef INCLUDED_FILEUTIL_HPP
+#define INCLUDED_FILEUTIL_HPP
+
+#include <string>
+
+#include <Poco/File.h>
+#include <Poco/Path.h>
+
+namespace FileUtil
+{
+    /// Create a secure, random directory path.
+    std::string createRandomDir(const std::string& path);
+
+    // Save data to a file (overwriting an existing file if necessary) with checks for errors. Write
+    // to a temporary file in the same directory that is then atomically renamed to the desired name
+    // if everything goes well. In case of any error, both the destination file (if it already
+    // exists) and the temporary file (if was created, or existed already) are removed. Return true
+    // if everything succeeded.
+    bool saveDataToFileSafely(const std::string& fileName, const char* data, size_t size);
+
+    // We work around some of the mess of using the same sources both on the server side and in unit
+    // tests with conditional compilation based on BUILDING_TESTS.
+
+#ifndef BUILDING_TESTS
+    // Send a 'error:' message with the specified cmd and kind parameters to all connected
+    // clients. This function can be called either in loolwsd or loolkit processes, even if only
+    // loolwsd obviously has contact with the actual clients; in loolkit it will be forwarded to
+    // loolwsd for redistribution. (This function must be implemented separately in each program
+    // that uses it, it is not in Util.cpp.)
+    void alertAllUsers(const std::string& cmd, const std::string& kind);
+#else
+    // No-op implementation in the test programs
+    inline void alertAllUsers(const std::string&, const std::string&)
+    {
+    }
+#endif
+
+    // Add the file system that 'path' is located on to a list of file systems that are periodically
+    // checked for available space. The list is initially empty.
+    void registerFileSystemForDiskSpaceChecks(const std::string& path);
+
+    // Perform the check. If the free space on any of the registered file systems is below 5%, call
+    // 'alertAllUsers("internal", "diskfull")'. The check will be made no more often than once a
+    // minute.
+    void checkDiskSpaceOnRegisteredFileSystems();
+
+    // Check disk space on a specific file system, the one where 'path' is located. This does not
+    // add that file system to the list used by 'registerFileSystemForDiskSpaceChecks'. If the free
+    // space on the file system is below 5%, return false, otherwise true. Note that this function
+    // does not call 'alertAllUsers'.
+    bool checkDiskSpace(const std::string& path);
+
+    /// Safely remove a file or directory.
+    /// Supresses exception when the file is already removed.
+    /// This can happen when there is a race (unavoidable) or when
+    /// we don't care to check before we remove (when no race exists).
+    inline void removeFile(const std::string& path, const bool recursive = false)
+    {
+        try
+        {
+            Poco::File(path).remove(recursive);
+        }
+        catch (const std::exception&)
+        {
+            // Already removed or we don't care about failures.
+        }
+    }
+
+    inline void removeFile(const Poco::Path& path, const bool recursive = false)
+    {
+        removeFile(path.toString(), recursive);
+    }
+
+    /// Make a temp copy of a file.
+    /// Primarily used by tests to avoid tainting the originals.
+    /// srcDir shouldn't end with '/' and srcFilename shouldn't contain '/'.
+    /// Returns the created file path.
+    std::string getTempFilePath(const std::string& srcDir, const std::string& srcFilename);
+
+} // end namespace FileUtil
+
+#endif
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/test/Makefile.am b/loolwsd/test/Makefile.am
index 12f30aa..9de6443 100644
--- a/loolwsd/test/Makefile.am
+++ b/loolwsd/test/Makefile.am
@@ -27,6 +27,7 @@ AM_LDFLAGS = -pthread -module $(MAGIC_TO_FORCE_SHLIB_CREATION)
 AM_CPPFLAGS = -pthread -I$(top_srcdir) -DBUILDING_TESTS
 
 wsd_sources = \
+            ../common/FileUtil.cpp \
             ../IoUtil.cpp \
             ../Log.cpp \
             ../LOOLKit.cpp \
diff --git a/loolwsd/test/helpers.hpp b/loolwsd/test/helpers.hpp
index 6b291d8..737601c 100644
--- a/loolwsd/test/helpers.hpp
+++ b/loolwsd/test/helpers.hpp
@@ -40,6 +40,7 @@
 #include <cppunit/extensions/HelperMacros.h>
 
 #include <Common.hpp>
+#include "common/FileUtil.hpp"
 #include <LOOLProtocol.hpp>
 #include <LOOLWebSocket.hpp>
 #include <UserMessages.hpp>
@@ -113,7 +114,7 @@ std::vector<char> readDataFromFile(std::unique_ptr<std::fstream>& file)
 inline
 void getDocumentPathAndURL(const std::string& docFilename, std::string& documentPath, std::string& documentURL)
 {
-    documentPath = Util::getTempFilePath(TDOC, docFilename);
+    documentPath = FileUtil::getTempFilePath(TDOC, docFilename);
     std::string encodedUri;
     Poco::URI::encode("file://" + Poco::Path(documentPath).makeAbsolute().toString(), ":/?", encodedUri);
     documentURL = "lool/" + encodedUri + "/ws";
diff --git a/loolwsd/test/integration-http-server.cpp b/loolwsd/test/integration-http-server.cpp
index 25aea88..9ca4ec1 100644
--- a/loolwsd/test/integration-http-server.cpp
+++ b/loolwsd/test/integration-http-server.cpp
@@ -26,6 +26,7 @@
 #include <cppunit/extensions/HelperMacros.h>
 
 #include <Common.hpp>
+#include "common/FileUtil.hpp"
 #include <Util.hpp>
 
 #include "countloolkits.hpp"
@@ -241,7 +242,7 @@ void HTTPServerTest::testScriptsAndLinksPost()
 
 void HTTPServerTest::testConvertTo()
 {
-    const auto srcPath = Util::getTempFilePath(TDOC, "hello.odt");
+    const auto srcPath = FileUtil::getTempFilePath(TDOC, "hello.odt");
     std::unique_ptr<Poco::Net::HTTPClientSession> session(helpers::createSession(_uri));
     session->setTimeout(Poco::Timespan(2, 0)); // 2 seconds.
 
@@ -265,7 +266,7 @@ void HTTPServerTest::testConvertTo()
     expectedStream << fileStream.rdbuf();
 
     // Remove the temp files.
-    Util::removeFile(srcPath);
+    FileUtil::removeFile(srcPath);
 
     // In some cases the result is prefixed with (the UTF-8 encoding of) the Unicode BOM
     // (U+FEFF). Skip that.


More information about the Libreoffice-commits mailing list