2015-04-13 04:09:02 -05:00
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2015-03-04 17:14:04 -06:00
/*
* 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/.
*
* Parts of this file is covered by :
Boost Software License - Version 1.0 - August 17 th , 2003
Permission is hereby granted , free of charge , to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license ( the " Software " ) to use , reproduce , display , distribute ,
execute , and transmit the Software , and to prepare derivative works of the
Software , and to permit third - parties to whom the Software is furnished to
do so , all subject to the following :
The copyright notices in the Software and this entire statement , including
the above license grant , this restriction and the following disclaimer ,
must be included in all copies of the Software , in whole or in part , and
all derivative works of the Software , unless such copies or derivative
works are solely in the form of machine - executable object code generated by
a source language processor .
THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
FITNESS FOR A PARTICULAR PURPOSE , TITLE AND NON - INFRINGEMENT . IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY , WHETHER IN CONTRACT , TORT OR OTHERWISE ,
ARISING FROM , OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE .
*/
2015-04-21 08:51:28 -05:00
# include "config.h"
2015-04-10 07:20:04 -05:00
// This is the main source for the loolwsd program. LOOL uses several loolwsd processes: one main
// parent process that listens on the TCP port and accepts connections from LOOL clients, and a
// number of child processes, each which handles a viewing (editing) session for one document.
2015-04-16 11:15:40 -05:00
# include <errno.h>
2015-10-13 12:05:42 -05:00
# include <locale.h>
2015-04-08 09:22:42 -05:00
# include <unistd.h>
2015-04-16 11:15:40 -05:00
# ifdef __linux
# include <sys/types.h>
# include <sys/wait.h>
2015-08-08 06:55:54 -05:00
# include <sys/prctl.h>
2015-04-16 11:15:40 -05:00
# endif
2015-07-13 09:13:06 -05:00
# include <ftw.h>
# include <utime.h>
2015-04-16 11:15:40 -05:00
# include <cassert>
2015-03-28 06:55:35 -05:00
# include <cstdlib>
2015-04-14 09:50:38 -05:00
# include <cstring>
2015-03-04 17:14:04 -06:00
# include <iostream>
2015-07-13 09:13:06 -05:00
# include <sstream>
# include <mutex>
2015-03-04 17:14:04 -06:00
# define LOK_USE_UNSTABLE_API
# include <LibreOfficeKit/LibreOfficeKitInit.h>
2015-05-08 07:46:10 -05:00
# include <Poco/Exception.h>
2015-04-16 11:15:40 -05:00
# include <Poco/File.h>
2015-10-16 10:38:24 -05:00
# include <Poco/Net/HTMLForm.h>
2015-03-04 17:14:04 -06:00
# include <Poco/Net/HTTPClientSession.h>
# include <Poco/Net/HTTPRequest.h>
# include <Poco/Net/HTTPRequestHandler.h>
# include <Poco/Net/HTTPRequestHandlerFactory.h>
# include <Poco/Net/HTTPServer.h>
# include <Poco/Net/HTTPServerParams.h>
# include <Poco/Net/HTTPServerParams.h>
# include <Poco/Net/HTTPServerRequest.h>
# include <Poco/Net/HTTPServerResponse.h>
2015-10-16 10:38:24 -05:00
# include <Poco/Net/MessageHeader.h>
2015-03-04 17:14:04 -06:00
# include <Poco/Net/NetException.h>
2015-10-16 10:38:24 -05:00
# include <Poco/Net/PartHandler.h>
2015-03-04 17:14:04 -06:00
# include <Poco/Net/ServerSocket.h>
2015-05-08 13:24:46 -05:00
# include <Poco/Net/SocketAddress.h>
2015-03-04 17:14:04 -06:00
# include <Poco/Net/WebSocket.h>
2015-04-16 11:15:40 -05:00
# include <Poco/Path.h>
2015-03-27 11:23:27 -05:00
# include <Poco/Process.h>
2015-05-18 03:21:30 -05:00
# include <Poco/StringTokenizer.h>
2015-05-29 00:49:49 -05:00
# include <Poco/ThreadPool.h>
2015-03-04 17:14:04 -06:00
# include <Poco/Util/HelpFormatter.h>
# include <Poco/Util/Option.h>
2015-04-08 09:22:42 -05:00
# include <Poco/Util/OptionException.h>
2015-03-04 17:14:04 -06:00
# include <Poco/Util/OptionSet.h>
# include <Poco/Util/ServerApplication.h>
2015-07-13 09:13:06 -05:00
# include <Poco/Mutex.h>
# include <Poco/Net/DialogSocket.h>
# include <Poco/Net/Net.h>
# include <Poco/ThreadLocal.h>
# include <Poco/NamedMutex.h>
2015-07-19 15:49:11 -05:00
# include <Poco/FileStream.h>
2015-10-20 08:00:05 -05:00
# include <Poco/TemporaryFile.h>
# include <Poco/StreamCopier.h>
2015-11-17 12:06:59 -06:00
# include <Poco/URI.h>
2015-12-13 12:28:01 -06:00
# include <Poco/Environment.h>
2015-03-04 17:14:04 -06:00
2015-12-27 16:23:43 -06:00
# include "Common.hpp"
2015-12-29 19:34:53 -06:00
# include "Capabilities.hpp"
2015-04-14 09:50:38 -05:00
# include "LOOLProtocol.hpp"
2015-03-09 03:01:30 -05:00
# include "LOOLSession.hpp"
2015-12-12 12:50:12 -06:00
# include "MasterProcessSession.hpp"
2015-12-12 13:23:44 -06:00
# include "ChildProcessSession.hpp"
2015-03-17 18:56:15 -05:00
# include "LOOLWSD.hpp"
2016-01-06 07:38:21 -06:00
# include "QueueHandler.hpp"
2015-03-17 18:56:15 -05:00
# include "Util.hpp"
2015-03-04 17:14:04 -06:00
2015-04-14 09:50:38 -05:00
using namespace LOOLProtocol ;
2015-05-29 00:49:49 -05:00
using Poco : : Exception ;
2015-04-16 11:15:40 -05:00
using Poco : : File ;
2015-05-29 00:49:49 -05:00
using Poco : : IOException ;
2015-03-04 17:14:04 -06:00
using Poco : : Net : : HTTPClientSession ;
using Poco : : Net : : HTTPRequest ;
using Poco : : Net : : HTTPRequestHandler ;
using Poco : : Net : : HTTPRequestHandlerFactory ;
using Poco : : Net : : HTTPResponse ;
using Poco : : Net : : HTTPServer ;
using Poco : : Net : : HTTPServerParams ;
using Poco : : Net : : HTTPServerRequest ;
using Poco : : Net : : HTTPServerResponse ;
using Poco : : Net : : ServerSocket ;
2015-05-08 13:24:46 -05:00
using Poco : : Net : : SocketAddress ;
2015-03-04 17:14:04 -06:00
using Poco : : Net : : WebSocket ;
using Poco : : Net : : WebSocketException ;
2015-04-16 11:15:40 -05:00
using Poco : : Path ;
using Poco : : Process ;
2015-03-16 06:59:40 -05:00
using Poco : : Runnable ;
2015-05-18 03:21:30 -05:00
using Poco : : StringTokenizer ;
2015-03-16 06:59:40 -05:00
using Poco : : Thread ;
2015-05-29 00:49:49 -05:00
using Poco : : ThreadPool ;
2015-03-04 17:14:04 -06:00
using Poco : : Util : : Application ;
using Poco : : Util : : HelpFormatter ;
2015-04-08 09:22:42 -05:00
using Poco : : Util : : IncompatibleOptionsException ;
using Poco : : Util : : MissingOptionException ;
2015-03-04 17:14:04 -06:00
using Poco : : Util : : Option ;
using Poco : : Util : : OptionSet ;
using Poco : : Util : : ServerApplication ;
2015-07-13 09:13:06 -05:00
using Poco : : Net : : DialogSocket ;
using Poco : : FastMutex ;
using Poco : : Net : : Socket ;
using Poco : : ThreadLocal ;
using Poco : : Random ;
using Poco : : NamedMutex ;
2015-12-19 19:09:48 -06:00
using Poco : : ProcessHandle ;
2015-11-17 12:06:59 -06:00
using Poco : : URI ;
2015-03-04 17:14:04 -06:00
2016-01-06 23:16:47 -06:00
// Document management mutex.
2016-01-12 17:11:04 -06:00
std : : mutex DocumentURI : : DocumentURIMutex ;
std : : map < std : : string , std : : shared_ptr < DocumentURI > > DocumentURI : : UriToDocumentURIMap ;
2016-01-06 23:16:47 -06:00
2015-10-16 10:38:24 -05:00
/// Handles the filename part of the convert-to POST request payload.
class ConvertToPartHandler : public Poco : : Net : : PartHandler
{
2015-10-19 09:03:16 -05:00
std : : string & _filename ;
2015-10-16 10:38:24 -05:00
public :
2015-10-20 08:00:05 -05:00
ConvertToPartHandler ( std : : string & filename )
: _filename ( filename )
2015-10-16 10:38:24 -05:00
{
}
2015-10-19 09:03:16 -05:00
virtual void handlePart ( const Poco : : Net : : MessageHeader & header , std : : istream & stream ) override
2015-10-16 10:38:24 -05:00
{
2015-10-20 08:00:05 -05:00
// Extract filename and put it to a temporary directory.
2015-10-19 09:03:16 -05:00
std : : string disp ;
Poco : : Net : : NameValueCollection params ;
if ( header . has ( " Content-Disposition " ) )
{
std : : string cd = header . get ( " Content-Disposition " ) ;
Poco : : Net : : MessageHeader : : splitParameters ( cd , disp , params ) ;
}
2015-10-22 10:27:29 -05:00
2015-10-20 08:00:05 -05:00
if ( ! params . has ( " filename " ) )
return ;
2015-10-19 09:03:16 -05:00
2015-10-20 08:00:05 -05:00
Path tempPath = Path : : forDirectory ( Poco : : TemporaryFile ( ) . tempName ( ) + Path : : separator ( ) ) ;
File ( tempPath ) . createDirectories ( ) ;
tempPath . setFileName ( params . get ( " filename " ) ) ;
_filename = tempPath . toString ( ) ;
// Copy the stream to _filename.
std : : ofstream fileStream ;
fileStream . open ( _filename ) ;
Poco : : StreamCopier : : copyStream ( stream , fileStream ) ;
fileStream . close ( ) ;
2015-10-16 10:38:24 -05:00
}
} ;
2016-01-05 20:29:12 -06:00
// Synchronously process WebSocket requests and dispatch to handler.
// Handler returns false to end.
void SocketProcessor ( std : : shared_ptr < WebSocket > ws ,
HTTPServerResponse & response ,
2016-01-05 21:32:58 -06:00
std : : function < bool ( const char * data , const int size , const bool singleLine ) > handler )
2015-03-04 17:14:04 -06:00
{
2016-01-05 21:32:58 -06:00
Log : : info ( " Starting Socket Processor. " ) ;
2016-01-23 16:35:16 -06:00
const Poco : : Timespan waitTime ( POLL_TIMEOUT_MS * 1000 ) ;
2016-01-05 20:29:12 -06:00
try
{
2016-01-05 21:32:58 -06:00
int flags = 0 ;
int n = 0 ;
2016-01-05 20:29:12 -06:00
ws - > setReceiveTimeout ( 0 ) ;
do
{
char buffer [ 200000 ] ; //FIXME: Dynamic?
2016-01-21 07:54:56 -06:00
if ( ws - > poll ( waitTime , Socket : : SELECT_READ ) )
2016-01-05 20:29:12 -06:00
{
n = ws - > receiveFrame ( buffer , sizeof ( buffer ) , flags ) ;
if ( ( flags & WebSocket : : FRAME_OP_BITMASK ) = = WebSocket : : FRAME_OP_PING )
{
2016-01-21 08:04:16 -06:00
// Echo back the ping payload as pong.
2016-01-23 16:35:16 -06:00
// Technically, we should send back a PONG control frame.
// However Firefox (probably) or Node.js (possibly) doesn't
// like that and closes the socket when we do.
// Echoing the payload as a normal frame works with Firefox.
ws - > sendFrame ( buffer , n /*, WebSocket::FRAME_OP_PONG*/ ) ;
2016-01-05 20:29:12 -06:00
}
else if ( ( flags & WebSocket : : FRAME_OP_BITMASK ) = = WebSocket : : FRAME_OP_PONG )
{
2016-01-21 08:04:16 -06:00
// In case we do send pings in the future.
2016-01-05 20:29:12 -06:00
}
2016-01-21 08:00:58 -06:00
else if ( n < = 0 )
2016-01-05 20:29:12 -06:00
{
2016-01-21 08:00:58 -06:00
// Connection closed.
2016-01-23 16:35:16 -06:00
Log : : warn ( ) < < " Received " < < n
< < " bytes. Connection closed. Flags: "
< < std : : hex < < flags < < Log : : end ;
2016-01-21 08:00:58 -06:00
break ;
}
else
{
assert ( n > 0 ) ;
2016-01-05 20:29:12 -06:00
const std : : string firstLine = getFirstLine ( buffer , n ) ;
if ( firstLine = = " eof " )
2016-01-05 21:32:58 -06:00
{
2016-01-23 16:32:09 -06:00
Log : : info ( " Received EOF. Finishing. " ) ;
2016-01-05 20:29:12 -06:00
break ;
2016-01-05 21:32:58 -06:00
}
2016-01-05 20:29:12 -06:00
2016-01-18 09:54:22 -06:00
if ( ( flags & WebSocket : : FrameFlags : : FRAME_FLAG_FIN ) ! = WebSocket : : FrameFlags : : FRAME_FLAG_FIN )
{
// One WS message split into multiple frames.
std : : vector < char > message ( buffer , buffer + n ) ;
while ( true )
{
n = ws - > receiveFrame ( buffer , sizeof ( buffer ) , flags ) ;
message . insert ( message . end ( ) , buffer , buffer + n ) ;
if ( ( flags & WebSocket : : FrameFlags : : FRAME_FLAG_FIN ) = = WebSocket : : FrameFlags : : FRAME_FLAG_FIN )
{
// No more frames: invoke the handler. Assume
// for now that this is always a multi-line
// message.
handler ( message . data ( ) , message . size ( ) , false ) ;
break ;
}
}
}
else if ( firstLine . size ( ) = = static_cast < std : : string : : size_type > ( n ) )
2016-01-05 20:29:12 -06:00
{
2016-01-05 21:32:58 -06:00
handler ( firstLine . c_str ( ) , firstLine . size ( ) , true ) ;
2016-01-05 20:29:12 -06:00
}
else
{
// Check if it is a "nextmessage:" and in that case read the large
// follow-up message separately, and handle that only.
2016-01-05 21:32:58 -06:00
StringTokenizer tokens ( firstLine , " " , StringTokenizer : : TOK_IGNORE_EMPTY | StringTokenizer : : TOK_TRIM ) ;
2016-01-05 20:29:12 -06:00
int size ;
if ( tokens . count ( ) = = 2 & &
tokens [ 0 ] = = " nextmessage: " & & getTokenInteger ( tokens [ 1 ] , " size " , size ) & & size > 0 )
{
char largeBuffer [ size ] ; //FIXME: Security risk! Flooding may segfault us.
n = ws - > receiveFrame ( largeBuffer , size , flags ) ;
2016-01-21 08:00:58 -06:00
if ( n > 0 & & ! handler ( largeBuffer , n , false ) )
2016-01-05 20:29:12 -06:00
{
2016-01-21 08:00:58 -06:00
Log : : info ( " Socket handler flagged for finishing. " ) ;
break ;
2016-01-05 20:29:12 -06:00
}
}
else
{
2016-01-05 21:32:58 -06:00
if ( ! handler ( buffer , n , false ) )
{
Log : : info ( " Socket handler flagged for finishing. " ) ;
break ;
}
2016-01-05 20:29:12 -06:00
}
}
}
}
}
while ( ! TerminationFlag & &
2016-01-06 07:56:50 -06:00
( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE ) ;
2016-01-05 21:32:58 -06:00
Log : : debug ( ) < < " Finishing SocketProcessor. TerminationFlag: " < < TerminationFlag
2016-01-21 07:54:56 -06:00
< < " , payload size: " < < n
2016-01-12 16:47:50 -06:00
< < " , flags: " < < std : : hex < < flags < < Log : : end ;
2016-01-05 20:29:12 -06:00
}
catch ( const WebSocketException & exc )
2015-03-04 17:14:04 -06:00
{
2016-01-05 20:29:12 -06:00
Log : : error ( " RequestHandler::handleRequest(), WebSocketException: " + exc . message ( ) ) ;
switch ( exc . code ( ) )
{
case WebSocket : : WS_ERR_HANDSHAKE_UNSUPPORTED_VERSION :
response . set ( " Sec-WebSocket-Version " , WebSocket : : WEBSOCKET_VERSION ) ;
// fallthrough
case WebSocket : : WS_ERR_NO_HANDSHAKE :
case WebSocket : : WS_ERR_HANDSHAKE_NO_VERSION :
case WebSocket : : WS_ERR_HANDSHAKE_NO_KEY :
response . setStatusAndReason ( HTTPResponse : : HTTP_BAD_REQUEST ) ;
response . setContentLength ( 0 ) ;
response . send ( ) ;
break ;
}
2015-03-04 17:14:04 -06:00
}
2016-01-05 21:32:58 -06:00
Log : : info ( " Finished Socket Processor. " ) ;
2016-01-05 20:29:12 -06:00
}
/// Handle a public connection from a client.
class ClientRequestHandler : public HTTPRequestHandler
{
2016-01-24 13:48:09 -06:00
private :
2015-03-04 17:14:04 -06:00
2016-01-24 13:48:09 -06:00
void handlePostRequest ( HTTPServerRequest & request , HTTPServerResponse & response , const std : : string & id )
2015-03-04 17:14:04 -06:00
{
2016-01-24 13:48:09 -06:00
Log : : info ( " Post request. " ) ;
StringTokenizer tokens ( request . getURI ( ) , " /? " ) ;
if ( tokens . count ( ) > = 2 & & tokens [ 1 ] = = " convert-to " )
2015-03-04 17:14:04 -06:00
{
2016-01-24 13:48:09 -06:00
Log : : info ( " Conversion request. " ) ;
std : : string fromPath ;
ConvertToPartHandler handler ( fromPath ) ;
Poco : : Net : : HTMLForm form ( request , request . stream ( ) , handler ) ;
std : : string format ;
if ( form . has ( " format " ) )
format = form . get ( " format " ) ;
bool sent = false ;
if ( ! fromPath . empty ( ) )
2015-10-16 10:38:24 -05:00
{
2016-01-24 13:48:09 -06:00
if ( ! format . empty ( ) )
2015-10-16 10:38:24 -05:00
{
2016-01-24 13:48:09 -06:00
// Load the document.
std : : shared_ptr < WebSocket > ws ;
const LOOLSession : : Kind kind = LOOLSession : : Kind : : ToClient ;
auto session = std : : make_shared < MasterProcessSession > ( id , kind , ws ) ;
const std : : string filePrefix ( " file:// " ) ;
std : : string encodedFrom ;
URI : : encode ( filePrefix + fromPath , std : : string ( ) , encodedFrom ) ;
const std : : string load = " load url= " + encodedFrom ;
session - > handleInput ( load . data ( ) , load . size ( ) ) ;
// Convert it to the requested format.
Path toPath ( fromPath ) ;
toPath . setExtension ( format ) ;
std : : string toJailURL = filePrefix + JailedDocumentRoot + toPath . getFileName ( ) ;
std : : string encodedTo ;
URI : : encode ( toJailURL , std : : string ( ) , encodedTo ) ;
2016-01-23 18:41:01 -06:00
std : : string saveas = " saveas url= " + encodedTo + " format= " + format + " options= " ;
2016-01-24 13:48:09 -06:00
session - > handleInput ( saveas . data ( ) , saveas . size ( ) ) ;
2016-01-23 18:41:01 -06:00
2016-01-24 13:48:09 -06:00
std : : string toURL = session - > getSaveAs ( ) ;
std : : string resultingURL ;
URI : : decode ( toURL , resultingURL ) ;
2016-01-23 18:41:01 -06:00
2016-01-24 13:48:09 -06:00
// Send it back to the client.
if ( resultingURL . find ( filePrefix ) = = 0 )
resultingURL = resultingURL . substr ( filePrefix . length ( ) ) ;
if ( ! resultingURL . empty ( ) )
{
const std : : string mimeType = " application/octet-stream " ;
response . sendFile ( resultingURL , mimeType ) ;
sent = true ;
2016-01-23 18:41:01 -06:00
}
2015-10-20 08:35:43 -05:00
}
2016-01-23 18:41:01 -06:00
2016-01-24 13:48:09 -06:00
// Clean up the temporary directory the HTMLForm ctor created.
Path tempDirectory ( fromPath ) ;
tempDirectory . setFileName ( " " ) ;
Util : : removeFile ( tempDirectory , /*recursive=*/ true ) ;
2015-10-16 10:38:24 -05:00
}
2016-01-24 13:48:09 -06:00
if ( ! sent )
2015-10-22 10:27:29 -05:00
{
2016-01-24 13:48:09 -06:00
response . setStatus ( HTTPResponse : : HTTP_BAD_REQUEST ) ;
response . setContentLength ( 0 ) ;
response . send ( ) ;
}
}
else if ( tokens . count ( ) > = 2 & & tokens [ 1 ] = = " insertfile " )
{
Log : : info ( " Insert file request. " ) ;
response . set ( " Access-Control-Allow-Origin " , " * " ) ;
response . set ( " Access-Control-Allow-Methods " , " GET, POST, OPTIONS " ) ;
response . set ( " Access-Control-Allow-Headers " , " Origin, X-Requested-With, Content-Type, Accept " ) ;
2015-10-22 10:27:29 -05:00
2016-01-24 13:48:09 -06:00
std : : string tmpPath ;
ConvertToPartHandler handler ( tmpPath ) ;
Poco : : Net : : HTMLForm form ( request , request . stream ( ) , handler ) ;
2015-10-22 10:27:29 -05:00
2016-01-24 13:48:09 -06:00
bool goodRequest = form . has ( " childid " ) & & form . has ( " name " ) ;
std : : string formChildid ( form . get ( " childid " ) ) ;
std : : string formName ( form . get ( " name " ) ) ;
2016-01-13 09:35:55 -06:00
2016-01-24 13:48:09 -06:00
// protect against attempts to inject something funny here
if ( goodRequest & & formChildid . find ( ' / ' ) ! = std : : string : : npos & & formName . find ( ' / ' ) ! = std : : string : : npos )
goodRequest = false ;
2016-01-13 09:35:55 -06:00
2016-01-24 13:48:09 -06:00
if ( goodRequest )
{
try
2016-01-13 09:35:55 -06:00
{
2016-01-24 13:48:09 -06:00
Log : : info ( ) < < " Perform insertfile: " < < formChildid < < " , " < < formName < < Log : : end ;
const std : : string dirPath = LOOLWSD : : ChildRoot + formChildid
+ JailedDocumentRoot + " insertfile " ;
File ( dirPath ) . createDirectories ( ) ;
std : : string fileName = dirPath + Path : : separator ( ) + form . get ( " name " ) ;
File ( tmpPath ) . moveTo ( fileName ) ;
response . setStatus ( HTTPResponse : : HTTP_OK ) ;
response . send ( ) ;
2015-10-22 10:27:29 -05:00
}
2016-01-24 13:48:09 -06:00
catch ( const IOException & exc )
2015-10-22 10:27:29 -05:00
{
2016-01-24 13:48:09 -06:00
Log : : info ( ) < < " IOException: " < < exc . message ( ) < < Log : : end ;
2015-10-22 10:27:29 -05:00
response . setStatus ( HTTPResponse : : HTTP_BAD_REQUEST ) ;
response . send ( ) ;
}
}
2016-01-24 13:48:09 -06:00
else
2015-10-09 07:55:49 -05:00
{
2016-01-24 13:48:09 -06:00
response . setStatus ( HTTPResponse : : HTTP_BAD_REQUEST ) ;
response . send ( ) ;
}
}
else if ( tokens . count ( ) > = 4 )
{
Log : : info ( " File download request. " ) ;
// The user might request a file to download
const std : : string dirPath = LOOLWSD : : ChildRoot + tokens [ 1 ]
+ JailedDocumentRoot + tokens [ 2 ] ;
std : : string fileName ;
URI : : decode ( tokens [ 3 ] , fileName ) ;
const std : : string filePath = dirPath + Path : : separator ( ) + fileName ;
Log : : info ( " HTTP request for: " + filePath ) ;
File file ( filePath ) ;
if ( file . exists ( ) )
{
response . set ( " Access-Control-Allow-Origin " , " * " ) ;
Poco : : Net : : HTMLForm form ( request ) ;
std : : string mimeType = " application/octet-stream " ;
if ( form . has ( " mime_type " ) )
mimeType = form . get ( " mime_type " ) ;
response . sendFile ( filePath , mimeType ) ;
Util : : removeFile ( dirPath , true ) ;
2015-10-09 07:55:49 -05:00
}
else
{
2016-01-24 13:48:09 -06:00
response . setStatus ( HTTPResponse : : HTTP_NOT_FOUND ) ;
2015-10-09 07:55:49 -05:00
response . setContentLength ( 0 ) ;
response . send ( ) ;
}
2015-03-04 17:14:04 -06:00
}
2016-01-24 13:48:09 -06:00
else
2015-03-04 17:14:04 -06:00
{
2016-01-24 13:48:09 -06:00
Log : : info ( " Bad request. " ) ;
response . setStatus ( HTTPResponse : : HTTP_BAD_REQUEST ) ;
response . setContentLength ( 0 ) ;
response . send ( ) ;
}
}
2015-04-21 07:06:41 -05:00
2016-01-24 13:48:09 -06:00
void handleGetRequest ( HTTPServerRequest & request , HTTPServerResponse & response , const std : : string & id )
{
Log : : info ( " Get request. " ) ;
auto ws = std : : make_shared < WebSocket > ( request , response ) ;
auto session = std : : make_shared < MasterProcessSession > ( id , LOOLSession : : Kind : : ToClient , ws ) ;
2016-01-06 07:38:21 -06:00
2016-01-24 13:48:09 -06:00
// For ToClient sessions, we store incoming messages in a queue and have a separate
// thread that handles them. This is so that we can empty the queue when we get a
// "canceltiles" message.
BasicTileQueue queue ;
QueueHandler handler ( queue , session , " wsd_queue_ " + session - > getId ( ) ) ;
2016-01-05 20:29:12 -06:00
2016-01-24 13:48:09 -06:00
Thread queueHandlerThread ;
queueHandlerThread . start ( handler ) ;
SocketProcessor ( ws , response , [ & session , & queue ] ( const char * data , const int size , const bool singleLine )
{
// FIXME: There is a race here when a request A gets in the queue and
// is processed _after_ a later request B, because B gets processed
// synchronously and A is waiting in the queue thread.
// The fix is to push everything into the queue
// (i.e. change MessageQueue to vector<char>).
const std : : string firstLine = getFirstLine ( data , size ) ;
if ( singleLine | | firstLine . find ( " paste " ) = = 0 )
2015-12-27 21:47:39 -06:00
{
2016-01-24 13:48:09 -06:00
queue . put ( std : : string ( data , size ) ) ;
return true ;
}
else
{
return session - > handleInput ( data , size ) ;
}
} ) ;
2015-06-05 08:12:06 -05:00
2016-01-24 13:48:09 -06:00
queue . clear ( ) ;
queue . put ( " eof " ) ;
queueHandlerThread . join ( ) ;
}
public :
void handleRequest ( HTTPServerRequest & request , HTTPServerResponse & response ) override
{
const auto id = LOOLWSD : : GenSessionId ( ) ;
const std : : string thread_name = " client_ws_ " + id ;
# ifdef __linux
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 + " . " ) ;
# endif
Log : : debug ( " Thread [ " + thread_name + " ] started. " ) ;
try
{
if ( ! ( request . find ( " Upgrade " ) ! = request . end ( ) & & Poco : : icompare ( request [ " Upgrade " ] , " websocket " ) = = 0 ) )
{
handlePostRequest ( request , response , id ) ;
}
else
{
handleGetRequest ( request , response , id ) ;
}
2016-01-05 20:29:12 -06:00
}
2016-01-06 08:13:21 -06:00
catch ( const Exception & exc )
2016-01-05 20:29:12 -06:00
{
2016-01-06 08:13:21 -06:00
Log : : error ( ) < < " Error: " < < exc . displayText ( )
< < ( exc . nested ( ) ? " ( " + exc . nested ( ) - > displayText ( ) + " ) " : " " )
< < Log : : end ;
}
catch ( const std : : exception & exc )
{
Log : : error ( std : : string ( " Exception: " ) + exc . what ( ) ) ;
}
catch ( . . . )
{
Log : : error ( " Unexpected Exception. " ) ;
2016-01-05 20:29:12 -06:00
}
2015-04-21 07:06:41 -05:00
2016-01-05 20:29:12 -06:00
Log : : debug ( " Thread [ " + thread_name + " ] finished. " ) ;
}
} ;
2015-11-25 20:23:08 -06:00
2016-01-05 20:29:12 -06:00
/// Handle requests from prisoners (internal).
class PrisonerRequestHandler : public HTTPRequestHandler
{
public :
2015-04-21 07:06:41 -05:00
2016-01-05 20:29:12 -06:00
void handleRequest ( HTTPServerRequest & request , HTTPServerResponse & response ) override
{
assert ( request . serverAddress ( ) . port ( ) = = MASTER_PORT_NUMBER ) ;
2016-01-10 09:35:28 -06:00
assert ( request . getURI ( ) . find ( LOOLWSD : : CHILD_URI ) = = 0 ) ;
2015-06-09 10:04:46 -05:00
2016-01-10 09:35:28 -06:00
std : : string thread_name = " prison_ws_ " ;
try
{
const auto index = request . getURI ( ) . find_last_of ( ' / ' ) ;
const auto id = request . getURI ( ) . substr ( index + 1 ) ;
thread_name + = id ;
2016-01-05 20:29:12 -06:00
# ifdef __linux
2016-01-10 09:35:28 -06:00
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 + " . " ) ;
2016-01-05 20:29:12 -06:00
# endif
2016-01-10 09:35:28 -06:00
Log : : debug ( " Thread [ " + thread_name + " ] started. " ) ;
2015-12-30 11:10:31 -06:00
2016-01-05 20:29:12 -06:00
auto ws = std : : make_shared < WebSocket > ( request , response ) ;
auto session = std : : make_shared < MasterProcessSession > ( id , LOOLSession : : Kind : : ToPrisoner , ws ) ;
2016-01-05 21:32:58 -06:00
SocketProcessor ( ws , response , [ & session ] ( const char * data , const int size , bool )
2015-04-21 07:06:41 -05:00
{
2016-01-05 20:29:12 -06:00
return session - > handleInput ( data , size ) ;
} ) ;
2015-03-04 17:14:04 -06:00
}
2016-01-06 08:13:21 -06:00
catch ( const Exception & exc )
{
Log : : error ( ) < < " Error: " < < exc . displayText ( )
< < ( exc . nested ( ) ? " ( " + exc . nested ( ) - > displayText ( ) + " ) " : " " )
< < Log : : end ;
}
catch ( const std : : exception & exc )
{
Log : : error ( std : : string ( " Exception: " ) + exc . what ( ) ) ;
}
catch ( . . . )
2015-03-04 17:14:04 -06:00
{
2016-01-06 08:13:21 -06:00
Log : : error ( " Unexpected Exception. " ) ;
2015-03-04 17:14:04 -06:00
}
2015-12-26 10:33:01 -06:00
Log : : debug ( " Thread [ " + thread_name + " ] finished. " ) ;
2015-03-04 17:14:04 -06:00
}
} ;
2016-01-05 20:29:12 -06:00
template < class RequestHandler >
2015-03-04 17:14:04 -06:00
class RequestHandlerFactory : public HTTPRequestHandlerFactory
{
public :
HTTPRequestHandler * createRequestHandler ( const HTTPServerRequest & request ) override
{
2016-01-19 19:38:43 -06:00
# ifdef __linux
2016-01-23 18:41:01 -06:00
if ( prctl ( PR_SET_NAME , reinterpret_cast < unsigned long > ( " request_handler " ) , 0 , 0 , 0 ) ! = 0 )
Log : : error ( " Cannot set thread name to request_handler. " ) ;
2016-01-19 19:38:43 -06:00
# endif
2015-12-25 11:35:23 -06:00
auto logger = Log : : info ( ) ;
logger < < " Request from " < < request . clientAddress ( ) . toString ( ) < < " : "
< < request . getMethod ( ) < < " " < < request . getURI ( ) < < " "
< < request . getVersion ( ) ;
2015-03-07 05:23:46 -06:00
2015-03-04 17:14:04 -06:00
for ( HTTPServerRequest : : ConstIterator it = request . begin ( ) ; it ! = request . end ( ) ; + + it )
{
2015-12-25 11:35:23 -06:00
logger < < " / " < < it - > first < < " : " < < it - > second ;
2015-03-04 17:14:04 -06:00
}
2015-03-07 05:23:46 -06:00
2015-12-25 11:35:23 -06:00
logger < < Log : : end ;
2015-10-09 07:55:49 -05:00
return new RequestHandler ( ) ;
2015-03-04 17:14:04 -06:00
}
} ;
2015-05-29 00:49:49 -05:00
class TestOutput : public Runnable
2015-03-04 17:14:04 -06:00
{
public :
TestOutput ( WebSocket & ws ) :
_ws ( ws )
{
}
void run ( ) override
{
int flags ;
int n ;
2015-03-27 09:53:33 -05:00
_ws . setReceiveTimeout ( 0 ) ;
2015-03-12 11:58:51 -05:00
try
2015-03-04 17:14:04 -06:00
{
2015-03-12 11:58:51 -05:00
do
2015-03-04 17:14:04 -06:00
{
2015-10-16 08:23:49 -05:00
char buffer [ 200000 ] ;
2015-03-12 11:58:51 -05:00
n = _ws . receiveFrame ( buffer , sizeof ( buffer ) , flags ) ;
2016-01-21 08:00:58 -06:00
if ( n > 0 )
2015-03-12 11:58:51 -05:00
{
2015-12-25 12:50:53 -06:00
Log : : trace ( ) < < " Client got " < < n < < " bytes: "
< < getAbbreviatedMessage ( buffer , n ) < < Log : : end ;
2015-03-12 11:58:51 -05:00
}
2015-03-04 17:14:04 -06:00
}
2015-03-12 11:58:51 -05:00
while ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE ) ;
}
2015-12-25 12:50:53 -06:00
catch ( const WebSocketException & exc )
2015-03-12 11:58:51 -05:00
{
2015-12-20 11:59:26 -06:00
Log : : error ( " TestOutput::run(), WebSocketException: " + exc . message ( ) ) ;
2015-03-12 11:58:51 -05:00
_ws . close ( ) ;
2015-03-04 17:14:04 -06:00
}
}
private :
WebSocket & _ws ;
} ;
2015-05-29 00:49:49 -05:00
class TestInput : public Runnable
2015-03-04 17:14:04 -06:00
{
public :
TestInput ( ServerApplication & main , ServerSocket & svs , HTTPServer & srv ) :
_main ( main ) ,
_svs ( svs ) ,
_srv ( srv )
{
}
void run ( ) override
{
2015-04-23 10:52:16 -05:00
HTTPClientSession cs ( " 127.0.0.1 " , _svs . address ( ) . port ( ) ) ;
2015-03-04 17:14:04 -06:00
HTTPRequest request ( HTTPRequest : : HTTP_GET , " /ws " ) ;
HTTPResponse response ;
WebSocket ws ( cs , request , response ) ;
Thread thread ;
TestOutput output ( ws ) ;
thread . start ( output ) ;
2015-03-17 18:56:15 -05:00
if ( isatty ( 0 ) )
{
std : : cout < < std : : endl ;
std : : cout < < " Enter LOOL WS requests, one per line. Enter EOF to finish. " < < std : : endl ;
}
2015-03-04 17:14:04 -06:00
while ( ! std : : cin . eof ( ) )
{
std : : string line ;
std : : getline ( std : : cin , line ) ;
ws . sendFrame ( line . c_str ( ) , line . size ( ) ) ;
}
thread . join ( ) ;
_srv . stopAll ( ) ;
_main . terminate ( ) ;
}
private :
ServerApplication & _main ;
ServerSocket & _svs ;
HTTPServer & _srv ;
} ;
2015-12-27 21:47:39 -06:00
std : : atomic < unsigned > LOOLWSD : : NextSessionId ;
2015-12-28 15:34:21 -06:00
int LOOLWSD : : BrokerWritePipe = - 1 ;
2016-01-15 02:40:54 -06:00
std : : string LOOLWSD : : Cache = LOOLWSD_CACHEDIR ;
std : : string LOOLWSD : : SysTemplate ;
std : : string LOOLWSD : : LoTemplate ;
std : : string LOOLWSD : : ChildRoot ;
std : : string LOOLWSD : : JailId ;
std : : string LOOLWSD : : LoSubPath = " lo " ;
2015-12-28 15:34:21 -06:00
Poco : : NamedMutex LOOLWSD : : NamedMutexLOOL ( " loolwsd " ) ;
2015-07-13 09:13:06 -05:00
2015-12-28 15:34:21 -06:00
int LOOLWSD : : NumPreSpawnedChildren = 10 ;
2016-01-15 02:40:54 -06:00
bool LOOLWSD : : DoTest = false ;
2015-04-20 09:43:31 -05:00
const std : : string LOOLWSD : : CHILD_URI = " /loolws/child/ " ;
2015-07-19 15:49:11 -05:00
const std : : string LOOLWSD : : PIDLOG = " /tmp/loolwsd.pid " ;
2015-08-05 19:20:05 -05:00
const std : : string LOOLWSD : : LOKIT_PIDLOG = " /tmp/lokit.pid " ;
2015-12-13 12:28:01 -06:00
const std : : string LOOLWSD : : FIFO_FILE = " /tmp/loolwsdfifo " ;
2015-03-17 18:56:15 -05:00
2015-11-25 21:17:08 -06:00
LOOLWSD : : LOOLWSD ( )
2015-03-04 17:14:04 -06:00
{
2015-03-17 18:56:15 -05:00
}
2015-03-07 05:23:46 -06:00
2015-03-17 18:56:15 -05:00
LOOLWSD : : ~ LOOLWSD ( )
{
}
2015-03-04 17:14:04 -06:00
2015-03-17 18:56:15 -05:00
void LOOLWSD : : initialize ( Application & self )
{
ServerApplication : : initialize ( self ) ;
}
2015-03-07 05:23:46 -06:00
2015-03-17 18:56:15 -05:00
void LOOLWSD : : uninitialize ( )
{
ServerApplication : : uninitialize ( ) ;
}
2015-10-28 04:55:03 -05:00
void LOOLWSD : : defineOptions ( OptionSet & optionSet )
2015-03-17 18:56:15 -05:00
{
2015-10-28 04:55:03 -05:00
ServerApplication : : defineOptions ( optionSet ) ;
2015-03-17 18:56:15 -05:00
2015-10-28 04:55:03 -05:00
optionSet . addOption ( Option ( " help " , " " , " Display help information on command line arguments. " )
. required ( false )
. repeatable ( false ) ) ;
2015-03-17 18:56:15 -05:00
2015-12-19 12:38:44 -06:00
optionSet . addOption ( Option ( " version " , " " , " Display version information. " )
. required ( false )
. repeatable ( false ) ) ;
2015-10-28 04:55:03 -05:00
optionSet . addOption ( Option ( " port " , " " , " Port number to listen to (default: " + std : : to_string ( DEFAULT_CLIENT_PORT_NUMBER ) + " ), "
2015-05-08 13:29:13 -05:00
" must not be " + std : : to_string ( MASTER_PORT_NUMBER ) + " . " )
2015-10-28 04:55:03 -05:00
. required ( false )
. repeatable ( false )
. argument ( " port number " ) ) ;
optionSet . addOption ( Option ( " cache " , " " , " Path to a directory where to keep the persistent tile cache (default: " + std : : string ( LOOLWSD_CACHEDIR ) + " ). " )
. required ( false )
. repeatable ( false )
. argument ( " directory " ) ) ;
optionSet . addOption ( Option ( " systemplate " , " " , " Path to a template tree with shared libraries etc to be used as source for chroot jails for child processes. " )
. required ( false )
. repeatable ( false )
. argument ( " directory " ) ) ;
optionSet . addOption ( Option ( " lotemplate " , " " , " Path to a LibreOffice installation tree to be copied (linked) into the jails for child processes. Should be on the same file system as systemplate. " )
. required ( false )
. repeatable ( false )
. argument ( " directory " ) ) ;
optionSet . addOption ( Option ( " childroot " , " " , " Path to the directory under which the chroot jails for the child processes will be created. Should be on the same file system as systemplate and lotemplate. " )
. required ( false )
. repeatable ( false )
. argument ( " directory " ) ) ;
2016-01-15 02:40:54 -06:00
optionSet . addOption ( Option ( " losubpath " , " " , " Relative path where the LibreOffice installation will be copied inside a jail (default: ' " + LoSubPath + " '). " )
2015-10-28 04:55:03 -05:00
. required ( false )
. repeatable ( false )
. argument ( " relative path " ) ) ;
optionSet . addOption ( Option ( " numprespawns " , " " , " Number of child processes to keep started in advance and waiting for new clients. " )
. required ( false )
. repeatable ( false )
. argument ( " number " ) ) ;
optionSet . addOption ( Option ( " test " , " " , " Interactive testing. " )
. required ( false )
. repeatable ( false ) ) ;
2015-05-13 06:22:19 -05:00
# if ENABLE_DEBUG
2015-10-28 04:55:03 -05:00
optionSet . addOption ( Option ( " uid " , " " , " Uid to assume if running under sudo for debugging purposes. " )
. required ( false )
. repeatable ( false )
. argument ( " uid " ) ) ;
2015-05-13 06:22:19 -05:00
# endif
2015-03-17 18:56:15 -05:00
}
2015-10-28 04:55:03 -05:00
void LOOLWSD : : handleOption ( const std : : string & optionName , const std : : string & value )
2015-03-25 07:39:58 -05:00
{
2015-10-28 04:55:03 -05:00
ServerApplication : : handleOption ( optionName , value ) ;
2015-03-25 07:39:58 -05:00
2015-10-28 04:55:03 -05:00
if ( optionName = = " help " )
2015-03-25 07:39:58 -05:00
{
displayHelp ( ) ;
exit ( Application : : EXIT_OK ) ;
}
2015-12-19 12:38:44 -06:00
else if ( optionName = = " version " )
{
displayVersion ( ) ;
exit ( Application : : EXIT_OK ) ;
}
2015-10-28 04:55:03 -05:00
else if ( optionName = = " port " )
2015-12-27 16:46:42 -06:00
ClientPortNumber = std : : stoi ( value ) ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " cache " )
2016-01-15 02:40:54 -06:00
Cache = value ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " systemplate " )
2016-01-15 02:40:54 -06:00
SysTemplate = value ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " lotemplate " )
2016-01-15 02:40:54 -06:00
LoTemplate = value ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " childroot " )
2016-01-15 02:40:54 -06:00
ChildRoot = value ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " losubpath " )
2016-01-15 02:40:54 -06:00
LoSubPath = value ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " numprespawns " )
2015-12-28 15:34:21 -06:00
NumPreSpawnedChildren = std : : stoi ( value ) ;
2015-10-28 04:55:03 -05:00
else if ( optionName = = " test " )
2016-01-15 02:40:54 -06:00
LOOLWSD : : DoTest = true ;
2015-05-13 06:22:19 -05:00
# if ENABLE_DEBUG
2015-10-28 04:55:03 -05:00
else if ( optionName = = " uid " )
2015-05-13 06:22:19 -05:00
uid = std : : stoull ( value ) ;
# endif
2015-03-25 07:39:58 -05:00
}
void LOOLWSD : : displayHelp ( )
{
HelpFormatter helpFormatter ( options ( ) ) ;
helpFormatter . setCommand ( commandName ( ) ) ;
helpFormatter . setUsage ( " OPTIONS " ) ;
helpFormatter . setHeader ( " LibreOffice On-Line WebSocket server. " ) ;
helpFormatter . format ( std : : cout ) ;
}
2015-03-17 18:56:15 -05:00
2015-12-19 12:38:44 -06:00
void LOOLWSD : : displayVersion ( )
{
std : : cout < < LOOLWSD_VERSION < < std : : endl ;
}
2016-01-07 08:09:35 -06:00
bool LOOLWSD : : createBroker ( const std : : string & rJailId )
2015-07-13 09:13:06 -05:00
{
2015-12-19 19:09:48 -06:00
Process : : Args args ;
2016-01-15 02:40:54 -06:00
args . push_back ( " --losubpath= " + LOOLWSD : : LoSubPath ) ;
args . push_back ( " --systemplate= " + SysTemplate ) ;
args . push_back ( " --lotemplate= " + LoTemplate ) ;
args . push_back ( " --childroot= " + ChildRoot ) ;
2016-01-07 08:09:35 -06:00
args . push_back ( " --jailid= " + rJailId ) ;
2015-12-28 15:34:21 -06:00
args . push_back ( " --numprespawns= " + std : : to_string ( NumPreSpawnedChildren ) ) ;
2015-12-27 16:46:42 -06:00
args . push_back ( " --clientport= " + std : : to_string ( ClientPortNumber ) ) ;
2015-12-19 19:09:48 -06:00
2015-12-28 16:23:05 -06:00
const std : : string brokerPath = Path ( Application : : instance ( ) . commandPath ( ) ) . parent ( ) . toString ( ) + " loolbroker " ;
2015-12-19 19:09:48 -06:00
2016-01-18 02:42:07 -06:00
const auto childIndex = MasterProcessSession : : ChildProcesses . size ( ) + 1 ;
2015-12-28 16:23:05 -06:00
Log : : info ( " Launching Broker # " + std : : to_string ( childIndex ) +
" : " + brokerPath + " " +
2015-12-24 21:19:50 -06:00
Poco : : cat ( std : : string ( " " ) , args . begin ( ) , args . end ( ) ) ) ;
2015-12-19 19:09:48 -06:00
2015-12-28 16:23:05 -06:00
ProcessHandle child = Process : : launch ( brokerPath , args ) ;
2015-12-19 19:09:48 -06:00
2015-12-28 16:23:05 -06:00
Log : : info ( ) < < " Adding Broker # " < < childIndex < < " PID " < < child . id ( ) < < Log : : end ;
2016-01-18 02:42:07 -06:00
MasterProcessSession : : ChildProcesses [ child . id ( ) ] = child . id ( ) ;
2015-12-19 19:09:48 -06:00
2016-01-06 21:17:30 -06:00
return true ;
2015-07-13 09:13:06 -05:00
}
2015-10-16 10:45:03 -05:00
int LOOLWSD : : main ( const std : : vector < std : : string > & /*args*/ )
2015-07-13 09:13:06 -05:00
{
2015-12-28 17:24:29 -06:00
Log : : initialize ( " wsd " ) ;
2015-12-13 12:28:01 -06:00
Poco : : Environment : : set ( " LD_BIND_NOW " , " 1 " ) ;
2016-01-08 20:53:57 -06:00
//Poco::Environment::set("LOK_VIEW_CALLBACK", "1");
2015-12-13 12:28:01 -06:00
2015-10-13 12:05:42 -05:00
# ifdef __linux
2015-12-29 19:34:53 -06:00
char * locale = setlocale ( LC_ALL , nullptr ) ;
if ( locale = = nullptr | | std : : strcmp ( locale , " C " ) = = 0 )
2015-10-13 12:05:42 -05:00
setlocale ( LC_ALL , " en_US.utf8 " ) ;
# endif
2016-01-09 12:51:55 -06:00
Util : : setSignals ( false ) ;
2016-01-15 02:40:54 -06:00
if ( access ( Cache . c_str ( ) , R_OK | W_OK | X_OK ) ! = 0 )
2015-07-17 16:55:27 -05:00
{
2016-01-15 02:40:54 -06:00
Log : : error ( " Unable to access cache [ " + Cache +
2015-12-25 12:50:53 -06:00
" ] please make sure it exists, and has write permission for this user. " ) ;
2015-07-17 16:55:27 -05:00
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.
2016-01-15 02:40:54 -06:00
if ( SysTemplate . empty ( ) )
2015-07-17 16:55:27 -05:00
throw MissingOptionException ( " systemplate " ) ;
2016-01-15 02:40:54 -06:00
if ( LoTemplate . empty ( ) )
2015-07-17 16:55:27 -05:00
throw MissingOptionException ( " lotemplate " ) ;
2016-01-04 15:15:02 -06:00
2016-01-15 02:40:54 -06:00
if ( ChildRoot . empty ( ) )
2015-07-17 16:55:27 -05:00
throw MissingOptionException ( " childroot " ) ;
2016-01-15 02:40:54 -06:00
else if ( ChildRoot [ ChildRoot . size ( ) - 1 ] ! = Path : : separator ( ) )
ChildRoot + = Path : : separator ( ) ;
2015-07-17 16:55:27 -05:00
2015-12-27 16:46:42 -06:00
if ( ClientPortNumber = = MASTER_PORT_NUMBER )
2015-07-17 16:55:27 -05:00
throw IncompatibleOptionsException ( " port " ) ;
2016-01-15 02:40:54 -06:00
if ( LOOLWSD : : DoTest )
2015-12-28 15:34:21 -06:00
NumPreSpawnedChildren = 1 ;
2015-07-17 16:55:27 -05:00
2015-07-19 15:49:11 -05:00
// log pid information
{
Poco : : FileOutputStream filePID ( LOOLWSD : : PIDLOG ) ;
if ( filePID . good ( ) )
filePID < < Process : : id ( ) ;
}
2015-12-13 12:28:01 -06:00
if ( ! File ( FIFO_FILE ) . exists ( ) & & mkfifo ( FIFO_FILE . c_str ( ) , 0666 ) = = - 1 )
{
2015-12-25 12:50:53 -06:00
Log : : error ( " Error: Failed to create pipe FIFO [ " + FIFO_FILE + " ]. " ) ;
2015-12-13 12:28:01 -06:00
return Application : : EXIT_UNAVAILABLE ;
}
2015-12-28 15:34:21 -06:00
NamedMutexLOOL . lock ( ) ;
2015-07-17 13:02:25 -05:00
2016-01-15 02:40:54 -06:00
JailId = Util : : createRandomDir ( ChildRoot ) ;
if ( ! createBroker ( JailId ) )
2016-01-06 21:17:30 -06:00
{
Log : : error ( " Failed to spawn loolBroker. " ) ;
return Application : : EXIT_UNAVAILABLE ;
}
2015-07-13 09:13:06 -05:00
# ifdef __linux
dropCapability ( CAP_SYS_CHROOT ) ;
2015-12-04 16:06:52 -06:00
dropCapability ( CAP_MKNOD ) ;
dropCapability ( CAP_FOWNER ) ;
2015-07-13 09:13:06 -05:00
# else
dropCapability ( ) ;
# endif
2015-07-17 13:02:25 -05:00
// Start a server listening on the port for clients
2015-12-28 15:34:21 -06:00
ServerSocket svs ( ClientPortNumber , NumPreSpawnedChildren * 10 ) ;
ThreadPool threadPool ( NumPreSpawnedChildren * 2 , NumPreSpawnedChildren * 5 ) ;
2016-01-05 20:29:12 -06:00
HTTPServer srv ( new RequestHandlerFactory < ClientRequestHandler > ( ) , threadPool , svs , new HTTPServerParams ) ;
2015-07-17 13:02:25 -05:00
srv . start ( ) ;
// And one on the port for child processes
SocketAddress addr2 ( " 127.0.0.1 " , MASTER_PORT_NUMBER ) ;
2015-12-28 15:34:21 -06:00
ServerSocket svs2 ( addr2 , NumPreSpawnedChildren ) ;
ThreadPool threadPool2 ( NumPreSpawnedChildren * 2 , NumPreSpawnedChildren * 5 ) ;
2016-01-05 20:29:12 -06:00
HTTPServer srv2 ( new RequestHandlerFactory < PrisonerRequestHandler > ( ) , threadPool2 , svs2 , new HTTPServerParams ) ;
2015-07-17 13:02:25 -05:00
srv2 . start ( ) ;
2015-12-28 15:34:21 -06:00
if ( ( BrokerWritePipe = open ( FIFO_FILE . c_str ( ) , O_WRONLY ) ) < 0 )
2015-12-19 19:09:48 -06:00
{
2015-12-24 21:19:50 -06:00
Log : : error ( " Error: failed to open pipe [ " + FIFO_FILE + " ] write only. " ) ;
2015-12-19 19:09:48 -06:00
return Application : : EXIT_UNAVAILABLE ;
}
2015-12-28 15:34:21 -06:00
NamedMutexLOOL . unlock ( ) ;
2015-07-17 13:02:25 -05:00
2015-09-11 15:31:43 -05:00
TestInput input ( * this , svs , srv ) ;
Thread inputThread ;
2016-01-15 02:40:54 -06:00
if ( LOOLWSD : : DoTest )
2015-09-11 15:31:43 -05:00
{
inputThread . start ( input ) ;
waitForTerminationRequest ( ) ;
}
2015-12-28 17:24:29 -06:00
int status = 0 ;
2015-12-28 15:34:21 -06:00
unsigned timeoutCounter = 0 ;
2016-01-18 02:42:07 -06:00
while ( ! TerminationFlag & & ! LOOLWSD : : DoTest & & MasterProcessSession : : ChildProcesses . size ( ) > 0 )
2015-07-17 13:02:25 -05:00
{
2016-01-14 21:31:02 -06:00
const pid_t pid = waitpid ( - 1 , & status , WUNTRACED | WNOHANG ) ;
2015-07-17 13:02:25 -05:00
if ( pid > 0 )
{
2016-01-18 02:42:07 -06:00
if ( MasterProcessSession : : ChildProcesses . find ( pid ) ! = MasterProcessSession : : ChildProcesses . end ( ) )
2015-07-17 13:02:25 -05:00
{
2016-01-14 21:31:02 -06:00
if ( WIFEXITED ( status ) )
2015-07-17 13:02:25 -05:00
{
2016-01-14 21:31:02 -06:00
Log : : info ( ) < < " Child process [ " < < pid < < " ] exited with code: "
< < WEXITSTATUS ( status ) < < " . " < < Log : : end ;
2016-01-18 02:42:07 -06:00
MasterProcessSession : : ChildProcesses . erase ( pid ) ;
2015-07-17 13:02:25 -05:00
}
2016-01-14 21:31:02 -06:00
else
if ( WIFSIGNALED ( status ) )
{
std : : string fate = " died " ;
# ifdef WCOREDUMP
if ( WCOREDUMP ( status ) )
fate = " core-dumped " ;
# endif
Log : : error ( ) < < " Child process [ " < < pid < < " ] " < < fate
< < " with " < < Util : : signalName ( WTERMSIG ( status ) )
< < " signal. " < < Log : : end ;
2015-07-17 13:02:25 -05:00
2016-01-14 21:31:02 -06:00
MasterProcessSession : : ChildProcesses . erase ( pid ) ;
}
else if ( WIFSTOPPED ( status ) )
{
Log : : info ( ) < < " Child process [ " < < pid < < " ] stopped with "
< < Util : : signalName ( WSTOPSIG ( status ) )
< < " signal. " < < Log : : end ;
}
else if ( WIFCONTINUED ( status ) )
{
Log : : info ( ) < < " Child process [ " < < pid < < " ] resumed with SIGCONT. "
< < Log : : end ;
}
else
{
Log : : warn ( ) < < " Unknown status returned by waitpid: "
< < std : : hex < < status < < " . " < < Log : : end ;
}
2015-07-17 13:02:25 -05:00
}
else
{
2015-12-24 21:19:50 -06:00
Log : : error ( " None of our known child processes died. PID: " + std : : to_string ( pid ) ) ;
2015-07-17 13:02:25 -05:00
}
}
else if ( pid < 0 )
2016-01-14 21:31:02 -06:00
Log : : error ( " Error: waitpid failed. " ) ;
2015-07-17 13:02:25 -05:00
2015-12-28 15:34:21 -06:00
if ( timeoutCounter + + = = INTERVAL_PROBES )
2015-07-24 13:10:24 -05:00
{
timeoutCounter = 0 ;
2015-07-24 14:17:46 -05:00
sleep ( MAINTENANCE_INTERVAL * 2 ) ;
2015-07-24 13:10:24 -05:00
}
2015-07-17 13:02:25 -05:00
}
2015-04-16 11:15:40 -05:00
2016-01-15 02:40:54 -06:00
if ( LOOLWSD : : DoTest )
2015-09-11 15:31:43 -05:00
inputThread . join ( ) ;
2015-11-25 20:59:24 -06:00
// stop the service, no more request
srv . stop ( ) ;
srv2 . stop ( ) ;
2015-12-02 18:15:39 -06:00
2015-11-25 20:59:24 -06:00
// close all websockets
threadPool . joinAll ( ) ;
threadPool2 . joinAll ( ) ;
2015-12-19 19:09:48 -06:00
// Terminate child processes
2016-01-21 08:26:34 -06:00
Util : : writeFIFO ( LOOLWSD : : BrokerWritePipe , " eof " ) ;
2016-01-18 02:42:07 -06:00
for ( auto i : MasterProcessSession : : ChildProcesses )
2015-12-19 19:09:48 -06:00
{
2015-12-20 11:59:26 -06:00
Log : : info ( " Requesting child process " + std : : to_string ( i . first ) + " to terminate " ) ;
2015-12-19 19:09:48 -06:00
Process : : requestTermination ( i . first ) ;
}
2015-12-02 18:15:39 -06:00
2015-11-25 20:59:24 -06:00
// wait broker process finish
waitpid ( - 1 , & status , WUNTRACED ) ;
2015-03-17 18:56:15 -05:00
2016-01-21 08:26:34 -06:00
close ( BrokerWritePipe ) ;
2016-01-15 02:40:54 -06:00
Log : : info ( " Cleaning up childroot directory [ " + ChildRoot + " ]. " ) ;
2016-01-04 15:15:02 -06:00
std : : vector < std : : string > jails ;
2016-01-15 02:40:54 -06:00
File ( ChildRoot ) . list ( jails ) ;
2016-01-04 15:15:02 -06:00
for ( auto & jail : jails )
{
2016-01-15 02:40:54 -06:00
const auto path = ChildRoot + jail ;
2016-01-04 15:15:02 -06:00
Log : : info ( " Removing jail [ " + path + " ]. " ) ;
Util : : removeFile ( path , true ) ;
}
2015-12-28 17:24:29 -06:00
2015-12-28 22:39:25 -06:00
Log : : info ( " Process [loolwsd] finished. " ) ;
2015-11-25 21:17:08 -06:00
return Application : : EXIT_OK ;
2015-03-17 18:56:15 -05:00
}
2015-03-04 17:14:04 -06:00
2015-03-09 10:34:11 -05:00
POCO_SERVER_MAIN ( LOOLWSD )
2015-03-04 17:14:04 -06:00
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */