[Libreoffice-commits] online.git: Branch 'distro/collabora/collabora-online-1-0' - 2 commits - loleaflet/dist loleaflet/src loolwsd/Connect.cpp loolwsd/DocumentBroker.cpp loolwsd/DocumentBroker.hpp loolwsd/Exceptions.hpp loolwsd/LOKitClient.cpp loolwsd/LOOLKit.cpp loolwsd/LOOLWSD.cpp loolwsd/Storage.cpp loolwsd/test loolwsd/Util.cpp loolwsd/Util.hpp
Tor Lillqvist
tml at collabora.com
Tue Oct 18 07:27:27 UTC 2016
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 <tml at 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 <tml at 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)
More information about the Libreoffice-commits
mailing list