enviro.cc #1

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

/*
 * enviro.cc - get/set environment variables/registry entries
 *
 * This code does a bit of extra work on NT to deal with the
 * the registry: it lets the environment take precedence, but
 * still looks for variables in two places in the registry:
 * in the machine area and in the user area.  If the user sets
 * a registry variable that is masked by an environment var,
 * a warning is sputtered out.
 *
 * This code attempts to cache registry values to avoid having
 * to ask too many times and because the RegQueryValueEx call requires
 * the caller's storage.  Whenever a variable is set, the cache
 * is updated.
 *
 * Unfortunately, this code lists the variables it deals with.
 */

# include <stdhdrs.h>
# include <strbuf.h>
# include <error.h>
# include <errornum.h>
# include <msgsupp.h>
# include <vararray.h>
# include <pathsys.h>
# include <filesys.h>
# include <i18napi.h>
# include <charcvt.h>
# include <debug.h>
# include <tunable.h>
# include "enviro.h"

// The global definition

Enviro enviro;

const char p4passwd[] = "P4PASSWD";
const char p4enviro[] = "P4ENVIRO";

// Cheesy -- known env vars
// Keep alpha sort.
// Keep in sync with HelpEnvironment in msgs/msghelp.cc

const static char *const envVars[] = {
	"P4AUDIT",
	"P4AUTH",
	"P4BROKEROPTIONS",
	"P4CHANGE",
	"P4CHARSET",
	"P4CLIENT",
	"P4CLIENTPATH",
	"P4COMMANDCHARSET",
	"P4CONFIG",
	"P4DEBUG",
	"P4DESCRIPTION",
	"P4DIFF",
	"P4DIFFUNICODE",
	"P4EDITOR",
	p4enviro,
	"P4FTPCHANGE",
	"P4FTPDEBUG",
	"P4FTPLOG",
	"P4FTPOPTIONS",
	"P4FTPPORT",
	"P4FTPPREFIX",
	"P4FTPSYSLOG",
	"P4FTPTEMPLATE",
	"P4HOST",
	"P4IGNORE",
	"P4JOURNAL",
	"P4LANGUAGE",
	"P4LOG",
	"P4LOGINSSO",
	"P4MERGE",
	"P4MERGEUNICODE",
	"P4NAME",
	"P4PAGER",
	p4passwd,
	"P4PCACHE",
	"P4PFSIZE",
	"P4POPTIONS",
	"P4PORT",
	"P4ROOT",
	"P4SSLDIR",
	"P4TARGET",
	"P4TICKETS",
	"P4TRUST",
	"P4USER",
	"P4WEBPORT",
	"P4WEBSERVICEFLAGS",
	"P4WEBVIEWER",
	"P4ZEROSYNC",
	0
};

struct EnviroItem {
	StrBuf	var;
	StrBuf	value;
	Enviro::ItemType type;
} ;

/*
 * EnviroTable -- cached variable settings
 */

class EnviroTable : public VarArray {

    public:
			~EnviroTable();

	EnviroItem	*GetItem( const StrRef &var );
	EnviroItem	*PutItem( const StrRef &var );
} ;

const StrPtr *Enviro::sServiceNameStrP = NULL;

const char *
Enviro::ServiceName()
{
	return serviceName.Length()? serviceName.Text() : 0;
}

const StrPtr *Enviro::GetCachedServerName()
{
	return sServiceNameStrP;
}

/*
 * Enviro on NT
 */

# ifdef OS_NT

# define WIN32_LEAN_AND_MEAN
# include <windows.h>

struct KeyPair {
	HKEY	hkey;
	char	*key;
} ;

KeyPair userKey = { HKEY_CURRENT_USER, "software\\perforce\\environment" };
KeyPair serverKey = { HKEY_LOCAL_MACHINE, "software\\perforce\\environment" };

void	GetRegKey( void *h, const KeyPair *keyPair, Error *e );
int 	GetRegValue( const char *key, StrBuf *valBuf, const KeyPair *keyPair );
int 	GetRegValueW( const char *key, StrBuf *valBuf, const KeyPair *keyPair );
static bool GetEnv(const char* var, EnviroItem* item = 0);
static bool GetEnvW(const char* var, EnviroItem* item = 0);

Enviro::Enviro()
{
	symbolTab = 0;
	setKey = &userKey;
	serviceKey = 0;
	charset = 0;
}

Enviro::~Enviro()
{
	delete symbolTab;
	delete serviceKey;
}

