nearly pure re-factor: split out code into its own modules.

StateRecorder.hpp split from ChildSession.cpp
KitWebSocketHandler.[ch]pp split from Kit.cpp.
ThreadPool.hpp split from RenderTiles.hpp

Expose headers for KitSocketPoll and Document
at the same time.

Not clear we need the DocumentManagerInterface anymore.

Conditionally compile out Document::createSession for unittest
dependency breaking, and avoid Rlimit::handleSetrlimitCommand
likewise.

Make makePropertyValue a private method of Kit.cpp.

clang-format new files.

Change-Id: I47a1d6afe20165f156b477a931b94c916cff4b9d
Signed-off-by: Michael Meeks <michael.meeks@collabora.com>
pull/8479/head
Michael Meeks 2024-03-06 12:43:53 +00:00 committed by Ashod Nakashian
parent e59349f1c3
commit c117d87bb4
14 changed files with 2262 additions and 2150 deletions

View File

@ -194,7 +194,8 @@ libsimd_a_CFLAGS = @SIMD_CFLAGS@
coolforkit_sources = kit/ChildSession.cpp \
kit/ForKit.cpp \
kit/Kit.cpp
kit/Kit.cpp \
kit/KitWebSocket.cpp
coolforkit_SOURCES = $(coolforkit_sources) \
$(shared_sources) \
@ -344,6 +345,7 @@ shared_headers = common/Common.hpp \
common/SpookyV2.h \
common/CommandControl.hpp \
common/Simd.hpp \
common/ThreadPool.hpp \
net/Buffer.hpp \
net/DelaySocket.hpp \
net/FakeSocket.hpp \
@ -366,7 +368,9 @@ kit_headers = kit/ChildSession.hpp \
kit/DummyLibreOfficeKit.hpp \
kit/Kit.hpp \
kit/KitHelper.hpp \
kit/KitWebSocket.hpp \
kit/SetupKitEnvironment.hpp \
kit/StateRecorder.hpp \
kit/Watermark.hpp
noinst_HEADERS = $(wsd_headers) $(shared_headers) $(kit_headers) \

View File

