clientservice.cc #1

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

# ifdef USE_EBCDIC
# define NEED_EBCDIC
# endif

# include <stdhdrs.h>

# include <strbuf.h>
# include <strdict.h>
# include <strops.h>
# include <strarray.h>
# include <strtable.h>
# include <error.h>
# include <mapapi.h>
# include <runcmd.h>
# include <handler.h>
# include <rpc.h>
# include <md5.h>
# include <mangle.h>
# include <i18napi.h>
# include <charcvt.h>
# include <transdict.h>
# include <debug.h>
# include <tunable.h>
# include <ignore.h>
# include <timer.h>
# include <progress.h>

# include <p4tags.h>

# include <filesys.h>
# include <pathsys.h>
# include <enviro.h>
# include <ticket.h>

# include "clientmerge.h"
# include "clientresolvea.h"
# include "clientuser.h"
# include <msgclient.h>
# include <msgsupp.h>
# include "clientservice.h"
# include "client.h"
# include "clientprog.h"

# define SSOMAXLENGTH 131072    // max sso message 128k

ClientFile::ClientFile( FileSys *fs )
{
	file = fs;
	indirectFile = 0;
	isDiff = 0;
	checksum = 0;
	matchDict = 0;
}

ClientFile::~ClientFile()
{
	delete file;
	delete indirectFile;
	delete checksum;
	delete matchDict;
}

/*
 * ProgressHandle - progress indicator handle
 */

class ProgressHandle : public LastChance {
    public:
	ProgressHandle( ClientProgress *p ) : progress( p ) {}
	~ProgressHandle() { delete progress; }

	ClientProgress *progress;
};

FileSysType 
LookupType( const StrPtr *type )
{
	if( !type ) return FST_TEXT;

	/*
	 * For types see DmtFileType::ClientPart() in dmtypes.h.
	 *
	 * For backward and forward compatibility the following bits 
	 * have be reserved (*type - '0'):
	 *
	 * 	0x01: generally text(0)/binary(1)
	 *	0x02: executable
	 *
	 * NB prior to 99.1 the 'unknowns' didn't acknowledge the
	 * exec bit, and were just plain files.
	 *
	 * NB 2000.2 appletext is no different from applefile: its
	 * sole purpose is to support 'diff -t' on applefile.
	 */

	// FileSysType - Can't do |'s on enums, so it's an int for now.

	int t;

	// fileType [ lineType [ uncompress ] ]

	int tf = 0;
	int tl = 0;
	int tu = 0;

	switch( type->Length() )
	{
	default:
	case 3: tu = StrOps::XtoO( (*type)[2] );
	case 2: tl = StrOps::XtoO( (*type)[1] );
	case 1: tf = StrOps::XtoO( (*type)[0] );
	case 0: /* nothing??? */;
	}

	// Map '[ uncompress ] fileType' into FileSysType.

	switch( ( tu << 8 ) | tf )
	{

	    // Normal.

	case 0x000: t = FST_TEXT;			break; 
	case 0x001: t = FST_BINARY;			break; 
	case 0x002: t = FST_TEXT 	| FST_M_EXEC; 	break; 
	case 0x003: t = FST_BINARY 	| FST_M_EXEC;	break; 
	case 0x004: t = FST_SYMLINK;			break;
	case 0x005: t = FST_RESOURCE;			break; 
	case 0x006: t = FST_SYMLINK 	| FST_M_EXEC;	break;	// disallowed
	case 0x007: t = FST_RESOURCE 	| FST_M_EXEC;	break;  // disallowed
	case 0x008: t = FST_UNICODE;			break;	// 2001.1
	case 0x009: t = FST_RTEXT;			break;	// 99.1
	case 0x00A: t = FST_UNICODE 	| FST_M_EXEC;	break;	// 2001.1
	case 0x00B: t = FST_RTEXT 	| FST_M_EXEC;	break;	// 99.1
	case 0x00C: t = FST_APPLETEXT;			break;	// 2000.2
	case 0x00D: t = FST_APPLEFILE;			break;	// 99.2 
	case 0x00E: t = FST_APPLETEXT 	| FST_M_EXEC;	break;	// 2000.2
	case 0x00F: t = FST_APPLEFILE 	| FST_M_EXEC;	break;	// 99.2 
	case 0x018: t = FST_UTF16;			break;	// 2007.2
	case 0x01A: t = FST_UTF16 	| FST_M_EXEC;	break;	// 2007.2

	    // Uncompressing.

	case 0x101: t = FST_GUNZIP;			break;	// 2004.1
	case 0x103: t = FST_GUNZIP	| FST_M_EXEC;	break;	// 2004.1

	    // Stop-gap.

	default:   t = FST_BINARY;			break;

	}

	// And the lineType
	// 0x0 -> FST_L_LOCAL == 0, so don't bother.

	// If linetype already set,  don't | in the linetype bits.
	// 
	// The only way that linetype will be set after setting base
	// type is if FST_RTEXT has been selected (this is a binary file
	// being treated as text, usually for the purpose of diff/resolve).
	//
	// Note:  we can't overwrite the linetype because there is
	// a danger that we will write a file to the client workspace
	// with line-end translations,  but when submit happens it
	// will be treated as binary (adding extra characters to the
	// file stored by the server).

	if( !(t & FST_L_MASK) )
	{
	    switch( tl )
	    {
	    case 0x1: t |= FST_L_LF; break;
	    case 0x2: t |= FST_L_CR; break;
	    case 0x3: t |= FST_L_CRLF; break;
	    case 0x4: t |= FST_L_LFCRLF; break;
	    }
	}

	return FileSysType( t );
}

CharSetCvt *
ClientSvc::XCharset( Client *client, XDir d )
{
    CharSetCvt::CharSet trans_charset = 
	    (CharSetCvt::CharSet) client->ContentCharset();

    switch( d )
    {
    case FromClient:
	return CharSetCvt::FindCachedCvt( trans_charset, CharSetCvt::UTF_8 );
	break;
    case FromServer:
	return CharSetCvt::FindCachedCvt( CharSetCvt::UTF_8, trans_charset );
	break;
    }

    return 0;
}

FileSys *
ClientSvc::FileFromPath( Client *client, const char *vName, Error *e )
{
	StrPtr *clientPath = client->transfname->GetVar( vName, e );
	StrPtr *clientType = client->GetVar( P4Tag::v_type );

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

	FileSys *f = client->GetUi()->File( LookupType( clientType ) );

	f->SetContentCharSetPriv( client->ContentCharset() );
	f->Set( *clientPath, e );
	if( e->Test() )
	{
	    delete f;
	    client->OutputError( e );
	    return 0;
	}

	if( !clientPath->Compare( client->GetTicketFile() ) ||
	    !clientPath->Compare( client->GetTrustFile() ) ||
	    !f->IsUnderPath( client->GetClientPath() ) )
	{
	    e->Set( MsgClient::NotUnderPath )
		    << f->Name() << client->GetClientPath();
	    client->OutputError( e );
	    return 0;
	}
	return f;
}

FileSys *
ClientSvc::File( Client *client, Error *e )
{
	return FileFromPath( client, P4Tag::v_path, e );
}

class ClientProgressReport : public ProgressReport {
    public:
	ClientProgressReport( ClientProgress *p ) : cp(p) {}
	void DoReport( int );

    protected:
	ClientProgress *cp;
};

void
ClientProgressReport::DoReport( int flag )
{
	if( cp )
	{
	    if( fieldChanged & ( CP_DESC|CP_UNITS) )
		cp->Description( &description, units );
	    if( fieldChanged & CP_TOTAL )
		cp->Total( total );
	    if( fieldChanged & CP_POS )
		cp->Update( position );
	    fieldChanged = 0;
	    if( flag == CPP_DONE || flag == CPP_FAILDONE )
	    {
		cp->Done( flag == CPP_FAILDONE );
		needfinal = 0;
	    }
	}
}

/*
 * Client Service -- top half 
 */

void
Client::OutputError( Error *e )
{
	if( e->Test() )
	{
	    // Note error so we can adjust exit()
	    // Format and output

	    SetError();
	    GetUi()->HandleError( e );
	    e->Clear();
	    ClearPBuf();
	}
}

// Client Service -- bottom half
// File support

// clientBailoutFile - called if handle is never deleted

