Changeset 39607 in vbox for trunk/src/VBox/HostServices/SharedFolders
- Timestamp:
- Dec 14, 2011 11:45:11 AM (13 years ago)
- svn:sync-xref-src-repo-rev:
- 75382
- Location:
- trunk/src/VBox/HostServices/SharedFolders
- Files:
-
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/HostServices/SharedFolders/mappings.cpp
r39542 r39607 113 113 } 114 114 115 static MAPPING *vbsfMappingGetByName (PRTUTF16 utf16Name, SHFLROOT *pRoot)115 static MAPPING *vbsfMappingGetByName (PRTUTF16 pwszName, SHFLROOT *pRoot) 116 116 { 117 117 unsigned i; … … 121 121 if (FolderMapping[i].fValid == true) 122 122 { 123 if (!RTUtf16LocaleICmp(FolderMapping[i].pMapName->String.ucs2, utf16Name))123 if (!RTUtf16LocaleICmp(FolderMapping[i].pMapName->String.ucs2, pwszName)) 124 124 { 125 125 SHFLROOT root = vbsfMappingGetRootFromIndex(i); … … 217 217 if (FolderMapping[i].fValid == false) 218 218 { 219 FolderMapping[i].pFolderName = (PSHFLSTRING)RTMemAlloc(ShflStringSizeOfBuffer(pFolderName)); 220 Assert(FolderMapping[i].pFolderName); 221 if (FolderMapping[i].pFolderName == NULL) 219 int rc = RTUtf16ToUtf8(pFolderName->String.ucs2, &FolderMapping[i].pszFolderName); 220 AssertRCReturn(rc, rc); 221 222 FolderMapping[i].pMapName = (PSHFLSTRING)RTMemAlloc(ShflStringSizeOfBuffer(pMapName)); 223 if (!FolderMapping[i].pMapName) 224 { 225 RTStrFree(FolderMapping[i].pszFolderName); 226 AssertFailed(); 222 227 return VERR_NO_MEMORY; 223 224 FolderMapping[i].pFolderName->u16Length = pFolderName->u16Length; 225 FolderMapping[i].pFolderName->u16Size = pFolderName->u16Size; 226 memcpy(FolderMapping[i].pFolderName->String.ucs2, pFolderName->String.ucs2, pFolderName->u16Size); 227 228 FolderMapping[i].pMapName = (PSHFLSTRING)RTMemAlloc(ShflStringSizeOfBuffer(pMapName)); 229 Assert(FolderMapping[i].pMapName); 230 if (FolderMapping[i].pMapName == NULL) 231 return VERR_NO_MEMORY; 228 } 232 229 233 230 FolderMapping[i].pMapName->u16Length = pMapName->u16Length; … … 242 239 /* Check if the host file system is case sensitive */ 243 240 RTFSPROPERTIES prop; 244 char *utf8Root, *asciiroot; 245 246 int rc = RTUtf16ToUtf8(FolderMapping[i].pFolderName->String.ucs2, &utf8Root); 247 AssertRC(rc); 248 241 char *pszAsciiRoot; 242 243 rc = RTStrUtf8ToCurrentCP(&pszAsciiRoot, FolderMapping[i].pszFolderName); 249 244 if (RT_SUCCESS(rc)) 250 245 { 251 rc = RTStrUtf8ToCurrentCP(&asciiroot, utf8Root); 252 if (RT_SUCCESS(rc)) 253 { 254 rc = RTFsQueryProperties(asciiroot, &prop); 255 AssertRC(rc); 256 RTStrFree(asciiroot); 257 } 258 RTStrFree(utf8Root); 246 rc = RTFsQueryProperties(pszAsciiRoot, &prop); 247 AssertRC(rc); 248 RTStrFree(pszAsciiRoot); 259 249 } 250 260 251 FolderMapping[i].fHostCaseSensitive = RT_SUCCESS(rc) ? prop.fCaseSensitive : false; 261 252 vbsfRootHandleAdd(i); … … 302 293 } 303 294 304 RT MemFree(FolderMapping[i].pFolderName);295 RTStrFree(FolderMapping[i].pszFolderName); 305 296 RTMemFree(FolderMapping[i].pMapName); 306 FolderMapping[i].p FolderName = NULL;307 FolderMapping[i].pMapName = NULL;308 FolderMapping[i].fValid = false;297 FolderMapping[i].pszFolderName = NULL; 298 FolderMapping[i].pMapName = NULL; 299 FolderMapping[i].fValid = false; 309 300 vbsfRootHandleRemove(i); 310 301 break; … … 322 313 } 323 314 324 PCRTUTF16 vbsfMappingsQueryHostRoot(SHFLROOT root, uint32_t *pcbRoot) 325 { 326 MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); 327 if (pFolderMapping == NULL) 328 { 329 AssertFailed(); 330 return NULL; 331 } 332 333 *pcbRoot = pFolderMapping->pFolderName->u16Size; 334 return &pFolderMapping->pFolderName->String.ucs2[0]; 315 const char* vbsfMappingsQueryHostRoot(SHFLROOT root) 316 { 317 MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); 318 AssertReturn(pFolderMapping, NULL); 319 return pFolderMapping->pszFolderName; 335 320 } 336 321 … … 338 323 { 339 324 MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); 340 if (pFolderMapping == NULL) 341 { 342 AssertFailed(); 343 return false; 344 } 345 325 AssertReturn(pFolderMapping, false); 346 326 return pFolderMapping->fGuestCaseSensitive; 347 327 } … … 350 330 { 351 331 MAPPING *pFolderMapping = vbsfMappingGetByRoot(root); 352 if (pFolderMapping == NULL) 353 { 354 AssertFailed(); 355 return false; 356 } 357 332 AssertReturn(pFolderMapping, false); 358 333 return pFolderMapping->fHostCaseSensitive; 359 334 } … … 534 509 #endif 535 510 int vbsfMapFolder(PSHFLCLIENTDATA pClient, PSHFLSTRING pszMapName, 536 RTUTF16 delimiter, bool fCaseSensitive, SHFLROOT *pRoot)511 RTUTF16 pwszDelimiter, bool fCaseSensitive, SHFLROOT *pRoot) 537 512 { 538 513 MAPPING *pFolderMapping = NULL; … … 549 524 if (pClient->PathDelimiter == 0) 550 525 { 551 pClient->PathDelimiter = delimiter;526 pClient->PathDelimiter = pwszDelimiter; 552 527 } 553 528 else 554 529 { 555 Assert( delimiter == pClient->PathDelimiter);530 Assert(pwszDelimiter == pClient->PathDelimiter); 556 531 } 557 532 -
trunk/src/VBox/HostServices/SharedFolders/mappings.h
r31052 r39607 23 23 typedef struct 24 24 { 25 PSHFLSTRING pFolderName;25 char *pszFolderName; 26 26 PSHFLSTRING pMapName; 27 27 uint32_t cMappings; … … 49 49 int vbsfUnmapFolder(PSHFLCLIENTDATA pClient, SHFLROOT root); 50 50 51 PCRTUTF16 vbsfMappingsQueryHostRoot(SHFLROOT root, uint32_t *pcbRoot);51 const char* vbsfMappingsQueryHostRoot(SHFLROOT root); 52 52 bool vbsfIsGuestMappingCaseSensitive(SHFLROOT root); 53 53 bool vbsfIsHostMappingCaseSensitive(SHFLROOT root); -
trunk/src/VBox/HostServices/SharedFolders/service.cpp
r39540 r39607 28 28 #include <VBox/vmm/pdmifs.h> 29 29 30 #define SHFL_SSM_VERSION 2 30 #define SHFL_SSM_VERSION_FOLDERNAME_UTF16 2 31 #define SHFL_SSM_VERSION 3 31 32 32 33 … … 146 147 uint32_t len; 147 148 148 len = ShflStringSizeOfBuffer(pFolderMapping->pFolderName);149 len = strlen(pFolderMapping->pszFolderName); 149 150 rc = SSMR3PutU32(pSSM, len); 150 151 AssertRCReturn(rc, rc); 151 152 152 rc = SSMR3Put Mem(pSSM, pFolderMapping->pFolderName, len);153 rc = SSMR3PutStrZ(pSSM, pFolderMapping->pszFolderName); 153 154 AssertRCReturn(rc, rc); 154 155 … … 184 185 AssertRCReturn(rc, rc); 185 186 186 if (version != SHFL_SSM_VERSION) 187 if ( version > SHFL_SSM_VERSION 188 || version < SHFL_SSM_VERSION_FOLDERNAME_UTF16) 187 189 return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; 188 190 … … 219 221 { 220 222 uint32_t cbFolderName; 221 PSHFLSTRING pFolderName;223 char *pszFolderName; 222 224 223 225 uint32_t cbMapName; … … 228 230 AssertRCReturn(rc, rc); 229 231 230 pFolderName = (PSHFLSTRING)RTMemAlloc(cbFolderName); 231 AssertReturn(pFolderName != NULL, VERR_NO_MEMORY); 232 233 rc = SSMR3GetMem(pSSM, pFolderName, cbFolderName); 234 AssertRCReturn(rc, rc); 232 if (version == SHFL_SSM_VERSION_FOLDERNAME_UTF16) 233 { 234 PSHFLSTRING pFolderName = (PSHFLSTRING)RTMemAlloc(cbFolderName); 235 AssertReturn(pFolderName != NULL, VERR_NO_MEMORY); 236 237 rc = SSMR3GetMem(pSSM, pFolderName, cbFolderName); 238 AssertRCReturn(rc, rc); 239 240 rc = RTUtf16ToUtf8(pFolderName->String.ucs2, &pszFolderName); 241 RTMemFree(pFolderName); 242 AssertRCReturn(rc, rc); 243 } 244 else 245 { 246 pszFolderName = (char*)RTStrAlloc(cbFolderName); 247 AssertReturn(pszFolderName, VERR_NO_MEMORY); 248 249 rc = SSMR3GetStrZ(pSSM, mapping.pszFolderName, cbFolderName); 250 AssertRCReturn(rc, rc); 251 mapping.pszFolderName = pszFolderName; 252 } 235 253 236 254 /* Load the map name. */ … … 250 268 AssertRCReturn(rc, rc); 251 269 252 mapping.p FolderName = pFolderName;270 mapping.pszFolderName = pszFolderName; 253 271 mapping.pMapName = pMapName; 254 272 … … 257 275 258 276 RTMemFree(pMapName); 259 RT MemFree(pFolderName);277 RTStrFree(pszFolderName); 260 278 261 279 AssertRCReturn(rc, rc); -
trunk/src/VBox/HostServices/SharedFolders/vbsf.cpp
r39594 r39607 253 253 { 254 254 int rc = VINF_SUCCESS; 255 256 255 char *pszFullPath = NULL; 257 258 /* Query UCS2 root prefix for the path, cbRoot is the length in bytes including trailing (RTUTF16)0. */ 259 uint32_t cbRoot = 0; 260 PCRTUTF16 pwszRoot = vbsfMappingsQueryHostRoot(root, &cbRoot); 261 262 if (!pwszRoot || cbRoot == 0) 256 size_t cbRoot; 257 const char *pszRoot = vbsfMappingsQueryHostRoot(root); 258 259 if ( !pszRoot 260 || !(cbRoot = strlen(pszRoot))) 263 261 { 264 262 Log(("vbsfBuildFullPath: invalid root!\n")); … … 268 266 if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) 269 267 { 270 char *utf8Root;271 272 268 /* Verify that the path is under the root directory. */ 273 269 rc = vbsfPathCheck((const char *)&pPath->String.utf8[0], pPath->u16Length); … … 275 271 if (RT_SUCCESS(rc)) 276 272 { 277 rc = RTUtf16ToUtf8(pwszRoot, &utf8Root); 278 } 279 280 if (RT_SUCCESS(rc)) 281 { 282 size_t cbUtf8Root, cbUtf8FullPath; 283 char *utf8FullPath; 284 285 cbUtf8Root = strlen(utf8Root); 286 cbUtf8FullPath = cbUtf8Root + 1 + pPath->u16Length + 1; 287 utf8FullPath = (char *) RTMemAllocZ(cbUtf8FullPath); 273 size_t cbUtf8FullPath = cbRoot + 1 + pPath->u16Length + 1; 274 char *utf8FullPath = (char *) RTMemAllocZ(cbUtf8FullPath); 288 275 289 276 if (!utf8FullPath) … … 295 282 else 296 283 { 297 memcpy(utf8FullPath, utf8Root, cbUtf8Root); 298 memcpy(utf8FullPath + cbUtf8Root + 1, 299 &pPath->String.utf8[0], 300 pPath->u16Length); 301 302 utf8FullPath[cbUtf8Root] = '/'; 284 memcpy(utf8FullPath, pszRoot, cbRoot); 285 utf8FullPath[cbRoot] = '/'; 286 memcpy(utf8FullPath + cbRoot + 1, &pPath->String.utf8[0], pPath->u16Length); 303 287 utf8FullPath[cbUtf8FullPath - 1] = 0; 304 288 pszFullPath = utf8FullPath; 305 289 306 290 if (pcbFullPathRoot) 307 *pcbFullPathRoot = (uint32_t)cbUtf8Root; /* Must index the path delimiter. */ 308 } 309 310 RTStrFree(utf8Root); 291 *pcbFullPathRoot = (uint32_t)cbRoot; /* Must index the path delimiter. */ 292 } 311 293 } 312 294 else … … 337 319 } 338 320 339 ::CFStringAppendCharacters(inStr, (UniChar*)pPathParameter->String.ucs2, pPathParameter->u16Length / sizeof(pPathParameter->String.ucs2[0])); 321 ::CFStringAppendCharacters(inStr, (UniChar*)pPathParameter->String.ucs2, 322 pPathParameter->u16Length / sizeof(pPathParameter->String.ucs2[0])); 340 323 ::CFStringNormalize(inStr, kCFStringNormalizationFormD); 341 324 ucs2Length = ::CFStringGetLength(inStr); … … 351 334 #endif 352 335 /* Client sends us UCS2, so convert it to UTF8. */ 353 Log(("Root %ls path %.*ls\n", pwszRoot, pPath->u16Length/sizeof(pPath->String.ucs2[0]), pPath->String.ucs2)); 354 355 /* Allocate buffer that will be able to contain 356 * the root prefix and the pPath converted to UTF8. 357 * Expect a 2 bytes UCS2 to be converted to 8 bytes UTF8 358 * in worst case. 336 Log(("Root %s path %.*ls\n", pszRoot, pPath->u16Length/sizeof(pPath->String.ucs2[0]), pPath->String.ucs2)); 337 338 /* Allocate buffer that will be able to contain the root prefix and 339 * the pPath converted to UTF8. Expect a 2 bytes UCS2 to be converted 340 * to 8 bytes UTF8 in the worst case. 359 341 */ 360 uint32_t cbFullPath = (cbRoot/sizeof(RTUTF16) + ShflStringLength(pPath)) * 4; 361 342 uint32_t cbFullPath = (cbRoot + ShflStringLength(pPath)) * 4; 362 343 pszFullPath = (char *)RTMemAllocZ(cbFullPath); 363 364 344 if (!pszFullPath) 365 345 { … … 368 348 else 369 349 { 370 uint32_t cb = cbFullPath; 371 372 rc = RTUtf16ToUtf8Ex(pwszRoot, RTSTR_MAX, &pszFullPath, cb, NULL); 373 if (RT_FAILURE(rc)) 374 { 375 AssertFailed(); 376 #ifdef RT_OS_DARWIN 377 RTMemFree(pPath); 378 pPath = pPathParameter; 379 #endif 380 return rc; 381 } 382 383 char *dst = pszFullPath; 384 385 cbRoot = (uint32_t)strlen(dst); 386 if (dst[cbRoot - 1] != RTPATH_DELIMITER) 387 { 388 dst[cbRoot] = RTPATH_DELIMITER; 389 cbRoot++; 350 memcpy(pszFullPath, pszRoot, cbRoot + 1); 351 char *pszDst = pszFullPath; 352 size_t cbDst = strlen(pszDst); 353 size_t cb = cbFullPath; 354 if (pszDst[cbDst - 1] != RTPATH_DELIMITER) 355 { 356 pszDst[cbDst] = RTPATH_DELIMITER; 357 cbDst++; 390 358 } 391 359 392 360 if (pcbFullPathRoot) 393 *pcbFullPathRoot = cb Root - 1; /* Must index the path delimiter. */394 395 dst += cbRoot;396 cb -= cbRoot;361 *pcbFullPathRoot = cbDst - 1; /* Must index the path delimiter. */ 362 363 pszDst += cbDst; 364 cb -= cbDst; 397 365 398 366 if (pPath->u16Length) 399 367 { 400 368 /* Convert and copy components. */ 401 PRTUTF16 src = &pPath->String.ucs2[0];369 PRTUTF16 pwszSrc = &pPath->String.ucs2[0]; 402 370 403 371 /* Correct path delimiters */ 404 372 if (pClient->PathDelimiter != RTPATH_DELIMITER) 405 373 { 406 LogFlow(("Correct path delimiter in %ls\n", src));407 while (* src)374 LogFlow(("Correct path delimiter in %ls\n", pwszSrc)); 375 while (*pwszSrc) 408 376 { 409 if (* src == pClient->PathDelimiter)410 * src = RTPATH_DELIMITER;411 src++;377 if (*pwszSrc == pClient->PathDelimiter) 378 *pwszSrc = RTPATH_DELIMITER; 379 pwszSrc++; 412 380 } 413 src = &pPath->String.ucs2[0];414 LogFlow(("Corrected string %ls\n", src));381 pwszSrc = &pPath->String.ucs2[0]; 382 LogFlow(("Corrected string %ls\n", pwszSrc)); 415 383 } 416 if (* src == RTPATH_DELIMITER)417 src++; /* we already appended a delimiter to the first part */418 419 rc = RTUtf16ToUtf8Ex( src, RTSTR_MAX, &dst, cb, NULL);384 if (*pwszSrc == RTPATH_DELIMITER) 385 pwszSrc++; /* we already appended a delimiter to the first part */ 386 387 rc = RTUtf16ToUtf8Ex(pwszSrc, RTSTR_MAX, &pszDst, cb, NULL); 420 388 if (RT_FAILURE(rc)) 421 389 { … … 428 396 } 429 397 430 uint32_t l = (uint32_t)strlen(dst);398 cbDst = (uint32_t)strlen(pszDst); 431 399 432 400 /* Verify that the path is under the root directory. */ 433 rc = vbsfPathCheck(dst, l); 434 401 rc = vbsfPathCheck(pszDst, cbDst); 435 402 if (RT_FAILURE(rc)) 436 403 { … … 442 409 } 443 410 444 cb -= l;445 dst += l;411 cb -= cbDst; 412 pszDst += cbDst; 446 413 447 414 Assert(cb > 0); … … 449 416 450 417 /* Nul terminate the string */ 451 * dst = 0;418 *pszDst = 0; 452 419 } 453 420 #ifdef RT_OS_DARWIN … … 469 436 { 470 437 /* strip off the last path component, that has to be preserved: contains the wildcard(s) or a 'rename' target. */ 471 uint32_t len = (uint32_t)strlen(pszFullPath);472 char *src = pszFullPath + len- 1;473 474 while (src > pszFullPath)438 size_t cb = strlen(pszFullPath); 439 char *pszSrc = pszFullPath + cb - 1; 440 441 while (pszSrc > pszFullPath) 475 442 { 476 if (* src == RTPATH_DELIMITER)443 if (*pszSrc == RTPATH_DELIMITER) 477 444 break; 478 src--;445 pszSrc--; 479 446 } 480 if (* src == RTPATH_DELIMITER)447 if (*pszSrc == RTPATH_DELIMITER) 481 448 { 482 449 bool fHaveWildcards = false; 483 char * temp = src;484 485 while (*temp)450 char *psz = pszSrc; 451 452 while (*psz) 486 453 { 487 char uc = *temp;488 if ( uc == '*' || uc == '?' || uc == '>' || uc == '<' || uc== '"')454 char ch = *psz; 455 if (ch == '*' || ch == '?' || ch == '>' || ch == '<' || ch == '"') 489 456 { 490 457 fHaveWildcards = true; 491 458 break; 492 459 } 493 temp++;460 psz++; 494 461 } 495 462 496 463 if (fHaveWildcards || fPreserveLastComponent) 497 464 { 498 pszLastComponent = src;465 pszLastComponent = pszSrc; 499 466 *pszLastComponent = 0; 500 467 } … … 506 473 if (rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND) 507 474 { 508 uint32_t len = (uint32_t)strlen(pszFullPath);509 char *src = pszFullPath + len- 1;475 size_t cb = strlen(pszFullPath); 476 char *pszSrc = pszFullPath + cb - 1; 510 477 511 478 Log(("Handle case insensitive guest fs on top of host case sensitive fs for %s\n", pszFullPath)); 512 479 513 480 /* Find partial path that's valid */ 514 while (src > pszFullPath)481 while (pszSrc > pszFullPath) 515 482 { 516 if (* src == RTPATH_DELIMITER)483 if (*pszSrc == RTPATH_DELIMITER) 517 484 { 518 * src = 0;485 *pszSrc = 0; 519 486 rc = RTPathQueryInfoEx(pszFullPath, &info, RTFSOBJATTRADD_NOTHING, SHFL_RT_LINK(pClient)); 520 * src = RTPATH_DELIMITER;487 *pszSrc = RTPATH_DELIMITER; 521 488 if (rc == VINF_SUCCESS) 522 489 { 523 490 #ifdef DEBUG 524 * src = 0;491 *pszSrc = 0; 525 492 Log(("Found valid partial path %s\n", pszFullPath)); 526 * src = RTPATH_DELIMITER;493 *pszSrc = RTPATH_DELIMITER; 527 494 #endif 528 495 break; … … 530 497 } 531 498 532 src--;499 pszSrc--; 533 500 } 534 Assert(* src == RTPATH_DELIMITER && RT_SUCCESS(rc));535 if ( * src == RTPATH_DELIMITER501 Assert(*pszSrc == RTPATH_DELIMITER && RT_SUCCESS(rc)); 502 if ( *pszSrc == RTPATH_DELIMITER 536 503 && RT_SUCCESS(rc)) 537 504 { 538 src++;505 pszSrc++; 539 506 for (;;) 540 507 { 541 char * end = src;508 char *pszEnd = pszSrc; 542 509 bool fEndOfString = true; 543 510 544 while (*end)511 while (*pszEnd) 545 512 { 546 if (* end == RTPATH_DELIMITER)513 if (*pszEnd == RTPATH_DELIMITER) 547 514 break; 548 end++;515 pszEnd++; 549 516 } 550 517 551 if (* end == RTPATH_DELIMITER)518 if (*pszEnd == RTPATH_DELIMITER) 552 519 { 553 520 fEndOfString = false; 554 * end = 0;555 rc = RTPathQueryInfoEx( src, &info, RTFSOBJATTRADD_NOTHING, SHFL_RT_LINK(pClient));521 *pszEnd = 0; 522 rc = RTPathQueryInfoEx(pszSrc, &info, RTFSOBJATTRADD_NOTHING, SHFL_RT_LINK(pClient)); 556 523 Assert(rc == VINF_SUCCESS || rc == VERR_FILE_NOT_FOUND || rc == VERR_PATH_NOT_FOUND); 557 524 } 558 else if ( end == src)525 else if (pszEnd == pszSrc) 559 526 rc = VINF_SUCCESS; /* trailing delimiter */ 560 527 else … … 564 531 { 565 532 /* path component is invalid; try to correct the casing */ 566 rc = vbsfCorrectCasing(pClient, pszFullPath, src);533 rc = vbsfCorrectCasing(pClient, pszFullPath, pszSrc); 567 534 if (RT_FAILURE(rc)) 568 535 { 569 536 if (!fEndOfString) 570 * end = RTPATH_DELIMITER; /* restore the original full path */537 *pszEnd = RTPATH_DELIMITER; /* restore the original full path */ 571 538 break; 572 539 } … … 576 543 break; 577 544 578 * end = RTPATH_DELIMITER;579 src = end + 1;545 *pszEnd = RTPATH_DELIMITER; 546 pszSrc = pszEnd + 1; 580 547 } 581 548 if (RT_FAILURE(rc)) … … 1593 1560 } 1594 1561 1595 while (cbBufferOrg)1562 while (cbBufferOrg) 1596 1563 { 1597 1564 size_t cbDirEntrySize = cbDirEntry;
Note:
See TracChangeset
for help on using the changeset viewer.