/* * Copyright 1995, 1996 Perforce Software. All rights reserved. * * This file is part of Perforce - the FAST SCM System. */ # include <stdhdrs.h> # include <debug.h> # include <strbuf.h> # include <strdict.h> # include <strtable.h> # include <error.h> # include <i18napi.h> # include <charcvt.h> # include <transdict.h> # include <options.h> # include <handler.h> # include <rpc.h> # include <ident.h> # include <enviro.h> # include <ignore.h> # include <filesys.h> # include <msgclient.h> # include <msgserver.h> # include <msgrpc.h> # include <msgsupp.h> # include <p4tags.h> # include <netportparser.h> # include "clientuser.h" # include "clientusernull.h" # include "clientservice.h" # include "clientmerge.h" # include "client.h" void clientTrust( Client *, Error * ); Ident p4api_ident = { IdentMagic "P4API" "/" ID_OS "/" ID_REL "/" ID_PATCH, ID_Y "/" ID_M "/" ID_D }; Client::Client( Enviro *e ) : Rpc( &service ) { // Use the builtin ClientMerger until SetMerger is called. fromTransDialog = toTransDialog = NULL; translated = this; transfname = this; errors = 0; fatals = 0; is_unicode = 0; unknownUnicode = 1; content_charset = 0; output_charset = 0; transErr.Clear(); ignore = new Ignore; lowerTag = upperTag = 0; authenticated = 0; ignoreList = 0; pubKeyChecked = 0; hostprotoset = 0; syncTime = 0; ownCwd = 1; fstatPartial = 0; extraVars = 0; protocolXfiles = -1; protocolNocase = 0; protocolSecurity = 0; protocolUnicode = 0; if( e ) { enviro = e; ownEnviro = 0; } else { enviro = new Enviro; ownEnviro = 1; } /* * If P4CONFIG is set, we'll need to load the environment * from the .p4 file in the directory path. */ enviro->Config( GetCwd() ); // Our standard implementation. // See msgs/p4tagl.cc for l_client setting. service.Dispatcher( clientDispatch ); service.SetProtocol( P4Tag::v_cmpfile ); // has clientCompareFile #1737 service.SetProtocol( P4Tag::v_client, P4Tag::l_client ); buildInfo = p4api_ident.GetIdent(); } Client::~Client() { CleanupTrans(); if( ownEnviro ) delete enviro; delete fstatPartial; delete ignore; delete extraVars; } void Client::Init( Error *e ) { // Set up address and connect. errors = 0; // re-add host and port in protocol message hostprotoset = 0; // unicode setup if possible if( unknownUnicode ) SetupUnicode( e ); if( GetEVar( P4Tag::v_ipaddr ) && GetEVar( P4Tag::v_svrname ) ) SetProtocol( P4Tag::v_ipaddr, GetEVar( P4Tag::v_ipaddr )->Text() ); if( !e->Test() ) service.SetEndpoint( GetPort().Text(), e ); if( !e->Test() ) Connect( e ); if( !e->Test() ) { DoHandshake( e ); // no-op if not ssl if( !e->Test() && unknownUnicode ) { ClientUserNULL cnull( e ); // discover unicode server status // if program name has not been set yet, use p4 api // ident as program name if( !programName.Length() ) SetVar( P4Tag::v_prog, p4api_ident.GetIdent() ); Run( "discover", &cnull ); // bad command is expected when connecting to // servers before 2014.2 if( e->CheckId( MsgServer::BadCommand ) ) { e->Clear(); errors = 0; } // on trust errors, ignore them, and ignore the // unicode detection result if( e->CheckId( MsgRpc::HostKeyMismatch ) || e->CheckId( MsgRpc::HostKeyUnknown ) ) { e->Clear(); errors = 0; } else { if( !e->Test() ) LearnUnicode( e ); } if( e->Test() ) (void) Final( e ); } // don't bother testing for error since caller will handle return; } e->Set( MsgClient::Connect ); } void Client::Run( const char *func, ClientUser *u ) { // Run the command async, and then wait. RunTag( func, u ); WaitTag(); } void Client::RunTag( const char *func, ClientUser *u ) { Error e; // for assembla... job067346 - late protocol set // we set this late so that an application can SetHost // after creating the Client object and init. if( !hostprotoset ) { hostprotoset = 1; if( GetInitRoot().Length() == 0 ) SetProtocolDynamic( P4Tag::v_host, GetHost() ); SetProtocolDynamic( P4Tag::v_port, GetPort() ); } // Warning: WaitTag() calls Dispatch() which may call Invoke(). // Fill this tag slot // Below we garantee it's empty. tags[ upperTag ] = u; // Cheesy access to RPC vars, a la OutputStat // Equally cheesy access to environment if( u ) { u->varList = this; u->enviro = enviro; if( output_charset ) u->SetOutputCharset( output_charset ); } if( func && *func == 't' && !strcmp( func, "trust" ) ) { Loopback( &e ); clientTrust( this, &e ); if( e.Test() && u ) u->Message( &e ); return; } if( !pubKeyChecked ) { CheckKnownHost( &e, GetTrustFile() ); if( e.Test() ) { if( u ) { e << "p4 trust"; u->Message( &e ); } SetError(); Clear(); return; } pubKeyChecked = 1; } if( programName.Length() ) SetVar( P4Tag::v_prog, programName ); // Formulate up function name. // Set user/client/os/cwd // Invoke client's request. StrBuf s; s << "user-" << ( func ? func : "help" ); GetEnv(); Invoke( s.Text() ); // Advance upper tag, and ensure the new slot is empty. int nextTag = ( upperTag + 1 ) % ClientTags; if( nextTag == lowerTag ) WaitTag( tags[ lowerTag ] ); upperTag = nextTag; // Until successful dispatch (after init or setting user/passwd) we // process one command at a time, so that they crypto goes through. if( !authenticated ) WaitTag(); } void Client::WaitTag( ClientUser *u ) { // While any RunTag() requests are outstanding, Dispatch(). // Dispatch() returns exactly once for each Invoke(). while( lowerTag != upperTag ) { Dispatch(); authenticated = 1; // lowerTag is done; signal that // and bump it. If that was the // tag we were waiting for, bail. ClientUser *ui = tags[ lowerTag ]; if( Dropped() && !IoError()->CheckId( MsgRpc::Break ) ) ui->Message( IoError() ); ui->Finished(); lowerTag = ( lowerTag + 1 ) % ClientTags; if( u == ui ) break; } } int Client::Final( Error *e ) { // Dispatch returns when the other end is waiting for another // request. We tell it to go away with ReleaseFinal. // Then we disconnect. ReleaseFinal(); Disconnect(); // Propagate any IO errors if( !e->Test() ) *e = *IoError(); // Return non-zero if any errors have been detected. // Note that the server still sends some errors back on // stdout, which we don't detect. return e->Test() || GetErrors(); } void Client::GetEnv() { /* * Load up into RPC variables: * * client - this client's name * cwd - the current working directory * host - this client's host name (for host locking) * language - preferred language for error messages * os - the name of the OS (for calculating paths) * user - the user's name */ const StrPtr &lang = GetLanguage(); const StrPtr &initroot = GetInitRoot(); translated->SetVar( P4Tag::v_client, GetClient() ); transfname->SetVar( P4Tag::v_cwd, GetCwd() ); if( transErr.Test() && translated != transfname ) translated->SetVar( P4Tag::v_cwd, GetCwd() ); if( initroot.Length() ) { transfname->SetVar( P4Tag::v_initroot, initroot ); if( transErr.Test() && translated != transfname ) translated->SetVar( P4Tag::v_initroot, initroot ); } else SetVar( P4Tag::v_host, GetHost() ); if( lang.Length() ) translated->SetVar( P4Tag::v_language, lang ); SetVar( P4Tag::v_os, GetOs() ); translated->SetVar( P4Tag::v_user, GetUser() ); /* * unicode - set if all messages are in unicode */ if( is_unicode ) { SetVar( P4Tag::v_unicode ); SetVar( P4Tag::v_charset, content_charset ); } else // no charset was defined { int cs = GuessCharset(); if (cs != 0) SetVar( P4Tag::v_charset, cs ); } SetVar( P4Tag::v_clientCase, StrBuf::CaseUsage() ); int i = GetUi()->ProgressIndicator(); if( i ) SetVar( P4Tag::v_progress, i ); } void Client::Confirm( const StrPtr *confirm ) { // pre 99.1 servers need GetEnv(), too, because they // reestablished DmCaller context each time. if( protocolServer < 6 ) GetEnv(); CopyVars(); Invoke( confirm->Text() ); } void Client::NewHandler() { if (translated != this) { ((TransDict *)translated)->Clear(); } if (transfname != this && transfname != translated) { ((TransDict *)transfname)->Clear(); } } void Client::SetArgv( int ac, char * const * av) { if (translated != this) translated->SetArgv(ac, av); else Rpc::SetArgv(ac, av); } void Client::VSetVar( const StrPtr &var, const StrPtr &val ) { if (translated != this) translated->RemoveVar(var.Text()); // careful about the order here Rpc::VSetVar(var, val); } void Client::SetProg( const char *prog ) { programName.Set( prog ); } void Client::SetProg( const StrPtr *prog ) { programName.Set( prog ); } void Client::SetVersion( const char *version ) { SetVar( P4Tag::v_version, version ); } void Client::SetVersion( const StrPtr *version ) { SetVar( P4Tag::v_version, version ); } StrPtr * Client::GetProtocol( const StrPtr &var ) { // NB: result only valid until next GetProtocol call. if( var == P4Tag::v_server2 || var == P4Tag::v_server ) protocolBuf.Set( protocolServer ); else if( var == P4Tag::v_nocase && protocolNocase ) protocolBuf.Set( protocolNocase ); else if( var == P4Tag::v_security ) protocolBuf.Set( protocolSecurity ); else if( var == P4Tag::v_unicode ) protocolBuf.Set( protocolUnicode ); else return 0; return &protocolBuf; } void Client::SetupUnicode( Error *e ) { const char *charsetVal = GetCharset().Text(); if( *charsetVal ) LateUnicodeSetup( charsetVal, e ); } void Client::LearnUnicode( Error *e ) { const char *value = "none"; if( protocolUnicode ) value = "auto"; SetCharset( value ); if( charsetVar.Length() ) enviro->Set( charsetVar.Text(), value, e ); // ignore any errors trying to save the charset e->Clear(); errors = 0; LateUnicodeSetup( value, e ); } void Client::LateUnicodeSetup( const char *value, Error *e ) { unknownUnicode = 0; int cs = CharSetApi::Lookup( value ); if( cs >= 0 ) SetTrans( cs ); else if( e ) e->Set( MsgClient::UnknownCharset ) << value; } void Client::SetEnviroFile( const char *c ) { enviro->SetEnviroFile( c ); enviro->Reload(); } const StrPtr * Client::GetEnviroFile() { return enviro->GetEnviroFile(); } void Client::FstatPartialAppend( StrDict *part ) { if( !fstatPartial ) fstatPartial = new StrBufDict; StrRef var, val; for( int i = 0; part->GetVar( i, var, val ); i++ ) fstatPartial->SetVar( var, val ); } void Client::FstatPartialClear() { delete fstatPartial; fstatPartial = 0; } StrPtr * Client::GetEVar( const char *k ) { StrRef key( k ); return GetEVar( &key ); } StrPtr * Client::GetEVar( const StrPtr *k ) { if( !extraVars ) return NULL; return extraVars->GetVar( *k ); } void Client::SetEVar( const StrPtr *k, const StrPtr *v ) { if( !extraVars ) extraVars = new StrBufDict; extraVars->SetVar( *k, *v ); }