Changeset 60793 in vbox for trunk/src/VBox/ValidationKit
- Timestamp:
- May 2, 2016 4:12:43 PM (9 years ago)
- Location:
- trunk/src/VBox/ValidationKit/utils/usb
- Files:
-
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp
r60548 r60793 542 542 543 543 /** 544 * Parses an unsigned integer from the given value string. 545 * 546 * @returns IPRT status code. 547 * @retval VERR_OUT_OF_RANGE if the parsed value exceeds the given maximum. 548 * @param uMax The maximum value. 549 * @param pu64 Where to store the parsed number on success. 550 */ 551 static int utsDoGadgetCreateCfgParseUInt(const char *pszVal, uint64_t uMax, uint64_t *pu64) 552 { 553 int rc = RTStrToUInt64Ex(pszVal, NULL, 0, pu64); 554 if (RT_SUCCESS(rc)) 555 { 556 if (*pu64 > uMax) 557 rc = VERR_OUT_OF_RANGE; 558 } 559 560 return rc; 561 } 562 563 /** 564 * Parses a signed integer from the given value string. 565 * 566 * @returns IPRT status code. 567 * @retval VERR_OUT_OF_RANGE if the parsed value exceeds the given range. 568 * @param iMin The minimum value. 569 * @param iMax The maximum value. 570 * @param pi64 Where to store the parsed number on success. 571 */ 572 static int utsDoGadgetCreateCfgParseInt(const char *pszVal, int64_t iMin, int64_t iMax, int64_t *pi64) 573 { 574 int rc = RTStrToInt64Ex(pszVal, NULL, 0, pi64); 575 if (RT_SUCCESS(rc)) 576 { 577 if ( *pi64 < iMin 578 || *pi64 > iMax) 579 rc = VERR_OUT_OF_RANGE; 580 } 581 582 return rc; 583 } 584 585 /** 586 * Parses the given config item and fills in the value according to the given type. 587 * 588 * @returns IPRT status code. 589 * @param pCfgItem The config item to parse. 590 * @param u32Type The config type. 591 * @param pszVal The value encoded as a string. 592 */ 593 static int utsDoGadgetCreateCfgParseItem(PUTSGADGETCFGITEM pCfgItem, uint32_t u32Type, 594 const char *pszVal) 595 { 596 int rc = VINF_SUCCESS; 597 598 switch (u32Type) 599 { 600 case UTSPKT_GDGT_CFG_ITEM_TYPE_BOOLEAN: 601 { 602 pCfgItem->Val.enmType = UTSGADGETCFGTYPE_BOOLEAN; 603 if ( RTStrICmp(pszVal, "enabled") 604 || RTStrICmp(pszVal, "1") 605 || RTStrICmp(pszVal, "true")) 606 pCfgItem->Val.u.f = true; 607 else if ( RTStrICmp(pszVal, "disabled") 608 || RTStrICmp(pszVal, "0") 609 || RTStrICmp(pszVal, "false")) 610 pCfgItem->Val.u.f = false; 611 else 612 rc = VERR_INVALID_PARAMETER; 613 break; 614 } 615 case UTSPKT_GDGT_CFG_ITEM_TYPE_STRING: 616 { 617 pCfgItem->Val.enmType = UTSGADGETCFGTYPE_STRING; 618 pCfgItem->Val.u.psz = RTStrDup(pszVal); 619 if (!pCfgItem->Val.u.psz) 620 rc = VERR_NO_STR_MEMORY; 621 break; 622 } 623 case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT8: 624 { 625 pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT8; 626 627 uint64_t u64; 628 rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT8_MAX, &u64); 629 if (RT_SUCCESS(rc)) 630 pCfgItem->Val.u.u8 = (uint8_t)u64; 631 break; 632 } 633 case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT16: 634 { 635 pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT16; 636 637 uint64_t u64; 638 rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT16_MAX, &u64); 639 if (RT_SUCCESS(rc)) 640 pCfgItem->Val.u.u16 = (uint16_t)u64; 641 break; 642 } 643 case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT32: 644 { 645 pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT32; 646 647 uint64_t u64; 648 rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT32_MAX, &u64); 649 if (RT_SUCCESS(rc)) 650 pCfgItem->Val.u.u32 = (uint32_t)u64; 651 break; 652 } 653 case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT64: 654 { 655 pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT64; 656 rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT64_MAX, &pCfgItem->Val.u.u64); 657 break; 658 } 659 case UTSPKT_GDGT_CFG_ITEM_TYPE_INT8: 660 { 661 pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT8; 662 663 int64_t i64; 664 rc = utsDoGadgetCreateCfgParseInt(pszVal, INT8_MIN, INT8_MAX, &i64); 665 if (RT_SUCCESS(rc)) 666 pCfgItem->Val.u.i8 = (int8_t)i64; 667 break; 668 } 669 case UTSPKT_GDGT_CFG_ITEM_TYPE_INT16: 670 { 671 pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT16; 672 673 int64_t i64; 674 rc = utsDoGadgetCreateCfgParseInt(pszVal, INT16_MIN, INT16_MAX, &i64); 675 if (RT_SUCCESS(rc)) 676 pCfgItem->Val.u.i16 = (int16_t)i64; 677 break; 678 } 679 case UTSPKT_GDGT_CFG_ITEM_TYPE_INT32: 680 { 681 pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT32; 682 683 int64_t i64; 684 rc = utsDoGadgetCreateCfgParseInt(pszVal, INT32_MIN, INT32_MAX, &i64); 685 if (RT_SUCCESS(rc)) 686 pCfgItem->Val.u.i32 = (int32_t)i64; 687 break; 688 } 689 case UTSPKT_GDGT_CFG_ITEM_TYPE_INT64: 690 { 691 pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT64; 692 rc = utsDoGadgetCreateCfgParseInt(pszVal, INT64_MIN, INT64_MAX, &pCfgItem->Val.u.i64); 693 break; 694 } 695 default: 696 rc = VERR_INVALID_PARAMETER; 697 } 698 699 return rc; 700 } 701 702 /** 544 703 * Creates the configuration from the given GADGET CREATE packet. 545 704 * 546 705 * @returns IPRT status code. 547 * @param pReq The gadget create request. 548 * @param paCfg The array of configuration items. 549 */ 550 static int utsDoGadgetCreateFillCfg(PUTSPKTREQGDGTCTOR pReq, PUTSGADGETCFGITEM paCfg) 551 { 552 return VERR_NOT_IMPLEMENTED; 706 * @param pCfgItem The first config item header in the request packet. 707 * @param cCfgItems Number of config items in the packet to parse. 708 * @param cbPkt Number of bytes left in the packet for the config data. 709 * @param paCfg The array of configuration items to fill. 710 */ 711 static int utsDoGadgetCreateFillCfg(PUTSPKTREQGDGTCTORCFGITEM pCfgItem, unsigned cCfgItems, 712 size_t cbPkt, PUTSGADGETCFGITEM paCfg) 713 { 714 int rc = VINF_SUCCESS; 715 unsigned idxCfg = 0; 716 717 while ( RT_SUCCESS(rc) 718 && cCfgItems 719 && cbPkt) 720 { 721 if (cbPkt >= sizeof(UTSPKTREQGDGTCTORCFGITEM)) 722 { 723 cbPkt -= sizeof(UTSPKTREQGDGTCTORCFGITEM); 724 if (pCfgItem->u32KeySize + pCfgItem->u32ValSize >= cbPkt) 725 { 726 const char *pszKey = (const char *)(pCfgItem + 1); 727 const char *pszVal = pszKey + pCfgItem->u32KeySize; 728 729 /* Validate termination. */ 730 if ( *(pszKey + pCfgItem->u32KeySize - 1) != '\0' 731 || *(pszVal + pCfgItem->u32ValSize - 1) != '\0') 732 rc = VERR_INVALID_PARAMETER; 733 else 734 { 735 paCfg[idxCfg].pszKey = RTStrDup(pszKey); 736 737 rc = utsDoGadgetCreateCfgParseItem(&paCfg[idxCfg], pCfgItem->u32Type, pszVal); 738 if (RT_SUCCESS(rc)) 739 { 740 cbPkt -= pCfgItem->u32KeySize + pCfgItem->u32ValSize; 741 cCfgItems--; 742 idxCfg++; 743 pCfgItem = (PUTSPKTREQGDGTCTORCFGITEM)(pszVal + pCfgItem->u32ValSize); 744 } 745 } 746 } 747 else 748 rc = VERR_INVALID_PARAMETER; 749 } 750 else 751 rc = VERR_INVALID_PARAMETER; 752 } 753 754 return rc; 553 755 } 554 756 … … 688 890 return utsReplyRC(pClient, pPktHdr, VERR_NO_MEMORY, "Failed to allocate memory for configration items"); 689 891 690 rc = utsDoGadgetCreateFillCfg(pReq, paCfg); 892 rc = utsDoGadgetCreateFillCfg((PUTSPKTREQGDGTCTORCFGITEM)(pReq + 1), pReq->u32CfgItems, 893 pPktHdr->cb - sizeof(UTSPKTREQGDGTCTOR), paCfg); 691 894 if (RT_FAILURE(rc)) 692 895 { … … 1079 1282 } 1080 1283 else 1081 { 1082 if (RTErrInfoIsSet(pErrInfo)) 1083 { 1084 RTMsgError("Failed to parse config with detailed error: %s (%Rrc)\n", 1085 pErrInfo->pszMsg, pErrInfo->rc); 1086 RTErrInfoFree(pErrInfo); 1087 } 1088 else 1089 RTMsgError("Faield to parse config with unknown error (%Rrc)\n", rc); 1090 return rc; 1091 } 1284 RTMsgError("Initializing the platform failed with %Rrc\n", rc); 1285 } 1286 else 1287 { 1288 if (RTErrInfoIsSet(pErrInfo)) 1289 { 1290 RTMsgError("Failed to parse config with detailed error: %s (%Rrc)\n", 1291 pErrInfo->pszMsg, pErrInfo->rc); 1292 RTErrInfoFree(pErrInfo); 1293 } 1294 else 1295 RTMsgError("Failed to parse config with unknown error (%Rrc)\n", rc); 1092 1296 } 1093 1297 -
trunk/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetCfg.cpp
r60493 r60793 89 89 90 90 if ( !pCfgItem 91 || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_STRING) 91 || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_BOOLEAN) 92 { 92 93 *pf = pCfgItem ? pCfgItem->Val.u.f : fDef; 94 rc = VINF_SUCCESS; 95 } 93 96 94 97 return rc; -
trunk/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetClassTest.cpp
r60522 r60793 291 291 char *pszManufacturer = NULL; 292 292 char *pszProduct = NULL; 293 bool fSuperSpeed = false; 293 294 294 295 /* Get basic device config. */ … … 304 305 if (RT_SUCCESS(rc)) 305 306 rc = utsGadgetCfgQueryStringDef(paCfg, "Gadget/Product", &pszProduct, UTS_GADGET_TEST_PRODUCT_DEF); 307 if (RT_SUCCESS(rc)) 308 rc = utsGadgetCfgQueryBoolDef(paCfg, "Gadget/SuperSpeed", &fSuperSpeed, false); 306 309 307 310 if (RT_SUCCESS(rc)) … … 357 360 pClass->pszUdc = NULL; 358 361 359 rc = utsPlatformLnxAcquireUDC( &pClass->pszUdc, &pClass->uBusId);362 rc = utsPlatformLnxAcquireUDC(fSuperSpeed, &pClass->pszUdc, &pClass->uBusId); 360 363 if (RT_SUCCESS(rc)) 361 364 rc = RTLinuxSysFsWriteStrFile(pClass->pszUdc, 0, NULL, "%s/UDC", pClass->pszGadgetPath); -
trunk/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp
r60522 r60793 45 45 46 46 /** 47 * A dummy UDC descriptor. 48 */ 49 typedef struct UTSPLATFORMLNXDUMMYHCD 50 { 51 /* Index of the dummy hcd entry. */ 52 uint32_t idxDummyHcd; 47 * A USB bus provided by the dummy HCD. 48 */ 49 typedef struct UTSPLATFORMLNXDUMMYHCDBUS 50 { 53 51 /** The bus ID on the host the dummy HCD is serving. */ 54 52 uint32_t uBusId; 53 /** Flag whether this is a super speed bus. */ 54 bool fSuperSpeed; 55 } UTSPLATFORMLNXDUMMYHCDBUS; 56 /** Pointer to a Dummy HCD bus. */ 57 typedef UTSPLATFORMLNXDUMMYHCDBUS *PUTSPLATFORMLNXDUMMYHCDBUS; 58 59 /** 60 * A dummy UDC descriptor. 61 */ 62 typedef struct UTSPLATFORMLNXDUMMYHCD 63 { 64 /** Index of the dummy hcd entry. */ 65 uint32_t idxDummyHcd; 55 66 /** Flag whether this HCD is free for use. */ 56 bool fAvailable; 67 bool fAvailable; 68 /** Number of busses this HCD instance serves. */ 69 unsigned cBusses; 70 /** Bus structures the HCD serves.*/ 71 PUTSPLATFORMLNXDUMMYHCDBUS paBusses; 57 72 } UTSPLATFORMLNXDUMMYHCD; 58 73 /** Pointer to a dummy HCD entry. */ … … 75 90 76 91 /** 77 * Queries the assigned bus ID for the given dummy HCD index.92 * Queries the assigned busses for the given dummy HCD instance. 78 93 * 79 94 * @returns IPRT status code. 80 * @param idxHcd The HCD index to query the bus number for. 81 * @param puBusId Where to store the bus number on success. 82 */ 83 static int utsPlatformLnxDummyHcdQueryBusId(uint32_t idxHcd, uint32_t *puBusId) 95 * @param pHcd The dummy HCD bus instance. 96 */ 97 static int utsPlatformLnxDummyHcdQueryBusses(PUTSPLATFORMLNXDUMMYHCD pHcd) 84 98 { 85 99 int rc = VINF_SUCCESS; 86 100 char aszPath[RTPATH_MAX + 1]; 87 88 size_t cchPath = RTStrPrintf(&aszPath[0], RT_ELEMENTS(aszPath), UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/dummy_hcd.%u/usb*", idxHcd); 101 unsigned idxBusCur = 0; 102 unsigned idxBusMax = 0; 103 104 size_t cchPath = RTStrPrintf(&aszPath[0], RT_ELEMENTS(aszPath), UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/dummy_hcd.%u/usb*", pHcd->idxDummyHcd); 89 105 if (cchPath == RT_ELEMENTS(aszPath)) 90 106 return VERR_BUFFER_OVERFLOW; … … 94 110 if (RT_SUCCESS(rc)) 95 111 { 96 RTDIRENTRY DirFolderContent; 97 rc = RTDirRead(pDir, &DirFolderContent, NULL); 98 if (RT_SUCCESS(rc)) 99 { 100 /* Extract the bus number - it is after "usb", i.e. "usb9" indicates a bus ID of 9. */ 101 rc = RTStrToUInt32Ex(&DirFolderContent.szName[3], NULL, 10, puBusId); 112 do 113 { 114 RTDIRENTRY DirFolderContent; 115 rc = RTDirRead(pDir, &DirFolderContent, NULL); 102 116 if (RT_SUCCESS(rc)) 103 117 { 104 /* Make sure there is no other entry or something screwed us up. */ 105 rc = RTDirRead(pDir, &DirFolderContent, NULL); 118 uint32_t uBusId = 0; 119 120 /* Extract the bus number - it is after "usb", i.e. "usb9" indicates a bus ID of 9. */ 121 rc = RTStrToUInt32Ex(&DirFolderContent.szName[3], NULL, 10, &uBusId); 106 122 if (RT_SUCCESS(rc)) 107 rc = VERR_INVALID_STATE; 108 else if (rc == VERR_NO_MORE_FILES) 109 rc = VINF_SUCCESS; 123 { 124 /* Check whether this is a super speed bus. */ 125 int64_t iSpeed = 0; 126 bool fSuperSpeed = false; 127 rc = RTLinuxSysFsReadIntFile(10, &iSpeed, UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/dummy_hcd.%u/%s/speed", 128 pHcd->idxDummyHcd, DirFolderContent.szName); 129 if ( RT_SUCCESS(rc) 130 && (iSpeed == 5000 || iSpeed == 10000)) 131 fSuperSpeed = true; 132 133 /* Add to array of available busses for this HCD. */ 134 if (idxBusCur == idxBusMax) 135 { 136 size_t cbNew = (idxBusMax + 10) * sizeof(UTSPLATFORMLNXDUMMYHCDBUS); 137 PUTSPLATFORMLNXDUMMYHCDBUS pNew = (PUTSPLATFORMLNXDUMMYHCDBUS)RTMemRealloc(pHcd->paBusses, cbNew); 138 if (pNew) 139 { 140 idxBusMax += 10; 141 pHcd->paBusses = pNew; 142 } 143 } 144 145 if (idxBusCur < idxBusMax) 146 { 147 pHcd->paBusses[idxBusCur].uBusId = uBusId; 148 pHcd->paBusses[idxBusCur].fSuperSpeed = fSuperSpeed; 149 idxBusCur++; 150 } 151 else 152 rc = VERR_NO_MEMORY; 153 } 110 154 } 111 } 155 } while (RT_SUCCESS(rc)); 156 157 pHcd->cBusses = idxBusCur; 158 159 if (rc == VERR_NO_MORE_FILES) 160 rc = VINF_SUCCESS; 112 161 113 162 RTDirClose(pDir); … … 124 173 if (RT_SUCCESS(rc)) 125 174 { 126 const char * pszArg = "num=2"; /** @todo: Make configurable from config. */127 rc = utsPlatformModuleLoad("dummy_hcd", & pszArg, 1);175 const char *apszArg[] = { "num=2", "is_super_speed=1" }; /** @todo: Make configurable from config. */ 176 rc = utsPlatformModuleLoad("dummy_hcd", &apszArg[0], RT_ELEMENTS(apszArg)); 128 177 if (RT_SUCCESS(rc)) 129 178 { … … 157 206 if (RT_SUCCESS(rc)) 158 207 { 159 uint32_t uBusId = 0; 160 rc = utsPlatformLnxDummyHcdQueryBusId(idxHcd, &uBusId); 161 if (RT_SUCCESS(rc)) 208 /* Add to array of available HCDs. */ 209 if (idxHcdCur == idxHcdMax) 162 210 { 163 /* Add to array of available HCDs. */ 164 if (idxHcdCur == idxHcdMax) 211 size_t cbNew = (idxHcdMax + 10) * sizeof(UTSPLATFORMLNXDUMMYHCD); 212 PUTSPLATFORMLNXDUMMYHCD pNew = (PUTSPLATFORMLNXDUMMYHCD)RTMemRealloc(g_paDummyHcd, cbNew); 213 if (pNew) 165 214 { 166 size_t cbNew = (idxHcdMax + 10) * sizeof(UTSPLATFORMLNXDUMMYHCD); 167 PUTSPLATFORMLNXDUMMYHCD pNew = (PUTSPLATFORMLNXDUMMYHCD)RTMemRealloc(g_paDummyHcd, cbNew); 168 if (pNew) 169 { 170 idxHcdMax += 10; 171 g_paDummyHcd = pNew; 172 } 173 } 174 175 if (idxHcdCur < idxHcdMax) 176 { 177 g_paDummyHcd[idxHcdCur].idxDummyHcd = idxHcd; 178 g_paDummyHcd[idxHcdCur].uBusId = uBusId; 179 g_paDummyHcd[idxHcdCur].fAvailable = true; 180 idxHcdCur++; 215 idxHcdMax += 10; 216 g_paDummyHcd = pNew; 181 217 } 182 218 } 219 220 if (idxHcdCur < idxHcdMax) 221 { 222 g_paDummyHcd[idxHcdCur].idxDummyHcd = idxHcd; 223 g_paDummyHcd[idxHcdCur].fAvailable = true; 224 g_paDummyHcd[idxHcdCur].cBusses = 0; 225 g_paDummyHcd[idxHcdCur].paBusses = NULL; 226 rc = utsPlatformLnxDummyHcdQueryBusses(&g_paDummyHcd[idxHcdCur]); 227 if (RT_SUCCESS(rc)) 228 idxHcdCur++; 229 } 230 else 231 rc = VERR_NO_MEMORY; 183 232 } 184 233 } … … 222 271 unsigned idx; 223 272 for (idx = 0; idx < cArgv; idx++) 224 papszArgs[2+idx] = papszArg s[idx];273 papszArgs[2+idx] = papszArgv[idx]; 225 274 papszArgs[2+idx] = NULL; 226 275 … … 271 320 272 321 273 DECLHIDDEN(int) utsPlatformLnxAcquireUDC( char **ppszUdc, uint32_t *puBusId)322 DECLHIDDEN(int) utsPlatformLnxAcquireUDC(bool fSuperSpeed, char **ppszUdc, uint32_t *puBusId) 274 323 { 275 324 int rc = VERR_NOT_FOUND; … … 277 326 for (unsigned i = 0; i < g_cDummyHcd; i++) 278 327 { 279 if (g_paDummyHcd[i].fAvailable) 280 { 281 rc = VINF_SUCCESS; 282 int cbRet = RTStrAPrintf(ppszUdc, "dummy_udc.%u", g_paDummyHcd[i].idxDummyHcd); 283 if (cbRet == -1) 284 rc = VERR_NO_STR_MEMORY; 285 *puBusId = g_paDummyHcd[i].uBusId; 286 g_paDummyHcd[i].fAvailable = false; 287 break; 328 PUTSPLATFORMLNXDUMMYHCD pHcd = &g_paDummyHcd[i]; 329 330 if (pHcd->fAvailable) 331 { 332 /* Check all assigned busses for a speed match. */ 333 for (unsigned idxBus = 0; idxBus < pHcd->cBusses; idxBus++) 334 { 335 if (pHcd->paBusses[idxBus].fSuperSpeed == fSuperSpeed) 336 { 337 rc = VINF_SUCCESS; 338 int cbRet = RTStrAPrintf(ppszUdc, "dummy_udc.%u", pHcd->idxDummyHcd); 339 if (cbRet == -1) 340 rc = VERR_NO_STR_MEMORY; 341 *puBusId = pHcd->paBusses[idxBus].uBusId; 342 pHcd->fAvailable = false; 343 break; 344 } 345 } 346 347 if (rc != VERR_NOT_FOUND) 348 break; 288 349 } 289 350 } -
trunk/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform.h
r60518 r60793 70 70 * 71 71 * @returns IPRT status code. 72 * @param fSuperSpeed Flag whether a super speed bus is required. 72 73 * @param ppszUdc Where to store the pointer to the name of the UDC on success. 73 74 * Free with RTStrFree(). 74 75 * @param puBusId Where to store the bus ID the UDC is attached to on the host side. 75 76 */ 76 DECLHIDDEN(int) utsPlatformLnxAcquireUDC( char **ppszUdc, uint32_t *puBusId);77 DECLHIDDEN(int) utsPlatformLnxAcquireUDC(bool fSuperSpeed, char **ppszUdc, uint32_t *puBusId); 77 78 78 79 /**
Note:
See TracChangeset
for help on using the changeset viewer.