serverhelper.cc #1

  • //
  • 2015-2/
  • guest/
  • perforce_software/
  • p4/
  • 2015-2/
  • client/
  • serverhelper.cc
  • View
  • Commits
  • Open Download .zip Download (32 KB)
/*
 * Copyright 1995, 2000 Perforce Software.  All rights reserved.
 *
 * This file is part of Perforce - the FAST SCM System.
 */

/*
 * serverhelper.cc - The logic for creating local Perforce Servers
 */

# define NEED_CHDIR
# define NEED_TYPES
# define NEED_STAT
# define NEED_FLOCK

# include <stdhdrs.h>

# include <strbuf.h>
# include <strdict.h>
# include <strtable.h>
# include <strarray.h>
# include <error.h>
# include <rpc.h>
# include <datetime.h>
# include <strops.h>

# include <pathsys.h>
# include <filesys.h>
# include <lockfile.h>

# include <enviro.h>
# include <hostenv.h>
# include <ignore.h>
# include <i18napi.h>
# include <charcvt.h>
# include <charset.h>
# include <handler.h>
# include <runcmd.h>

# include <msgclient.h>
# include <p4tags.h>

# include "client.h"
# include "clientuser.h"

# include "serverhelper.h"


static void LockCheck( Error * );

/*
 * The ServerHelper class
 */
ServerHelper::ServerHelper( Error *e )
{
	needLogin = 0;
	fetchAllowed = -1;
	quiet = 0;
	state = 0;
	maxCommitChange = 0;
	unicode = -1;
	security = -1;

	slaveUi = 0;

	defaultStreamChanged = 0;
	defaultStream.Set( "//stream/main" );

	config.Set( INIT_CONFIG );
	ignore.Set( INIT_IGNORE );

	version.Clear();
	prog.Set( "p4api" );
	version << ID_REL "/" << ID_OS << "/" << ID_PATCH;

	// Load P4CONFIG, or set it if it's not already set
	Enviro enviro;
	const char *s;
	if( ( s = enviro.Get( "P4CONFIG" ) ) )
	    config.Set( s );
	else
	    enviro.Set( "P4CONFIG", config.Text(), e );

	HostEnv h;
	h.GetCwd( pwd, &enviro );
}

/*
 * Checks to see if a DVCS server exists in a specified directory.
 * This step moves us into that directory too!
 */
int
ServerHelper::Exists( ClientUser *ui, Error *e )
{
	Enviro env;
	const char *s;

	Ignore ign;
	StrArray ignArr;
	
	// If dir hasn't been set, then we're working here

	if( !dir.Length() )
	    dir = pwd;
	
	PathSys *curdir = PathSys::Create();
	FileSys *fsys = FileSys::Create( FST_TEXT );

	curdir->SetLocal( pwd, dir );
	fsys->Set( dir );

	// If the directory doesn't exist, create it now

	if( !( fsys->Stat() & FSF_EXISTS ) )
	{
	    curdir->SetLocal( *curdir, StrRef( "file" ) );
	    fsys->MkDir( *curdir, e );
	    if( e->Test() )
	        goto finish;

	    curdir->ToParent();
	}
	
	if( chdir( curdir->Text() ) < 0 )
	    e->Set( MsgClient::ChdirFail ) << curdir;

	if( e->Test() )
	    goto finish;

	
	// Move the working directory to match
	env.Update( "PWD", curdir->Text() );
	env.Config( *curdir );
	dir.Set( curdir );

	// Check to see if we're somewhere under a DVCS environment
	// or if a .p4root directory exists here.

	
	// Get P4CHARSET and P4IGNORE whilst we're here
	if( ( s = env.Get( "P4CHARSET" ) ) )
	    unicode = StrPtr::CCompare( s, "none" );
	if( ( s = env.Get( "P4IGNORE" ) ) )
	    ignore.Set( s );

	// Make sure that if there are multiple P4IGNORE files specified, we
	// write the correct one.
	if( ign.GetIgnoreFiles( ignore, 0, 1, ignArr ) )
	    ignoreFile = *ignArr.Get( 0 );
	else
	{
	    ignore << ";" << INIT_IGNORE;
	    ignoreFile = INIT_IGNORE;
	}

	fsys->Set( INIT_ROOT );
	if( ( s = env.Get( "P4INITROOT" ) ) || ( fsys->Stat() & FSF_EXISTS  ) )
	{
	    if( !quiet && ui )
	    {
	        Error e1;
	        e1.Set( MsgClient::InitRootExists )
	                << ( s ? s : fsys->Path()->Text() );

	        ui->Message( &e1 );
	    }

	    delete curdir;
	    delete fsys;
	    return 1;
	}

	// Common failure/no-server cleanup
finish:
	delete curdir;
	delete fsys;

	if( !e->Test() )
	{
	    LockCheck( e );
	    if( e->Test() )
	        commandError.Set( MsgClient::LockCheckFail );
	}
	
	if( !commandError.Test() && e->Test() )
	    commandError = *e;
	if( commandError.Test() && ui )
	{
	    ui->Message( &commandError );
	    commandError.Clear();
	}

	return 0;
}