int
Enviro::BeServer( const StrPtr *name, int checkName )
{
	if( name )
	{
	    // cache service name for NetSslCredentials constructor
	    sServiceNameStrP = name;

	    // Variables under service 'name'
	    serviceName.Set( *name );
	    serviceKeyName.Clear();
	    serviceKeyName << "SYSTEM\\CurrentControlSet\\Services\\"
			   << *name
			   << "\\Parameters";

	    delete serviceKey;
	    serviceKey = new KeyPair;
	    serviceKey->hkey = HKEY_LOCAL_MACHINE;
	    serviceKey->key = serviceKeyName.Value();

	    setKey = serviceKey;

	    if( checkName )
	    {
		HKEY svcKey;
	        int serviceKeyExists = RegOpenKeyEx(
		        serviceKey->hkey, 
		        serviceKey->key,
		        0,
# ifdef OS_NTX64
		        KEY_READ|KEY_WRITE|KEY_WOW64_32KEY,
# else /* OS_NTX64 */
		        KEY_READ|KEY_WRITE,
# endif /* OS_NTX64 */
	                &svcKey ) == ERROR_SUCCESS;

	        if( serviceKeyExists )
	            RegCloseKey( svcKey );
	        else
	            return 0;
	    }
	}
	else
	{
	    // Server variables

	    serviceName.Clear();
	    setKey = &serverKey;
	}
	return 1;
}

void
Enviro::OsServer()
{
	delete serviceKey;
	serviceKey = new KeyPair;
	serviceKey->hkey = HKEY_LOCAL_MACHINE;
	serviceKey->key = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
}

void
Enviro::Reload()
{
	delete symbolTab;
	symbolTab = 0;
}

bool
Enviro::ReadItemPlatform( ItemType type, const char *var, EnviroItem * a )
{
	bool isUnicode = CharSetApi::isUnicode( (CharSetApi::CharSet)charset );

	switch ( type )
	{
	    case SVC:
	        if( serviceKey )
	        {
	            // from service-specific area of the registry

	            if( isUnicode &&
	                GetRegValueW( var, &a->value, serviceKey ) ||
	                GetRegValue( var, &a->value, serviceKey ) )
	            {
	                a->type = SVC;
	                return true;
	            }
	        }
	    break;
        
	    case ENV:
	        if( isUnicode && GetEnvW( var, a ) || GetEnv( var, a ) )
	            return true;
	    break;

	    // from user area of the registry

	    case USER:
	        if( isUnicode &&
	            GetRegValueW( var, &a->value, &userKey ) ||
	            GetRegValue( var, &a->value, &userKey ) )
	        {
	            a->type = USER;
	            return true;
	        }
	    break;

	    // from machine area of the registry

	    case SYS:
                /*
                 * Only Admin can write a shared registry key,
                 * but GetRegValue() and GetRegValueW() try
                 * to create a read/write key, and so will
                 * normally fail.
                 *
                 * The only shared registry keys that we currently
                 * fetch (and don't write) are "CurrentVersion"
                 * and "CSDVersion" (see rhmain.cc), so we
                 * special-case them and use GetVersionEx() instead.
                 */
                if( !strcmp(var, "CurrentVersion") )
                {
                    OSVERSIONINFOEX     info;

                    memset( &info, '\0', sizeof info );
                    info.dwOSVersionInfoSize = sizeof info;
                    if( GetVersionEx( reinterpret_cast<OSVERSIONINFO *>(&info) ) )
                    {
                        a->value.Clear();
                        a->value << info.dwMajorVersion << "." << info.dwMinorVersion;
                        a->type = SYS;
                        return true;
                    }
                    return false;
                }
                else if( !strcmp(var, "CSDVersion") )
                {
                    OSVERSIONINFOEX     info;

                    memset( &info, '\0', sizeof info );
                    info.dwOSVersionInfoSize = sizeof info;
                    if( GetVersionEx( reinterpret_cast<OSVERSIONINFO *>(&info) ) )
                    {
                        a->value.Set( info.szCSDVersion) ;
                        a->type = SYS;
                        return true;
                    }
                    return false;
                }

                // not one of our special lookups, so go get it from the registry
	        if( isUnicode &&
	            GetRegValueW( var, &a->value, &serverKey ) ||
	            GetRegValue( var, &a->value, &serverKey ) )
	        {
	            a->type = SYS;
	            return true;
	        }
	    break;

	    default:
	        // Fall through to the failure at the end of the method
	    break;
	}

	a->type = UNSET;
	return false;
}

