diff --git a/build/unix/os9/Makefile b/build/unix/os9/Makefile index 31aff15..442b3b3 100644 --- a/build/unix/os9/Makefile +++ b/build/unix/os9/Makefile @@ -8,7 +8,7 @@ CFLAGS += -I../../../include -Wall -MMD -MP SRCS = os9copy.c os9dsave.c os9gen.c os9modbust.c os9dcheck.c os9dump.c \ os9id.c os9padrom.c os9_main.c os9del.c os9format.c os9ident.c \ os9rename.c os9attr.c os9deldir.c os9free.c os9list.c os9cmp.c \ - os9dir.c os9fstat.c os9makdir.c + os9dir.c os9fstat.c os9makdir.c os9reveal.c OBJS = $(SRCS:.c=.o) DEPS = $(OBJS:.o=.d) diff --git a/doc/ToolShed.md b/doc/ToolShed.md index 67125be..af84ab4 100644 --- a/doc/ToolShed.md +++ b/doc/ToolShed.md @@ -36,6 +36,7 @@ ToolShed v2.5.1 * [MODBUST](#modbust) - Bust a single merged file of OS-9 modules into separate files * [PADROM](#padrom) - Pad a file to a specific length * [RENAME](#rename_os9) - Give a file a new filename + * [REVEAL](#reveal_os9) - Explain what lives at a given LSN of a disk image * [decb](#decb) - Manipulate RSDOS formatted disk images * [Options](#decb_exec_option) - The DECB executive's option * [ATTR](#attr_decb) - Display or modify file attributes @@ -828,6 +829,35 @@ The rename command renames a file with a new filename. --- +

REVEAL - Explain what lives at a given LSN of a disk image

+ +#### Syntax and Scope + + reveal {[]} [:] + reveal -b + +This command is intended for RBF disk image files only. + +#### Description + +Reveal takes an LSN (and, optionally, a byte offset within that sector) or an absolute byte offset into the image, and explains, in plain English, exactly what lives at that location: a field of LSN0, a bitmap byte and the cluster/LSN range it tracks, a file descriptor and which field of it, a segment entry, a directory entry (and which byte of its name or LSN pointer), or a byte inside a file's data. If the LSN doesn't map to anything reveal recognizes, it reports whether the bitmap marks it allocated or free, and whether it falls within the boot track. + +Options: + + -b Treat as an absolute byte offset from the start + of the disk image, rather than an LSN[:offset] pair. + -d Dragon disk (used to find the location of a boot track) + +#### Example + + os9 reveal 68SDC.VHD -b 88179138 + + Examining LSN 344,449, byte 194 of '68SDC.VHD'... + + This is byte 164,290 of file "/SOURCECODE/ASM/NITROS9/CMDS/grfdrv_older_source.lzh" (the 164,291st byte overall), found in the 2nd segment/extent of this file, which begins at LSN 344,064. + +--- +

decb

The following pages document the commands built into the decb tool. Its interface is similar to that of the os9 tool discussed in previous pages. diff --git a/include/rbfutil.h b/include/rbfutil.h index 4485671..74f5cea 100644 --- a/include/rbfutil.h +++ b/include/rbfutil.h @@ -46,7 +46,14 @@ int os9padrom(int, char **); int os9rename(int, char **); int StrToInt(char *s); -void show_help(char **helpMessage); +void show_help(char const * const *helpMessage); + +struct personality +{ + int startlsn; +}; + +error_code get_boottrack_lsn(lsn0_sect LSN0, struct personality *hwtype, int *startlsn, int verbose); #ifdef __cplusplus } diff --git a/include/util.h b/include/util.h index 71d824d..86973e4 100644 --- a/include/util.h +++ b/include/util.h @@ -42,6 +42,7 @@ int os9makdir(int, char **); int os9modbust(int, char **); int os9padrom(int, char **); int os9rename(int, char **); +int os9reveal(int, char **); int os9rdump(int, char **); int StrToInt(char *s); diff --git a/os9/os9_main.c b/os9/os9_main.c index 1a1ca0d..67118e9 100644 --- a/os9/os9_main.c +++ b/os9/os9_main.c @@ -56,6 +56,7 @@ static struct cmdtbl table[] = { {os9modbust, "modbust"}, {os9padrom, "padrom"}, {os9rename, "rename"}, + {os9reveal, "reveal"}, {NULL, NULL} }; diff --git a/os9/os9gen.c b/os9/os9gen.c index ff4bc11..bc060b8 100644 --- a/os9/os9gen.c +++ b/os9/os9gen.c @@ -12,18 +12,12 @@ #include #include #include - -struct personality -{ - int startlsn; -}; +#include static int do_os9gen(char **argv, char *device, char *bootfile, char *trackfile, struct personality *hwtype, int extended); -error_code get_boottrack_lsn(lsn0_sect LSN0, struct personality *hwtype, int *startlsn); - static struct personality coco = { 18 * 34 }; static struct personality dragon = { 2 }; @@ -193,7 +187,7 @@ static int do_os9gen(char **argv, char *device, char *bootfile, return (1); } - ec = get_boottrack_lsn(*cpath->path.os9->lsn0, hwtype, &startlsn); + ec = get_boottrack_lsn(*cpath->path.os9->lsn0, hwtype, &startlsn, 1); if (ec != 0) { _coco_close(cpath); @@ -269,7 +263,7 @@ static int do_os9gen(char **argv, char *device, char *bootfile, return (1); } - ec = get_boottrack_lsn(*opath->path.os9->lsn0, hwtype, &startlsn); + ec = get_boottrack_lsn(*opath->path.os9->lsn0, hwtype, &startlsn, 1); if (ec != 0) { _coco_close(opath); @@ -433,7 +427,19 @@ static int do_os9gen(char **argv, char *device, char *bootfile, return (0); } -error_code get_boottrack_lsn(lsn0_sect LSN0, struct personality *hwtype, int *startlsn) +#define QPRINTF(enabled, ...) \ + do { \ + if (enabled) \ + printf(__VA_ARGS__); \ + } while (0) + +#define QFPRINTF(enabled, ...) \ + do { \ + if (enabled) \ + fprintf(stderr, __VA_ARGS__); \ + } while (0) + +error_code get_boottrack_lsn(lsn0_sect LSN0, struct personality *hwtype, int *startlsn, int verbose) { int is_osk; u_char *pd_sct, *pd_cyl, *pd_sid, *pd_typ; @@ -459,12 +465,12 @@ error_code get_boottrack_lsn(lsn0_sect LSN0, struct personality *hwtype, int *st if (*startlsn == 2) { - printf("Dragon boottrack selected: "); + QPRINTF(verbose, "Dragon boottrack selected: "); /* Check to make sure the disk image has minimum of 18 sectors per track */ if (int2(pd_sct) < 18) { - printf("\n"); - fprintf(stderr, + QPRINTF(verbose, "\n"); + QFPRINTF(verbose, "Error: minimum sectors per track of 18 required for DragonDOS, found %d\n", int2(pd_sct)); return (1); @@ -472,7 +478,7 @@ error_code get_boottrack_lsn(lsn0_sect LSN0, struct personality *hwtype, int *st } else { - printf("CoCo boottrack selected: "); + QPRINTF(verbose, "CoCo boottrack selected: "); /* If special startLSN for boottrack is set then set startlsn to */ /* the value stored in specialStartLSN */ if (specialStartLSN > 0) @@ -492,7 +498,7 @@ error_code get_boottrack_lsn(lsn0_sect LSN0, struct personality *hwtype, int *st /* Check to make sure the disk image has minimum of 18 sectors per track */ if (int2(pd_sct) < 18) { - printf("\n"); + QPRINTF(verbose, "\n"); fprintf(stderr, "Error: minimum sectors per track of 18 required for Disk Basic, found %d\n", int2(pd_sct)); @@ -501,7 +507,7 @@ error_code get_boottrack_lsn(lsn0_sect LSN0, struct personality *hwtype, int *st /* Check to make sure the disk image has minimum of 35 tracks */ if (int2(pd_cyl) < 35) { - printf("\n"); + QPRINTF(verbose, "\n"); fprintf(stderr, "Error: minimum number of tracks required for Disk Basic is 35, found %d\n", int2(pd_cyl)); diff --git a/os9/os9reveal.c b/os9/os9reveal.c new file mode 100644 index 0000000..c0237ac --- /dev/null +++ b/os9/os9reveal.c @@ -0,0 +1,877 @@ +/******************************************************************** + * reveal.c - os9 disk archaeology utility + * + * Given an LSN (and optionally a byte offset within that sector), or + * an absolute byte offset into the image, "reveal" walks the LSN0 + * header, the allocation bitmap, and the live directory tree of an + * OS-9 disk image and explains -- in plain English -- exactly what + * lives at that location: a field of LSN0, a bitmap byte and the + * cluster/LSN range it tracks, a file descriptor and which field of + * it, a segment entry, a directory entry (and which byte of + * its name or LSN pointer), or a byte inside a file's data. + * + * $Id$ + ********************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ------------------------------------------------------------------ + * types + * ------------------------------------------------------------------ */ + +/* full definition is further down, near get_boottrack_lsn(); a + * pointer is all reveal_target needs to carry it that far */ +// struct personality; + +typedef struct +{ + unsigned int target_lsn; /* LSN being asked about */ + unsigned int target_offset; /* byte offset within that LSN */ + struct personality *hwtype; + unsigned int max_valid_lsn; +} reveal_target; + +struct field_desc +{ + size_t offset; + size_t size; + const char *label; +}; + + +/* ------------------------------------------------------------------ + * globals / help + * ------------------------------------------------------------------ */ + +static char os9pathlist[256]; + +static char const *const helpMessage[] = { + "Syntax: reveal {[]} [:]\n", + " reveal -b \n", + "Usage: Explain exactly what lives at a given LSN (and, optionally,\n", + " byte offset within that sector) on an os9 disk image.\n", + "Options:\n", + " -b Treat as an absolute byte offset from the start\n", + " of the disk image, rather than an LSN[:offset] pair.\n", + " -d Dragon disk\n", + NULL +}; + +static long get_image_byte_size(os9_path_id path) +{ + struct stat st; + + if (path->fd == NULL) + return (-1); + + if (fstat(fileno(path->fd), &st) != 0) + return (-1); + + return ((long) st.st_size); +} + + +/* ------------------------------------------------------------------ + * small helpers + * ------------------------------------------------------------------ */ + +/* Format a number with thousands separators (e.g. 164291 -> "164,291"). + * Rotates through a small pool of buffers for the same reason ordinal() + * does -- so multiple calls in one printf() don't clobber each other. */ +#define NUM_NUM_BUFS 8 +static char num_bufs[NUM_NUM_BUFS][32]; +static int num_buf_idx = 0; + +static const char *format_num(unsigned long n) +{ + char raw[24]; + char *buf = num_bufs[num_buf_idx]; + int rawlen, i, j; + + num_buf_idx = (num_buf_idx + 1) % NUM_NUM_BUFS; + + snprintf(raw, sizeof(raw), "%lu", n); + rawlen = strlen(raw); + + for (i = 0, j = 0; i < rawlen; i++) + { + if (i > 0 && (rawlen - i) % 3 == 0) + buf[j++] = ','; + buf[j++] = raw[i]; + } + buf[j] = '\0'; + + return (buf); +} + +#define ORD_NUM_BUFS 8 +static char ord_bufs[ORD_NUM_BUFS][24]; +static int ord_buf_idx = 0; + +static const char *ordinal(unsigned int n) +{ + const char *suffix = "th"; + char *buf = ord_bufs[ord_buf_idx]; + + ord_buf_idx = (ord_buf_idx + 1) % ORD_NUM_BUFS; + + if ((n % 100) < 11 || (n % 100) > 13) + { + switch (n % 10) + { + case 1: + suffix = "st"; + break; + case 2: + suffix = "nd"; + break; + case 3: + suffix = "rd"; + break; + default: + suffix = "th"; + break; + } + } + + snprintf(buf, 24, "%s%s", format_num(n), suffix); + return (buf); +} + + +/* Decode a fixed-length os9 name field (high bit terminates the + * final character) into a plain C string. Mirrors the pattern + * os9id.c already uses for the LSN0 disk name. */ +static u_char *decode_os9_name(const u_char *raw, size_t len) +{ + char *tmp = malloc(len + 1); + + memcpy(tmp, raw, len); + tmp[len] = '\0'; + + return (OS9StringToCString((u_char *) tmp)); +} + +static u_char *decode_entry_name(os9_dir_entry *e) +{ + return (decode_os9_name(e->name, D_NAMELEN)); +} + + +/* ------------------------------------------------------------------ + * LSN0 field table + * ------------------------------------------------------------------ */ + +static const struct field_desc lsn0_fields[] = { + { offsetof(lsn0_sect, dd_tot), sizeof(((lsn0_sect *) 0)->dd_tot), "total sector count (dd_tot)" }, + { offsetof(lsn0_sect, dd_tks), sizeof(((lsn0_sect *) 0)->dd_tks), "track size (dd_tks)" }, + { offsetof(lsn0_sect, dd_map), sizeof(((lsn0_sect *) 0)->dd_map), "bitmap byte count (dd_map)" }, + { offsetof(lsn0_sect, dd_bit), sizeof(((lsn0_sect *) 0)->dd_bit), "sectors-per-cluster (dd_bit)" }, + { offsetof(lsn0_sect, dd_dir), sizeof(((lsn0_sect *) 0)->dd_dir), "root directory LSN (dd_dir)" }, + { offsetof(lsn0_sect, dd_own), sizeof(((lsn0_sect *) 0)->dd_own), "disk owner id (dd_own)" }, + { offsetof(lsn0_sect, dd_att), sizeof(((lsn0_sect *) 0)->dd_att), "disk attributes (dd_att)" }, + { offsetof(lsn0_sect, dd_dsk), sizeof(((lsn0_sect *) 0)->dd_dsk), "disk id (dd_dsk)" }, + { offsetof(lsn0_sect, dd_fmt), sizeof(((lsn0_sect *) 0)->dd_fmt), "disk format flags (dd_fmt)" }, + { offsetof(lsn0_sect, dd_spt), sizeof(((lsn0_sect *) 0)->dd_spt), "sectors-per-track (dd_spt)" }, + { offsetof(lsn0_sect, dd_res), sizeof(((lsn0_sect *) 0)->dd_res), "reserved area (dd_res)" }, + { offsetof(lsn0_sect, dd_bt), sizeof(((lsn0_sect *) 0)->dd_bt), "bootstrap LSN (dd_bt)" }, + { offsetof(lsn0_sect, dd_bsz), sizeof(((lsn0_sect *) 0)->dd_bsz), "bootfile size (dd_bsz)" }, + { offsetof(lsn0_sect, dd_dat), sizeof(((lsn0_sect *) 0)->dd_dat), "disk creation date (dd_dat)" }, + { offsetof(lsn0_sect, dd_nam), sizeof(((lsn0_sect *) 0)->dd_nam), "disk name (dd_nam)" }, + { offsetof(lsn0_sect, dd_opt), sizeof(((lsn0_sect *) 0)->dd_opt), "path descriptor options (dd_opt)" }, + { offsetof(lsn0_sect, dd_res2), sizeof(((lsn0_sect *) 0)->dd_res2), "reserved (dd_res2)" }, + { offsetof(lsn0_sect, dd_sync), sizeof(((lsn0_sect *) 0)->dd_sync), "OS-9/68K sync bytes (dd_sync, CRUZ)" }, + { offsetof(lsn0_sect, dd_maplsn), sizeof(((lsn0_sect *) 0)->dd_maplsn), "bitmap sector LSN (dd_maplsn)" }, + { offsetof(lsn0_sect, dd_lsnsize), sizeof(((lsn0_sect *) 0)->dd_lsnsize), "LSN size multiplier (dd_lsnsize)" }, + { offsetof(lsn0_sect, dd_versid), sizeof(((lsn0_sect *) 0)->dd_versid), "LSN0 version id (dd_versid)" }, +}; + +#define N_LSN0_FIELDS (sizeof(lsn0_fields) / sizeof(lsn0_fields[0])) + +static const size_t DD_NAM_OFFSET = offsetof(lsn0_sect, dd_nam); +static const size_t DD_NAM_SIZE = sizeof(((lsn0_sect *) 0)->dd_nam); + + +static void describe_lsn0_offset(lsn0_sect *l0, unsigned int offset) +{ + size_t i; + u_char *diskname = decode_os9_name(l0->dd_nam, DD_NAM_SIZE); + + if (offset >= DD_NAM_OFFSET && offset < DD_NAM_OFFSET + DD_NAM_SIZE) + { + unsigned int charIndex = offset - (unsigned int) DD_NAM_OFFSET + 1; + + printf("This is byte %s of LSN0 -- specifically, this is the %s character of the disk name (currently \"%s\").\n", + format_num(offset), ordinal(charIndex), diskname); + return; + } + + for (i = 0; i < N_LSN0_FIELDS; i++) + { + const struct field_desc *f = &lsn0_fields[i]; + + if (offset >= f->offset && offset < f->offset + f->size) + { + printf("This is byte %s of LSN0, the header sector of disk \"%s\" -- within the %s field (byte %u of %lu of it).\n", + format_num(offset), diskname, f->label, + offset - (unsigned int) f->offset + 1, + (unsigned long) f->size); + return; + } + } + + printf("This is byte %s of LSN0, in an unnamed or reserved area.\n", + format_num(offset)); +} + + +/* ------------------------------------------------------------------ + * file descriptor field table + * ------------------------------------------------------------------ */ + +static const struct field_desc fd_fields[] = { + { offsetof(fd_stats, fd_att), sizeof(((fd_stats *) 0)->fd_att), "attributes field (fd_att)" }, + { offsetof(fd_stats, fd_own), sizeof(((fd_stats *) 0)->fd_own), "owner id (fd_own)" }, + { offsetof(fd_stats, fd_dat), sizeof(((fd_stats *) 0)->fd_dat), "last-modified date (fd_dat)" }, + { offsetof(fd_stats, fd_lnk), sizeof(((fd_stats *) 0)->fd_lnk), "link count (fd_lnk)" }, + { offsetof(fd_stats, fd_siz), sizeof(((fd_stats *) 0)->fd_siz), "file size in bytes (fd_siz)" }, + { offsetof(fd_stats, fd_creat), sizeof(((fd_stats *) 0)->fd_creat), "creation date (fd_creat)" }, +}; + +#define N_FD_FIELDS (sizeof(fd_fields) / sizeof(fd_fields[0])) + + +static void describe_fd_offset(const char *pathname, unsigned int offset) +{ + size_t i; + size_t segArea = offsetof(fd_stats, fd_seg); + + if (offset == 0) + { + printf("This is the start of the file descriptor for file \"%s\".\n", + pathname); + return; + } + + if (offset >= segArea) + { + unsigned int segOffset = offset - (unsigned int) segArea; + unsigned int segIndex = segOffset / sizeof(fd_seg); + unsigned int fieldOff = segOffset % sizeof(fd_seg); + + if (segIndex >= NUM_SEGS) + { + printf("This is byte %s of the file descriptor for file \"%s\", past its last possible segment entry.\n", + format_num(offset), pathname); + return; + } + + if (fieldOff < 3) + printf("This is byte %s of the file descriptor for file \"%s\" -- the LSN field of its %s segment entry.\n", + format_num(offset), pathname, ordinal(segIndex + 1)); + else + printf("This is byte %s of the file descriptor for file \"%s\" -- the sector-count field of its %s segment entry.\n", + format_num(offset), pathname, ordinal(segIndex + 1)); + return; + } + + for (i = 0; i < N_FD_FIELDS; i++) + { + const struct field_desc *f = &fd_fields[i]; + + if (offset >= f->offset && offset < f->offset + f->size) + { + printf("This is byte %s of the file descriptor for file \"%s\", within its %s.\n", + format_num(offset), pathname, f->label); + return; + } + } + + printf("This is byte %s of the file descriptor for file \"%s\".\n", + format_num(offset), pathname); +} + + +/* ------------------------------------------------------------------ + * boot track LSN calculation + * + * Pasted in from a newer os9gen.c, and wired up below (see the -d + * option and the ALLOCATED-but-unmapped-LSN case in reveal()). + * ------------------------------------------------------------------ */ + +static struct personality coco = { 18 * 34 }; +static struct personality dragon = { 2 }; + +/* ------------------------------------------------------------------ + * bitmap + * ------------------------------------------------------------------ */ + +static void describe_bitmap_offset(os9_path_id path, reveal_target *tgt) +{ + unsigned int byteInBitmap = + (tgt->target_lsn - 1) * path->bps + tgt->target_offset; + unsigned int clusterStart = byteInBitmap * 8; + unsigned int lsnStart = clusterStart * path->spc; + unsigned int lsnEnd = lsnStart + (8 * path->spc) - 1; + + printf("This is byte %s of the allocation bitmap (LSN %s, %s bytes into the bitmap area). Each bit marks one %u-sector cluster as free or allocated, so this byte's 8 bits cover clusters %s through %s -- that is, LSNs %s through %s.\n", + format_num(byteInBitmap), format_num(tgt->target_lsn), + format_num(byteInBitmap), path->spc, + format_num(clusterStart), format_num(clusterStart + 7), + format_num(lsnStart), format_num(lsnEnd)); +} + + +/* ------------------------------------------------------------------ + * recursive directory-tree walk + * ------------------------------------------------------------------ */ + +static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, + const char *pathname, reveal_target *tgt, + unsigned int depth) +{ + fd_stats fd; + int i; + unsigned int prevBytes = 0; + int is_dir; + + /* A corrupted image can have a directory entry that points back + * at itself or an ancestor (accidentally or via disk damage -- + * no malice required). Without a limit that's unbounded + * recursion and a stack-overflow crash rather than a clean + * error, since we don't track visited LSNs. A generous cap + * catches that without affecting any real directory tree, which + * won't nest anywhere close to this deep. */ +#define REVEAL_MAX_DEPTH 256 + if (depth > REVEAL_MAX_DEPTH) + { + fprintf(stderr, + "reveal: directory nesting exceeds %d levels at\n" + "\"%s\" -- stopping (the image may have a corrupt\n" + "or cyclic directory structure).\n", + REVEAL_MAX_DEPTH, pathname); + return (0); + } + + /* A corrupt directory entry or segment can claim an LSN that's + * beyond the physical file or the declared format size. The + * actual target LSN is already known to be in-bounds (reveal() + * checked that before calling us at all), so this can only ever + * reject a bogus reference, never the real target. */ + if (fd_lsn > tgt->max_valid_lsn) + return (0); + + if (fd_lsn == tgt->target_lsn) + { + describe_fd_offset(pathname, tgt->target_offset); + return (1); + } + + /* read_lsn() returns the number of bytes read on success, not an + * error_code -- 0 (or negative) means the read failed. */ + if (read_lsn(path, fd_lsn, &fd) <= 0) + return (0); + + is_dir = (fd.fd_att & FAP_DIR) ? 1 : 0; + + /* does the target LSN fall inside one of this FD's segements? */ + for (i = 0; i < NUM_SEGS; i++) + { + unsigned int seg_lsn = int3(fd.fd_seg[i].lsn); + unsigned int seg_num = int2(fd.fd_seg[i].num); + + if (seg_num == 0) + break; + + if (tgt->target_lsn >= seg_lsn && + tgt->target_lsn < seg_lsn + seg_num) + { + unsigned int byteOffset = prevBytes + + (tgt->target_lsn - seg_lsn) * path->bps + + tgt->target_offset; + + if (is_dir) + { + unsigned int entriesPerSector = + path->bps / sizeof(os9_dir_entry); + unsigned int entryIndex = + byteOffset / sizeof(os9_dir_entry); + unsigned int entryByte = + byteOffset % sizeof(os9_dir_entry); + unsigned int localIndex; + os9_dir_entry *entbuf; + u_char *entname = NULL; + + /* A corrupted bps smaller than one directory + * entry would make entriesPerSector 0 and the + * modulo below a crash; bail cleanly instead. */ + if (entriesPerSector == 0) + { + printf("This is byte %s of the %s segment of directory file \"%s\", but this disk's sector size (%u bytes) looks too small to hold a directory entry -- the image may be corrupt.\n", + format_num(byteOffset), ordinal(i + 1), + pathname, path->bps); + return (1); + } + + localIndex = entryIndex % entriesPerSector; + + /* entbuf is sized to path->bps rather than a + * fixed array -- bps comes from this image's + * own (possibly corrupt) LSN0, and a fixed + * stack buffer sized for the common 256-byte + * sector would overflow on a bad/garbage bps + * larger than that. */ + entbuf = malloc(path->bps); + + if (entbuf != NULL) + { + /* read_lsn() returns bytes read, not + * an error_code -- > 0 means success. */ + if (read_lsn(path, tgt->target_lsn, entbuf) > 0 && + entbuf[localIndex].name[0] != 0) + entname = decode_entry_name(&entbuf[localIndex]); + } + + if (entryByte < D_NAMELEN) + printf("This is byte %s of the %s segment of directory file \"%s\" -- it lands on the %s directory entry%s%s%s, the %s character of that entry's filename.\n", + format_num(byteOffset), ordinal(i + 1), pathname, + ordinal(entryIndex + 1), + entname ? " (\"" : "", + entname ? entname : (u_char *)"", + entname ? "\")" : "", + ordinal(entryByte + 1)); + else + printf("This is byte %s of the %s segment of directory file \"%s\" -- it lands on the %s directory entry%s%s%s, byte %u of the LSN pointer for that entry.\n", + format_num(byteOffset), ordinal(i + 1), pathname, + ordinal(entryIndex + 1), + entname ? " (\"" : "", + entname ? entname : (u_char *)"", + entname ? "\")" : "", + entryByte - D_NAMELEN + 1); + + if (entname != NULL) + free(entname); + if (entbuf != NULL) + free(entbuf); + } + else + { + printf("This is byte %s of file \"%s\" (the %s byte overall), found in the %s segment of this file, which begins at LSN %s.\n", + format_num(byteOffset), pathname, + ordinal(byteOffset + 1), + ordinal(i + 1), format_num(seg_lsn)); + } + + return (1); + } + + prevBytes += seg_num * path->bps; + } + + /* not in this FD's own sectors -- if it's a directory, recurse. + * A directory's last allocated sector is often only partially + * used -- OS-9 allocates directories in whole clusters, and the + * unused tail of that final sector is just leftover disk content + * (frequently stale bytes from whatever file previously owned + * that sector), not zeroed. So we bound the scan by fd_siz -- the + * directory's actual byte length -- rather than reading every + * entry slot in every allocated sector; otherwise we walk off + * the end of the real entries into that stale tail and start + * "recursing" into garbage LSNs as if they were real children. */ + if (is_dir) + { + unsigned int fsize = int4(fd.fd_siz); + unsigned int totalEntries = fsize / sizeof(os9_dir_entry); + unsigned int entriesSeen = 0; + int nEntries = path->bps / sizeof(os9_dir_entry); + + /* entbuf is sized to path->bps rather than a fixed array -- + * bps comes from this image's own (possibly corrupt) LSN0, + * and a fixed stack buffer sized for the common 256-byte + * sector would overflow on a bad/garbage bps larger than + * that. Allocated once and reused for every sector read. */ + os9_dir_entry *entbuf = (nEntries > 0) ? malloc(path->bps) : NULL; + + if (entbuf == NULL) + return (0); + + for (i = 0; i < NUM_SEGS && entriesSeen < totalEntries; i++) + { + unsigned int seg_lsn = int3(fd.fd_seg[i].lsn); + unsigned int seg_num = int2(fd.fd_seg[i].num); + unsigned int s; + + if (seg_num == 0) + break; + + for (s = 0; s < seg_num && entriesSeen < totalEntries; s++) + { + int e; + + /* read_lsn() returns bytes read, not an + * error_code -- <= 0 means the read failed. */ + if (read_lsn(path, seg_lsn + s, entbuf) <= 0) + { + /* can't verify this sector's entries; + * skip it but keep the entry count in + * sync so we don't overrun elsewhere */ + entriesSeen += nEntries; + continue; + } + + for (e = 0; e < nEntries && entriesSeen < totalEntries; + e++, entriesSeen++) + { + char *childpath; + size_t pathlen; + u_char *childname; + unsigned int childlsn; + int found; + + if (entbuf[e].name[0] == 0) + continue; + + childname = decode_entry_name(&entbuf[e]); + childlsn = int3(entbuf[e].lsn); + + if (strcmp((char *) childname, ".") == 0 || + strcmp((char *) childname, "..") == 0) + { + free(childname); + continue; + } + + /* pathname + '/' + childname + '\0' -- + * sized exactly, no fixed path limit */ + pathlen = strlen(pathname) + + strlen((char *) childname) + 2; + childpath = malloc(pathlen); + + if (childpath == NULL) + { + free(childname); + continue; + } + + if (strcmp(pathname, "/") == 0) + snprintf(childpath, pathlen, + "/%s", childname); + else + snprintf(childpath, pathlen, + "%s/%s", pathname, childname); + + free(childname); + + /* skip a clearly bogus child reference + * (out past both the physical file and + * the declared format size) rather than + * wasting a read attempt on it */ + if (childlsn > tgt->max_valid_lsn) + { + free(childpath); + continue; + } + + found = reveal_examine_fd(path, childlsn, + childpath, tgt, + depth + 1); + free(childpath); + + if (found) + { + free(entbuf); + return (1); + } + } + } + } + + free(entbuf); + } + + return (0); +} + + +/* ------------------------------------------------------------------ + * top-level dispatch + * ------------------------------------------------------------------ */ + +static void reveal(os9_path_id path, reveal_target *tgt) +{ + lsn0_sect *l0 = path->lsn0; + unsigned int bitmapSectors; + unsigned int rootDirLsn; + unsigned int logicalTotalSectors; + unsigned int physSectors = 0; + unsigned int bound; + long imageBytes; + unsigned long targetByteOffset; + + printf("\nExamining LSN %s", format_num(tgt->target_lsn)); + if (tgt->target_offset != 0) + printf(", byte %s", format_num(tgt->target_offset)); + printf(" of '%s'...\n\n", path->imgfile); + + logicalTotalSectors = int3(l0->dd_tot); + imageBytes = get_image_byte_size(path); + targetByteOffset = (unsigned long) tgt->target_lsn * path->bps + + tgt->target_offset; + + /* logical end of the formatted filesystem -- LSN0 itself says + * this disk doesn't contain this LSN at all (dd_tot). This is + * the authoritative bound regardless of the container file's + * physical size. */ + if (logicalTotalSectors > 0 && tgt->target_lsn >= logicalTotalSectors) + { + printf("LSN %s is past the logical end of the formatted filesystem -- LSN0 declares only %s total sectors (dd_tot). This is most likely trailing padding, space past a smaller format than the container file, or a corrupt dd_tot value.\n", + format_num(tgt->target_lsn), format_num(logicalTotalSectors)); + return; + } + + /* physical end of the container file. A host image is allowed + * to be shorter than the sectors LSN0 declares -- that's normal + * for a dynamically-growing/sparse image, and just means those + * trailing sectors haven't been referred to (allocated) yet. It + * only becomes a real problem if the bitmap disagrees -- i.e. + * something in this filesystem claims a sector that the file + * doesn't actually contain. */ + if (imageBytes >= 0 && targetByteOffset >= (unsigned long) imageBytes) + { + int allocated = _os9_ckbit(path->bitmap, tgt->target_lsn); + + printf("LSN %s is past the physical end of the image file '%s' (which is %s bytes / %s sectors long).", + format_num(tgt->target_lsn), path->imgfile, + format_num((unsigned long) imageBytes), + format_num((unsigned long) (path->bps ? + (unsigned long) imageBytes / path->bps : 0))); + + if (allocated) + printf(" The bitmap marks it ALLOCATED, though -- something in this filesystem references a sector the container file doesn't actually contain. That's inconsistent, and likely means the image is truncated or corrupt.\n"); + else + printf(" The bitmap marks it UNALLOCATED, so this is simply space that hasn't been grown into yet -- expected for a dynamically-growing image, not a problem.\n"); + + return; + } + + /* Bound used deeper in the walk (reveal_examine_fd) to skip + * obviously-corrupt LSNs -- e.g. a directory entry or segment + * pointing somewhere that can't exist -- without wasting a read + * attempt on them. 0xFFFFFFFF means "no bound known". */ + if (imageBytes >= 0 && path->bps > 0) + physSectors = (unsigned int) ((unsigned long) imageBytes / path->bps); + + bound = 0xFFFFFFFFu; + if (physSectors > 0 && physSectors < bound) + bound = physSectors; + if (logicalTotalSectors > 0 && logicalTotalSectors < bound) + bound = logicalTotalSectors; + tgt->max_valid_lsn = (bound == 0xFFFFFFFFu) ? bound : bound - 1; + + if (tgt->target_lsn == 0) + { + describe_lsn0_offset(l0, tgt->target_offset); + return; + } + + bitmapSectors = (path->bitmap_bytes + path->bps - 1) / path->bps; + + if (tgt->target_lsn >= 1 && tgt->target_lsn <= bitmapSectors) + { + describe_bitmap_offset(path, tgt); + return; + } + + rootDirLsn = int3(l0->dd_dir); + + if (!reveal_examine_fd(path, rootDirLsn, "/", tgt, 0)) + { + int allocated = _os9_ckbit(path->bitmap, tgt->target_lsn); + + printf("LSN %s doesn't currently map to any known filesystem structure (LSN0, the bitmap, or a live file/directory).\n", + format_num(tgt->target_lsn)); + + if (allocated) + { + int startlsn = 0; + error_code btec = get_boottrack_lsn(*l0, tgt->hwtype, &startlsn, 0); + int in_boot_track = 0; + + if (btec == 0) + { + /* Boot track is exactly one track long, + * starting at startlsn -- same pd_sct fetch + * get_boottrack_lsn() itself uses. */ + int is_osk = (memcmp(l0->dd_sync, "Cruz", 4) == 0); + u_char *pd_sct = is_osk ? l0->dd_opt.m68k.pd_sct + : l0->dd_opt.m6809.pd_sct; + unsigned int spt = int2(pd_sct); + + in_boot_track = (spt > 0 && + tgt->target_lsn >= (unsigned int) startlsn && + tgt->target_lsn < (unsigned int) startlsn + spt); + } + + if (in_boot_track) + printf("The bitmap marks it as ALLOCATED, though -- it falls within the boot track (starting at LSN %s), so it likely belongs to boot code rather than a file.\n", + format_num((unsigned int) startlsn)); + else + printf("The bitmap marks it as ALLOCATED, though -- it likely belongs to a deleted file, a reserved area, or something this walk didn't reach.\n"); + } + else + printf("The bitmap marks it as UNALLOCATED -- it's free space.\n"); + } +} + + +/* ------------------------------------------------------------------ + * argument parsing / entry point + * ------------------------------------------------------------------ */ + +static void parse_target(const char *arg, int byte_mode, os9_path_id path, + reveal_target *tgt) +{ + if (byte_mode) + { + unsigned long byteoff = strtoul(arg, NULL, 0); + + tgt->target_lsn = (unsigned int) (byteoff / path->bps); + tgt->target_offset = (unsigned int) (byteoff % path->bps); + } + else + { + char *copy = strdup(arg); + char *colon = strchr(copy, ':'); + + if (colon != NULL) + { + *colon = '\0'; + tgt->target_lsn = (unsigned int) strtoul(copy, NULL, 0); + tgt->target_offset = (unsigned int) strtoul(colon + 1, NULL, 0); + } + else + { + tgt->target_lsn = (unsigned int) strtoul(copy, NULL, 0); + tgt->target_offset = 0; + } + + free(copy); + } +} + + +int os9reveal(int argc, char *argv[]) +{ + error_code ec = 0; + int i; + int byte_mode = 0; + char *imgpath = NULL; + char *targetspec = NULL; + os9_path_id path; + reveal_target tgt; + struct personality *hwtype = &coco; + + for (i = 1; i < argc; i++) + { + if (argv[i][0] == '-') + { + char *p; + + for (p = &argv[i][1]; *p != '\0'; p++) + { + switch (*p) + { + case '?': + case 'h': + show_help(helpMessage); + return (0); + + case 'b': + byte_mode = 1; + break; + + case 'd': + hwtype = &dragon; + break; + + default: + fprintf(stderr, + "%s: unknown option '%c'\n", + argv[0], *p); + return (0); + } + } + } + else if (imgpath == NULL) + { + imgpath = argv[i]; + } + else if (targetspec == NULL) + { + targetspec = argv[i]; + } + } + + if (imgpath == NULL || targetspec == NULL) + { + show_help(helpMessage); + return (0); + } + + strcpy(os9pathlist, imgpath); + strcat(os9pathlist, ",@"); + + ec = _os9_open(&path, os9pathlist, FAM_READ); + if (ec != 0) + { + fprintf(stderr, "%s: error %d opening '%s'\n", + argv[0], ec, os9pathlist); + return (ec); + } + + /* A corrupted LSN0 could yield a bytes-per-sector of 0, which + * would otherwise crash later on division/modulo (bitmap sector + * count, -b byte-offset math). Bail out cleanly instead. */ + if (path->bps == 0) + { + fprintf(stderr, + "%s: '%s' reports 0 bytes per sector -- LSN0 looks\n" + "corrupt or unreadable, refusing to proceed.\n", + argv[0], os9pathlist); + _os9_close(path); + return (-1); + } + + /* Likewise a missing LSN0/bitmap (e.g. _os9_open succeeded on a + * badly damaged image without fully populating these) would + * crash reveal() on the first dereference. */ + if (path->lsn0 == NULL || path->bitmap == NULL) + { + fprintf(stderr, + "%s: '%s' didn't yield a usable LSN0/bitmap --\n" + "refusing to proceed on a possibly corrupt image.\n", + argv[0], os9pathlist); + _os9_close(path); + return (-1); + } + + parse_target(targetspec, byte_mode, path, &tgt); + tgt.hwtype = hwtype; + + reveal(path, &tgt); + + _os9_close(path); + + return (0); +}