|
|
1.1 ! root 1: #include <windows.h> ! 2: #include <devioctl.h> ! 3: #include <ntdddisk.h> ! 4: #include <ntddscsi.h> ! 5: #include <stdio.h> ! 6: #include <stddef.h> ! 7: #include <stdlib.h> ! 8: #include "spti.h" ! 9: ! 10: VOID ! 11: main( ! 12: int argc, ! 13: char *argv[] ! 14: ) ! 15: ! 16: { ! 17: BOOLEAN status; ! 18: DWORD accessMode, ! 19: shareMode; ! 20: HANDLE fileHandle; ! 21: IO_SCSI_CAPABILITIES capabilities; ! 22: PUCHAR dataBuffer; ! 23: SCSI_PASS_THROUGH_WITH_BUFFERS sptwb; ! 24: SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER sptdwb; ! 25: UCHAR buffer[2048]; ! 26: UCHAR string[25]; ! 27: ULONG length, ! 28: errorCode, ! 29: returned, ! 30: sectorSize = 512; ! 31: ! 32: if ((argc < 2) || (argc > 3)) { ! 33: printf("Usage: %s <port-name> [-mode]\n", argv[0] ); ! 34: printf("Examples:\n"); ! 35: printf(" spti g: (open the SCSI disk class driver in SHARED READ/WRITE mode)\n"); ! 36: printf(" spti Scsi2: (open the SCSI miniport driver for the 3rd host adapter)\n"); ! 37: printf(" spti Tape0 w (open the SCSI tape class driver in SHARED WRITE mode)\n"); ! 38: printf(" spti i: c (open the SCSI CD-ROM class driver in SHARED READ mode)\n"); ! 39: return; ! 40: } ! 41: ! 42: strcpy(string,"\\\\.\\"); ! 43: strcat(string,argv[1]); ! 44: ! 45: shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; // default ! 46: accessMode = GENERIC_WRITE | GENERIC_READ; // default ! 47: ! 48: if (argc == 3) { ! 49: if (!stricmp("r", argv[2])) { ! 50: shareMode = FILE_SHARE_READ; ! 51: accessMode = GENERIC_READ; ! 52: } ! 53: if (!stricmp("w", argv[2])) { ! 54: shareMode = FILE_SHARE_WRITE; ! 55: accessMode = GENERIC_WRITE; ! 56: } ! 57: if (!stricmp("c",argv[2])) { ! 58: shareMode = FILE_SHARE_READ; ! 59: accessMode = GENERIC_READ; ! 60: sectorSize = 2048; ! 61: } ! 62: } ! 63: ! 64: fileHandle = CreateFile(string, ! 65: accessMode, ! 66: shareMode, ! 67: NULL, ! 68: OPEN_EXISTING, ! 69: 0, ! 70: NULL); ! 71: ! 72: if (fileHandle == INVALID_HANDLE_VALUE) { ! 73: printf("Error opening %s. Error: %d\n", ! 74: argv[1], errorCode = GetLastError()); ! 75: PrintError(errorCode); ! 76: return; ! 77: } ! 78: ! 79: // ! 80: // Get the inquiry data. ! 81: // ! 82: ! 83: status = DeviceIoControl(fileHandle, ! 84: IOCTL_SCSI_GET_INQUIRY_DATA, ! 85: NULL, ! 86: 0, ! 87: buffer, ! 88: sizeof(buffer), ! 89: &returned, ! 90: FALSE); ! 91: ! 92: if (!status) { ! 93: printf( "Error reading inquiry data information; error was %d\n", ! 94: errorCode = GetLastError()); ! 95: PrintError(errorCode); ! 96: return; ! 97: } ! 98: ! 99: printf("Read %Xh bytes of inquiry data Information.\n\n",returned); ! 100: ! 101: PrintInquiryData(buffer); ! 102: ! 103: // ! 104: // Get the capabilities structure. ! 105: // ! 106: ! 107: status = DeviceIoControl(fileHandle, ! 108: IOCTL_SCSI_GET_CAPABILITIES, ! 109: NULL, ! 110: 0, ! 111: &capabilities, ! 112: sizeof(IO_SCSI_CAPABILITIES), ! 113: &returned, ! 114: FALSE); ! 115: ! 116: if (!status ) { ! 117: printf( "Error in io control; error was %d\n", ! 118: errorCode = GetLastError() ); ! 119: PrintError(errorCode); ! 120: return; ! 121: } ! 122: printf(" ***** MODE SENSE -- return all pages *****\n"); ! 123: printf(" ***** with SenseInfo buffer *****\n\n"); ! 124: ! 125: ZeroMemory(&sptwb,sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS)); ! 126: ! 127: sptwb.spt.Length = sizeof(SCSI_PASS_THROUGH); ! 128: sptwb.spt.PathId = 0; ! 129: sptwb.spt.TargetId = 1; ! 130: sptwb.spt.Lun = 0; ! 131: sptwb.spt.CdbLength = CDB6GENERIC_LENGTH; ! 132: sptwb.spt.SenseInfoLength = 24; ! 133: sptwb.spt.DataIn = SCSI_IOCTL_DATA_IN; ! 134: sptwb.spt.DataTransferLength = 192; ! 135: sptwb.spt.TimeOutValue = 2; ! 136: sptwb.spt.DataBufferOffset = ! 137: offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucDataBuf); ! 138: sptwb.spt.SenseInfoOffset = ! 139: offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucSenseBuf); ! 140: sptwb.spt.Cdb[0] = SCSIOP_MODE_SENSE; ! 141: sptwb.spt.Cdb[2] = MODE_SENSE_RETURN_ALL; ! 142: sptwb.spt.Cdb[4] = 192; ! 143: length = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucDataBuf) + ! 144: sptwb.spt.DataTransferLength; ! 145: ! 146: status = DeviceIoControl(fileHandle, ! 147: IOCTL_SCSI_PASS_THROUGH, ! 148: &sptwb, ! 149: sizeof(SCSI_PASS_THROUGH), ! 150: &sptwb, ! 151: length, ! 152: &returned, ! 153: FALSE); ! 154: ! 155: PrintStatusResults(status,returned,&sptwb,length); ! 156: ! 157: printf(" ***** MODE SENSE -- return all pages *****\n"); ! 158: printf(" ***** without SenseInfo buffer *****\n\n"); ! 159: ! 160: ZeroMemory(&sptwb,sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS)); ! 161: ! 162: sptwb.spt.Length = sizeof(SCSI_PASS_THROUGH); ! 163: sptwb.spt.PathId = 0; ! 164: sptwb.spt.TargetId = 1; ! 165: sptwb.spt.Lun = 0; ! 166: sptwb.spt.CdbLength = CDB6GENERIC_LENGTH; ! 167: sptwb.spt.SenseInfoLength = 0; ! 168: sptwb.spt.DataIn = SCSI_IOCTL_DATA_IN; ! 169: sptwb.spt.DataTransferLength = 192; ! 170: sptwb.spt.TimeOutValue = 2; ! 171: sptwb.spt.DataBufferOffset = ! 172: offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucDataBuf); ! 173: sptwb.spt.SenseInfoOffset = ! 174: offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucSenseBuf); ! 175: sptwb.spt.Cdb[0] = SCSIOP_MODE_SENSE; ! 176: sptwb.spt.Cdb[2] = MODE_SENSE_RETURN_ALL; ! 177: sptwb.spt.Cdb[4] = 192; ! 178: length = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucDataBuf) + ! 179: sptwb.spt.DataTransferLength; ! 180: ! 181: status = DeviceIoControl(fileHandle, ! 182: IOCTL_SCSI_PASS_THROUGH, ! 183: &sptwb, ! 184: sizeof(SCSI_PASS_THROUGH), ! 185: &sptwb, ! 186: length, ! 187: &returned, ! 188: FALSE); ! 189: ! 190: PrintStatusResults(status,returned,&sptwb,length); ! 191: ! 192: printf(" ***** TEST UNIT READY *****\n"); ! 193: printf(" ***** DataBufferLength = 0 *****\n\n"); ! 194: ! 195: ! 196: ZeroMemory(&sptwb,sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS)); ! 197: ! 198: sptwb.spt.Length = sizeof(SCSI_PASS_THROUGH); ! 199: sptwb.spt.PathId = 0; ! 200: sptwb.spt.TargetId = 1; ! 201: sptwb.spt.Lun = 0; ! 202: sptwb.spt.CdbLength = CDB6GENERIC_LENGTH; ! 203: sptwb.spt.SenseInfoLength = 24; ! 204: sptwb.spt.DataIn = SCSI_IOCTL_DATA_IN; ! 205: sptwb.spt.DataTransferLength = 0; ! 206: sptwb.spt.TimeOutValue = 2; ! 207: sptwb.spt.DataBufferOffset = ! 208: offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucDataBuf); ! 209: sptwb.spt.SenseInfoOffset = ! 210: offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucSenseBuf); ! 211: sptwb.spt.Cdb[0] = SCSIOP_TEST_UNIT_READY; ! 212: length = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucDataBuf); ! 213: ! 214: status = DeviceIoControl(fileHandle, ! 215: IOCTL_SCSI_PASS_THROUGH, ! 216: &sptwb, ! 217: sizeof(SCSI_PASS_THROUGH), ! 218: &sptwb, ! 219: length, ! 220: &returned, ! 221: FALSE); ! 222: ! 223: PrintStatusResults(status,returned,&sptwb,length); ! 224: ! 225: // ! 226: // Do a mode sense with a bad data buffer offset. This should fail. ! 227: // ! 228: printf(" ***** MODE SENSE -- return all pages *****\n"); ! 229: printf(" ***** bad DataBufferOffset -- should fail *****\n\n"); ! 230: ! 231: ZeroMemory(&sptwb,sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS)); ! 232: ! 233: sptwb.spt.Length = sizeof(SCSI_PASS_THROUGH); ! 234: sptwb.spt.PathId = 0; ! 235: sptwb.spt.TargetId = 1; ! 236: sptwb.spt.Lun = 0; ! 237: sptwb.spt.CdbLength = CDB6GENERIC_LENGTH; ! 238: sptwb.spt.SenseInfoLength = 0; ! 239: sptwb.spt.DataIn = SCSI_IOCTL_DATA_IN; ! 240: sptwb.spt.DataTransferLength = 192; ! 241: sptwb.spt.TimeOutValue = 2; ! 242: sptwb.spt.DataBufferOffset = 0; ! 243: sptwb.spt.SenseInfoOffset = ! 244: offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucSenseBuf); ! 245: sptwb.spt.Cdb[0] = SCSIOP_MODE_SENSE; ! 246: sptwb.spt.Cdb[2] = MODE_SENSE_RETURN_ALL; ! 247: sptwb.spt.Cdb[4] = 192; ! 248: length = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucDataBuf) + ! 249: sptwb.spt.DataTransferLength; ! 250: status = DeviceIoControl(fileHandle, ! 251: IOCTL_SCSI_PASS_THROUGH, ! 252: &sptwb, ! 253: sizeof(SCSI_PASS_THROUGH), ! 254: &sptwb, ! 255: length, ! 256: &returned, ! 257: FALSE); ! 258: ! 259: PrintStatusResults(status,returned,&sptwb,length); ! 260: ! 261: ! 262: // ! 263: // Get caching mode sense page. ! 264: // ! 265: printf(" ***** MODE SENSE *****\n"); ! 266: printf(" ***** return caching mode sense page *****\n\n"); ! 267: ! 268: ZeroMemory(&sptwb,sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS)); ! 269: ! 270: sptwb.spt.Length = sizeof(SCSI_PASS_THROUGH); ! 271: sptwb.spt.PathId = 0; ! 272: sptwb.spt.TargetId = 1; ! 273: sptwb.spt.Lun = 0; ! 274: sptwb.spt.CdbLength = CDB6GENERIC_LENGTH; ! 275: sptwb.spt.SenseInfoLength = 24; ! 276: sptwb.spt.DataIn = SCSI_IOCTL_DATA_IN; ! 277: sptwb.spt.DataTransferLength = 192; ! 278: sptwb.spt.TimeOutValue = 2; ! 279: sptwb.spt.DataBufferOffset = ! 280: offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucDataBuf); ! 281: sptwb.spt.SenseInfoOffset = ! 282: offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucSenseBuf); ! 283: sptwb.spt.Cdb[0] = SCSIOP_MODE_SENSE; ! 284: sptwb.spt.Cdb[1] = 0x08; // target shall not return any block descriptors ! 285: sptwb.spt.Cdb[2] = MODE_PAGE_CACHING; ! 286: sptwb.spt.Cdb[4] = 192; ! 287: length = offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS,ucDataBuf) + ! 288: sptwb.spt.DataTransferLength; ! 289: status = DeviceIoControl(fileHandle, ! 290: IOCTL_SCSI_PASS_THROUGH, ! 291: &sptwb, ! 292: sizeof(SCSI_PASS_THROUGH), ! 293: &sptwb, ! 294: length, ! 295: &returned, ! 296: FALSE); ! 297: ! 298: PrintStatusResults(status,returned,&sptwb,length); ! 299: ! 300: printf(" ***** WRITE DATA BUFFER operation *****\n"); ! 301: ! 302: ZeroMemory(&sptdwb, sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER)); ! 303: dataBuffer = AllocateAlignedBuffer(sectorSize,capabilities.AlignmentMask); ! 304: FillMemory(dataBuffer,sectorSize/2,'N'); ! 305: FillMemory(dataBuffer + sectorSize/2,sectorSize/2,'T'); ! 306: ! 307: sptdwb.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); ! 308: sptdwb.sptd.PathId = 0; ! 309: sptdwb.sptd.TargetId = 1; ! 310: sptdwb.sptd.Lun = 0; ! 311: sptdwb.sptd.CdbLength = CDB10GENERIC_LENGTH; ! 312: sptdwb.sptd.SenseInfoLength = 24; ! 313: sptdwb.sptd.DataIn = SCSI_IOCTL_DATA_OUT; ! 314: sptdwb.sptd.DataTransferLength = sectorSize; ! 315: sptdwb.sptd.TimeOutValue = 2; ! 316: sptdwb.sptd.DataBuffer = dataBuffer; ! 317: sptdwb.sptd.SenseInfoOffset = ! 318: offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER,ucSenseBuf); ! 319: sptdwb.sptd.Cdb[0] = SCSIOP_WRITE_DATA_BUFF; ! 320: sptdwb.sptd.Cdb[1] = 2; // Data mode ! 321: sptdwb.sptd.Cdb[7] = (UCHAR)(sectorSize >> 8); // Parameter List length ! 322: sptdwb.sptd.Cdb[8] = 0; ! 323: length = sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER); ! 324: status = DeviceIoControl(fileHandle, ! 325: IOCTL_SCSI_PASS_THROUGH_DIRECT, ! 326: &sptdwb, ! 327: length, ! 328: &sptdwb, ! 329: length, ! 330: &returned, ! 331: FALSE); ! 332: ! 333: PrintStatusResults(status,returned, ! 334: (PSCSI_PASS_THROUGH_WITH_BUFFERS)&sptdwb,length); ! 335: if ((sptdwb.sptd.ScsiStatus == 0) && (status != 0)) { ! 336: PrintDataBuffer(dataBuffer,sptdwb.sptd.DataTransferLength); ! 337: } ! 338: ! 339: printf(" ***** READ DATA BUFFER operation *****\n"); ! 340: ! 341: ZeroMemory(&sptdwb, sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER)); ! 342: ZeroMemory(dataBuffer,sectorSize); ! 343: ! 344: sptdwb.sptd.Length = sizeof(SCSI_PASS_THROUGH_DIRECT); ! 345: sptdwb.sptd.PathId = 0; ! 346: sptdwb.sptd.TargetId = 1; ! 347: sptdwb.sptd.Lun = 0; ! 348: sptdwb.sptd.CdbLength = CDB10GENERIC_LENGTH; ! 349: sptdwb.sptd.DataIn = SCSI_IOCTL_DATA_IN; ! 350: sptdwb.sptd.SenseInfoLength = 24; ! 351: sptdwb.sptd.DataTransferLength = sectorSize; ! 352: sptdwb.sptd.TimeOutValue = 2; ! 353: sptdwb.sptd.DataBuffer = dataBuffer; ! 354: sptdwb.sptd.SenseInfoOffset = ! 355: offsetof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER,ucSenseBuf); ! 356: sptdwb.sptd.Cdb[0] = SCSIOP_READ_DATA_BUFF; ! 357: sptdwb.sptd.Cdb[1] = 2; // Data mode ! 358: sptdwb.sptd.Cdb[7] = (UCHAR)(sectorSize >> 8); // Parameter List length ! 359: sptdwb.sptd.Cdb[8] = 0; ! 360: length = sizeof(SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER); ! 361: status = DeviceIoControl(fileHandle, ! 362: IOCTL_SCSI_PASS_THROUGH_DIRECT, ! 363: &sptdwb, ! 364: length, ! 365: &sptdwb, ! 366: length, ! 367: &returned, ! 368: FALSE); ! 369: ! 370: PrintStatusResults(status,returned, ! 371: (PSCSI_PASS_THROUGH_WITH_BUFFERS)&sptdwb,length); ! 372: if ((sptdwb.sptd.ScsiStatus == 0) && (status != 0)) { ! 373: PrintDataBuffer(dataBuffer,sptdwb.sptd.DataTransferLength); ! 374: } ! 375: } ! 376: ! 377: VOID ! 378: PrintError(ULONG ErrorCode) ! 379: { ! 380: UCHAR errorBuffer[80]; ! 381: ULONG count; ! 382: ! 383: count = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, ! 384: NULL, ! 385: ErrorCode, ! 386: 0, ! 387: errorBuffer, ! 388: sizeof(errorBuffer), ! 389: NULL ! 390: ); ! 391: ! 392: if (count != 0) { ! 393: printf("%s\n", errorBuffer); ! 394: } else { ! 395: printf("Format message failed. Error: %d\n", GetLastError()); ! 396: } ! 397: } ! 398: ! 399: VOID ! 400: PrintDataBuffer(PUCHAR DataBuffer, ULONG BufferLength) ! 401: { ! 402: ULONG Cnt; ! 403: ! 404: printf(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n"); ! 405: printf(" ---------------------------------------------------------------\n"); ! 406: for (Cnt = 0; Cnt < BufferLength; Cnt++) { ! 407: if ((Cnt) % 16 == 0) { ! 408: printf(" %03X ",Cnt); ! 409: } ! 410: printf("%02X ", DataBuffer[Cnt]); ! 411: if ((Cnt+1) % 8 == 0) { ! 412: printf(" "); ! 413: } ! 414: if ((Cnt+1) % 16 == 0) { ! 415: printf("\n"); ! 416: } ! 417: } ! 418: printf("\n\n"); ! 419: } ! 420: ! 421: VOID ! 422: PrintInquiryData(PCHAR DataBuffer) ! 423: { ! 424: PSCSI_ADAPTER_BUS_INFO adapterInfo; ! 425: PSCSI_INQUIRY_DATA inquiryData; ! 426: ULONG i, ! 427: j; ! 428: ! 429: adapterInfo = (PSCSI_ADAPTER_BUS_INFO) DataBuffer; ! 430: ! 431: printf("Bus\n"); ! 432: printf("Num TID LUN Claimed String Inquiry Header\n"); ! 433: printf("--- --- --- ------- ---------------------------- -----------------------\n"); ! 434: ! 435: for (i = 0; i < adapterInfo->NumberOfBuses; i++) { ! 436: inquiryData = (PSCSI_INQUIRY_DATA) (DataBuffer + ! 437: adapterInfo->BusData[i].InquiryDataOffset); ! 438: while (adapterInfo->BusData[i].InquiryDataOffset) { ! 439: printf(" %d %d %3d %s %.28s ", ! 440: i, ! 441: inquiryData->TargetId, ! 442: inquiryData->Lun, ! 443: (inquiryData->DeviceClaimed) ? "Y" : "N", ! 444: &inquiryData->InquiryData[8]); ! 445: for (j = 0; j < 8; j++) { ! 446: printf("%02X ", inquiryData->InquiryData[j]); ! 447: } ! 448: printf("\n"); ! 449: if (inquiryData->NextInquiryDataOffset == 0) { ! 450: break; ! 451: } ! 452: inquiryData = (PSCSI_INQUIRY_DATA) (DataBuffer + ! 453: inquiryData->NextInquiryDataOffset); ! 454: } ! 455: } ! 456: ! 457: printf("\n\n"); ! 458: } ! 459: ! 460: PUCHAR ! 461: AllocateAlignedBuffer(ULONG size, ULONG Align) ! 462: { ! 463: PUCHAR ptr; ! 464: ! 465: if (!Align) { ! 466: ptr = malloc(size); ! 467: } ! 468: else { ! 469: ptr = malloc(size + Align); ! 470: ptr = (PUCHAR)(((ULONG)ptr + Align) & ~Align); ! 471: } ! 472: if (ptr == NULL) { ! 473: printf("Memory allocation error. Terminating program\n"); ! 474: exit(1); ! 475: } ! 476: else { ! 477: return ptr; ! 478: } ! 479: } ! 480: ! 481: VOID ! 482: PrintStatusResults( ! 483: BOOLEAN status,DWORD returned,PSCSI_PASS_THROUGH_WITH_BUFFERS psptwb, ! 484: ULONG length) ! 485: { ! 486: ULONG errorCode; ! 487: ! 488: if (!status ) { ! 489: printf( "Error: %d ", ! 490: errorCode = GetLastError() ); ! 491: PrintError(errorCode); ! 492: return; ! 493: } ! 494: if (psptwb->spt.ScsiStatus) { ! 495: PrintSenseInfo(psptwb); ! 496: return; ! 497: } ! 498: else { ! 499: printf("Scsi status: %02Xh, Bytes returned: %Xh, ", ! 500: psptwb->spt.ScsiStatus,returned); ! 501: printf("Data buffer length: %Xh\n\n\n", ! 502: psptwb->spt.DataTransferLength); ! 503: PrintDataBuffer((PUCHAR)psptwb,length); ! 504: } ! 505: } ! 506: ! 507: VOID ! 508: PrintSenseInfo(PSCSI_PASS_THROUGH_WITH_BUFFERS psptwb) ! 509: { ! 510: int i; ! 511: ! 512: printf("Scsi status: %02Xh\n\n",psptwb->spt.ScsiStatus); ! 513: if (psptwb->spt.SenseInfoLength == 0) { ! 514: return; ! 515: } ! 516: printf("Sense Info -- consult SCSI spec for details\n"); ! 517: printf("-------------------------------------------------------------\n"); ! 518: for (i=0; i < psptwb->spt.SenseInfoLength; i++) { ! 519: printf("%02X ",psptwb->ucSenseBuf[i]); ! 520: } ! 521: printf("\n\n"); ! 522: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.