void
Enviro::Set( const char *var, const char *value, Error *e )
{
	// Make sure symbol tab is there

	Setup();

	// Do not set p4passwd in enviro file
	if( var && stricmp( var, p4passwd ) && !SetEnviro( var, value, e ) )
	    return;

	// Get from registry

	HKEY	hKeyEnv;

	GetRegKey( &hKeyEnv, setKey, e );

	if( e->Test() )
	    return;

	if( value && *value )
	{
	    if( CharSetApi::isUnicode( (CharSetApi::CharSet)charset ) )
	    {
		CharSetCvtUTF816 cvtvar, cvtval;

		WCHAR *val = (WCHAR *)cvtval.FastCvt( value, strlen( value ) );

		if( !val || RegSetValueExW( 
		    hKeyEnv, 
		    (WCHAR *)cvtvar.FastCvt( var, strlen( var ) ),
		    0, 
		    REG_SZ, 
		    (LPBYTE)val,
		    sizeof(WCHAR)*( wcslen( val ) + 1 ) ) != ERROR_SUCCESS )
		{
		    e->Sys( "registry", "set key" );
		}
	    }
	    else
	    {
		if( RegSetValueExA( 
		    hKeyEnv, 
		    var, 
		    0, 
		    REG_SZ, 
		    (LPBYTE)value,
		    strlen( value ) + 1 ) != ERROR_SUCCESS )
		{
		    e->Sys( "registry", "set key" );
		}
	    }
	}
	else
	{
	    if( RegDeleteValue( hKeyEnv, var ) < ERROR_SUCCESS )
	    {
		e->Sys( "registry", "delete key" );
	    }
	}

	RegCloseKey( hKeyEnv );

	// warn the user if the env variable is also set.

	if( value && 
		( CharSetApi::isUnicode( (CharSetApi::CharSet)charset )
		    && GetEnvW( var ) || GetEnv( var ) ) )
	    e->Set( MsgSupp::HidesVar ) << var;

	// Make symbol undefined, so next Get resets it

	EnviroItem *a = symbolTab->GetItem( StrRef( (char *)var ) );

	if( a )
	    a->type = NEW;

	if( !StrBuf::SCompare( var[0], 'P' ) && var[1] == '4' && !IsKnown( var ) )
	    e->Set( MsgSupp::NoSuchVariable ) << var;
}

/*
 * GetRegKey -- Get the NT-specific handle for the registry key.
 */

void
GetRegKey( void *hKeyEnv, const KeyPair *keyPair, Error *e )
{
	DWORD	disp;
	long 	stat;

	if( RegCreateKeyEx(
		keyPair->hkey, 
		keyPair->key,
		0,
		NULL, 
		REG_OPTION_NON_VOLATILE,
# ifdef OS_NTX64
		KEY_READ|KEY_WRITE|KEY_WOW64_32KEY,
# else /* OS_NTX64 */
		KEY_READ|KEY_WRITE,
# endif /* OS_NTX64 */
		NULL,
		(HKEY *)hKeyEnv,
		&disp ) != ERROR_SUCCESS )
	{
	    e->Sys( "registry", "create key" );
	}
}

/*
 * GetRegValue() - get a value from the registry. using MultiByte API
 */

int
GetRegValue( const char *var, StrBuf *valBuf, const KeyPair *keyPair )
{
	HKEY	hKeyEnv;
	Error e;
	long stat;
	DWORD type;
	DWORD length = 0;

	GetRegKey( &hKeyEnv, keyPair, &e );

	if( e.Test() )
	    return 0;

	// once to get the length

	stat = RegQueryValueExA(
		hKeyEnv,
		var,
		0,
		&type, 
		0,
		&length );


	if( stat && stat != ERROR_MORE_DATA )
	{
		RegCloseKey( hKeyEnv );
		return 0;
	}

	// once to get the data

	valBuf->Clear();

	stat = RegQueryValueExA(
		hKeyEnv,
		var,
		0,
		&type, 
		(LPBYTE)(valBuf->Alloc( length )),
		&length );

	RegCloseKey( hKeyEnv );

	return 1;
}

/*
 * GetRegValueW() - get a value from the registry, using Unicode API
 */