/*
 * Helper method for getting the user and client
 */
void
ServerHelper::SetUserClient( const StrPtr *uIn, const StrPtr *cIn )
{
	Client client;

	// Get username

	if( uIn && uIn->Length() )
	{
	    p4user.Set( uIn );
	    p4user.TrimBlanks();
	}

	if( !uIn || !uIn->Length() || !p4user.Length() )
	    p4user = client.GetUser();

	// Get clientname or generate one

	if( cIn && cIn->Length() )
	{
	    p4client.Set( cIn );
	    p4client.TrimBlanks();
	}

	if( !cIn || !cIn->Length() || !p4client.Length() )
	{
	    DateTime dt;

	    dt.SetNow();
	    StrNum dn( dt.Value() );
	    p4client.Set( p4user );
	    p4client.UAppend( "-dvcs-" );
	    p4client.UAppend( &dn );
	}
}

/*
 * Validates that the caseflag passed in is 0 or 1 (with or without the '-C')
 */
void
ServerHelper::SetCaseFlag( const StrPtr *c, Error *e )
{
	int caseVal = -1;
	if( strncmp( "-C", c->Text(), 2 ) )
	    caseVal = c->Atoi();
	else
	    caseVal = StrRef( c->Text() + 2 ).Atoi();

	if( caseVal != 0 && caseVal != 1 && caseVal != 2 )
	{
	    e->Set( MsgClient::InitCaseFlagUnset );
	    return;
	}

	caseFlag.Set( "-C" );
	caseFlag << caseVal;
}

/*
 * Sets the default stream name (validated to be 2 levels deep)
 */
void
ServerHelper::SetDefaultStream( const StrPtr *branch, Error *e )
{
	// Must look like a stream
	// e.g.   //depot/mainline
	// Or just the short stream name
	// e.g.           mainline

	StrRef slash( "/" );
	if( InvalidChars( branch->Text(), branch->Length() ) ||
	    ( branch->Contains( slash ) && TooWide( branch->Text(), 2, 1 ) ) )
	{
	    e->Set( MsgClient::NotValidStreamName ) << branch;
	    return;
	}

	defaultStream.Set( branch );
	defaultStreamChanged = 1;
}

/*
 * Helper method for configuring a Client object to use either the remote
 * or local Perforce Server.
 */
void
ServerHelper::InitClient( Client *client, int useEnv, Error *e )
{
	if( !useEnv )
	{
	    if( !p4port.Length() )
	        p4port = client->GetPort();

	    SetUserClient( &p4user, &p4client );

	    client->SetPort( &p4port );
	    client->SetUser( &p4user );
	    client->SetClient( &p4client );
	}

	client->SetProtocol( P4Tag::v_tag );
	client->SetProtocol( P4Tag::v_enableStreams );

	client->SetCwd( &dir );

	if( p4passwd.Length() )
	    client->SetPassword( &p4passwd );

	client->SetProg( &prog );
	client->SetVersion( &version );

	client->Init( e );
}

/*
 * Runs 'p4 info' to discover configuration details from a remote server
 */
int
ServerHelper::Discover( const StrPtr* port, ClientUser *ui, Error *e )
{
	if( port )
	    p4port.Set( port );

	Client client;

	InitClient( &client, 0, e );
	if( e->Test() )
	{
	    // Don't report the connection error, let the caller decide
	    commandError = *e;
	    return 0;
	}

	// Run info command, no crypto required (yet)

	SetCommand( "info", ui );
	client.Run( "info", this );

	state |= DISCOVERED;

	client.Final( e );
	return 1;
}

/*
 * Loads a remote spec into memory from a remote server
 */