void
clientOpenFile( Client *client, Error *e )
{
	ClientFile *f;
	FileSys *fs = 0;

	client->NewHandler();
	StrPtr *clientPath = client->transfname->GetVar( P4Tag::v_path, e );
	StrPtr *clientHandle = client->GetVar( P4Tag::v_handle, e );
	StrPtr *modTime = client->GetVar( P4Tag::v_time );
	StrPtr *noclobber = client->GetVar( P4Tag::v_noclobber );
	StrPtr *sizeHint = client->GetVar( P4Tag::v_fileSize );
	StrPtr *perms = client->GetVar( P4Tag::v_perms );
	StrPtr *func = client->GetVar( P4Tag::v_func, e );
	StrPtr *diffFlags = client->GetVar( P4Tag::v_diffFlags );
	StrPtr *digest = client->GetVar( P4Tag::v_digest );

	if( e->Test() )
	{
	    if( !e->IsFatal() )
	    {
		f = new ClientFile( NULL );
		client->handles.Install( clientHandle, f, e );
		goto bail;
	    }
	    return;
	}

	// Create file object for writing.
	// Set binary/text/etc file type

	fs = ClientSvc::File( client, e );
	f = new ClientFile( fs );

	if( !fs )
	    e->Set( MsgClient::FileOpenError );
	if( e->Test() )
	{
	    f->SetError( e );
	    e->Clear();
	}

	// Create handle.
	// Toss file if we can't save handle.

	client->handles.Install( clientHandle, f, e );

	if( e->Test() )
	{
	    delete f;
	    return;
	}
	if( f->IsError() )
	    return;

	// Clear previous errors set by a failed refresh (Bug 30647).
	// Ignore return value.

	if( *clientHandle == "sync" )
	    client->handles.AnyErrors( clientHandle );

	// Prepare write/diff, as appropriate.
	// Before 97.2, the default was to clobber files and there wasn't
	// a noclobber var, so now the lack of noclobber means clobber.
	// Clobbering means writing an already writeable file.

	if( *func == P4Tag::c_OpenDiff || *func == P4Tag::c_OpenMatch )
	{
	    // Set up to be a diff
	    // We save the real name as the altFile and
	    // replace the real name with a temp file.

	    f->isDiff = 1;
	    f->file->SetDeleteOnClose();
	    f->diffName.Set( *clientPath );

	    // Save diffFlags for diff operation.

	    if( diffFlags )
		f->diffFlags.Set( diffFlags );

	    // Make temp dir 

	    f->file->MakeGlobalTemp();

	    // N-way diff?  Save rpc vars for the close..

	    if( *func == P4Tag::c_OpenMatch )
		clientOpenMatch( client, f, e );
	}
	else
	{
	    // Handle noclobber.

	    int statFlags = f->file->Stat();

	    if( noclobber &&
		( statFlags & (FSF_WRITEABLE|FSF_SYMLINK) ) == FSF_WRITEABLE )
	    {
		e->Set( MsgClient::ClobberFile ) << f->file->Name();
		goto bail;
	    }

	    // If the target file exists and is not a special file
	    // i.e. a device, we write a temp file and
	    // arrange for Close() to rename it into place.

	    if( ( statFlags & ( FSF_SYMLINK | FSF_EXISTS ) )
		  && ! ( statFlags & FSF_SPECIAL )
		  && f->file->DoIndirectWrites() )
	    {
		f->indirectFile = f->file;

		f->file = client->GetUi()->File( f->file->GetType() );
		f->file->SetContentCharSetPriv(
		    f->file->GetContentCharSetPriv() );
		f->file->MakeLocalTemp( f->indirectFile->Name() );
		f->file->SetDeleteOnClose();
	    }
	    else if( statFlags & FSF_SYMLINK )
	    {
		// This case is the target is an existing symlink
		// and the new file can not do indirect writes, but
		// we do need to replace the symlink...
		// so... delete the symlink and just
		// transfer normally...

		// XXX maybe we should move the symlink to
		// a temp name and move it back if the transfer
		// fails?

		f->file->Unlink( e );

		if( e->Test() )
		{
		    goto bail;
		}

		// delete the file if transfer fails

		f->file->SetDeleteOnClose();
	    }
	    else if( ( statFlags & FSF_EXISTS ) )
	    {
		f->file->Chmod2( FPM_RW, e );
		// Ignore chmod: we'll get error at open
		e->Clear();
	    }
	    else
	    {
		// create directory path when writing a (new file)
		// We do this after checking to see if the file
		// exists, because the fstat() of Stat() will
		// trigger the automounter, while the access() of
		// MkDir() will not (we think).

		f->file->MkDir( e );

		if( e->Test() )
		{
		    e->Set( MsgClient::MkDir ) << f->file->Name();
		    goto bail;
		}

		// If we are writing a file that doesn't exist,
		// we'll want to delete it if we fail to write it
		// fully.

		f->file->SetDeleteOnClose();
	    }

	    // Set closing permissions and mod time.
	    // Done after indirectFile shuffle.

	    if( perms && *perms == "rw" )
		f->file->Perms( FPM_RW );

	    if( modTime )
		f->file->ModTime( modTime );

	    // Pre-allocate space,  only NT fileio will use this

	    if( sizeHint )
	        f->file->SetSizeHint( sizeHint->Atoi64() );
	}

	// Actually open file

	f->file->Open( FOM_WRITE, e );

	// 2010.2 can verify transfered contents from server to client, for
	// non-textual filetypes updates are performed by FileIOBinary::Write()
	// note:  do this after the open (because open does a write on NT).

	if( digest && p4tunable.Get( P4TUNE_LBR_VERIFY_OUT ) 
	           && !f->file->IsSymlink() )
	{
	    f->serverDigest.Set( digest );
	    f->checksum = new MD5;
	    if( !f->file->IsTextual() && !( f->file->GetType() & FST_M_APPLE ) 
	                              && !( f->file->GetType() == FST_RESOURCE ) )
	        f->file->SetDigest( f->checksum );
	}

	// Character set translations
	f->file->Translator( ClientSvc::XCharset( client, FromServer ) );

	// If anything went wrong, we mark the handle so that
	// the rest of the write protocol is honored (but ignored).

    bail:
	// Mark handle with any error
	// Report non-fatal error and clear it.

	f->SetError( e );
	client->OutputError( e );
}

void
clientWriteFile( Client *client, Error *e )
{
	StrPtr *clientHandle = client->GetVar( P4Tag::v_handle, e );
	StrPtr *data = client->GetVar( P4Tag::v_data, e );

	if( e->Test() )
	    return;

	// Get handle.

	ClientFile *f = (ClientFile *)client->handles.Get( clientHandle, e );

	if( e->Test() || f->IsError() )
	    return;

	if( f->serverDigest.Length() && 
	  ( f->file->IsTextual() || ( f->file->GetType() & FST_M_APPLE ) 
	                         || ( f->file->GetType() == FST_RESOURCE ) ) )
	    f->checksum->Update( *data );

	// Write data.
	// On failure, mark handle with error.

# ifdef USE_EBCDIC
	// Translate back!
	if( !f->file->IsTextual() )
	    __etoa_l( data->Text(), data->Length() );
# endif

	f->file->Write( data, e );

	// Mark handle with any error
	// Report non-fatal error and clear it.

	f->SetError( e );
	client->OutputError( e );
}

void
clientCloseFile( Client *client, Error *e )
{
	StrPtr *clientHandle = client->GetVar( P4Tag::v_handle, e );
	StrPtr *func = client->GetVar( P4Tag::v_func, e );
	StrPtr *commit = client->GetVar( P4Tag::v_commit );

	if( e->Test() )
	    return;

	// Get handle.

	ClientFile *f = (ClientFile *)client->handles.Get( clientHandle, e );

	if( e->Test() )
	    return;

	// Close file, and then diff/rename as appropriate.

	if( f->file )
	    f->file->Close( e );

	if( !e->Test() && !f->IsError() && f->serverDigest.Length() && commit )
	{
	    StrBuf clientDigest;
	    f->checksum->Final( clientDigest );

	    if( f->serverDigest != clientDigest )
		e->Set( MsgClient::DigestMisMatch ) << f->file->Name()
		                                    << clientDigest
		                                    << f->serverDigest;
	}

	if( e->Test() || f->IsError() )
	{
	    // nothing
	}
	else if( f->isDiff )
	{
	    if( *func == P4Tag::c_CloseMatch )
	    {
		// Pass off control to clientFindMatch.
		// Don't delete handle yet, clientAckMatch needs it.

		clientCloseMatch( client, f, e );
		return;
	    }
	    FileSys *f2 = client->GetUi()->File( f->file->GetType() );
	    f2->SetContentCharSetPriv(
		f->file->GetContentCharSetPriv() );
	    f2->Set( f->diffName );
	    client->GetUi()->Diff( f->file, f2, 0, f->diffFlags.Text(), e );
	    delete f2;
	}
	else if( !commit )
	{
	    // nothing
	}
	else if( f->indirectFile )
	{
	    // rename to actual target

	    f->file->Rename( f->indirectFile, e );

	    // If rename worked, no longer need to delete temp.

	    if( !e->Test() )
		f->file->ClearDeleteOnClose();
	}
	else
	{
	    // just close actual target
	    f->file->ClearDeleteOnClose();
	}

	// Handle remembers if any error occurred 
	// Report non-fatal error and clear it.

	f->SetError( e );
	client->OutputError( e );

	delete f;
}

void
clientMoveFile( Client *client, Error *e )
{
	// Move file, clientPath is old,  targetPath is new

	client->NewHandler();
	StrPtr *clientPath = client->transfname->GetVar( P4Tag::v_path, e );
	StrPtr *targetPath = client->transfname->GetVar( P4Tag::v_path2, e );
	StrPtr *targetType = client->GetVar( P4Tag::v_type2, e );
	StrPtr *clientHandle = client->GetVar( P4Tag::v_handle );
	StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );
	StrPtr *rmdir = client->GetVar( P4Tag::v_rmdir );
	StrPtr *doForce = client->GetVar( P4Tag::v_force );
	StrPtr *perm = client->GetVar( P4Tag::v_perm );

	if( e->Test() )
	    return;

	FileSys *s = ClientSvc::File( client, e );

	if( e->Test() || !s )
	    return;

	// stat the source file

	if( !( s->Stat() & ( FSF_SYMLINK|FSF_EXISTS ) ) )
	{
	    e->Set( MsgClient::NoSuchFile ) << clientPath;
	    client->OutputError( e );
	    delete s;
	    return;
	}

	// set the source Perms, so they are reflected in the target
	// 2011.1+ server may request that existing RO perms be preserved.
	
	if( !perm || ( s->Stat() & FSF_WRITEABLE ) )
	    s->Perms( FPM_RW );

	// check target file does not exist

	FileSys *t = ClientSvc::FileFromPath( client, P4Tag::v_path2, e );
	if( e->Test() || !t )
	    return;

	// Target file exists,  could be a case change,  allow this only if
	// the server is case sensitive.

	if( ( t->Stat() & ( FSF_SYMLINK|FSF_EXISTS ) ) && !doForce &&
	    ( client->protocolNocase || clientPath->Compare( *targetPath ) ) )
	        e->Set( MsgClient::FileExists ) << targetPath;

	if( !e->Test() )
	    t->MkDir( e );

	if( !e->Test() )
	    s->Rename( t, e );

	// ok, now zonk the source directory, ignoring errors

	if( !e->Test() && rmdir )
	    s->RmDir();

	delete s;
	delete t;

	if( e->Test() )
	    client->OutputError( e );
	else
	    client->Confirm( confirm );
}

void
clientDeleteFile( Client *client, Error *e )
{
	client->NewHandler();
	StrPtr *clientPath = client->transfname->GetVar( P4Tag::v_path, e );
	StrPtr *clientType = client->GetVar( P4Tag::v_type );
	StrPtr *noclobber = client->GetVar( P4Tag::v_noclobber );
	StrPtr *clientHandle = client->GetVar( P4Tag::v_handle );
	StrPtr *rmdir = client->GetVar( P4Tag::v_rmdir );

	if( e->Test() && !e->IsFatal() )
	{
	    client->OutputError( e );
	    return;
	}

	FileSys *f = ClientSvc::File( client, e );

	if( e->Test() || !f )
	    return;

	// stat the file

	int stat = f->Stat();

	// Don't try to unlink a directory. It won't work, and it will be
	// confusing. Worse, it might mess up directory permissions.
	//
	if( (stat & (FSF_EXISTS | FSF_DIRECTORY | FSF_SYMLINK)) ==
	            (FSF_EXISTS | FSF_DIRECTORY) )
	{
	    delete f;
	    return;
	}

	// Don't clobber poor file
	// noclobber, handle new to 99.1
	// be safe about clientHandle being set

	if( noclobber && clientHandle &&
		( stat & ( FSF_WRITEABLE | FSF_SYMLINK ) ) == FSF_WRITEABLE )
	{
	    LastChance l;
	    client->handles.Install( clientHandle, &l, e );
	    l.SetError();

	    e->Set( MsgClient::ClobberFile ) << f->Name();
	    client->OutputError( e );
	    delete f;
	    return;
	}

	f->Unlink( e );

	// If unlink returned an error and the file existed before
	// the unlink attempt, then it failed.  Except it could be
	// an applefile, in that case one of the forks could have
	// been previously removed, if its applefile, stat it again.

	if( e->Test() && clientHandle && ( f->GetType() & FST_M_APPLE ) )
	    stat = f->Stat();

	if( e->Test() && clientHandle && ( stat & FSF_EXISTS ) )
	{
	    LastChance l;
	    client->handles.Install( clientHandle, &l, e );
	    l.SetError();

	    client->OutputError( e );

	    // Some platforms change the file mode to writeable so that
	    // unlink can work, make sure thats changed back.

	    if( !( stat & FSF_WRITEABLE ) )
	        f->Chmod( FPM_RO, e );

	    delete f;
	    return;
	}

	// Clear unlink error of non-existent file.

	e->Clear();

	// ok, now zonk the directory, ignoring errors

	if( rmdir )
	    f->RmDir();

	delete f;
}