int
GetRegValueW( const char *var, StrBuf *valBuf, const KeyPair *keyPair )
{
	HKEY	hKeyEnv;
	Error e;
	long stat;
	DWORD type;
	DWORD length = 0;

	GetRegKey( &hKeyEnv, keyPair, &e );

	if( e.Test() )
	    return 0;

	CharSetCvtUTF816 cvtvar;

	WCHAR *wvar = (WCHAR *)cvtvar.FastCvt( var, strlen( var ) );

	// once to get the length, in bytes

	stat = RegQueryValueExW(
		hKeyEnv,
		wvar,
		0,
		&type, 
		0,
		&length );


	if( stat && stat != ERROR_MORE_DATA )
	{
		RegCloseKey( hKeyEnv );
		return 0;
	}

	// allocate enough space for the value; note that length may
	// not represent an integral number of WCHARs

	WCHAR *val = new WCHAR[(length + 1)/sizeof(WCHAR)];

	// once to get the data

	stat = RegQueryValueExW(
		hKeyEnv,
		wvar,
		0,
		&type, 
		(LPBYTE)val,
		&length );

	// adjust length, since RegQueryValueExW sometimes gifts us with
	// an extra byte of garbage following the terminating null.

	length = sizeof(WCHAR)*(wcslen(val) + 1);

	CharSetCvtUTF168 cvtval;
	int retlen;
	const char *retval = cvtval.FastCvt( (const char *)val, length, &retlen );

	delete [] val;

	RegCloseKey( hKeyEnv );

	if( !retval )
	    return 0;

	valBuf->Set( retval, retlen );

	return 1;
}

/*
 * GetEnv() - get a value from the environment, using MultiByte API
 */

static bool
GetEnv( const char * var, EnviroItem * item )
{
	StrBuf result;
	int len = GetEnvironmentVariableA( var, result.Alloc( 80 ), 80 );
	if( len == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND )
	    return false;

	if ( !item )
	    return true;

	if ( len > 80 )
	{
	    // refetch with longer length

	    item->value.Clear();
	    len = GetEnvironmentVariableA( var, item->value.Alloc( len ), len );
	    item->value.SetLength( len );
	}
	else
	{
	    result.SetLength( len );
	    item->value.Set( result );
	}
	item->type = Enviro::ENV;
	return true;
}

/*
 * GetEnvW() - get a value from the environment, using Unicode API
 */

static bool
GetEnvW( const char * var, EnviroItem * item )
{
	CharSetCvtUTF816 cvtvar;
	WCHAR *wvar = (WCHAR *)cvtvar.FastCvt( var, strlen( var ) );

	StrBuf result;
	int len = GetEnvironmentVariableW( wvar, (wchar_t *)result.Alloc( 160 ), 80 );
	if( len == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND )
	    return false;

	if( !item )
	    return true;

	if( len > 80 )
	{
	    // refetch with longer length

	    result.Clear();
	    len = GetEnvironmentVariableW( wvar, (wchar_t *)result.Alloc( len * 2 ), len );
	}
	result.SetLength( 2 * len );

	CharSetCvtUTF168 cvtval;
	const char *retval = cvtval.FastCvt( result.Text(), result.Length() );
	if ( !retval )
	    return false;
	item->value.Set( retval );
	item->type = Enviro::ENV;
	return true;
}

void
Enviro::SetCharSet( int i )
{
	charset = i;
	Reload();
}

int
Enviro::GetCharSet()
{
	return charset;
}



# elif defined ( OS_MACOSX ) || defined ( OS_DARWIN )

/*
 * Enviro on Mac OS X
 */

# include <CoreFoundation/CFPreferences.h>

// Apple's Preferences API here
//
// http://developer.apple.com/documentation/CoreFoundation/Reference/CFPreferencesUtils/index.html
//

CFStringRef applicationID = CFSTR("com.perforce.environment");

void StrBufSetToCFString(
	 StrBuf *         buf,
	 CFStringRef      stringRef,
	 CFStringEncoding encoding )
{
	CFDataRef data = CFStringCreateExternalRepresentation(
	                   kCFAllocatorDefault,
	                   stringRef,
	                   encoding,
	                   '?' );
	
	buf->Set( (const char *)CFDataGetBytePtr(data), CFDataGetLength( data ) );
	CFRelease( data );
}

extern CFPropertyListRef CFPreferencesCopyValue(CFStringRef, CFStringRef, CFStringRef, CFStringRef) __attribute ((weak_import));

static bool HasCFPreferences()
{
	// CoreFoundation should be weak-linked and we can check for
	// the presence of a CFPreferences symbol dynamically.
	//   -weak_framework <framework_name>

	// http://developer.apple.com/mac/library/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WeakLinking.html
	return (CFPreferencesCopyValue != NULL);
}

Enviro::Enviro()
{
	symbolTab = 0;
	domain = Enviro::USER;
}

Enviro::~Enviro()
{
	delete symbolTab;
}

int
Enviro::BeServer( const StrPtr *name, int checkName )
{
	sServiceNameStrP = name;
	domain = Enviro::SYS;
	return 1;
}

