loolwsd/LOOLSession.cpp | 245 +++------------------- loolwsd/LOOLSession.hpp | 7 loolwsd/LOOLWSD.cpp | 528 ++++++++++++++++++++++++++++++++++++------------ loolwsd/LOOLWSD.hpp | 14 + 4 files changed, 454 insertions(+), 340 deletions(-)
New commits: commit bb3611399be37ba545421198ae0d7a2016fb45ea Author: Henry Castro <hcas...@collabora.com> Date: Mon Jul 13 10:13:06 2015 -0400 loolwsd: Use fork but no execve diff --git a/loolwsd/LOOLSession.cpp b/loolwsd/LOOLSession.cpp index 982a95f..0fdf4d8 100644 --- a/loolwsd/LOOLSession.cpp +++ b/loolwsd/LOOLSession.cpp @@ -43,6 +43,9 @@ #include <Poco/URIStreamOpener.h> #include <Poco/Util/Application.h> #include <Poco/Exception.h> +#include <Poco/Net/NetException.h> +#include <Poco/Net/DialogSocket.h> +#include <Poco/Net/SocketAddress.h> #include "LOKitHelper.hpp" #include "LOOLProtocol.hpp" @@ -70,6 +73,9 @@ using Poco::URI; using Poco::URIStreamOpener; using Poco::Util::Application; using Poco::Exception; +using Poco::Net::DialogSocket; +using Poco::Net::SocketAddress; +using Poco::Net::WebSocketException; const std::string LOOLSession::jailDocumentURL = "/user/thedocument"; @@ -215,13 +221,22 @@ bool MasterProcessSession::handleInput(const char *buffer, int length) sendTextFrame("error: cmd=child kind=syntax"); return false; } + UInt64 childId = std::stoull(tokens[1]); - if (_pendingPreSpawnedChildren.find(childId) == _pendingPreSpawnedChildren.end()) + // TODO. rework, the desktop and its childrem is jail root same folder + /*if (_pendingPreSpawnedChildren.find(childId) == _pendingPreSpawnedChildren.end()) { + std::cout << Util::logPrefix() << "Error _pendingPreSpawnedChildren.find(childId)" << this << " id=" << childId << std::endl; + sendTextFrame("error: cmd=child kind=notfound"); return false; + }*/ + + if (_pendingPreSpawnedChildren.size() > 0) + { + std::set<UInt64>::iterator it = _pendingPreSpawnedChildren.begin(); + _pendingPreSpawnedChildren.erase(it); } - _pendingPreSpawnedChildren.erase(childId); std::unique_lock<std::mutex> lock(_availableChildSessionMutex); _availableChildSessions.insert(shared_from_this()); std::cout << Util::logPrefix() << "Inserted " << this << " id=" << childId << " into _availableChildSessions, size=" << _availableChildSessions.size() << std::endl; @@ -306,176 +321,22 @@ Path MasterProcessSession::getJailPath(UInt64 childId) return Path::forDirectory(LOOLWSD::childRoot + Path::separator() + std::to_string(childId)); } -namespace +void MasterProcessSession::addPendingChildrem(UInt64 childId) { - ThreadLocal<std::string> sourceForLinkOrCopy; - ThreadLocal<Path> destinationForLinkOrCopy; - - 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; - -#ifdef __APPLE__ - if (strcmp(relativeOldPath, "PkgInfo") == 0) - return 0; -#endif - - Path newPath(*destinationForLinkOrCopy, Path(relativeOldPath)); - - switch (typeflag) - { - case FTW_F: - File(newPath.parent()).createDirectories(); - if (link(fpath, newPath.toString().c_str()) == -1) - { - Application::instance().logger().error(Util::logPrefix() + - "link(\"" + fpath + "\",\"" + newPath.toString() + "\") failed: " + - strerror(errno)); - exit(1); - } - break; - case FTW_DP: - { - struct stat st; - if (stat(fpath, &st) == -1) - { - Application::instance().logger().error(Util::logPrefix() + - "stat(\"" + fpath + "\") failed: " + - strerror(errno)); - return 1; - } - File(newPath).createDirectories(); - struct utimbuf ut; - ut.actime = st.st_atime; - ut.modtime = st.st_mtime; - if (utime(newPath.toString().c_str(), &ut) == -1) - { - Application::instance().logger().error(Util::logPrefix() + - "utime(\"" + newPath.toString() + "\", &ut) failed: " + - strerror(errno)); - return 1; - } - } - break; - case FTW_DNR: - Application::instance().logger().error(Util::logPrefix() + - "Cannot read directory '" + fpath + "'"); - return 1; - case FTW_NS: - Application::instance().logger().error(Util::logPrefix() + - "nftw: stat failed for '" + fpath + "'"); - return 1; - case FTW_SLN: - Application::instance().logger().information(Util::logPrefix() + - "nftw: symlink to nonexistent file: '" + fpath + "', ignored"); - break; - default: - assert(false); - } - return 0; - } - - void linkOrCopy(const std::string& source, const Path& destination) - { - *sourceForLinkOrCopy = source; - if (sourceForLinkOrCopy->back() == '/') - sourceForLinkOrCopy->pop_back(); - *destinationForLinkOrCopy = destination; - if (nftw(source.c_str(), linkOrCopyFunction, 10, FTW_DEPTH) == -1) - Application::instance().logger().error(Util::logPrefix() + - "linkOrCopy: nftw() failed for '" + source + "'"); - } + _pendingPreSpawnedChildren.insert(childId); } -void MasterProcessSession::preSpawn() +int MasterProcessSession::getAvailableChildSessions() { - // Create child-specific subtree that will become its chroot root - - std::unique_lock<std::mutex> rngLock(_rngMutex); - UInt64 childId = (((UInt64)_rng.next()) << 32) | _rng.next() | 1; - rngLock.unlock(); - - Path jail = getJailPath(childId); - File(jail).createDirectory(); - - Path jailLOInstallation(jail, LOOLWSD::loSubPath); - jailLOInstallation.makeDirectory(); - File(jailLOInstallation).createDirectory(); - - // Copy (link) LO installation and other necessary files into it from the template - - linkOrCopy(LOOLWSD::sysTemplate, jail); - linkOrCopy(LOOLWSD::loTemplate, jailLOInstallation); - -#ifdef __linux - // Create the urandom and random devices - File(Path(jail, "/dev")).createDirectory(); - if (mknod((jail.toString() + "/dev/random").c_str(), - S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, - makedev(1, 8)) != 0) - { - Application::instance().logger().error(Util::logPrefix() + - "mknod(" + jail.toString() + "/dev/random) failed: " + - strerror(errno)); - - } - if (mknod((jail.toString() + "/dev/urandom").c_str(), - S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, - makedev(1, 9)) != 0) - { - Application::instance().logger().error(Util::logPrefix() + - "mknod(" + jail.toString() + "/dev/urandom) failed: " + - strerror(errno)); - - } -#endif - - _pendingPreSpawnedChildren.insert(childId); - - Process::Args args; - -#if ENABLE_DEBUG - if (LOOLWSD::runningAsRoot) - args.push_back(Application::instance().commandPath()); -#endif - - args.push_back("--child=" + std::to_string(childId)); - args.push_back("--port=" + std::to_string(LOOLWSD::portNumber)); - args.push_back("--jail=" + jail.toString()); - args.push_back("--losubpath=" + LOOLWSD::loSubPath); - - std::string executable; - -#if ENABLE_DEBUG - if (LOOLWSD::runningAsRoot) - { - args.push_back("--uid=" + std::to_string(LOOLWSD::uid)); - executable = "/usr/bin/sudo"; - } - else -#endif - { - executable = Application::instance().commandPath(); - } - - Application::instance().logger().information(Util::logPrefix() + "Launching child: " + executable + " " + Poco::cat(std::string(" "), args.begin(), args.end())); - -#if ENABLE_DEBUG - ProcessHandle child = Process::launch(executable, args); -#else - ProcessHandle child = Process::launch(Application::instance().commandPath(), args); -#endif + return _availableChildSessions.size(); +} - _childProcesses[child.id()] = childId; +int MasterProcessSession::getPendingPreSpawnedChildren() +{ + return _pendingPreSpawnedChildren.size(); } + bool MasterProcessSession::invalidateTiles(const char *buffer, int length, StringTokenizer& tokens) { int part, tilePosX, tilePosY, tileWidth, tileHeight; @@ -616,9 +477,6 @@ void MasterProcessSession::dispatchChild() { // Running out of pre-spawned children, so spawn one more Application::instance().logger().information(Util::logPrefix() + "Running out of pre-spawned childred, adding one more"); - lock.unlock(); - preSpawn(); - lock.lock(); } std::cout << Util::logPrefix() << "waiting for a child session to become available" << std::endl; @@ -640,46 +498,29 @@ void MasterProcessSession::dispatchChild() if (!aUri.empty() && aUri.getScheme() == "file") { - Path aSrcFile(aUri.getPath()); - Path aDstFile(Path(getJailPath(childSession->_childId), jailDocumentURL.substr(1)), aSrcFile.getFileName()); - Path aDstPath(getJailPath(childSession->_childId), jailDocumentURL.substr(1)); - Path aJailFile(jailDocumentURL, aSrcFile.getFileName()); - + // The process is jail rooted, so it requests loolMain process to transfer files. try { - File(aDstPath).createDirectories(); + Path aSrcFile(aUri.getPath()); + Path aDstFile(Path(getJailPath(childSession->_childId), jailDocumentURL.substr(1)), aSrcFile.getFileName()); + std::string sCopy(aSrcFile.toString() + " " + aDstFile.toString()); + std::string str; + + DialogSocket ds; + ds.connect(SocketAddress("127.0.0.1", LOOLWSD::FILE_PORT_NUMBER)); + ds.sendMessage(sCopy); + ds.receiveMessage(str); + + if (str != "OK") + Application::instance().logger().error( Util::logPrefix() + + "DataSocket copyTo(\"" + aSrcFile.toString() + "\",\"" + aDstFile.toString() + "\") failed: " + str); } catch (Exception& exc) { Application::instance().logger().error( Util::logPrefix() + - "createDirectories(\"" + aDstPath.toString() + "\") failed: " + exc.displayText() ); - + "FileTransferHanlder failed: " + exc.displayText()); } -#ifdef __linux - Application::instance().logger().information(Util::logPrefix() + "Linking " + aSrcFile.toString() + " to " + aDstFile.toString()); - if (link(aSrcFile.toString().c_str(), aDstFile.toString().c_str()) == -1) - { - // Failed - Application::instance().logger().error( Util::logPrefix() + - "link(\"" + aSrcFile.toString() + "\",\"" + aDstFile.toString() + "\") failed: " + strerror(errno) ); - } -#endif - - try - { - //fallback - if (!File(aDstFile).exists()) - { - Application::instance().logger().information(Util::logPrefix() + "Copying " + aSrcFile.toString() + " to " + aDstFile.toString()); - File(aSrcFile).copyTo(aDstFile.toString()); - } - } - catch (Exception& exc) - { - Application::instance().logger().error( Util::logPrefix() + - "copyTo(\"" + aSrcFile.toString() + "\",\"" + aDstFile.toString() + "\") failed: " + exc.displayText()); - } } _peer = childSession; @@ -687,9 +528,6 @@ void MasterProcessSession::dispatchChild() std::string loadRequest = "load url=" + _docURL; forwardToPeer(loadRequest.c_str(), loadRequest.size()); - - // As we took one child process into use, spawn a new one - preSpawn(); } void MasterProcessSession::forwardToPeer(const char *buffer, int length) @@ -919,6 +757,7 @@ bool ChildProcessSession::loadDocument(const char *buffer, int length, StringTok sendTextFrame("error: cmd=load kind=failed"); return false; } + _loKitDocument->pClass->initializeForRendering(_loKitDocument); if (!getStatus(buffer, length)) diff --git a/loolwsd/LOOLSession.hpp b/loolwsd/LOOLSession.hpp index 221de8b..a5381ca 100644 --- a/loolwsd/LOOLSession.hpp +++ b/loolwsd/LOOLSession.hpp @@ -102,10 +102,9 @@ public: bool haveSeparateProcess(); static Poco::Path getJailPath(Poco::UInt64 childId); - - // Set up the chroot environment for one child process and start - // it, in advance of it being actually needed - static void preSpawn(); + static void addPendingChildrem(Poco::UInt64 childId); + static int getAvailableChildSessions(); + static int getPendingPreSpawnedChildren(); static std::map<Poco::Process::PID, Poco::UInt64> _childProcesses; diff --git a/loolwsd/LOOLWSD.cpp b/loolwsd/LOOLWSD.cpp index 6856429..631e2b3 100644 --- a/loolwsd/LOOLWSD.cpp +++ b/loolwsd/LOOLWSD.cpp @@ -50,10 +50,15 @@ DEALINGS IN THE SOFTWARE. #include <sys/wait.h> #endif +#include <ftw.h> +#include <utime.h> + #include <cassert> #include <cstdlib> #include <cstring> #include <iostream> +#include <sstream> +#include <mutex> #define LOK_USE_UNSTABLE_API #include <LibreOfficeKit/LibreOfficeKitInit.h> @@ -82,6 +87,12 @@ DEALINGS IN THE SOFTWARE. #include <Poco/Util/OptionException.h> #include <Poco/Util/OptionSet.h> #include <Poco/Util/ServerApplication.h> +#include <Poco/Mutex.h> +#include <Poco/Net/DialogSocket.h> +#include <Poco/Net/Net.h> +#include <Poco/ThreadLocal.h> +#include <Poco/NamedMutex.h> + #include "LOOLProtocol.hpp" #include "LOOLSession.hpp" @@ -120,6 +131,12 @@ using Poco::Util::MissingOptionException; using Poco::Util::Option; using Poco::Util::OptionSet; using Poco::Util::ServerApplication; +using Poco::Net::DialogSocket; +using Poco::FastMutex; +using Poco::Net::Socket; +using Poco::ThreadLocal; +using Poco::Random; +using Poco::NamedMutex; class QueueHandler: public Runnable { @@ -392,71 +409,92 @@ private: HTTPServer& _srv; }; -class Undertaker : public Runnable +class FileTransferHandler : public Runnable { public: - Undertaker() + FileTransferHandler() : _socket(ServerSocket(LOOLWSD::FILE_PORT_NUMBER)) { } void run() override { - bool someChildrenHaveDied = false; + Poco::Timespan span(250000); - while (!someChildrenHaveDied || MasterProcessSession::_childProcesses.size() > 0) + while (true) { - int status; - pid_t pid = waitpid(-1, &status, 0); - if (pid < 0) + if (_socket.poll(span, Socket::SELECT_READ)) { - if (errno == ECHILD) + DialogSocket ds = _socket.acceptConnection(); + + try { - if (!someChildrenHaveDied) + std::string command; + while (ds.receiveMessage(command)) { - // We haven't spawned any children yet, or at least none has died yet. So - // wait a bit and try again - Thread::sleep(1000); - continue; - } - else - { - // We have spawned children, and we think that we still have them running, - // but we don't, huh? Something badly messed up, or just a timing glitch, - // like we are at the moment in the process of spawning new children? - // Sleep or return from the function (i.e. finish the Undertaker thread)? - std::cout << Util::logPrefix() << "No child processes even if we think there should be some!?" << std::endl; - return; + FastMutex::ScopedLock lock(_mutex); + ds.sendMessage(transferFile(command)); } } + catch (Poco::Exception& exc) + { + std::cerr << "FileTransferHandler: " << exc.displayText() << std::endl; + } } + } + } + + std::string transferFile(std::string command) + { + StringTokenizer tokens(command, " ", StringTokenizer::TOK_IGNORE_EMPTY | StringTokenizer::TOK_TRIM); + if ( tokens.count() != 2 ) + return "Souce and Destination is needed :" + command; + + Path aSrcFile(tokens[0]); + Path aDstFile(tokens[1]); + Path aDstPath(aDstFile.parent()); + + try + { + File(aDstPath).createDirectories(); + } + catch (Exception& exc) + { + return exc.displayText(); + } - if (WIFSIGNALED(status)) - Application::instance().logger().error(Util::logPrefix() + "Child " + std::to_string(pid) + " killed by signal " + Util::signalName(WTERMSIG(status))); - else - Application::instance().logger().information(Util::logPrefix() + "Child " + std::to_string(pid) + " died normally, status: " + std::to_string(WEXITSTATUS(status))); +#ifdef __linux + Application::instance().logger().information(Util::logPrefix() + "Linking " + aSrcFile.toString() + " to " + aDstFile.toString()); + if (link(aSrcFile.toString().c_str(), aDstFile.toString().c_str()) == -1) + { + // Failed + Application::instance().logger().error( Util::logPrefix() + + "link(\"" + aSrcFile.toString() + "\",\"" + aDstFile.toString() + "\") failed: " + strerror(errno) ); + } +#endif - if (MasterProcessSession::_childProcesses.find(pid) == MasterProcessSession::_childProcesses.end()) - std::cout << Util::logPrefix() << "(Not one of our known child processes)" << std::endl; - else + try + { + //fallback + if (!File(aDstFile).exists()) { - File jailDir(MasterProcessSession::getJailPath(MasterProcessSession::_childProcesses[pid])); - MasterProcessSession::_childProcesses.erase(pid); - someChildrenHaveDied = true; - if (!jailDir.exists() || !jailDir.isDirectory()) - { - Application::instance().logger().error(Util::logPrefix() + "Jail '" + jailDir.path() + "' does not exist or is not a directory"); - } - else - { - std::cout << Util::logPrefix() << "Removing jail tree " << jailDir.path() << std::endl; - jailDir.remove(true); - } + Application::instance().logger().information(Util::logPrefix() + "Copying " + aSrcFile.toString() + " to " + aDstFile.toString()); + File(aSrcFile).copyTo(aDstFile.toString()); } } - std::cout << Util::logPrefix() << "All child processes have died (I hope)" << std::endl; + catch (Exception& exc) + { + return exc.displayText(); + } + + return "OK"; } + +private: + Poco::Net::ServerSocket _socket; + mutable Poco::FastMutex _mutex; }; + int LOOLWSD::portNumber = DEFAULT_CLIENT_PORT_NUMBER; std::string LOOLWSD::cache = LOOLWSD_CACHEDIR; std::string LOOLWSD::sysTemplate; @@ -464,6 +502,10 @@ std::string LOOLWSD::loTemplate; std::string LOOLWSD::childRoot; std::string LOOLWSD::loSubPath = "lo"; std::string LOOLWSD::jail; +std::mutex LOOLWSD::_rngMutex; +Random LOOLWSD::_rng; +static NamedMutex namedMutexLOOL("loolwsd"); + int LOOLWSD::_numPreSpawnedChildren = 10; #if ENABLE_DEBUG bool LOOLWSD::runningAsRoot = false; @@ -603,6 +645,91 @@ void LOOLWSD::displayHelp() namespace { + ThreadLocal<std::string> sourceForLinkOrCopy; + ThreadLocal<Path> destinationForLinkOrCopy; + + 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; + +#ifdef __APPLE__ + if (strcmp(relativeOldPath, "PkgInfo") == 0) + return 0; +#endif + + Path newPath(*destinationForLinkOrCopy, Path(relativeOldPath)); + + switch (typeflag) + { + case FTW_F: + File(newPath.parent()).createDirectories(); + if (link(fpath, newPath.toString().c_str()) == -1) + { + Application::instance().logger().error(Util::logPrefix() + + "link(\"" + fpath + "\",\"" + newPath.toString() + "\") failed: " + + strerror(errno)); + exit(1); + } + break; + case FTW_DP: + { + struct stat st; + if (stat(fpath, &st) == -1) + { + Application::instance().logger().error(Util::logPrefix() + + "stat(\"" + fpath + "\") failed: " + + strerror(errno)); + return 1; + } + File(newPath).createDirectories(); + struct utimbuf ut; + ut.actime = st.st_atime; + ut.modtime = st.st_mtime; + if (utime(newPath.toString().c_str(), &ut) == -1) + { + Application::instance().logger().error(Util::logPrefix() + + "utime(\"" + newPath.toString() + "\", &ut) failed: " + + strerror(errno)); + return 1; + } + } + break; + case FTW_DNR: + Application::instance().logger().error(Util::logPrefix() + + "Cannot read directory '" + fpath + "'"); + return 1; + case FTW_NS: + Application::instance().logger().error(Util::logPrefix() + + "nftw: stat failed for '" + fpath + "'"); + return 1; + case FTW_SLN: + Application::instance().logger().information(Util::logPrefix() + + "nftw: symlink to nonexistent file: '" + fpath + "', ignored"); + break; + default: + assert(false); + } + return 0; + } + + void linkOrCopy(const std::string& source, const Path& destination) + { + *sourceForLinkOrCopy = source; + if (sourceForLinkOrCopy->back() == '/') + sourceForLinkOrCopy->pop_back(); + *destinationForLinkOrCopy = destination; + if (nftw(source.c_str(), linkOrCopyFunction, 10, FTW_DEPTH) == -1) + Application::instance().logger().error(Util::logPrefix() + + "linkOrCopy: nftw() failed for '" + source + "'"); + } + void dropCapability( #ifdef __linux cap_value_t capability @@ -677,55 +804,14 @@ namespace } } -int LOOLWSD::childMain() +// Writer, Impress or Calc +void LOOLWSD::componentMain() { - std::cout << Util::logPrefix() << "Child here! id=" << _childId << std::endl; - -#ifdef __linux - dropCapability(CAP_FOWNER); - dropCapability(CAP_MKNOD); -#endif - // We use the same option set for both parent and child loolwsd, - // so must check options required in the child (but not in the - // parent) separately now. And also for options that are - // meaningless to the child. - if (jail == "") - throw MissingOptionException("systemplate"); - - if (sysTemplate != "") - throw IncompatibleOptionsException("systemplate"); - if (loTemplate != "") - throw IncompatibleOptionsException("lotemplate"); - if (childRoot != "") - throw IncompatibleOptionsException("childroot"); - - if (chroot(jail.c_str()) == -1) - { - logger().error("chroot(\"" + jail + "\") failed: " + strerror(errno)); - exit(1); - } - -#ifdef __linux - dropCapability(CAP_SYS_CHROOT); -#else - dropCapability(); -#endif - - if (chdir("/") == -1) - { - logger().error(std::string("chdir(\"/\") in jail failed: ") + strerror(errno)); - exit(1); - } - - if (std::getenv("SLEEPFORDEBUGGER")) - { - std::cout << "Sleeping " << std::getenv("SLEEPFORDEBUGGER") << " seconds, " << - "attach process " << Process::id() << " in debugger now." << std::endl; - Thread::sleep(std::stoul(std::getenv("SLEEPFORDEBUGGER")) * 1000); - } - try { + // initialisation + //_childId = Process::id(); + #ifdef __APPLE__ LibreOfficeKit *loKit(lok_init_2(("/" + loSubPath + "/Frameworks").c_str(), "file:///user")); #else @@ -735,9 +821,13 @@ int LOOLWSD::childMain() if (!loKit) { logger().fatal(Util::logPrefix() + "LibreOfficeKit initialisation failed"); - return Application::EXIT_UNAVAILABLE; + exit(Application::EXIT_UNAVAILABLE); } + // wait until desktop sockets setup + namedMutexLOOL.lock(); + namedMutexLOOL.unlock(); + // Open websocket connection between the child process and the // parent. The parent forwards us requests that it can't handle. @@ -803,14 +893,92 @@ int LOOLWSD::childMain() logger().error(Util::logPrefix() + "Exception: " + exc.what()); } - // Safest to just bluntly exit - _Exit(Application::EXIT_OK); + exit(Application::EXIT_OK); } -int LOOLWSD::main(const std::vector<std::string>& args) +int LOOLWSD::createComponent() { - if (childMode()) - return childMain(); + int pid; + + if ((pid = fork()) == -1) + { + std::cout << "Fork failed." << std::endl; + return Application::EXIT_UNAVAILABLE; + } + + if (!pid) + { + componentMain(); + } + + MasterProcessSession::addPendingChildrem(pid); + MasterProcessSession::_childProcesses[pid] = pid; + + return Application::EXIT_OK; +} + +void LOOLWSD::startupComponent(int nComponents) +{ + for (int nCntr = nComponents; nCntr; nCntr--) + { + if (createComponent() < 0) + break; + } +} + +void LOOLWSD::desktopMain() +{ + // Initialization + std::unique_lock<std::mutex> rngLock(_rngMutex); + _childId = (((Poco::UInt64)_rng.next()) << 32) | _rng.next() | 1; + rngLock.unlock(); + + Path jail = Path::forDirectory(LOOLWSD::childRoot + Path::separator() + std::to_string(_childId)); + File(jail).createDirectory(); + + Path jailLOInstallation(jail, LOOLWSD::loSubPath); + jailLOInstallation.makeDirectory(); + File(jailLOInstallation).createDirectory(); + + // Copy (link) LO installation and other necessary files into it from the template + + linkOrCopy(LOOLWSD::sysTemplate, jail); + linkOrCopy(LOOLWSD::loTemplate, jailLOInstallation); + +#ifdef __linux + // Create the urandom and random devices + File(Path(jail, "/dev")).createDirectory(); + if (mknod((jail.toString() + "/dev/random").c_str(), + S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, + makedev(1, 8)) != 0) + { + Application::instance().logger().error(Util::logPrefix() + + "mknod(" + jail.toString() + "/dev/random) failed: " + + strerror(errno)); + + } + if (mknod((jail.toString() + "/dev/urandom").c_str(), + S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, + makedev(1, 9)) != 0) + { + Application::instance().logger().error(Util::logPrefix() + + "mknod(" + jail.toString() + "/dev/urandom) failed: " + + strerror(errno)); + } +#endif + + Application::instance().logger().information("desktopMain -> chroot(\"" + jail.toString() + "\")"); + if (chroot(jail.toString().c_str()) == -1) + { + logger().error("chroot(\"" + jail.toString() + "\") failed: " + strerror(errno)); + exit(Application::EXIT_UNAVAILABLE); + } + + if (chdir("/") == -1) + { + logger().error(std::string("chdir(\"/\") in jail failed: ") + strerror(errno)); + exit(Application::EXIT_UNAVAILABLE); + } #ifdef __linux dropCapability(CAP_SYS_CHROOT); @@ -818,36 +986,30 @@ int LOOLWSD::main(const std::vector<std::string>& args) dropCapability(); #endif - if (access(cache.c_str(), R_OK | W_OK | X_OK) != 0) + if (std::getenv("SLEEPFORDEBUGGER")) { - std::cout << "Unable to access " << cache << - ", please make sure it exists, and has write permission for this user." << std::endl; - return Application::EXIT_UNAVAILABLE; + std::cout << "Sleeping " << std::getenv("SLEEPFORDEBUGGER") << " seconds, " << + "attach process " << Process::id() << " in debugger now." << std::endl; + Thread::sleep(std::stoul(std::getenv("SLEEPFORDEBUGGER")) * 1000); } - // We use the same option set for both parent and child loolwsd, - // so must check options required in the parent (but not in the - // child) separately now. Also check for options that are - // meaningless for the parent. - if (sysTemplate == "") - throw MissingOptionException("systemplate"); - if (loTemplate == "") - throw MissingOptionException("lotemplate"); - if (childRoot == "") - throw MissingOptionException("childroot"); + // TODO loKit SolarMutex is locked, it ends deadlock +/* +#ifdef __APPLE__ + LibreOfficeKit *loKit(lok_init_2(("/" + loSubPath + "/Frameworks").c_str(), "file:///user")); +#else + LibreOfficeKit *loKit(lok_init_2(("/" + loSubPath + "/program").c_str(), "file:///user")); +#endif - if (_childId != 0) - throw IncompatibleOptionsException("child"); - if (jail != "") - throw IncompatibleOptionsException("jail"); - if (portNumber == MASTER_PORT_NUMBER) - throw IncompatibleOptionsException("port"); + if (!loKit) + { + logger().fatal(Util::logPrefix() + "LibreOfficeKit initialisation failed"); + exit(Application::EXIT_UNAVAILABLE); + }*/ + namedMutexLOOL.lock(); - // Set up a thread to reap child processes and clean up after them - Undertaker undertaker; - Thread undertakerThread; - undertakerThread.start(undertaker); + startupComponent(_numPreSpawnedChildren); // Start a server listening on the port for clients ServerSocket svs(portNumber, _numPreSpawnedChildren*10); @@ -864,26 +1026,97 @@ int LOOLWSD::main(const std::vector<std::string>& args) srv2.start(); - if (_doTest) - _numPreSpawnedChildren = 1; + namedMutexLOOL.unlock(); - for (int i = 0; i < _numPreSpawnedChildren; i++) - MasterProcessSession::preSpawn(); + while (MasterProcessSession::_childProcesses.size() > 0) + { + int status; + pid_t pid = waitpid(-1, &status, WUNTRACED | WNOHANG); + if (pid < 0) + { + if (errno == ECHILD) + { + // We have spawned children, and we think that we still have them running, + // but we don't, huh? Something badly messed up, or just a timing glitch, + // like we are at the moment in the process of spawning new children? + // Sleep or return from the function (i.e. finish the Undertaker thread)? + std::cout << Util::logPrefix() << "No child processes even if we think there should be some!?" << std::endl; + } + } - TestInput input(*this, svs, srv); - Thread inputThread; - if (_doTest) + if ((WIFEXITED(status) || WIFSIGNALED(status) || WTERMSIG(status) ) && + MasterProcessSession::_childProcesses.find(pid) != MasterProcessSession::_childProcesses.end()) + std::cout << Util::logPrefix() << "One of our known child processes died" << std::endl; + + if (WSTOPSIG(status) && MasterProcessSession::getAvailableChildSessions() == 0 && MasterProcessSession::getPendingPreSpawnedChildren() == 0 ) + { + std::cout << Util::logPrefix() << "No availabe child session, fork new one" << std::endl; + if (createComponent() < 0 ) + break; + } + } + + // Terminate child processes + for (auto i : MasterProcessSession::_childProcesses) { - inputThread.start(input); + logger().information(Util::logPrefix() + "Requesting child process " + std::to_string(i.first) + " to terminate"); + Process::requestTermination(i.first); } - waitForTerminationRequest(); + exit(Application::EXIT_OK); +} - // Doing this causes a crash. So just let the process proceed and exit. - // srv.stop(); - if (_doTest) - inputThread.join(); +int LOOLWSD::createDesktop() +{ + int pid; + + if ((pid = fork()) == -1) + { + std::cout << "createDesktop fork failed." << std::endl; + return Application::EXIT_UNAVAILABLE; + } + + if (!pid) + { + desktopMain(); + } + + MasterProcessSession::_childProcesses[pid] = pid; + + return Application::EXIT_OK; +} + +void LOOLWSD::startupDesktop(int nDesktops) +{ + for (int nCntr = nDesktops; nCntr; nCntr--) + { + if (createDesktop() < 0) + break; + } +} + + +void LOOLWSD::loolMain() +{ + std::unique_lock<std::mutex> rngLock(_rngMutex); + _childId = (((Poco::UInt64)_rng.next()) << 32) | _rng.next() | 1; + rngLock.unlock(); + + startupDesktop(1); + +#ifdef __linux + dropCapability(CAP_SYS_CHROOT); +#else + dropCapability(); +#endif + + Thread threadFile; + FileTransferHandler svrFile; + threadFile.start(svrFile); + + int status; + waitpid(-1, &status, 0); // Terminate child processes for (auto i : MasterProcessSession::_childProcesses) @@ -891,8 +1124,39 @@ int LOOLWSD::main(const std::vector<std::string>& args) logger().information(Util::logPrefix() + "Requesting child process " + std::to_string(i.first) + " to terminate"); Process::requestTermination(i.first); } +} + +int LOOLWSD::main(const std::vector<std::string>& args) +{ + if (access(cache.c_str(), R_OK | W_OK | X_OK) != 0) + { + std::cout << "Unable to access " << cache << + ", please make sure it exists, and has write permission for this user." << std::endl; + return Application::EXIT_UNAVAILABLE; + } + + // We use the same option set for both parent and child loolwsd, + // so must check options required in the parent (but not in the + // child) separately now. Also check for options that are + // meaningless for the parent. + if (sysTemplate == "") + throw MissingOptionException("systemplate"); + if (loTemplate == "") + throw MissingOptionException("lotemplate"); + if (childRoot == "") + throw MissingOptionException("childroot"); + + if (_childId != 0) + throw IncompatibleOptionsException("child"); + if (jail != "") + throw IncompatibleOptionsException("jail"); + if (portNumber == MASTER_PORT_NUMBER) + throw IncompatibleOptionsException("port"); + + if (_doTest) + _numPreSpawnedChildren = 1; - undertakerThread.join(); + loolMain(); return Application::EXIT_OK; } diff --git a/loolwsd/LOOLWSD.hpp b/loolwsd/LOOLWSD.hpp index 56a8939..bccb98f 100644 --- a/loolwsd/LOOLWSD.hpp +++ b/loolwsd/LOOLWSD.hpp @@ -13,8 +13,11 @@ #include "config.h" #include <string> +#include <mutex> #include <Poco/Util/OptionSet.h> +#include <Poco/Random.h> +#include <Poco/Path.h> #include <Poco/Util/ServerApplication.h> class LOOLWSD: public Poco::Util::ServerApplication @@ -35,6 +38,7 @@ public: static const int DEFAULT_CLIENT_PORT_NUMBER = 9980; static const int MASTER_PORT_NUMBER = 9981; + static const int FILE_PORT_NUMBER = 9979; static const std::string CHILD_URI; protected: @@ -46,12 +50,20 @@ protected: private: void displayHelp(); - int childMain(); bool childMode() const; + void componentMain(); + void desktopMain(); + void loolMain(); + void startupComponent(int nComponents); + void startupDesktop(int nDesktop); + int createComponent(); + int createDesktop(); bool _doTest; Poco::UInt64 _childId; static int _numPreSpawnedChildren; + static std::mutex _rngMutex; + static Poco::Random _rng; #if ENABLE_DEBUG public: _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/libreoffice-commits