/* A simple interactive command line Macintosh disk image archiver. Public Domain 2019 Andrew Makousky This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to */ /* NOTE: This source code has been tested to compile with THINK C 3.02, but with some porting work, it can be used with other compilers. */ /* IMPORTANT NOTE! Please be careful when programming in pre-standard K&R C: Because function prototypes are not defined, you must make sure that the width of integer constants is explicitly specified if it is not the default `int' width. By default, integers are assumed to be the width of a `short', so if a parameter must be a `long', you must specify `(long)0', for example. Likewise, you must make sure that if a variable type does not match the function prototype, you must explicitly cast it to match. */ #include #include #include #include #include #include /* Since old Lightspeed C does not define the extended drive queue element structure, we define it ourselves here to guarantee that we have it available. */ typedef struct { /* long flags; */ struct QElem *qLink; int qType; int dQDrive; int dQRefNum; int dQFSID; int dQDrvSize; int dQDrvSz2; } DrvQEl2,*DrvQEl2Ptr ; /* File Tags buffer global variables appear not to be defined in the LightSpeed C headers. */ extern long BufTgFNum : 0x2fc; extern int BufTgFFlag : 0x300; extern int BufTgFBkNum : 0x302; extern long BufTgDate : 0x304; typedef struct { int td_dnum; /* Drive number */ int td_dref; /* Driver reference number */ int blksize; unsigned long td_size; /* Size in blocks */ /* User manual override for volume size, if applicable. */ unsigned long user_size; } VolMeta; /* SCSI definitions */ /* Commands */ #define SC_INQUIRY 0x12 #define SC_READ_CAPACITY_10 0x25 #define SC_READ_10 0x28 /* Statuses */ #define SC_GOOD 0x00 #define SC_CHECK_CONDITION 0x02 #define SC_CONDITION_MET 0x04 #define SC_BUSY 0x08 #define SC_ACA_ACTIVE 0x30 #define SC_TASK_ABORTED 0x40 #define SC_VENDOR_ID_SIZE 8 #define SC_PRODUCT_ID_SIZE 16 #define SC_REVISION_SIZE 4 #define SC_COMPLETION_TIMEOUT 300 typedef struct { int scsi_id; unsigned char cdb[16]; int cdb_len; unsigned char *buffer; unsigned int buffer_len; int comp_status; int comp_msg; OSErr comp_err; } PBSc1; typedef struct { unsigned char DeviceType; unsigned char DeviceQualifier; unsigned char Version; unsigned char ResponseFormat; unsigned char AdditionalLength; unsigned char VendorUse1; int Reserved1; char VendorID[SC_VENDOR_ID_SIZE]; char ProductID[SC_PRODUCT_ID_SIZE]; char Revision[SC_REVISION_SIZE]; unsigned char VendorUse2[20]; unsigned char Reserved2[42]; unsigned char VendorUse3[158]; } ScInquiryRecord; typedef struct { unsigned long last_lba; unsigned long blksize; } ScReadCapRecord; typedef struct { int scsi_id; ScInquiryRecord iq_resp; ScReadCapRecord read_cap; } ScMeta; /* TODO: We should also use the drive status call to get additional information, as the drive queue entry is not generally accurate in size. */ /* TODO: User override of disk size. */ /* TODO: We should check the driver descriptor indices and see if we are being shown a large hard disk. We should also ask the user if this size looks correct, and if not, they can manually enter the correct number of blocks to read. OS 6.0.7 versus OS 6.0.8 behave markedly differently, surprisingly. */ int g_ser_init = 0; int g_ser_speed = 1; /* Buffer 4 blocks of 512 bytes, this is about as large as is practical a buffer when transferring data at 28.8 kbits/sec. */ /* NOTE: We allocate 532 * 4 bytes because Hard Disk 20SC contains 532-byte sectors, the extra 20 bytes are for system use only. */ unsigned char g_buffer[532*4]; /* N.B. We define these variables as global to avoid risks of stack overflows. */ ScMeta g_sc; Block0 g_ddr; /* Macintosh requires at least three partitions: (0) partition table, (1) disk device driver, (2) Macintosh filesystem volume. Typically, there is another (4) free partition to reserve space to grow the partition table. Plus we have one more entry to catch the extra 20 bytes that come from the 532-byte size sectors from Hard Disk 20SC, so that a buffer overflow does not corrupt unintended data. */ Partition g_pm[5]; void showhex(buffer, size) unsigned char *buffer; unsigned long size; { unsigned long i; for (i = 0; i < size; i++) { if ((i & (8 - 1)) == 0) { printf("\n%08lx-", i); } printf(" %02x", (int)buffer[i]); } putchar('\n'); } /* Print out SCSI core metadata in a human-readable format. */ void sc_print_meta(meta) ScMeta *meta; { unsigned char new_DeviceType; unsigned char new_DeviceQualifier; new_DeviceType = meta->iq_resp.DeviceType & (32 - 1); new_DeviceQualifier = meta->iq_resp.DeviceType >> 5; switch (new_DeviceType) { case 0: puts("Direct access block device."); break; case 1: puts("Sequential access device (e.g. tape)."); break; case 2: puts("Printer device."); break; case 3: puts("Processor device."); break; case 4: puts("Write-once device (e.g. optical disc)."); break; case 5: puts("CD/DVD device."); break; case 6: puts("Scanner device (obsolete)."); break; case 7: puts("Optical memory device (e.g. optical disc)."); break; case 8: puts("Medium changer device (e.g. jukebox)."); break; case 9: puts("Communications device (obsolete)."); break; case 10: case 11: puts("Graphic arts pre-press device (obsolete)."); break; case 12: puts("Storage array controller device (e.g. RAID)."); break; case 13: puts("Enclosure services device."); break; case 14: puts("Simplified direct-access device (e.g. disk)."); break; case 15: puts("Optical card reader/writer device."); break; case 16: puts("Bridge Controller Commands."); break; case 17: puts("Object-based Storage Device."); break; case 18: puts("Automation/Drive Interface."); break; case 19: case 20: case 21: case 22: case 23: case 24: case 25: case 26: case 27: case 28: case 29: puts("Reserved peripheral device type."); break; case 30: puts("Well known logical unit [b]."); break; case 31: puts("Unknown or no device type."); break; default: printf("Unknown or no device type 0x%02x.\n", (int)new_DeviceType); break; } switch (new_DeviceQualifier) { case 0: puts("Peripheral device connected or unknown."); break; case 1: puts("No connected peripheral, but supported."); break; case 2: puts("Reserved peripheral qualifier."); break; case 3: puts("Peripheral device not supported."); break; default: printf("Vendor-specific peri. qual. 0x%x.\n", (int)new_DeviceQualifier); break; } printf("Reserved = 0x%02x\n", meta->iq_resp.DeviceQualifier); printf("SCSI version = 0x%02x\n", (int)meta->iq_resp.Version); printf("Response data format = 0x%02x\n", (int)meta->iq_resp.ResponseFormat); if (meta->iq_resp.AdditionalLength > 0) { unsigned i; fputs("VendorID: ", stdout); for (i = 0; i < SC_VENDOR_ID_SIZE; i++) putchar(meta->iq_resp.VendorID[i]); putchar('\n'); fputs("ProductID: ", stdout); for (i = 0; i < SC_PRODUCT_ID_SIZE; i++) putchar(meta->iq_resp.ProductID[i]); putchar('\n'); fputs("Revision: ", stdout); for (i = 0; i < SC_REVISION_SIZE; i++) putchar(meta->iq_resp.Revision[i]); putchar('\n'); } /* We get the LBA of the last block, add one to get number of blocks. */ printf("lbasize = %lu, blksize = %lu\n", meta->read_cap.last_lba + 1, meta->read_cap.blksize); } /* Print out the Macintosh Driver Descriptor Record (DDR) in a human-readable format. */ void mac_print_ddr(ddr) Block0 *ddr; { if (ddr->sbSig == sbSIGWord) puts("Driver descriptor signature matches."); else puts("Driver descriptor signature does NOT match."); printf("Block size = %d, block count = %ld\n", ddr->sbBlkSize, ddr->sbBlkCount); printf("sbDevType = %d, sbDevId = %d, sbData = 0x%08lx\n", ddr->sbDevType, ddr->sbDevId, ddr->sbData); printf("Driver count = %d, ddBlock = 0x%08lx, " "ddSize = %d, ddType = %d\n", ddr->sbDrvrCount, ddr->ddBlock, ddr->ddSize, ddr->ddType); } /* Print out the Macintosh Partition Map in a human-readable format. */ void mac_print_pm(pm, pm_len) Partition *pm; int pm_len; { unsigned i; int max_parts = pm[0].pmMapBlkCnt; if (pm[0].pmSig != pMapSIG) max_parts = 1; else if (max_parts > pm_len) max_parts = pm_len; for (i = 0; i < max_parts; i++) { if (pm[i].pmSig == pMapSIG) printf("Partition map %d signature matches. ", i); else { printf("Partition map %d signature " "does NOT match.\n", i); continue; } printf("pmSigPad = %d, pmMapBlkCnt = %ld\n", pm[i].pmSigPad, pm[i].pmMapBlkCnt); printf("pmPyPartStart = 0x%08lx, pmPartBlkCnt = %ld\n", pm[i].pmPyPartStart, pm[i].pmPartBlkCnt); { unsigned j; fputs("pmPartName: ", stdout); for (j = 0; j < 32; j++) putchar(pm[i].pmPartName[j]); putchar('\n'); fputs("pmParType: ", stdout); for (j = 0; j < 32; j++) putchar(pm[i].pmParType[j]); putchar('\n'); } printf("pmLgDataStart = 0x%08lx, pmDataCnt = %ld\n", pm[i].pmLgDataStart, pm[i].pmDataCnt); printf("pmPartStatus = 0x%08lx, pmLgBootStart = 0x%08lx, " "pmBootSize = %ld\n", pm[i].pmPartStatus, pm[i].pmLgBootStart, pm[i].pmBootSize); printf("pmBootAddr = 0x%08lx, pmBootAddr2 = 0x%08lx\n" "pmBootEntry = 0x%08lx, pmBootEntry2 = 0x%08lx\n", pm[i].pmBootAddr, pm[i].pmBootAddr2, pm[i].pmBootEntry, pm[i].pmBootEntry2); printf("pmBootCksum = 0x%08lx\n", pm[i].pmBootCksum); { /* Now print the pmProcessor field, if applicable. Classic Macintoshes will show nothing but garbage. */ unsigned i; char *pmProcessor = (char*)&pm[i] + 120; fputs("pmProcessor: ", stdout); for (i = 0; i < 16; i++) putchar(pmProcessor[i]); putchar('\n'); } } } /* Configure serial communications. */ OSErr config_serial(ret_soutref) int *ret_soutref; { int soutref; /* int use_ram_drv = 0; */ int pchar = 'n'; OSErr err; IOParam pb; CntrlParam cpb; SerStaRec *ssr; /* if (!use_ram_drv) { */ /* Open the ROM serial driver output. */ pb.ioNamePtr = (StringPtr)"\p.AOut"; pb.ioPermssn = fsWrPerm; /* fsRdPerm fsRdWrPerm */ err = PBOpen(&pb, false); if (err != noErr) { puts("Error opening ROM serial driver."); if (err == badUnitErr) puts("Bad reference number error."); else if (err == dInstErr) puts("Couldn't find driver error."); else if (err == openErr) puts("Can't perform requested read/write error."); else if (err == unitEmptyErr) puts("Bad reference number error."); else puts("Unknown error."); return err; } soutref = pb.ioRefNum; *ret_soutref = soutref; /* } else { /\* Open the RAM serial driver for output. *\/ err = RAMSDOpen(sPortA); if (err != noErr) { puts("Error opening RAM serial driver."); if (err == openErr) puts("Can't open driver error."); else puts("Unknown error."); return err; } soutref = -7; *ret_soutref = soutref; } */ /* SerReset(): Two options: */ /* 57.6 kbits/sec baud, 8 data bits, 1 stop bit, no parity. */ /* 19.2 kbits/sec baud, 8 data bits, 1 stop bit, no parity. */ cpb.ioVRefNum = 0; cpb.ioRefNum = soutref; cpb.csCode = 8; fputs("57.6 kbits/sec baud? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar == 'y') { puts("Using 57.6 kbits/sec baud."); cpb.csParam[0] = (int)baud57600 | data8 | stop10 | noParity; g_ser_speed = (int)baud57600; } else { puts("Using 19.2 kbits/sec baud."); cpb.csParam[0] = (int)baud19200 | data8 | stop10 | noParity; g_ser_speed = (int)baud19200; } err = PBControl(&cpb, false); if (err != noErr) { puts("Error on reset serial driver."); if (err == badUnitErr) puts("Bad reference number error."); else if (err == notOpenErr) puts("Not open error."); else if (err == unitEmptyErr) puts("Bad reference number error."); else if (err == controlErr) puts("Control error."); else puts("Unknown error."); return err; } /* SerHShake() */ /* Hardware flow control left on as is default for ROM serial driver. */ /* TESTING: Disable handshake. */ cpb.ioVRefNum = 0; cpb.ioRefNum = soutref; cpb.csCode = 10; fputs("Hardware CTS handshake? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar == 'y') { puts("Using hardware CTS handshake."); cpb.csParam[0] = 0x00ff; /* fXOn, fCTS */ } else { puts("Using no handshake."); cpb.csParam[0] = 0x0000; /* fXOn, fCTS */ } cpb.csParam[1] = 0x0000; /* xOn, xOff */ cpb.csParam[2] = 0x0000; /* errs, evts */ cpb.csParam[3] = 0x0000; /* fInX, null */ cpb.csParam[4] = 0; /* safety */ cpb.csParam[5] = 0; /* safety */ cpb.csParam[6] = 0; /* safety */ err = PBControl(&cpb, false); if (err != noErr) { puts("Error on set serial driver handshake."); if (err == badUnitErr) puts("Bad reference number error."); else if (err == notOpenErr) puts("Not open error."); else if (err == unitEmptyErr) puts("Bad reference number error."); else if (err == controlErr) puts("Control error."); else puts("Unknown error."); return err; } /* SerClrBrk() */ cpb.ioVRefNum = 0; cpb.ioRefNum = soutref; cpb.csCode = 11; err = PBControl(&cpb, false); if (err != noErr) { puts("Error on serial break mode."); if (err == notOpenErr) puts("Not open error."); else if (err == unitEmptyErr) puts("Bad reference number error."); else if (err == controlErr) puts("Control error."); else puts("Unknown error."); return err; } fputs("Test serial port? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar == 'y') { /* Write data to the serial port. */ char *test_buffer = "Hello world!\r\n"; err = ser_send_all(soutref, test_buffer, 14); if (err == noErr) printf("Great write, %d bytes.\n", 14); /* pb.ioActCount */ else return err; /* SerStatus() */ cpb.csCode = 8; err = PBStatus(&cpb, false); if (err == noErr) { ssr = (SerStaRec*)cpb.csParam; printf("Status serial driver: " "cumErrs = %d, rdPend = %d, wrPend = %d\n", (int)ssr->cumErrs, (int)ssr->rdPend, (int)ssr->wrPend); } else { if (err == badUnitErr) puts("Bad reference number error."); else if (err == notOpenErr) puts("Not open error."); else if (err == unitEmptyErr) puts("Bad reference number error."); else if (err == statusErr) puts("Status error."); else puts("Unknown error."); return err; } } return err; } void shutdown_serial() { /* RAMSDClose (sPortA); */ } void reconf_serial(soutref) int *soutref; { OSErr err; if (g_ser_init) { shutdown_serial(); g_ser_init = 0; } err = config_serial(soutref); if (err == noErr) g_ser_init = 1; if (!g_ser_init) { puts("Serial communications have NOT been configured.\n" "You will only be able to display information."); } } /* Fill up the buffer with disk driver (i.e. "volume") reads. */ OSErr vol_read_all(vrefnum, refnum, offset, buffer, size) int vrefnum; int refnum; unsigned long offset; unsigned char *buffer; unsigned int size; { OSErr err; IOParam pb; int pend_bufsz = 0; /* Fill up the buffer with synchronous disk reads. */ while (pend_bufsz < size) { pb.ioVRefNum = vrefnum; pb.ioRefNum = refnum; pb.ioBuffer = (char*)buffer + pend_bufsz; pb.ioReqCount = size - pend_bufsz; pb.ioPosMode = fsFromStart; pb.ioPosOffset = offset + pend_bufsz; err = PBRead(&pb, false); if (err != noErr) { puts("Disk input error."); if (err == badUnitErr) puts("Bad unit error."); else if (err == notOpenErr) puts("Drive isn't open error."); else if (err == unitEmptyErr) puts("Bad reference number error."); else if (err == readErr) puts("Read error."); else puts("Unknown error."); break; } pend_bufsz += pb.ioActCount; } return err; } /* Send all bytes in the buffer to the serial communications port. */ OSErr ser_send_all(soutref, buffer, size) int soutref; unsigned char *buffer; unsigned int size; { OSErr err; IOParam pb; unsigned int pend_bufsz = 0; while (pend_bufsz < size) { pb.ioVRefNum = 0; pb.ioRefNum = soutref; pb.ioBuffer = (char*)buffer + pend_bufsz; pb.ioReqCount = size - pend_bufsz; pb.ioPosMode = fsFromMark; pb.ioPosOffset = 0; err = PBWrite(&pb, false); if (err != noErr) { puts("Serial output error."); if (err == badUnitErr) puts("Bad unit error."); else if (err == notOpenErr) puts("Drive isn't open error."); else if (err == unitEmptyErr) puts("Bad reference number error."); else if (err == writErr) puts("Write error."); else puts("Unknown error."); break; } pend_bufsz += pb.ioActCount; } return err; } /* Execute a single SCSI read-style command, with proper bus arbitrate and free. N.B. Purportedly the reason for SCSIInstr magic is to provide for a more optimized code path for reading multiple blocks, but we don't need that additional speed since our serial communications is the main speed bottleneck. Also, SCSI READ allows for reading more than one block at a time. TODO: Evaluate/answer this question. Is there a limit on the number of SCSI blocks that can be read continuously in a single SCSI command? I imagine that could have been one of the motivations for the SCSIInstr construct. And that purportedly older hardware would have a lower limit than newer hardware. Hopefully reading 4 blocks at a time isn't too much for the oldest of hardware. */ OSErr sc1read(pbsc) PBSc1 *pbsc; { SCSIInstr tib[2]; OSErr err; int err_cmd = 0; tib[0].scOpcode = scNoInc; tib[0].scParam1 = (long)pbsc->buffer; tib[0].scParam2 = pbsc->buffer_len; tib[1].scOpcode = scStop; tib[1].scParam1 = 0; tib[1].scParam2 = 0; err = SCSIGet(); if (err != noErr) { err_cmd = 0; goto showerr; } err = SCSISelect(pbsc->scsi_id); if (err != noErr) { err_cmd = 1; /* N.B. Some Macintoshes will arbitrate the bus on `SCSIGet()', in which case we must call `SCSIComplete()' to free the bus. */ goto cleanup; } err = SCSICmd((char*)pbsc->cdb, pbsc->cdb_len); if (err != noErr) { err_cmd = 2; goto cleanup; } err = SCSIRead((char*)tib); if (err != noErr) err_cmd = 3; cleanup: pbsc->comp_err = SCSIComplete(&pbsc->comp_status, &pbsc->comp_msg, (unsigned long)SC_COMPLETION_TIMEOUT); showerr: if (err == noErr && pbsc->comp_err == noErr) goto check_status; switch (err_cmd) { case 0: puts("Error executing SCSIGet()."); if (err == scCommErr) puts("Communications erro, operation timeoutr."); else if (err == scArbNBErr) puts("Bus busy, arbitration timeout."); else if (err == scMgrBusyErr) puts("SCSI Manager busy error."); else puts("Unknown error."); break; case 1: puts("Error executing SCSISelect()."); if (err == scCommErr) puts("Communications error, operation timeout."); else if (err == scArbNBErr) puts("Bus busy, arbitration timeout."); else if (err == scSequenceErr) puts("Attempted operation is out of sequence."); else puts("Unknown error."); break; case 2: puts("Error executing SCSICmd()."); if (err == scCommErr) puts("Communications error, operation timeout."); else if (err == scArbNBErr) puts("Bus busy, arbitration timeout."); else if (err == scPhaseErr) puts("Phase error on the SCSI bus."); else puts("Unknown error."); break; case 3: puts("Error executing SCSIRead()."); if (err == scCommErr) puts("Communications error, operation timeout."); else if (err == scBadParmsErr) puts("Unrecognized TIB instruction."); else if (err == scPhaseErr) puts("Phase error on the SCSI bus."); else if (err == scCompareErr) puts("Comparison error from scComp instruction."); else puts("Unknown error."); break; } if (pbsc->comp_err != noErr) { puts("Error executing SCSIComplete()."); if (pbsc->comp_err == scCommErr) puts("Communications error, operation timeout."); else if (pbsc->comp_err == scPhaseErr) puts("Phase error on the SCSI bus."); else if (pbsc->comp_err == scComplPhaseErr) puts("SCSI bus was not in status phase on " "entry to SCSIComplete."); else puts("Unknown error."); /* Make sure we return an error in the general error return. */ if (err == noErr) err = pbsc->comp_err; } return err; check_status: /* When checking status, we make sure we return an error in the general error return. */ if (pbsc->comp_status == SC_CHECK_CONDITION) { puts("Status error on SCSI command."); err = scCommErr; } else if (pbsc->comp_status != SC_GOOD) { printf("Status error %d on SCSI command.", pbsc->comp_status); err = scCommErr; } return err; } /* Execute two SCSI INQUIRY commands and one SCSI READ CAPACITY (10) command to gather core metadata about the indicated SCSI device. */ OSErr sc3read_meta(meta) ScMeta *meta; { OSErr err; PBSc1 pbsc; /* Set up the command buffer with the SCSI INQUIRY command. */ /* PLEASE NOTE: MC68000 cannot do unaligned memory accesses, so when constructing the SCSI command buffer, be careful not to write any C code that would be naively compiled into such machine code. */ /* PLEASE NOTE: Not all Macintosh emulators emulate processor exceptions on unaligned memory accesses, so be extra cautious when developing in an emulated environment. */ pbsc.scsi_id = meta->scsi_id; pbsc.cdb[0] = SC_INQUIRY; /* OPCODE */ pbsc.cdb[1] = 0; /* Reserved (6 bit), CMDDT, EVPD */ pbsc.cdb[2] = 0; /* PAGE CODE */ /* ALLOCATION LENGTH (short) */ pbsc.cdb[3] = 0; pbsc.cdb[4] = 5; pbsc.cdb[5] = 0; /* CONTROL */ pbsc.cdb_len = 6; pbsc.buffer = (unsigned char*)&meta->iq_resp; pbsc.buffer_len = 5; /* First get the length of the additional INQUIRY data. */ err = sc1read(&pbsc); if (err != noErr) { puts("Error executing SCSI INQUIRY command 1."); return err; } /* Now read the additional INQUIRY data. */ pbsc.cdb[3] = 0; pbsc.cdb[4] += meta->iq_resp.AdditionalLength; pbsc.buffer_len += meta->iq_resp.AdditionalLength; if (5 + meta->iq_resp.AdditionalLength > sizeof(ScInquiryRecord)) { puts("Warning: Truncating SCSI INQUIRY data."); pbsc.cdb[4] = sizeof(ScInquiryRecord); pbsc.buffer_len = sizeof(ScInquiryRecord); } err = sc1read(&pbsc); if (err != noErr) { puts("Error executing SCSI INQUIRY command 2."); return err; } /* Now do a SCSI READ CAPACITY (10). */ pbsc.scsi_id = meta->scsi_id; pbsc.cdb[0] = SC_READ_CAPACITY_10; /* OPCODE */ pbsc.cdb[1] = 0; /* Reserved */ /* LOGICAL BLOCK ADDRESS (long) */ *(int*)(pbsc.cdb + 2) = (int)0; *(int*)(pbsc.cdb + 4) = (int)0; *(int*)(pbsc.cdb + 6) = 0; /* Reserved */ pbsc.cdb[8] = 0; /* Reserved */ pbsc.cdb[9] = 0; /* CONTROL */ pbsc.cdb_len = 10; pbsc.buffer = (unsigned char*)&meta->read_cap; pbsc.buffer_len = sizeof(ScReadCapRecord); err = sc1read(&pbsc); if (err != noErr) { puts("Error executing SCSI READ CAPACITY command."); return err; } return err; } /* Execute a single SCSI READ (10) command to read disk block(s). */ OSErr sc1read_blk(scsi_id, first_lba, blkcount, buffer, size) int scsi_id; unsigned long first_lba; unsigned int blkcount; unsigned char *buffer; unsigned int size; { OSErr err; PBSc1 pbsc; /* Fill up the buffer with synchronous SCSI disk reads. */ /* Set up the command buffer with the SCSI READ (10) command. */ /* PLEASE NOTE: MC68000 cannot do unaligned memory accesses, so when constructing the SCSI command buffer, be careful not to write any C code that would be naively compiled into such machine code. */ /* PLEASE NOTE: Not all Macintosh emulators emulate processor exceptions on unaligned memory accesses, so be extra cautious when developing in an emulated environment. */ pbsc.scsi_id = scsi_id; pbsc.cdb[0] = SC_READ_10; /* OPCODE */ pbsc.cdb[1] = 0; /* misc flags */ /* LOGICAL BLOCK ADDRESS (long) */ *(int*)(pbsc.cdb + 2) = (int)(first_lba >> 16); *(int*)(pbsc.cdb + 4) = (int)first_lba; /* Reserved (3 bits), GROUP NUMBER? (5 bits) */ pbsc.cdb[6] = 0; /* TRANSFER LENGTH (short, blocks) */ pbsc.cdb[7] = (unsigned char)(blkcount >> 8); pbsc.cdb[8] = (unsigned char)blkcount; pbsc.cdb[9] = 0; /* CONTROL */ pbsc.cdb_len = 10; pbsc.buffer = buffer; pbsc.buffer_len = size; err = sc1read(&pbsc); return err; } /* N.B. "Drive" is actually a "volume" abd "volume" is a "virtual volume" in modern terminology. */ void prompt_pick_vol(td_pick, td_dnum, td_dref, td_size) int *td_pick; int *td_dnum; int *td_dref; unsigned long *td_size; { QHdrPtr the_head = &DrvQHdr; DrvQEl2 *elem = (DrvQEl2*)(the_head->qHead); unsigned char *dflags = ((unsigned char*)elem) - 4; /* PLEASE NOTE: Drive queue sizes are in units of 512-byte blocks. */ while (elem) { unsigned long new_td_size; int pchar = 'n'; if (elem->qType == drvQType) { int pchar = 'n'; puts("Classic drive queue header."); printf("Drive %d, ref %d, FSID %d, DrvSize %d\n", elem->dQDrive, elem->dQRefNum, elem->dQFSID, elem->dQDrvSize); new_td_size = 1600; } else if (elem->qType == 0) { int pchar = 'n'; puts("Small disk drive queue header."); printf("Drive %d, ref %d, FSID %d, DrvSize %d\n", elem->dQDrive, elem->dQRefNum, elem->dQFSID, elem->dQDrvSize); new_td_size = 1600; } else if (elem->qType == 1) { int pchar = 'n'; unsigned long full_size = (unsigned long)elem->dQDrvSize | ((unsigned long)elem->dQDrvSz2 << 16); puts("Large disk drive queue header."); printf("Drive %d, ref %d, FSID %d, DrvSize %lu\n", elem->dQDrive, elem->dQRefNum, elem->dQFSID, full_size); new_td_size = full_size; } else { printf("Unexpected type %d!\n", elem->qType); printf("Drive %d, ref %d, FSID %d, DrvSize %d\n", elem->dQDrive, elem->dQRefNum, elem->dQFSID, elem->dQDrvSize); } if ((dflags[0] & 0x80) == 0x80) puts("Volume locked."); else puts("Volume unlocked."); if (dflags[1] == 0) puts("No disk in drive."); else if (dflags[1] == 1 || dflags[1] == 2) puts("Disk in drive."); else if (dflags[1] == 8) puts("Non-ejectable disk."); else if (dflags[1] >= 0xfc && dflags[1] <= 0xff) puts("Disk was ejected within last 1.5 seconds."); else if (dflags[1] == 0x48) puts("Non-ejectable but driver wants eject call."); else printf("dflags[1] == %d\n", (int)dflags[1]); if ((dflags[3] & 0x80) == 0) { puts("Single-sided disk."); if (new_td_size == 1600) new_td_size = 400; } else puts("Double-sided disk or not applicable."); fputs("Pick? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar == 'y' && !(*td_pick)) { *td_pick = 1; *td_dnum = elem->dQDrive; *td_dref = elem->dQRefNum; *td_size = new_td_size; } elem = (DrvQEl2*)elem->qLink; } } void prompt_send_vol(soutref) int soutref; { int td_pick = 0; int td_dnum; int td_dref; unsigned long td_size; int pchar = 'n'; unsigned long td_cur = 0; int bufsz_lim = 0; char *ftagbase = (char*)g_buffer; prompt_pick_vol(&td_pick, &td_dnum, &td_dref, &td_size); if (!td_pick || td_size == 0) return; { OSErr err; int pchar = 'n'; /* First test the volume with a single block read. */ err = vol_read_all(td_dnum, td_dref, (long)0, (char*)g_buffer, 512); if (err == noErr) { unsigned long res_size = 512; /* pb.ioActCount; */ printf("Great read, %ld bytes.\n", res_size); fputs("Show hex dump? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar == 'y') showhex((char*)g_buffer, res_size); } else return; /* Return to main menu. */ if (!g_ser_init) { puts("Serial communications have NOT been configured.\n" "Returning to main menu..."); return; } } fputs("Include regular disk blocks? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar == 'y') { bufsz_lim += 512; ftagbase += 512; } fputs("Include file tags? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar == 'y') bufsz_lim += 12; fputs("Send 14 bytes volume metadata over serial? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar == 'y') { /* Now we can send over a binary copy of the volume metadata. */ VolMeta vol_meta; OSErr err; vol_meta.td_dnum = td_dnum; vol_meta.td_dref = td_dref; vol_meta.blksize = bufsz_lim; vol_meta.td_size = td_size; vol_meta.user_size = td_size; err = ser_send_all(soutref, (char*)&vol_meta, sizeof(vol_meta)); /* In the event of an error here, we can still carry on with more volume operations. */ } { /* Show the transfer size and time estimate. */ unsigned long total_size = td_size << 9; /* td_size * 512 */ char *kbits_str = ""; unsigned long est_secs = 0; if (g_ser_speed == (int)baud57600) { kbits_str = "57.6"; est_secs = (total_size / 1000 + 1) * 80 / 576; } else /* 19.2 kbits/sec */ { kbits_str = "19.2"; est_secs = (total_size / 1000 + 1) * 80 / 192; } printf("Send %ld bytes @ %s kbits/sec, est. %ld seconds\n", total_size, kbits_str, est_secs); } { /* Prompt the user again if they are really ready to send the full volume. */ int pchar = 'n'; fputs("Perform full disk + file tags transfer? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar != 'y') return; } fputs("Copy in progress (^C to interrupt)", stdout); /* Loop through copying disk blocks, work with all sizes in bytes. */ td_size <<= 9; /* td_size *= 512; */ while (td_cur < td_size) { OSErr err; if ((bufsz_lim & (16 - 1)) == 12) { /* Read one disk block + file tags. */ IOParam pb; pb.ioVRefNum = td_dnum; pb.ioRefNum = td_dref; pb.ioBuffer = (char*)g_buffer; pb.ioReqCount = 512; pb.ioPosMode = fsFromStart; pb.ioPosOffset = td_cur; err = PBRead(&pb, false); if (err != noErr) { puts("Disk input error."); if (err == badUnitErr) puts("Bad unit error."); else if (err == notOpenErr) puts("Drive isn't open error."); else if (err == unitEmptyErr) puts("Bad reference number error."); else if (err == readErr) puts("Read error."); else puts("Unknown error."); break; } if (pb.ioActCount != 512) { puts("Error: Failed to read exactly one block."); break; } td_cur += 512; *(long*)(ftagbase + 0) = BufTgFNum; *(int*)(ftagbase + 4) = BufTgFFlag; *(int*)(ftagbase + 6) = BufTgFBkNum; *(long*)(ftagbase + 8) = BufTgDate; } else { /* Fill up the buffer with synchronous disk reads. */ int read_bytes = 512 * 4; /* Check if we have a non-divisible-by-4 block read at the end. */ if (td_cur + read_bytes > td_size) read_bytes = td_size - td_cur; err = vol_read_all(td_dnum, td_dref, td_cur, (char*)g_buffer, read_bytes); if (err != noErr) break; td_cur += read_bytes; } /* Empty the buffer with synchronous serial writes. */ err = ser_send_all(soutref, (char*)g_buffer, bufsz_lim); if (err != noErr) break; { /* Check for user interrupt. */ EventRecord ev; int we_handle; we_handle = EventAvail(keyDownMask | autoKeyMask, &ev); if (we_handle && ev.what == keyDown || ev.what == autoKey) { char charCode = ev.message & 0x000000ff; if (((charCode == 'C' || charCode == 'c') && (ev.modifiers & cmdKey) == cmdKey) || charCode == '\x03') { /* Consume the event and interrupt our copy loop. */ GetNextEvent(keyDownMask | autoKeyMask, &ev); puts("^C\nUser interrupt!"); break; } } } /* Update status to the user. */ if ((td_cur & (2048 - 1)) == 0) putchar('.'); } putchar('\n'); if (td_cur == td_size) { puts("Successful disk + file tags copy. Enjoy!!!"); } td_size >>= 9; /* td_size /= 512; */ } void prompt_send_scsi(soutref) int soutref; { int pchar = 'n'; OSErr err; while (1) { fputs("SCSI ID (0-6): ", stdout); pchar = getchar(); putchar('\n'); if (pchar >= '0' && pchar <= '6') g_sc.scsi_id = pchar - '0'; else g_sc.scsi_id = 0; printf("Using SCSI ID %d\n", g_sc.scsi_id); err = sc3read_meta(&g_sc); if (err == noErr) sc_print_meta(&g_sc); /* else Error message has already been printed. */ fputs("Try again with a different SCSI ID? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar != 'y') { if (err != noErr) return; else break; } } /* Now do a READ on block zero. */ err = sc1read_blk(g_sc.scsi_id, (long)0, 1, (unsigned char*)&g_ddr, sizeof(Block0)); if (err != noErr) { puts("Error executing SCSI READ command."); return; } { /* Now do a READ of the first four partition map entries, assuming there are at least four. */ /* N.B. We do a second SCSI read instruction so that we do not have to deal with blocks greater than 512 bytes in this code. */ unsigned i; for (i = 0; i < 4; i++) { err = sc1read_blk(g_sc.scsi_id, (long)(i + 1), 1, (unsigned char*)&g_pm[i], sizeof(Partition)); if (err != noErr) { puts("Error executing SCSI READ command."); return; } } } /* Now we can print out all the information we've discovered. */ sc_print_meta(&g_sc); { int pchar = 'n'; unsigned long sw_size = 512; fputs("Show hex dump? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar == 'y') showhex((char*)&g_ddr, sw_size); } fputs("Display driver descriptor record? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar == 'y') mac_print_ddr(&g_ddr); fputs("Display partition map? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar == 'y') mac_print_pm(&g_pm, 4); /* Perform some consistency checks between the SCSI metadata and the driver descriptor record. */ if (g_sc.read_cap.blksize != g_ddr.sbBlkSize) { puts("Error: SCSI block size does not match driver " "descriptor block size."); return; } if (g_sc.read_cap.last_lba + 1 != g_ddr.sbBlkCount) { puts("Warning: SCSI block count does not match driver " "descriptor block count."); } if (!g_ser_init) { puts("Serial communications have NOT been configured.\n" "Returning to main menu..."); return; } fputs("Send 266 bytes SCSI metadata over serial? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar == 'y') { /* Now we can send over a binary copy of the SCSI ID, SCSI INQUIRY, and SCSI READ CAPACITY response records. */ err = ser_send_all(soutref, (char*)&g_sc, sizeof(g_sc)); /* In the event of an error here, we can still carry on with more SCSI operations. */ } { /* Show the transfer size and time estimate. */ unsigned long total_size = (g_sc.read_cap.last_lba + 1) * g_sc.read_cap.blksize; char *kbits_str = ""; unsigned long est_secs = 0; if (g_ser_speed == (int)baud57600) { kbits_str = "57.6"; est_secs = (total_size / 1000 + 1) * 80 / 576; } else /* 19.2 kbits/sec */ { kbits_str = "19.2"; est_secs = (total_size / 1000 + 1) * 80 / 192; } printf("Send %ld bytes @ %s kbits/sec, est. %ld seconds\n", total_size, kbits_str, est_secs); } fputs("Perform full disk + system bytes transfer? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar == 'y' && g_sc.read_cap.blksize > 532) { puts("Error: Only <= 532-byte block SCSI disks are " "currently supported."); return; } else if (pchar == 'y') { unsigned long sc_size = g_sc.read_cap.last_lba + 1; int blksize = g_sc.read_cap.blksize; /* Loop through copying disk blocks, work with all sizes in bytes. */ unsigned long sc_cur = 0; if (blksize > 512) { int pchar = 'n'; printf("Block size %d > 512. Proceed? (y/n) ", blksize); pchar = getchar(); putchar('\n'); if (pchar != 'y') return; } fputs("Copy in progress (^C to interrupt)", stdout); while (sc_cur < sc_size) { OSErr err; PBSc1 pbsc; IOParam pb; int bufsz_lim = 0; /* Fill up the buffer with synchronous SCSI disk reads. */ int read_blks = 4; /* Check if we have a non-divisible-by-4 block read at the end. */ if (sc_cur + read_blks > sc_size) read_blks = sc_size - sc_cur; err = sc1read_blk(g_sc.scsi_id, sc_cur, read_blks, (unsigned char*)g_buffer, blksize * read_blks); if (err != noErr) { puts("Error executing SCSI READ command."); break; } bufsz_lim = blksize * read_blks; sc_cur += read_blks; /* Empty the buffer with synchronous serial writes. */ err = ser_send_all(soutref, (char*)g_buffer, bufsz_lim); if (err != noErr) break; { /* Check for user interrupt. */ EventRecord ev; int we_handle; we_handle = EventAvail(keyDownMask | autoKeyMask, &ev); if (we_handle && ev.what == keyDown || ev.what == autoKey) { char charCode = ev.message & 0x000000ff; if (((charCode == 'C' || charCode == 'c') && (ev.modifiers & cmdKey) == cmdKey) || charCode == '\x03') { /* Consume the event and interrupt our copy loop. */ GetNextEvent(keyDownMask | autoKeyMask, &ev); puts("^C\nUser interrupt!"); break; } } } /* Update status to the user. */ putchar('.'); } putchar('\n'); if (sc_cur == sc_size) { puts("Successful disk copy. Enjoy!!!"); } } } void prompt_eject_vol() { QHdrPtr the_vhead = &VCBQHdr; int v_pick = 0; int v_ref; OSErr err; IOParam pb; VCB *elem = (VCB*)(the_vhead->qHead); /* Print a list of volumes to choose from, letter keys, prompt user to select. */ puts("Volumes:"); while (elem) { StringPtr pstr = (StringPtr)elem->vcbVN; char *curpos = (char*)pstr + 1; char *end = curpos + pstr[0]; int pchar = 'n'; fputs("Volume Name: ", stdout); while (curpos != end) putchar(*curpos++); putchar('\n'); printf("Volume reference number: %d\n", elem->vcbVRefNum); fputs("Pick? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar == 'y' && !v_pick) { v_pick = 1; v_ref = elem->vcbVRefNum; } elem = (VCB*)elem->qLink; } if (v_pick) { /* If chosen volume is NOT the startup volume, also unmount. */ VCB *startup = (VCB*)(the_vhead->qHead); pb.ioVRefNum = v_ref; pb.ioNamePtr = (StringPtr)0; err = PBEject(&pb); if (err == noErr) { puts("Successful eject."); if (startup->vcbVRefNum != pb.ioVRefNum) { err = PBUnmountVol(&pb); if (err == noErr) puts("Successful unmount."); } } if (err == noErr) ; else if (err == bdNamErr) puts("Bad volume name error."); else if (err == extFSErr) puts("External file system error."); else if (err == ioErr) puts("I/O error."); else if (err == nsDrvErr) puts("No such drive error."); else if (err == nsvErr) puts("No such volume error."); else if (err == paramErr) puts("No default volume error."); else puts("Unknown error."); /* TODO: However, do not allow user to eject the startup volume if it is a hard disk. */ } } stdio_main() { int soutref = 0; /* We have to output some short dummy text or else THINK C stdio will stall, I don't know why. */ puts(""); { int pchar = 'n'; fputs( "PLEASE NOTE: If your hard disk drive is making abnormal sounds that it\n" "didn't previously make when it was younger in normal operation, please\n" "shut down immediately and contact a professional to make the disk\n" "image. These are signs of impending hard disk failure.\n" "\n" "Continue? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar != 'y') return; } { int pchar = 'n'; fputs( "\n" "For optimal performance, please ensure that disk caching is disabled\n" "and you booted the single-tasking Finder rather than MultiFinder.\n" "\n" "Continue? (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar != 'y') return; } { int pchar = 'n'; fputs("Configure serial communications (y/n) ", stdout); pchar = getchar(); putchar('\n'); if (pchar == 'y') reconf_serial(&soutref); else puts("Serial communications have NOT been configured.\n" "You will only be able to display information."); } while (1) { int pchar = 'n'; fputs( "\n" "MAIN MENU\n" "\n" "1. Configure Serial 2. Send Volume 3. Send SCSI 4. Eject\n" "5. Quit\n" "Choice? (1-5) ", stdout); pchar = getchar(); putchar('\n'); switch (pchar) { case '1': reconf_serial(&soutref); break; case '2': prompt_send_vol(soutref); break; case '3': prompt_send_scsi(soutref); break; case '4': prompt_eject_vol(); break; case '5': break; default: puts("Invalid choice."); break; } if (pchar == '5') break; } if (g_ser_init) shutdown_serial(); } main() { stdio_main(); }