loleaflet/dist/errormessages.js |    2 
 loleaflet/src/core/Socket.js    |   13 ++++
 loolwsd/Connect.cpp             |   11 ++++
 loolwsd/DocumentBroker.cpp      |   17 ++++++
 loolwsd/DocumentBroker.hpp      |    2 
 loolwsd/Exceptions.hpp          |    6 ++
 loolwsd/LOKitClient.cpp         |   11 ++++
 loolwsd/LOOLKit.cpp             |   64 ++++++++++++++++++++---
 loolwsd/LOOLWSD.cpp             |   64 ++++++++++++++++++++++-
 loolwsd/Storage.cpp             |   13 ++++
 loolwsd/Util.cpp                |  107 ++++++++++++++++++++++++++++++++++++++--
 loolwsd/Util.hpp                |   29 ++++++++++
 loolwsd/test/Makefile.am        |    9 ++-
 13 files changed, 329 insertions(+), 19 deletions(-)

New commits:
commit 98b45399caae9f584ad5f19f28f1756b559ebf99
Author: Tor Lillqvist <t...@collabora.com>
Date:   Mon Oct 17 16:55:20 2016 +0300

    Back-port of attempt to handle unauthorized WOPI usage better
    
    The exception dance is more sad and complex here as we lack some
    re-factoring done in master. The below comment has been left as in
    master and does not necessarily corrrespond 100% to what actually
    happens here in this branch.
    
    Use the previously unused UnauthorizedRequestException for this, and
    throw a such in StorageBase::create() when the WOPI host doesn't match
    any of those configured.
    
    In a developer debug build, without access to any real WOPI
    functionality, you can test by setting the FAKE_UNAUTHORIZED
    environment variable and attempting to edit a plain local file:
    URI. That will cause such an exception to be thrown in that function.
    
    Catch that UnauthorizedRequestException in
    ClientRequestHandler::handleGetRequest(), and send an 'error:
    cmd=internal kind=unauthorized' message to the client. Handle that in
    loleaflet in the same place where the 'error: cmd=internal
    kild=diskfull' message is handled, and in the same fashion, giving up
    on the document.
    
    Actually, using exceptions for relatively non-exceptional situations
    like this is lame and makes understanding the code harder, but that is
    just my personal preference...
    
    FIXME: By the time StorageBase::create() gets called we have already
    sent three 'statusindicator:' messages ('find', 'connect', and
    'ready') to the client. We should ideally do the checks we do in
    StorageBase::create() much earlier.
    
    Also consider that ClientRequestHandler::handleClientRequest() has
    code that catches UnauthorizedRequestException and
    BadRequestException, and tries to set the HTTP response in those
    cases. I am not sure if that functionality has ever been exercised,
    though. Currently, we upgrade the HTTP connection to WebSocket early,
    and only after that we check whether the WOPI host is authorized
    etc. By that time it is too late to return an HTTP response to the
    user. If that even is what we ideally should do? If not, then we
    probably should drop the code that constructs HTTP responses and
    attempts to send them.
    
    Also, if I, as a test, force an HTTPResponse::HTTP_BAD_REQUEST to be
    sent before the HTTP connection is upgraded to WebSocket, loleaflet
    throws up the generic "Well, this is embarrassing" dialog anyway. At
    least in Firefox on Linux. (Instead of the browser showing some own
    dialog, which I was half-expecting to happen.)