int
ServerHelper::LoadRemote( const StrPtr* port, const StrPtr *remote,
    ClientUser *ui, Error *e )
{
	if( ( state & DISCOVERED ) && !FetchAllowed() )
	{
	    commandError.Set( MsgClient::CloneCantFetch ) << p4port;
	    ui->Message( &commandError );
	}

	if( !( state & DISCOVERED ) && port )
	    p4port.Set( port );

	if( remoteName.Length() )
	    commandError.Set( MsgClient::RemoteAlreadySet ) << remoteName;
	
	if( commandError.Test() )
	{
	    *e = commandError;
	    return 0;
	}

	// Run remote command, crypto required
	// Check remote exists
	
	Client client;
	char *args[2];

	InitClient( &client, 0, e );
	if( e->Test() )
	{
	    // Don't report the connection error, let the caller decide
	    commandError = *e;
	    return 0;
	}

	if( !( state & DISCOVERED ) )
	{
	    // Run info command, no crypto required (yet)

	    SetCommand( "info", ui );
	    client.Run( "info", this );

	    state |= DISCOVERED;

	    if( !FetchAllowed() )
	    {
	        commandError.Set( MsgClient::CloneCantFetch ) << p4port;
	        ui->Message( &commandError );
	        *e = commandError;
	        client.Final( e );
	        return 0;
	    }
	}

	args[0] = ( char * ) "-E";
	args[1] = remote->Text();
	SetCommand( "remotes", ui );
	client.SetArgv( 2, args );
	client.Run( "remotes", this );

	if( NeedLogin() )
	{
	    commandError.Set( MsgClient::CloneNeedLogin2 ) << p4user << p4port;
	    commandError.Set( MsgClient::CloneNeedLogin1 ) << p4user << p4port;
	    ui->Message( &commandError );
	    *e = commandError;
	    client.Final( e );
	    return 0;
	}

	// The remoteName is loaded from the returned data
	if( !remoteName.Length() )
	{
	    if( !GotError() )
	    {
	        commandError.Set( MsgClient::CloneNoRemote )
	            << p4port << remote;
	        ui->Message( &commandError );
	        *e = commandError;
	    }
	    client.Final( e );
	    return 0;
	}

	// Gather the remote spec's mappings and description

	args[0] = ( char * ) "-o";
	args[1] = remote->Text();
	SetCommand( "remote-out", ui );
	client.SetArgv( 2, args );
	client.Run( "remote", this );
	client.Final( e );

	if( GotError() )
	{
	    ui->Message( &commandError );
	    *e = commandError;
	    return 0;
	}
	
	state |= WITHREMOTE;

	return 1;
}

/*
 * Constructs a new remote spec in memory using the provided filepath
 */
int
ServerHelper::MakeRemote( const StrPtr *port, const StrPtr *filepath,
    ClientUser *ui, Error *e )
{
	if( ( state & DISCOVERED ) && !FetchAllowed() )
	{
	    commandError.Set( MsgClient::CloneCantFetch ) << p4port;
	    ui->Message( &commandError );
	    *e = commandError;
	}

	if( !( state & DISCOVERED ) && port )
	    p4port.Set( port );

	if( remoteName.Length() )
	    commandError.Set( MsgClient::RemoteAlreadySet ) << remoteName;
	
	if( commandError.Test() )
	    return 0;
	
	Client client;
	char *args[2];

	InitClient( &client, 0, e );
	if( e->Test() )
	{
	    // Don't report the connection error, let the caller decide
	    commandError = *e;
	    return 0;
	}


	if( !( state & DISCOVERED ) )
	{
	    // Run info command, no crypto required (yet)

	    SetCommand( "info", ui );
	    client.Run( "info", this );

	    state |= DISCOVERED;

	    if( !FetchAllowed() )
	    {
	        commandError.Set( MsgClient::CloneCantFetch ) << p4port;
	        ui->Message( &commandError );
	        *e = commandError;
	        client.Final( e );
	        return 0;
	    }
	}

	// Fakeup a remote map called origin
	// We can only map a filepath if its only wildcards are
	// trailing ellipses.

	StrBuf depotMap;
	int cannotMap = 0;
	int reMap = 0;
	const char *s = filepath->Text();
	const char *x = s + strlen( s );
	const char *p = strstr( s, "/..." );
	const char *newPath;

	if( !p || p != ( x - 4 ) )
	    cannotMap = 1;

	if( TooWide( s, 2, 0 ) && cannotMap )
	{
	    if( *s == '/' && *(s+1) == '/'  && strstr( (s+2), "/" ) 
	                    && *(x-1) != '/'  )
	    {
	        reMap = 1;
	        newPath = x;
	        while( *newPath != '/' ) 
	            newPath--;
	    }
	    else
	    {
	        commandError.Set( MsgClient::ClonePathNoMap ) << filepath;
	        ui->Message( &commandError );
	        *e = commandError;
	        client.Final( e );
	        return 0;
	    }

	}
	else if( TooWide( s, 2, 0 ) )
	{
	    commandError.Set( MsgClient::ClonePathTooWide ) << filepath;
	    ui->Message( &commandError );
	    *e = commandError;
	    client.Final( e );
	    return 0;
	}

	int invalid = 0;
	if( ( invalid = InvalidChars( s, x - s - 4 ) ) )
	{
	    commandError.Set( invalid > 1 ? MsgClient::ClonePathHasWild
	                                  : MsgClient::ClonePathHasIllegal )
	        << filepath;
	    ui->Message( &commandError );
	    *e = commandError;
	    client.Final( e );
	    return 0;
	}

	args[0] = (char *) "-s";
	SetCommand( "login-s", ui );
	client.SetArgv( 1, args );
	client.Run( "login", this );

	if( NeedLogin() )
	{
	    commandError.Set( MsgClient::CloneNeedLogin2 ) << p4user << p4port;
	    commandError.Set( MsgClient::CloneNeedLogin1 ) << p4user << p4port;
	    ui->Message( &commandError );
	    *e = commandError;
	    client.Final( e );
	    return 0;
	}

	client.Final( e );

	StrRef slash( "/" );
	StrBuf left;
	if( defaultStreamChanged && !defaultStream.Contains( slash ) )
	    left << "//stream/";
	left << defaultStream;

	if( reMap )
	    depotMap << left << newPath << " " << filepath;
	else if( cannotMap )
	    depotMap << filepath << " " << filepath;
	else
	    depotMap << left << "/..." << " " << filepath;

	Dict()->SetVar( StrRef( "depotMap" ), depotMap );
	SetDescription( "auto-generated from clone command" );
	remoteName.Set( "origin" );

	state |= WITHREMOTE;

	return 1;
}

