/*******************************************************************************
* 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.
*******************************************************************************/
/*
* Program to start/stop multiple Perforce servers on a machine
*/
#define NEED_GETUID
#include <stdhdrs.h>
#include <stdarg.h>
#include <strbuf.h>
#include <error.h>
#include <errorlog.h>
#include <vararray.h>
#include <strdict.h>
#include <strtable.h>
#include <options.h>
#include <ident.h>
#include "p4dctlerr.h"
#include "cmdline.h"
#include "server.h"
#include "config.h"
/*
* Ident String
*/
Ident ident = {
IdentMagic "P4DCTL" "/" ID_OS "/" ID_REL "/" ID_PATCH,
ID_Y "/" ID_M "/" ID_D
};
//------------------------------------------------------------------------------
// Local types
//------------------------------------------------------------------------------
typedef struct StatBlock
{
int matched;
int invalid;
int skipped;
int denied;
int disabled;
int running;
int stopped;
int attempted;
int succeeded;
StatBlock()
{
matched = invalid = skipped = denied = disabled =
running = stopped = attempted = succeeded = 0;
}
} StatBlock;
void
ReportError( Error *e, int tag = 1 )
{
if( tag )
AssertLog.Report( e );
else
AssertLog.ReportNoTag( e );
e->Clear();
}
//
// Start all, or specified, servers. Return 0 on failure, 1 on success
// and update started with the number of servers actually started.
//
int
StartServers( Config &config, int startAll, StrPtr &name, int type,
int &started, StrPtr &p4dOptions, Error *e )
{
// Keep track of what we find
StatBlock stats;
int i;
Server * s;
//
// Iterate over servers
//
for( started = 0, i = 0; s = config.GetServer( i ); i++ )
{
if( startAll || name == s->Name() )
{
if( !s->KindOf( type ) )
continue;
stats.matched++;
switch( s->CheckAccess( e ) )
{
case CHECK_DENIED:
e->Clear();
e->Set( CtlErr::SkippedServer ) << s->Name()
<< s->Label()
<< s->Owner();
ReportError( e, 0 ); // No tag this time
stats.skipped++;
continue;
case CHECK_DENIED_ROOT:
ReportError( e );
stats.denied++;
continue;
default:
break;
}
if( !s->IsValid( e ) )
{
stats.invalid++;
ReportError( e );
continue;
}
if( !s->Enabled() )
{
e->Set( CtlErr::IgnoreDisabled ) << s->Name() << s->Label();
ReportError( e );
stats.disabled++;
continue;
}
if( s->Running( e ) )
{
e->Set( CtlErr::AlreadyRunning ) << s->Name() << s->Label();
ReportError( e );
stats.running++;
continue;
}
stats.attempted++;
if( s->Start( p4dOptions, e ) )
{
e->Set( CtlErr::StartedServer ) << s->Name() << s->Label();
ReportError( e, 0 ); // No tag
stats.succeeded++;
}
else
{
ReportError( e );
}
}
}
started = stats.succeeded;
if( !startAll && !stats.matched ) return 0;
if( stats.invalid ) return 0;
if( stats.denied ) return 0;
if( stats.attempted != stats.succeeded ) return 0;
return 1;
}
int
StopServers( Config &config, int stopAll, StrPtr &name, int type,
int &stopped, Error *e )
{
int i;
Server * s;
// Keep track of what we find
StatBlock stats;
//
// Iterate over servers
//
for( stopped = 0, i = 0; s = config.GetServer( i ); i++ )
{
if( stopAll || name == s->Name() )
{
if( !s->KindOf( type ) )
continue;
stats.matched++;
switch( s->CheckAccess( e ) )
{
case CHECK_DENIED:
e->Clear();
e->Set( CtlErr::SkippedServer ) << s->Name()
<< s->Label()
<< s->Owner();
ReportError( e, 0 ); // No tag
stats.skipped++;
continue;
case CHECK_DENIED_ROOT:
ReportError( e );
stats.denied++;
continue;
default:
break;
}
if( !s->IsValid( e ) )
{
ReportError( e );
stats.invalid++;
continue;
}
if( !s->Running( e ) )
{
e->Set(CtlErr::ServerNotRunning) << s->Name() << s->Label();
ReportError( e, 0 ); // No tag
stats.stopped++;
continue;
}
stats.attempted++;
if( !s->Stop( e ) )
{
ReportError( e, 0 );
}
else
{
e->Set( CtlErr::StoppedServer ) << s->Name() << s->Label();
ReportError( e, 0 ); // No tag
stats.succeeded++;
}
}
}
stopped = stats.succeeded;
if( !stopAll && !stats.matched ) return 0;
if( stats.invalid ) return 0;
if( stats.denied ) return 0;
if( stats.attempted != stats.succeeded ) return 0;
return 1;
}
int
CheckServers( Config &config, int checkAll, StrPtr &name, int type, Error *e )
{
Server * s;
StatBlock stats;
int r;
//
// Iterate over servers
//
for( int i = 0; s = config.GetServer( i ); i++ )
{
if( checkAll || name == s->Name() )
{
if( !s->KindOf( type ) )
continue;
stats.matched++;
switch( s->CheckAccess( e ) )
{
case CHECK_DENIED:
e->Clear();
e->Set( CtlErr::SkippedServer ) << s->Name()
<< s->Label()
<< s->Owner();
ReportError( e, 0 ); // No tag
stats.skipped++;
continue;
case CHECK_DENIED_ROOT:
ReportError( e );
stats.denied++;
continue;
default:
break;
}
if( !s->IsValid( e ) )
{
ReportError( e );
stats.invalid++;
continue;
}
stats.attempted++;
if( ( r = s->Running( e ) ) )
{
e->Set( r == 1 ? CtlErr::ServerRunning : CtlErr::RunningErrors)
<< s->Name() << s->Label();
ReportError( e, 0 ); // No tag
stats.running++;
stats.succeeded++;
continue;
}
e->Set( CtlErr::ServerNotRunning ) << s->Name() << s->Label();
ReportError( e, 0 ); // No tag
}
}
if( !checkAll && !stats.matched ) return 0;
if( stats.invalid ) return 0;
if( stats.denied ) return 0;
if( stats.attempted != stats.succeeded ) return 0;
return 1;
}
void
ServerEnv( Config &config, int doAll, StrPtr &name, int type,
int argc, char **argv, Error *e )
{
int i;
Server * s;
//
// Iterate over servers
//
for( i = 0; s = config.GetServer( i ); i++ )
{
if( doAll || name == s->Name() )
{
if( !s->KindOf( type ) )
continue;
// Disable full validation for env.
if( !s->IsValid( e, 0 ) )
{
ReportError( e );
continue;
}
switch( s->CheckAccess( e ) )
{
case CHECK_DENIED:
case CHECK_DENIED_ROOT:
e->Clear();
continue;
default:
break;
}
// Iterate over args, treating each one as an environment
// variable to be displayed.
int ac = argc;
char **av = argv;
StrBuf var;
StrPtr *val;
for( ; ac-- ; av++ )
{
var = *av;
val = s->GetVar( var );
if( val )
printf( "%s=%s\n", var.Text(), val->Text() );
}
}
}
}
int
RotateJournals( Config &config, int rotateAll, StrPtr &name, Error *e )
{
StatBlock stats;
Server * s;
P4D * p4d = 0;
//
// Iterate over servers
//
for( int i = 0; s = config.GetServer( i ); i++ )
{
if( rotateAll || name == s->Name() )
{
if( !s->KindOf( SERVER_P4D ) )
continue;
stats.matched++;
switch( s->CheckAccess( e ) )
{
case CHECK_DENIED:
e->Clear();
e->Set( CtlErr::SkippedServer ) << s->Name()
<< s->Label()
<< s->Owner();
ReportError( e, 0 ); // No tag
stats.skipped++;
continue;
case CHECK_DENIED_ROOT:
ReportError( e );
stats.denied++;
continue;
default:
break;
}
if( !s->IsValid( e ) )
{
ReportError( e );
stats.invalid++;
continue;
}
p4d = (P4D *) s;
stats.attempted++;
// Blurt out our message
e->Set( CtlErr::JnlRotate ) << p4d->Name();
ReportError( e, 0 ); // No tag
p4d->RotateJournal( 1, e );
if( e->Test() )
ReportError( e );
else
stats.succeeded++;
e->Clear();
}
}
if( !rotateAll && !stats.matched ) return 0;
if( stats.denied ) return 0;
if( stats.invalid ) return 0;
if( stats.attempted != stats.succeeded ) return 0;
return 1;
}
int
CheckpointServers( Config &config, int ckpAll, StrPtr &name, Error *e )
{
StatBlock stats;
Server * s;
P4D * p4d = 0;
//
// Iterate over servers
//
for( int i = 0; s = config.GetServer( i ); i++ )
{
if( ckpAll || name == s->Name() )
{
if( !s->KindOf( SERVER_P4D ) )
continue;
switch( s->CheckAccess( e ) )
{
case CHECK_DENIED:
e->Clear();
e->Set( CtlErr::SkippedServer ) << s->Name()
<< s->Label()
<< s->Owner();
ReportError( e, 0 ); // No tag
stats.skipped++;
continue;
case CHECK_DENIED_ROOT:
ReportError( e );
stats.denied++;
continue;
default:
break;
}
if( !s->IsValid( e ) )
{
ReportError( e );
stats.invalid++;
continue;
}
stats.attempted++;
p4d = (P4D *) s;
// Blurt out our message
e->Set( CtlErr::Checkpointing ) << p4d->Name();
ReportError( e, 0 ); // No tag
p4d->Checkpoint( 1, e );
if( e->Test() )
ReportError( e );
else
stats.succeeded++;
e->Clear();
}
}
if( !ckpAll && !stats.matched ) return 0;
if( stats.denied ) return 0;
if( stats.invalid ) return 0;
if( stats.attempted != stats.succeeded ) return 0;
return 1;
}
void
ListServers( Config &config, int type, Error *e )
{
int i;
Server * s;
const char * recordFmt = "%-8s %-12s %-12s %-s\n";
//
// Iterate over servers
//
fprintf( stderr, recordFmt, "Type", "Owner", "Name", "Config" );
for( i = 0; s = config.GetServer( i ); i++ )
{
if( !s->KindOf( type ) )
continue;
switch( s->CheckAccess( e ) )
{
case CHECK_DENIED:
case CHECK_DENIED_ROOT:
e->Clear();
continue;
default:
break;
}
if( !s->IsValid( e ) )
{
ReportError( e );
continue;
}
StrBuf extra;
s->ExtraText( extra );
printf( recordFmt, s->Label().Text(),
s->Owner()->Text(),
s->Name().Text(),
extra.Text() );
}
}
int
CmdStart( Config &config, int argc, char **argv, Error *e )
{
Options opts;
StrRef name;
StrPtr * opt;
StrRef p4dOptions = StrRef::Null();
int started = 0;
int startAll = 0;
int status = 0;
int type = SERVER_ALL;
opts.Parse( argc, argv, "ao:t:", OPT_OPT, CtlErr::UsageStart, e );
if( e->Test() )
{
ReportError( e );
return 0;
}
if( opts[ 'a' ] )
startAll++;
if( opt = opts[ 'o' ] )
{
p4dOptions.Set(opt->Text());
}
if( opt = opts[ 't' ] )
type = config.ServerType( opt->Text() );
if( !argc && !startAll )
{
e->Set( CtlErr::UsageStart );
ReportError( e );
return 0;
}
if( argc )
{
argc--;
name.Set( *argv++ );
if( !config.GetServer( name ) )
{
e->Set( CtlErr::IgnoreNonExist );
ReportError( e );
return 0;
}
}
status = StartServers( config, startAll, name, type, started, p4dOptions, e );
e->Set( CtlErr::StartedServers ) << started;
ReportError( e, 0 ); // No tag
if( !status )
{
e->Set( CtlErr::SomeFailedStarts );
ReportError( e );
}
return status;
}
int
CmdStop( Config &config, int argc, char **argv, Error *e )
{
Options opts;
StrRef name;
StrPtr * opt;
int stopped = 0;
int stopAll = 0;
int status = 0;
int type = SERVER_ALL;
opts.Parse( argc, argv, "at:", OPT_OPT, CtlErr::UsageStop, e );
if( e->Test() )
{
ReportError( e );
return 0;
}
if( opts[ 'a' ] )
stopAll++;
if( opt = opts[ 't' ] )
type = config.ServerType( opt->Text() );
if( !argc && !stopAll )
{
e->Set( CtlErr::UsageStop );
ReportError( e );
return 0;
}
if( argc )
{
argc--;
name.Set( *argv++ );
if( !config.GetServer( name ) )
{
e->Set( CtlErr::IgnoreNonExist );
ReportError( e );
return 0;
}
}
status = StopServers( config, stopAll, name, type, stopped, e );
e->Set( CtlErr::StoppedServers ) << stopped;
AssertLog.ReportNoTag( e );
e->Clear();
if( !status )
{
e->Set( CtlErr::SomeFailedStops );
ReportError( e );
}
return status;
}
int
CmdRestart( Config &config, int argc, char **argv, Error *e )
{
Options opts;
StrRef name;
StrPtr * opt;
StrRef p4dOptions = StrRef::Null();;
int started = 0;
int stopped = 0;
int startAll = 0;
int status = 0;
int type = SERVER_ALL;
opts.Parse( argc, argv, "ao:t:", OPT_OPT, CtlErr::UsageRestart, e );
if( e->Test() )
{
ReportError( e );
return 0;
}
if( opts[ 'a' ] )
startAll++;
if( opt = opts[ 'o' ] )
{
p4dOptions.Set( opt->Text() );
}
if( opt = opts[ 't' ] )
type = config.ServerType( opt->Text() );
if( !argc && !startAll )
{
e->Set( CtlErr::UsageRestart );
ReportError( e );
return 0;
}
if( argc )
{
argc--;
name.Set( *argv++ );
if( !config.GetServer( name ) )
{
e->Set( CtlErr::IgnoreNonExist );
ReportError( e );
return 0;
}
}
//
// In restart we only care about failures to start. Failures to stop
// are glossed over since they're less important
//
StopServers( config, startAll, name, type, stopped, e );
e->Set( CtlErr::StoppedServers ) << stopped;
AssertLog.ReportNoTag( e );
e->Clear();
status = StartServers( config, startAll, name, type, started, p4dOptions, e );
e->Set( CtlErr::StartedServers ) << started;
ReportError( e, 0 ); // No tag
if( !status )
{
e->Set( CtlErr::SomeFailedStarts );
ReportError( e );
}
return status;
}
int
CmdStatus( Config &config, int argc, char **argv, Error *e )
{
Options opts;
StrPtr * opt;
StrRef name;
int checkAll = 0;
int type = SERVER_ALL;
opts.Parse( argc, argv, "at:", OPT_OPT, CtlErr::UsageStatus, e );
if( e->Test() )
{
ReportError( e );
return 0;
}
if( opts[ 'a' ] )
checkAll++;
if( opt = opts[ 't' ] )
type = config.ServerType( opt->Text() );
if( !argc && !checkAll )
{
e->Set( CtlErr::UsageStatus );
ReportError( e );
return 0;
}
if( argc )
{
argc--;
name.Set( *argv++ );
if( !config.GetServer( name ) )
{
e->Set( CtlErr::IgnoreNonExist );
ReportError( e );
return 0;
}
}
if( !CheckServers( config, checkAll, name, type, e ) || e->Test() )
{
ReportError( e );
return 0;
}
return 1;
}
int
CmdList( Config &config, int argc, char **argv, Error *e )
{
Options opts;
StrPtr * opt;
int type = SERVER_ALL;
opts.Parse( argc, argv, "t:", OPT_OPT, CtlErr::UsageList, e );
if( e->Test() )
{
ReportError( e );
return 0;
}
if( opt = opts[ 't' ] )
type = config.ServerType( opt->Text() );
ListServers( config, type, e );
if( e->Test() )
{
ReportError( e );
return 0;
}
return 1;
}
int
CmdEnv( Config &config, int argc, char **argv, Error *e )
{
Options opts;
StrPtr * opt;
StrRef name;
int doAll = 0;
int type = SERVER_ALL;
opts.Parse( argc, argv, "at:", OPT_ANY, CtlErr::UsageEnv, e );
if( e->Test() )
{
ReportError( e );
return 0;
}
if( opts[ 'a' ] )
doAll++;
if( opt = opts[ 't' ] )
type = config.ServerType( opt->Text() );
if( argc && !doAll )
{
argc--;
name.Set( *argv++ );
if( !config.GetServer( name ) )
{
e->Set( CtlErr::IgnoreNonExist );
ReportError( e );
return 0;
}
}
if( !argc )
{
e->Set( CtlErr::UsageEnv );
ReportError( e );
return 0;
}
ServerEnv( config, doAll, name, type, argc, argv, e );
if( e->Test() )
{
ReportError( e );
return 0;
}
return 1;
}
int
CmdCheckpoint( Config &config, int argc, char **argv, Error *e )
{
Options opts;
StrPtr * opt;
StrRef name;
int ckpAll = 0;
int status = 0;
opts.Parse( argc, argv, "a", OPT_OPT, CtlErr::UsageCheckpoint, e );
if( e->Test() )
{
ReportError( e );
return 0;
}
if( opts[ 'a' ] )
ckpAll++;
if( !argc && !ckpAll )
{
e->Set( CtlErr::UsageCheckpoint );
ReportError( e );
return 0;
}
if( argc )
{
argc--;
name.Set( *argv++ );
if( !config.GetServer( name ) )
{
e->Set( CtlErr::IgnoreNonExist );
ReportError( e );
return 0;
}
}
status = CheckpointServers( config, ckpAll, name, e );
if( e->Test() )
{
ReportError( e );
return 0;
}
return status;
}
int
CmdJournal( Config &config, int argc, char **argv, Error *e )
{
Options opts;
StrPtr * opt;
StrRef name;
int rotateAll = 0;
int status = 0;
opts.Parse( argc, argv, "a", OPT_OPT, CtlErr::UsageJournal, e );
if( e->Test() )
{
ReportError( e );
return 0;
}
if( opts[ 'a' ] )
rotateAll++;
if( !argc && !rotateAll )
{
e->Set( CtlErr::UsageJournal );
ReportError( e );
return 0;
}
if( argc )
{
argc--;
name.Set( *argv++ );
if( !config.GetServer( name ) )
{
e->Set( CtlErr::IgnoreNonExist );
ReportError( e );
return 0;
}
}
status = RotateJournals( config, rotateAll, name, e );
if( e->Test() )
ReportError( e );
return status;
}
//
// Should we force the user to use only our safe config paths? Basically,
// if the process has an euid of zero, then messing with the configuration
// on the command line is dangerous.
//
extern int yydebug;
int main( int argc, char **argv )
{
const char *safe_cfg_file = "/etc/perforce/p4dctl.conf";
const char *safe_pid_dir = "/var/run";
const char *cfg_file = safe_cfg_file;
const char *pid_dir = safe_pid_dir;
const char *cmd = 0;
int debug = 0;
int quiet = 0;
int test = 0;
int retval = 0;
Options opts;
StrPtr * o;
Error e;
Config config;
AssertLog.SetTag( "p4dctl" );
opts.Parse( --argc, ++argv, "c:p:qv:TV", OPT_ANY, CtlErr::UsageMain, &e );
AssertLog.Abort( &e );
if( o = opts[ 'c' ] )
cfg_file = o->Text();
if( o = opts[ 'p' ] )
pid_dir = o->Text();
if( o = opts[ 'v' ] )
debug = o->Atoi();
if( opts[ 'q' ] )
quiet++;
if( opts[ 'T' ] )
test++;
if( debug > 5 ) yydebug = 1;
//
// If we're running as root, then you can't pass arbitrary config
// files or pid directories.
//
if( !geteuid() && ( pid_dir != safe_pid_dir || cfg_file != safe_cfg_file ))
{
pid_dir = safe_pid_dir;
cfg_file = safe_cfg_file;
e.Set( CtlErr::UnsafeConfig );
AssertLog.Abort( &e );
}
if( opts[ 'V' ] )
{
StrBuf t;
ident.GetMessage( &t );
printf( "%s", t.Text() );
return 0;
}
//
// Drop privs now, before parsing config file
//
Config::DropPrivs( &e );
AssertLog.Abort( &e );
//
// Load our configuration
//
config.SetPidDir( pid_dir );
config.SetDebug( debug );
config.Parse( cfg_file, &e );
AssertLog.Abort( &e );
if( test )
{
config.Dump();
exit( 0 );
}
//
// If there are no more args, obviously that's a problem...
//
if( !argc )
{
e.Set( CtlErr::UsageMain );
AssertLog.ReportNoTag( &e );
return 0;
}
//
// What's left in argc, and argv are our commands and any arguments/flags
// to those commands. So
//
argc--;
cmd = *argv++;
// From here on in, be quiet if we've been asked to.
if( quiet )
AssertLog.SetSyslog();
if( !strcmp( "start", cmd ) )
retval = CmdStart( config, argc, argv, &e );
else if( !strcmp( "stop", cmd ) )
retval = CmdStop( config, argc, argv, &e );
else if( !strcmp( "restart", cmd ) )
retval = CmdRestart( config, argc, argv, &e );
else if( !strcmp( "status", cmd ) )
retval = CmdStatus( config, argc, argv, &e );
else if( !strcmp( "checkpoint", cmd ) )
retval = CmdCheckpoint( config, argc, argv, &e );
else if( !strcmp( "journal", cmd ) )
retval = CmdJournal( config, argc, argv, &e );
else if( !strcmp( "list", cmd ) )
retval = CmdList( config, argc, argv, &e );
else if( !strcmp( "env", cmd ) )
retval = CmdEnv( config, argc, argv, &e );
else
{
e.Set( CtlErr::UsageMain );
e.Set( CtlErr::BadCommand ) << cmd;
AssertLog.Abort( &e );
}
return !retval ? !retval : e.Test();
}