void
clientChmodFile( Client *client, Error *e )
{
	client->NewHandler();
	StrPtr *clientPath = client->transfname->GetVar( P4Tag::v_path, e );
	StrPtr *perms = client->GetVar( P4Tag::v_perms, e );
	StrPtr *clientType = client->GetVar( P4Tag::v_type );
	StrPtr *modTime = client->GetVar( P4Tag::v_time );

	if( e->Test() && !e->IsFatal() )
	{
	    client->OutputError( e );
	    return;
	}

	FileSys *f = ClientSvc::File( client, e );

	if( e->Test() || !f )
	    return;

	// Set mode and time.  Mode is sensitive to exec bit.
	// Don't try and set the time if the file is not writable.
	// If its not writeable then its time was not changed anyhow,
	// this also avoids an access error.
	//

	if( modTime && ( f->Stat() & FSF_WRITEABLE ) )
	{
	    f->ModTime( modTime );
	    f->ChmodTime( e );
	}

	if( !e->Test() )
	    f->Chmod2( perms->Text(), e );

	delete f;

	// Report non-fatal error and clear it.

	client->OutputError( e );
}

void
clientConvertFile( Client *client, Error *e )
{
	StrPtr *clientPath = client->transfname->GetVar( P4Tag::v_path, e );
	StrPtr *perms      = client->GetVar( P4Tag::v_perms, e );
	StrPtr *fromCS     = client->GetVar( StrRef( P4Tag::v_charset ), 1 );
	StrPtr *toCS       = client->GetVar( StrRef( P4Tag::v_charset ), 2 );

	if( !fromCS || !toCS )
	    e->Set( MsgSupp::NoParm ) << P4Tag::v_charset;

	if( e->Test() )
	    return;

	int size = FileSys::BufferSize();
	StrBuf bu, bt;
	char *b = bu.Alloc( size );
	int l, statFlags;

	FileSys *f = 0;
	FileSys *t = 0;

	CharSetCvt::CharSet cs1 = CharSetCvt::Lookup( fromCS->Text() );
	CharSetCvt::CharSet cs2 = CharSetCvt::Lookup( toCS->Text() );
	if( (int)cs2 == -1 || (int)cs1 == -1 )
	    goto convertFileFinish;

	f = ClientSvc::File( client, e );
	f->SetContentCharSetPriv( cs1 );
	if( e->Test() )
	    goto convertFileFinish;

	statFlags = f->Stat();
	if( !( statFlags & FSF_EXISTS  ) ||
	     ( statFlags & FSF_SYMLINK )  )
	{
	    e->Set( MsgClient::FileOpenError );
	    goto convertFileFinish;
	}

	t = client->GetUi()->File( f->GetType() );
	t->MakeLocalTemp( f->Name() );
	t->SetContentCharSetPriv( cs2 );

	f->Open( FOM_READ, e );
	f->Translator( CharSetCvt::FindCachedCvt( cs1, CharSetCvt::UTF_8 ) );

	t->Open( FOM_WRITE, e );
	t->Translator( CharSetCvt::FindCachedCvt( CharSetCvt::UTF_8, cs2 ) );

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

	while( ( l = f->Read( b, size, e ) ) && !e->GetErrorCount() )
	    t->Write( b, l, e );

	// Translation errors are info, and FileSys::Close clears
	// info messages, so we need to trap those here since we
	// don't want to go ahead with a mangled file.

	if( e->GetErrorCount() )
	{
	    e->Set( MsgSupp::ConvertFailed )
		    << clientPath
		    << fromCS
		    << toCS;
	    client->OutputError( e );
	    f->Close( e );
	    t->Close( e );
	    t->Unlink( e );
	    delete f;
	    delete t;
	    return;
	}

	f->Close( e );
	t->Close( e );

	if( e->Test() )
	{
	    t->Unlink( e );
	    goto convertFileFinish;
	}

	t->Rename( f, e );
	f->Chmod( perms->Text(), e );

convertFileFinish:
	if( e->GetErrorCount() )
	{
	    e->Set( MsgSupp::ConvertFailed )
		    << clientPath
		    << fromCS
		    << toCS;
	    client->OutputError( e );
	}
	delete f;
	delete t;
}

/*
 * clientCheckFile() -- "inquire" about file, returning info to server
 *
 * This routine, for compatibility purposes, has several modes.
 *
 * 1.	If clientType is set, we know the type and we're checking to see
 *	if the file exists and (if digest is set) if the file has the same
 *	fingerprint.  We return this in "status" with a value of "missing",
 *	"exists", or "same".  This starts around version 1742.
 *
 * 2.	If clientType is unset, we're looking for the type of the file,
 *	and we'll return it in "type".  This is sort of overloaded, 'cause
 *	it can also get set with pseudo-types like "missing".  In this
 *	case, we use the "xfiles" protocol check to make sure we don't
 *	return something the server doesn't expect.
 *
 *	- files unset: return text, binary.
 * 	- files >= 0: also return xtext, xbinary.
 *	- files >= 1: also return symlink.
 *	- files >= 2; also return resource (mac resource file).  
 *	- files >= 3; also return ubinary
 *	- files >= 4; also return apple
 *
 *	If forceType is set, we'll use that in preference over what
 *	we've discovered.  We still check the file (to make sure they're
 *	not adding a directory, and so they get to right warning if
 *	they add an empty file), but we'll just override that back to
 *	the (typemap's) forceType.
 *
 * We map empty/missing/unreadable into forceType/"text".
 */

enum ctAction { 
	OK, 	// use forceType/the discovered type
	ASS, 	// missing/unreadable/empty: assume it is forceType/text
	SUBST,	// server can't handle it: substitute altType
	CHKSZ,  // size is to big for default, use alternate
	CANT 	// just can't be added
};

/*
 * Handle the varieties of the protocol.
 * Don't ever "missing", "empty", or "unreadable" 
 * Don't send "resource" unless xfiles >= 2
 * Don't send "special", "symlink", or "directory" unless xfiles >= 1
 * Don't send "xtext" or "xbinary" unless xfiles >= 0
 */

const struct ctTable {

	FileSysType 	checkType;
	int		xlevel;
	ctAction	action[2];
	const char	*type;
	const char	*altType;
	const char	*cmpType;

} checkTable[] = {
	
	FST_TEXT,	0, OK, CHKSZ,   "text", "text", "ctext", 
	FST_XTEXT,	0, SUBST, CHKSZ,"xtext", "text", "text+Cx", 
	FST_BINARY,	0, OK, OK,	"binary", "binary", 0,
	FST_XBINARY,	0, SUBST, OK,	"xbinary", "binary", 0,
	FST_APPLEFILE,	4, SUBST, OK,	"apple", "binary", 0,
	FST_XAPPLEFILE,	4, SUBST, OK,	"apple+x", "binary", 0,
	FST_CBINARY,	3, SUBST, OK,	"ubinary", "binary", 0,
	FST_SYMLINK,	1, CANT, OK,	"symlink", 0, 0,
	FST_RESOURCE,	2, CANT, OK,	"resource", 0, 0,
	FST_SPECIAL,	-1, CANT, CANT,	"special", 0, 0,
	FST_DIRECTORY,	-1, CANT, CANT,	"directory", 0, 0,
	FST_MISSING,	-1, ASS, ASS,	"missing", "text", 0,
	FST_CANTTELL,	-1, ASS, ASS,	"unreadable", "text", 0,
	FST_EMPTY,	-1, ASS, ASS,	"empty", "text", 0,
	FST_UNICODE,	5, SUBST, CHKSZ,"unicode", "text", "unicode+C",
	FST_XUNICODE,	5, SUBST, CHKSZ,"xunicode", "text","xunicode+C",
	FST_UTF16,	6, SUBST, CHKSZ,"utf16", "binary","utf16+C",
	FST_XUTF16,	6, SUBST, CHKSZ,"xutf16", "binary", "xutf16+C",
	FST_TEXT,	0, OK, OK,	0, 0, 0
} ;

