/******************************************************************************* * 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 ) { 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(); 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(); } //------------------------------------------------------------------------------ // 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 ); } 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(); } return 0; } // // 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 ); } 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 ); } 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 ); } 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 ); } 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 ); } 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 ); } 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; }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#1 | 16543 | Matt Attaway | Update P4DCTL with code from 15.2 and reorg files | ||
//guest/perforce_software/p4dctl/src/server.cpp | |||||
#2 | 10658 | Jason Gibson |
Update P4DCTL from internal Perforce tree. Note that this change does not bring the internal Jam files and deletes the existing builds, leaving that as a future task. Changes include: ################################################################################ Added support for a -o option which allows command line arguments to be passed directly to the service being started. ################################################################################ Support for P4DTG in P4DCTL. Example config: p4dtg test { Execute = /usr/local/share/p4dtg-2010.2.452775/p4dtg-repl Owner = perforce Environment { P4DTGROOT = /p4/dtg P4DTGMAPPING = local } } ################################################################################ Move p4dctl's default configuration file (and the only one that is considered safe to use) to '/etc/perforce/p4dctl.conf', and also move the directory that gets included by default to '/etc/perforce/p4dctl.conf.d'. ################################################################################ Make p4dctl provide LANG and LC_ALL environment variables to child processes. These both default to 'C'. They can be overridden in either global or server-specific environment blocks, but given that 2014.2 relies on LANG, it's better if that is set than missing. ################################################################################ Make p4dctl status exit with a code of 1 if any of the servers that it attempts to check are not running. For automation support ################################################################################ Change syntax of p4dctl config files so as to more clearly differentiate between 'settings' that p4dctl understands and environment variables to be set for the child processes. The old style syntax looked like this: p4d main { Owner = perforce Execute = /usr/local/bin/p4d P4ROOT = /home/perforce/p4-main P4JOURNAL = journal P4PORT = 1666 PATH = /bin:/usr/bin:/usr/local/bin } Where the only thing that differentiated the two was a convention on the use of case. Anything p4dctl didn't understand would be set as an environment variable. Now, we're separating the environment into a dedicated block, so p4dctl can flag up early if it encounters settings it doesn't know about. The new syntax looks like this: p4d main { Owner = perforce Execute = /usr/local/bin/p4d Environment { P4ROOT = /home/perforce/p4-main P4JOURNAL = journal P4PORT = 1666 PATH = /bin:/usr/bin:/usr/local/bin } } ################################################################################ A new command in p4dctl that allows you to query the environment for any configured server. For example: list all p4d's with their Name, port and root: $ p4dctl env -a -t p4d Name P4PORT P4ROOT Name=p4d-master P4PORT=ssl:1666 P4ROOT=/var/lib/perforce/p4d-master Find the P4ROOT of a known Perforce server and set P4ROOT in your environment: $ eval `p4dctl env -t p4d p4d-master P4ROOT` ################################################################################ Manpages for p4dctl and its config file. ################################################################################ Add 'list' command to p4dctl. Lists configured servers visible to the current user. Lists useful information about all the standard Perforce Server types. |
||
#1 | 8907 | Matt Attaway |
Branch Tony Smith's p4dctl tool into perforce_software area p4dctl is a tool that helps admins to manage multiple Perforce servers. This tool is used by the Perforce Linux packages to help manage servers. |
||
//guest/tony_smith/perforce/p4dctl/src/server.cpp | |||||
#12 | 8326 | Tony Smith |
Some significant updates to p4dctl. 1. Support for interacting with SSL enabled servers 2. New command 'list' to list configured servers p4dctl [ options ] list [ -t type ] -a p4dctl [ options ] list [ -t type ] servername 3. New command 'env' to allow you to fetch arbitrary settings from a configured server in a form suitable for use with 'eval'. p4dctl [ options ] env [ -t type ] -a var [ var... ] p4dctl [ options ] env [ -t type ] servername var [ var... ] 4. Supply a new manpage for p4dctl |
||
#11 | 8172 | Tony Smith |
Update p4dctl with a few fixes and enhancements: 1. New 'Enable=(true|false)' flag for servers so you can temporarily prevent servers from starting. 2. Ensure exit status is correct when commands partially fail. For example, start now exits with non-zero status if there are some failures. 3. Remove debug printf() that was left over from the change that introduced include files. |
||
#10 | 8000 | Tony Smith |
Move chdir() call to after we've switched UID when starting processes as a particular user. Bug fix. |
||
#9 | 7996 | Tony Smith |
Add support for checkpoint and journal prefixes - configured in the server block. For example: Prefix = /some/disk/p4backup |
||
#8 | 7995 | Tony Smith |
Support for P4FTP servers listening on privileged ports. Limited to P4Ftp services only, if the P4FTPPORT is less than 1024, then the service will be started as root, and the '-u <user>' flag added automatically to the P4FTP commandline. |
||
#7 | 7828 | Tony Smith |
Add support for the broker to p4dctl andupdate version file. Simplified the code, and removed some ugly duplication which was bothering me. |
||
#6 | 7190 | Tony Smith |
Bug fix: Empty argument lists weren't handled correctly. This change fixes that |
||
#5 | 7184 | Tony Smith |
Fix bug that prevented globally defined environment variables from being overridden by local definitions. The diffs here are a little large because the most sensible way to do this was to switch to using dictionaries for building the environment vars (to make replacing values easy), and then convert them to a VarArray later in a format that can be used as an environ pointer. |
||
#4 | 7177 | Tony Smith |
Update p4dctl to build with 2008.2 API. No major changes needed, mostly jam stuff. The big change is to include all the rules from the sample Jamrules included in the Perforce API since that's the Jamrules used to build the API. This ensures that we're using the right compiler/linker flags on every platform. I also made sure that when lex & yacc are installed, and the grammar is compiled as part of the build, that the source tree is updated with the pre-compiled grammar. Makes it easy to maintain. Lastly, I updated the binary while I was there. |
||
#3 | 6285 | Tony Smith |
Add support for starting arbitrary daemons through this framework using configuration entries like this: other <name> { Execute = <binary> Owner = <username> [ Port = <listen port (if any)> ] [ Umask = <umask> ] PATH = "/usr/bin: etc. etc." } |
||
#2 | 6152 | Tony Smith |
Add support for configuring the umask with which Perforce services should run to p4dctl. If unspecified, the default umask is 022. |
||
#1 | 5945 | Tony Smith |
Release p4dctl, a program for starting/stopping Perforce services on Unix operating systems. Similar to, and developed in concert with, Sven Erik Knop's p4dcfg. For example: p4dctl start -a Can start multiple P4D, P4P, P4Web, or P4FTP servers in one easy command line. It can be executed by root, or by the 'owners' of the configured services and it maintains pidfiles no matter who uses it (so they remain accurate). An init script using p4dctl will typically just use: p4dctl start -a p4dctl stop -a p4dctl restart -a And check the exit status. |