diff --git a/loleaflet/dist/errormessages.js b/loleaflet/dist/errormessages.js
index 84c6c3e..51a9aba 100644
--- a/loleaflet/dist/errormessages.js
+++ b/loleaflet/dist/errormessages.js
@@ -2,3 +2,4 @@ var wrongwopisrc = _('Wrong WOPISrc, usage: WOPISrc=valid 
encoded URI, or file_p
 var emptyhosturl = _('The host URL is empty. The loolwsd server is probably 
misconfigured, please contact the administrator.');
 var diskfull = _('No disk space left on server, please contact the server 
administrator to continue.');
 var limitreached = _('This development build is limited to %0 documents, and 
%1 connections - to avoid the impression that it is suitable for deployment in 
large enterprises. To find out more about deploy    ing and scaling %2 
checkout: <br/><a href=\"%3\">%3</a>.');
+var unauthorized = _('Unauthorized WOPI host. Please try again later and 
report to your administrator if the issue persists.');
diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index 7eb34a5..bc68a6a 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -149,6 +149,9 @@ L.Socket = L.Class.extend({
                        if (command.errorKind === 'diskfull') {
                                this._map.fire('error', {msg: diskfull});
                        }
+                       else if (command.errorKind === 'unauthorized') {
+                               this._map.fire('error', {msg: unauthorized});
+                       }
 
                        this.close();
 
diff --git a/loolwsd/DocumentBroker.cpp b/loolwsd/DocumentBroker.cpp
index 59e1a40..3f5a421 100644
--- a/loolwsd/DocumentBroker.cpp
+++ b/loolwsd/DocumentBroker.cpp
@@ -127,6 +127,11 @@ const StorageBase::FileInfo DocumentBroker::validate(const 
Poco::URI& uri)
 
         return fileinfo;
     }
+    catch (const UnauthorizedRequestException&)
+    {
+        // Sigh...
+        throw;
+    }
     catch (const std::exception&)
     {
         throw BadRequestException("Invalid URI or access denied.");
diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index 9a7077c..0e33abd 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -691,8 +691,24 @@ private:
         }
 
         // Validate the URI and Storage before moving on.
-        const auto fileinfo = docBroker->validate(uriPublic);
-        Log::debug("Validated [" + uriPublic.toString() + "]");
+        StorageBase::FileInfo fileinfo;
+        try
+        {
+            fileinfo = docBroker->validate(uriPublic);
+            Log::debug("Validated [" + uriPublic.toString() + "]");
+        }
+        catch (const UnauthorizedRequestException& exc)
+        {
+            // This is more convoluted here than in the master branch. In 
master there is no
+            // docBroker->validate() call as above, and the 
UnauthorizedRequestException is thrown
+            // later, and is caught from the try block below. But we need to 
catch it here to be
+            // able to send the 'error:' message to the client before the 
socket gets closed.
+            Log::error("Error in client request handler: " + 
std::string(exc.what()));
+            status = "error: cmd=internal kind=unauthorized";
+            Log::trace("Sending to Client [" + status + "].");
+            ws->sendFrame(status.data(), (int) status.size());
+            throw;
+        }
 
         if (newDoc)
         {
@@ -1028,10 +1044,17 @@ public:
             Log::error("ClientRequestHandler::handleRequest:: Unexpected 
exception");
         }
 
+        if (responded)
+            Log::debug("Already sent response!?");
         if (!responded)
         {
+            Log::debug("Attempting to send response");
             response.setContentLength(0);
-            response.send();
+            std::ostream& os = response.send();
+            if (!os.good())
+                Log::debug("Response stream is not good after send");
+            else
+                Log::debug("Response stream *is* good after send");
         }
 
         Log::debug("Thread finished.");
diff --git a/loolwsd/Storage.cpp b/loolwsd/Storage.cpp
index a105555..b53260f 100644
--- a/loolwsd/Storage.cpp
+++ b/loolwsd/Storage.cpp
@@ -143,6 +143,13 @@ std::unique_ptr<StorageBase> StorageBase::create(const 
std::string& jailRoot, co
     else if (uri.isRelative() || uri.getScheme() == "file")
     {
         Log::info("Public URI [" + uri.toString() + "] is a file.");
+#if ENABLE_DEBUG
+        if (std::getenv("FAKE_UNAUTHORIZED"))
+        {
+            Log::error("Faking an UnauthorizedRequestException");
+            throw UnauthorizedRequestException("No acceptable WOPI hosts found 
matching the target host in config.");
+        }
+#endif
         if (_filesystemEnabled)
         {
             return std::unique_ptr<StorageBase>(new LocalStorage(jailRoot, 
jailPath, uri.getPath()));
@@ -159,7 +166,7 @@ std::unique_ptr<StorageBase> StorageBase::create(const 
std::string& jailRoot, co
             return std::unique_ptr<StorageBase>(new WopiStorage(jailRoot, 
jailPath, uri.toString()));
         }
 
-        Log::error("No acceptable WOPI hosts found matching the target host [" 
+ targetHost + "] in config.");
+        throw UnauthorizedRequestException("No acceptable WOPI hosts found 
matching the target host [" + targetHost + "] in config.");
     }
 
     throw BadRequestException("No Storage configured or invalid URI.");
commit 315e6c6778cfed7615861a63154ebbc68b478cd1
Author: Tor Lillqvist <t...@collabora.com>
Date:   Thu Oct 13 15:33:14 2016 +0300

    Back-port of disk space checking
    
    See master branch for full commit messages.
    
    Change-Id: I1f21b5ce4d23bb45e2f758b6da10edf0d5e53245

diff --git a/loleaflet/dist/errormessages.js b/loleaflet/dist/errormessages.js
index 0ea91ff..84c6c3e 100644
--- a/loleaflet/dist/errormessages.js
+++ b/loleaflet/dist/errormessages.js
@@ -1,3 +1,4 @@
 var wrongwopisrc = _('Wrong WOPISrc, usage: WOPISrc=valid encoded URI, or 
file_path, usage: file_path=/path/to/doc/');
 var emptyhosturl = _('The host URL is empty. The loolwsd server is probably 
misconfigured, please contact the administrator.');
+var diskfull = _('No disk space left on server, please contact the server 
administrator to continue.');
 var limitreached = _('This development build is limited to %0 documents, and 
%1 connections - to avoid the impression that it is suitable for deployment in 
large enterprises. To find out more about deploy    ing and scaling %2 
checkout: <br/><a href=\"%3\">%3</a>.');
diff --git a/loleaflet/src/core/Socket.js b/loleaflet/src/core/Socket.js
index 4cf4ef8..7eb34a5 100644
--- a/loleaflet/src/core/Socket.js
+++ b/loleaflet/src/core/Socket.js
@@ -144,6 +144,16 @@ L.Socket = L.Class.extend({
                                                 lokitVersionObj.ProductVersion 
+ lokitVersionObj.ProductExtension.replace('.10.','-') +
                                                 ' (git hash: ' + 
lokitVersionObj.BuildId.substring(0, 7) + ')');
                }
+               else if (textMsg.startsWith('error:') && command.errorCmd === 
'internal') {
+                       this._map._fatal = true;
+                       if (command.errorKind === 'diskfull') {
+                               this._map.fire('error', {msg: diskfull});
+                       }
+
+                       this.close();
+
+                       return;
+               }
                else if (textMsg.startsWith('error:') && command.errorCmd === 
'load') {
                        this.close();
 
diff --git a/loolwsd/Connect.cpp b/loolwsd/Connect.cpp
index 3765399..fe7bc8e 100644
--- a/loolwsd/Connect.cpp
+++ b/loolwsd/Connect.cpp
@@ -237,6 +237,17 @@ private:
     URI _uri;
 };
 
+namespace Util
+{
+
+void alertAllUsers(const std::string& cmd, const std::string& kind)
+{
+    std::cout << "error: cmd=" << cmd << " kind=" << kind << std::endl;
+    (void) kind;
+}
+
+}
+
 POCO_APP_MAIN(Connect)
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/DocumentBroker.cpp b/loolwsd/DocumentBroker.cpp
index 2bd8716..59e1a40 100644
--- a/loolwsd/DocumentBroker.cpp
+++ b/loolwsd/DocumentBroker.cpp
@@ -441,6 +441,18 @@ size_t DocumentBroker::removeSession(const std::string& id)
     return _sessions.size();
 }
 
+void DocumentBroker::alertAllUsersOfDocument(const std::string& cmd, const 
std::string& kind)
+{
+    std::lock_guard<std::mutex> lock(_mutex);
+
+    std::stringstream ss;
+    ss << "error: cmd=" << cmd << " kind=" << kind;
+    for (auto& it: _sessions)
+    {
+        it.second->sendTextFrame(ss.str());
+    }
+}
+
 bool DocumentBroker::handleInput(const std::vector<char>& payload)
 {
     Log::trace("DocumentBroker got child message: [" + 
LOOLProtocol::getAbbreviatedMessage(payload) + "].");
diff --git a/loolwsd/DocumentBroker.hpp b/loolwsd/DocumentBroker.hpp
index c15c293..f078f21 100644
--- a/loolwsd/DocumentBroker.hpp
+++ b/loolwsd/DocumentBroker.hpp
@@ -197,6 +197,8 @@ public:
     /// Removes a session by ID. Returns the new number of sessions.
     size_t removeSession(const std::string& id);
 
+    void alertAllUsersOfDocument(const std::string& cmd, const std::string& 
kind);
+
     void handleTileRequest(int part, int width, int height, int tilePosX,
                            int tilePosY, int tileWidth, int tileHeight, int id,
                            const std::shared_ptr<MasterProcessSession>& 
session);
diff --git a/loolwsd/Exceptions.hpp b/loolwsd/Exceptions.hpp
index 0384bde..572896c 100644
--- a/loolwsd/Exceptions.hpp
+++ b/loolwsd/Exceptions.hpp
@@ -21,6 +21,12 @@ protected:
     using std::runtime_error::runtime_error;
 };
 
+class StorageSpaceLowException : public LoolException
+{
+public:
+    using LoolException::LoolException;
+};
+
 /// A bad-request exception that is means to signify,
 /// and translate into, an HTTP bad request.
 class BadRequestException : public LoolException
diff --git a/loolwsd/LOKitClient.cpp b/loolwsd/LOKitClient.cpp
index dcbd28d..61f194c 100644
--- a/loolwsd/LOKitClient.cpp
+++ b/loolwsd/LOKitClient.cpp
@@ -202,6 +202,17 @@ protected:
     }
 };
 
+namespace Util
+{
+
+void alertAllUsers(const std::string& cmd, const std::string& kind)
+{
+    std::cout << "error: cmd=" << cmd << " kind=" << kind << std::endl;
+    (void) kind;
+}
+
+}
+
 POCO_APP_MAIN(LOKitClient)
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/LOOLKit.cpp b/loolwsd/LOOLKit.cpp
index e989d9f..de4f71d 100644
--- a/loolwsd/LOOLKit.cpp
+++ b/loolwsd/LOOLKit.cpp
@@ -39,6 +39,7 @@
 #include <Poco/Net/HTTPRequest.h>
 #include <Poco/Net/HTTPResponse.h>
 #include <Poco/Net/NetException.h>
+#include <Poco/Net/Socket.h>
 #include <Poco/Net/WebSocket.h>
 #include <Poco/Process.h>
 #include <Poco/Runnable.h>
@@ -74,6 +75,7 @@ using Poco::Net::NetException;
 using Poco::Net::HTTPClientSession;
 using Poco::Net::HTTPResponse;
 using Poco::Net::HTTPRequest;
+using Poco::Net::Socket;
 using Poco::Net::WebSocket;
 using Poco::Path;
 using Poco::Process;
@@ -84,6 +86,10 @@ using Poco::Timestamp;
 using Poco::Util::Application;
 using Poco::URI;
 
+// We only host a single document in our lifetime.
+class Document;
+static std::shared_ptr<Document> document;
+
 namespace
 {
     typedef enum { COPY_ALL, COPY_LO, COPY_NO_USR } LinkOrCopyType;
@@ -359,7 +365,8 @@ public:
     Document(LibreOfficeKit *loKit,
              const std::string& jailId,
              const std::string& docKey,
-             const std::string& url)
+             const std::string& url,
+             std::shared_ptr<WebSocket>& ws)
       : _multiView(std::getenv("LOK_VIEW_CALLBACK")),
         _loKit(loKit),
         _jailId(jailId),
@@ -371,7 +378,8 @@ public:
         _isDocPasswordProtected(false),
         _docPasswordType(PasswordType::ToView),
         _isLoading(0),
-        _clientViews(0)
+        _clientViews(0),
+        _ws(ws)
     {
         Log::info("Document ctor for url [" + _url + "] on child [" + _jailId +
                   "] LOK_VIEW_CALLBACK=" + std::to_string(_multiView) + ".");
@@ -690,6 +698,36 @@ public:
         ws->sendFrame(output.data(), length, WebSocket::FRAME_BINARY);
     }
 
+    bool sendTextFrame(const std::string& message)
+    {
+        try
+        {
+            if (!_ws || _ws->poll(Poco::Timespan(0), 
Socket::SelectMode::SELECT_ERROR))
+            {
+                Log::error("Child Doc: Bad socket while sending [" + 
getAbbreviatedMessage(message) + "].");
+                return false;
+            }
+
+            const auto length = message.size();
+            if (length > SMALL_MESSAGE_SIZE)
+            {
+                const std::string nextmessage = "nextmessage: size=" + 
std::to_string(length);
+                _ws->sendFrame(nextmessage.data(), nextmessage.size());
+            }
+
+            _ws->sendFrame(message.data(), length);
+            return true;
+        }
+        catch (const Exception& exc)
+        {
+            Log::error() << "Document::sendTextFrame: "
+                         << "Exception: " << exc.displayText()
+                         << (exc.nested() ? "( " + exc.nested()->displayText() 
+ ")" : "");
+        }
+
+        return false;
+    }
+
     void sendCombinedTiles(const char* /*buffer*/, int /*length*/, 
StringTokenizer& /*tokens*/)
     {
         // This is unnecessary at this point, since the DocumentBroker will 
send us individual
@@ -1164,6 +1202,7 @@ private:
     std::atomic_size_t _isLoading;
     std::map<unsigned, std::shared_ptr<Connection>> _connections;
     std::atomic_size_t _clientViews;
+    std::shared_ptr<WebSocket> _ws;
 };
 
 namespace {
@@ -1204,9 +1243,6 @@ void lokit_main(const std::string& childRoot,
     assert(!loTemplate.empty());
     assert(!loSubPath.empty());
 
-    // We only host a single document in our lifetime.
-    std::shared_ptr<Document> document;
-
     // Ideally this will be a random ID, but forkit will cleanup
     // our jail directory when we die, and it's simpler to know
     // the jailId (i.e. the path) implicitly by knowing our pid.
@@ -1364,7 +1400,7 @@ void lokit_main(const std::string& childRoot,
 
         const std::string socketName = "ChildControllerWS";
         IoUtil::SocketProcessor(ws,
-                [&socketName, &ws, &document, &loKit](const std::vector<char>& 
data)
+                [&socketName, &ws, &loKit](const std::vector<char>& data)
                 {
                     std::string message(data.data(), data.size());
 
@@ -1390,7 +1426,7 @@ void lokit_main(const std::string& childRoot,
 
                         if (!document)
                         {
-                            document = std::make_shared<Document>(loKit, 
jailId, docKey, url);
+                            document = std::make_shared<Document>(loKit, 
jailId, docKey, url, ws);
                         }
 
                         // Validate and create session.
@@ -1419,7 +1455,7 @@ void lokit_main(const std::string& childRoot,
                     return true;
                 },
                 []() {},
-                [&document]()
+                []()
                 {
                     if (document && document->canDiscard())
                         TerminationFlag = true;
@@ -1520,4 +1556,16 @@ bool globalPreinit(const std::string &loTemplate)
     return true;
 }
 
+namespace Util
+{
+
+#ifndef BUILDING_TESTS
+void alertAllUsers(const std::string& cmd, const std::string& kind)
+{
+    document->sendTextFrame("errortoall: cmd=" + cmd + " kind=" + kind);
+}
+#endif
+
+}
+
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp
index bb1a9c6..9a7077c 100644
--- a/loolwsd/LOOLWSD.cpp
+++ b/loolwsd/LOOLWSD.cpp
@@ -221,6 +221,7 @@ static void forkChildren(const int number)
 
     if (number > 0)
     {
+        Util::checkDiskSpaceOnRegisteredFileSystems();
         const std::string aMessage = "spawn " + std::to_string(number) + "\n";
         Log::debug("MasterToForKit: " + aMessage.substr(0, aMessage.length() - 
1));
         IoUtil::writeFIFO(LOOLWSD::ForKitWritePipe, aMessage);
@@ -741,6 +742,8 @@ private:
             Log::trace("Sending to Client [" + status + "].");
             ws->sendFrame(status.data(), (int) status.size());
 
+            Util::checkDiskSpaceOnRegisteredFileSystems();
+
             QueueHandler handler(queue, session, "wsd_queue_" + 
session->getId());
             Thread queueHandlerThread;
             queueHandlerThread.start(handler);
@@ -1153,7 +1156,19 @@ public:
                 }
             }
 
-            docBroker->load(jailId);
+            try
+            {
+                docBroker->load(jailId);
+            }
+            catch (const StorageSpaceLowException&)
+            {
+                // We use the same message as is sent when some of lool's own 
locations are full,
+                // even if in this case it might be a totally different 
location (file system, or
+                // some other type of storage somewhere). This message is not 
sent to all clients,
+                // though, just to all sessions of this document.
+                docBroker->alertAllUsersOfDocument("internal", "diskfull");
+                throw;
+            }
 
             auto ws = std::make_shared<WebSocket>(request, response);
             auto session = std::make_shared<MasterProcessSession>(sessionId, 
LOOLSession::Kind::ToPrisoner, ws, docBroker, nullptr);
@@ -1703,6 +1718,9 @@ int LOOLWSD::main(const std::vector<std::string>& 
/*args*/)
     else if (ChildRoot[ChildRoot.size() - 1] != '/')
         ChildRoot += '/';
 
+    Util::registerFileSystemForDiskSpaceChecks(ChildRoot);
+    Util::registerFileSystemForDiskSpaceChecks(Cache + "/.");
+
     if (FileServerRoot.empty())
         FileServerRoot = 
Poco::Path(Application::instance().commandPath()).parent().parent().toString();
     FileServerRoot = Poco::Path(FileServerRoot).absolute().toString();
@@ -1944,6 +1962,21 @@ void UnitWSD::testHandleRequest(TestRequest type, 
UnitHTTPServerRequest& request
     }
 }
 
+namespace Util
+{
+
+void alertAllUsers(const std::string& cmd, const std::string& kind)
+{
+    std::lock_guard<std::mutex> docBrokersLock(docBrokersMutex);
+
+    for (auto& brokerIt : docBrokers)
+    {
+        brokerIt.second->alertAllUsersOfDocument(cmd, kind);
+    }
+}
+
+}
+
 POCO_SERVER_MAIN(LOOLWSD)
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/Storage.cpp b/loolwsd/Storage.cpp
index a4043b3..a105555 100644
--- a/loolwsd/Storage.cpp
+++ b/loolwsd/Storage.cpp
@@ -191,6 +191,10 @@ std::string LocalStorage::loadStorageFileToLocal()
               "] jailed to [" + _jailedFilePath + "].");
 
     const auto publicFilePath = _uri;
+
+    if (!Util::checkDiskSpace(publicFilePath))
+        throw StorageSpaceLowException("Low disk space for " + publicFilePath);
+
     Log::info("Linking " + publicFilePath + " to " + _jailedFilePath);
     if (!Poco::File(_jailedFilePath).exists() && link(publicFilePath.c_str(), 
_jailedFilePath.c_str()) == -1)
     {
diff --git a/loolwsd/Util.cpp b/loolwsd/Util.cpp
index 8caf37f..0aae00f 100644
--- a/loolwsd/Util.cpp
+++ b/loolwsd/Util.cpp
@@ -13,7 +13,9 @@
 #include <signal.h>
 #include <sys/poll.h>
 #include <sys/prctl.h>
+#include <sys/stat.h>
 #include <sys/uio.h>
+#include <sys/vfs.h>
 #include <unistd.h>
 
 #include <atomic>
@@ -130,6 +132,15 @@ 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)
@@ -179,7 +190,7 @@ namespace Util
         // If we can't create the file properly, just remove it
         if (!outStream.good())
         {
-            Log::error("Creating " + tempFileName + " failed, disk full?");
+            alertAllUsersAndLog("Creating " + tempFileName + " failed, disk 
full?", "internal", "diskfull");
             // Try removing both just in case
             std::remove(tempFileName.c_str());
             std::remove(fileName.c_str());
@@ -190,7 +201,7 @@ namespace Util
             outStream.write(data, size);
             if (!outStream.good())
             {
-                Log::error("Writing to " + tempFileName + " failed, disk 
full?");
+                alertAllUsersAndLog("Writing to " + tempFileName + " failed, 
disk full?", "internal", "diskfull");
                 outStream.close();
                 std::remove(tempFileName.c_str());
                 std::remove(fileName.c_str());
@@ -201,7 +212,7 @@ namespace Util
                 outStream.close();
                 if (!outStream.good())
                 {
-                    Log::error("Closing " + tempFileName + " failed, disk 
full?");
+                    alertAllUsersAndLog("Closing " + tempFileName + " failed, 
disk full?", "internal", "diskfull");
                     std::remove(tempFileName.c_str());
                     std::remove(fileName.c_str());
                     return false;
@@ -216,7 +227,7 @@ namespace Util
                     }
                     else
                     {
-                        Log::error("Renaming " + tempFileName + " to " + 
fileName + " failed, disk full?");
+                        alertAllUsersAndLog("Renaming " + tempFileName + " to 
" + fileName + " failed, disk full?", "internal", "diskfull");
                         std::remove(tempFileName.c_str());
                         std::remove(fileName.c_str());
                         return false;
@@ -226,6 +237,94 @@ namespace Util
         }
     }
 
+} // 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;
+    }
+
     bool encodeBufferToPNG(unsigned char *pixmap, int width, int height, 
std::vector<char>& output, LibreOfficeKitTileMode mode)
     {
 
diff --git a/loolwsd/Util.hpp b/loolwsd/Util.hpp
index 17142f1..bf929e5 100644
--- a/loolwsd/Util.hpp
+++ b/loolwsd/Util.hpp
@@ -56,6 +56,35 @@ namespace Util
     // if everything succeeded.
     bool saveDataToFileSafely(std::string fileName, const char *data, size_t 
size);
 
+#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);
+
     // Sadly, older libpng headers don't use const for the pixmap pointer 
parameter to
     // png_write_row(), so can't use const here for pixmap.
     bool encodeBufferToPNG(unsigned char* pixmap, int width, int height,
diff --git a/loolwsd/test/Makefile.am b/loolwsd/test/Makefile.am
index a01478d..9f7a2b9 100644
--- a/loolwsd/test/Makefile.am
+++ b/loolwsd/test/Makefile.am
@@ -15,7 +15,12 @@ noinst_LTLIBRARIES = \
 
 MAGIC_TO_FORCE_SHLIB_CREATION = -rpath /dummy
 AM_LDFLAGS = -pthread -module $(MAGIC_TO_FORCE_SHLIB_CREATION)
-AM_CPPFLAGS = -pthread -I$(top_srcdir)
+
+# We work around some of the mess of using the same sources both on
+# the server side and here in unit tests with conditional compilation
+# based on BUILDING_TESTS
+
+AM_CPPFLAGS = -pthread -I$(top_srcdir) -DBUILDING_TESTS
 
 wsd_sources = \
             ../IoUtil.cpp \
@@ -26,7 +31,7 @@ wsd_sources = \
             ../Unit.cpp \
             ../Util.cpp
 
-test_CPPFLAGS = -DTDOC=\"$(top_srcdir)/test/data\" -I$(top_srcdir)
+test_CPPFLAGS = -DTDOC=\"$(top_srcdir)/test/data\" -I$(top_srcdir) 
-DBUILDING_TESTS
 test_SOURCES = TileCacheTests.cpp WhiteBoxTests.cpp 
integration-http-server.cpp \
                httpwstest.cpp httpcrashtest.cpp httpwserror.cpp test.cpp 
$(wsd_sources)
 test_LDADD = $(CPPUNIT_LIBS)
_______________________________________________
Libreoffice-commits mailing list
libreoffice-comm...@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to