Changeset 55588 in vbox for trunk/src/VBox/Main
- Timestamp:
- May 1, 2015 7:37:46 PM (10 years ago)
- Location:
- trunk/src/VBox/Main
- Files:
-
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/Main/include/GuestCtrlImplPrivate.h
r55535 r55588 6 6 7 7 /* 8 * Copyright (C) 2011-201 3Oracle Corporation8 * Copyright (C) 2011-2015 Oracle Corporation 9 9 * 10 10 * This file is part of VirtualBox Open Source Edition (OSE), as … … 21 21 22 22 #include "ConsoleImpl.h" 23 #include "Global.h" 23 24 24 25 #include <iprt/asm.h> 26 #include <iprt/env.h> 25 27 #include <iprt/semaphore.h> 26 28 … … 60 62 61 63 62 typedef std::vector <Utf8Str> GuestEnvironmentArray; 63 class GuestEnvironment 64 { 65 public: 66 67 int BuildEnvironmentBlock(void **ppvEnv, size_t *pcbEnv, uint32_t *pcEnvVars); 68 69 void Clear(void); 70 71 int CopyFrom(const GuestEnvironmentArray &environment); 72 73 int CopyTo(GuestEnvironmentArray &environment); 74 75 static void FreeEnvironmentBlock(void *pvEnv); 76 77 Utf8Str Get(const Utf8Str &strKey); 78 79 Utf8Str Get(size_t nPos); 80 81 bool Has(const Utf8Str &strKey); 82 83 int Set(const Utf8Str &strKey, const Utf8Str &strValue); 84 85 int Set(const Utf8Str &strPair); 86 87 size_t Size(void); 88 89 int Unset(const Utf8Str &strKey); 90 91 public: 92 93 GuestEnvironment& operator=(const GuestEnvironmentArray &that); 94 95 GuestEnvironment& operator=(const GuestEnvironment &that); 96 97 protected: 98 99 int appendToEnvBlock(const char *pszEnv, void **ppvList, size_t *pcbList, uint32_t *pcEnvVars); 100 101 protected: 102 103 std::map <Utf8Str, Utf8Str> mEnvironment; 64 65 /** 66 * Wrapper around the RTEnv API, unusable base class. 67 * 68 * @remarks Feel free to elevate this class to iprt/cpp/env.h as RTCEnv. 69 */ 70 class GuestEnvironmentBase 71 { 72 public: 73 /** 74 * Default constructor. 75 * 76 * The user must invoke one of the init methods before using the object. 77 */ 78 GuestEnvironmentBase(void) 79 : m_hEnv(NIL_RTENV) 80 { } 81 82 /** 83 * Destructor. 84 */ 85 virtual ~GuestEnvironmentBase(void) 86 { 87 int rc = RTEnvDestroy(m_hEnv); AssertRC(rc); 88 m_hEnv = NIL_RTENV; 89 } 90 91 /** 92 * Initialize this as a normal environment block. 93 * @returns IPRT status code. 94 */ 95 int initNormal(void) 96 { 97 AssertReturn(m_hEnv == NIL_RTENV, VERR_WRONG_ORDER); 98 return RTEnvCreate(&m_hEnv); 99 } 100 101 /** 102 * Returns the variable count. 103 * @return Number of variables. 104 * @sa RTEnvCountEx 105 */ 106 uint32_t count(void) const 107 { 108 return RTEnvCountEx(m_hEnv); 109 } 110 111 /** 112 * Deletes the environment change record entirely. 113 * 114 * The count() method will return zero after this call. 115 * 116 * @sa RTEnvReset 117 */ 118 void reset(void) 119 { 120 int rc = RTEnvReset(m_hEnv); 121 AssertRC(rc); 122 } 123 124 /** 125 * Exports the environment change block as an array of putenv style strings. 126 * 127 * 128 * @returns VINF_SUCCESS or VERR_NO_MEMORY. 129 * @param pArray The output array. 130 */ 131 int queryPutEnvArray(std::vector<com::Utf8Str> *pArray) const 132 { 133 uint32_t cVars = RTEnvCountEx(m_hEnv); 134 try 135 { 136 pArray->resize(cVars); 137 for (uint32_t iVar = 0; iVar < cVars; iVar++) 138 { 139 const char *psz = RTEnvGetByIndexRawEx(m_hEnv, iVar); 140 AssertReturn(psz, VERR_INTERNAL_ERROR_3); /* someone is racing us! */ 141 (*pArray)[iVar] = psz; 142 } 143 return VINF_SUCCESS; 144 } 145 catch (std::bad_alloc &) 146 { 147 return VERR_NO_MEMORY; 148 } 149 } 150 151 /** 152 * Applies an array of putenv style strings. 153 * 154 * @returns IPRT status code. 155 * @param rArray The array with the putenv style strings. 156 * @sa RTEnvPutEnvEx 157 */ 158 int applyPutEnvArray(const std::vector<com::Utf8Str> &rArray) 159 { 160 size_t cArray = rArray.size(); 161 for (size_t i = 0; i < cArray; i++) 162 { 163 int rc = RTEnvPutEx(m_hEnv, rArray[i].c_str()); 164 if (RT_FAILURE(rc)) 165 return rc; 166 } 167 return VINF_SUCCESS; 168 } 169 170 /** 171 * See RTEnvQueryUtf8Block for details. 172 * @returns IPRT status code. 173 * @param ppszzBlock Where to return the block pointer. 174 * @param pcbBlock Where to optionally return the block size. 175 * @sa RTEnvQueryUtf8Block 176 */ 177 int queryUtf8Block(char **ppszzBlock, size_t *pcbBlock) 178 { 179 return RTEnvQueryUtf8Block(m_hEnv, true /*fSorted*/, ppszzBlock, pcbBlock); 180 } 181 182 /** 183 * Frees what queryUtf8Block returned, NULL ignored. 184 * @sa RTEnvFreeUtf8Block 185 */ 186 static void freeUtf8Block(char *pszzBlock) 187 { 188 return RTEnvFreeUtf8Block(pszzBlock); 189 } 190 191 /** 192 * Get an environment variable. 193 * 194 * @returns IPRT status code. 195 * @param rName The variable name. 196 * @param pValue Where to return the value. 197 * @sa RTEnvGetEx 198 */ 199 int getVariable(const com::Utf8Str &rName, com::Utf8Str *pValue) const 200 { 201 size_t cchNeeded; 202 int rc = RTEnvGetEx(m_hEnv, rName.c_str(), NULL, 0, &cchNeeded); 203 if ( RT_SUCCESS(rc) 204 || rc == VERR_BUFFER_OVERFLOW) 205 { 206 try 207 { 208 pValue->reserve(cchNeeded + 1); 209 rc = RTEnvGetEx(m_hEnv, rName.c_str(), pValue->mutableRaw(), pValue->capacity(), NULL); 210 pValue->jolt(); 211 } 212 catch (std::bad_alloc &) 213 { 214 rc = VERR_NO_STR_MEMORY; 215 } 216 } 217 return rc; 218 } 219 220 /** 221 * Set an environment variable. 222 * 223 * @returns IPRT status code. 224 * @param rName The variable name. 225 * @param rValue The value of the variable. 226 * @sa RTEnvSetEx 227 */ 228 int setVariable(const com::Utf8Str &rName, const com::Utf8Str &rValue) 229 { 230 return RTEnvSetEx(m_hEnv, rName.c_str(), rValue.c_str()); 231 } 232 233 /** 234 * Unset an environment variable. 235 * 236 * @returns IPRT status code. 237 * @param rName The variable name. 238 * @sa RTEnvUnsetEx 239 */ 240 int unsetVariable(const com::Utf8Str &rName) 241 { 242 return RTEnvUnsetEx(m_hEnv, rName.c_str()); 243 } 244 245 #if 0 246 private: 247 /* No copy operator. */ 248 GuestEnvironmentBase(const GuestEnvironmentBase &) { throw E_FAIL; } 249 #else 250 /** 251 * Copy constructor. 252 * @throws HRESULT 253 */ 254 GuestEnvironmentBase(const GuestEnvironmentBase &rThat, bool fChangeRecord) 255 : m_hEnv(NIL_RTENV) 256 { 257 int rc = cloneCommon(rThat, fChangeRecord); 258 if (RT_FAILURE(rc)) 259 throw (Global::vboxStatusCodeToCOM(rc)); 260 } 261 #endif 262 263 protected: 264 /** 265 * Common clone/copy method with type conversion abilities. 266 * 267 * @returns IPRT status code. 268 * @param rThat The object to clone. 269 * @param fChangeRecord Whether the this instance is a change record (true) 270 * or normal (false) environment. 271 */ 272 int cloneCommon(const GuestEnvironmentBase &rThat, bool fChangeRecord) 273 { 274 int rc = VINF_SUCCESS; 275 RTENV hNewEnv = NIL_RTENV; 276 if (rThat.m_hEnv != NIL_RTENV) 277 { 278 if (RTEnvIsChangeRecord(rThat.m_hEnv) == fChangeRecord) 279 rc = RTEnvClone(&hNewEnv, rThat.m_hEnv); 280 else 281 { 282 /* Need to type convert it. */ 283 if (fChangeRecord) 284 rc = RTEnvCreateChangeRecord(&hNewEnv); 285 else 286 rc = RTEnvCreate(&hNewEnv); 287 if (RT_SUCCESS(rc)) 288 { 289 rc = RTEnvApplyChanges(hNewEnv, rThat.m_hEnv); 290 if (RT_FAILURE(rc)) 291 RTEnvDestroy(hNewEnv); 292 } 293 } 294 295 } 296 if (RT_SUCCESS(rc)) 297 { 298 RTEnvDestroy(m_hEnv); 299 m_hEnv = hNewEnv; 300 } 301 return rc; 302 } 303 304 305 /** The environment change record. */ 306 RTENV m_hEnv; 307 }; 308 309 310 #if 0 /* Not currently used. */ 311 /** 312 * Wrapper around the RTEnv API for a normal environment. 313 */ 314 class GuestEnvironment : public GuestEnvironmentBase 315 { 316 public: 317 /** 318 * Default constructor. 319 * 320 * The user must invoke one of the init methods before using the object. 321 */ 322 GuestEnvironment(void) 323 : GuestEnvironmentBase() 324 { } 325 326 /** 327 * Copy operator. 328 * @param rThat The object to copy. 329 * @throws HRESULT 330 */ 331 GuestEnvironment(const GuestEnvironment &rThat) 332 : GuestEnvironmentBase(rThat, false /*fChangeRecord*/) 333 { } 334 335 /** 336 * Copy operator. 337 * @param rThat The object to copy. 338 * @throws HRESULT 339 */ 340 GuestEnvironment(const GuestEnvironmentBase &rThat) 341 : GuestEnvironmentBase(rThat, false /*fChangeRecord*/) 342 { } 343 344 /** 345 * Initialize this as a normal environment block. 346 * @returns IPRT status code. 347 */ 348 int initNormal(void) 349 { 350 AssertReturn(m_hEnv == NIL_RTENV, VERR_WRONG_ORDER); 351 return RTEnvCreate(&m_hEnv); 352 } 353 354 /** 355 * Replaces this environemnt with that in @a rThat. 356 * 357 * @returns IPRT status code 358 * @param rThat The environment to copy. If it's a different type 359 * we'll convert the data to a normal environment block. 360 */ 361 int copy(const GuestEnvironmentBase &rThat) 362 { 363 return cloneCommon(rThat, false /*fChangeRecord*/); 364 } 365 }; 366 #endif /* unused */ 367 368 369 /** 370 * Wrapper around the RTEnv API for a environment change record. 371 * 372 * This class is used as a record of changes to be applied to a different 373 * environment block (in VBoxService before launching a new process). 374 */ 375 class GuestEnvironmentChanges : public GuestEnvironmentBase 376 { 377 public: 378 /** 379 * Default constructor. 380 * 381 * The user must invoke one of the init methods before using the object. 382 */ 383 GuestEnvironmentChanges(void) 384 : GuestEnvironmentBase() 385 { } 386 387 /** 388 * Copy operator. 389 * @param rThat The object to copy. 390 * @throws HRESULT 391 */ 392 GuestEnvironmentChanges(const GuestEnvironmentChanges &rThat) 393 : GuestEnvironmentBase(rThat, true /*fChangeRecord*/) 394 { } 395 396 /** 397 * Copy operator. 398 * @param rThat The object to copy. 399 * @throws HRESULT 400 */ 401 GuestEnvironmentChanges(const GuestEnvironmentBase &rThat) 402 : GuestEnvironmentBase(rThat, true /*fChangeRecord*/) 403 { } 404 405 /** 406 * Initialize this as a environment change record. 407 * @returns IPRT status code. 408 */ 409 int initChangeRecord(void) 410 { 411 AssertReturn(m_hEnv == NIL_RTENV, VERR_WRONG_ORDER); 412 return RTEnvCreateChangeRecord(&m_hEnv); 413 } 414 415 /** 416 * Replaces this environemnt with that in @a rThat. 417 * 418 * @returns IPRT status code 419 * @param rThat The environment to copy. If it's a different type 420 * we'll convert the data to a set of changes. 421 */ 422 int copy(const GuestEnvironmentBase &rThat) 423 { 424 return cloneCommon(rThat, true /*fChangeRecord*/); 425 } 104 426 }; 105 427 … … 225 547 /** Arguments vector (starting with argument \#0). */ 226 548 ProcessArguments mArguments; 227 GuestEnvironment mEnvironment; 549 /** The process environment change record. */ 550 GuestEnvironmentChanges mEnvironment; 228 551 /** Process creation flags. */ 229 552 uint32_t mFlags; -
trunk/src/VBox/Main/include/GuestSessionImpl.h
r55541 r55588 391 391 public: 392 392 /** @name Public internal methods. 393 * @todo r=bird: Most of these are public for no real reason... 393 394 * @{ */ 394 395 int i_closeSession(uint32_t uFlags, uint32_t uTimeoutMS, int *pGuestRc); … … 415 416 int i_fsQueryInfoInternal(const Utf8Str &strPath, GuestFsObjData &objData, int *pGuestRc); 416 417 const GuestCredentials &i_getCredentials(void); 417 const GuestEnvironment &i_getEnvironment(void);418 418 EventSource *i_getEventSource(void) { return mEventSource; } 419 419 Utf8Str i_getName(void); … … 441 441 int i_startTaskAsync(const Utf8Str &strTaskDesc, GuestSessionTask *pTask, 442 442 ComObjPtr<Progress> &pProgress); 443 int i_ queryInfo(void);443 int i_determineProtocolVersion(void); 444 444 int i_waitFor(uint32_t fWaitFlags, ULONG uTimeoutMS, GuestSessionWaitResult_T &waitResult, int *pGuestRc); 445 445 int i_waitForStatusChange(GuestWaitEvent *pEvent, uint32_t fWaitFlags, uint32_t uTimeoutMS, … … 470 470 /** The session's current status. */ 471 471 GuestSessionStatus_T mStatus; 472 /** The se ssion's environment block. Can be473 * overwritten/extended by ProcessCreate(Ex). */474 GuestEnvironment 472 /** The set of environment changes for the session for use when 473 * creating new guest processes. */ 474 GuestEnvironmentChanges mEnvironment; 475 475 /** Directory objects bound to this session. */ 476 476 SessionDirectories mDirectories; -
trunk/src/VBox/Main/src-client/GuestCtrlPrivate.cpp
r52981 r55588 1 1 /* $Id$ */ 2 2 /** @file 3 *4 3 * Internal helpers/structures for guest control functionality. 5 4 */ 6 5 7 6 /* 8 * Copyright (C) 2011-201 4Oracle Corporation7 * Copyright (C) 2011-2015 Oracle Corporation 9 8 * 10 9 * This file is part of VirtualBox Open Source Edition (OSE), as … … 37 36 #include <VBox/log.h> 38 37 39 /****************************************************************************** 40 * Structures and Typedefs * 41 ******************************************************************************/ 42 43 int GuestEnvironment::BuildEnvironmentBlock(void **ppvEnv, size_t *pcbEnv, uint32_t *pcEnvVars) 44 { 45 AssertPtrReturn(ppvEnv, VERR_INVALID_POINTER); 46 /* Rest is optional. */ 47 48 size_t cbEnv = 0; 49 uint32_t cEnvVars = 0; 50 51 int rc = VINF_SUCCESS; 52 53 size_t cEnv = mEnvironment.size(); 54 if (cEnv) 55 { 56 std::map<Utf8Str, Utf8Str>::const_iterator itEnv = mEnvironment.begin(); 57 for (; itEnv != mEnvironment.end() && RT_SUCCESS(rc); itEnv++) 58 { 59 char *pszEnv; 60 if (!RTStrAPrintf(&pszEnv, "%s=%s", itEnv->first.c_str(), itEnv->second.c_str())) 61 { 62 rc = VERR_NO_MEMORY; 63 break; 64 } 65 AssertPtr(pszEnv); 66 rc = appendToEnvBlock(pszEnv, ppvEnv, &cbEnv, &cEnvVars); 67 RTStrFree(pszEnv); 68 } 69 Assert(cEnv == cEnvVars); 70 } 71 72 if (pcbEnv) 73 *pcbEnv = cbEnv; 74 if (pcEnvVars) 75 *pcEnvVars = cEnvVars; 76 77 return rc; 78 } 79 80 void GuestEnvironment::Clear(void) 81 { 82 mEnvironment.clear(); 83 } 84 85 int GuestEnvironment::CopyFrom(const GuestEnvironmentArray &environment) 86 { 87 int rc = VINF_SUCCESS; 88 89 for (GuestEnvironmentArray::const_iterator it = environment.begin(); 90 it != environment.end() && RT_SUCCESS(rc); 91 ++it) 92 { 93 rc = Set((*it)); 94 } 95 96 return rc; 97 } 98 99 int GuestEnvironment::CopyTo(GuestEnvironmentArray &environment) 100 { 101 size_t s = 0; 102 for (std::map<Utf8Str, Utf8Str>::const_iterator it = mEnvironment.begin(); 103 it != mEnvironment.end(); 104 ++it, ++s) 105 { 106 environment[s] = Bstr(it->first + "=" + it->second).raw(); 107 } 108 109 return VINF_SUCCESS; 110 } 111 112 /* static */ 113 void GuestEnvironment::FreeEnvironmentBlock(void *pvEnv) 114 { 115 if (pvEnv) 116 RTMemFree(pvEnv); 117 } 118 119 Utf8Str GuestEnvironment::Get(size_t nPos) 120 { 121 size_t curPos = 0; 122 std::map<Utf8Str, Utf8Str>::const_iterator it = mEnvironment.begin(); 123 for (; it != mEnvironment.end() && curPos < nPos; 124 ++it, ++curPos) { } 125 126 if (it != mEnvironment.end()) 127 return Utf8Str(it->first + "=" + it->second); 128 129 return Utf8Str(""); 130 } 131 132 Utf8Str GuestEnvironment::Get(const Utf8Str &strKey) 133 { 134 std::map <Utf8Str, Utf8Str>::const_iterator itEnv = mEnvironment.find(strKey); 135 Utf8Str strRet; 136 if (itEnv != mEnvironment.end()) 137 strRet = itEnv->second; 138 return strRet; 139 } 140 141 bool GuestEnvironment::Has(const Utf8Str &strKey) 142 { 143 std::map <Utf8Str, Utf8Str>::const_iterator itEnv = mEnvironment.find(strKey); 144 return (itEnv != mEnvironment.end()); 145 } 146 147 int GuestEnvironment::Set(const Utf8Str &strKey, const Utf8Str &strValue) 148 { 149 /** @todo Do some validation using regex. */ 150 if (strKey.isEmpty()) 151 return VERR_INVALID_PARAMETER; 152 153 int rc = VINF_SUCCESS; 154 const char *pszString = strKey.c_str(); 155 while (*pszString != '\0' && RT_SUCCESS(rc)) 156 { 157 if ( !RT_C_IS_ALNUM(*pszString) 158 && !RT_C_IS_GRAPH(*pszString)) 159 rc = VERR_INVALID_PARAMETER; 160 *pszString++; 161 } 162 163 if (RT_SUCCESS(rc)) 164 mEnvironment[strKey] = strValue; 165 166 return rc; 167 } 168 169 int GuestEnvironment::Set(const Utf8Str &strPair) 170 { 171 RTCList<RTCString> listPair = strPair.split("=", RTCString::KeepEmptyParts); 172 /* Skip completely empty pairs. Note that we still need pairs with a valid 173 * (set) key and an empty value. */ 174 if (listPair.size() <= 1) 175 return VINF_SUCCESS; 176 177 int rc = VINF_SUCCESS; 178 size_t p = 0; 179 while (p < listPair.size() && RT_SUCCESS(rc)) 180 { 181 Utf8Str strKey = listPair.at(p++); 182 if ( strKey.isEmpty() 183 || strKey.equals("=")) /* Skip pairs with empty keys (e.g. "=FOO"). */ 184 { 185 break; 186 } 187 Utf8Str strValue; 188 if (p < listPair.size()) /* Does the list also contain a value? */ 189 strValue = listPair.at(p++); 190 191 #ifdef DEBUG 192 LogFlowFunc(("strKey=%s, strValue=%s\n", 193 strKey.c_str(), strValue.c_str())); 194 #endif 195 rc = Set(strKey, strValue); 196 } 197 198 return rc; 199 } 200 201 size_t GuestEnvironment::Size(void) 202 { 203 return mEnvironment.size(); 204 } 205 206 int GuestEnvironment::Unset(const Utf8Str &strKey) 207 { 208 std::map <Utf8Str, Utf8Str>::iterator itEnv = mEnvironment.find(strKey); 209 if (itEnv != mEnvironment.end()) 210 { 211 mEnvironment.erase(itEnv); 212 return VINF_SUCCESS; 213 } 214 215 return VERR_NOT_FOUND; 216 } 217 218 GuestEnvironment& GuestEnvironment::operator=(const GuestEnvironmentArray &that) 219 { 220 CopyFrom(that); 221 return *this; 222 } 223 224 GuestEnvironment& GuestEnvironment::operator=(const GuestEnvironment &that) 225 { 226 for (std::map<Utf8Str, Utf8Str>::const_iterator it = that.mEnvironment.begin(); 227 it != that.mEnvironment.end(); 228 ++it) 229 { 230 mEnvironment[it->first] = it->second; 231 } 232 233 return *this; 234 } 235 236 /** 237 * Appends environment variables to the environment block. 238 * 239 * Each var=value pair is separated by the null character ('\\0'). The whole 240 * block will be stored in one blob and disassembled on the guest side later to 241 * fit into the HGCM param structure. 242 * 243 * @returns VBox status code. 244 * 245 * @param pszEnvVar The environment variable=value to append to the 246 * environment block. 247 * @param ppvList This is actually a pointer to a char pointer 248 * variable which keeps track of the environment block 249 * that we're constructing. 250 * @param pcbList Pointer to the variable holding the current size of 251 * the environment block. (List is a misnomer, go 252 * ahead a be confused.) 253 * @param pcEnvVars Pointer to the variable holding count of variables 254 * stored in the environment block. 255 */ 256 int GuestEnvironment::appendToEnvBlock(const char *pszEnv, void **ppvList, size_t *pcbList, uint32_t *pcEnvVars) 257 { 258 int rc = VINF_SUCCESS; 259 size_t cchEnv = strlen(pszEnv); Assert(cchEnv >= 2); 260 if (*ppvList) 261 { 262 size_t cbNewLen = *pcbList + cchEnv + 1; /* Include zero termination. */ 263 char *pvTmp = (char *)RTMemRealloc(*ppvList, cbNewLen); 264 if (pvTmp == NULL) 265 rc = VERR_NO_MEMORY; 266 else 267 { 268 memcpy(pvTmp + *pcbList, pszEnv, cchEnv); 269 pvTmp[cbNewLen - 1] = '\0'; /* Add zero termination. */ 270 *ppvList = (void **)pvTmp; 271 } 272 } 273 else 274 { 275 char *pszTmp; 276 if (RTStrAPrintf(&pszTmp, "%s", pszEnv) >= 0) 277 { 278 *ppvList = (void **)pszTmp; 279 /* Reset counters. */ 280 *pcEnvVars = 0; 281 *pcbList = 0; 282 } 283 } 284 if (RT_SUCCESS(rc)) 285 { 286 *pcbList += cchEnv + 1; /* Include zero termination. */ 287 *pcEnvVars += 1; /* Increase env variable count. */ 288 } 289 return rc; 290 } 38 291 39 292 40 int GuestFsObjData::FromLs(const GuestProcessStreamBlock &strmBlk) -
trunk/src/VBox/Main/src-client/GuestProcessImpl.cpp
r55540 r55588 304 304 305 305 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); 306 mData.mProcess.mEnvironment. CopyTo(aEnvironment);306 mData.mProcess.mEnvironment.queryPutEnvArray(&aEnvironment); 307 307 return S_OK; 308 308 #endif /* VBOX_WITH_GUEST_CONTROL */ … … 1054 1054 1055 1055 /* Prepare environment. */ 1056 void *pvEnv = NULL;1057 size_t cbEnv = 0;1056 size_t cbEnvBlock; 1057 char *pszzEnvBlock; 1058 1058 if (RT_SUCCESS(vrc)) 1059 vrc = mData.mProcess.mEnvironment. BuildEnvironmentBlock(&pvEnv, &cbEnv, NULL /* cEnv */);1059 vrc = mData.mProcess.mEnvironment.queryUtf8Block(&pszzEnvBlock, &cbEnvBlock); 1060 1060 1061 1061 if (RT_SUCCESS(vrc)) … … 1065 1065 int i = 0; 1066 1066 paParms[i++].setUInt32(pEvent->ContextID()); 1067 paParms[i++].setPointer((void*)mData.mProcess.mExecutable.c_str(), 1068 (ULONG)mData.mProcess.mExecutable.length() + 1); 1067 paParms[i++].setCppString(mData.mProcess.mExecutable); 1069 1068 paParms[i++].setUInt32(mData.mProcess.mFlags); 1070 1069 paParms[i++].setUInt32((uint32_t)mData.mProcess.mArguments.size()); 1071 paParms[i++].setPointer( (void*)pszArgs, (uint32_t)cbArgs);1072 paParms[i++].setUInt32( (uint32_t)mData.mProcess.mEnvironment.Size());1073 paParms[i++].setUInt32((uint32_t)cbEnv );1074 paParms[i++].setPointer( (void*)pvEnv, (uint32_t)cbEnv);1070 paParms[i++].setPointer(pszArgs, (uint32_t)cbArgs); 1071 paParms[i++].setUInt32(mData.mProcess.mEnvironment.count()); 1072 paParms[i++].setUInt32((uint32_t)cbEnvBlock); 1073 paParms[i++].setPointer(pszzEnvBlock, (uint32_t)cbEnvBlock); 1075 1074 if (uProtocol < 2) 1076 1075 { … … 1078 1077 * call. In newer protocols these credentials are part of the opened guest 1079 1078 * session, so not needed anymore here. */ 1080 paParms[i++].set Pointer((void*)sessionCreds.mUser.c_str(), (ULONG)sessionCreds.mUser.length() + 1);1081 paParms[i++].set Pointer((void*)sessionCreds.mPassword.c_str(), (ULONG)sessionCreds.mPassword.length() + 1);1079 paParms[i++].setCppString(sessionCreds.mUser); 1080 paParms[i++].setCppString(sessionCreds.mPassword); 1082 1081 } 1083 1082 /* … … 1098 1097 paParms[i++].setUInt32(1); 1099 1098 /* The actual CPU affinity blocks. */ 1100 paParms[i++].setPointer((void *)&mData.mProcess.mAffinity, sizeof(mData.mProcess.mAffinity));1099 paParms[i++].setPointer((void *)&mData.mProcess.mAffinity, sizeof(mData.mProcess.mAffinity)); 1101 1100 } 1102 1101 … … 1109 1108 AssertRC(rc2); 1110 1109 } 1111 } 1112 1113 GuestEnvironment::FreeEnvironmentBlock(pvEnv); 1110 1111 mData.mProcess.mEnvironment.freeUtf8Block(pszzEnvBlock); 1112 } 1113 1114 1114 if (pszArgs) 1115 1115 RTStrFree(pszArgs); -
trunk/src/VBox/Main/src-client/GuestSessionImpl.cpp
r55535 r55588 183 183 AssertPtrReturn(pGuest, VERR_INVALID_POINTER); 184 184 185 /* 186 * Initialize our data members from the input. 187 */ 185 188 mParent = pGuest; 186 189 … … 198 201 mData.mCredentials.mDomain = guestCreds.mDomain; 199 202 203 /* Initialize the remainder of the data. */ 200 204 mData.mRC = VINF_SUCCESS; 201 205 mData.mStatus = GuestSessionStatus_Undefined; 202 206 mData.mNumObjects = 0; 203 204 HRESULT hr; 205 206 int rc = i_queryInfo(); 207 int rc = mData.mEnvironment.initNormal(); 207 208 if (RT_SUCCESS(rc)) 208 209 { 209 hr = unconst(mEventSource).createObject(); 210 if (FAILED(hr)) 211 rc = VERR_NO_MEMORY; 212 else 213 { 210 rc = RTCritSectInit(&mWaitEventCritSect); 211 AssertRC(rc); 212 } 213 if (RT_SUCCESS(rc)) 214 rc = i_determineProtocolVersion(); 215 if (RT_SUCCESS(rc)) 216 { 217 /* 218 * <Replace this if you figure out what the code is doing.> 219 */ 220 HRESULT hr = unconst(mEventSource).createObject(); 221 if (SUCCEEDED(hr)) 214 222 hr = mEventSource->init(); 215 if (FAILED(hr)) 216 rc = VERR_COM_UNEXPECTED; 217 } 218 } 219 220 if (RT_SUCCESS(rc)) 221 { 222 try 223 { 224 GuestSessionListener *pListener = new GuestSessionListener(); 225 ComObjPtr<GuestSessionListenerImpl> thisListener; 226 hr = thisListener.createObject(); 227 if (SUCCEEDED(hr)) 228 hr = thisListener->init(pListener, this); 229 230 if (SUCCEEDED(hr)) 223 if (SUCCEEDED(hr)) 224 { 225 try 231 226 { 232 com::SafeArray <VBoxEventType_T> eventTypes;233 eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged);234 hr = mEventSource->RegisterListener(thisListener,235 ComSafeArrayAsInParam(eventTypes),236 TRUE /* Active listener */);227 GuestSessionListener *pListener = new GuestSessionListener(); 228 ComObjPtr<GuestSessionListenerImpl> thisListener; 229 hr = thisListener.createObject(); 230 if (SUCCEEDED(hr)) 231 hr = thisListener->init(pListener, this); 237 232 if (SUCCEEDED(hr)) 238 233 { 239 mLocalListener = thisListener; 240 241 rc = RTCritSectInit(&mWaitEventCritSect); 242 AssertRC(rc); 234 com::SafeArray <VBoxEventType_T> eventTypes; 235 eventTypes.push_back(VBoxEventType_OnGuestSessionStateChanged); 236 hr = mEventSource->RegisterListener(thisListener, 237 ComSafeArrayAsInParam(eventTypes), 238 TRUE /* Active listener */); 239 if (SUCCEEDED(hr)) 240 { 241 mLocalListener = thisListener; 242 243 /* 244 * Mark this object as operational and return success. 245 */ 246 autoInitSpan.setSucceeded(); 247 LogFlowThisFunc(("mName=%s mID=%RU32 mIsInternal=%RTbool rc=VINF_SUCCESS\n", 248 mData.mSession.mName.c_str(), mData.mSession.mID, mData.mSession.mIsInternal)); 249 return VINF_SUCCESS; 250 } 243 251 } 244 else245 rc = VERR_COM_UNEXPECTED;246 252 } 247 else 248 rc = VERR_COM_UNEXPECTED; 249 } 250 catch(std::bad_alloc &) 251 { 252 rc = VERR_NO_MEMORY; 253 } 254 } 255 256 if (RT_SUCCESS(rc)) 257 { 258 /* Confirm a successful initialization when it's the case. */ 259 autoInitSpan.setSucceeded(); 260 } 261 else 262 autoInitSpan.setFailed(); 263 264 LogFlowThisFunc(("mName=%s, mID=%RU32, mIsInternal=%RTbool, rc=%Rrc\n", 265 mData.mSession.mName.c_str(), mData.mSession.mID, mData.mSession.mIsInternal, rc)); 253 catch (std::bad_alloc &) 254 { 255 hr = E_OUTOFMEMORY; 256 } 257 } 258 rc = Global::vboxStatusCodeFromCOM(hr); 259 } 260 261 autoInitSpan.setFailed(); 262 LogThisFunc(("Failed! mName=%s mID=%RU32 mIsInternal=%RTbool => rc=%Rrc\n", 263 mData.mSession.mName.c_str(), mData.mSession.mID, mData.mSession.mIsInternal, rc)); 266 264 return rc; 267 265 #endif /* VBOX_WITH_GUEST_CONTROL */ … … 322 320 mData.mProcesses.clear(); 323 321 322 mData.mEnvironment.reset(); 323 324 324 AssertMsg(mData.mNumObjects == 0, 325 325 ("mNumObjects=%RU32 when it should be 0\n", mData.mNumObjects)); … … 471 471 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); 472 472 473 size_t cEnvVars = mData.mEnvironment.Size(); 474 aEnvironment.resize(cEnvVars); 475 476 LogFlowThisFunc(("[%s]: cEnvVars=%RU32\n", 477 mData.mSession.mName.c_str(), cEnvVars)); 478 479 for (size_t i = 0; i < cEnvVars; i++) 480 aEnvironment[i] = mData.mEnvironment.Get(i); 481 482 LogFlowThisFuncLeave(); 483 return S_OK; 473 int vrc = mData.mEnvironment.queryPutEnvArray(&aEnvironment); 474 475 LogFlowFuncLeaveRC(vrc); 476 return Global::vboxStatusCodeToCOM(vrc); 484 477 #endif /* VBOX_WITH_GUEST_CONTROL */ 485 478 } … … 494 487 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); 495 488 496 int rc = VINF_SUCCESS; 497 for (size_t i = 0; i < aEnvironment.size() && RT_SUCCESS(rc); ++i) 498 if (!aEnvironment[i].isEmpty()) /* Silently skip empty entries. */ 499 rc = mData.mEnvironment.Set(aEnvironment[i]); 500 501 HRESULT hr = RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR; 502 LogFlowFuncLeaveRC(hr); 503 return hr; 489 mData.mEnvironment.reset(); 490 int vrc = mData.mEnvironment.applyPutEnvArray(aEnvironment); 491 492 LogFlowFuncLeaveRC(vrc); 493 return Global::vboxStatusCodeToCOM(vrc); 504 494 #endif /* VBOX_WITH_GUEST_CONTROL */ 505 495 } … … 1414 1404 { 1415 1405 return mData.mCredentials; 1416 }1417 1418 const GuestEnvironment& GuestSession::i_getEnvironment(void)1419 {1420 return mData.mEnvironment;1421 1406 } 1422 1407 … … 2118 2103 2119 2104 /** 2120 * Queries/collects information prior to establishing a guest session. 2121 * This is necessary to know which guest control protocol version to use, 2122 * among other things (later). 2105 * Determines the protocol version (sets mData.mProtocolVersion). 2106 * 2107 * This is called from the init method prior to to establishing a guest 2108 * session. 2123 2109 * 2124 2110 * @return IPRT status code. 2125 2111 */ 2126 int GuestSession::i_ queryInfo(void)2112 int GuestSession::i_determineProtocolVersion(void) 2127 2113 { 2128 2114 /* 2129 * Try querying the guest control protocol version running on the guest.2130 * This is done using the Guest Additions version2115 * We currently do this based on the reported guest additions version, 2116 * ASSUMING that VBoxService and VBoxDrv are at the same version. 2131 2117 */ 2132 2118 ComObjPtr<Guest> pGuest = mParent; 2133 Assert(!pGuest.isNull()); 2134 2135 uint32_t uVerAdditions = pGuest->i_getAdditionsVersion(); 2136 uint32_t uVBoxMajor = VBOX_FULL_VERSION_GET_MAJOR(uVerAdditions); 2137 uint32_t uVBoxMinor = VBOX_FULL_VERSION_GET_MINOR(uVerAdditions); 2138 2139 #ifdef DEBUG_andy 2140 /* Hardcode the to-used protocol version; nice for testing side effects. */ 2141 mData.mProtocolVersion = 2; 2142 #else 2143 mData.mProtocolVersion = ( 2144 /* VBox 5.0 and up. */ 2145 uVBoxMajor >= 5 2146 /* VBox 4.3 and up. */ 2147 || (uVBoxMajor == 4 && uVBoxMinor >= 3)) 2148 ? 2 /* Guest control 2.0. */ 2149 : 1; /* Legacy guest control (VBox < 4.3). */ 2150 /* Build revision is ignored. */ 2151 #endif 2152 2153 LogFlowThisFunc(("uVerAdditions=%RU32 (%RU32.%RU32), mProtocolVersion=%RU32\n", 2154 uVerAdditions, uVBoxMajor, uVBoxMinor, mData.mProtocolVersion)); 2155 2156 /* Tell the user but don't bitch too often. */ 2157 /** @todo Find a bit nicer text. */ 2119 AssertReturn(!pGuest.isNull(), VERR_NOT_SUPPORTED); 2120 uint32_t uGaVersion = pGuest->i_getAdditionsVersion(); 2121 2122 /* Everyone supports version one, if they support anything at all. */ 2123 mData.mProtocolVersion = 1; 2124 2125 /* Guest control 2.0 was introduced with 4.3.0. */ 2126 if (uGaVersion >= VBOX_FULL_VERSION_MAKE(4,3,0)) 2127 mData.mProtocolVersion = 2; /* Guest control 2.0. */ 2128 2129 LogFlowThisFunc(("uGaVersion=%u.%u.%u => mProtocolVersion=%u\n", 2130 VBOX_FULL_VERSION_GET_MAJOR(uGaVersion), VBOX_FULL_VERSION_GET_MINOR(uGaVersion), 2131 VBOX_FULL_VERSION_GET_BUILD(uGaVersion), mData.mProtocolVersion)); 2132 2133 /* 2134 * Inform the user about outdated guest additions (VM release log). 2135 */ 2158 2136 if (mData.mProtocolVersion < 2) 2159 LogRelMax(3, (tr("Warning: Guest Additions are older (%ld.%ld) than host capabilities for guest control, please upgrade them. Using protocol version %ld now\n"), 2160 uVBoxMajor, uVBoxMinor, mData.mProtocolVersion)); 2137 LogRelMax(3, (tr("Warning: Guest Additions v%u.%u.%u only supports the older guest control protocol version %u.\n" 2138 " Please upgrade GAs to the current version to get full guest control capabilities.\n"), 2139 VBOX_FULL_VERSION_GET_MAJOR(uGaVersion), VBOX_FULL_VERSION_GET_MINOR(uGaVersion), 2140 VBOX_FULL_VERSION_GET_BUILD(uGaVersion), mData.mProtocolVersion)); 2161 2141 2162 2142 return VINF_SUCCESS; … … 2924 2904 } 2925 2905 2906 /** @todo remove this it duplicates the 'environment' attribute. */ 2926 2907 HRESULT GuestSession::environmentClear() 2927 2908 { … … 2933 2914 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); 2934 2915 2935 mData.mEnvironment. Clear();2916 mData.mEnvironment.reset(); 2936 2917 2937 2918 LogFlowThisFuncLeave(); … … 2940 2921 } 2941 2922 2923 /** @todo Remove this because the interface isn't suitable for returning 'unset' 2924 * or empty values, and it can easily be misunderstood. Besides there 2925 * is hardly a usecase for it as long as it just works on 2926 * environment changes and there is the 'environment' attribute. */ 2942 2927 HRESULT GuestSession::environmentGet(const com::Utf8Str &aName, com::Utf8Str &aValue) 2943 2928 { … … 2947 2932 LogFlowThisFuncEnter(); 2948 2933 2949 if (RT_UNLIKELY(aName.c_str() == NULL) || *(aName.c_str()) == '\0') 2950 return setError(E_INVALIDARG, tr("No value name specified")); 2951 2952 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); 2953 2954 aValue = mData.mEnvironment.Get(aName); 2934 HRESULT hrc; 2935 if (RT_LIKELY(aName.isNotEmpty())) 2936 { 2937 if (RT_LIKELY(strchr(aName.c_str(), '=') == NULL)) 2938 { 2939 AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); 2940 2941 mData.mEnvironment.getVariable(aName, &aValue); 2942 hrc = S_OK; 2943 } 2944 else 2945 hrc = setError(E_INVALIDARG, tr("The equal char is not allowed in environment variable names")); 2946 } 2947 else 2948 hrc = setError(E_INVALIDARG, tr("No variable name specified")); 2955 2949 2956 2950 LogFlowThisFuncLeave(); 2957 return S_OK;2951 return hrc; 2958 2952 #endif /* VBOX_WITH_GUEST_CONTROL */ 2959 2953 } … … 2966 2960 LogFlowThisFuncEnter(); 2967 2961 2968 if (RT_UNLIKELY((aName.c_str() == NULL) || *(aName.c_str()) == '\0')) 2969 return setError(E_INVALIDARG, tr("No value name specified")); 2970 2971 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); 2972 2973 int rc = mData.mEnvironment.Set(aName, aValue); 2974 2975 HRESULT hr = RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR; 2976 LogFlowFuncLeaveRC(hr); 2977 return hr; 2962 HRESULT hrc; 2963 if (RT_LIKELY(aName.isNotEmpty())) 2964 { 2965 if (RT_LIKELY(strchr(aName.c_str(), '=') == NULL)) 2966 { 2967 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); 2968 int vrc = mData.mEnvironment.setVariable(aName, aValue); 2969 if (RT_SUCCESS(vrc)) 2970 hrc = S_OK; 2971 else 2972 hrc = setErrorVrc(vrc); 2973 } 2974 else 2975 hrc = setError(E_INVALIDARG, tr("The equal char is not allowed in environment variable names")); 2976 } 2977 else 2978 hrc = setError(E_INVALIDARG, tr("No variable name specified")); 2979 2980 LogFlowThisFuncLeave(); 2981 return hrc; 2978 2982 #endif /* VBOX_WITH_GUEST_CONTROL */ 2979 2983 } … … 2985 2989 #else 2986 2990 LogFlowThisFuncEnter(); 2987 2988 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); 2989 2990 mData.mEnvironment.Unset(aName); 2991 HRESULT hrc; 2992 if (RT_LIKELY(aName.isNotEmpty())) 2993 { 2994 if (RT_LIKELY(strchr(aName.c_str(), '=') == NULL)) 2995 { 2996 AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); 2997 int vrc = mData.mEnvironment.unsetVariable(aName); 2998 if (RT_SUCCESS(vrc)) 2999 hrc = S_OK; 3000 else 3001 hrc = setErrorVrc(vrc); 3002 } 3003 else 3004 hrc = setError(E_INVALIDARG, tr("The equal char is not allowed in environment variable names")); 3005 } 3006 else 3007 hrc = setError(E_INVALIDARG, tr("No variable name specified")); 2991 3008 2992 3009 LogFlowThisFuncLeave(); 2993 return S_OK;3010 return hrc; 2994 3011 #endif /* VBOX_WITH_GUEST_CONTROL */ 2995 3012 } … … 3338 3355 LogFlowThisFuncEnter(); 3339 3356 3357 /** @todo r=bird: Check input better? aPriority is passed on to the guest 3358 * without any validation. Flags not existing in this vbox version are 3359 * ignored, potentially doing something entirely different than what the 3360 * caller had in mind. */ 3361 3340 3362 /* 3341 3363 * Must have an executable to execute. If none is given, we try use the … … 3369 3391 procInfo.mArguments.push_back(aArguments[i]); 3370 3392 3371 /* 3372 * Create the process environment: 3373 * - Apply the session environment in a first step, and 3374 * - Apply environment variables specified by this call to 3375 * have the chance of overwriting/deleting session entries. 3376 */ 3377 procInfo.mEnvironment = mData.mEnvironment; /* Apply original session environment. */ 3378 3379 int rc = VINF_SUCCESS; 3380 if (aEnvironment.size()) 3381 for (size_t i = 0; i < aEnvironment.size() && RT_SUCCESS(rc); i++) 3382 { 3383 /** @todo r=bird: What ARE you trying to do here??? The documentation is crystal 3384 * clear on that each entry contains ONE pair, however, 3385 * GuestEnvironment::Set(const Utf8Str &) here will split up the input 3386 * into any number of pairs, from what I can tell. Such that for e.g. 3387 * "VBOX_LOG_DEST=file=/tmp/foobared.log" becomes "VBOX_LOG_DEST=file" 3388 * and "/tmp/foobared.log" - which I obviously don't want! */ 3389 rc = procInfo.mEnvironment.Set(aEnvironment[i]); 3390 } 3391 3392 if (RT_SUCCESS(rc)) 3393 /* Combine the environment changes associated with the ones passed in by 3394 the caller, giving priority to the latter. The changes are putenv style 3395 and will be applied to the standard environment for the guest user. */ 3396 int vrc = procInfo.mEnvironment.copy(mData.mEnvironment); 3397 if (RT_SUCCESS(vrc)) 3398 vrc = procInfo.mEnvironment.applyPutEnvArray(aEnvironment); 3399 if (RT_SUCCESS(vrc)) 3393 3400 { 3394 3401 /* Convert the flag array into a mask. */ … … 3411 3418 */ 3412 3419 ComObjPtr<GuestProcess> pProcess; 3413 rc = i_processCreateExInternal(procInfo, pProcess);3414 if (RT_SUCCESS( rc))3420 vrc = i_processCreateExInternal(procInfo, pProcess); 3421 if (RT_SUCCESS(vrc)) 3415 3422 { 3416 3423 /* Return guest session to the caller. */ 3417 HRESULT hr2= pProcess.queryInterfaceTo(aGuestProcess.asOutParam());3418 if (SUCCEEDED(hr 2))3424 hr = pProcess.queryInterfaceTo(aGuestProcess.asOutParam()); 3425 if (SUCCEEDED(hr)) 3419 3426 { 3420 3427 /* 3421 3428 * Start the process. 3422 3429 */ 3423 rc = pProcess->i_startProcessAsync();3424 if (RT_ FAILURE(rc))3430 vrc = pProcess->i_startProcessAsync(); 3431 if (RT_SUCCESS(vrc)) 3425 3432 { 3426 /** @todo r=bird: What happens to the interface that *aGuestProcess points to3427 * now? Looks like a leak or an undocument hack of sorts... */3433 LogFlowFuncLeaveRC(vrc); 3434 return S_OK; 3428 3435 } 3436 3437 hr = setErrorVrc(vrc, tr("Failed to start guest process: %Rrc"), vrc); 3438 /** @todo r=bird: What happens to the interface that *aGuestProcess points to 3439 * now? Looks like a leak or an undocument hack of sorts... */ 3429 3440 } 3430 else 3431 rc = VERR_COM_OBJECT_NOT_FOUND; 3432 } 3433 } 3434 3435 /** @todo you're better off doing this is 'else if (rc == xxx') statements, 3436 * since there is just one place where you'll get 3437 * VERR_MAX_PROCS_REACHED in the above code. */ 3438 if (RT_FAILURE(rc)) 3439 { 3440 switch (rc) 3441 { 3442 case VERR_MAX_PROCS_REACHED: 3443 hr = setError(VBOX_E_IPRT_ERROR, tr("Maximum number of concurrent guest processes per session (%ld) reached"), 3444 VBOX_GUESTCTRL_MAX_OBJECTS); 3445 break; 3446 3447 /** @todo Add more errors here. */ 3448 3449 default: 3450 hr = setError(VBOX_E_IPRT_ERROR, tr("Could not create guest process, rc=%Rrc"), rc); 3451 break; 3452 } 3453 } 3454 3455 LogFlowFuncLeaveRC(rc); 3441 } 3442 else if (vrc == VERR_MAX_PROCS_REACHED) 3443 hr = setErrorVrc(vrc, tr("Maximum number of concurrent guest processes per session (%u) reached"), 3444 VBOX_GUESTCTRL_MAX_OBJECTS); 3445 else 3446 hr = setErrorVrc(vrc, tr("Failed to create guest process object: %Rrc"), vrc); 3447 } 3448 else 3449 hr = setErrorVrc(vrc, tr("Failed to set up the environment: %Rrc"), vrc); 3450 3451 LogFlowFuncLeaveRC(vrc); 3456 3452 return hr; 3457 3453 #endif /* VBOX_WITH_GUEST_CONTROL */
Note:
See TracChangeset
for help on using the changeset viewer.