void
Enviro::OsServer()
{
}

void
Enviro::Reload()
{
	delete symbolTab;
	symbolTab = 0;
}

bool
Enviro::ReadItemPlatform( ItemType type, const char *var, EnviroItem * a )
{
	if ( (type == USER || type == SYS) && HasCFPreferences() )
	{
	    CFStringRef userName = kCFPreferencesCurrentUser;
	    CFStringRef hostName = kCFPreferencesAnyHost;

	    if ( type == SYS )
	    {
	        userName = kCFPreferencesAnyUser;
	        hostName = kCFPreferencesCurrentHost;
	    }

	    // Reads the var name in UTF-8 format (the default for the Terminal)
	
	    // A note about CoreFoundation naming conventions:
	    //
	    // <Method>Create() or <Method>Copy means you have to call CFRelease()
	    // when you are through with your ref object (except for
	    // CFStringCreateWithCStringNoCopy() because of the "NoCopy" part )
	    // <Method>Get means you don't have to "release" it, but it you want
	    // to save it, you musts increment the reference count by calling
	    // CFRetain( ref )
	    //
	    // http://developer.apple.com/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html
	
	    CFStringRef varRef = CFStringCreateWithCStringNoCopy(
	                        kCFAllocatorDefault,
	                        var,
	                        kCFStringEncodingUTF8,
	                        kCFAllocatorNull );

	    CFPropertyListRef propRef = CFPreferencesCopyValue(
	           varRef,
	           applicationID,
	           userName,
	           hostName );
	           
	    CFRelease( varRef );
	    
	    if ( propRef && (CFGetTypeID( propRef ) == CFStringGetTypeID()) )
	    {
	        StrBufSetToCFString( &(a->value),
	                         (CFStringRef)propRef,
	                         kCFStringEncodingUTF8 );
	        a->type = type;
	        CFRelease( propRef );
	        return true;
	    }
	    
	    if ( propRef )
	        CFRelease( propRef );
	    return false;
	}

	if (type == ENV)
	{
	    char *c = NULL;
	    if( c = getenv( var ) )
	    {
	        a->value.Set( c );
	        a->type = ENV;
	        return true;
	    }
	}

	// Not set

	return false;
}

void
Enviro::Set( const char *var, const char *value, Error *e )
{
	if ( !var || !strcasecmp( var, p4passwd ) || !HasCFPreferences() )
	    return;

	// Make sure symbol tab is there

	Setup();

	if( !SetEnviro( var, value, e ) )
	    return;

	// Get from preferences
	CFStringRef domainRef     = kCFPreferencesCurrentUser;
	CFStringRef hostDomainRef = kCFPreferencesAnyHost;

	if ( domain == SYS )
	{
	    domainRef     = kCFPreferencesAnyUser;
	    hostDomainRef = kCFPreferencesCurrentHost;
	}


	CFStringRef varRef = CFStringCreateWithCStringNoCopy(
	                           kCFAllocatorDefault,
	                           var,
	                           kCFStringEncodingUTF8,
	                           kCFAllocatorNull );

	CFStringRef valueRef = NULL;
	
	// passing NULL to CFPreferencesSetValue removes the value
	
	if ( value && *value )
	{
	    valueRef = CFStringCreateWithCString(
	                 kCFAllocatorDefault,
	                 value,
	                 kCFStringEncodingUTF8 );
	}
	
	// add to key
	CFPreferencesSetValue( varRef,
	                       valueRef,
	                       applicationID,
	                       domainRef,
	                       hostDomainRef );

	CFRelease( varRef );
	if (valueRef ) CFRelease( valueRef );

	bool success = CFPreferencesSynchronize(
	                 applicationID,
	                 domainRef,
	                 hostDomainRef);

	if ( !success )
	    e->Sys( "preferences", value ? "set key" : "delete key" );
	
	// warn the user if the env variable is also set.
	if( value && getenv( var ) )
	    e->Set( MsgSupp::HidesVar ) << var;

	// Make symbol undefined, so next Get resets it

	EnviroItem *a = symbolTab->GetItem( StrRef( (char *)var ) );

	if( a )
	    a->type = NEW;

	if( var[0] == 'P' && var[1] == '4' && !IsKnown( var ) )
	    e->Set( MsgSupp::NoSuchVariable ) << var;
}

void
Enviro::SetCharSet( int i )
{
	// nothing - only important for win32 - so far...
}

int
Enviro::GetCharSet()
{
	// nothing - only important for win32 - so far...
	return 0;
}