void
clientCheckFile( Client *client, Error *e )
{
	client->NewHandler();
	StrPtr *clientPath = client->transfname->GetVar( P4Tag::v_path, e );
	StrPtr *clientType = client->GetVar( P4Tag::v_type );
	StrPtr *wildType = client->GetVar( P4Tag::v_type2 );
	StrPtr *forceType = client->GetVar( P4Tag::v_forceType );
	StrPtr *digest = client->GetVar( P4Tag::v_digest );
	StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );
	StrPtr *fileSize = client->GetVar( P4Tag::v_fileSize );
	StrPtr *scanSize = client->GetVar( P4Tag::v_scanSize );
	StrPtr *ignore = client->GetVar( P4Tag::v_ignore );

	if( e->Test() && !e->IsFatal() )
	{
	    client->OutputError( e );
	    return;
	}

	const char *status = "exists";
	const char *ntype = clientType ? clientType->Text() : "text";

	// For adding files,  checkSize is a maximum (or use alt type)
	// For flush,  checkSize is an optimization check on binary files.

	offL_t checkSize = fileSize ? fileSize->Atoi64() : 0;

	// 2012.1 server asks the client to do ignore checks (on add), in the
	// case of forced client type, ignore == ack, do quick confirm

	if( ignore )
	{
	    if( client->GetIgnore()->Reject( *clientPath, 
	                                     client->GetIgnoreFile() ) )
	    {
	        Error msg;
	        msg.Set( MsgClient::CheckFileCant )
		         << clientPath->Text() << "ignored";
	        client->GetUi()->Message( &msg );
	        client->SetError();
	        return;
	    }

	    // No client-type check from add,  just ack
	    // Blank confirm, just return.

	    if( *ignore == P4Tag::c_Ack )
	    {
	        if( confirm->Length() )
	            client->Confirm( confirm );
	        return;
	    }
	}

	/*
	 * Behavior is split depending on whether clientType is set.
	 */

	if( clientType )
	{
	    /*
	     * If we do know the type, we want to know if it's missing.
	     * If it isn't missing and a digest is given, we want to know if
	     * it is the same.
	     */

	    FileSys *f = ClientSvc::File( client, e );

	    if( e->Test() || !f )
		return;
	    int statVal = f->Stat();

	    if( !( statVal & ( FSF_SYMLINK|FSF_EXISTS ) ) )
	    {
		status = "missing";
	    } 
	    else if ( ( !( statVal & FSF_SYMLINK ) && ( f->IsSymlink() ) ) 
	  	    || ( ( statVal & FSF_SYMLINK ) && !( f->IsSymlink() ) ) ) 
	    {
	        ; /* do nothing */
	    }
	    else if( digest )
	    {
	        if( !checkSize || checkSize == f->GetSize() )
	        {
		    StrBuf localDigest;

                    f->Translator( ClientSvc::XCharset( client, FromClient ) );

		    f->Digest( &localDigest, e );

		    if( !e->Test() && !localDigest.XCompare( *digest ) )
		        status = "same";
	        }

		// If we can't read the file (for some reason -- wrong type?)
		// we consider the files different.

		e->Clear();
	    }

	    delete f;
	}
	else
	{
	    /*
	     * If we don't know the type, that's what we're trying to find out.
	     *
	     * This mode (!clientType) is compatible with pre-1742 servers.
	     *
	     * Set type to binary, as OS2 won't even read binary chars
	     * from a file opened as text.
	     */

	    int scan = -1;
	    if( scanSize )
		scan = scanSize->Atoi();

	    Error msg;
	    const ctTable *c;

	    FileSys *f = client->GetUi()->File( FST_BINARY );
	    f->SetContentCharSetPriv( client->ContentCharset() );
	    f->Set( *clientPath );
	    FileSysType t = f->CheckType( scan );
	    offL_t size = f->GetSize();

	    for( c = checkTable; c->type; c++ )
		if( t == c->checkType )
		    break;

	    if( !c->type )
		c = checkTable;

	    switch( c->action[ client->protocolXfiles >= c->xlevel ] )
	    {
	    case OK:
		// Use the primary type
		ntype = forceType ? forceType->Text() : c->type;
		break;

	    case CHKSZ:
		// If server sends a maximum size for file, check it
		if( forceType )
		    ntype = forceType->Text();
	        else if( fileSize && size > checkSize )
	 	    ntype = c->cmpType;
		else
	 	    ntype = c->type;
		break;

	    case ASS:
		// Use the altType, saying we're assuming it
		ntype = forceType ? forceType->Text() : c->altType;

	        if( wildType )
		    msg.Set( MsgClient::CheckFileAssumeWild ) 
		        << f->Name() << c->type << ntype << wildType;
	        else
		    msg.Set( MsgClient::CheckFileAssume ) 
		        << f->Name() << c->type << ntype;

		client->GetUi()->Message( &msg );
		break;

	    case SUBST:
		// Substitute altType for type
		ntype = c->altType;

		msg.Set( MsgClient::CheckFileSubst )
		    << f->Name() << c->altType << c->type;

		client->GetUi()->Message( &msg );
		break;

	    case CANT:
		// Just can't do it

		msg.Set( MsgClient::CheckFileCant )
		    << f->Name() << c->type;

		client->GetUi()->Message( &msg );
		client->SetError();
		delete f;
		return;
	    }

	    delete f;
	}

	// tell the server 

    // set the charset here?

	client->SetVar( P4Tag::v_type, ntype );
	client->SetVar( P4Tag::v_status, status );
	client->Confirm( confirm );

	// Report non-fatal error and clear it.

	client->OutputError( e );
}

// Integ support

void
clientActionResolve( Client *client, Error *e )
{
	// So as to be 100% translatable, action resolve gets
	// all of its strings sent as Error objects from the server.

	// should always get these
	StrPtr *actionType	= client->GetVar( P4Tag::v_rActionType, e );
	StrPtr *autoResult	= client->GetVar( P4Tag::v_rAutoResult, e );

	// should get preview or confirm/decline
	StrPtr *preview		= client->GetVar( P4Tag::v_preview );
	StrPtr *confirm		= client->GetVar( P4Tag::v_confirm );
	StrPtr *decline		= client->GetVar( P4Tag::v_decline );

	// should definitely get at least one of these
	StrPtr *actionMerge	= client->GetVar( P4Tag::v_rActionMerge );
	StrPtr *actionTheirs	= client->GetVar( P4Tag::v_rActionTheirs );
	StrPtr *actionYours	= client->GetVar( P4Tag::v_rActionYours );

	// CLI UI strings -- might not always need/get these?
	StrPtr *optAuto		= client->GetVar( P4Tag::v_rOptAuto );
	StrPtr *optHelp		= client->GetVar( P4Tag::v_rOptHelp );
	StrPtr *optMerge	= client->GetVar( P4Tag::v_rOptMerge );
	StrPtr *optSkip		= client->GetVar( P4Tag::v_rOptSkip );
	StrPtr *optTheirs	= client->GetVar( P4Tag::v_rOptTheirs );
	StrPtr *optYours	= client->GetVar( P4Tag::v_rOptYours );
	StrPtr *promptMerge	= client->GetVar( P4Tag::v_rPromptMerge );
	StrPtr *promptTheirs	= client->GetVar( P4Tag::v_rPromptTheirs );
	StrPtr *promptYours	= client->GetVar( P4Tag::v_rPromptYours );
	StrPtr *promptType	= client->GetVar( P4Tag::v_rPromptType );
	StrPtr *userError	= client->GetVar( P4Tag::v_rUserError );
	StrPtr *userHelp	= client->GetVar( P4Tag::v_rUserHelp );
	StrPtr *userPrompt	= client->GetVar( P4Tag::v_rUserPrompt );

	if ( !e->Test() && !preview && ( !confirm || !decline ) )
	    e->Set( MsgSupp::NoParm ) << "confirm/decline";

	if ( e->Test() )
	{
	    client->OutputError( e );
	    return; 
	}

	Error   mActionType, mActionMerge, mActionTheirs, mActionYours,
		mOptAuto, mOptHelp, mOptMerge, mOptSkip, mOptTheirs,
		mOptYours, mPromptMerge, mPromptTheirs,	mPromptYours, 
		mPromptType, mUserError, mUserHelp, mUserPrompt;

				mActionType.	UnMarshall2( *actionType );
	if ( actionMerge )	mActionMerge.	UnMarshall2( *actionMerge );
	if ( actionTheirs )	mActionTheirs.  UnMarshall2( *actionTheirs );
	if ( actionYours )	mActionYours.	UnMarshall2( *actionYours );
	if ( optAuto )		mOptAuto.	UnMarshall2( *optAuto );
	if ( optHelp )		mOptHelp.	UnMarshall2( *optHelp );
	if ( optMerge )		mOptMerge.	UnMarshall2( *optMerge );
	if ( optSkip )		mOptSkip.	UnMarshall2( *optSkip );
	if ( optTheirs )	mOptTheirs.	UnMarshall2( *optTheirs );
	if ( optYours )		mOptYours.	UnMarshall2( *optYours );
	if ( promptMerge )	mPromptMerge.	UnMarshall2( *promptMerge );
	if ( promptTheirs )	mPromptTheirs.	UnMarshall2( *promptTheirs );
	if ( promptYours )	mPromptYours.	UnMarshall2( *promptYours );
	if ( promptType )	mPromptType.	UnMarshall2( *promptType );
	if ( userError )	mUserError.	UnMarshall2( *userError );
	if ( userHelp )		mUserHelp.	UnMarshall2( *userHelp );
	if ( userPrompt )	mUserPrompt.	UnMarshall2( *userPrompt );

	// set up ClientResolveA object

	ClientResolveA resolve( client->GetUi() );

	MergeStatus suggest;
	if ( !autoResult )
	    suggest = CMS_SKIP;
	else if ( *autoResult == P4Tag::v_rOptTheirs )
	    suggest = CMS_THEIRS;
	else if ( *autoResult == P4Tag::v_rOptMerge )
	    suggest = CMS_MERGED;
	else if ( *autoResult == P4Tag::v_rOptYours )
	    suggest = CMS_YOURS;
	else
	    suggest = CMS_SKIP;
	resolve.SetAuto( suggest );

	resolve.SetType       ( mActionType );
	resolve.SetMergeAction( mActionMerge );
	resolve.SetTheirAction( mActionTheirs );
	resolve.SetYoursAction( mActionYours );
	resolve.SetAutoOpt    (	mOptAuto );
	resolve.SetHelpOpt    (	mOptHelp );
	resolve.SetMergeOpt   (	mOptMerge );
	resolve.SetSkipOpt    (	mOptSkip );
	resolve.SetTheirOpt   (	mOptTheirs );
	resolve.SetYoursOpt   (	mOptYours );
	resolve.SetMergePrompt( mPromptMerge );
	resolve.SetTheirPrompt( mPromptTheirs );
	resolve.SetYoursPrompt( mPromptYours );
	resolve.SetTypePrompt ( mPromptType );
	resolve.SetUsageError (	mUserError );
	resolve.SetHelp       (	mUserHelp );
	resolve.SetPrompt     (	mUserPrompt );

	// ask ClientUser to do the resolve

	int userResult = client->GetUi()->Resolve( &resolve, !!preview, e );

	if ( e->Test() )
	{
	    client->GetUi()->Message( e );
	    e->Clear();
	    userResult = CMS_QUIT;
	}

	if ( preview )
	    return;

	// back to the server with the result

	switch( userResult )
	{
	case CMS_THEIRS:
	    client->SetVar( P4Tag::v_rUserResult, P4Tag::v_rOptTheirs );
	    break;
	case CMS_YOURS:
	    client->SetVar( P4Tag::v_rUserResult, P4Tag::v_rOptYours );
	    break;
	case CMS_MERGED:
	    client->SetVar( P4Tag::v_rUserResult, P4Tag::v_rOptMerge );
	    break;
	case CMS_SKIP:
	default:
	    client->SetVar( P4Tag::v_rUserResult, P4Tag::v_rOptSkip );
	    confirm = decline;
	    break;
	}

	client->Confirm( confirm );
}