/*
 * Wrapper method around the steps required to fully initialise the DVCS server
 */
int
ServerHelper::InitLocalServer( ClientUser *ui, Error *e )
{
	int ecode = 0;

	SetUserClient( &p4user, &p4client );

	if( state & CREATED_MASK )
	    return 0;

	if( !caseFlag.Length() )
	    e->Set( MsgClient::InitCaseFlagUnset );
	else if( unicode < 0 )
	    e->Set( MsgClient::InitUnicodeUnset );

	if( e->Test() )
	    return 0;

	WriteConfig( e );

	if( !e->Test() )
	    ecode = CreateLocalServer( ui, e );

	if( !ecode && !e->Test() && PostInit( ui ) )
	    WriteIgnore( e );

	if( e->Test() )
	{
	    ui->Message( e );
	    ecode = ecode ? ecode : 1;
	}

	if( commandError.Test() )
	{
	    ecode = ecode ? ecode : 1;
	    if( !e->Test() )
	        *e = commandError;
	}

	if( !ecode )
	    state |= CREATED_SUCCESS;
	else
	    state |= CREATED_FAIL;

	return ecode;
}

/*
 * Write the P4CONFIG file
 */
void
ServerHelper::WriteConfig( Error *e )
{
	FileSys *fsys = FileSys::Create( FST_TEXT );
	char *s = 0;

	fsys->Set( config );

	// Does a P4CONFIG file exist?

	if( ( fsys->Stat() & ( FSF_EXISTS|FSF_EMPTY ) ) == FSF_EXISTS )
	{
	    // Yes. So append to the config file instead.
	    delete fsys;
	    fsys = FileSys::Create( FST_ATEXT );
	    fsys->Set( config );
	}

	// Add values
	fsys->Perms( FPM_RW );
	fsys->Open( FOM_WRITE, e );

	if( e->Test() )
	    goto finish;

	fsys->Write( StrRef( "P4IGNORE=" ), e );
	fsys->Write( ignore, e );

	fsys->Write( StrRef( "\nP4CHARSET=" ), e );
	fsys->Write( StrRef( unicode ? "auto" : "none" ), e );

	fsys->Write( StrRef("\n"
	    "P4INITROOT=$configdir\n"
	    "P4USER=" ), e );
	fsys->Write( p4user, e );

# ifdef OS_NT
	fsys->Write( StrRef( "\nP4PORT=rsh:p4d.exe -i " ), e );
# else
	fsys->Write( StrRef(
	    "\nP4PORT=rsh:/bin/sh -c \"umask 077 && exec p4d -i " ), e );
# endif
	if( debug.Length() ) { fsys->Write( StrRef( "-v" ), e );
	                       fsys->Write( debug, e ); }
	                else { fsys->Write( StrRef( "-J off" ), e ); }
# ifdef OS_NT
	fsys->Write( StrRef( " -r \"$configdir\\" INIT_ROOT "\"\n" ), e );
# else
	fsys->Write( StrRef( " -r '$configdir/" INIT_ROOT "'\"\n" ), e );
# endif
	fsys->Write( StrRef( "P4CLIENT=" ), e );
	fsys->Write( p4client, e );
	fsys->Write( "\n", 1, e );

	fsys->Close( e );

finish:
	delete fsys;
}

/*
 * Initialise the new local server using the -xn/-xnn flags
 */
int
ServerHelper::CreateLocalServer( ClientUser *ui, Error *e )
{
	FileSys *fsys = FileSys::Create( FST_TEXT );
	FileSys *rootDir = FileSys::Create( FST_TEXT );
	PathSys *curdir = PathSys::Create();

	RunCommandIo rc;
	RunArgv ra;
	StrBuf res;
	Error e1;
	int ecode = 1;

	// Move to the .p4root directory
	curdir->SetLocal( dir, StrRef( INIT_ROOT ) );

	ra << "p4d";

	// Handle unicode
	if( unicode ) ra << "-xn";
	         else ra << "-xnn";

	ra << "-r" << *curdir;

	ra << "-u" << p4user;

	// Handle case flags
	ra << caseFlag;

	// quiet
	ra << "-q";

	curdir->SetLocal( *curdir, StrRef( "file" ) );
	fsys->Set( *curdir );

# ifndef OS_NT
	int oldumask = umask( 077 );
# endif

	fsys->MkDir( e );

	// Make the .p4root directory hidden

	curdir->ToParent();
	rootDir->Set( *curdir );
	rootDir->SetAttribute( FSA_HIDDEN, e );

	fsys->Set( INIT_SERVERID );
	fsys->Perms( FPM_RW );
	fsys->Open( FOM_WRITE, e );

	if( e->Test() )
	    goto finish;

	fsys->Write( p4client, e );
	fsys->Write( "\n", 1, e );
	fsys->Close( e );

	if( e->Test() )
	    goto finish;
	
	ecode = rc.Run( ra, res, e );

	res.TrimBlanks();
	if( res.Length() )
	{
	    e1.Set( E_INFO, "%output%" ) << res;
	    ui->Message( &e1 );
	}

	if( ecode )
	{
	    // server failed to initialize

	    fsys->Set( config );
	    fsys->Unlink( e );

	    fsys->Set( INIT_SERVERID );
	    fsys->Unlink( e );
	    fsys->RmDir( e );

	    e->Set( MsgClient::InitServerFail );
	    goto finish;
	}

	if( e->Test() )
	    goto finish;

finish:

# ifndef OS_NT
	umask( oldumask );
# endif

	delete curdir;
	delete rootDir;
	delete fsys;

	return ecode;
}