# else

/*
 * Enviro on UNIX, etc
 */

Enviro::Enviro()
{
	symbolTab = 0;
}

Enviro::~Enviro()
{
	delete symbolTab;
}

int
Enviro::BeServer( const StrPtr *name, int checkName )
{
	sServiceNameStrP = name;
	return 1;
}

void
Enviro::OsServer()
{
}

void
Enviro::Reload()
{
}

void
Enviro::Set( const char *var, const char *value, Error *e )
{
	if ( !var || !strcasecmp( var, p4passwd ) )
	    return;

	if( SetEnviro( var, value, e ) )
	    e->Set( MsgSupp::NoUnixReg );
}

bool
Enviro::ReadItemPlatform( ItemType type, const char *var, EnviroItem * a )
{
	if ( type != ENV )
	    return false;

	char *c = NULL;
	if( c = getenv( var ) )
	{
	    a->value.Set( c );
	    a->type = ENV;
	    return true;
	}
	
	return false;
}

void
Enviro::SetCharSet( int i )
{
	// nothing - only important for win32 - so far...
}

int
Enviro::GetCharSet()
{
	// nothing - only important for win32 - so far...
	return 0;
}

# endif


/*
 * Platform independent
 */

EnviroTable::~EnviroTable()
{
	for( int i = 0; i < Count(); i++ )
	    delete (EnviroItem *)Get(i);
}

EnviroItem *
EnviroTable::GetItem( const StrRef &var )
{
	EnviroItem *a;

	for( int i = 0; i < Count(); i++ )
	{
	    a = (EnviroItem *)Get(i);

	    if( !a->var.SCompare( var ) )
		return a;
	}

	return 0;
}

EnviroItem *
EnviroTable::PutItem( const StrRef &var )
{
	EnviroItem *a = GetItem( var );

	if( !a )
	{
	    a = new EnviroItem;
	    a->type = Enviro::NEW;
	    a->var.Set( var );
	    VarArray::Put( a );
	}

	return a;
}

void
Enviro::List()
{
	const char *const *i;

	for( i = envVars; *i; i++ )
	    Print( *i );
}

int
Enviro::FormatVariable( int i, StrBuf *sb )
{
	if( i >= 0 && i < (sizeof(envVars)/sizeof(envVars[0])) && envVars[i] )
	{
	    Format( envVars[i], sb );
	    return 1;
	}
	return 0;
}

int	
Enviro::IsKnown( const char *nm )
{
	StrRef name( nm );
	const char *const *i;

	for( i = envVars; *i; i++ )
	    if( !name.SCompare( StrRef( *i ) ) )
	        return 1;
	if( !strncmp( name.Text(), "P4_", 3 ) &&
		name.EndsWith( "_CHARSET", 8 ) )
	    return 1;
	return 0;
}

int	
Enviro::HasVariable( int i )
{
	return i >= 0 && i < (sizeof(envVars)/sizeof(envVars[0])) && envVars[i];
}
void	
Enviro::GetVarName( int i, StrBuf &sb )
{
	if( HasVariable( i ) )
	{
	    EnviroItem *a = GetItem( envVars[i] );
	    sb.Set( a->var );
	}
}

void	
Enviro::GetVarValue( int i, StrBuf &sb )
{
	if( HasVariable( i ) )
	{
	    EnviroItem *a = GetItem( envVars[i] );
	    sb.Set( a->value );
	}
}

char *
Enviro::Get( const char *var )
{
	EnviroItem *a = GetItem( var );

	return a->value.Length() ? a->value.Text() : 0;
}

EnviroItem *
Enviro::GetItem( const char *var )
{
	// Make sure symbol tab is there

	Setup();

	// attempt to return cached value
	// null string means unset

	EnviroItem *a = symbolTab->PutItem( StrRef( (char *)var ) );

	if( a->type != NEW )
	    return a;
	if ( ReadItemPlatform( SVC, var, a ) )
	    return a;

	if ( ReadItemPlatform( ENV, var, a ) )
	    return a;

	if ( ReadItemPlatform( USER, var, a ) )
	    return a;

	if ( ReadItemPlatform( SYS, var, a ) )
	    return a;

	a->type = UNSET;

	return a;
}

Enviro::ItemType
Enviro::GetType( const char *var )
{
	EnviroItem *a = GetItem( var );

	return a->type;
}

int
Enviro::FromRegistry( const char *var )
{
	EnviroItem *a = GetItem( var );

	return( a->type == SVC || a->type == USER || a->type == SYS );
}