@ -21,6 +21,7 @@ add_library(androidapp SHARED
../../../../../kit/ChildSession.cpp
../../../../../kit/DeltaSimd.c
../../../../../kit/Kit.cpp
../../../../../kit/KitWebSocket.cpp
../../../../../net/FakeSocket.cpp
../../../../../net/Socket.cpp
../../../../../wsd/ClientSession.cpp

View File

@ -20,139 +20,15 @@
#include <unordered_map>
#include <vector>
#include <LibreOfficeKit/LibreOfficeKit.hxx>
#include <common/ThreadPool.hpp>
#include "Png.hpp"
#include "Delta.hpp"
#include "Rectangle.hpp"
#include "TileDesc.hpp"
class ThreadPool {
std::mutex _mutex;
std::condition_variable _cond;
std::condition_variable _complete;
typedef std::function<void()> ThreadFn;
std::queue<ThreadFn> _work;
std::vector<std::thread> _threads;
size_t _working;
bool _shutdown;
std::atomic<bool> _running;
public:
ThreadPool()
: _working(0),
_shutdown(false),
_running(false)
{
int maxConcurrency = 2;
#if WASMAPP
// Leave it at that.
#elif MOBILEAPP && !defined(GTKAPP)
maxConcurrency = std::max<int>(std::thread::hardware_concurrency(), 2);
#else
// coverity[tainted_return_value] - we trust the contents of this variable
const char *max = getenv("MAX_CONCURRENCY");
if (max)
maxConcurrency = atoi(max);
#endif
LOG_TRC("PNG compression thread pool size " << maxConcurrency);
for (int i = 1; i < maxConcurrency; ++i)
_threads.push_back(std::thread(&ThreadPool::work, this));
}
~ThreadPool() { stop(); }
void stop()
{
{
std::unique_lock< std::mutex > lock(_mutex);
assert(_working == 0);
_shutdown = true;
}
_cond.notify_all();
for (auto& it : _threads)
it.join();
}
size_t count() const
{
return _work.size();
}
void pushWork(const ThreadFn &fn)
{
std::unique_lock< std::mutex > lock(_mutex);
assert(!_running);
assert(_working == 0);
_work.push(fn);
}
void runOne(std::unique_lock< std::mutex >& lock)
{
assert(_running);
assert(!_work.empty());
ThreadFn fn = _work.front();
_work.pop();
_working++;
lock.unlock();
try {
fn();
} catch(...) {
LOG_ERR("Exception in thread pool execution.");
}
lock.lock();
_working--;
if (_work.empty() && _working == 0)
_complete.notify_all();
}
void run()
{
std::unique_lock< std::mutex > lock(_mutex);
assert(!_running);
assert(_working == 0);
_running = true;
// Avoid notifying threads if we don't need to.
bool useThreads = _threads.size() > 1 && _work.size() > 1;
if (useThreads)
_cond.notify_all();
while(!_work.empty())
runOne(lock);
if (useThreads && (_working > 0 || !_work.empty()))
_complete.wait(lock, [this]() { return _working == 0 && _work.empty(); } );
_running = false;
assert(_working==0);
assert(_work.empty());
}
void work()
{
std::unique_lock< std::mutex > lock(_mutex);
while (!_shutdown)
{
_cond.wait(lock);
while (!_shutdown && !_work.empty() && _running)
runOne(lock);
}
}
void dumpState(std::ostream& oss)
{
oss << "\tthreadPool:"
<< "\n\t\tshutdown: " << _shutdown
<< "\n\t\tworking: " << _working
<< "\n\t\twork count: " << count()
<< "\n\t\tthread count " << _threads.size()
<< "\n";
}
};
namespace RenderTiles
{
struct Buffer {

View File

@ -0,0 +1,148 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* Copyright the Collabora Online contributors.
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#pragma once
#include <cassert>
#include <memory>
#include <queue>
#include <thread>
#include <condition_variable>
#include <fstream>
#include <unordered_map>
#include <vector>
class ThreadPool
{
std::mutex _mutex;
std::condition_variable _cond;
std::condition_variable _complete;
typedef std::function<void()> ThreadFn;
std::queue<ThreadFn> _work;
std::vector<std::thread> _threads;
size_t _working;
bool _shutdown;
std::atomic<bool> _running;
public:
ThreadPool()
: _working(0)
, _shutdown(false)
, _running(false)
{
int maxConcurrency = 2;
#if WASMAPP
// Leave it at that.
#elif MOBILEAPP && !defined(GTKAPP)
maxConcurrency = std::max<int>(std::thread::hardware_concurrency(), 2);
#else
// coverity[tainted_return_value] - we trust the contents of this variable
const char* max = getenv("MAX_CONCURRENCY");
if (max)
maxConcurrency = atoi(max);
#endif
LOG_TRC("PNG compression thread pool size " << maxConcurrency);
for (int i = 1; i < maxConcurrency; ++i)
_threads.push_back(std::thread(&ThreadPool::work, this));
}
~ThreadPool() { stop(); }
void stop()
{
{
std::unique_lock<std::mutex> lock(_mutex);
assert(_working == 0);
_shutdown = true;
}
_cond.notify_all();
for (auto& it : _threads)
it.join();
}
size_t count() const { return _work.size(); }
void pushWork(const ThreadFn& fn)
{
std::unique_lock<std::mutex> lock(_mutex);
assert(!_running);
assert(_working == 0);
_work.push(fn);
}
void runOne(std::unique_lock<std::mutex>& lock)
{
assert(_running);
assert(!_work.empty());
ThreadFn fn = _work.front();
_work.pop();
_working++;
lock.unlock();
try
{
fn();
}
catch (...)
{
LOG_ERR("Exception in thread pool execution.");
}
lock.lock();
_working--;
if (_work.empty() && _working == 0)
_complete.notify_all();
}
void run()
{
std::unique_lock<std::mutex> lock(_mutex);
assert(!_running);
assert(_working == 0);
_running = true;
// Avoid notifying threads if we don't need to.
bool useThreads = _threads.size() > 1 && _work.size() > 1;
if (useThreads)
_cond.notify_all();
while (!_work.empty())
runOne(lock);
if (useThreads && (_working > 0 || !_work.empty()))
_complete.wait(lock, [this]() { return _working == 0 && _work.empty(); });
_running = false;
assert(_working == 0);
assert(_work.empty());
}
void work()
{
std::unique_lock<std::mutex> lock(_mutex);
while (!_shutdown)
{
_cond.wait(lock);
while (!_shutdown && !_work.empty() && _running)
runOne(lock);
}
}
void dumpState(std::ostream& oss)
{
oss << "\tthreadPool:"
<< "\n\t\tshutdown: " << _shutdown << "\n\t\tworking: " << _working
<< "\n\t\twork count: " << count() << "\n\t\tthread count " << _threads.size() << "\n";
}
};

View File

@ -34,7 +34,8 @@ common_sources = \
../common/Util.cpp
kit_sources = ../kit/ChildSession.cpp \
../kit/Kit.cpp
../kit/Kit.cpp \
../kit/KitWebSocket.cpp
net_sources = ../net/FakeSocket.cpp \
../net/Socket.cpp

View File

@ -23,6 +23,7 @@
#include "Kit.hpp"
#include "Session.hpp"
#include "Watermark.hpp"
#include "StateRecorder.hpp"
class ChildSession;
@ -32,181 +33,7 @@ enum class LokEventTargetEnum
Window
};
// An abstract interface.
class DocumentManagerInterface
{
public:
virtual ~DocumentManagerInterface() {}
/// Request loading a document, or a new view, if one exists.
virtual bool onLoad(const std::string& sessionId,
const std::string& uriAnonym,
const std::string& renderOpts) = 0;
/// Unload a client session, which unloads the document
/// if it is the last and only.
virtual void onUnload(const ChildSession& session) = 0;
/// Access to the Kit instance.
virtual std::shared_ptr<lok::Office> getLOKit() = 0;
/// Access to the document instance.
virtual std::shared_ptr<lok::Document> getLOKitDocument() = 0;
/// Send msg to all active sessions.
virtual bool notifyAll(const std::string& msg) = 0;
/// Send updated view info to all active sessions.
virtual void notifyViewInfo() = 0;
virtual void updateEditorSpeeds(int id, int speed) = 0;
virtual int getEditorId() const = 0;
/// Get a view ID <-> UserInfo map.
virtual std::map<int, UserInfo> getViewInfo() = 0;
virtual std::string getObfuscatedFileId() = 0;
virtual std::shared_ptr<TileQueue>& getTileQueue() = 0;
virtual bool sendFrame(const char* buffer, int length, WSOpCode opCode = WSOpCode::Text) = 0;
virtual void alertAllUsers(const std::string& cmd, const std::string& kind) = 0;
virtual unsigned getMobileAppDocId() const = 0;
/// See if we should clear out our memory
virtual void trimIfInactive() = 0;
virtual bool isDocPasswordProtected() const = 0;
virtual bool haveDocPassword() const = 0;
virtual std::string getDocPassword() const = 0;
virtual DocumentPasswordType getDocPasswordType() const = 0;
virtual void updateActivityHeader() const = 0;
};
struct RecordedEvent
{
private:
int _type = 0;
std::string _payload;
public:
RecordedEvent()
{
}
RecordedEvent(int type, const std::string& payload)
: _type(type),
_payload(payload)
{
}
void setType(int type)
{
_type = type;
}
int getType() const
{
return _type;
}
void setPayload(const std::string& payload)
{
_payload = payload;
}
const std::string& getPayload() const
{
return _payload;
}
};
/// When the session is inactive, we need to record its state for a replay.
class StateRecorder
{
private:
bool _invalidate;
std::unordered_map<std::string, std::string> _recordedStates;
std::unordered_map<int, std::unordered_map<int, RecordedEvent>> _recordedViewEvents;
std::unordered_map<int, RecordedEvent> _recordedEvents;
std::vector<RecordedEvent> _recordedEventsVector;
public:
StateRecorder() : _invalidate(false) {}
// TODO Remember the maximal area we need to invalidate - grow it step by step.
void recordInvalidate()
{
_invalidate = true;
}
bool isInvalidate() const
{
return _invalidate;
}
const std::unordered_map<std::string, std::string>& getRecordedStates() const
{
return _recordedStates;
}
const std::unordered_map<int, std::unordered_map<int, RecordedEvent>>& getRecordedViewEvents() const
{
return _recordedViewEvents;
}
const std::unordered_map<int, RecordedEvent>& getRecordedEvents() const
{
return _recordedEvents;
}
const std::vector<RecordedEvent>& getRecordedEventsVector() const
{
return _recordedEventsVector;
}
void recordEvent(const int type, const std::string& payload)
{
_recordedEvents[type] = RecordedEvent(type, payload);
}
void recordViewEvent(const int viewId, const int type, const std::string& payload)
{
_recordedViewEvents[viewId][type] = {type, payload};
}
void recordState(const std::string& name, const std::string& value)
{
_recordedStates[name] = value;
}
/// In the case we need to remember all the events that come, not just
/// the final state.
void recordEventSequence(const int type, const std::string& payload)
{
_recordedEventsVector.emplace_back(type, payload);
}
void dumpState(std::ostream&)
{
// TODO: the rest ...
}
void clear()
{
_invalidate = false;
_recordedEvents.clear();
_recordedViewEvents.clear();
_recordedStates.clear();
_recordedEventsVector.clear();
}
};
class DocumentManagerInterface;
/// Represents a session to the WSD process, in a Kit process. Note that this is not a singleton.
class ChildSession final : public Session

View File

@ -32,6 +32,7 @@
#include <chrono>
#include <Poco/Path.h>
#include <Poco/URI.h>
#include <Common.hpp>
#include "Kit.hpp"
@ -41,9 +42,6 @@
#include <Unit.hpp>
#include <Util.hpp>
#include <WebSocketHandler.hpp>
#if !MOBILEAPP
#include <Admin.hpp>
#endif
#include <common/FileUtil.hpp>
#include <common/JailUtil.hpp>

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,11 @@
#include <string>
#include <common/Util.hpp>
#include <common/Session.hpp>
#include <common/ThreadPool.hpp>
#include <wsd/TileDesc.hpp>
#include "Delta.hpp"
#include "Socket.hpp"
#define LOK_USE_UNSTABLE_API
@ -31,20 +35,13 @@
void lokit_main(
#if !MOBILEAPP
const std::string& childRoot,
const std::string& jailId,
const std::string& sysTemplate,
const std::string& loTemplate,
bool noCapabilities,
bool noSeccomp,
bool queryVersionInfo,
bool displayVersion,
const std::string& childRoot, const std::string& jailId, const std::string& sysTemplate,
const std::string& loTemplate, bool noCapabilities, bool noSeccomp, bool queryVersionInfo,
bool displayVersion,
#else
int docBrokerSocket,
const std::string& userInterface,
int docBrokerSocket, const std::string& userInterface,
#endif
std::size_t numericIdentifier
);
std::size_t numericIdentifier);
#ifdef IOS
void runKitLoopInAThread();
@ -60,22 +57,15 @@ class DocumentManagerInterface;
/// callback to a specific view.
struct CallbackDescriptor
{
CallbackDescriptor(DocumentManagerInterface* const doc,
const int viewId) :
_doc(doc),
_viewId(viewId)
CallbackDescriptor(DocumentManagerInterface* const doc, const int viewId)
: _doc(doc)
, _viewId(viewId)
{
}
DocumentManagerInterface* getDoc() const
{
return _doc;
}
DocumentManagerInterface* getDoc() const { return _doc; }
int getViewId() const
{
return _viewId;
}
int getViewId() const { return _viewId; }
private:
DocumentManagerInterface* const _doc;
@ -92,43 +82,25 @@ struct UserInfo
{
}
UserInfo(const std::string& userId,
const std::string& userName,
const std::string& userExtraInfo,
const std::string& userPrivateInfo,
bool readOnly) :
_userId(userId),
_userName(userName),
_userExtraInfo(userExtraInfo),
_userPrivateInfo(userPrivateInfo),
_readOnly(readOnly)
UserInfo(const std::string& userId, const std::string& userName,
const std::string& userExtraInfo, const std::string& userPrivateInfo, bool readOnly)
: _userId(userId)
, _userName(userName)
, _userExtraInfo(userExtraInfo)
, _userPrivateInfo(userPrivateInfo)
, _readOnly(readOnly)
{
}
const std::string& getUserId() const
{
return _userId;
}
const std::string& getUserId() const { return _userId; }
const std::string& getUserName() const
{
return _userName;
}
const std::string& getUserName() const { return _userName; }
const std::string& getUserExtraInfo() const
{
return _userExtraInfo;
}
const std::string& getUserExtraInfo() const { return _userExtraInfo; }
const std::string& getUserPrivateInfo() const
{
return _userPrivateInfo;
}
const std::string& getUserPrivateInfo() const { return _userPrivateInfo; }
bool isReadOnly() const
{
return _readOnly;
}
bool isReadOnly() const { return _readOnly; }
private:
std::string _userId;
@ -138,17 +110,332 @@ private:
bool _readOnly;
};
/// We have two types of password protected documents
/// 1) Documents which require password to view
/// 2) Document which require password to modify
enum class DocumentPasswordType { ToView, ToModify };
enum class DocumentPasswordType
{
ToView,
ToModify
};
/// Check the ForkCounter, and if non-zero, fork more of them accordingly.
void forkLibreOfficeKit(const std::string& childRoot,
const std::string& sysTemplate,
void forkLibreOfficeKit(const std::string& childRoot, const std::string& sysTemplate,
const std::string& loTemplate);
class Document;
/// The main main-loop of the Kit process
class KitSocketPoll final : public SocketPoll
{
std::chrono::steady_clock::time_point _pollEnd;
std::shared_ptr<Document> _document;
static KitSocketPoll* mainPoll;
KitSocketPoll();
public:
~KitSocketPoll();
void drainQueue();
static void dumpGlobalState(std::ostream& oss);
static std::shared_ptr<KitSocketPoll> create();
virtual void wakeupHook() override;
#if ENABLE_DEBUG
struct ReEntrancyGuard
{
std::atomic<int>& _count;
ReEntrancyGuard(std::atomic<int>& count)
: _count(count)
{
count++;
}
~ReEntrancyGuard() { _count--; }
};
#endif
int kitPoll(int timeoutMicroS);
void setDocument(std::shared_ptr<Document> document) { _document = std::move(document); }
// unusual LOK event from another thread, push into our loop to process.
static bool pushToMainThread(LibreOfficeKitCallback callback, int type, const char* p,
void* data);
#ifdef IOS
static std::mutex KSPollsMutex;
// static std::condition_variable KSPollsCV;
static std::vector<std::weak_ptr<KitSocketPoll>> KSPolls;
std::mutex terminationMutex;
std::condition_variable terminationCV;
bool terminationFlag;
#endif
};
class TileQueue;
class ChildSession;
// An abstract interface.
class DocumentManagerInterface
{
public:
virtual ~DocumentManagerInterface() {}
/// Request loading a document, or a new view, if one exists.
virtual bool onLoad(const std::string& sessionId, const std::string& uriAnonym,
const std::string& renderOpts) = 0;
/// Unload a client session, which unloads the document
/// if it is the last and only.
virtual void onUnload(const ChildSession& session) = 0;
/// Access to the Kit instance.
virtual std::shared_ptr<lok::Office> getLOKit() = 0;
/// Access to the document instance.
virtual std::shared_ptr<lok::Document> getLOKitDocument() = 0;
/// Send msg to all active sessions.
virtual bool notifyAll(const std::string& msg) = 0;
/// Send updated view info to all active sessions.
virtual void notifyViewInfo() = 0;
virtual void updateEditorSpeeds(int id, int speed) = 0;
virtual int getEditorId() const = 0;
/// Get a view ID <-> UserInfo map.
virtual std::map<int, UserInfo> getViewInfo() = 0;
virtual std::string getObfuscatedFileId() = 0;
virtual std::shared_ptr<TileQueue>& getTileQueue() = 0;
virtual bool sendFrame(const char* buffer, int length, WSOpCode opCode = WSOpCode::Text) = 0;
virtual void alertAllUsers(const std::string& cmd, const std::string& kind) = 0;
virtual unsigned getMobileAppDocId() const = 0;
/// See if we should clear out our memory
virtual void trimIfInactive() = 0;
virtual bool isDocPasswordProtected() const = 0;
virtual bool haveDocPassword() const = 0;
virtual std::string getDocPassword() const = 0;
virtual DocumentPasswordType getDocPasswordType() const = 0;
virtual void updateActivityHeader() const = 0;
};
/// 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 coolkit instance
/// per user or group of users (a trusted circle).
class Document final : public DocumentManagerInterface
{
public:
Document(const std::shared_ptr<lok::Office>& loKit, const std::string& jailId,
const std::string& docKey, const std::string& docId, const std::string& url,
std::shared_ptr<TileQueue> tileQueue,
const std::shared_ptr<WebSocketHandler>& websocketHandler, unsigned mobileAppDocId);
virtual ~Document();
const std::string& getUrl() const { return _url; }
/// Post the message - in the unipoll world we're in the right thread anyway
bool postMessage(const char* data, int size, const WSOpCode code) const;
bool createSession(const std::string& sessionId);
/// Purges dead connections and returns
/// the remaining number of clients.
/// Returns -1 on failure.
std::size_t purgeSessions();
void setDocumentPassword(int passwordType);
void renderTiles(TileCombined& tileCombined);
bool sendTextFrame(const std::string& message)
{
return sendFrame(message.data(), message.size());
}
bool sendFrame(const char* buffer, int length, WSOpCode opCode = WSOpCode::Text) override;
void alertNotAsync()
{
// load unfortunately enables inputprocessing in some cases.
if (processInputEnabled() && !_duringLoad)
notifyAll("error: cmd=notasync kind=failure");
}
void alertAllUsers(const std::string& cmd, const std::string& kind) override
{
alertAllUsers("errortoall: cmd=" + cmd + " kind=" + kind);
}
void alertAllUsers(const std::string& msg) { sendTextFrame(msg); }
/// Notify all views with the given message
bool notifyAll(const std::string& msg) override
{
// Broadcast updated viewinfo to all clients.
return sendTextFrame("client-all " + msg);
}
unsigned getMobileAppDocId() const override { return _mobileAppDocId; }
void trimIfInactive() override;
void trimAfterInactivity();
// LibreOfficeKit callback entry points
static void GlobalCallback(const int type, const char* p, void* data);
static void ViewCallback(const int type, const char* p, void* data);
private:
/// Helper method to broadcast callback and its payload to all clients
void broadcastCallbackToClients(const int type, const std::string& payload)
{
_tileQueue->put("callback all " + std::to_string(type) + ' ' + payload);
}
/// Load a document (or view) and register callbacks.
bool onLoad(const std::string& sessionId, const std::string& uriAnonym,
const std::string& renderOpts) override;
void onUnload(const ChildSession& session) override;
std::map<int, UserInfo> getViewInfo() override { return _sessionUserInfo; }
std::shared_ptr<TileQueue>& getTileQueue() override { return _tileQueue; }
int getEditorId() const override { return _editorId; }
bool isDocPasswordProtected() const override { return _isDocPasswordProtected; }
bool haveDocPassword() const override { return _haveDocPassword; }
std::string getDocPassword() const override { return _docPassword; }
DocumentPasswordType getDocPasswordType() const override { return _docPasswordType; }
void updateActivityHeader() const override;
/// Notify all views of viewId and their associated usernames
void notifyViewInfo() override;
std::shared_ptr<ChildSession> findSessionByViewId(int viewId);
void invalidateCanonicalId(const std::string& sessionId);
std::string getViewProps(const std::shared_ptr<ChildSession>& session);
void updateEditorSpeeds(int id, int speed) override;
private:
// Get the color value for all author names from the core
std::map<std::string, int> getViewColors();
std::string getDefaultTheme(const std::shared_ptr<ChildSession>& session) const;
std::shared_ptr<lok::Document> load(const std::shared_ptr<ChildSession>& session,
const std::string& renderOpts);
bool forwardToChild(const std::string& prefix, const std::vector<char>& payload);
static std::string makeRenderParams(const std::string& renderOpts, const std::string& userName,
const std::string& spellOnline, const std::string& theme);
bool isTileRequestInsideVisibleArea(const TileCombined& tileCombined);
public:
void enableProcessInput(bool enable = true) { _inputProcessingEnabled = enable; }
bool processInputEnabled() const { return _inputProcessingEnabled; }
bool hasQueueItems() const { return _tileQueue && !_tileQueue->isEmpty(); }
// poll is idle, are we ?
void checkIdle();
void drainQueue();
void dumpState(std::ostream& oss);
private:
/// Return access to the lok::Office instance.
std::shared_ptr<lok::Office> getLOKit() override { return _loKit; }
/// Return access to the lok::Document instance.
std::shared_ptr<lok::Document> getLOKitDocument() override;
std::string getObfuscatedFileId() override { return _obfuscatedFileId; }
#if !MOBILEAPP
/// Stops theads, flushes buffers, and exits the process.
void flushAndExit(int code);
#endif
private:
std::shared_ptr<lok::Office> _loKit;
const std::string _jailId;
/// URL-based key. May be repeated during the lifetime of WSD.
const std::string _docKey;
/// Short numerical ID. Unique during the lifetime of WSD.
const std::string _docId;
const std::string _url;
const std::string _obfuscatedFileId;
std::string _jailedUrl;
std::string _renderOpts;
std::shared_ptr<lok::Document> _loKitDocument;
#ifdef __ANDROID__
static std::shared_ptr<lok::Document> _loKitDocumentForAndroidOnly;
#endif
std::shared_ptr<TileQueue> _tileQueue;
std::shared_ptr<WebSocketHandler> _websocketHandler;
// Document password provided
std::string _docPassword;
// Whether password was provided or not
bool _haveDocPassword;
// Whether document is password protected
bool _isDocPasswordProtected;
// Whether password is required to view the document, or modify it
DocumentPasswordType _docPasswordType;
std::atomic<bool> _stop;
ThreadPool _pngPool;
DeltaGenerator _deltaGen;
std::condition_variable _cvLoading;
int _editorId;
bool _editorChangeWarning;
std::map<int, std::unique_ptr<CallbackDescriptor>> _viewIdToCallbackDescr;
SessionMap<ChildSession> _sessions;
/// The timestamp of the last memory trimming.
std::chrono::steady_clock::time_point _lastMemTrimTime;
std::map<int, std::chrono::steady_clock::time_point> _lastUpdatedAt;
std::map<int, int> _speedCount;
/// For showing disconnected user info in the doc repair dialog.
std::map<int, UserInfo> _sessionUserInfo;
#ifdef __ANDROID__
friend std::shared_ptr<lok::Document> getLOKDocumentForAndroidOnly();
#endif
const unsigned _mobileAppDocId;
bool _inputProcessingEnabled;
int _duringLoad;
};
/// main function of the forkit process or thread
int forkit_main(int argc, char** argv);
@ -177,4 +464,7 @@ bool isURPEnabled();
/// Start a URP connection, checking if URP is enabled and there is not already an active URP session
bool startURP(std::shared_ptr<lok::Office> LOKit, void** ppURPContext);
/// Ensure all recorded traces hit the disk
void flushTraceEventRecordings();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View File

@ -0,0 +1,183 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* Copyright the Collabora Online contributors.
*
* SPDX-License-Identifier: MPL-2.0
*
* 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/.
*/
/*
* The main entry point for the LibreOfficeKit process serving
* a document editing session.
*/
#include <config.h>
#include <Poco/URI.h>
#include <sysexits.h> // EX_OK
#include <common/Seccomp.hpp>
#include <common/TraceEvent.hpp>
#include <common/MessageQueue.hpp>
#include <Kit.hpp>
#include "KitWebSocket.hpp"
using Poco::Exception;
using Poco::URI;
void KitWebSocketHandler::handleMessage(const std::vector<char>& data)
{
// To get A LOT of Trace Events, to exercise their handling, uncomment this:
// ProfileZone profileZone("KitWebSocketHandler::handleMessage");
std::string message(data.data(), data.size());
#if !MOBILEAPP
if (UnitKit::get().filterKitMessage(this, message))
return;
#endif
StringVector tokens = StringVector::tokenize(message);
LOG_DBG(_socketName << ": recv [" << [&](auto& log) {
for (const auto& token : tokens)
{
// Don't log user-data, there are anonymized versions that get logged instead.
if (tokens.startsWith(token, "jail") || tokens.startsWith(token, "author") ||
tokens.startsWith(token, "name") || tokens.startsWith(token, "url"))
continue;
log << tokens.getParam(token) << ' ';
}
});
// Note: Syntax or parsing errors here are unexpected and fatal.
if (SigUtil::getTerminationFlag())
{
LOG_DBG("Too late, TerminationFlag is set, we're going down");
}
else if (tokens.equals(0, "session"))
{
const std::string& sessionId = tokens[1];
_docKey = tokens[2];
const std::string& docId = tokens[3];
const std::string fileId = Util::getFilenameFromURL(_docKey);
Util::mapAnonymized(fileId, fileId); // Identity mapping, since fileId is already obfuscated
std::string url;
URI::decode(_docKey, url);
#ifndef IOS
Util::setThreadName("kit" SHARED_DOC_THREADNAME_SUFFIX + docId);
#endif
if (!_document)
{
_document = std::make_shared<Document>(
_loKit, _jailId, _docKey, docId, url, _queue,
std::static_pointer_cast<WebSocketHandler>(shared_from_this()), _mobileAppDocId);
_ksPoll->setDocument(_document);
// We need to send the process name information to WSD if Trace Event recording is enabled (but
// not turned on) because it might be turned on later.
// We can do this only after creating the Document object.
TraceEvent::emitOneRecordingIfEnabled(
std::string("{\"name\":\"process_name\",\"ph\":\"M\",\"args\":{\"name\":\"") +
"Kit-" + docId + "\"},\"pid\":" + std::to_string(getpid()) +
",\"tid\":" + std::to_string(Util::getThreadId()) + "},\n");
}
// Validate and create session.
if (!(url == _document->getUrl() && _document->createSession(sessionId)))
{
LOG_DBG("CreateSession failed.");
}
}
else if (tokens.equals(0, "exit"))
{
#if !MOBILEAPP
LOG_INF("Terminating immediately due to parent 'exit' command.");
flushTraceEventRecordings();
_document.reset();
if (!Util::isKitInProcess())
Util::forcedExit(EX_OK);
else
SigUtil::setTerminationFlag();
#else
#ifdef IOS
LOG_INF("Setting our KitSocketPoll's termination flag due to 'exit' command.");
std::unique_lock<std::mutex> lock(_ksPoll->terminationMutex);
_ksPoll->terminationFlag = true;
_ksPoll->terminationCV.notify_all();
#else
LOG_INF("Setting TerminationFlag due to 'exit' command.");
SigUtil::setTerminationFlag();
#endif
_document.reset();
#endif
}
else if (tokens.equals(0, "tile") || tokens.equals(0, "tilecombine") ||
tokens.equals(0, "paintwindow") || tokens.equals(0, "resizewindow") ||
COOLProtocol::getFirstToken(tokens[0], '-') == "child")
{
if (_document)
{
_queue->put(message);
}
else
{
LOG_WRN("No document while processing " << tokens[0] << " request.");
}
}
else if (tokens.size() == 3 && tokens.equals(0, "setconfig"))
{
#if !MOBILEAPP && !defined(BUILDING_TESTS)
// Currently only rlimit entries are supported.
if (!Rlimit::handleSetrlimitCommand(tokens))
{
LOG_ERR("Unknown setconfig command: " << message);
}
#endif
}
else if (tokens.equals(0, "setloglevel"))
{
Log::logger().setLevel(tokens[1]);
}
else
{
LOG_ERR("Bad or unknown token [" << tokens[0] << ']');
}
}
void KitWebSocketHandler::enableProcessInput(bool enable)
{
WebSocketHandler::enableProcessInput(enable);
if (_document)
_document->enableProcessInput(enable);
// Wake up poll to process data from socket input buffer
if (enable && _ksPoll)
_ksPoll->wakeup();
}
void KitWebSocketHandler::onDisconnect()
{
#if !MOBILEAPP
//FIXME: We could try to recover.
LOG_ERR("Kit for DocBroker ["
<< _docKey
<< "] connection lost without exit arriving from wsd. Setting TerminationFlag");
SigUtil::setTerminationFlag();
#endif
#ifdef IOS
{
std::unique_lock<std::mutex> lock(_ksPoll->terminationMutex);
_ksPoll->terminationFlag = true;
_ksPoll->terminationCV.notify_all();
}
#endif
_ksPoll.reset();
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View File

@ -0,0 +1,56 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* Copyright the Collabora Online contributors.
*
* SPDX-License-Identifier: MPL-2.0
*
* 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/.
*/
/*
* The main entry point for the LibreOfficeKit process serving
* a document editing session.
*/
#include <net/WebSocketHandler.hpp>
class Document;
class TileQueue;
class KitSocketPoll;
class KitWebSocketHandler final : public WebSocketHandler
{
std::shared_ptr<TileQueue> _queue;
std::string _socketName;
std::shared_ptr<lok::Office> _loKit;
std::string _jailId;
std::string _docKey; //< When we get it while creating a new view.
std::shared_ptr<Document> _document;
std::shared_ptr<KitSocketPoll> _ksPoll;
const unsigned _mobileAppDocId;
public:
KitWebSocketHandler(const std::string& socketName, const std::shared_ptr<lok::Office>& loKit,
const std::string& jailId, std::shared_ptr<KitSocketPoll> ksPoll,
unsigned mobileAppDocId)
: WebSocketHandler(/* isClient = */ true, /* isMasking */ false)
, _queue(std::make_shared<TileQueue>())
, _socketName(socketName)
, _loKit(loKit)
, _jailId(jailId)
, _ksPoll(std::move(ksPoll))
, _mobileAppDocId(mobileAppDocId)
{
}
~KitWebSocketHandler()
{
// Just to make it easier to set a breakpoint
}
protected:
virtual void handleMessage(const std::vector<char>& data) override;
virtual void enableProcessInput(bool enable = true) override;
virtual void onDisconnect() override;
};

View File

@ -0,0 +1,117 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* Copyright the Collabora Online contributors.
*
* SPDX-License-Identifier: MPL-2.0
*
* 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/.
*/
/*
* When the session is inactive, we need to record its state for a replay.
*/
struct RecordedEvent
{
private:
int _type = 0;
std::string _payload;
public:
RecordedEvent() {}
RecordedEvent(int type, const std::string& payload)
: _type(type)
, _payload(payload)
{
}
void setType(int type) { _type = type; }
int getType() const { return _type; }
void setPayload(const std::string& payload) { _payload = payload; }
const std::string& getPayload() const { return _payload; }
};
class StateRecorder
{
private:
bool _invalidate;
std::unordered_map<std::string, std::string> _recordedStates;
std::unordered_map<int, std::unordered_map<int, RecordedEvent>> _recordedViewEvents;
std::unordered_map<int, RecordedEvent> _recordedEvents;
std::vector<RecordedEvent> _recordedEventsVector;
public:
StateRecorder()
: _invalidate(false)
{
}
// TODO Remember the maximal area we need to invalidate - grow it step by step.
void recordInvalidate() { _invalidate = true; }
bool isInvalidate() const { return _invalidate; }
const std::unordered_map<std::string, std::string>& getRecordedStates() const
{
return _recordedStates;
}
const std::unordered_map<int, std::unordered_map<int, RecordedEvent>>&
getRecordedViewEvents() const
{
return _recordedViewEvents;
}
const std::unordered_map<int, RecordedEvent>& getRecordedEvents() const
{
return _recordedEvents;
}
const std::vector<RecordedEvent>& getRecordedEventsVector() const
{
return _recordedEventsVector;
}
void recordEvent(const int type, const std::string& payload)
{
_recordedEvents[type] = RecordedEvent(type, payload);
}
void recordViewEvent(const int viewId, const int type, const std::string& payload)
{
_recordedViewEvents[viewId][type] = { type, payload };
}
void recordState(const std::string& name, const std::string& value)
{
_recordedStates[name] = value;
}
/// In the case we need to remember all the events that come, not just
/// the final state.
void recordEventSequence(const int type, const std::string& payload)
{
_recordedEventsVector.emplace_back(type, payload);
}
void dumpState(std::ostream&)
{
// TODO: the rest ...
}
void clear()
{
_invalidate = false;
_recordedEvents.clear();
_recordedViewEvents.clear();
_recordedStates.clear();
_recordedEventsVector.clear();
}
};
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

View File

@ -113,9 +113,10 @@ endif
AM_CPPFLAGS = -pthread -I$(top_srcdir) -DBUILDING_TESTS -DLOK_ABORT_ON_ASSERTION
wsd_sources = \
../common/SpookyV2.cpp \
../common/Authorization.cpp \
../common/SpookyV2.cpp \
../kit/Kit.cpp \
../kit/KitWebSocket.cpp \
../kit/TestStubs.cpp \
../wsd/FileServerUtil.cpp \
../wsd/RequestDetails.cpp \

View File

@ -38,6 +38,7 @@ online_SOURCES = \
../common/Util.cpp \
../kit/ChildSession.cpp \
../kit/Kit.cpp \
../kit/KitWebSocket.cpp \
../kit/DeltaSimd.c \
../net/FakeSocket.cpp \
../net/Socket.cpp \