/*
 * Do the things we need to do after we initialise the Perforce Server, but
 * before it can be considered "init'ed".
 *
 * This involes:
 * - creating the server spec
 * - initialising any streams (creating the workspace in the progress)
 */
int
ServerHelper::PostInit( ClientUser *ui )
{
	Error e;
	int i, j;
	StrRef var, val;
	Client client;
	char *args[1000];
	inputData.Clear();

	// Create server record

	InitClient( &client, 1, &e );
	if( e.Test() )
	{
	    // Don't report the connection error, let the caller decide
	    commandError = e;
	    return 0;
	}

	inputData << "\nServerID: " << p4client;
	inputData << "\nType: server";
	inputData << "\nServices: local";
	inputData << "\nDescription:\n\tDVCS local personal repo\n";

	args[0] = (char *) "-i";
	SetCommand( "server-in", ui );
	client.SetArgv( 1, args );
	client.Run( "server", this );
	client.Final( &e );

	if( e.Test() || GotError() )
	{
	    ui->Message( e.Test() ? &e : &commandError );
	    return 0;
	}

	// Grab a new client now (we've just changed the server's context)
	
	InitClient( &client, 1, &e );
	if( e.Test() )
	{
	    // Don't report the connection error, let the caller decide
	    commandError = e;
	    return 0;
	}

	// Initialize depot, build mainline streams
	// Use the first map entry to create the first mainline

	int index = 0;
	int initDone = 0;
	int argsCount;
	StrBuf filePath;

	for( i = 0; Dict()->GetVar( i, var, val ); i++ )
	{
	    GetStreamName( &filePath, val );

	    if( StreamExists( filePath ) )
	        continue;

	    argsCount = 0;

	    if( initDone )
	    {
	        args[argsCount++] = (char *) "-m";
	        args[argsCount++] = (char *) "-c";
	    }
	    else
	    {
	        args[argsCount++] = (char *) "-i";
	        initDone++;
	    }

	    args[argsCount++] = (char *) "-s";
	    args[argsCount++] = (char *) Trim( filePath, val );

	    for( j = (i+1); Dict()->GetVar( j, var, val ); j++ )
	    {
	        if( !filePath.XCompareN( val ) )
	        {
	            args[argsCount++] = (char *) "-s";
	            args[argsCount++] = (char *) Trim( filePath, val );
	        }
	    }

	    args[argsCount++] = filePath.Text();
	    
	    SetCommand( "switch", ui );
	    client.SetArgv( argsCount, args );
	    client.Run( "switch", this );

	    // cleanup allocated trimmed paths

	    for( int x = 0; x < argsCount; ++x )
	    {
	        if( !strcmp( args[ x ], "-s" ) )
	            free( args[ ++x ] );
	    }

	    if( GotError() )
	        break;
	}

	// Handle init with no stream name

	if( !initDone )
	{
	    argsCount = 0;
	    args[argsCount++] = (char *) "-i";
	    if( defaultStreamChanged )
	        args[argsCount++] = (char *) defaultStream.Text();
	    SetCommand( "switch", ui );
	    client.SetArgv( argsCount, args );
	    client.Run( "switch", this );
	}

	client.Final( &e );

	if( GotError() )
	{
	    ui->Message( &commandError );
	    return 0;
	}

	return 1;
}

/*
 * Do the initial fetch (after saving the remore spec)
 */