void
Enviro::Update( const char *var, const char *value )
{
	EnviroItem *a = GetItem( var );

	a->type = UPDATE;
	a->value.Set( value );
}	

void
Enviro::Print( const char *var )
{
	StrBuf buf;
	Format( var, &buf );
	if( buf.Length() )
	    printf( "%s\n", buf.Text() );
}

void
Enviro::Format( const char *var, StrBuf *sb )
{
	EnviroItem *a = GetItem( var );
	sb->Clear();

	int isSet = 1;

	switch( a->type )
	{
	case ENV:
		*sb << a->var.Text() << "=" << a->value.Text();
		break;
	case CONFIG:
		*sb << a->var.Text() << "=" << a->value.Text() << " (config)";
		break;
	case ENVIRO:
		*sb << a->var.Text() << "=" << a->value.Text() << " (enviro)";
		break;
	case SVC:
		*sb << a->var.Text() << "=" << a->value.Text() << " (set -S)";
		break;
	case USER:
		*sb << a->var.Text() << "=" << a->value.Text() << " (set)";
		break;
	case SYS:
		*sb << a->var.Text() << "=" << a->value.Text() << " (set -s)";
		break;
	default:
		isSet = 0;
		break;
	}
	if( isSet && a->var == "P4CONFIG" )
	    *sb << " (config '" << GetConfig() << "')";
}

void
Enviro::ReadConfig( FileSys *f, Error *e, int checkSyntax, Enviro::ItemType ty )
{
	StrBuf line, var;

	while( f->ReadLine( &line, e ) )
	{
	    line.TruncateBlanks();

	    char *equals = strchr( line.Text(), '=' );
	    if( !equals ) continue;

	    // tunable ?

	    p4debug.SetLevel( line.Text() );

	    // Just variable name

	    var.Set( line.Text(), equals - line.Text() );

	    if( checkSyntax &&
	            var.Text()[0] != '#' && !IsKnown( var.Text() ) &&
	            !p4tunable.IsKnown( var.Text() ) )
	    {
		StrBuf errBuf;
		e->Set( MsgSupp::NoSuchVariable ) << var;
		e->Fmt( &errBuf );
		p4debug.printf( "%s", errBuf.Text() );
		e->Clear();
	    }

	    // Set as config'ed

	    EnviroItem *a = GetItem( var.Text() );
	    a->value.Set( equals + 1 );
	    a->type = ty;
	}
}

void
Enviro::Setup()
{
	if( !symbolTab )
	{
	    symbolTab = new EnviroTable;

	    LoadEnviro( 0 );
	}
}

void
Enviro::Config( const StrPtr &cwd )
{
	LoadConfig( cwd, 0 );
}

void
Enviro::LoadConfig( const StrPtr &cwd, int checkSyntax )
{
	// We don't care about errors

	Error e;

	// Only load P4CONFIG file if it is set.

	char *setFile = Get( "P4CONFIG" );

	if( !setFile )
	    return;

	// Make sure symbol tab is there

	Setup();

	// client up the directory tree, looking for setFile

	PathSys *p = PathSys::Create();
	PathSys *q = PathSys::Create();
	FileSys *f = FileSys::Create( FileSysType( FST_TEXT|FST_L_CRLF ) );

# if defined(OS_NT)
	if( charset )
	{
	    p->SetCharSet( charset );
	    q->SetCharSet( charset );
	    f->SetCharSetPriv( charset );
	}
# endif

	// Start with current dir

	p->Set( cwd );

	do {
	    // Can we find the file?

	    e.Clear();
	    q->SetLocal( *p, StrRef( setFile ) );
	    f->Set( *q );
	    f->Open( FOM_READ, &e );

	    if( e.Test() )
		continue;

	    // Save the name of the config file
	    
	    configFile.Set( f->Name() );

	    // Slurp contents into client name

	    ReadConfig( f, &e, checkSyntax, CONFIG );

	    f->Close( &e );
	    break;
	}
	while( p->ToParent() );

	// free & clear

	delete f;
	delete q;
	delete p;
}

void
Enviro::LoadEnviro( int checkSyntax )
{
	Error e;

	const StrPtr *envFile = GetEnviroFile();

	if( !envFile )
	    return;

	// client up the directory tree, looking for setFile

	FileSys *f = FileSys::Create( FileSysType( FST_TEXT|FST_L_CRLF ) );

# if defined(OS_NT)
	if( charset )
	    f->SetCharSetPriv( charset );
# endif

	// Can we find the file?

	e.Clear();
	f->Set( *envFile );
	f->Open( FOM_READ, &e );

	if( !e.Test() )
	{
	    // Slurp contents into symbol table

	    ReadConfig( f, &e, checkSyntax, ENVIRO );

	    f->Close( &e );
	}

	// free & clear

	delete f;
}

