/*******************************************************************************
* Copyright (c) 2007, Perforce Software, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL PERFORCE
* SOFTWARE, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*******************************************************************************/
#define NEED_SIGNAL
#define NEED_GETUID
#define NEED_GETPWUID
#define NEED_FORK
#define NEED_TYPES
#define NEED_STAT
#include <errno.h>
#include <grp.h>
#include <clientapi.h>
#include <vararray.h>
#include <strtable.h>
#include <error.h>
#include <errorlog.h>
#include <debug.h>
#include "p4dctlerr.h"
#include "parsesupp.h"
#include "cmdline.h"
#include "config.h"
#include "varlist.h"
#include "server.h"
#include "p4ui.h"
#include "p4dctldebug.h"
//
// Declare environ. It may already be declared, but it does no harm to
// make sure it's there as not all platforms do so (Solaris, FreeBSD
// at least).
//
extern char **environ;
//
// Special exit statuses from our children.
//
enum
{
CHILD_SUCCESS,
CHILD_FAIL,
CHILD_EXEC_FAIL,
};
//------------------------------------------------------------------------------
// Server class implementation
//------------------------------------------------------------------------------
//
Server::Server( const char *name, int type, VarList *settings, VarList *env, const Config *config )
{
this->name = name;
this->enabled = 1;
this->env = env;
this->settings = settings;
this->owner_uid = 0;
this->owner_gid = 0;
this->global_config = config;
this->type = type;
this->label = "(unknown)";
this->umask = 022;
this->childEnv = 0;
UpdateFromSettings();
}
void
Server::UpdateFromSettings()
{
//
// Extract the owner, binary and umask from the configuration and store
// them separately
//
StrPtr *s;
if( s = settings->GetVar( "Execute" ) )
this->binary = *s;
if( s = settings->GetVar( "Owner" ) )
this->owner = *s;
if( s = settings->GetVar( "Umask" ) )
this->umask = strtol( s->Text(), 0, 8 );
if( s = settings->GetVar( "Args" ) )
this->args = *s;
if( s = settings->GetVar( "Enabled" ) )
if( *s == "false" )
this->enabled = 0;
}
Server::~Server()
{
delete childEnv;
delete env;
delete settings;
}
//
// Merge a list of settings into this server's
// definitions making sure to preserve any
// existing settings. This should only add new
// settings.
//
void
Server::MergeSettings( VarList *s )
{
// Construct a new VarList and import
VarList *t = new VarList;
VarList *t2 = settings;
t->Import( s );
t->Import( settings );
settings = t;
delete t2;
// Refresh the internally cached settings
UpdateFromSettings();
}
//
// Check that the current user has the right to work with
// this server
//
int
Server::CheckAccess( Error *e )
{
if( ! owner_uid ) LoadUserData( e );
if( e->Test() ) return CHECK_FAILED;
// Get our own UID
uid_t uid = getuid();
//
// If we're root, we can run as anyone. If we're not root, we
// can only work with our own servers. No-one can work with a server that
// runs as root.
//
if( uid != 0 && uid != owner_uid )
{
e->Set( CtlErr::ServerAccessDenied ) << name << label << owner;
return CHECK_DENIED;
}
else if( owner_uid == 0 )
{
e->Set( CtlErr::NoRootServers ) << name << owner;
return CHECK_DENIED_ROOT;
}
// Otherwise, access is granted
return CHECK_GRANTED;
}
//
// Exec the binary to start the server. Returns non-zero if the server is
// started.
//
int
Server::Start( StrPtr extraArgs, Error *e )
{
pid_t pid;
StrBuf procName;
if( Running( e ) )
{
e->Set( CtlErr::AlreadyRunning ) << name << label;
return 0;
}
if( ! enabled )
{
e->Set( CtlErr::IgnoreDisabled ) << name << label;
return 0;
}
if( extraArgs.Length() > 0 )
{
args.Append( extraArgs.Text() );
args.Append(" ");
}
SetProcessName( procName );
CmdLine cmdline( procName );
ExtractArgs( cmdline );
if( !RunCommand( binary.Text(), &cmdline, pid, 1, 1, e ) )
return 0;
// Save pid.
SavePid( pid, e );
return 1;
}
//
// Shut down the running server. Returns non-zero on error
//
int
Server::Stop( Error *e )
{
pid_t pid;
// Attempt to stop it using the pidfile
if( pid = LoadPid( e ) )
{
// Whether this works or not, we'll remove the pid file.
CleanPid();
if( kill( pid, SIGTERM ) < 0 )
return 0;
return 1;
}
e->Clear();
return 0;
}
//
// Check the status of the server. Returns non-zero if the server is alive
//
int
Server::Running( Error *e )
{
pid_t pid;
pid = LoadPid( e );
e->Clear();
if( !pid ) return 0;
if( kill( pid, 0 ) < 0 )
return 0;
return 1;
}
// Debugging
void
Server::Dump()
{
Error e;
LoadUserData( &e );
printf( "Name: %s\n", name.Text() );
printf( " Owner: %s\n", owner.Text() );
printf( " Owner uid: %d\n", owner_uid );
printf( " Owner gid: %d\n", owner_gid );
printf( " Owner home: %s\n", owner_home.Text() );
printf( " Umask: %#o\n", this->umask );
printf( " Binary: %s\n", binary.Text() );
printf( " Settings:\n" );
settings->Dump();
printf( " Environment\n" );
env->Dump();
}
StrPtr *
Server::GetVar( StrPtr &var )
{
static StrRef True = StrRef( "true" );
static StrRef False = StrRef( "false" );
if( var == "Name" )
return &this->name;
if( var == "Execute" )
return &this->binary;
if( var == "Owner" )
return &this->owner;
if( var == "Args" )
return &this->args;
if( var == "Enabled" )
if( this->enabled )
return &True;
else
return &False;
return env->GetVar( var );
}
int
Server::Debug()
{
return global_config->Debug();
}
//
// Compute pid file path for this server
//
void
Server::PidFile( StrBuf &buf )
{
buf = global_config->PidDir();
buf << "/" << label << "." << name << ".pid";
}
//
// Write pid file
//
void
Server::SavePid( pid_t pid, Error *e )
{
StrBuf pidfile;
StrBuf pidbuf;
PidFile( pidfile );
pidbuf << pid << "\n";
FileSys *f = FileSys::Create( FST_TEXT );
f->Set( pidfile );
//
// We need to be root to write pid files.
//
Config::RestorePrivs( e );
if( e->Test() ) return;
// Open the file
f->Open( FOM_WRITE, e );
if( !e->Test() )
{
f->Write( pidbuf, e );
f->Close( e );
}
// Lose the privs, we no longer need them.
Config::DropPrivs( e );
delete f;
}
//
// Load pid from file
//
pid_t
Server::LoadPid( Error *e )
{
StrBuf pidfile;
StrBuf pidbuf;
pid_t pid = 0;
PidFile( pidfile );
FileSys *f = FileSys::Create( FST_TEXT );
f->Set( pidfile );
Config::RestorePrivs( e );
// Open the file
f->Open( FOM_READ, e );
if( !e->Test() )
{
if( f->ReadLine( &pidbuf, e ) )
pid = pidbuf.Atoi();
f->Close( e );
}
Config::DropPrivs( e );
delete f;
return pid;
}
//
// Remove pid file
//
void
Server::CleanPid()
{
Error e;
StrBuf pidfile;
PidFile( pidfile );
FileSys *f = FileSys::Create( FST_TEXT );
f->Set( pidfile );
Config::RestorePrivs( &e );
f->Unlink();
Config::DropPrivs( &e );
delete f;
}
void
Server::LoadUserData( Error *e )
{
struct passwd * pw;
if( !owner.Length() )
{
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Owner";
return;
}
if( !( pw = getpwnam( owner.Text() ) ) )
{
e->Set( CtlErr::OwnerNotFound ) << name << label << owner.Text();
return;
}
owner = pw->pw_name;
owner_uid = pw->pw_uid;
owner_gid = pw->pw_gid;
owner_home = pw->pw_dir;
owner_shell = pw->pw_shell;
}
pid_t
Server::CreateChild( int daemon, int silent, Error *e )
{
fflush( stdout );
fflush( stderr );
pid_t pid;
switch( pid = fork() )
{
case -1:
e->Sys( "fork", "" );
return pid;
case 0:
// Child
break;
default:
// parent
return pid;
}
// child continues
// Set up the child's environment. This leaks a little, but the child
// will soon exit or exec() so it doesn't matter.
childEnv = new VarArray;
VarList ce;
// Set Standard variables first
ce.SetVar( "HOME", owner_home.Text() );
ce.SetVar( "SHELL", owner_shell.Text() );
ce.SetVar( "USER", owner.Text() );
ce.SetVar( "LOGNAME", owner.Text() );
ce.SetVar( "LANG", "C" );
ce.SetVar( "LC_ALL", "C" );
// Now merge in the global, and local environment lists together getting
// rid of any dups.
ce.Import( global_config->GetEnv() ); // Then global
ce.Import( env ); // Then server specific
// Now build the new environ pointer
ce.Export( childEnv ); // Transfer to VarArray
childEnv->Put( 0 ); // Terminate!
environ = (char **) childEnv->ElemTab();
//
// Now the next step is to become the relevant user. To do that, we'll
// first need our privileges back temporarily.
//
Config::RestorePrivs( e );
if( e->Test() )
{
AssertLog.Report( e );
exit( CHILD_FAIL );
}
if( initgroups( owner.Text(), owner_gid ) < 0 )
{
e->Sys( "initgroups", owner.Text() );
AssertLog.Report( e );
exit( CHILD_FAIL );
}
if( setgid( owner_gid ) < 0 )
{
e->Sys( "setgid", owner.Text() );
AssertLog.Report( e );
exit( CHILD_FAIL );
}
if( setuid( owner_uid ) < 0 )
{
e->Sys( "setuid", owner.Text() );
AssertLog.Report( e );
exit( CHILD_FAIL );
}
//
// Now set the umask
//
if( Debug() )
printf( "Setting umask to: %#o\n", this->umask );
::umask( this->umask );
//
// Change directory to owner's home. Must happen after we've become the owner
//
if( chdir( owner_home.Text() ) < 0 )
{
e->Sys( "chdir", owner_home.Text() );
AssertLog.Report( e );
exit( CHILD_FAIL );
}
// Dump the output unless explicitly requested, or debugging.
if( silent && !Debug() )
{
FILE *t; // to squelch compiler gripes.
t = freopen( "/dev/null", "r", stdin );
t = freopen( "/dev/null", "w", stdout );
t = freopen( "/dev/null", "w", stderr );
}
for( int i = 3; i < 256; i++ )
close( i );
//
// If they want a daemon, we now detach from the controlling terminal
//
if( daemon )
setsid();
return 0;
}
//
// Returns to Parent:
// 0 on error
// 1 on success
//
// Sets pid to the pid of the child process.
//
int
Server::RunCommand( const char *cmd, CmdLine *args, pid_t &pid,
int daemon, int silent, Error *e )
{
int status;
StrBuf cmdStr;
//
// Format the command line into a buffer
//
args->Fmt( cmd, cmdStr );
switch( pid = CreateChild( daemon, silent, e ) )
{
case -1:
return 0;
case 0:
break;
default:
// Parent - if we're not starting a daemon, then reap the exit
// status of the child. If we are starting a daemon, then we'll
// wait 1 second for the child's exec to potentially fail. In this
// way we hope to be more accurate with error reporting.
//
int waitopts = 0;
if( daemon )
{
sleep( 1 );
waitopts = WNOHANG;
}
switch( waitpid( pid, &status, waitopts ) )
{
case -1:
e->Sys( "waitpid", "" );
return 0;
case 0:
// child has not yet exited. Cool!
return 1;
default:
// Whoops. Child died on us.
break;
}
if( WIFSIGNALED( status ) )
{
e->Set( CtlErr::ChildKilled ) << pid << WTERMSIG( status );
return 0;
}
if( WEXITSTATUS( status ) )
{
if( WEXITSTATUS( status ) == CHILD_EXEC_FAIL )
e->Set( CtlErr::ChildExecFail ) << cmdStr;
else
e->Set( CtlErr::ChildFailed ) << name << label << cmdStr << WEXITSTATUS(status);
return 0;
}
return 1;
}
// Child now exec's
if( DEBUG_CMDS )
printf( "Executing: \"%s\"\n", cmdStr.Text() );
char *const * argv = args->Argv();
execvp( cmd, argv );
delete []argv;
// Whoops!
e->Sys( "exec()", cmdStr.Text() );
exit( CHILD_EXEC_FAIL );
}
void
Server::ExtractArgs( CmdLine &cmdline )
{
StrBuf t;
int i;
char * b;
char * e;
for( b = e = args.Text(); b; )
{
switch( *e )
{
case ' ':
t.Set( b, e - b );
cmdline.Arg( t.Text() );
b = ++e;
break;
case 0:
t.Set( b, e - b );
if( t.Length() )
cmdline.Arg( t.Text() );
return;
default:
e++;
}
}
}
//
// Run a Perforce command: we run this in a subprocess to ensure that the
// environment is 100% correct.
//
int
Server::RunP4Command( const char *cmd, CmdLine &c, Error *e )
{
pid_t pid;
int status;
StrBuf cmdStr;
//
// Format the command line into a buffer
//
c.Fmt( cmd, cmdStr );
switch( pid = CreateChild( 0, 1, e ) )
{
case -1:
e->Sys( "fork", "" );
return 0;
case 0:
// Child
break;
default:
// parent
if( waitpid( pid, &status, 0 ) < 0 )
{
e->Sys( "waitpid", "" );
return 0;
}
if( WIFSIGNALED( status ) )
{
e->Set( CtlErr::ChildKilled ) << pid << WTERMSIG( status );
return 0;
}
//
// What we return now is 1 if the command ran successfully
// and 0 if there was an error
return !WEXITSTATUS( status );
}
// Child now runs the Perforce command
if( DEBUG_CMDS )
printf( "Running: \"p4 %s\"\n", cmdStr.Text() );
ClientApi client;
P4UI ui;
StrPtr * port;
port = env->GetVar( "P4PORT" );
client.SetPort( port );
client.Init( e );
if( e->Test() )
AssertLog.Abort( e );
if( c.Count() ) client.SetArgv( c.Count(), c.Argv() );
client.Run( cmd, &ui );
if( Debug() )
printf( "Output: %s\n", ui.GetOutput().Text() );
client.Final( e );
printf( "Perforce command done\n" );
if( !ui.Success() ) ui.GetErrors( e );
exit( e->Test() );
}
int
Server::CheckDirectory( StrPtr *d, Error *e )
{
FileSys *f = FileSys::Create( FST_BINARY );
f->Set( d->Text() );
int st = f->Stat();
delete f;
if( ! st & FSF_EXISTS )
e->Set( CtlErr::MissingDir ) << name << label << d->Text();
else if( ! st & FSF_DIRECTORY )
e->Set( CtlErr::NotDirectory ) << name << label << d->Text();
if( e->Test() )
return !e->Test();
// Now try to create a file under the requested directory (e.g. P4PROOT)
// as a permissions check. There are various approaches to this, but
// actually doing it seems like the most robust, and spawning out to
// system utilities saves code that we'd have to duplicate from RunCommand.
pid_t pid;
StrBuf tmpName;
CmdLine cmdline( StrRef( "touch" ) );
FileSys *tmp = FileSys::Create( FST_TEXT );
tmpName << *d << "/" << "foo";
tmp->MakeLocalTemp( tmpName.Text() );
tmp->SetDeleteOnClose();
cmdline.Arg( tmp->Name() );
if( !RunCommand( "touch", &cmdline, pid, 0, 1, e ) )
{
e->Clear();
e->Set( CtlErr::DirPerms ) << name << label << d->Text();
}
delete tmp;
return !e->Test();
}
int
Server::CheckOwner( StrPtr &o, Error *e )
{
struct passwd * pw;
if( !o.Length() )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Owner";
if( !e->Test() && !( pw = getpwnam( o.Text() ) ) )
e->Set( CtlErr::OwnerNotFound ) << name << label << o.Text();
return !e->Test();
}
int
Server::IsValid( Error *e, int full )
{
for( int i = 0; Server *s = global_config->GetServer( i ); i++ )
{
if( this == &*s )
continue;
// Check for name + type uniqueness
if( s->Name() == Name() && s->Type() == Type() )
{
e->Set( CtlErr::DupeParm ) << Name() << label << "name";
return 0;
}
// Only perform the P4PORT check for services that would listen on it
if( !( Type() & SERVER_LISTENS ) || !( s->Type() & SERVER_LISTENS ) )
continue;
const StrPtr *mp = env->GetVar( "P4PORT" );
const StrPtr *sp = s->env->GetVar( "P4PORT" );
if( mp && sp && *mp == *sp )
{
e->Set( CtlErr::DupeParm ) << Name() << label << "P4PORT";
return 0;
}
}
return 1;
}
//------------------------------------------------------------------------------
// P4D class implementation
//------------------------------------------------------------------------------
P4D::P4D( const char *name, VarList *settings, VarList *env, const Config *config )
: Server( name, SERVER_P4D, settings, env, config )
{
label = "p4d";
prefix = "";
UpdateFromSettings();
}
void
P4D::UpdateFromSettings()
{
StrPtr *s;
Server::UpdateFromSettings();
if( s = settings->GetVar( "Prefix" ) )
prefix = *s;
}
//
// Check the server's configuration
//
int
P4D::IsValid( Error *e, int full )
{
if( !binary.Length() )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Execute" ;
else if( !owner.Length() )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Owner" ;
else if( !env->GetVar( "P4ROOT" ) )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "P4ROOT" ;
else if( !env->GetVar( "P4PORT" ) )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "P4PORT" ;
else if( !env->GetVar( "PATH" ) )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "PATH" ;
if( full )
{
// If we're OK this far, check that P4ROOT exists and is a directory
if( ! e->Test() )
CheckDirectory( env->GetVar( "P4ROOT" ), e );
// Now check the owner exists
if( ! e->Test() )
CheckOwner( owner, e );
if( !e->Test() )
Server::IsValid( e, full );
}
return !e->Test();
}
//
// Test if the server is using SSL or not. Look at configured P4PORT
//
int
P4D::SSLEnabled()
{
StrPtr *p4port = env->GetVar( "P4PORT" );
if( !p4port ) return 0;
StrBuf t = *p4port;
if( t.Length() < 3 ) return 0;
t.SetLength( 3 );
t.Terminate();
if( t == "ssl" ) return 1;
return 0;
}
//
// Set the process name for P4D servers
//
void
P4D::SetProcessName( StrBuf &processName )
{
StrPtr * pretty = settings->GetVar( "PrettyNames" );
if( pretty && *pretty == "false" )
{
processName << binary;
return;
}
StrPtr * port;
port = env->GetVar( "P4PORT" );
processName << "p4d [" << name << "/" << *port << "]";
}
void
P4D::ExtraText( StrBuf &extra )
{
StrPtr * port;
StrPtr * root;
port = env->GetVar( "P4PORT" );
root = env->GetVar( "P4ROOT" );
extra << "port=" << port << " root=" << root;
}
//
// Shut down the running server. Returns non-zero on error
//
int
P4D::Stop( Error *e )
{
StrPtr * port;
pid_t pid;
CmdLine cmdline;
cmdline.Arg( "stop" );
if( RunP4Command( "admin", cmdline, e ) )
{
CleanPid();
return 1;
}
e->Clear();
if( DEBUG_CMDS )
{
e->Set( CtlErr::KillingServer ) << name << label;
AssertLog.ReportNoTag( e );
e->Clear();
}
return Server::Stop( e );
}
//
// Check the status of the server. Returns non-zero if the server is alive
//
int
P4D::Running( Error *e )
{
CmdLine info_cmdline;
CmdLine trust_cmdline;
for( int loop=0; loop < 2; loop++ )
{
if( SSLEnabled() && loop )
{
trust_cmdline.Arg( "-f" );
trust_cmdline.Arg( "-y" );
RunP4Command( "trust", trust_cmdline, e );
e->Clear();
}
if( RunP4Command( "info", info_cmdline, e ) )
return 1;
e->Clear();
}
int pidCheck = Server::Running( e );
if( pidCheck )
pidCheck++;
return pidCheck;
}
//
// Take a checkpoint using 'p4d -jc'. Not interested in doing this
// via 'p4 admin checkpoint' since (a) we want to be able to do this
// with the server shut down, and (b) we need the exit status from
// the server.
//
void
P4D::Checkpoint( int compress, Error *e )
{
CmdLine cmdline( binary );
StrBuf t;
pid_t pid;
cmdline.Arg( "-jc" );
if( compress )
cmdline.Arg( "-z" );
if( prefix.Length() )
cmdline.Arg( prefix.Text() );
RunCommand( binary.Text(), &cmdline, pid, 0, 1, e );
if( e->Test() )
e->Set( CtlErr::CheckpointFail ) << name;
}
//
// Rotate the journal using 'p4d -jj'
//
void
P4D::RotateJournal( int compress, Error *e )
{
CmdLine cmdline( binary );
StrBuf t;
pid_t pid;
cmdline.Arg( "-jj" );
if( compress )
cmdline.Arg( "-z" );
if( prefix.Length() )
cmdline.Arg( prefix.Text() );
RunCommand( binary.Text(), &cmdline, pid, 0, 1, e );
if( e->Test() )
e->Set( CtlErr::RotateFail ) << name;
}
//------------------------------------------------------------------------------
// P4P class implementation
//------------------------------------------------------------------------------
//
// Check the server's configuration
//
int
P4P::IsValid( Error *e, int full )
{
if( !binary.Length() )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Execute" ;
else if( !owner.Length() )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Owner" ;
else if( !env->GetVar( "P4PORT" ) )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "P4PORT" ;
else if( !env->GetVar( "P4TARGET" ) )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "P4TARGET" ;
else if( !env->GetVar( "P4PCACHE" ) )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "P4PCACHE" ;
else if( !env->GetVar( "PATH" ) )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "PATH" ;
if( full )
{
// If we're OK this far, check that P4PCACHE exists and is a directory
if( ! e->Test() )
CheckDirectory( env->GetVar( "P4PCACHE" ), e );
// Now check the owner exists
if( ! e->Test() )
CheckOwner( owner, e );
if( !e->Test() )
Server::IsValid( e, full );
}
return !e->Test();
}
int
P4P::SSLEnabled()
{
StrPtr *p4port = env->GetVar( "P4PORT" );
if( !p4port ) return 0;
StrBuf t = *p4port;
if( t.Length() < 3 ) return 0;
t.SetLength( 3 );
t.Terminate();
if( t == "ssl" ) return 1;
return 0;
}
//
// Set the process name for P4P servers
//
void
P4P::SetProcessName( StrBuf &processName )
{
StrPtr * pretty = settings->GetVar( "PrettyNames" );
if( pretty && *pretty == "false" )
{
processName << binary;
return;
}
StrPtr * port;
port = env->GetVar( "P4PORT" );
processName << "p4p [" << name << "/" << *port << "]";
}
void
P4P::ExtraText( StrBuf &extra )
{
StrPtr * port;
StrPtr * target;
StrPtr * cache;
port = env->GetVar( "P4PORT" );
target = env->GetVar( "P4TARGET" );
cache = env->GetVar( "P4PCACHE" );
extra << "port=" << port << " target=" << target << " cache=" << cache;
}
//
// Check the status of the server. Returns non-zero if the server is alive.
// We can use 'p4 info' for this.
//
int
P4P::Running( Error *e )
{
CmdLine info_cmdline;
CmdLine trust_cmdline;
for( int loop=0; loop < 2; loop++ )
{
if( SSLEnabled() && loop )
{
trust_cmdline.Arg( "-f" );
trust_cmdline.Arg( "-y" );
RunP4Command( "trust", trust_cmdline, e );
e->Clear();
}
if( RunP4Command( "info", info_cmdline, e ) )
return 1;
e->Clear();
}
// p4 info failed. That doesn't necessarily mean the proxy isn't
// running though. It might mean the p4d or network link is
// down.
e->Clear();
return Server::Running( e );
}
//------------------------------------------------------------------------------
// P4Web class implementation
//------------------------------------------------------------------------------
//
// Check the server's configuration
//
int
P4Web::IsValid( Error *e, int full )
{
if( !binary.Length() )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Execute" ;
else if( !owner.Length() )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Owner" ;
else if( !env->GetVar( "P4PORT" ) )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "P4PORT" ;
else if( !env->GetVar( "P4WEBPORT" ) )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "P4WEBPORT" ;
if( full )
{
// Now check the owner exists
if( ! e->Test() )
CheckOwner( owner, e );
if( !e->Test() )
Server::IsValid( e, full );
}
return !e->Test();
}
//
// Set the process name for P4Web servers
//
void
P4Web::SetProcessName( StrBuf &processName )
{
StrPtr * pretty = settings->GetVar( "PrettyNames" );
if( pretty && *pretty == "false" )
{
processName << binary;
return;
}
StrPtr * port;
port = env->GetVar( "P4WEBPORT" );
processName << "p4web [" << name << "/" << *port << "]";
}
void
P4Web::ExtraText( StrBuf &extra )
{
StrPtr * port;
StrPtr * webport;
port = env->GetVar( "P4PORT" );
webport = env->GetVar( "P4WEBPORT" );
extra << "port=" << port << " webport=" << webport;
}
//------------------------------------------------------------------------------
// P4FTP class implementation
//------------------------------------------------------------------------------
//
// Start P4FTP. If the P4FTPPORT is < 1024, then we'll need to be root
// to open the listen address so we need to start it as root and
// add the -u flag to the command line args.
//
int
P4Ftp::Start( StrPtr extraArgs, Error *e )
{
if( !IsValid( e ) )
return 1;
StrPtr *port = env->GetVar( "P4FTPPORT" );
// If it's a non-privileged port, just call super-class implementation
// and return
if( port->Atoi() > 1023 )
return Server::Start( extraArgs, e );
//
// Trying to listen on a priv'd port (probably 21). Append "-u owner"
// to command line, and start it up as root. We have to reload the
// user data now that the owner's changed, and then we start her up.
//
args.Append( "-u " );
args << owner;
owner = "root";
LoadUserData( e );
return Server::Start( extraArgs, e );
}
//
// Check the server's configuration
//
int
P4Ftp::IsValid( Error *e, int full )
{
if( !binary.Length() )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Execute" ;
else if( !owner.Length() )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Owner" ;
else if( !env->GetVar( "P4PORT" ) )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "P4PORT" ;
else if( !env->GetVar( "P4FTPPORT" ) )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "P4FTPPORT" ;
if( full )
{
// Now check the owner exists
if( ! e->Test() )
CheckOwner( owner, e );
if( !e->Test() )
Server::IsValid( e, full );
}
return !e->Test();
}
//
// Set the process name for P4Ftp servers
//
void
P4Ftp::SetProcessName( StrBuf &processName )
{
StrPtr * pretty = settings->GetVar( "PrettyNames" );
if( pretty && *pretty == "false" )
{
processName << binary;
return;
}
StrPtr * port;
port = env->GetVar( "P4FTPPORT" );
processName << "p4ftpd [" << name << "/" << *port << "]";
}
void
P4Ftp::ExtraText( StrBuf &extra )
{
StrPtr * port;
StrPtr * ftpport;
port = env->GetVar( "P4PORT" );
ftpport = env->GetVar( "P4FTPPORT" );
extra << "port=" << port << " ftpport=" << ftpport;
}
//------------------------------------------------------------------------------
// P4Broker class implementation
//------------------------------------------------------------------------------
//
// Check the server's configuration
//
int
P4Broker::IsValid( Error *e, int full )
{
if( !binary.Length() )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Execute" ;
else if( !owner.Length() )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Owner" ;
else if( !env->GetVar( "P4BROKEROPTIONS" ) )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "P4BROKEROPTIONS" ;
if( full )
{
// Now check the owner exists
if( ! e->Test() )
CheckOwner( owner, e );
if( !e->Test() )
Server::IsValid( e, full );
}
return !e->Test();
}
//
// Set the process name for P4Ftp servers
//
void
P4Broker::SetProcessName( StrBuf &processName )
{
StrPtr * pretty = settings->GetVar( "PrettyNames" );
if( pretty && *pretty == "false" )
{
processName << binary;
return;
}
processName << "p4broker [" << name << "]";
}
void
P4Broker::ExtraText( StrBuf &extra )
{
StrPtr * options;
options = env->GetVar( "P4BROKEROPTIONS" );
extra << "options=" << options;
}
//------------------------------------------------------------------------------
// P4Dtg class implementation
//------------------------------------------------------------------------------
//
// Start P4DTG. The P4DTG replicator will not start if a run file still
// exists from the last run. If it's there, clear it.
//
int
P4Dtg::Start( StrPtr extraArgs, Error *e )
{
if( !IsValid( e ) )
return 0;
StrPtr *root = env->GetVar( "P4DTGROOT" );
StrPtr *map = env->GetVar( "P4DTGMAPPING" );
StrBuf run;
run << root << "/repl/run-" << map;
if( FileSys::FileExists( run.Text() ) )
{
FileSys *f = FileSys::Create( FST_TEXT );
f->Set( run );
f->Unlink( e );
delete f;
if( e->Test() )
return 0;
}
StrBuf stop;
stop << root << "/repl/stop-" << map;
if( FileSys::FileExists( stop.Text() ) )
{
FileSys *f = FileSys::Create( FST_TEXT );
f->Set( stop );
f->Unlink( e );
delete f;
if( e->Test() )
return 0;
}
args.Append( map );
args.Append( " " );
args.Append( root );
return Server::Start( extraArgs, e );
}
//
// Shut down the running P4DTG.
// We try to do this cleanly by touching a stop file.
// Returns non-zero on error
//
int
P4Dtg::Stop( Error *e )
{
if( !IsValid( e ) )
return 1;
StrPtr *root = env->GetVar( "P4DTGROOT" );
StrPtr *map = env->GetVar( "P4DTGMAPPING" );
StrBuf stop;
stop << root << "/repl/stop-" << map;
FileSys *f = FileSys::Create( FST_TEXT );
f->Set( stop );
f->Open( FOM_WRITE, e );
delete f;
if( e->Test() )
return 1;
int count = 0;
while( Server::Running( e ) && count++ <= 30 )
sleep( 1 );
if( !Server::Running( e ) )
{
CleanPid();
return 1;
}
return Server::Stop( e );
}
//
// Check the server's configuration
//
int
P4Dtg::IsValid( Error *e, int full )
{
if( !binary.Length() )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Execute" ;
else if( !owner.Length() )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Owner" ;
else if( !env->GetVar( "P4DTGROOT" ) )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "P4DTGROOT" ;
else if( !env->GetVar( "P4DTGMAPPING" ) )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "P4DTGMAPPING" ;
if( full )
{
// If we're OK this far, check that P4DTGROOT exists and is a directory
if( ! e->Test() )
CheckDirectory( env->GetVar( "P4DTGROOT" ), e );
// Now check that P4DTGROOT has a /repl sub-directory
if( ! e->Test() )
{
StrBuf rp;
rp << env->GetVar( "P4DTGROOT" ) << "/repl";
CheckDirectory( &rp, e );
}
// Now check the owner exists
if( ! e->Test() )
CheckOwner( owner, e );
if( !e->Test() )
Server::IsValid( e, full );
}
return !e->Test();
}
//
// Set the process name for P4DTG replicators
//
void
P4Dtg::SetProcessName( StrBuf &processName )
{
StrPtr * pretty = settings->GetVar( "PrettyNames" );
if( pretty && *pretty == "false" )
{
processName << binary;
return;
}
processName << "p4dtg [" << name << "]";
}
void
P4Dtg::ExtraText( StrBuf &extra )
{
StrPtr * root;
StrPtr * mapping;
root = env->GetVar( "P4DTGROOT" );
mapping = env->GetVar( "P4DTGMAPPING" );
extra << "root=" << root << " mapping=" << mapping;
}
//------------------------------------------------------------------------------
// OtherServer class implementation
//------------------------------------------------------------------------------
OtherServer::OtherServer( const char *name, VarList *settings, VarList *env, const Config *config )
: Server( name, SERVER_OTHER, settings, env, config )
{
label = "other";
UpdateFromSettings();
}
void
OtherServer::UpdateFromSettings()
{
Server::UpdateFromSettings();
StrPtr *s;
if( s = settings->GetVar( "Port" ) )
port = *s;
}
//
// Check the server's configuration
//
int
OtherServer::IsValid( Error *e, int full )
{
if( !binary.Length() )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Execute" ;
else if( !owner.Length() )
e->Set( CtlErr::MissingRequiredParm ) << name << label << "Owner" ;
if( full )
{
// Now check the owner exists
if( ! e->Test() )
CheckOwner( owner, e );
if( !e->Test() )
Server::IsValid( e, full );
}
return !e->Test();
}
//
// Set the process name for other servers
//
void
OtherServer::SetProcessName( StrBuf &processName )
{
StrPtr * pretty = settings->GetVar( "PrettyNames" );
if( pretty && *pretty == "false" )
{
processName << binary;
return;
}
processName << binary << " [" << name;
if( port.Length() ) processName << "/" << port;
processName << "]";
}
void
OtherServer::ExtraText( StrBuf &extra )
{
extra << "execute=" << binary;
}