int
ServerHelper::FirstFetch(
	int depth,
	int noArchivesFlag,
	const StrPtr *debugFlag,
	ClientUser *ui,
	Error *e )
{
	if( !remoteName.Length() )
	{
	    commandError.Set( MsgClient::NoRemoteToSet );
	    return 0;
	}
	
	Client client;
	int i;
	StrRef var, val;
	char *args[1000];
	inputData.Clear();

	if( !remoteName.Length() )
	    return 0;
	
	InitClient( &client, 1, e );

	if( e->Test() )
	{
	    // Don't report the connection error, let the caller decide
	    commandError = *e;
	    return 0;
	}

	inputData.Clear();
	inputData << "\nRemoteID: origin";
	inputData << "\nAddress: " << p4port;
	inputData << "\nOwner: " << p4user;
	inputData << "\nOptions: unlocked nocompress";
	inputData << "\nDescription: ";

	char *ptr = description.Text();

	while( *ptr != '\0' )
	{
	    inputData.Append( ptr, 1 );
	    if( *ptr++ == '\n' && *ptr != '\0' )
	        inputData << "\t";
	}

	inputData << "\nDepotMap:\n";

	for( i = 0; Dict()->GetVar( i, var, val ); i++ )
	    inputData << "\t" << val.Text() << "\n";

	args[0] = ( char * ) "-i";

	SetCommand( "remote-in", ui );
	client.SetArgv( 1, args );
	client.Run( "remote", this );

	if( GotError() )
	{
	    ui->Message( &commandError );
	    *e = commandError;
	    client.Final( e );
	    return 0;
	}

	commandError.Set( MsgClient::CloneStart ) << p4port;
	ui->Message( &commandError );
	commandError.Clear();

	// Issue fetch command
	int argc = 0;

	if( debugFlag )
	{
	    args[argc++] = ( char * ) "-v";
	}

	if( depth )
	{
	    args[argc++] = ( char * ) "-m";
	    args[argc++] = (char *) StrNum( depth ).Text();
	}
	if( noArchivesFlag )
	    args[argc++] = ( char * ) "-A";

	SetCommand( "fetch", ui );
	client.SetArgv( argc, args );
	client.Run( "fetch", this );

	// Get MaxCommitChange.

	args[0] = ( char * ) "maxCommitChange";

	SetCommand( "counter", ui );
	client.SetArgv( 1, args );
	client.Run( "counter", this );

	// Only update LastPush if data was pulled

	if( !MaxChange() )
	{
	    client.Final( e );
	    return 0;
	}

	// Reload origin remote map.

	args[0] = ( char * ) "-o";
	args[1] = ( char * ) "origin";

	SetCommand( "remote-out2", ui );
	client.SetArgv( 2, args );
	client.Run( "remote", this );

	// Alter last push value

	inputData.Clear();

	StrBuf map;
	map.Set( "DepotMap" );
	int mapCount = 0;

	for( i = 0; Dict()->GetVar( i, var, val ); i++ )
	{
	    // extra vars not in form

	    if( var == "specFormatted" || var == "func" )
		continue;
	    else if( var == "Description" )
	    {
		char *ptr = val.Text();
		inputData << var << ": ";

		while( *ptr != '\0' )
		{
		    inputData.Append( ptr, 1 );
		    if( *ptr++ == '\n' && *ptr != '\0' )
			inputData << "\t";
		}
	    }
	    else if( !map.XCompareN( var ) )
	    {
		if( !mapCount++ )
		    inputData << "DepotMap:\n";
		inputData << "\t" << val << "\n";
	    }
	    else
	    {
		inputData << var << ":\n\t";
		if( var == "LastPush" )
		    inputData << MaxChange() << "\n";
		else
		    inputData << val << "\n";
	    }
	}

	args[0] = ( char * ) "-i";

	SetCommand( "remote-in", ui );
	client.SetArgv( 1, args );
	client.Run( "remote", this );

	// End commands

	client.Final( e );

	return 1;
}

/*
 * Writes the P4IGNORE file
 */
void
ServerHelper::WriteIgnore( Error *e )
{
	FileSys *fsys;

	fsys = FileSys::Create( FST_TEXT );
	
	fsys->Set( ignoreFile );

	if( ( fsys->Stat() & ( FSF_EXISTS|FSF_EMPTY ) ) == FSF_EXISTS )
	{
	    // Might have been created by a failed clone, check before
	    // appending.

	    // Assume the clone fetch dumped it here... bail...

	    if( remoteName.Length() )
	        goto finish;

	    fsys->Open( FOM_READ, e );

	    if( e->Test() )
	        goto finish;

	    StrBuf line;
	    while( fsys->ReadLine( &line, e  ) )
	    {
	        if( !line.Compare( StrRef( INIT_ROOT ) ) )
	        {
	            fsys->Close( e );
	            goto finish;
	        }
	    }

	    fsys->Close( e );
	    delete fsys;
	    fsys = FileSys::Create( FST_ATEXT );
	    fsys->Set( ignore );
	}

	fsys->Perms( FPM_RW );
	fsys->Open( FOM_WRITE, e );

	if( e->Test() )
	    goto finish;
 
	fsys->Write( config, e );
	fsys->Write( "\n", 1, e );
	fsys->Write( ignore, e );
	fsys->Write( StrRef( "\n.svn\n.git\n.DS_Store\n"
	             INIT_ROOT "\n*.swp\n" ), e );
	fsys->Close( e );

finish:
	delete fsys;
}

/*
 * Is the streamname in our list
 */
int
ServerHelper::StreamExists( StrPtr &filePath )
{
	int i;
	StrRef var, val;

	for( i = 0; mainlines.GetVar( i, var, val ); i++ )
	{
	    if( !filePath.Compare( var ) )
	        return 1;
	}

	mainlines.SetVar( filePath, filePath );
	return 0;
}