void
clientOpenMerge( Client *client, Error *e )
{
	client->NewHandler();
	StrPtr *clientPath = client->transfname->GetVar( P4Tag::v_path, e );
	StrPtr *clientHandle = client->GetVar( P4Tag::v_handle, e );
	StrPtr *func = client->GetVar( P4Tag::v_func, e );
	StrPtr *clientType = client->GetVar( P4Tag::v_type );
	StrPtr *resultType = client->GetVar( P4Tag::v_type2 );
	StrPtr *theirType = client->GetVar( P4Tag::v_type3 );
	StrPtr *baseType = client->GetVar( P4Tag::v_type4 );
	StrPtr *showAll = client->GetVar( P4Tag::v_showAll );
	StrPtr *diffFlags = client->GetVar( P4Tag::v_diffFlags );
	StrPtr *noBase = client->GetVar( P4Tag::v_noBase );
	StrPtr *digest = client->GetVar( P4Tag::v_digest );

	FileSys *s = ClientSvc::File( client, e ); // For P4CLIENTPATH verification

	if( e->Test() || !s )
	{
	    delete s;
	    if ( !e->IsFatal() )
		client->OutputError( e );
	    return;
	}

	delete s;
	s = 0;

	// very old servers do not send result type
	if( !resultType )
	    resultType = clientType;

	// old servers do not send theirs/base types
	if ( !theirType )
	    theirType = resultType;
	if ( !baseType )
	    baseType = clientType;

	// Create integrator
	// Note that noBase comes from pre-12354 servers.

	MergeType mt;

	if( !strcmp( func->Text(), P4Tag::c_OpenMerge2 ) )
	    mt = CMT_BINARY;
	else if( noBase )
	    mt = CMT_2WAY;
	else
	    mt = CMT_3WAY;

	// Set binary/text/etc file type - mostly for exec bit

	FileSysType type = LookupType( clientType );
	FileSysType rType = LookupType( resultType );
	FileSysType tType = LookupType( theirType );
	FileSysType bType = LookupType( baseType );
	ClientMerge *merge = ClientMerge::Create
		( client->GetUi(), type, rType, tType, bType, mt );

	// Pass on showAll/diff flag.

	if( showAll )
	    merge->SetShowAll();

	if( diffFlags )
	    merge->SetDiffFlags( diffFlags );

	// Save digest for binary resolve, 2003.1 sends base digest.

	if( client->protocolServer >= 16 && digest )
	    merge->CopyDigest( digest, e );

	// Create handle
	// If that fails, we have to toss the integrator

	client->handles.Install( clientHandle, merge, e );

	if( e->Test() )
	{
	    delete merge;
	    return;
	}

	// Start integration.
	// If that fails, we mark the handle so the rest of the
	// protocol is honored (but ignored).

	// You would think these names should be translated
	// but not really... These names are used as markers
	// in the generation of merge files so we actually
	// want these in UTF-8 because the merge code will
	// translated them if the type is unicode.
	// If the file type is text then we do want these
	// translated...  This is really confusing...  Sorry...
	StrDict *usedict = client;
	if( ( type & FST_MASK ) != FST_UNICODE )
	    usedict = client->transfname;
	merge->SetNames( 
		usedict->GetVar( P4Tag::v_baseName ),
		usedict->GetVar( P4Tag::v_theirName ),
		usedict->GetVar( P4Tag::v_yourName ) );

	merge->Open( clientPath, e, ClientSvc::XCharset( client, FromServer ),
			client->ContentCharset() );

	// Mark handle with any error
	// Report non-fatal error and clear it.

	merge->SetError( e );
	client->OutputError( e );
}

void
clientWriteMerge( Client *client, Error *e )
{
	StrPtr *clientHandle = client->GetVar( P4Tag::v_handle, e );
	StrPtr *data = client->GetVar( P4Tag::v_data, e );
	StrPtr *bits = client->GetVar( P4Tag::v_bits );
	ClientMerge *merge;

	if( e->Test() )
	    return;

	// Get handle.

	merge = (ClientMerge *)client->handles.Get( clientHandle, e );

	if( e->Test() || merge->IsError() )
	    return;

	// Write data.

	merge->Write( data, bits, e );

	// Mark handle with any error
	// Report non-fatal error and clear it.

	merge->SetError( e );
	client->OutputError( e );
}

void
clientCloseMerge( Client *client, Error *e )
{
	StrPtr *clientHandle = client->GetVar( P4Tag::v_handle, e );
	StrPtr *mergeConfirm = client->GetVar( P4Tag::v_mergeConfirm );
	StrPtr *mergeDecline = client->GetVar( P4Tag::v_mergeDecline );
	StrPtr *mergePerms = client->GetVar( P4Tag::v_mergePerms );
	StrPtr *mergeAuto = client->GetVar( P4Tag::v_mergeAuto );
	const StrPtr *resultDigest;
	ClientMerge *merge;
	int manualMerge = 0;

	if( e->Test() )
	    return;

	// Get handle.

	merge = (ClientMerge *)client->handles.Get( clientHandle, e );

	if( e->Test() )
	    return;

	// Close & resolve.

	merge->Close( e );

	// Mark handle with any error

	merge->SetError( e );

	// Accept or decline integration depending on what Resolve() said.

	// If mergeConfirm is null, it means sender detected error and
	// doesn't want us to proceed.

	if( merge->IsError() )
	{
	    mergeConfirm = mergeDecline;
	}
	else while( mergeConfirm )
	{
	    MergeStatus stat;

	    // Make user's file writeable for the duration of the resolve.
	    // We only do this if mergePerms is set for two reasons: 1) then
	    // we can be sure to revert the file to the proper perms below,
	    // 2) only servers which set mergePerms are smart enough to leave
	    // files opened for integ r/o to begin with.

	    if( mergePerms )
		merge->Chmod( "rw", e );

	    // Do automatic merge if requested.
	    // 'auto' means skip conflicts.
	    // 'force' means accept conflicts.

	    if( mergeAuto && *mergeAuto == "safe" )
	    {
		stat = merge->AutoResolve( CMF_SAFE );
	    }
	    else if( mergeAuto && *mergeAuto == "force" )
	    {
		stat = merge->AutoResolve( CMF_FORCE );
	    }
	    else if( mergeAuto && *mergeAuto == "auto" )
	    {
		stat = merge->AutoResolve( CMF_AUTO );
	    }
	    else // manual merge
	    {
		stat = (MergeStatus)client->GetUi()->Resolve( merge, e );
	        manualMerge = 1;
	    }

	    // server2 < 11 can't take CMS_EDIT.

	    if( stat == CMS_EDIT && client->protocolServer < 11 )
		stat = CMS_MERGED;

	    // Formulate response to server according to user's action.
	    // If the target file was updated

	    switch( stat )
	    {
	    case CMS_QUIT:	// user wants to quit
		mergeConfirm = mergeDecline;
		break;

	    case CMS_SKIP:	// skip the integration record
		mergeConfirm = mergeDecline;
		break;

	    case CMS_MERGED: // accepted merged theirs and yours
		resultDigest = merge->GetMergeDigest();
		if( resultDigest )
		    client->SetVar( P4Tag::v_digest, resultDigest );
		client->SetVar( P4Tag::v_mergeHow, "merged" );
		break;

	    case CMS_EDIT: // accepted edited merge
		client->SetVar( P4Tag::v_mergeHow, "edit" );
		break;

	    case CMS_THEIRS: // accepted theirs
	    {
		resultDigest = merge->GetTheirDigest();
		if( resultDigest )
		    client->SetVar( P4Tag::v_digest, resultDigest );
		client->SetVar( P4Tag::v_mergeHow, "theirs" );
		const char *forced = "no";
		if( merge->GetYourChunks() > 0 ||
		    merge->GetConflictChunks() > 0 )
		    forced = "yes";
		else if( merge->GetTheirChunks() > 0 )
		    forced = "theirs";
		client->SetVar( P4Tag::v_force, forced );
		break;
	    }
	    case CMS_YOURS:	// accepted yours
		resultDigest = merge->GetYourDigest();
		if( resultDigest )
		    client->SetVar( P4Tag::v_digest, resultDigest );
		client->SetVar( P4Tag::v_mergeHow, "yours" );
		break;
	    }

	    // Move selected file into position

	    if( !e->Test() )
		merge->Select( stat, e );

	    // Rename could fail because yours file is busy, retry

	    if( e->Test() && manualMerge && mergeConfirm != mergeDecline )
	    {
	        client->RemoveVar( P4Tag::v_mergeHow );
	        client->OutputError( e );
	        e->Clear();
	        continue;
	    }

	    // Revert permissions

	    if( !e->Test() && mergePerms )
		merge->Chmod( mergePerms->Text(), e );

	    // bail if errors in select or chmod

	    if( e->Test() )
		mergeConfirm = mergeDecline;

	    break;
	}

	// Confirm resolve.

	if( mergeConfirm )
	    client->Confirm( mergeConfirm );

	// Handle remembers if any error occurred 
	// Report non-fatal error and clear it.

	merge->SetError( e );
	client->OutputError( e );

	delete merge;
}

