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-08 09:22:42 -05:00
# include <unistd.h>
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>
# define LOK_USE_UNSTABLE_API
# include <LibreOfficeKit/LibreOfficeKitInit.h>
# include <Poco/Format.h>
# 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>
# include <Poco/Net/NetException.h>
# include <Poco/Net/ServerSocket.h>
# include <Poco/Net/WebSocket.h>
2015-03-27 11:23:27 -05:00
# include <Poco/Process.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-04-14 09:50:38 -05:00
# include "LOOLProtocol.hpp"
2015-03-09 03:01:30 -05:00
# include "LOOLSession.hpp"
2015-03-17 18:56:15 -05:00
# include "LOOLWSD.hpp"
# include "Util.hpp"
2015-03-04 17:14:04 -06:00
2015-04-14 09:50:38 -05:00
using namespace LOOLProtocol ;
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 ;
using Poco : : Net : : WebSocket ;
using Poco : : Net : : WebSocketException ;
2015-03-16 06:59:40 -05:00
using Poco : : Runnable ;
using Poco : : Thread ;
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 ;
class WebSocketRequestHandler : public HTTPRequestHandler
/// Handle a WebSocket connection.
{
public :
2015-03-17 18:56:15 -05:00
WebSocketRequestHandler ( )
2015-03-04 17:14:04 -06:00
{
}
void handleRequest ( HTTPServerRequest & request , HTTPServerResponse & response ) override
{
if ( ! ( request . find ( " Upgrade " ) ! = request . end ( ) & & Poco : : icompare ( request [ " Upgrade " ] , " websocket " ) = = 0 ) )
{
response . setStatusAndReason ( HTTPResponse : : HTTP_BAD_REQUEST ) ;
response . setContentLength ( 0 ) ;
response . send ( ) ;
return ;
}
Application & app = Application : : instance ( ) ;
try
{
WebSocket ws ( request , response ) ;
2015-03-17 18:56:15 -05:00
LOOLSession session ( ws ) ;
2015-03-09 10:39:37 -05:00
2015-03-17 18:56:15 -05:00
// Loop, receiving WebSocket messages either from the
// client, or from the child process (to be forwarded to
// the client).
2015-03-09 10:39:37 -05:00
int flags ;
int n ;
2015-03-27 09:53:33 -05:00
ws . setReceiveTimeout ( 0 ) ;
2015-03-09 10:39:37 -05:00
do
2015-03-04 17:14:04 -06:00
{
2015-03-17 18:56:15 -05:00
char buffer [ 100000 ] ;
2015-03-09 10:39:37 -05:00
n = ws . receiveFrame ( buffer , sizeof ( buffer ) , flags ) ;
if ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE )
2015-03-12 09:18:35 -05:00
if ( ! session . handleInput ( buffer , n ) )
2015-03-09 10:39:37 -05:00
n = 0 ;
2015-03-04 17:14:04 -06:00
}
2015-03-17 18:56:15 -05:00
while ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE ) ;
2015-03-04 17:14:04 -06:00
}
catch ( WebSocketException & exc )
{
2015-03-17 18:56:15 -05:00
app . logger ( ) . error ( Util : : logPrefix ( ) + " WebSocketException: " + exc . message ( ) ) ;
2015-03-04 17:14:04 -06:00
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 ;
}
}
}
} ;
class RequestHandlerFactory : public HTTPRequestHandlerFactory
{
public :
2015-03-17 18:56:15 -05:00
RequestHandlerFactory ( )
2015-03-04 17:14:04 -06:00
{
}
HTTPRequestHandler * createRequestHandler ( const HTTPServerRequest & request ) override
{
Application & app = Application : : instance ( ) ;
2015-03-17 18:56:15 -05:00
std : : string line = ( Util : : logPrefix ( ) + " 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-03-17 18:56:15 -05:00
line + = " / " + it - > first + " : " + it - > second ;
2015-03-04 17:14:04 -06:00
}
2015-03-07 05:23:46 -06:00
2015-03-17 18:56:15 -05:00
app . logger ( ) . information ( line ) ;
return new WebSocketRequestHandler ( ) ;
2015-03-04 17:14:04 -06:00
}
} ;
class TestOutput : public Runnable
{
public :
TestOutput ( WebSocket & ws ) :
_ws ( ws )
{
}
void run ( ) override
{
int flags ;
int n ;
2015-03-12 11:58:51 -05:00
Application & app = Application : : instance ( ) ;
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-03-17 18:56:15 -05:00
char buffer [ 100000 ] ;
2015-03-12 11:58:51 -05:00
n = _ws . receiveFrame ( buffer , sizeof ( buffer ) , flags ) ;
if ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE )
{
2015-03-17 18:56:15 -05:00
std : : cout < <
2015-04-14 09:50:38 -05:00
Util : : logPrefix ( ) < <
" Client got " < < n < < " bytes: " < < getAbbreviatedMessage ( buffer , n ) < <
2015-03-17 18:56:15 -05:00
std : : endl ;
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 ) ;
}
catch ( WebSocketException & exc )
{
2015-03-17 18:56:15 -05:00
app . logger ( ) . error ( Util : : logPrefix ( ) + " WebSocketException: " + exc . message ( ) ) ;
2015-03-12 11:58:51 -05:00
_ws . close ( ) ;
2015-03-04 17:14:04 -06:00
}
}
private :
WebSocket & _ws ;
} ;
class TestInput : public Runnable
{
public :
TestInput ( ServerApplication & main , ServerSocket & svs , HTTPServer & srv ) :
_main ( main ) ,
_svs ( svs ) ,
_srv ( srv )
{
}
void run ( ) override
{
HTTPClientSession cs ( " localhost " , _svs . address ( ) . port ( ) ) ;
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-03-17 18:56:15 -05:00
int LOOLWSD : : portNumber = DEFAULT_PORT_NUMBER ;
2015-04-08 09:22:42 -05:00
std : : string LOOLWSD : : sysTemplate ;
std : : string LOOLWSD : : loTemplate ;
std : : string LOOLWSD : : childRoot ;
std : : string LOOLWSD : : loSubPath = " lo " ;
std : : string LOOLWSD : : jail ;
2015-04-15 07:17:15 -05:00
int LOOLWSD : : numPreSpawnedChildren = 10 ;
2015-03-17 18:56:15 -05:00
LOOLWSD : : LOOLWSD ( ) :
_doTest ( false ) ,
_childId ( 0 )
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 ( ) ;
}
void LOOLWSD : : defineOptions ( OptionSet & options )
{
ServerApplication : : defineOptions ( options ) ;
2015-04-15 07:21:17 -05:00
options . addOption ( Option ( " help " , " " , " Display help information on command line arguments. " )
2015-03-17 18:56:15 -05:00
. required ( false )
. repeatable ( false ) ) ;
2015-04-15 07:21:17 -05:00
options . addOption ( Option ( " port " , " " , " Port number to listen to (default: " + std : : to_string ( LOOLWSD : : DEFAULT_PORT_NUMBER ) + " ). " )
2015-03-17 18:56:15 -05:00
. required ( false )
. repeatable ( false )
2015-03-27 11:23:27 -05:00
. argument ( " port number " ) ) ;
2015-03-17 18:56:15 -05:00
2015-04-15 07:21:17 -05:00
options . addOption ( Option ( " systemplate " , " " , " Path to a template tree with shared libraries etc to be used as source for chroot jails for child processes. " )
2015-04-08 09:22:42 -05:00
. required ( false )
. repeatable ( false )
. argument ( " directory " ) ) ;
2015-04-15 07:21:17 -05:00
options . 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. " )
2015-04-08 09:22:42 -05:00
. required ( false )
. repeatable ( false )
. argument ( " directory " ) ) ;
2015-04-15 07:21:17 -05:00
options . 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. " )
2015-04-08 09:22:42 -05:00
. required ( false )
2015-03-17 18:56:15 -05:00
. repeatable ( false )
. argument ( " directory " ) ) ;
2015-04-15 07:21:17 -05:00
options . addOption ( Option ( " losubpath " , " " , " Relative path where the LibreOffice installation will be copied inside a jail (default: ' " + loSubPath + " '). " )
2015-04-08 09:22:42 -05:00
. required ( false )
. repeatable ( false )
. argument ( " relative path " ) ) ;
2015-04-15 07:21:17 -05:00
options . addOption ( Option ( " numprespawns " , " " , " Number of child processes to keep started in advance and waiting for new clients. " )
2015-04-08 09:22:42 -05:00
. required ( false )
. repeatable ( false )
. argument ( " port number " ) ) ;
2015-04-15 07:21:17 -05:00
options . addOption ( Option ( " test " , " " , " Interactive testing. " )
2015-03-17 18:56:15 -05:00
. required ( false )
. repeatable ( false ) ) ;
2015-04-15 07:21:17 -05:00
options . addOption ( Option ( " child " , " " , " For internal use only. " )
2015-03-17 18:56:15 -05:00
. required ( false )
. repeatable ( false )
2015-03-27 11:23:27 -05:00
. argument ( " child id " ) ) ;
2015-04-08 09:22:42 -05:00
2015-04-15 07:21:17 -05:00
options . addOption ( Option ( " jail " , " " , " For internal use only. " )
2015-04-08 09:22:42 -05:00
. required ( false )
. repeatable ( false )
. argument ( " directory " ) ) ;
2015-03-17 18:56:15 -05:00
}
2015-03-25 07:39:58 -05:00
void LOOLWSD : : handleOption ( const std : : string & name , const std : : string & value )
{
ServerApplication : : handleOption ( name , value ) ;
if ( name = = " help " )
{
displayHelp ( ) ;
exit ( Application : : EXIT_OK ) ;
}
else if ( name = = " port " )
portNumber = std : : stoi ( value ) ;
2015-04-08 09:22:42 -05:00
else if ( name = = " systemplate " )
sysTemplate = value ;
else if ( name = = " lotemplate " )
loTemplate = value ;
else if ( name = = " childroot " )
childRoot = value ;
else if ( name = = " losubpath " )
loSubPath = value ;
2015-04-15 07:17:15 -05:00
else if ( name = = " numprespawns " )
numPreSpawnedChildren = std : : stoi ( value ) ;
2015-03-25 07:39:58 -05:00
else if ( name = = " test " )
_doTest = true ;
else if ( name = = " child " )
_childId = std : : stoull ( value ) ;
2015-04-08 09:22:42 -05:00
else if ( name = = " jail " )
jail = value ;
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
int LOOLWSD : : childMain ( )
{
std : : cout < < Util : : logPrefix ( ) < < " Child here! " < < std : : endl ;
2015-03-04 17:14:04 -06:00
2015-04-08 09:22:42 -05:00
// 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 ) ;
}
if ( chdir ( " / " ) = = - 1 )
{
logger ( ) . error ( std : : string ( " chdir( \" / \" ) in jail failed: " ) + strerror ( errno ) ) ;
exit ( 1 ) ;
}
LibreOfficeKit * loKit ( lok_init_2 ( ( " / " + loSubPath + " /program " ) . c_str ( ) , " file:///user " ) ) ;
2015-03-17 18:56:15 -05:00
if ( ! loKit )
2015-03-04 17:14:04 -06:00
{
2015-03-17 18:56:15 -05:00
logger ( ) . fatal ( Util : : logPrefix ( ) + " LibreOfficeKit initialisation failed " ) ;
return Application : : EXIT_UNAVAILABLE ;
2015-03-04 17:14:04 -06:00
}
2015-03-17 18:56:15 -05:00
// Open websocket connection between the child process and the
// parent. The parent forwars us requests that it can't handle.
2015-03-04 17:14:04 -06:00
2015-03-17 18:56:15 -05:00
HTTPClientSession cs ( " localhost " , portNumber ) ;
HTTPRequest request ( HTTPRequest : : HTTP_GET , " /ws " ) ;
HTTPResponse response ;
WebSocket ws ( cs , request , response ) ;
2015-03-04 17:14:04 -06:00
2015-04-13 07:13:38 -05:00
LOOLSession session ( ws , loKit , _childId ) ;
2015-03-17 18:56:15 -05:00
2015-04-08 09:22:42 -05:00
ws . setReceiveTimeout ( 0 ) ;
2015-03-17 18:56:15 -05:00
std : : string hello ( " child " + std : : to_string ( _childId ) ) ;
2015-04-08 09:22:42 -05:00
session . sendTextFrame ( hello ) ;
2015-03-04 17:14:04 -06:00
2015-03-17 18:56:15 -05:00
int flags ;
int n ;
do
2015-03-04 17:14:04 -06:00
{
2015-03-17 18:56:15 -05:00
char buffer [ 1024 ] ;
n = ws . receiveFrame ( buffer , sizeof ( buffer ) , flags ) ;
2015-03-04 17:14:04 -06:00
2015-03-17 18:56:15 -05:00
if ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE )
if ( ! session . handleInput ( buffer , n ) )
n = 0 ;
}
while ( n > 0 & & ( flags & WebSocket : : FRAME_OP_BITMASK ) ! = WebSocket : : FRAME_OP_CLOSE ) ;
2015-03-04 17:14:04 -06:00
2015-03-17 18:56:15 -05:00
return Application : : EXIT_OK ;
}
2015-03-04 17:14:04 -06:00
2015-03-17 18:56:15 -05:00
int LOOLWSD : : main ( const std : : vector < std : : string > & args )
{
2015-04-21 08:51:28 -05:00
if ( access ( LOOLWSD_CACHEDIR , R_OK | W_OK | X_OK ) ! = 0 )
{
std : : cout < < " Unable to access " < < LOOLWSD_CACHEDIR < <
" , please make sure it exists, and has write permission for this user. " < < std : : endl ;
return Application : : EXIT_UNAVAILABLE ;
}
2015-03-17 18:56:15 -05:00
if ( childMode ( ) )
return childMain ( ) ;
2015-03-04 17:14:04 -06:00
2015-04-08 09:22:42 -05:00
// 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 " ) ;
2015-04-15 07:17:15 -05:00
for ( int i = 0 ; i < numPreSpawnedChildren ; i + + )
LOOLSession : : preSpawn ( ) ;
2015-04-08 09:22:42 -05:00
2015-03-17 18:56:15 -05:00
ServerSocket svs ( portNumber ) ;
2015-03-04 17:14:04 -06:00
2015-03-17 18:56:15 -05:00
HTTPServer srv ( new RequestHandlerFactory ( ) , svs , new HTTPServerParams ) ;
2015-03-04 17:14:04 -06:00
2015-03-17 18:56:15 -05:00
srv . start ( ) ;
2015-03-04 17:14:04 -06:00
2015-03-17 18:56:15 -05:00
Thread thread ;
TestInput input ( * this , svs , srv ) ;
if ( _doTest )
{
thread . start ( input ) ;
2015-03-04 17:14:04 -06:00
}
2015-03-17 18:56:15 -05:00
waitForTerminationRequest ( ) ;
srv . stop ( ) ;
if ( _doTest )
thread . join ( ) ;
return Application : : EXIT_OK ;
}
bool LOOLWSD : : childMode ( ) const
{
return _childId ! = 0 ;
}
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: */