/*
 * Gets the first two levels from a path.
 */
void
ServerHelper::GetStreamName( StrBuf *filePath, StrPtr &val )
{
	filePath->Clear();

	StrBuf buf;
	buf << val.Text();

	if( buf.Length() < 5 )
	    return;

	char *p, *s;

	s = buf.Text();
	if( *s == '-' ) s++;
	p = s + 2;
	p = strchr( p, '/' );

	if( !p || !*p )
	    return;

	p++;
	p = strchr( p, '/' );

	if( !p )
	    return;

	filePath->Append( s, p - s );
}

/*
 * Gets the bit between the streamname and the first space.
 */
const char *
ServerHelper::Trim( StrPtr &filePath, StrPtr &val )
{
	StrBuf sharePath;

	char *p = val.Text() + filePath.Length() + 1;
	char *e = p;

	while( *e && *e != ' ' )
	    ++e;

	sharePath.Append( p, e - p );

	p = (char *) malloc( sharePath.Length() + 1 );
	strcpy( p, sharePath.Text() );
	return p;
}

/*
 * Intercepting client tagged output parser
 */
void
ServerHelper::OutputStat( StrDict *varList )
{
	// Capture data from info and remote

	if( GetCommand() == "info" )
	{
	    StrPtr *s;

	    unicode = varList->GetVar( P4Tag::v_unicode ) != 0;

	    security = varList->GetVar( P4Tag::v_security ) != 0;

	    s = varList->GetVar( P4Tag::v_userName );
	    if( s )
	        userName.Set( s );

	    s = varList->GetVar( P4Tag::v_serverAddress );
	    if( s )
	        serverAddress.Set( s );

	    s = varList->GetVar( P4Tag::v_caseHandling );
	    if( !s || *s == "sensitive" )  caseFlag.Set( "-C0" );
	    else if( *s == "insensitive" ) caseFlag.Set( "-C1" );
	    else if( *s == "hybrid" )      caseFlag.Set( "-C2" );

	    s = varList->GetVar( "allowFetch" );
	    if( !s || s->Atoi() < 2 )
	        fetchAllowed = 0;
	    else
	        fetchAllowed = 1;

	}
	else if( GetCommand() == "remote-out" )
	{
	    int i;
	    int count = 0;
	    int quoted;
	    StrRef var, val;
	    StrBuf lhs, depot, empty, map;
	    map.Set( "DepotMap" );
	    empty.Set( "..." );
	    const char *p;

	    for( i = 0; varList->GetVar( i, var, val ); i++ )
	    {
	        depot.Clear();

	        if( !map.XCompareN( var ) )
	        {
	            StrOps::GetDepotName( val.Text(), depot );

	           if( !empty.XCompareN( depot ) )
	           {
	                commandError.Set( MsgClient::CloneRemoteInvalid );
	                break;
	           }

	            if( count++ )
	            {
	                if( depotName != depot )
	                {
	                    StrBuf depots;
	                    for( int j = 0; varList->GetVar(j, var, val); j++ )
	                        if( !map.XCompareN( var ) )
	                            depots << "\n" << val.Text();

	                    commandError.Set( MsgClient::CloneTooManyDepots )
	                        << depots;
	                    break;
	                }
	            }
	            else
	                depotName << depot;

	            // Validate stream(branch) side of map

	            lhs.Clear();

	            p = val.Text();
	            quoted = (*p++ == '"' );

	            while( *p != 0 )
	            {
	                if( ( quoted && *p == '"' ) ||
	                    (!quoted && *p == ' ' )  )
	                    break;
	                ++p;
	            }

	            if( quoted )
	                lhs.Append( val.Text()+1, (p - val.Text()) - 2 );
	            else
	                lhs.Append( val.Text(), p - val.Text() );

	            p = lhs.Text();
	            if( *p == '-' )
	               ++p;

	            if( TooWide( p, 2, 0 ) )
	                commandError.Set( MsgClient::CloneTooWide ) << lhs;

	            remoteMap.SetVar( var, val );
	            continue;
	        }

	        if( var ==  "Description" )
	            description << val;
	    }
	}
	else if( GetCommand() == "remote-out2" )
	{
	    int i;
	    StrRef var, val;
	    remoteMap.Clear();

	    for( i = 0; varList->GetVar( i, var, val ); i++ )
	        remoteMap.SetVar( var, val );
	}
	else if( GetCommand() == "counter" )
	{
	    StrPtr *s = varList->GetVar( "value" );

	    if( s )
	        maxCommitChange = s->Atoi();
	}
	else if( GetCommand() == "remotes" )
	{
	    remoteName.Set( varList->GetVar( "RemoteID" ) );
	}
	else if( GetCommand() == "fetch" )
	{
	    StrPtr *cc = varList->GetVar( "changeCount" );
	    StrPtr *fc = varList->GetVar( "totalFileCount" );
	    if( cc && fc && !quiet && slaveUi )
	    {
	        commandError.Set( MsgClient::CloneFetchCounts ) << cc << fc;
	        slaveUi->Message( &commandError );
	    }
	}
}

