[Libreoffice-commits] online.git: loolwsd/LOOLBroker.cpp loolwsd/LOOLKit.cpp loolwsd/PROBLEMS
Tor Lillqvist
tml at collabora.com
Mon Apr 4 06:24:48 UTC 2016
loolwsd/LOOLBroker.cpp | 1085 +++++++++++++++++++++++++++++++++++++++++++++++-
loolwsd/LOOLKit.cpp | 1107 -------------------------------------------------
loolwsd/PROBLEMS | 2
3 files changed, 1079 insertions(+), 1115 deletions(-)
New commits:
commit 8e7196ffa09479bd9c18e61ed19fd7624f6ff9af
Author: Tor Lillqvist <tml at collabora.com>
Date: Mon Apr 4 09:22:35 2016 +0300
Insert LOOLKit.cpp contents into LOOLBroker.cpp
As we don't build a separate loolkit program any more we don't need
LOOLKit.cpp.
diff --git a/loolwsd/LOOLBroker.cpp b/loolwsd/LOOLBroker.cpp
index defcfae..e0fa29a 100644
--- a/loolwsd/LOOLBroker.cpp
+++ b/loolwsd/LOOLBroker.cpp
@@ -7,22 +7,48 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+#include <dlfcn.h>
+#include <ftw.h>
+#include <sys/prctl.h>
#include <sys/wait.h>
+#include <unistd.h>
+#include <utime.h>
+#include <atomic>
+#include <cassert>
+#include <condition_variable>
#include <cstdlib>
#include <cstring>
+#include <iostream>
+
+#include <memory>
+#define LOK_USE_UNSTABLE_API
+#include <LibreOfficeKit/LibreOfficeKitInit.h>
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+
+#include <Poco/Exception.h>
+#include <Poco/Mutex.h>
+#include <Poco/Net/HTTPClientSession.h>
+#include <Poco/Net/HTTPRequest.h>
+#include <Poco/Net/HTTPResponse.h>
+#include <Poco/Net/NetException.h>
+#include <Poco/Net/WebSocket.h>
+#include <Poco/Process.h>
+#include <Poco/Runnable.h>
+#include <Poco/StringTokenizer.h>
+#include <Poco/Thread.h>
+#include <Poco/Util/Application.h>
+#include <Poco/URI.h>
-#include "Common.hpp"
#include "Capabilities.hpp"
+#include "ChildProcessSession.hpp"
+#include "Common.hpp"
#include "IoUtil.hpp"
+#include "LOKitHelper.hpp"
+#include "LOOLProtocol.hpp"
+#include "QueueHandler.hpp"
#include "Util.hpp"
-// First include the grist of the helper process - ideally
-// we can avoid execve and share lots of memory here. We
-// can't link to a non-PIC translation unit though, so
-// include to share.
-#include "LOOLKit.cpp"
-
#define LIB_SOFFICEAPP "lib" "sofficeapp" ".so"
#define LIB_MERGED "lib" "mergedlo" ".so"
@@ -30,13 +56,1058 @@ typedef int (LokHookPreInit) (const char *install_path, const char *user_profil
const std::string BROKER_SUFIX = ".fifo";
const std::string BROKER_PREFIX = "lokit";
+const std::string FIFO_ADMIN_NOTIFY = "lool_admin_notify.fifo";
+static int WriterNotify = -1;
static int ReaderBroker = -1;
static std::atomic<unsigned> ForkCounter;
static unsigned int ChildCounter = 0;
static int NumPreSpawnedChildren = 1;
+using namespace LOOLProtocol;
+
+using Poco::File;
+using Poco::Net::HTTPRequest;
+using Poco::Net::WebSocket;
+using Poco::Path;
+using Poco::Process;
+using Poco::Runnable;
+using Poco::StringTokenizer;
+using Poco::Thread;
+using Poco::Util::Application;
+
+namespace
+{
+ typedef enum { COPY_ALL, COPY_LO, COPY_NO_USR } LinkOrCopyType;
+ LinkOrCopyType linkOrCopyType;
+ std::string sourceForLinkOrCopy;
+ Path destinationForLinkOrCopy;
+
+ bool shouldCopyDir(const char *path)
+ {
+ switch (linkOrCopyType)
+ {
+ case COPY_NO_USR:
+ // bind mounted.
+ return strcmp(path,"usr");
+ case COPY_LO:
+ return
+ strcmp(path, "program/wizards") &&
+ strcmp(path, "sdk") &&
+ strcmp(path, "share/basic") &&
+ strcmp(path, "share/gallery") &&
+ strcmp(path, "share/Scripts") &&
+ strcmp(path, "share/template") &&
+ strcmp(path, "share/config/wizard") &&
+ strcmp(path, "share/config/wizard");
+ default: // COPY_ALL
+ return true;
+ }
+ }
+
+ int linkOrCopyFunction(const char *fpath,
+ const struct stat* /*sb*/,
+ int typeflag,
+ struct FTW* /*ftwbuf*/)
+ {
+ if (strcmp(fpath, sourceForLinkOrCopy.c_str()) == 0)
+ return 0;
+
+ assert(fpath[strlen(sourceForLinkOrCopy.c_str())] == '/');
+ const char *relativeOldPath = fpath + strlen(sourceForLinkOrCopy.c_str()) + 1;
+ Path newPath(destinationForLinkOrCopy, Path(relativeOldPath));
+
+ switch (typeflag)
+ {
+ case FTW_F:
+ File(newPath.parent()).createDirectories();
+ if (link(fpath, newPath.toString().c_str()) == -1)
+ {
+ Log::error("Error: link(\"" + std::string(fpath) + "\",\"" + newPath.toString() +
+ "\") failed. Exiting.");
+ std::exit(Application::EXIT_SOFTWARE);
+ }
+ break;
+ case FTW_D:
+ {
+ struct stat st;
+ if (stat(fpath, &st) == -1)
+ {
+ Log::error("Error: stat(\"" + std::string(fpath) + "\") failed.");
+ return 1;
+ }
+ if (!shouldCopyDir(relativeOldPath))
+ {
+ Log::trace("skip redundant paths " + std::string(relativeOldPath));
+ return FTW_SKIP_SUBTREE;
+ }
+ File(newPath).createDirectories();
+ struct utimbuf ut;
+ ut.actime = st.st_atime;
+ ut.modtime = st.st_mtime;
+ if (utime(newPath.toString().c_str(), &ut) == -1)
+ {
+ Log::error("Error: utime(\"" + newPath.toString() + "\", &ut) failed.");
+ return 1;
+ }
+ }
+ break;
+ case FTW_DNR:
+ Log::error("Cannot read directory '" + std::string(fpath) + "'");
+ return 1;
+ case FTW_NS:
+ Log::error("nftw: stat failed for '" + std::string(fpath) + "'");
+ return 1;
+ case FTW_SLN:
+ Log::error("nftw: symlink to nonexistent file: '" + std::string(fpath) + "', ignored.");
+ break;
+ default:
+ Log::error("nftw: unexpected type: '" + std::to_string(typeflag));
+ assert(false);
+ break;
+ }
+ return 0;
+ }
+
+ void linkOrCopy(const std::string& source,
+ const Path& destination,
+ LinkOrCopyType type)
+ {
+ linkOrCopyType = type;
+ sourceForLinkOrCopy = source;
+ if (sourceForLinkOrCopy.back() == '/')
+ sourceForLinkOrCopy.pop_back();
+ destinationForLinkOrCopy = destination;
+ if (nftw(source.c_str(), linkOrCopyFunction, 10, FTW_ACTIONRETVAL) == -1)
+ Log::error("linkOrCopy: nftw() failed for '" + source + "'");
+ }
+}
+
+class Connection: public Runnable
+{
+public:
+ Connection(std::shared_ptr<ChildProcessSession> session,
+ std::shared_ptr<WebSocket> ws) :
+ _session(session),
+ _ws(ws),
+ _stop(false)
+ {
+ Log::info("Connection ctor in child for " + _session->getId());
+ }
+
+ ~Connection()
+ {
+ Log::info("~Connection dtor in child for " + _session->getId());
+ stop();
+ }
+
+ std::shared_ptr<WebSocket> getWebSocket() const { return _ws; }
+ std::shared_ptr<ChildProcessSession> getSession() { return _session; }
+
+ void start()
+ {
+ _thread.start(*this);
+ }
+
+ bool isRunning()
+ {
+ return _thread.isRunning();
+ }
+
+ void stop()
+ {
+ _stop = true;
+ }
+
+ void join()
+ {
+ _thread.join();
+ }
+
+ void handle(std::shared_ptr<TileQueue> queue, const std::string& firstLine, char* buffer, int n)
+ {
+ if (firstLine.find("paste") != 0)
+ {
+ // Everything else is expected to be a single line.
+ assert(firstLine.size() == static_cast<std::string::size_type>(n));
+ queue->put(firstLine);
+ }
+ else
+ queue->put(std::string(buffer, n));
+ }
+
+ void run() override
+ {
+ const std::string thread_name = "kit_ws_" + _session->getId();
+
+ if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(thread_name.c_str()), 0, 0, 0) != 0)
+ Log::error("Cannot set thread name to " + thread_name + ".");
+
+ Log::debug("Thread [" + thread_name + "] started.");
+
+ try
+ {
+ auto queue = std::make_shared<TileQueue>();
+ QueueHandler handler(queue, _session, "kit_queue_" + _session->getId());
+
+ Thread queueHandlerThread;
+ queueHandlerThread.start(handler);
+
+ int flags;
+ int n;
+ do
+ {
+ char buffer[1024];
+ n = _ws->receiveFrame(buffer, sizeof(buffer), flags);
+ if (n > 0 && (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE)
+ {
+ std::string firstLine = getFirstLine(buffer, n);
+ if (firstLine == "eof")
+ {
+ Log::info("Received EOF. Finishing.");
+ break;
+ }
+
+ StringTokenizer tokens(firstLine, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+
+ if (firstLine == "disconnect")
+ {
+ Log::info("Client disconnected [" + (tokens.count() == 2 ? tokens[1] : std::string("no reason")) + "].");
+ break;
+ }
+
+ // Check if it is a "nextmessage:" and in that case read the large
+ // follow-up message separately, and handle that only.
+ int size;
+ if (tokens.count() == 2 && tokens[0] == "nextmessage:" && getTokenInteger(tokens[1], "size", size) && size > 0)
+ {
+ char largeBuffer[size];
+ n = _ws->receiveFrame(largeBuffer, size, flags);
+ if (n > 0 && (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE)
+ {
+ firstLine = getFirstLine(largeBuffer, n);
+ handle(queue, firstLine, largeBuffer, n);
+ }
+ }
+ else
+ handle(queue, firstLine, buffer, n);
+ }
+ }
+ while (!_stop && n > 0 && (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE);
+ Log::debug() << "Finishing " << thread_name << ". stop " << _stop
+ << ", payload size: " << n
+ << ", flags: " << std::hex << flags << Log::end;
+
+ queue->clear();
+ queue->put("eof");
+ queueHandlerThread.join();
+
+ _session->disconnect();
+ }
+ catch (const Poco::Exception& exc)
+ {
+ Log::error() << "Connection::run: Exception: " << exc.displayText()
+ << (exc.nested() ? " (" + exc.nested()->displayText() + ")" : "")
+ << Log::end;
+ }
+ catch (const std::exception& exc)
+ {
+ Log::error(std::string("Connection::run: Exception: ") + exc.what());
+ }
+ catch (...)
+ {
+ Log::error("Connection::run:: Unexpected exception");
+ }
+
+ Log::debug("Thread [" + thread_name + "] finished.");
+ }
+
+private:
+ Thread _thread;
+ std::shared_ptr<ChildProcessSession> _session;
+ std::shared_ptr<WebSocket> _ws;
+ volatile bool _stop;
+};
+
+/// A document container.
+/// Owns LOKitDocument instance and connections.
+/// Manages the lifetime of a document.
+/// Technically, we can host multiple documents
+/// per process. But for security reasons don't.
+/// However, we could have a loolkit instance
+/// per user or group of users (a trusted circle).
+class Document
+{
+public:
+ /// We have two types of password protected documents
+ /// 1) Documents which require password to view
+ /// 2) Document which require password to modify
+ enum class PasswordType { ToView, ToModify };
+
+ Document(LibreOfficeKit *loKit,
+ const std::string& jailId,
+ const std::string& docKey,
+ const std::string& url)
+ : _multiView(std::getenv("LOK_VIEW_CALLBACK")),
+ _loKit(loKit),
+ _jailId(jailId),
+ _docKey(docKey),
+ _url(url),
+ _loKitDocument(nullptr),
+ _docPassword(""),
+ _isDocPasswordProvided(false),
+ _isDocPasswordProtected(false),
+ _docPasswordType(PasswordType::ToView),
+ _isLoading(0),
+ _clientViews(0)
+ {
+ Log::info("Document ctor for url [" + _url + "] on child [" + _jailId +
+ "] LOK_VIEW_CALLBACK=" + std::to_string(_multiView) + ".");
+ }
+
+ ~Document()
+ {
+ Log::info("~Document dtor for url [" + _url + "] on child [" + _jailId +
+ "]. There are " + std::to_string(_clientViews) + " views.");
+
+ // Flag all connections to stop.
+ for (auto aIterator : _connections)
+ {
+ aIterator.second->stop();
+ }
+
+ // Destroy all connections and views.
+ for (auto aIterator : _connections)
+ {
+ try
+ {
+ // stop all websockets
+ if (aIterator.second->isRunning())
+ {
+ std::shared_ptr<WebSocket> ws = aIterator.second->getWebSocket();
+ if (ws)
+ {
+ ws->shutdownReceive();
+ aIterator.second->join();
+ }
+ }
+ }
+ catch(Poco::Net::NetException& exc)
+ {
+ Log::error() << "Document::~Document: " << exc.displayText()
+ << (exc.nested() ? " (" + exc.nested()->displayText() + ")" : "")
+ << Log::end;
+ }
+ }
+
+ // Destroy all connections and views.
+ _connections.clear();
+
+ // TODO. check what is happening when destroying lokit document,
+ // often it blows up.
+ // Destroy the document.
+ if (_loKitDocument != nullptr)
+ {
+ try
+ {
+ _loKitDocument->pClass->destroy(_loKitDocument);
+ }
+ catch (const std::exception& exc)
+ {
+ Log::error() << "Document::~Document: " << exc.what()
+ << Log::end;
+ }
+ }
+ }
+
+ const std::string& getUrl() const { return _url; }
+
+ bool createSession(const std::string& sessionId, const unsigned intSessionId)
+ {
+ std::unique_lock<std::mutex> lock(_mutex);
+
+ try
+ {
+ const auto& it = _connections.find(intSessionId);
+ if (it != _connections.end())
+ {
+ // found item, check if still running
+ if (it->second->isRunning())
+ {
+ Log::warn("Session [" + sessionId + "] is already running.");
+ return true;
+ }
+
+ // Restore thread. TODO: Review this logic.
+ Log::warn("Session [" + sessionId + "] is not running. Restoring.");
+ _connections.erase(intSessionId);
+ }
+
+ Log::info() << "Creating " << (_clientViews ? "new" : "first")
+ << " view for url: " << _url << " for sessionId: " << sessionId
+ << " on jailId: " << _jailId << Log::end;
+
+ // Open websocket connection between the child process and the
+ // parent. The parent forwards us requests that it can't handle (i.e most).
+ Poco::Net::HTTPClientSession cs("127.0.0.1", MASTER_PORT_NUMBER);
+ cs.setTimeout(0);
+ HTTPRequest request(HTTPRequest::HTTP_GET, std::string(CHILD_URI) + "sessionId=" + sessionId + "&jailId=" + _jailId + "&docKey=" + _docKey);
+ Poco::Net::HTTPResponse response;
+
+ auto ws = std::make_shared<WebSocket>(cs, request, response);
+ ws->setReceiveTimeout(0);
+
+ auto session = std::make_shared<ChildProcessSession>(sessionId, ws, _loKitDocument, _jailId,
+ [this](const std::string& id, const std::string& uri, const std::string& docPassword, bool isDocPasswordProvided) { return onLoad(id, uri, docPassword, isDocPasswordProvided); },
+ [this](const std::string& id) { onUnload(id); });
+
+ auto thread = std::make_shared<Connection>(session, ws);
+ const auto aInserted = _connections.emplace(intSessionId, thread);
+ if (aInserted.second)
+ {
+ thread->start();
+ }
+ else
+ {
+ Log::error("Connection already exists for child: " + _jailId + ", session: " + sessionId);
+ }
+
+ Log::debug("Connections: " + std::to_string(_connections.size()));
+ return true;
+ }
+ catch (const std::exception& ex)
+ {
+ Log::error("Exception while creating session [" + sessionId + "] on url [" + _url + "] - '" + ex.what() + "'.");
+ return false;
+ }
+ }
+
+ /// Purges dead connections and returns
+ /// the remaining number of clients.
+ /// Returns -1 on failure.
+ size_t purgeSessions()
+ {
+ std::vector<std::shared_ptr<ChildProcessSession>> deadSessions;
+ size_t num_connections = 0;
+ {
+ std::unique_lock<std::mutex> lock(_mutex, std::defer_lock);
+ if (!lock.try_lock())
+ {
+ // Not a good time, try later.
+ return -1;
+ }
+
+ for (auto it = _connections.cbegin(); it != _connections.cend(); )
+ {
+ if (!it->second->isRunning())
+ {
+ deadSessions.push_back(it->second->getSession());
+ it = _connections.erase(it);
+ }
+ else
+ {
+ ++it;
+ }
+ }
+
+ num_connections = _connections.size();
+ }
+
+ // Don't destroy sessions while holding our lock.
+ // We may deadlock if a session is waiting on us
+ // during callback initiated while handling a command
+ // and the dtor tries to take its lock (which is taken).
+ deadSessions.clear();
+
+ return num_connections;
+ }
+
+ /// Returns true if at least one *live* connection exists.
+ /// Does not consider user activity, just socket status.
+ bool hasConnections()
+ {
+ // -ve values for failure.
+ return purgeSessions() != 0;
+ }
+
+ /// Returns true if there is no activity and
+ /// the document is saved.
+ bool canDiscard()
+ {
+ //TODO: Implement proper time-out on inactivity.
+ return !hasConnections();
+ }
+
+ /// Set Document password for given URL
+ void setDocumentPassword(int nPasswordType)
+ {
+ Log::info() << "setDocumentPassword: passwordProtected=" << _isDocPasswordProtected
+ << " passwordProvided=" << _isDocPasswordProvided
+ << " password='" << _docPassword << "'" << Log::end;
+
+ if (_isDocPasswordProtected && _isDocPasswordProvided)
+ {
+ // it means this is the second attempt with the wrong password; abort the load operation
+ _loKit->pClass->setDocumentPassword(_loKit, _jailedUrl.c_str(), nullptr);
+ return;
+ }
+
+ // One thing for sure, this is a password protected document
+ _isDocPasswordProtected = true;
+ if (nPasswordType == LOK_CALLBACK_DOCUMENT_PASSWORD)
+ _docPasswordType = PasswordType::ToView;
+ else if (nPasswordType == LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY)
+ _docPasswordType = PasswordType::ToModify;
+
+ Log::info("Caling _loKit->pClass->setDocumentPassword");
+ if (_isDocPasswordProvided)
+ _loKit->pClass->setDocumentPassword(_loKit, _jailedUrl.c_str(), _docPassword.c_str());
+ else
+ _loKit->pClass->setDocumentPassword(_loKit, _jailedUrl.c_str(), nullptr);
+ Log::info("setDocumentPassword returned");
+ }
+
+private:
+ static void KitCallback(int nType, const char* pPayload, void* pData)
+ {
+ Document* self = reinterpret_cast<Document*>(pData);
+ Log::trace() << "Document::KitCallback "
+ << LOKitHelper::kitCallbackTypeToString(nType)
+ << " [" << (pPayload ? pPayload : "") << "]." << Log::end;
+
+ if (self)
+ {
+ std::unique_lock<std::mutex> lock(self->_mutex);
+ for (auto& it: self->_connections)
+ {
+ if (it.second->isRunning())
+ {
+ auto session = it.second->getSession();
+ auto sessionLock = session->getLock();
+
+ switch (nType)
+ {
+ case LOK_CALLBACK_STATUS_INDICATOR_START:
+ session->sendTextFrame("statusindicatorstart:");
+ break;
+ case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
+ session->sendTextFrame("statusindicatorsetvalue: " + std::string(pPayload));
+ break;
+ case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
+ session->sendTextFrame("statusindicatorfinish:");
+ break;
+ case LOK_CALLBACK_DOCUMENT_PASSWORD:
+ case LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY:
+ self->setDocumentPassword(nType);
+ break;
+ }
+
+ // Ideally, there would be only one *live* connection at this point of time
+ // So, just get the first running one and break out.
+ // TODO: Find a better way to find the correct connection.
+ break;
+ }
+ }
+ }
+ }
+
+ static void ViewCallback(int , const char* , void* )
+ {
+ //TODO: Delegate the callback.
+ }
+
+ static void DocumentCallback(int nType, const char* pPayload, void* pData)
+ {
+ Document* self = reinterpret_cast<Document*>(pData);
+ if (self)
+ {
+ std::unique_lock<std::mutex> lock(self->_mutex);
+
+ for (auto& it: self->_connections)
+ {
+ if (it.second->isRunning())
+ {
+ auto session = it.second->getSession();
+ if (session)
+ {
+ session->loKitCallback(nType, pPayload);
+ }
+ }
+ }
+ }
+ }
+
+ /// Load a document (or view) and register callbacks.
+ LibreOfficeKitDocument* onLoad(const std::string& sessionId, const std::string& uri, const std::string& docPassword, bool isDocPasswordProvided)
+ {
+ Log::info("Session " + sessionId + " is loading. " + std::to_string(_clientViews) + " views loaded.");
+
+ std::unique_lock<std::mutex> lock(_mutex);
+ while (_isLoading)
+ {
+ _cvLoading.wait(lock);
+ }
+
+ // Flag and release lock.
+ ++_isLoading;
+ lock.unlock();
+
+ try
+ {
+ load(sessionId, uri, docPassword, isDocPasswordProvided);
+ }
+ catch (const std::exception& exc)
+ {
+ Log::error("Exception while loading [" + uri + "] : " + exc.what());
+ }
+
+ // Done loading, let the next one in (if any).
+ lock.lock();
+ ++_clientViews;
+ --_isLoading;
+ _cvLoading.notify_one();
+
+ return _loKitDocument;
+ }
+
+ void onUnload(const std::string& sessionId)
+ {
+ const unsigned intSessionId = Util::decodeId(sessionId);
+ const auto it = _connections.find(intSessionId);
+ if (it == _connections.end() || !it->second || !_loKitDocument)
+ {
+ // Nothing to do.
+ return;
+ }
+
+ auto session = it->second->getSession();
+ auto sessionLock = session->getLock();
+ std::unique_lock<std::mutex> lock(_mutex);
+
+ Log::info("Session " + sessionId + " is unloading. Erasing connection.");
+ _connections.erase(it);
+ --_clientViews;
+ Log::info("Session " + sessionId + " is unloading. " + std::to_string(_clientViews) + " views will remain.");
+
+ std::ostringstream message;
+ message << "rmview" << " "
+ << Process::id() << " "
+ << sessionId << " "
+ << "\n";
+ IoUtil::writeFIFO(WriterNotify, message.str());
+
+ if (_multiView && _loKitDocument)
+ {
+ Log::info() << "Document [" << _url << "] session ["
+ << sessionId << "] unloaded, leaving "
+ << _clientViews << " views." << Log::end;
+
+ const auto viewId = _loKitDocument->pClass->getView(_loKitDocument);
+ _loKitDocument->pClass->registerCallback(_loKitDocument, nullptr, nullptr);
+ _loKitDocument->pClass->destroyView(_loKitDocument, viewId);
+ }
+ }
+
+private:
+
+ LibreOfficeKitDocument* load(const std::string& sessionId, const std::string& uri, const std::string& docPassword, bool isDocPasswordProvided)
+ {
+ const unsigned intSessionId = Util::decodeId(sessionId);
+ const auto it = _connections.find(intSessionId);
+ if (it == _connections.end() || !it->second)
+ {
+ Log::error("Cannot find session [" + sessionId + "].");
+ return nullptr;
+ }
+
+ auto session = it->second->getSession();
+
+ if (_loKitDocument == nullptr)
+ {
+ // This is the first time we are loading the document
+ Log::info("Loading new document from URI: [" + uri + "] for session [" + sessionId + "].");
+
+ if (LIBREOFFICEKIT_HAS(_loKit, registerCallback))
+ {
+ _loKit->pClass->registerCallback(_loKit, KitCallback, this);
+ _loKit->pClass->setOptionalFeatures(_loKit, LOK_FEATURE_DOCUMENT_PASSWORD |
+ LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY);
+ }
+
+ // Save the provided password with us and the jailed url
+ _isDocPasswordProvided = isDocPasswordProvided;
+ _docPassword = docPassword;
+ _jailedUrl = uri;
+ _isDocPasswordProtected = false;
+
+ Log::debug("Calling documentLoad");
+ _loKitDocument = _loKit->pClass->documentLoad(_loKit, uri.c_str());
+ Log::debug("documentLoad returned");
+
+ if (_loKitDocument == nullptr)
+ {
+ Log::error("Failed to load: " + uri + ", error: " + _loKit->pClass->getError(_loKit));
+
+ // Checking if wrong password or no password was reason for failure.
+ if (_isDocPasswordProtected)
+ {
+ if (!_isDocPasswordProvided)
+ {
+ std::string passwordFrame = "passwordrequired:";
+ if (_docPasswordType == PasswordType::ToView)
+ passwordFrame += "to-view";
+ else if (_docPasswordType == PasswordType::ToModify)
+ passwordFrame += "to-modify";
+ session->sendTextFrame("error: cmd=load kind=" + passwordFrame);
+ }
+ else
+ session->sendTextFrame("error: cmd=load kind=wrongpassword");
+ }
+
+ return nullptr;
+ }
+
+ // Notify the Admin thread
+ std::ostringstream message;
+ message << "document" << " "
+ << Process::id() << " "
+ << uri.substr(uri.find_last_of("/") + 1) << " "
+ << "\n";
+ IoUtil::writeFIFO(WriterNotify, message.str());
+
+ if (_multiView)
+ {
+ Log::info("Loading view to document from URI: [" + uri + "] for session [" + sessionId + "].");
+ const auto viewId = _loKitDocument->pClass->createView(_loKitDocument);
+
+ _loKitDocument->pClass->registerCallback(_loKitDocument, ViewCallback, reinterpret_cast<void*>(intSessionId));
+
+ Log::info() << "Document [" << _url << "] view ["
+ << viewId << "] loaded, leaving "
+ << (_clientViews + 1) << " views." << Log::end;
+ }
+ else
+ {
+ _loKitDocument->pClass->registerCallback(_loKitDocument, DocumentCallback, this);
+ }
+ }
+ else
+ {
+ // Check if this document requires password
+ if (_isDocPasswordProtected)
+ {
+ if (!isDocPasswordProvided)
+ {
+ std::string passwordFrame = "passwordrequired:";
+ if (_docPasswordType == PasswordType::ToView)
+ passwordFrame += "to-view";
+ else if (_docPasswordType == PasswordType::ToModify)
+ passwordFrame += "to-modify";
+ session->sendTextFrame("error: cmd=load kind=" + passwordFrame);
+ return nullptr;
+ }
+ else if (docPassword != _docPassword)
+ {
+ session->sendTextFrame("error: cmd=load kind=wrongpassword");
+ return nullptr;
+ }
+ }
+ }
+
+ std::ostringstream message;
+ message << "addview" << " "
+ << Process::id() << " "
+ << sessionId << " "
+ << "\n";
+ IoUtil::writeFIFO(WriterNotify, message.str());
+
+ return _loKitDocument;
+ }
+
+private:
+
+ const bool _multiView;
+ LibreOfficeKit* const _loKit;
+ const std::string _jailId;
+ const std::string _docKey;
+ const std::string _url;
+ std::string _jailedUrl;
+
+ LibreOfficeKitDocument *_loKitDocument;
+
+ // Document password provided
+ std::string _docPassword;
+ // Whether password was provided or not
+ bool _isDocPasswordProvided;
+ // Whether document is password protected
+ bool _isDocPasswordProtected;
+ // Whether password is required to view the document, or modify it
+ PasswordType _docPasswordType;
+
+ std::mutex _mutex;
+ std::condition_variable _cvLoading;
+ std::atomic_size_t _isLoading;
+ std::map<unsigned, std::shared_ptr<Connection>> _connections;
+ std::atomic_size_t _clientViews;
+};
+
+static void lokit_main(const std::string& childRoot,
+ const std::string& sysTemplate,
+ const std::string& loTemplate,
+ const std::string& loSubPath,
+ const std::string& pipe,
+ bool doBenchmark = false)
+{
+ // Reinitialize logging when forked.
+ Log::initialize("kit");
+ Util::rng::reseed();
+
+ assert(!childRoot.empty());
+ assert(!sysTemplate.empty());
+ assert(!loTemplate.empty());
+ assert(!loSubPath.empty());
+ assert(!pipe.empty());
+
+ // We only host a single document in our lifetime.
+ std::shared_ptr<Document> document;
+
+ // Ideally this will be a random ID, but broker 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.
+ static const std::string pid = std::to_string(Process::id());
+ static const std::string jailId = pid;
+ static const std::string process_name = "loolkit";
+
+ if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(process_name.c_str()), 0, 0, 0) != 0)
+ Log::error("Cannot set process name to " + process_name + ".");
+
+ Util::setTerminationSignals();
+ Util::setFatalSignals();
+
+ Log::debug("Process [" + process_name + "] started.");
+
+ static const std::string instdir_path = "/" + loSubPath + "/program";
+ LibreOfficeKit* loKit = nullptr;
+
+ try
+ {
+ if (!doBenchmark)
+ {
+ // Open notify pipe
+ const Path pipePath = Path::forDirectory(childRoot + Path::separator() + FIFO_PATH);
+ const std::string pipeNotify = Path(pipePath, FIFO_ADMIN_NOTIFY).toString();
+ if ((WriterNotify = open(pipeNotify.c_str(), O_WRONLY) ) < 0)
+ {
+ Log::error("Error: failed to open notify pipe [" + FIFO_ADMIN_NOTIFY + "] for writing.");
+ exit(Application::EXIT_SOFTWARE);
+ }
+ }
+
+ const Path jailPath = Path::forDirectory(childRoot + Path::separator() + jailId);
+ Log::info("Jail path: " + jailPath.toString());
+
+ File(jailPath).createDirectories();
+
+ // 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).
+ Path symlinkSource(jailPath, Path(loTemplate.substr(1)));
+
+ File(symlinkSource.parent()).createDirectories();
+
+ std::string symlinkTarget;
+ for (auto i = 0; i < Path(loTemplate).depth(); i++)
+ symlinkTarget += "../";
+ symlinkTarget += loSubPath;
+
+ Log::debug("symlink(\"" + symlinkTarget + "\",\"" + symlinkSource.toString() + "\")");
+ if (symlink(symlinkTarget.c_str(), symlinkSource.toString().c_str()) == -1)
+ {
+ Log::error("Error: symlink(\"" + symlinkTarget + "\",\"" + symlinkSource.toString() + "\") failed");
+ throw Poco::Exception("symlink() failed");
+ }
+
+ Path jailLOInstallation(jailPath, loSubPath);
+ jailLOInstallation.makeDirectory();
+ File(jailLOInstallation).createDirectory();
+
+ // Copy (link) LO installation and other necessary files into it from the template.
+ bool bLoopMounted = false;
+ if (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::debug("Initializing jail bind mount.");
+ bLoopMounted = !system(mountCommand.c_str());
+ Log::debug("Initialized jail bind mount.");
+ }
+ linkOrCopy(sysTemplate, jailPath,
+ bLoopMounted ? COPY_NO_USR : COPY_ALL);
+ linkOrCopy(loTemplate, jailLOInstallation, COPY_LO);
+
+ Log::debug("Initialized jail files.");
+
+ // We need this because sometimes the hostname is not resolved
+ const std::vector<std::string> networkFiles = {"/etc/host.conf", "/etc/hosts", "/etc/nsswitch.conf", "/etc/resolv.conf"};
+ for (const auto& filename : networkFiles)
+ {
+ const File networkFile(filename);
+ if (networkFile.exists())
+ {
+ networkFile.copyTo(Path(jailPath, "/etc").toString());
+ }
+ }
+
+ // 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::error("Error: mknod(" + jailPath.toString() + "/dev/random) failed.");
+
+ }
+ 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::error("Error: mknod(" + jailPath.toString() + "/dev/urandom) failed.");
+ }
+
+ Log::info("chroot(\"" + jailPath.toString() + "\")");
+ if (chroot(jailPath.toString().c_str()) == -1)
+ {
+ Log::error("Error: chroot(\"" + jailPath.toString() + "\") failed.");
+ std::exit(Application::EXIT_SOFTWARE);
+ }
+
+ if (chdir("/") == -1)
+ {
+ Log::error("Error: chdir(\"/\") in jail failed.");
+ std::exit(Application::EXIT_SOFTWARE);
+ }
+
+ dropCapability(CAP_SYS_CHROOT);
+ dropCapability(CAP_MKNOD);
+ dropCapability(CAP_FOWNER);
+
+ Log::debug("Initialized jail nodes, dropped caps.");
+
+ loKit = lok_init_2(instdir_path.c_str(), "file:///user");
+ if (loKit == nullptr)
+ {
+ Log::error("Error: LibreOfficeKit initialization failed. Exiting.");
+ std::exit(Application::EXIT_SOFTWARE);
+ }
+
+ Log::info("loolkit [" + std::to_string(Process::id()) + "] is ready.");
+
+ // Open websocket connection between the child process and WSD.
+ Poco::Net::HTTPClientSession cs("127.0.0.1", MASTER_PORT_NUMBER);
+ cs.setTimeout(0);
+ HTTPRequest request(HTTPRequest::HTTP_GET, std::string(NEW_CHILD_URI) + "pid=" + pid);
+ Poco::Net::HTTPResponse response;
+ auto ws = std::make_shared<WebSocket>(cs, request, response);
+ ws->setReceiveTimeout(0);
+
+ const std::string socketName = "ChildControllerWS";
+ IoUtil::SocketProcessor(ws, response, [&socketName, &ws, &document, &loKit](const std::vector<char>& data)
+ {
+ const std::string message(data.data(), data.size());
+ Log::debug(socketName + ": recv [" + message + "].");
+ StringTokenizer tokens(message, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
+ auto response = std::to_string(Process::id()) + " ";
+
+ if (TerminationFlag)
+ {
+ // Too late, we're going down.
+ response += "down\n";
+ }
+ else if (tokens[0] == "session")
+ {
+ const std::string& sessionId = tokens[1];
+ const unsigned intSessionId = Util::decodeId(sessionId);
+ const std::string& docKey = tokens[2];
+
+ std::string url;
+ Poco::URI::decode(docKey, url);
+ Log::info("New session [" + sessionId + "] request on url [" + url + "].");
+
+ if (!document)
+ {
+ document = std::make_shared<Document>(loKit, jailId, docKey, url);
+ }
+
+ // Validate and create session.
+ if (url == document->getUrl() &&
+ document->createSession(sessionId, intSessionId))
+ {
+ response += "ok\n";
+ }
+ else
+ {
+ response += "bad\n";
+ }
+ }
+ else if (document && document->canDiscard())
+ {
+ TerminationFlag = true;
+ response += "down\n";
+ }
+ else
+ {
+ response += "bad unknown token [" + tokens[0] + "]\n";
+ }
+
+ //FIXME: Do we really need to respond here?
+ Log::trace("KitToDocBroker: " + response.substr(0, response.length()-2));
+ ws->sendFrame(response.data(), response.size());
+
+ return true;
+ },
+ [](){ return TerminationFlag; },
+ socketName);
+ }
+ catch (const Poco::Exception& exc)
+ {
+ Log::error() << exc.name() << ": " << exc.displayText()
+ << (exc.nested() ? " (" + exc.nested()->displayText() + ")" : "")
+ << Log::end;
+ }
+ catch (const std::exception& exc)
+ {
+ Log::error(std::string("Exception: ") + exc.what());
+ }
+
+ if (document)
+ {
+ Log::info("Destroying document [" + document->getUrl() + "].");
+ document.reset();
+ }
+
+ // Destroy LibreOfficeKit
+ if (loKit)
+ {
+ Log::debug("Destroying LibreOfficeKit.");
+ loKit->pClass->destroy(loKit);
+ }
+
+ std::ostringstream message;
+ message << "rmdoc" << " "
+ << Process::id() << " "
+ << "\n";
+ IoUtil::writeFIFO(WriterNotify, message.str());
+ close(WriterNotify);
+
+ Log::info("Process [" + process_name + "] finished.");
+}
+
class ChildDispatcher
{
public:
diff --git a/loolwsd/LOOLKit.cpp b/loolwsd/LOOLKit.cpp
deleted file mode 100644
index c45aebc..0000000
--- a/loolwsd/LOOLKit.cpp
+++ /dev/null
@@ -1,1107 +0,0 @@
-/* -*- 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/.
- */
-
-/*
- * NB. this file is compiled as part of the LOOLBroker.
- */
-
-#include <sys/prctl.h>
-#include <sys/poll.h>
-#include <sys/syscall.h>
-#include <signal.h>
-#include <ftw.h>
-#include <utime.h>
-#include <unistd.h>
-#include <dlfcn.h>
-
-#include <atomic>
-#include <condition_variable>
-#include <cstdlib>
-#include <cstring>
-#include <iostream>
-#include <memory>
-
-#include <Poco/Exception.h>
-#include <Poco/Mutex.h>
-#include <Poco/Net/HTTPClientSession.h>
-#include <Poco/Net/HTTPRequest.h>
-#include <Poco/Net/HTTPResponse.h>
-#include <Poco/Net/NetException.h>
-#include <Poco/Net/WebSocket.h>
-#include <Poco/Process.h>
-#include <Poco/Runnable.h>
-#include <Poco/StringTokenizer.h>
-#include <Poco/Thread.h>
-#include <Poco/Util/Application.h>
-#include <Poco/URI.h>
-
-#define LOK_USE_UNSTABLE_API
-#include <LibreOfficeKit/LibreOfficeKitInit.h>
-#include <LibreOfficeKit/LibreOfficeKitEnums.h>
-
-#include "Capabilities.hpp"
-#include "ChildProcessSession.hpp"
-#include "Common.hpp"
-#include "LOKitHelper.hpp"
-#include "LOOLProtocol.hpp"
-#include "QueueHandler.hpp"
-#include "IoUtil.hpp"
-#include "Util.hpp"
-
-using namespace LOOLProtocol;
-
-using Poco::File;
-using Poco::Net::HTTPRequest;
-using Poco::Net::WebSocket;
-using Poco::Path;
-using Poco::Process;
-using Poco::Runnable;
-using Poco::StringTokenizer;
-using Poco::Thread;
-using Poco::Util::Application;
-
-const std::string FIFO_ADMIN_NOTIFY = "lool_admin_notify.fifo";
-static int WriterNotify = -1;
-
-namespace
-{
- typedef enum { COPY_ALL, COPY_LO, COPY_NO_USR } LinkOrCopyType;
- LinkOrCopyType linkOrCopyType;
- std::string sourceForLinkOrCopy;
- Path destinationForLinkOrCopy;
-
- bool shouldCopyDir(const char *path)
- {
- switch (linkOrCopyType)
- {
- case COPY_NO_USR:
- // bind mounted.
- return strcmp(path,"usr");
- case COPY_LO:
- return
- strcmp(path, "program/wizards") &&
- strcmp(path, "sdk") &&
- strcmp(path, "share/basic") &&
- strcmp(path, "share/gallery") &&
- strcmp(path, "share/Scripts") &&
- strcmp(path, "share/template") &&
- strcmp(path, "share/config/wizard") &&
- strcmp(path, "share/config/wizard");
- default: // COPY_ALL
- return true;
- }
- }
-
- int linkOrCopyFunction(const char *fpath,
- const struct stat* /*sb*/,
- int typeflag,
- struct FTW* /*ftwbuf*/)
- {
- if (strcmp(fpath, sourceForLinkOrCopy.c_str()) == 0)
- return 0;
-
- assert(fpath[strlen(sourceForLinkOrCopy.c_str())] == '/');
- const char *relativeOldPath = fpath + strlen(sourceForLinkOrCopy.c_str()) + 1;
- Path newPath(destinationForLinkOrCopy, Path(relativeOldPath));
-
- switch (typeflag)
- {
- case FTW_F:
- File(newPath.parent()).createDirectories();
- if (link(fpath, newPath.toString().c_str()) == -1)
- {
- Log::error("Error: link(\"" + std::string(fpath) + "\",\"" + newPath.toString() +
- "\") failed. Exiting.");
- std::exit(Application::EXIT_SOFTWARE);
- }
- break;
- case FTW_D:
- {
- struct stat st;
- if (stat(fpath, &st) == -1)
- {
- Log::error("Error: stat(\"" + std::string(fpath) + "\") failed.");
- return 1;
- }
- if (!shouldCopyDir(relativeOldPath))
- {
- Log::trace("skip redundant paths " + std::string(relativeOldPath));
- return FTW_SKIP_SUBTREE;
- }
- File(newPath).createDirectories();
- struct utimbuf ut;
- ut.actime = st.st_atime;
- ut.modtime = st.st_mtime;
- if (utime(newPath.toString().c_str(), &ut) == -1)
- {
- Log::error("Error: utime(\"" + newPath.toString() + "\", &ut) failed.");
- return 1;
- }
- }
- break;
- case FTW_DNR:
- Log::error("Cannot read directory '" + std::string(fpath) + "'");
- return 1;
- case FTW_NS:
- Log::error("nftw: stat failed for '" + std::string(fpath) + "'");
- return 1;
- case FTW_SLN:
- Log::error("nftw: symlink to nonexistent file: '" + std::string(fpath) + "', ignored.");
- break;
- default:
- Log::error("nftw: unexpected type: '" + std::to_string(typeflag));
- assert(false);
- break;
- }
- return 0;
- }
-
- void linkOrCopy(const std::string& source,
- const Path& destination,
- LinkOrCopyType type)
- {
- linkOrCopyType = type;
- sourceForLinkOrCopy = source;
- if (sourceForLinkOrCopy.back() == '/')
- sourceForLinkOrCopy.pop_back();
- destinationForLinkOrCopy = destination;
- if (nftw(source.c_str(), linkOrCopyFunction, 10, FTW_ACTIONRETVAL) == -1)
- Log::error("linkOrCopy: nftw() failed for '" + source + "'");
- }
-}
-
-class Connection: public Runnable
-{
-public:
- Connection(std::shared_ptr<ChildProcessSession> session,
- std::shared_ptr<WebSocket> ws) :
- _session(session),
- _ws(ws),
- _stop(false)
- {
- Log::info("Connection ctor in child for " + _session->getId());
- }
-
- ~Connection()
- {
- Log::info("~Connection dtor in child for " + _session->getId());
- stop();
- }
-
- std::shared_ptr<WebSocket> getWebSocket() const { return _ws; }
- std::shared_ptr<ChildProcessSession> getSession() { return _session; }
-
- void start()
- {
- _thread.start(*this);
- }
-
- bool isRunning()
- {
- return _thread.isRunning();
- }
-
- void stop()
- {
- _stop = true;
- }
-
- void join()
- {
- _thread.join();
- }
-
- void handle(std::shared_ptr<TileQueue> queue, const std::string& firstLine, char* buffer, int n)
- {
- if (firstLine.find("paste") != 0)
- {
- // Everything else is expected to be a single line.
- assert(firstLine.size() == static_cast<std::string::size_type>(n));
- queue->put(firstLine);
- }
- else
- queue->put(std::string(buffer, n));
- }
-
- void run() override
- {
- const std::string thread_name = "kit_ws_" + _session->getId();
-
- if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(thread_name.c_str()), 0, 0, 0) != 0)
- Log::error("Cannot set thread name to " + thread_name + ".");
-
- Log::debug("Thread [" + thread_name + "] started.");
-
- try
- {
- auto queue = std::make_shared<TileQueue>();
- QueueHandler handler(queue, _session, "kit_queue_" + _session->getId());
-
- Thread queueHandlerThread;
- queueHandlerThread.start(handler);
-
- int flags;
- int n;
- do
- {
- char buffer[1024];
- n = _ws->receiveFrame(buffer, sizeof(buffer), flags);
- if (n > 0 && (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE)
- {
- std::string firstLine = getFirstLine(buffer, n);
- if (firstLine == "eof")
- {
- Log::info("Received EOF. Finishing.");
- break;
- }
-
- StringTokenizer tokens(firstLine, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
-
- if (firstLine == "disconnect")
- {
- Log::info("Client disconnected [" + (tokens.count() == 2 ? tokens[1] : std::string("no reason")) + "].");
- break;
- }
-
- // Check if it is a "nextmessage:" and in that case read the large
- // follow-up message separately, and handle that only.
- int size;
- if (tokens.count() == 2 && tokens[0] == "nextmessage:" && getTokenInteger(tokens[1], "size", size) && size > 0)
- {
- char largeBuffer[size];
- n = _ws->receiveFrame(largeBuffer, size, flags);
- if (n > 0 && (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE)
- {
- firstLine = getFirstLine(largeBuffer, n);
- handle(queue, firstLine, largeBuffer, n);
- }
- }
- else
- handle(queue, firstLine, buffer, n);
- }
- }
- while (!_stop && n > 0 && (flags & WebSocket::FRAME_OP_BITMASK) != WebSocket::FRAME_OP_CLOSE);
- Log::debug() << "Finishing " << thread_name << ". stop " << _stop
- << ", payload size: " << n
- << ", flags: " << std::hex << flags << Log::end;
-
- queue->clear();
- queue->put("eof");
- queueHandlerThread.join();
-
- _session->disconnect();
- }
- catch (const Poco::Exception& exc)
- {
- Log::error() << "Connection::run: Exception: " << exc.displayText()
- << (exc.nested() ? " (" + exc.nested()->displayText() + ")" : "")
- << Log::end;
- }
- catch (const std::exception& exc)
- {
- Log::error(std::string("Connection::run: Exception: ") + exc.what());
- }
- catch (...)
- {
- Log::error("Connection::run:: Unexpected exception");
- }
-
- Log::debug("Thread [" + thread_name + "] finished.");
- }
-
-private:
- Thread _thread;
- std::shared_ptr<ChildProcessSession> _session;
- std::shared_ptr<WebSocket> _ws;
- volatile bool _stop;
-};
-
-/// A document container.
-/// Owns LOKitDocument instance and connections.
-/// Manages the lifetime of a document.
-/// Technically, we can host multiple documents
-/// per process. But for security reasons don't.
-/// However, we could have a loolkit instance
-/// per user or group of users (a trusted circle).
-class Document
-{
-public:
- /// We have two types of password protected documents
- /// 1) Documents which require password to view
- /// 2) Document which require password to modify
- enum class PasswordType { ToView, ToModify };
-
- Document(LibreOfficeKit *loKit,
- const std::string& jailId,
- const std::string& docKey,
- const std::string& url)
- : _multiView(std::getenv("LOK_VIEW_CALLBACK")),
- _loKit(loKit),
- _jailId(jailId),
- _docKey(docKey),
- _url(url),
- _loKitDocument(nullptr),
- _docPassword(""),
- _isDocPasswordProvided(false),
- _isDocLoaded(false),
- _isDocPasswordProtected(false),
- _docPasswordType(PasswordType::ToView),
- _isLoading(0),
- _clientViews(0)
- {
- (void)_isDocLoaded; // FIXME LOOLBroker.cpp includes LOOLKit.cpp
- Log::info("Document ctor for url [" + _url + "] on child [" + _jailId +
- "] LOK_VIEW_CALLBACK=" + std::to_string(_multiView) + ".");
- }
-
- ~Document()
- {
- Log::info("~Document dtor for url [" + _url + "] on child [" + _jailId +
- "]. There are " + std::to_string(_clientViews) + " views.");
-
- // Flag all connections to stop.
- for (auto aIterator : _connections)
- {
- aIterator.second->stop();
- }
-
- // Destroy all connections and views.
- for (auto aIterator : _connections)
- {
- try
- {
- // stop all websockets
- if (aIterator.second->isRunning())
- {
- std::shared_ptr<WebSocket> ws = aIterator.second->getWebSocket();
- if (ws)
- {
- ws->shutdownReceive();
- aIterator.second->join();
- }
- }
- }
- catch(Poco::Net::NetException& exc)
- {
- Log::error() << "Document::~Document: " << exc.displayText()
- << (exc.nested() ? " (" + exc.nested()->displayText() + ")" : "")
- << Log::end;
- }
- }
-
- // Destroy all connections and views.
- _connections.clear();
-
- // TODO. check what is happening when destroying lokit document,
- // often it blows up.
- // Destroy the document.
- if (_loKitDocument != nullptr)
- {
- try
- {
- _loKitDocument->pClass->destroy(_loKitDocument);
- }
- catch (const std::exception& exc)
- {
- Log::error() << "Document::~Document: " << exc.what()
- << Log::end;
- }
- }
- }
-
- const std::string& getUrl() const { return _url; }
-
- bool createSession(const std::string& sessionId, const unsigned intSessionId)
- {
- std::unique_lock<std::mutex> lock(_mutex);
-
- try
- {
- const auto& it = _connections.find(intSessionId);
- if (it != _connections.end())
- {
- // found item, check if still running
- if (it->second->isRunning())
- {
- Log::warn("Session [" + sessionId + "] is already running.");
- return true;
- }
-
- // Restore thread. TODO: Review this logic.
- Log::warn("Session [" + sessionId + "] is not running. Restoring.");
- _connections.erase(intSessionId);
- }
-
- Log::info() << "Creating " << (_clientViews ? "new" : "first")
- << " view for url: " << _url << " for sessionId: " << sessionId
- << " on jailId: " << _jailId << Log::end;
-
- // Open websocket connection between the child process and the
- // parent. The parent forwards us requests that it can't handle (i.e most).
- Poco::Net::HTTPClientSession cs("127.0.0.1", MASTER_PORT_NUMBER);
- cs.setTimeout(0);
- HTTPRequest request(HTTPRequest::HTTP_GET, std::string(CHILD_URI) + "sessionId=" + sessionId + "&jailId=" + _jailId + "&docKey=" + _docKey);
- Poco::Net::HTTPResponse response;
-
- auto ws = std::make_shared<WebSocket>(cs, request, response);
- ws->setReceiveTimeout(0);
-
- auto session = std::make_shared<ChildProcessSession>(sessionId, ws, _loKitDocument, _jailId,
- [this](const std::string& id, const std::string& uri, const std::string& docPassword, bool isDocPasswordProvided) { return onLoad(id, uri, docPassword, isDocPasswordProvided); },
- [this](const std::string& id) { onUnload(id); });
-
- auto thread = std::make_shared<Connection>(session, ws);
- const auto aInserted = _connections.emplace(intSessionId, thread);
- if (aInserted.second)
- {
- thread->start();
- }
- else
- {
- Log::error("Connection already exists for child: " + _jailId + ", session: " + sessionId);
- }
-
- Log::debug("Connections: " + std::to_string(_connections.size()));
- return true;
- }
- catch (const std::exception& ex)
- {
- Log::error("Exception while creating session [" + sessionId + "] on url [" + _url + "] - '" + ex.what() + "'.");
- return false;
- }
- }
-
- /// Purges dead connections and returns
- /// the remaining number of clients.
- /// Returns -1 on failure.
- size_t purgeSessions()
- {
- std::vector<std::shared_ptr<ChildProcessSession>> deadSessions;
- size_t num_connections = 0;
- {
- std::unique_lock<std::mutex> lock(_mutex, std::defer_lock);
- if (!lock.try_lock())
- {
- // Not a good time, try later.
- return -1;
- }
-
- for (auto it = _connections.cbegin(); it != _connections.cend(); )
- {
- if (!it->second->isRunning())
- {
- deadSessions.push_back(it->second->getSession());
- it = _connections.erase(it);
- }
- else
- {
- ++it;
- }
- }
-
- num_connections = _connections.size();
- }
-
- // Don't destroy sessions while holding our lock.
- // We may deadlock if a session is waiting on us
- // during callback initiated while handling a command
- // and the dtor tries to take its lock (which is taken).
- deadSessions.clear();
-
- return num_connections;
- }
-
- /// Returns true if at least one *live* connection exists.
- /// Does not consider user activity, just socket status.
- bool hasConnections()
- {
- // -ve values for failure.
- return purgeSessions() != 0;
- }
-
- /// Returns true if there is no activity and
- /// the document is saved.
- bool canDiscard()
- {
- //TODO: Implement proper time-out on inactivity.
- return !hasConnections();
- }
-
- /// Set Document password for given URL
- void setDocumentPassword(int nPasswordType)
- {
- Log::info() << "setDocumentPassword: passwordProtected=" << _isDocPasswordProtected
- << " passwordProvided=" << _isDocPasswordProvided
- << " password='" << _docPassword << "'" << Log::end;
-
- if (_isDocPasswordProtected && _isDocPasswordProvided)
- {
- // it means this is the second attempt with the wrong password; abort the load operation
- _loKit->pClass->setDocumentPassword(_loKit, _jailedUrl.c_str(), nullptr);
- return;
- }
-
- // One thing for sure, this is a password protected document
- _isDocPasswordProtected = true;
- if (nPasswordType == LOK_CALLBACK_DOCUMENT_PASSWORD)
- _docPasswordType = PasswordType::ToView;
- else if (nPasswordType == LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY)
- _docPasswordType = PasswordType::ToModify;
-
- Log::info("Caling _loKit->pClass->setDocumentPassword");
- if (_isDocPasswordProvided)
- _loKit->pClass->setDocumentPassword(_loKit, _jailedUrl.c_str(), _docPassword.c_str());
- else
- _loKit->pClass->setDocumentPassword(_loKit, _jailedUrl.c_str(), nullptr);
- Log::info("setDocumentPassword returned");
- }
-
-private:
- static void KitCallback(int nType, const char* pPayload, void* pData)
- {
- Document* self = reinterpret_cast<Document*>(pData);
- Log::trace() << "Document::KitCallback "
- << LOKitHelper::kitCallbackTypeToString(nType)
- << " [" << (pPayload ? pPayload : "") << "]." << Log::end;
-
- if (self)
- {
- std::unique_lock<std::mutex> lock(self->_mutex);
- for (auto& it: self->_connections)
- {
- if (it.second->isRunning())
- {
- auto session = it.second->getSession();
- auto sessionLock = session->getLock();
-
- switch (nType)
- {
- case LOK_CALLBACK_STATUS_INDICATOR_START:
- session->sendTextFrame("statusindicatorstart:");
- break;
- case LOK_CALLBACK_STATUS_INDICATOR_SET_VALUE:
- session->sendTextFrame("statusindicatorsetvalue: " + std::string(pPayload));
- break;
- case LOK_CALLBACK_STATUS_INDICATOR_FINISH:
- session->sendTextFrame("statusindicatorfinish:");
- break;
- case LOK_CALLBACK_DOCUMENT_PASSWORD:
- case LOK_CALLBACK_DOCUMENT_PASSWORD_TO_MODIFY:
- self->setDocumentPassword(nType);
- break;
- }
-
- // Ideally, there would be only one *live* connection at this point of time
- // So, just get the first running one and break out.
- // TODO: Find a better way to find the correct connection.
- break;
- }
- }
- }
- }
-
- static void ViewCallback(int , const char* , void* )
- {
- //TODO: Delegate the callback.
- }
-
- static void DocumentCallback(int nType, const char* pPayload, void* pData)
- {
- Document* self = reinterpret_cast<Document*>(pData);
- if (self)
- {
- std::unique_lock<std::mutex> lock(self->_mutex);
-
- for (auto& it: self->_connections)
- {
- if (it.second->isRunning())
- {
- auto session = it.second->getSession();
- if (session)
- {
- session->loKitCallback(nType, pPayload);
- }
- }
- }
- }
- }
-
- /// Load a document (or view) and register callbacks.
- LibreOfficeKitDocument* onLoad(const std::string& sessionId, const std::string& uri, const std::string& docPassword, bool isDocPasswordProvided)
- {
- Log::info("Session " + sessionId + " is loading. " + std::to_string(_clientViews) + " views loaded.");
-
- std::unique_lock<std::mutex> lock(_mutex);
- while (_isLoading)
- {
- _cvLoading.wait(lock);
- }
-
- // Flag and release lock.
- ++_isLoading;
- lock.unlock();
-
- try
- {
- load(sessionId, uri, docPassword, isDocPasswordProvided);
- }
- catch (const std::exception& exc)
- {
- Log::error("Exception while loading [" + uri + "] : " + exc.what());
- }
-
- // Done loading, let the next one in (if any).
- lock.lock();
- ++_clientViews;
- --_isLoading;
- _cvLoading.notify_one();
-
- return _loKitDocument;
- }
-
- void onUnload(const std::string& sessionId)
- {
- const unsigned intSessionId = Util::decodeId(sessionId);
- const auto it = _connections.find(intSessionId);
- if (it == _connections.end() || !it->second || !_loKitDocument)
- {
- // Nothing to do.
- return;
- }
-
- auto session = it->second->getSession();
- auto sessionLock = session->getLock();
- std::unique_lock<std::mutex> lock(_mutex);
-
- Log::info("Session " + sessionId + " is unloading. Erasing connection.");
- _connections.erase(it);
- --_clientViews;
- Log::info("Session " + sessionId + " is unloading. " + std::to_string(_clientViews) + " views will remain.");
-
- std::ostringstream message;
- message << "rmview" << " "
- << Process::id() << " "
- << sessionId << " "
- << "\n";
- IoUtil::writeFIFO(WriterNotify, message.str());
-
- if (_multiView && _loKitDocument)
- {
- Log::info() << "Document [" << _url << "] session ["
- << sessionId << "] unloaded, leaving "
- << _clientViews << " views." << Log::end;
-
- const auto viewId = _loKitDocument->pClass->getView(_loKitDocument);
- _loKitDocument->pClass->registerCallback(_loKitDocument, nullptr, nullptr);
- _loKitDocument->pClass->destroyView(_loKitDocument, viewId);
- }
- }
-
-private:
-
- LibreOfficeKitDocument* load(const std::string& sessionId, const std::string& uri, const std::string& docPassword, bool isDocPasswordProvided)
- {
- const unsigned intSessionId = Util::decodeId(sessionId);
- const auto it = _connections.find(intSessionId);
- if (it == _connections.end() || !it->second)
- {
- Log::error("Cannot find session [" + sessionId + "].");
- return nullptr;
- }
-
- auto session = it->second->getSession();
-
- if (_loKitDocument == nullptr)
- {
- // This is the first time we are loading the document
- Log::info("Loading new document from URI: [" + uri + "] for session [" + sessionId + "].");
-
- if (LIBREOFFICEKIT_HAS(_loKit, registerCallback))
- {
- _loKit->pClass->registerCallback(_loKit, KitCallback, this);
- _loKit->pClass->setOptionalFeatures(_loKit, LOK_FEATURE_DOCUMENT_PASSWORD |
- LOK_FEATURE_DOCUMENT_PASSWORD_TO_MODIFY);
- }
-
- // Save the provided password with us and the jailed url
- _isDocPasswordProvided = isDocPasswordProvided;
- _docPassword = docPassword;
- _jailedUrl = uri;
- _isDocPasswordProtected = false;
-
- Log::debug("Calling documentLoad");
- _loKitDocument = _loKit->pClass->documentLoad(_loKit, uri.c_str());
- Log::debug("documentLoad returned");
-
- if (_loKitDocument == nullptr)
- {
- Log::error("Failed to load: " + uri + ", error: " + _loKit->pClass->getError(_loKit));
-
- // Checking if wrong password or no password was reason for failure.
- if (_isDocPasswordProtected)
- {
- if (!_isDocPasswordProvided)
- {
- std::string passwordFrame = "passwordrequired:";
- if (_docPasswordType == PasswordType::ToView)
- passwordFrame += "to-view";
- else if (_docPasswordType == PasswordType::ToModify)
- passwordFrame += "to-modify";
- session->sendTextFrame("error: cmd=load kind=" + passwordFrame);
- }
- else
- session->sendTextFrame("error: cmd=load kind=wrongpassword");
- }
-
- return nullptr;
- }
-
- // Notify the Admin thread
- std::ostringstream message;
- message << "document" << " "
- << Process::id() << " "
- << uri.substr(uri.find_last_of("/") + 1) << " "
- << "\n";
- IoUtil::writeFIFO(WriterNotify, message.str());
-
- if (_multiView)
- {
- Log::info("Loading view to document from URI: [" + uri + "] for session [" + sessionId + "].");
- const auto viewId = _loKitDocument->pClass->createView(_loKitDocument);
-
- _loKitDocument->pClass->registerCallback(_loKitDocument, ViewCallback, reinterpret_cast<void*>(intSessionId));
-
- Log::info() << "Document [" << _url << "] view ["
- << viewId << "] loaded, leaving "
- << (_clientViews + 1) << " views." << Log::end;
- }
- else
- {
- _loKitDocument->pClass->registerCallback(_loKitDocument, DocumentCallback, this);
- }
- }
- else
- {
- // Check if this document requires password
- if (_isDocPasswordProtected)
- {
- if (!isDocPasswordProvided)
- {
- std::string passwordFrame = "passwordrequired:";
- if (_docPasswordType == PasswordType::ToView)
- passwordFrame += "to-view";
- else if (_docPasswordType == PasswordType::ToModify)
- passwordFrame += "to-modify";
- session->sendTextFrame("error: cmd=load kind=" + passwordFrame);
- return nullptr;
- }
- else if (docPassword != _docPassword)
- {
- session->sendTextFrame("error: cmd=load kind=wrongpassword");
- return nullptr;
- }
- }
- }
-
- std::ostringstream message;
- message << "addview" << " "
- << Process::id() << " "
- << sessionId << " "
- << "\n";
- IoUtil::writeFIFO(WriterNotify, message.str());
-
- return _loKitDocument;
- }
-
-private:
-
- const bool _multiView;
- LibreOfficeKit* const _loKit;
- const std::string _jailId;
- const std::string _docKey;
- const std::string _url;
- std::string _jailedUrl;
-
- LibreOfficeKitDocument *_loKitDocument;
-
- // Document password provided
- std::string _docPassword;
- // Whether password was provided or not
- bool _isDocPasswordProvided;
- // Whether documet has been opened successfully
- bool _isDocLoaded;
- // Whether document is password protected
- bool _isDocPasswordProtected;
- // Whether password is required to view the document, or modify it
- PasswordType _docPasswordType;
-
- std::mutex _mutex;
- std::condition_variable _cvLoading;
- std::atomic_size_t _isLoading;
- std::map<unsigned, std::shared_ptr<Connection>> _connections;
- std::atomic_size_t _clientViews;
-};
-
-void lokit_main(const std::string& childRoot,
- const std::string& sysTemplate,
- const std::string& loTemplate,
- const std::string& loSubPath,
- const std::string& pipe,
- bool doBenchmark = false)
-{
- // Reinitialize logging when forked.
- Log::initialize("kit");
- Util::rng::reseed();
-
- assert(!childRoot.empty());
- assert(!sysTemplate.empty());
- assert(!loTemplate.empty());
- assert(!loSubPath.empty());
- assert(!pipe.empty());
-
- // We only host a single document in our lifetime.
- std::shared_ptr<Document> document;
-
- // Ideally this will be a random ID, but broker 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.
- static const std::string pid = std::to_string(Process::id());
- static const std::string jailId = pid;
- static const std::string process_name = "loolkit";
-
- if (prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(process_name.c_str()), 0, 0, 0) != 0)
- Log::error("Cannot set process name to " + process_name + ".");
-
- Util::setTerminationSignals();
- Util::setFatalSignals();
-
- Log::debug("Process [" + process_name + "] started.");
-
- static const std::string instdir_path = "/" + loSubPath + "/program";
- LibreOfficeKit* loKit = nullptr;
-
- try
- {
- if (!doBenchmark)
- {
- // Open notify pipe
- const Path pipePath = Path::forDirectory(childRoot + Path::separator() + FIFO_PATH);
- const std::string pipeNotify = Path(pipePath, FIFO_ADMIN_NOTIFY).toString();
- if ((WriterNotify = open(pipeNotify.c_str(), O_WRONLY) ) < 0)
- {
- Log::error("Error: failed to open notify pipe [" + FIFO_ADMIN_NOTIFY + "] for writing.");
- exit(Application::EXIT_SOFTWARE);
- }
- }
-
- const Path jailPath = Path::forDirectory(childRoot + Path::separator() + jailId);
- Log::info("Jail path: " + jailPath.toString());
-
- File(jailPath).createDirectories();
-
- // 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).
- Path symlinkSource(jailPath, Path(loTemplate.substr(1)));
-
- File(symlinkSource.parent()).createDirectories();
-
- std::string symlinkTarget;
- for (auto i = 0; i < Path(loTemplate).depth(); i++)
- symlinkTarget += "../";
- symlinkTarget += loSubPath;
-
- Log::debug("symlink(\"" + symlinkTarget + "\",\"" + symlinkSource.toString() + "\")");
- if (symlink(symlinkTarget.c_str(), symlinkSource.toString().c_str()) == -1)
- {
- Log::error("Error: symlink(\"" + symlinkTarget + "\",\"" + symlinkSource.toString() + "\") failed");
- throw Poco::Exception("symlink() failed");
- }
-
- Path jailLOInstallation(jailPath, loSubPath);
- jailLOInstallation.makeDirectory();
- File(jailLOInstallation).createDirectory();
-
- // Copy (link) LO installation and other necessary files into it from the template.
- bool bLoopMounted = false;
- if (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::debug("Initializing jail bind mount.");
- bLoopMounted = !system(mountCommand.c_str());
- Log::debug("Initialized jail bind mount.");
- }
- linkOrCopy(sysTemplate, jailPath,
- bLoopMounted ? COPY_NO_USR : COPY_ALL);
- linkOrCopy(loTemplate, jailLOInstallation, COPY_LO);
-
- Log::debug("Initialized jail files.");
-
- // We need this because sometimes the hostname is not resolved
- const std::vector<std::string> networkFiles = {"/etc/host.conf", "/etc/hosts", "/etc/nsswitch.conf", "/etc/resolv.conf"};
- for (const auto& filename : networkFiles)
- {
- const File networkFile(filename);
- if (networkFile.exists())
- {
- networkFile.copyTo(Path(jailPath, "/etc").toString());
- }
- }
-
- // 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::error("Error: mknod(" + jailPath.toString() + "/dev/random) failed.");
-
- }
- 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::error("Error: mknod(" + jailPath.toString() + "/dev/urandom) failed.");
- }
-
- Log::info("chroot(\"" + jailPath.toString() + "\")");
- if (chroot(jailPath.toString().c_str()) == -1)
- {
- Log::error("Error: chroot(\"" + jailPath.toString() + "\") failed.");
- std::exit(Application::EXIT_SOFTWARE);
- }
-
- if (chdir("/") == -1)
- {
- Log::error("Error: chdir(\"/\") in jail failed.");
- std::exit(Application::EXIT_SOFTWARE);
- }
-
- dropCapability(CAP_SYS_CHROOT);
- dropCapability(CAP_MKNOD);
- dropCapability(CAP_FOWNER);
-
- Log::debug("Initialized jail nodes, dropped caps.");
-
- loKit = lok_init_2(instdir_path.c_str(), "file:///user");
- if (loKit == nullptr)
- {
- Log::error("Error: LibreOfficeKit initialization failed. Exiting.");
- std::exit(Application::EXIT_SOFTWARE);
- }
-
- Log::info("loolkit [" + std::to_string(Process::id()) + "] is ready.");
-
- // Open websocket connection between the child process and WSD.
- Poco::Net::HTTPClientSession cs("127.0.0.1", MASTER_PORT_NUMBER);
- cs.setTimeout(0);
- HTTPRequest request(HTTPRequest::HTTP_GET, std::string(NEW_CHILD_URI) + "pid=" + pid);
- Poco::Net::HTTPResponse response;
- auto ws = std::make_shared<WebSocket>(cs, request, response);
- ws->setReceiveTimeout(0);
-
- const std::string socketName = "ChildControllerWS";
- IoUtil::SocketProcessor(ws, response, [&socketName, &ws, &document, &loKit](const std::vector<char>& data)
- {
- const std::string message(data.data(), data.size());
- Log::debug(socketName + ": recv [" + message + "].");
- StringTokenizer tokens(message, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM);
- auto response = std::to_string(Process::id()) + " ";
-
- if (TerminationFlag)
- {
- // Too late, we're going down.
- response += "down\n";
- }
- else if (tokens[0] == "session")
- {
- const std::string& sessionId = tokens[1];
- const unsigned intSessionId = Util::decodeId(sessionId);
- const std::string& docKey = tokens[2];
-
- std::string url;
- Poco::URI::decode(docKey, url);
- Log::info("New session [" + sessionId + "] request on url [" + url + "].");
-
- if (!document)
- {
- document = std::make_shared<Document>(loKit, jailId, docKey, url);
- }
-
- // Validate and create session.
- if (url == document->getUrl() &&
- document->createSession(sessionId, intSessionId))
- {
- response += "ok\n";
- }
- else
- {
- response += "bad\n";
- }
- }
- else if (document && document->canDiscard())
- {
- TerminationFlag = true;
- response += "down\n";
- }
- else
- {
- response += "bad unknown token [" + tokens[0] + "]\n";
- }
-
- //FIXME: Do we really need to respond here?
- Log::trace("KitToDocBroker: " + response.substr(0, response.length()-2));
- ws->sendFrame(response.data(), response.size());
-
- return true;
- },
- [](){ return TerminationFlag; },
- socketName);
- }
- catch (const Poco::Exception& exc)
- {
- Log::error() << exc.name() << ": " << exc.displayText()
- << (exc.nested() ? " (" + exc.nested()->displayText() + ")" : "")
- << Log::end;
- }
- catch (const std::exception& exc)
- {
- Log::error(std::string("Exception: ") + exc.what());
- }
-
- if (document)
- {
- Log::info("Destroying document [" + document->getUrl() + "].");
- document.reset();
- }
-
- // Destroy LibreOfficeKit
- if (loKit)
- {
- Log::debug("Destroying LibreOfficeKit.");
- loKit->pClass->destroy(loKit);
- }
-
- std::ostringstream message;
- message << "rmdoc" << " "
- << Process::id() << " "
- << "\n";
- IoUtil::writeFIFO(WriterNotify, message.str());
- close(WriterNotify);
-
- Log::info("Process [" + process_name + "] finished.");
-}
-
-/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/loolwsd/PROBLEMS b/loolwsd/PROBLEMS
index 981e24c..6268eff 100644
--- a/loolwsd/PROBLEMS
+++ b/loolwsd/PROBLEMS
@@ -11,7 +11,7 @@
is declared *static* in ChildProcessSession.hpp and thus is a
separate variable in each compilation unit (object file) that
includes ChildProcessSession.hpp. The variable that is assigned in
- main() in LOOLKit.cpp is not the variable used in
+ main() in LOOLBroker.cpp is not the variable used in
ChildProcessSession::downloadAs() in ChildProcessSession.cpp.
- Recursive mutexes are evil. In general, I think the consensus is
More information about the Libreoffice-commits
mailing list