void
clientSendFile( Client *client, Error *e )
{
	client->NewHandler();
	StrPtr *clientPath = client->transfname->GetVar( P4Tag::v_path, e );
	StrPtr *clientType = client->GetVar( P4Tag::v_type );
	StrPtr *perms = client->GetVar( P4Tag::v_perms );
	StrPtr *handle = client->GetVar( P4Tag::v_handle, e );
	StrPtr *open = client->GetVar( P4Tag::v_open, e );
	StrPtr *write = client->GetVar( P4Tag::v_write, e );
	StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );
	StrPtr *decline = client->GetVar( P4Tag::v_decline, e );
	StrPtr *serverDigest = client->GetVar( "serverDigest" );
	StrPtr *pendingDigest = client->GetVar( "pendingDigest" );
	StrPtr *revertUnchanged = client->GetVar( P4Tag::v_revertUnchanged );
	StrPtr *reopen = client->GetVar( P4Tag::v_reopen );
	StrPtr *skipDigestCheck = client->GetVar( "skipDigestCheck" );

	if( e->Test() && !e->IsFatal() )
	{
	    client->OutputError( e );
	    return;
	}

	FileSys *f = ClientSvc::File( client, e );

	if( e->Test() )
	    return;

	if( !f )
	{
	    client->Confirm( open );
	    client->Confirm( decline );
	    return;
	}

	// 2014.2 submit --forcenoretransfer skips file transfer to call 
	// dmsubmitfile directly, only fixing up file perms as needed.

	if( skipDigestCheck )
	{
	    client->SetVar( P4Tag::v_status, "same" );
	    client->SetVar( P4Tag::v_digest, skipDigestCheck );
	    client->Confirm( confirm );

	    // Ignore failure to chmod file since the file may not exist

	    Error te;
	    if( !e->Test() && perms && revertUnchanged )
		f->Chmod2( perms->Text(), &te );
	    delete f;
	    return;
	}

	// 2000.1 started sending modTime
	// 2003.2 wants a digest
	// 2005.1 sends filesize

	int modTime = f->StatModTime();
	int sendDigest = ( client->protocolServer >= 17 );
	int sendFileSize = ( client->protocolServer >= 19 );

	StrBuf digest;
	offL_t len = 0;
	offL_t filesize = 0;
	MD5 md5;

	// 2006.2 server, sends digest for 'p4 submit -R'
	// 2010.2 server @257282 sends digest for 'p4 resolve' of integ
	// 2014.1 server sends digest for shelve -a leaveunchanged

	if( serverDigest || pendingDigest )
	{
	    StrBuf localDigest;
	    f->Translator( ClientSvc::XCharset( client, FromClient ) );
	    f->Digest( &localDigest, e );

	    if( !e->Test() && ( ( serverDigest &&
				  !localDigest.XCompare( *serverDigest ) ) ||
			        ( pendingDigest &&
				  !localDigest.XCompare( *pendingDigest ) ) ) )
	    {
	        client->SetVar( P4Tag::v_status, "same" );
	        client->SetVar( P4Tag::v_digest, &localDigest );
	        client->Confirm( confirm );
	        if( !e->Test() && perms && revertUnchanged )
	            f->Chmod2( perms->Text(), e );
	        delete f;
	        return;
	    }
	}

	// pre-2003.2 server, send modTime on open

	if( modTime && !sendDigest )
	    client->SetVar( P4Tag::v_time, modTime );

	// open source file, even if file open fails.

	f->Open( FOM_READ, e );

	if( !e->Test() )
	{
	    // This is just an estimate. The actual file size is computed below

	    filesize = f->GetSize();
	    client->SetVar( P4Tag::v_fileSize, StrNum( filesize ) );
	}

	// Send open (with modTime, and possibly estimated file size)

	client->Confirm( open );

	int size = FileSys::BufferSize();

	ProgressReport *progress = NULL;

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

	f->Translator( ClientSvc::XCharset( client, FromClient ) );

	ClientProgress *indicator;

	if( indicator = client->GetUi()->CreateProgress( CPT_SENDFILE ) )
	{
	    progress = new ClientProgressReport( indicator );
	    progress->Description( *clientPath );
	    progress->Units( CPU_KBYTES );
	    progress->Total( filesize / 1024 );
	}

	// send data, as long as no rpc error

	while( !client->Dropped() )
	{
		StrBuf *bu = client->MakeVar( P4Tag::v_data );
		char *b = bu->Alloc( size );
		int l = f->Read( b, size, e );

		if( e->Test() )
		{
		    if( progress )
			progress->Increment( 0, CPP_FAILDONE );
		    bu->SetEnd( b );
		    break;
		}

# ifdef USE_EBCDIC
		// Pre un-Translate!
		if( !f->IsTextual() )
		    __atoe_l( b, l );
# endif

		bu->SetEnd( b + l );

		len += l;

		if( progress )
		    if( l )
			progress->Position( len / 1024, CPP_NORMAL );
		    else
			progress->Position( filesize / 1024, CPP_DONE );

		if( !l )
		    break;

		if( sendDigest )
		{
#ifdef USE_EBCDIC
		    __etoa_l( b, l );
#endif
		    md5.Update( StrRef( b, l) );
#ifdef USE_EBCDIC
		    __atoe_l( b, l );
#endif
		}

		client->SetVar( P4Tag::v_handle, handle );
		client->Invoke( write->Text() );
	}

	f->Close( e );

	// Chmod according to perms.
	// 2000.1 server does this to avoid a separate 
	// client-ChmodFile call.
	// 2007.2 server will send reopen flag, because it had to
	// send the permissions bit for revertUnchanged.

	if( !e->Test() && perms && !reopen )
	    f->Chmod2( perms->Text(), e );

    bail:
	// close it down
	// On any failure we'll send the decline so that the server
	// knows something went wrong.

	delete f;
	if( progress )
	{
	    delete progress;
	    delete indicator;
	}

	if( sendDigest )
	{
	    // 2005.1 server, send filesize

	    if( sendFileSize )
	        client->SetVar( P4Tag::v_fileSize, StrNum( len ) );

	    // 2003.2 server, send digest and modTime together

	    md5.Final( digest );
	    client->SetVar( P4Tag::v_digest, &digest );

	    if( modTime )
	        client->SetVar( P4Tag::v_time, modTime );
	}

	client->Confirm( e->Test() ? decline : confirm );

	// Report non-fatal error and clear it.

	client->OutputError( e );
}

// Spec support

void
clientEditData( Client *client, Error *e )
{
	StrPtr *spec = client->GetVar( P4Tag::v_data, e );
	StrPtr *confirm = client->GetVar( P4Tag::v_confirm );
	StrPtr *decline = client->GetVar( P4Tag::v_decline );
	StrPtr *compare = client->GetVar( P4Tag::v_compare );
	StrBuf newSpec;

	if( e->Test() )
	    return;

	// this does the work of FileSys::CreateGlobalTemp but
	// uses the client user object's FileSys create method
	FileSys *f = client->GetUi()->File( FST_UNICODE );
	f->SetContentCharSetPriv( client->content_charset );
	f->SetDeleteOnClose();
	f->MakeGlobalTemp();

	if( confirm )
	    f->Perms( FPM_RW );

	/* Set different translators between write/read */
	f->Translator( client->fromTransDialog );

	if( !e->Test() )	f->WriteFile( spec, e );
	if( !e->Test() )	client->GetUi()->Edit( f, e );

	f->Translator( client->toTransDialog );

	if( !e->Test() )	f->ReadFile( &newSpec, e );

	delete f;

	if( e->Test() )
	    confirm = decline;

	// send the confirmation

	if( confirm )
	{
	    // If asked to compare old vs new, do so.

	    if( compare )
		client->SetVar( P4Tag::v_compare, newSpec == *spec ? "same" : "diff" );

	    client->SetVar( P4Tag::v_data, &newSpec );

	    client->Confirm( confirm );
	}

	// Report non-fatal error and clear it.

	client->OutputError( e );
}

void
clientInputData( Client *client, Error *e )
{
	client->NewHandler();
	StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );
	StrBuf newSpec;

	client->GetUi()->InputData( &newSpec, e );

	// send the confirmation

	client->translated->SetVar( P4Tag::v_data, &newSpec );

	client->Confirm( confirm );
}


//
// clientPrompt
// clientErrorPause
// clientHandleError
// clientOutputError
// clientOutputText
// clientOutputInfo
// clientOutputData
// 

void
clientPrompt( Client *client, Error *e )
{
	client->NewHandler();
	StrPtr *data = client->translated->GetVar( P4Tag::v_data, e );
	StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );
	StrPtr *truncate = client->GetVar( P4Tag::v_truncate );
	StrPtr *func = client->GetVar( P4Tag::v_func );
	StrPtr *noecho = client->GetVar( P4Tag::v_noecho );
	StrPtr *noprompt = client->GetVar( P4Tag::v_noprompt );
	StrPtr *digest = client->GetVar( P4Tag::v_digest );
	StrPtr *mangle = client->GetVar( P4Tag::v_mangle );
	StrPtr *user = client->GetVar( P4Tag::v_user );
	StrBuf resp;

	if( e->Test() )
	{
	    if ( !e->IsFatal() )
		client->OutputError( e );
	    return;
	}

	if( noprompt )
	    resp = *client->GetPBuf();
	else
	    client->GetUi()->Prompt( *data, resp, noecho != 0, e );
	client->SetPBuf( resp );

	if( e->Test() )
	    return;

	// if digest set, we'll send the md5 of the response
	// otherwise, we'll send the raw thing.
	// The server accepts an md5 of the password in leiu
	// of the actual thing.

	// New to 2003.2:
	//
	// Depending on the server security level the server can
	// set the mangle variable to indicate that the return value
	// should be encrypted and not hashed.
	//
	// For added security when asked for the old password the
	// digest string will now contain a token which the password
	// md5 should be md5'd with.
	// 
	// Currently unicode clients will not be able to have their
	// password complexity checked.  Because of limitations on the
	// mangle algorithm input to digest or mangle will be truncated
	// to 16 characters.
	// 
	// note: scrambling the ticket does not provide robust security.
	// Sites with strong security requirement should use SSL support
	// added in 2012.1

	if( ( mangle || digest ) && resp.Length() )
	{
	    MD5 md5;
# ifdef USE_EBCDIC
	    __etoa_l( data->Text(), data->Length() );
	    __etoa_l( resp.Text(), resp.Length() );
# endif
	    if( client != client->translated )
	    {
		int newl = 0;
		const char *t = ((TransDict *)client->translated)->ToCvt()->
		    FastCvt( resp.Text(), resp.Length(), &newl );
		if (t)
		    resp.Set(t, newl);
	    }

	    // truncate to 16 characters

	    if( truncate && resp.Length() > 16 )
	        resp.SetLength( 16 );

	    if( digest )
	    {
	        md5.Update( resp );
	        md5.Final( resp );
#ifdef USE_EBCDIC
		// Back to ascii for redigestion.
		__etoa_l( resp.Text(), resp.Length() );
#endif

		// 2005.2 server can use the oldpassword MD5 as part of
		// the encryption key, only the client and server will
		// know this.

		if( client->protocolServer >= 20 )
		    client->SetSecretKey( resp );

	        // 2003.2 server will send a hashed token to avoid
	        // putting on the wire a string that can be used by
	        // an old client to authenticate.

	        if( digest->Length() )
	        {
	            MD5 md5;

#ifdef USE_EBCDIC
		    // To ASCII for digesting
		    __etoa_l( digest->Text(), digest->Length() );
#endif
	            md5.Update( resp );
	            md5.Update( *digest );
	            md5.Final( resp );
#ifdef USE_EBCDIC
		    // XXX: The digest is in ASCII, but when it gets sent back 
		    // to the server, we need it in EBCDIC so that the RPC 
		    // layer can convert it back to ASCII.
		    __atoe_l( digest->Text(), digest->Length() );
#endif
	        }

		StrPtr *daddr = client->GetPeerAddress( RAF_PORT );

		if( daddr )
		{
		    client->SetVar( P4Tag::v_daddr, *daddr );

		    if( client->protocolServer >= 29 )
		    {
			MD5 md5;

			if( daddr )
			{
			    md5.Update( resp );
			    md5.Update( *daddr );
			    md5.Final( resp );
			}
		    }
		}

	        client->SetVar( P4Tag::v_data, resp );
	    }
	    else
	    {
	        // Create a key which the server can duplicate to unmangle
	        // the password:

	        Mangle m;
	        StrBuf key;                
		StrPtr *secretKey=0;
	        StrPtr *data = &resp;

		if( client->protocolServer >= 20 )
		    secretKey = client->GetSecretKey();

#ifdef USE_EBCDIC
		__etoa_l( mangle->Text(), mangle->Length() );
		__etoa_l( user->Text(), user->Length() );
#endif
	        md5.Update( *mangle );               
		md5.Update( *user );     

#ifdef USE_EBCDIC
		__atoe_l( mangle->Text(), mangle->Length() );
		__atoe_l( user->Text(), user->Length() );
#endif
		// 2005.2 add salt to the key

		if( secretKey && secretKey->Length() )
		{
		    md5.Update( *secretKey );
		    if( client->GetVar( P4Tag::v_data2 ) )
		        client->ClearSecretKey();
		}

		md5.Final( key );                    
#ifdef USE_EBCDIC
		__etoa_l( key.Text(), key.Length() );
#endif
	        mangle = &key;

	        m.In( *data, *mangle, key, e );     

	        if( e->Test() )
	            return;

	        client->SetVar( P4Tag::v_data, key );
	    }
	}
	else
	{
	    // Drop support for really old password/login exchange

	    StrBuf promptStr;
	    promptStr << *data;
	    StrOps::Lower( promptStr );

	    if( resp.Length() && 
	      ( noecho || promptStr.Contains( StrRef( "pass" ) ) ) )
	    {
	        MD5 md5;
	        md5.Update( resp );
	        md5.Final( resp );
	    }

	    client->translated->SetVar( P4Tag::v_data, resp );
	}

	client->Confirm( confirm );
}