/*
 * Intercepting client text data passthough
 */
void
ServerHelper::OutputText( const char *data, int length )
{
	slaveUi->OutputText( data, length );
}

/*
 * Intercepting client specific command silencing info message passthough
 */
void
ServerHelper::OutputInfo( char level, const char *data )
{
	// quiet where possible

	if( !debug.Length() &&
	    ( GetCommand() == "remote-in" || GetCommand() == "switch" ) )
	    return;

	if( slaveUi )
	    slaveUi->OutputInfo( level, data );
}

/*
 * Intercepting client session validating error message passthough
 */
void
ServerHelper::OutputError( const char *errBuf )
{
	if( ( GetCommand() == "remotes" || GetCommand() == "login-s" )
	    && ( !strncmp( errBuf, "Perforce password", 17 ) || 
	         !strncmp( errBuf, "Your session has expired", 24 ) ) )
	{
	    needLogin++;
	}
	else if( slaveUi )
	{
	    slaveUi->OutputError( errBuf );
	}
}

/*
 * Intercepting client data sender
 */
void
ServerHelper::InputData( StrBuf *buf, Error *e )
{
	if( GetCommand() == "remote-in" ||
	    GetCommand() == "server-in" )
	    buf->Set( inputData );
}

/*
 * Handle overriden progress indicators
 */
int
ServerHelper::ProgressIndicator()
{
	return slaveUi ? slaveUi->ProgressIndicator()
	               : ClientUserProgress::ProgressIndicator();
}

ClientProgress *
ServerHelper::CreateProgress( int t )
{
	return slaveUi ? slaveUi->CreateProgress( t )
	               : ClientUserProgress::CreateProgress( t );
}

/*
 * Checks a path is at least or eactly N levels deep.
 */
int
ServerHelper::TooWide( const char *s, int levels, int exact )
{
	const char *p;
	int invalid = 0;

	if( *s != '/' || *( s + 1 ) != '/' || *( s + 2 ) == '/' )
	    invalid = 1;

	p = s + 1;

	for( int level = 1; !invalid && level <= levels; level++ )
	{
	    if( level == levels )
	    {
	        if( !exact &&
	            ( !( p = strstr( p + 1, "/" ) ) || *( p + 1 ) == '\0' ) )
	            invalid = 1;
	        else if( exact && ( p = strstr( p + 1, "/" ) ) )
	            invalid = 1;
	    }
	    else if( !( p = strstr( p + 1, "/" ) ) || *( p + 1 ) == '/' )
	        invalid = 1;
	}
	return invalid;
}

/*
 * Checks that a path does not include illegal charaters or embedded wildcards
 */
int
ServerHelper::InvalidChars( const char *s, int l )
{
	const char *p;
	for( p = s; p - s < l; p++ )
	    if( ( *p == '@' || *p == '%' || *p == '#' || *p == '*' ) ||
	        ( *p == '.' && *( p + 1 ) == '.' && *( p + 2 ) == '.' ) )
	        return *p == '.' ? 2 : 1;
	return 0;
}


/*
 * Ensure we're not about to init a server of a filesystem that doesn't
 * support locking (we're looking at you NFS!).
 *
 * Called from Exists(): we only need to check if we're going to init a new
 * server and since that method potentially moved the CWD, it makes sense.
 */
static void
LockCheck( Error *e )
{
	FileSys *fsys, *altsys;

	fsys = FileSys::Create( FST_BINARY );

	fsys->Set( "db.check" );
	fsys->Perms( FPM_RW );
	fsys->Open( FOM_WRITE, e );

	if( e->Test() )
	    goto finish;

	altsys = FileSys::Create( FST_BINARY );

	altsys->Set( fsys->Name() );
	altsys->Perms( FPM_RW );
	altsys->Open( FOM_READ, e );

	if( !e->Test() )
	{
	    int ffd, afd;

	    ffd = fsys->GetFd();
	    afd = altsys->GetFd();

// Because Solaris locking is at a process level not a file descriptor
// level, we can't do all the checks we want

	    if( ! (
	            lockFile( ffd, LOCKF_EX_NB ) == 0 &&
# ifndef OS_SOLARIS
	            lockFile( afd, LOCKF_SH_NB ) == -1 &&
# endif
	            lockFile( ffd, LOCKF_UN ) == 0 &&
	            lockFile( afd, LOCKF_SH_NB ) == 0 &&
# ifndef OS_SOLARIS
	            lockFile( ffd, LOCKF_EX_NB ) == -1 &&
# endif
	            lockFile( afd, LOCKF_UN ) == 0 ) )
	        e->Sys( "lockFile", "db.check" );

	    altsys->Close( e );
	}

	fsys->Close( e );

	delete altsys;

finish:
	fsys->Unlink( e );
	
	delete fsys;
}
# Change User Description Committed
#1 18917 Liz Lam Submitting 15.2 source code.