static void
WriteItem( FileSys *newf, const char *var, const char *value, Error *e )
{
	newf->Write( var, strlen( var ), e );
	newf->Write( "=", 1, e );
	newf->Write( value, strlen( value ), e );
	newf->Write( "\n", 1, e );
}

void
Enviro::SetEnviroFile( const char *f )
{
	enviroFile.Set( f ? f : "" );
}

const StrPtr *
Enviro::GetEnviroFile()
{
	// If BeServer, do not use enviroment file at all
	if( sServiceNameStrP )
	    return NULL;

	if( !enviroFile.Length() )
	{	    
	    char *setFile = Get( p4enviro );

	    if( setFile )
	    {
		enviroFile.Set( setFile );
	    }
	    else
	    {
# if defined( OS_NT ) || defined( OS_DARWIN ) || defined( OS_MACOSX )
		return NULL;
# else
		// defaults for Unix platforms...
		setFile = Get( "HOME" );
		if( setFile )
		{
		    enviroFile.Set( setFile );
		    enviroFile.Append( "/.p4enviro" );
		}
		else
		    return NULL;
# endif
	    }
	}
	return &enviroFile;
}

int
Enviro::SetEnviro( const char *var, const char *value, Error *e )
{
	int didwrite = 0;
	const StrPtr *envFile = GetEnviroFile();

	if( !envFile )
	    return 1;

	// client up the directory tree, looking for setFile

	FileSys *f = FileSys::Create( FileSysType( FST_TEXT|FST_L_CRLF ) );
	FileSys *newf = FileSys::Create( FileSysType( FST_TEXT|FST_L_CRLF ) );

# if defined(OS_NT)
	if( charset )
	{
	    f->SetCharSetPriv( charset );
	    newf->SetCharSetPriv( charset );
	}
# endif

	// Can we find the file?

	e->Clear();
	f->Set( *envFile );
	f->Open( FOM_READ, e );

	if( !e->Test() )
	{
	    newf->MakeLocalTemp( envFile->Text() );
	    newf->SetDeleteOnClose();
	    newf->Perms( FPM_RW );

	    newf->Open( FOM_WRITE, e );

	    if( !e->Test() )
	    {
		// got both files - do the work

		StrBuf line, filevar;
		StrRef svar( var );

		while( !e->Test() && f->ReadLine( &line, e ) )
		{
		    line.TruncateBlanks();

		    char *equals = strchr( line.Text(), '=' );
		    if( !didwrite && equals && line.Text()[0] != '#')
		    {
			// Just variable name

			filevar.Set( line.Text(), equals - line.Text() );
			if( !filevar.SCompare( svar ) )
			{
			    // This is the one!
			    if( value && *value )
				WriteItem( newf, var, value, e );

			    didwrite = 1;

			    // do not duplicate this line in the temp file
			    continue;
			}
		    }
		    line.Extend( '\n' );
		    newf->Write( line.Text(), line.Length(), e );
		}

		if( !didwrite && value && *value )
		{
		    WriteItem( newf, var, value, e );
		    didwrite = 1;
		}

		newf->Close( e );
	    }

	    f->Close( e );

	    if( !e->Test() && didwrite )
	    {
		newf->Rename( f, e );

		if( !e->Test() )
		    newf->ClearDeleteOnClose();
	    }
	}
	else
	{
	    // could not open the enviro file, try to write it...
	    e->Clear();
	    f->Perms( FPM_RW );
	    f->Open( FOM_WRITE, e );
	    if( !e->Test() )
	    {
		WriteItem( f, var, value, e );
		didwrite = 1;
		f->Close( e );
	    }
	}

	delete newf;
	delete f;

	// warn the user if the env variable is also set.
	if( value && getenv( var ) )
	    e->Set( MsgSupp::HidesVar ) << var;

	return e->Test() || !didwrite;
}

const StrPtr &
Enviro::GetConfig()
{
	// configFile - name of last P4CONFIG file found by Config()

	if( !configFile.Length() )
	    configFile.Set( "noconfig" );

	return configFile;
}
# 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/sys/enviro.cc
#1 15901 Matt Attaway Clean up code to fit modern Workshop naming standards
//guest/perforce_software/p4/2014.2/sys/enviro.cc
#1 12189 Matt Attaway Initial (and much belated) drop of 2014.2 p4 source code