void
clientSyncTrigger( Client *client, Error *e )
{
	StrPtr *syncClient = client->GetVar( "zerosync", e );

	if( e->Test() )
	{
	    if ( !e->IsFatal() )
		client->OutputError( e );
	    return;
	}

	const StrPtr *syncTrigger = &client->GetSyncTrigger();

	if( !strcmp( syncTrigger->Text(), "unset" ) )
	    return;

	// execute syncUpdater command   (shiv trigger)

	RunCommandIo *rc = new RunCommandIo;
	StrBuf result;

	RunArgs cmd;
	StrOps::Expand( cmd.SetBuf(), *syncTrigger, *client );

	rc->Run( cmd, result, e );
	delete rc;
}

void
clientSingleSignon( Client *client, Error *e )
{
	StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );

	if( e->Test() )
	{
	    if ( !e->IsFatal() )
		client->OutputError( e );
	    return;
	}

	const StrPtr *loginSSO = &client->GetLoginSSO();

	if( !strcmp( loginSSO->Text(), "unset" ) )
	{
	    client->SetVar( P4Tag::v_status, "unset" );
	    client->SetVar( P4Tag::v_sso );
	}
	else
	{
	    // execute P4LOGINSSO command

	    RunCommandIo *rc = new RunCommandIo;
	    StrBuf result;

	    RunArgs cmd;

	    StrBufDict SSODict;
	    StrRef var, val;

	    for( int i = 0; client->GetVar( i, var, val ); i++ )
	        SSODict.SetVar( var, val );

	    // Allow the trigger to know if it's talking through a broker or proxy
	    // since the serverAddress will be different in this case.

	    SSODict.SetVar( "P4PORT", client->GetPort() );

	    StrOps::Expand( cmd.SetBuf(), *loginSSO, SSODict );

	    if( rc->Run( cmd, result, e ) || e->Test() )
	        client->SetVar( P4Tag::v_status, "fail" );
	    else
	        client->SetVar( P4Tag::v_status, "pass" );

	    // truncate to 128K max length

	    if( result.Length() > SSOMAXLENGTH )
	    {
	        result.SetLength( SSOMAXLENGTH );
	        result.Terminate();
	    }

	    client->SetVar( P4Tag::v_sso, result );
	    delete rc;
	}

	client->Confirm( confirm );
}

void
clientErrorPause( Client *client, Error *e )
{
	client->NewHandler();
	StrPtr *data = client->translated->GetVar( P4Tag::v_data, e );

	if( e->Test() )
	    return;

	client->GetUi()->ErrorPause( data->Text(), e );
}

void
clientHandleError( Client *client, Error *e )
{
	client->NewHandler();
	StrPtr *data = client->translated->GetVar( P4Tag::v_data, e );

	if( e->Test() )
	{
	    if ( !e->IsFatal() )
		client->OutputError( e );
	    return;
	}

	// Unmarshall error object

	Error rcvErr;

	rcvErr.UnMarshall0( *data );

	if( rcvErr.IsError() )
	    client->SetError();

	client->GetUi()->HandleError( &rcvErr );
	client->ClearPBuf();
}

void
clientMessage( Client *client, Error *e )
{
	client->NewHandler();

	/* Unpack the error and hand it to the user. */

	Error rcvErr;
	StrDict *errorDict = client;
	if( client != client->translated )
	{
	    errorDict = ((TransDict *)client->translated)
		->CreateErrorOutputDict();
	}
	rcvErr.UnMarshall1( *errorDict );

	if( rcvErr.IsError() )
	    client->SetError();

	client->GetUi()->Message( &rcvErr );

	// MsgDm::DomainSave
	ErrorId clientUpdate = { ErrorOf( 6, 6370, 1, 0, 2 ), "" };

	// zerosync client shiv trigger

	if( rcvErr.CheckId( clientUpdate ) )
	{
	    StrPtr *syncClient = client->GetVar( "zerosync" );

	    if( syncClient )
	    {
	        Error te;  //ignore error
	        clientSyncTrigger( client, &te );

	        if( te.Test() )
	            client->GetUi()->Message( &te );
	    }
	}

	if( errorDict != client )
	    delete errorDict;
}

void
clientOutputError( Client *client, Error *e )
{
	client->NewHandler();
	StrPtr *data = client->translated->GetVar( P4Tag::v_data, e );
	StrPtr *warn = client->GetVar( P4Tag::v_warning );

	// Note error so we can adjust exit()

	if( !warn )
	    client->SetError();

	if( e->Test() )
	{
	    if ( !e->IsFatal() )
		client->OutputError( e );
	    return;
	}

	client->GetUi()->OutputError( data->Text() );
}

void
clientOutputInfo( Client *client, Error *e )
{
	client->NewHandler();
	StrPtr *data = client->translated->GetVar( P4Tag::v_data, e );
	StrPtr *level = client->GetVar( P4Tag::v_level );
	int lev = level ? *level->Text() : '0';

	if( e->Test() )
	{
	    if ( !e->IsFatal() )
		client->OutputError( e );
	    return;
	}

	client->GetUi()->OutputInfo( lev, data->Text() );
}

void
clientOutputText( Client *client, Error *e )
{
	client->NewHandler();
	StrPtr *trans = client->GetVar( P4Tag::v_trans );
	StrPtr *data;

	if ( trans && *trans == "no" )
	    data = client->GetVar( P4Tag::v_data, e );
        else
	    data = client->translated->GetVar( P4Tag::v_data, e );

	if( e->Test() )
	{
	    if ( !e->IsFatal() )
		client->OutputError( e );
	    return;
	}

	client->GetUi()->OutputText( data->Text(), data->Length() );
}

void
clientOutputBinary( Client *client, Error *e )
{
	StrPtr *data = client->GetVar( P4Tag::v_data, e );

	if( e->Test() )
	    return;

	client->GetUi()->OutputBinary( data->Text(), data->Length() );
}


void
clientProgress( Client *client, Error *e )
{
	client->NewHandler();

	StrPtr *handle = client->GetVar( P4Tag::v_handle, e );

	if( e->Test() )
	    return;

	ProgressHandle *progh = (ProgressHandle *)client->handles.Get( handle );
	ClientProgress *prog;

	if( progh )
	    prog = progh->progress;
	else
	{
	    prog = client->GetUi()->CreateProgress(
		client->GetVar( "type" )->Atoi() );
	    if( !prog )
		return;
	}
	StrPtr *val;

	val = client->GetVar( "desc" );
	if( val )
	    prog->Description( val, client->GetVar( "units" )->Atoi() );

	val = client->GetVar( "total" );
	if( val )
	    prog->Total( val->Atoi() );

	val = client->GetVar( "update" );
	if( val )
	    prog->Update( val->Atoi() ); // cancel error XXX

	val = client->GetVar( "done" );
	if( val )
	{
	    prog->Done( val->Atoi() ? CPP_FAILDONE : CPP_DONE );
	    if( progh )
		delete progh;
	    else
		delete prog;
	}
	else if( !progh )
	    client->handles.Install( handle, new ProgressHandle( prog ), e );
}

//
// clientFstatInfo
//

void
clientFstatInfo( Client *client, Error *e )
{
	// Rpc has a StrDict interface

	client->NewHandler();
	// XXX hmmm... since we potentially have different translations
	// which one should we choose
	client->GetUi()->OutputStat( client->translated );
}

//
// clientAck
//

void
clientAck( Client *client, Error *e )
{
	StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );
	StrPtr *decline = client->GetVar( P4Tag::v_decline );
	StrPtr *handle = client->GetVar( P4Tag::v_handle ); 

	if( e->Test() )
	    return;

	// Ack decline if handle indicates failure.
	// Ack confirm if no handle or handle shows success.

	if( handle && client->handles.AnyErrors( handle ) )
	    confirm = decline;

	if( confirm )
	    client->Confirm( confirm );
}


//
void
clientPing( Client *client, Error *e )
{
	StrPtr *payloadSize = client->GetVar( P4Tag::v_fileSize);
	StrPtr *timer = client->GetVar( P4Tag::v_time );
	StrPtr *clientMessageBuf = client->GetVar( P4Tag::v_fileSize );
	StrPtr *serverMessageBuf = client->GetVar( P4Tag::v_value );
        StrPtr *acksBuf = client->GetVar( P4Tag::v_blockCount );
        StrPtr *secsBuf = client->GetVar( P4Tag::v_token );
        StrPtr *taggedFlag = client->GetVar( P4Tag::v_tag );

	if( e->Test() )
	    return;

	if ( payloadSize ) 
	{
	    int size = payloadSize->Atoi();
	    if( size > 1000000 )
	        size = 1000000;

	    StrBuf sbuf;
	    sbuf.Alloc( size );
	    sbuf.Fill( "b" );
	    sbuf.Terminate();
	    client->SetVar( P4Tag::v_desc, sbuf );
	}
	client->SetVar( P4Tag::v_fileSize, clientMessageBuf );
	client->SetVar( P4Tag::v_value, serverMessageBuf );
	client->SetVar( P4Tag::v_blockCount, acksBuf );
	client->SetVar( P4Tag::v_token, secsBuf );
	client->SetVar( P4Tag::v_tag, taggedFlag );
	if ( timer )
	    client->SetVar(  P4Tag::v_time, timer );
	client->Invoke( "dm-Ping" );
}


//
// clientCrypto - encrypt the string with the user's password
//

void
clientCrypto( Client *client, Error *e )
{
	StrPtr *confirm = client->GetVar( P4Tag::v_confirm, e );
	StrPtr *token = client->GetVar( P4Tag::v_token, e );
	StrPtr *truncate = client->GetVar( P4Tag::v_truncate );
	StrPtr *serverID = client->GetVar( P4Tag::v_serverAddress );

	if( e->Test() )
	    return;

	// Set the normalized server address (2007.2) 

	client->SetServerID( serverID ? serverID->Text() : "" );

	StrPtr *daddr = client->GetPeerAddress( RAF_PORT );
	if( daddr )
	    client->SetVar( P4Tag::v_daddr, *daddr );

	// If no password, just send back an empty string
	// If a password, send the digest of the token and digested password.
	// Yes, the password itself must be digested first.

	StrBuf result;
	const StrPtr &password = client->GetPassword();
	const StrPtr &password2 = client->GetPassword2();

	if( !password.Length() )
	{
	    client->SetVar( P4Tag::v_token, &result );
	    client->Invoke( confirm->Text() );
	    return;
	}

	// send 2 passwords (token,token2) if both ticket and P4PASSWD
	// are set (2007.2).

	int max = password2.Length() ? 2 : 1;

	for( int i = 0; i < max; ++i )
	{
	    // Digest of password
	    // If password already digested, use it straight.

	    result = ( i == 0 ) ? password : password2;

	    if( !StrOps::IsDigest( result ) )
	    {
		MD5 md5;
# ifdef USE_EBCDIC
		__etoa_l( result.Text(), result.Length() );
# endif
		if( client != client->translated )
		{
		    int newl = 0;
		    const char *t = ((TransDict *)client->translated)->
			ToCvt()->FastCvt( result.Text(),
					  result.Length(), &newl );
		    if (t)
			result.Set(t, newl);
		}
		else
		{
		    // New to 2003.2, truncate password to 16 characters,
		    // if password was strength checked.

		    if( truncate && result.Length() > 16 )
		        result.SetLength( 16 );
		}

		md5.Update( result );
		md5.Final( result );
	    }

	    // Digest of token and digested password.

	    {
		MD5 md5;

# ifdef USE_EBCDIC
		__etoa_l( token->Text(), token->Length() );
		__etoa_l( result.Text(), result.Length() );
# endif

		md5.Update( *token );
		md5.Update( result );
		md5.Final( result );
	    }

	    if( client->protocolServer >= 29 )
	    {
		// 2010.1 or later...

		if( daddr )
		{
		    MD5 md5;

		    md5.Update( result );
		    md5.Update( *daddr );
		    md5.Final( result );
		}
	    }

	    if( i == 0 ) client->SetVar( P4Tag::v_token, &result );
	            else client->SetVar( P4Tag::v_token2, &result );
	}

	client->Invoke( confirm->Text() );
}

//
// clientSetPassword - set the password on the client
//

void
clientSetPassword( Client *client, Error *e )
{
	client->NewHandler();
	StrPtr *data = client->GetVar( P4Tag::v_data, e );
	StrPtr *serverID = client->GetVar( P4Tag::v_serverAddress );
	StrPtr *noprompt = client->GetVar( P4Tag::v_noprompt );
	StrPtr *func = client->GetVar( P4Tag::v_func );

	if( e->Test() )
	    return;

	// In 2004.1 'p4 login' will also send the username along
	// this is required when a user is impersonating another 
	// through the P4USER or -u options.  Since this has come
	// from the server and is used as a key to replace/delete 
	// a ticket in the ticketfile, we must translate it.

	StrPtr *user = client->GetVar( P4Tag::v_user );
	int sameUser = !user || !user->Compare( client->GetUser() );

	// In 2003.2 the new command 'p4 login' also calls
	// SetPassword,  check to see if v_data2 has been set
	// to show that this is a callback from 'login' and 
	// not password.

	StrPtr *data2 = client->GetVar( P4Tag::v_data2 );

	// Its possible they have a 3.2 server and 4.1 client, instead
	// of spitting out the instructions on how to set configuration
	// environment we use "******" to represent any user.

	StrRef userWild( "******" );
	StrBuf ticket;
	StrBuf u;

	// In 2010.2 tickets are XOR'd with encrypted tokens

	StrPtr *token = client->GetVar( P4Tag::v_digest ); 

	if( token )
	{
	    StrBuf secretKey;
	    StrBuf token2;
	    Mangle sk;

	    if( sameUser && client->GetSecretKey()->Length())
	    {
	        secretKey << client->GetSecretKey();
	    }
	    else
	    {
	        secretKey << client->GetPassword();
	        if( !StrOps::IsDigest( secretKey ) )
	        {
	            MD5 md5;
		    md5.Update( secretKey );
		    md5.Final( secretKey );
	        }
	    }

	    ticket << data;

	    sk.InMD5( *token, secretKey, token2, e );
	    sk.XOR( ticket, token2, e );

	    if( e->Test() )
	        return;

	    data = &ticket;
	}

	// Clear secret key - cms bug

	client->ClearSecretKey();

	if( noprompt )
	    client->ClearPBuf();

	if( client->GetVar( P4Tag::v_output ) ) // print
	{
	    Error msg;
	    msg.Set( MsgClient::LoginPrintTicket ) << data;
	    client->GetUi()->Message( &msg );
	    return;
	}
	
	// Set the password

	if( sameUser )
	   client->SetPassword( data->Text() );

	// Downcase the username when setting/updating the ticket file
	// from a case insensitive server.

	if( user && client->protocolNocase )
	{
	    u = *user;
	    StrOps::Lower( u );
	    user = &u;
	}

	if( !user )
	    user = &userWild;

	// 2007.2 server sends the serverAddress as the identifier which
	// can be used as the key to find the ticket file entry.
	// If the server issues a "logout" then also clean up any other
	// entries for this server by also logging out with the old
	// key P4PORT.

	if( data2 && !strcmp( data2->Text(), "login" ) )
	{
	    Ticket t( &client->GetTicketFile() );
	    const StrPtr *port = serverID ? serverID : &client->GetPort();
	    t.ReplaceTicket( *port, *user, *data, e );
	    client->SetTicketKey( port );
	    return;
	}

	if( data2 && !strcmp( data2->Text(), "logout" ) )
	{
	    if( serverID )
	    {
	        Ticket t( &client->GetTicketFile() );
	        const StrPtr *port = serverID;
	        t.DeleteTicket( *port, *user, e );
	    }

            Ticket t( &client->GetTicketFile() );
	    const StrPtr *port = &client->GetPort();
	    t.DeleteTicket( *port, *user, e );
	    return;
	}

	// Pre 2004.1 server,  still support windows registry
	// stuff - (this does nothing on UNIX).

	client->DefinePassword( data->Text(), e );

	// Zap any registry errors
	e->Clear();
}

//
// clientProtocol - handle server's 'protocol' message
//

void
clientProtocol( Client *client, Error *e )
{
	StrPtr *s;

	if( s = client->GetVar( P4Tag::v_xfiles ) )
	    client->protocolXfiles = s->Atoi();

	if( ( s = client->GetVar( P4Tag::v_server2 ) ) || 
	    ( s = client->GetVar( P4Tag::v_server ) ) )
	    client->protocolServer = s->Atoi();

	if( s = client->GetVar( P4Tag::v_security ) ) 
	    client->protocolSecurity = s->Atoi();

	client->protocolNocase = client->GetVar( P4Tag::v_nocase ) != 0;

	client->protocolUnicode = client->GetVar( P4Tag::v_unicode ) != 0;
}

//
// clientFatalError - handle protocol errors
// called when above functions return prematurely.
//

void
clientFatalError( Client *client, Error *e )
{
	// add our "fatal error" message
	// Note error so we can adjust exit()
	// Output it via the UI
	// release ourselves from Dispatch()
	// XXX Woe be to tagged calls: not thought through!

	e->Set( MsgClient::Fatal );
	client->SetError();
	client->SetFatal();
	client->GetUi()->HandleError( e );
	client->ClearSecretKey();
	client->ClearPBuf();
	client->GotReleased();
}

void clientReceiveFiles( Client *client, Error *e );

/*
 * clientDispatch - dispatch table
 */

const RpcDispatch clientDispatch[] = {
	P4Tag::c_OpenFile,	RpcCallback(clientOpenFile),
	P4Tag::c_OpenDiff,	RpcCallback(clientOpenFile),
	P4Tag::c_OpenMatch,	RpcCallback(clientOpenFile),
	P4Tag::c_WriteFile,	RpcCallback(clientWriteFile),
	P4Tag::c_WriteDiff,	RpcCallback(clientWriteFile),
	P4Tag::c_WriteMatch,	RpcCallback(clientWriteFile),
	P4Tag::c_CloseFile,	RpcCallback(clientCloseFile),
	P4Tag::c_CloseDiff,	RpcCallback(clientCloseFile),
	P4Tag::c_CloseMatch,	RpcCallback(clientCloseFile),
	P4Tag::c_AckMatch,	RpcCallback(clientAckMatch),

	P4Tag::c_DeleteFile,	RpcCallback(clientDeleteFile),
	P4Tag::c_ChmodFile,	RpcCallback(clientChmodFile),
	P4Tag::c_CheckFile,	RpcCallback(clientCheckFile),
	P4Tag::c_ConvertFile,   RpcCallback(clientConvertFile),
	P4Tag::c_ReconcileEdit,	RpcCallback(clientReconcileEdit),
	P4Tag::c_MoveFile,	RpcCallback(clientMoveFile),

	P4Tag::c_ActionResolve, RpcCallback(clientActionResolve),

	P4Tag::c_OpenMerge2,	RpcCallback(clientOpenMerge),
	P4Tag::c_OpenMerge3,	RpcCallback(clientOpenMerge),
	P4Tag::c_WriteMerge,	RpcCallback(clientWriteMerge),
	P4Tag::c_CloseMerge,	RpcCallback(clientCloseMerge),

	P4Tag::c_ReceiveFiles,	RpcCallback(clientReceiveFiles),
	P4Tag::c_SendFile,	RpcCallback(clientSendFile),
	P4Tag::c_EditData,	RpcCallback(clientEditData),
	P4Tag::c_InputData,	RpcCallback(clientInputData),
	P4Tag::c_ReconcileAdd,	RpcCallback(clientReconcileAdd),
	P4Tag::c_ReconcileFlush,RpcCallback(clientReconcileFlush),
	P4Tag::c_ExactMatch,    RpcCallback(clientExactMatch),

	P4Tag::c_Prompt,	RpcCallback(clientPrompt),
	P4Tag::c_Progress,	RpcCallback(clientProgress),
	P4Tag::c_ErrorPause,	RpcCallback(clientErrorPause),
	P4Tag::c_HandleError,	RpcCallback(clientHandleError),
	P4Tag::c_Message,	RpcCallback(clientMessage),
	P4Tag::c_OutputError,	RpcCallback(clientOutputError),
	P4Tag::c_OutputInfo,	RpcCallback(clientOutputInfo),
	P4Tag::c_OutputData,	RpcCallback(clientOutputInfo),
	P4Tag::c_OutputText,	RpcCallback(clientOutputText),
	P4Tag::c_OutputBinary,	RpcCallback(clientOutputBinary),
	P4Tag::c_FstatInfo,	RpcCallback(clientFstatInfo),

	P4Tag::c_Ack,		RpcCallback(clientAck),
	P4Tag::c_Ping,		RpcCallback(clientPing),

	P4Tag::c_Crypto,	RpcCallback(clientCrypto),
	P4Tag::c_SetPassword,	RpcCallback(clientSetPassword),
	P4Tag::c_SSO,		RpcCallback(clientSingleSignon),

	P4Tag::p_protocol,	RpcCallback(clientProtocol),
	P4Tag::p_errorHandler,	RpcCallback(clientFatalError),

	0, 0
} ;
# Change User Description Committed
#1 15903 Matt Attaway Everything should be happy now between the Workshop and the depot paths
//guest/perforce_software/p4/2014_2/client/clientservice.cc
#1 15901 Matt Attaway Clean up code to fit modern Workshop naming standards
//guest/perforce_software/p4/2014.2/client/clientservice.cc
#1 12189 Matt Attaway Initial (and much belated) drop of 2014.2 p4 source code