From 288f35a654d19ecec9384ee6ce9fcf78f055a7a6 Mon Sep 17 00:00:00 2001 From: tim lindner Date: Fri, 24 Jul 2026 16:04:43 -0700 Subject: [PATCH 1/7] initial commit for the new command --- build/unix/os9/Makefile | 2 +- include/util.h | 1 + os9/os9_main.c | 1 + os9/os9reveal.c | 601 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 604 insertions(+), 1 deletion(-) create mode 100644 os9/os9reveal.c diff --git a/build/unix/os9/Makefile b/build/unix/os9/Makefile index 31aff15b..442b3b39 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/include/util.h b/include/util.h index 71d824d1..86973e4d 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 1a1ca0d6..67118e9c 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/os9reveal.c b/os9/os9reveal.c new file mode 100644 index 00000000..d2a7f18f --- /dev/null +++ b/os9/os9reveal.c @@ -0,0 +1,601 @@ +/******************************************************************** + * 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/extent entry, a directory entry (and which byte of + * its name or LSN pointer), or a byte inside a file's data. + * + * Assumptions carried over from the rest of the os9 library: + * - os9_path_id already carries bps/spc/bitmap_bytes/cs and a + * parsed copy of LSN0 (path->lsn0) once _os9_open() succeeds. + * - read_lsn(path, lsn, buffer) reads exactly path->bps bytes of + * a single raw LSN into buffer, regardless of interpretation. + * - fd_stats/os9_dir_entry/lsn0_sect map byte-for-byte onto their + * on-disk sector layouts (as os9id.c already relies on). + * + * $Id$ + ********************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* ------------------------------------------------------------------ + * types + * ------------------------------------------------------------------ */ + +typedef struct +{ + unsigned int target_lsn; /* LSN being asked about */ + unsigned int target_offset; /* byte offset within that 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 -- a\n", + " piece of LSN0, a bitmap byte and the LSNs it tracks, a file\n", + " descriptor field, a directory entry, or file data -- by\n", + " name and structure, not just a raw hex dump.\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", + NULL +}; + + +/* ------------------------------------------------------------------ + * small helpers + * ------------------------------------------------------------------ */ + +static char ord_buf[16]; + +static const char *ordinal(unsigned int n) +{ + const char *suffix = "th"; + + 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(ord_buf, sizeof(ord_buf), "%u%s", n, suffix); + return (ord_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 %u of LSN0 -- specifically, this is the\n" + "%s character of the disk name (currently \"%s\").\n", + 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 %u of LSN0, the header sector of disk\n" + "\"%s\" -- within the %s field (byte %u of %lu of it).\n", + offset, diskname, f->label, + offset - (unsigned int) f->offset + 1, + (unsigned long) f->size); + return; + } + } + + printf("This is byte %u of LSN0, in an unnamed or reserved area.\n", + 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\n" + "\"%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 %u of the file descriptor for file\n" + "\"%s\", past its last possible segment/extent entry.\n", + offset, pathname); + return; + } + + if (fieldOff < 3) + printf("This is byte %u of the file descriptor for file\n" + "\"%s\" -- the LSN field of its %s segment (extent) entry.\n", + offset, pathname, ordinal(segIndex + 1)); + else + printf("This is byte %u of the file descriptor for file\n" + "\"%s\" -- the sector-count field of its %s segment\n" + "(extent) entry.\n", + 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 %u of the file descriptor for file\n" + "\"%s\", within its %s.\n", + offset, pathname, f->label); + return; + } + } + + printf("This is byte %u of the file descriptor for file \"%s\".\n", + offset, pathname); +} + + +/* ------------------------------------------------------------------ + * 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 %u of the allocation bitmap (LSN %u, %u bytes\n" + "into the bitmap area). Each bit marks one %u-sector cluster\n" + "as free or allocated, so this byte's 8 bits cover clusters\n" + "%u through %u -- that is, LSNs %u through %u.\n", + byteInBitmap, tgt->target_lsn, + (tgt->target_lsn - 1) * path->bps + tgt->target_offset, + path->spc, clusterStart, clusterStart + 7, lsnStart, lsnEnd); +} + + +/* ------------------------------------------------------------------ + * recursive directory-tree walk + * ------------------------------------------------------------------ */ + +static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, + const char *pathname, reveal_target *tgt) +{ + fd_stats fd; + int i; + unsigned int prevBytes = 0; + int is_dir; + + if (fd_lsn == tgt->target_lsn) + { + describe_fd_offset(pathname, tgt->target_offset); + return (1); + } + + 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 extents? */ + 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 = + entryIndex % entriesPerSector; + os9_dir_entry entbuf[64]; + u_char *entname = NULL; + + if (read_lsn(path, tgt->target_lsn, entbuf) == 0 && + entbuf[localIndex].name[0] != 0) + entname = decode_entry_name(&entbuf[localIndex]); + + printf("This is byte %u of the %s extent (segment)\n" + "of directory file \"%s\" --\n", + byteOffset, ordinal(i + 1), pathname); + + if (entryByte < D_NAMELEN) + printf("it lands on the %s directory entry%s%s%s,\n" + "the %s character of that entry's filename.\n", + ordinal(entryIndex + 1), + entname ? " (\"" : "", + entname ? entname : (u_char *)"", + entname ? "\")" : "", + ordinal(entryByte + 1)); + else + printf("it lands on the %s directory entry%s%s%s,\n" + "byte %u of the LSN pointer for that entry.\n", + ordinal(entryIndex + 1), + entname ? " (\"" : "", + entname ? entname : (u_char *)"", + entname ? "\")" : "", + entryByte - D_NAMELEN + 1); + } + else + { + printf("This is byte %u of file \"%s\" (the %s\n" + "byte overall), found in the %s segment/extent\n" + "of this file, which begins at LSN %u.\n", + byteOffset, pathname, + ordinal(byteOffset + 1), + ordinal(i + 1), seg_lsn); + } + + return (1); + } + + prevBytes += seg_num * path->bps; + } + + /* not in this FD's own sectors -- if it's a directory, recurse */ + if (is_dir) + { + unsigned int fsize = int4(fd.fd_siz); + unsigned int fileBytes = 0; + + 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); + unsigned int s; + + if (seg_num == 0) + break; + + for (s = 0; s < seg_num && fileBytes < fsize; s++) + { + os9_dir_entry entbuf[64]; + int nEntries = path->bps / sizeof(os9_dir_entry); + int e; + + if (read_lsn(path, seg_lsn + s, entbuf) != 0) + { + fileBytes += path->bps; + continue; + } + + for (e = 0; e < nEntries; e++) + { + char childpath[512]; + u_char *childname; + unsigned int childlsn; + + 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; + } + + if (strcmp(pathname, "/") == 0) + snprintf(childpath, sizeof(childpath), + "/%s", childname); + else + snprintf(childpath, sizeof(childpath), + "%s/%s", pathname, childname); + + free(childname); + + if (reveal_examine_fd(path, childlsn, + childpath, tgt)) + return (1); + } + + fileBytes += path->bps; + } + } + } + + 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; + + printf("\nExamining LSN %u", tgt->target_lsn); + if (tgt->target_offset != 0) + printf(", byte %u", tgt->target_offset); + printf(" of '%s'...\n\n", path->imgfile); + + 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)) + { + printf("LSN %u doesn't currently map to any known filesystem\n" + "structure (LSN0, the bitmap, or a live file/directory).\n" + "It's most likely unallocated free space, or it falls in a\n" + "reserved/system area of the disk.\n", + tgt->target_lsn); + } +} + + +/* ------------------------------------------------------------------ + * 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; + + 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; + + 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); + } + + parse_target(targetspec, byte_mode, path, &tgt); + + reveal(path, &tgt); + + _os9_close(path); + + return (0); +} From dba4697c4b8dcee2b71b26e137d38e0d94baa4cc Mon Sep 17 00:00:00 2001 From: tim lindner Date: Fri, 24 Jul 2026 21:25:14 -0700 Subject: [PATCH 2/7] working --- os9/os9reveal.c | 120 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 81 insertions(+), 39 deletions(-) diff --git a/os9/os9reveal.c b/os9/os9reveal.c index d2a7f18f..2127f1cf 100644 --- a/os9/os9reveal.c +++ b/os9/os9reveal.c @@ -10,14 +10,6 @@ * it, a segment/extent entry, a directory entry (and which byte of * its name or LSN pointer), or a byte inside a file's data. * - * Assumptions carried over from the rest of the os9 library: - * - os9_path_id already carries bps/spc/bitmap_bytes/cs and a - * parsed copy of LSN0 (path->lsn0) once _os9_open() succeeds. - * - read_lsn(path, lsn, buffer) reads exactly path->bps bytes of - * a single raw LSN into buffer, regardless of interpretation. - * - fd_stats/os9_dir_entry/lsn0_sect map byte-for-byte onto their - * on-disk sector layouts (as os9id.c already relies on). - * * $Id$ ********************************************************************/ #include @@ -60,10 +52,7 @@ 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 -- a\n", - " piece of LSN0, a bitmap byte and the LSNs it tracks, a file\n", - " descriptor field, a directory entry, or file data -- by\n", - " name and structure, not just a raw hex dump.\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", @@ -75,11 +64,23 @@ static char const *const helpMessage[] = { * small helpers * ------------------------------------------------------------------ */ -static char ord_buf[16]; +/* Several messages below call ordinal() more than once in a single + * printf() -- e.g. "%s byte ... %s segment". A single shared static + * buffer would get overwritten by the second call before printf() + * ever reads the first (argument evaluation order is unspecified in + * C), silently corrupting or blanking out one of the two. Rotate + * through a small pool of buffers instead so each call in the same + * statement gets its own. */ +#define ORD_NUM_BUFS 4 +static char ord_bufs[ORD_NUM_BUFS][16]; +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) { @@ -100,8 +101,8 @@ static const char *ordinal(unsigned int n) } } - snprintf(ord_buf, sizeof(ord_buf), "%u%s", n, suffix); - return (ord_buf); + snprintf(buf, 16, "%u%s", n, suffix); + return (buf); } @@ -230,19 +231,19 @@ static void describe_fd_offset(const char *pathname, unsigned int offset) if (segIndex >= NUM_SEGS) { printf("This is byte %u of the file descriptor for file\n" - "\"%s\", past its last possible segment/extent entry.\n", + "\"%s\", past its last possible segment entry.\n", offset, pathname); return; } if (fieldOff < 3) printf("This is byte %u of the file descriptor for file\n" - "\"%s\" -- the LSN field of its %s segment (extent) entry.\n", + "\"%s\" -- the LSN field of its %s segment entry.\n", offset, pathname, ordinal(segIndex + 1)); else printf("This is byte %u of the file descriptor for file\n" "\"%s\" -- the sector-count field of its %s segment\n" - "(extent) entry.\n", + "entry.\n", offset, pathname, ordinal(segIndex + 1)); return; } @@ -305,7 +306,9 @@ static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, return (1); } - if (read_lsn(path, fd_lsn, &fd) != 0) + /* 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; @@ -339,7 +342,9 @@ static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, os9_dir_entry entbuf[64]; u_char *entname = NULL; - if (read_lsn(path, tgt->target_lsn, entbuf) == 0 && + /* 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]); @@ -380,13 +385,23 @@ static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, prevBytes += seg_num * path->bps; } - /* not in this FD's own sectors -- if it's a directory, recurse */ + /* 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 fileBytes = 0; + unsigned int totalEntries = fsize / sizeof(os9_dir_entry); + unsigned int entriesSeen = 0; - for (i = 0; i < NUM_SEGS; i++) + 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); @@ -395,23 +410,31 @@ static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, if (seg_num == 0) break; - for (s = 0; s < seg_num && fileBytes < fsize; s++) + for (s = 0; s < seg_num && entriesSeen < totalEntries; s++) { os9_dir_entry entbuf[64]; int nEntries = path->bps / sizeof(os9_dir_entry); int e; - if (read_lsn(path, seg_lsn + s, entbuf) != 0) + /* read_lsn() returns bytes read, not an + * error_code -- <= 0 means the read failed. */ + if (read_lsn(path, seg_lsn + s, entbuf) <= 0) { - fileBytes += path->bps; + /* 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; e++) + for (e = 0; e < nEntries && entriesSeen < totalEntries; + e++, entriesSeen++) { - char childpath[512]; + char *childpath; + size_t pathlen; u_char *childname; unsigned int childlsn; + int found; if (entbuf[e].name[0] == 0) continue; @@ -419,28 +442,41 @@ static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, childname = decode_entry_name(&entbuf[e]); childlsn = int3(entbuf[e].lsn); - if (strcmp((char *)childname, ".") == 0 || - strcmp((char *)childname, "..") == 0) + 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, sizeof(childpath), + snprintf(childpath, pathlen, "/%s", childname); else - snprintf(childpath, sizeof(childpath), + snprintf(childpath, pathlen, "%s/%s", pathname, childname); free(childname); - if (reveal_examine_fd(path, childlsn, - childpath, tgt)) + found = reveal_examine_fd(path, childlsn, + childpath, tgt); + free(childpath); + + if (found) return (1); } - - fileBytes += path->bps; } } } @@ -482,11 +518,17 @@ static void reveal(os9_path_id path, reveal_target *tgt) if (!reveal_examine_fd(path, rootDirLsn, "/", tgt)) { + int allocated = _os9_ckbit(path->bitmap, tgt->target_lsn); + printf("LSN %u doesn't currently map to any known filesystem\n" - "structure (LSN0, the bitmap, or a live file/directory).\n" - "It's most likely unallocated free space, or it falls in a\n" - "reserved/system area of the disk.\n", + "structure (LSN0, the bitmap, or a live file/directory).\n", tgt->target_lsn); + + if (allocated) + printf("The bitmap marks it as ALLOCATED, though -- it likely\n" + "belongs to boottrack.\n"); + else + printf("The bitmap marks it as UNALLOCATED -- it's free space.\n"); } } From b382ca7a102d0150d3b7d266fcd68d059eacb41e Mon Sep 17 00:00:00 2001 From: tim lindner Date: Sat, 25 Jul 2026 06:52:10 -0700 Subject: [PATCH 3/7] oneline output with commas --- os9/os9reveal.c | 132 ++++++++++++++++++++++++------------------------ 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/os9/os9reveal.c b/os9/os9reveal.c index 2127f1cf..ea52315a 100644 --- a/os9/os9reveal.c +++ b/os9/os9reveal.c @@ -7,7 +7,7 @@ * 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/extent entry, a directory entry (and which byte 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$ @@ -64,15 +64,35 @@ static char const *const helpMessage[] = { * small helpers * ------------------------------------------------------------------ */ -/* Several messages below call ordinal() more than once in a single - * printf() -- e.g. "%s byte ... %s segment". A single shared static - * buffer would get overwritten by the second call before printf() - * ever reads the first (argument evaluation order is unspecified in - * C), silently corrupting or blanking out one of the two. Rotate - * through a small pool of buffers instead so each call in the same - * statement gets its own. */ +#define NUM_NUM_BUFS 4 +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 4 -static char ord_bufs[ORD_NUM_BUFS][16]; +static char ord_bufs[ORD_NUM_BUFS][24]; static int ord_buf_idx = 0; static const char *ordinal(unsigned int n) @@ -101,14 +121,11 @@ static const char *ordinal(unsigned int n) } } - snprintf(buf, 16, "%u%s", n, suffix); + 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); @@ -168,9 +185,8 @@ static void describe_lsn0_offset(lsn0_sect *l0, unsigned int offset) { unsigned int charIndex = offset - (unsigned int) DD_NAM_OFFSET + 1; - printf("This is byte %u of LSN0 -- specifically, this is the\n" - "%s character of the disk name (currently \"%s\").\n", - offset, ordinal(charIndex), diskname); + 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; } @@ -180,17 +196,16 @@ static void describe_lsn0_offset(lsn0_sect *l0, unsigned int offset) if (offset >= f->offset && offset < f->offset + f->size) { - printf("This is byte %u of LSN0, the header sector of disk\n" - "\"%s\" -- within the %s field (byte %u of %lu of it).\n", - offset, diskname, f->label, + 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 %u of LSN0, in an unnamed or reserved area.\n", - offset); + printf("This is byte %s of LSN0, in an unnamed or reserved area.\n", + format_num(offset)); } @@ -217,8 +232,8 @@ static void describe_fd_offset(const char *pathname, unsigned int offset) if (offset == 0) { - printf("This is the start of the file descriptor for file\n" - "\"%s\".\n", pathname); + printf("This is the start of the file descriptor for file \"%s\".\n", + pathname); return; } @@ -230,21 +245,17 @@ static void describe_fd_offset(const char *pathname, unsigned int offset) if (segIndex >= NUM_SEGS) { - printf("This is byte %u of the file descriptor for file\n" - "\"%s\", past its last possible segment entry.\n", - offset, pathname); + 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 %u of the file descriptor for file\n" - "\"%s\" -- the LSN field of its %s segment entry.\n", - offset, pathname, ordinal(segIndex + 1)); + 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 %u of the file descriptor for file\n" - "\"%s\" -- the sector-count field of its %s segment\n" - "entry.\n", - offset, pathname, ordinal(segIndex + 1)); + 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; } @@ -254,15 +265,14 @@ static void describe_fd_offset(const char *pathname, unsigned int offset) if (offset >= f->offset && offset < f->offset + f->size) { - printf("This is byte %u of the file descriptor for file\n" - "\"%s\", within its %s.\n", - offset, pathname, f->label); + 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 %u of the file descriptor for file \"%s\".\n", - offset, pathname); + printf("This is byte %s of the file descriptor for file \"%s\".\n", + format_num(offset), pathname); } @@ -278,13 +288,11 @@ static void describe_bitmap_offset(os9_path_id path, reveal_target *tgt) unsigned int lsnStart = clusterStart * path->spc; unsigned int lsnEnd = lsnStart + (8 * path->spc) - 1; - printf("This is byte %u of the allocation bitmap (LSN %u, %u bytes\n" - "into the bitmap area). Each bit marks one %u-sector cluster\n" - "as free or allocated, so this byte's 8 bits cover clusters\n" - "%u through %u -- that is, LSNs %u through %u.\n", - byteInBitmap, tgt->target_lsn, - (tgt->target_lsn - 1) * path->bps + tgt->target_offset, - path->spc, clusterStart, clusterStart + 7, lsnStart, lsnEnd); + 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)); } @@ -313,7 +321,7 @@ static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, is_dir = (fd.fd_att & FAP_DIR) ? 1 : 0; - /* does the target LSN fall inside one of this FD's extents? */ + /* 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); @@ -348,21 +356,17 @@ static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, entbuf[localIndex].name[0] != 0) entname = decode_entry_name(&entbuf[localIndex]); - printf("This is byte %u of the %s extent (segment)\n" - "of directory file \"%s\" --\n", - byteOffset, ordinal(i + 1), pathname); - if (entryByte < D_NAMELEN) - printf("it lands on the %s directory entry%s%s%s,\n" - "the %s character of that entry's filename.\n", + 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("it lands on the %s directory entry%s%s%s,\n" - "byte %u of the LSN pointer for that entry.\n", + 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 *)"", @@ -371,12 +375,10 @@ static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, } else { - printf("This is byte %u of file \"%s\" (the %s\n" - "byte overall), found in the %s segment/extent\n" - "of this file, which begins at LSN %u.\n", - byteOffset, pathname, + 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), seg_lsn); + ordinal(i + 1), format_num(seg_lsn)); } return (1); @@ -495,9 +497,9 @@ static void reveal(os9_path_id path, reveal_target *tgt) unsigned int bitmapSectors; unsigned int rootDirLsn; - printf("\nExamining LSN %u", tgt->target_lsn); + printf("\nExamining LSN %s", format_num(tgt->target_lsn)); if (tgt->target_offset != 0) - printf(", byte %u", tgt->target_offset); + printf(", byte %s", format_num(tgt->target_offset)); printf(" of '%s'...\n\n", path->imgfile); if (tgt->target_lsn == 0) @@ -520,13 +522,11 @@ static void reveal(os9_path_id path, reveal_target *tgt) { int allocated = _os9_ckbit(path->bitmap, tgt->target_lsn); - printf("LSN %u doesn't currently map to any known filesystem\n" - "structure (LSN0, the bitmap, or a live file/directory).\n", - 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) - printf("The bitmap marks it as ALLOCATED, though -- it likely\n" - "belongs to boottrack.\n"); + printf("The bitmap marks it as ALLOCATED, though -- it likely belongs to boottrack.\n"); else printf("The bitmap marks it as UNALLOCATED -- it's free space.\n"); } From 2a9ca6650351c00fab14f36e8ea643a0687f733b Mon Sep 17 00:00:00 2001 From: tim lindner Date: Sat, 25 Jul 2026 21:40:40 -0700 Subject: [PATCH 4/7] report boot track correctly. More error circumvention. --- os9/os9reveal.c | 338 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 323 insertions(+), 15 deletions(-) diff --git a/os9/os9reveal.c b/os9/os9reveal.c index ea52315a..beaa617b 100644 --- a/os9/os9reveal.c +++ b/os9/os9reveal.c @@ -13,6 +13,7 @@ * $Id$ ********************************************************************/ #include +#include #include #include #include @@ -28,10 +29,16 @@ * 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 @@ -56,14 +63,31 @@ static char const *const helpMessage[] = { "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 4 static char num_bufs[NUM_NUM_BUFS][32]; static int num_buf_idx = 0; @@ -90,7 +114,6 @@ static const char *format_num(unsigned long n) return (buf); } - #define ORD_NUM_BUFS 4 static char ord_bufs[ORD_NUM_BUFS][24]; static int ord_buf_idx = 0; @@ -126,6 +149,9 @@ static const char *ordinal(unsigned int n) } +/* 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); @@ -276,6 +302,91 @@ static void describe_fd_offset(const char *pathname, unsigned int offset) } +/* ------------------------------------------------------------------ + * 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()). + * ------------------------------------------------------------------ */ + +struct personality +{ + int startlsn; +}; + +static struct personality coco = { 18 * 34 }; +static struct personality dragon = { 2 }; + +/* No CLI override for this yet (os9gen.c has one; reveal doesn't + * expose an equivalent option), so it's always "unset". */ +static int specialStartLSN = 0; + +error_code get_boottrack_lsn(lsn0_sect LSN0, struct personality *hwtype, int *startlsn) +{ + int is_osk; + u_char *pd_sct, *pd_cyl, *pd_sid, *pd_typ; + is_osk = (memcmp(LSN0.dd_sync, "Cruz", 4) == 0); + if (is_osk != 0) + { + pd_sct = LSN0.dd_opt.m68k.pd_sct; + pd_cyl = LSN0.dd_opt.m68k.pd_cyl; + pd_sid = LSN0.dd_opt.m68k.pd_sid; + pd_typ = LSN0.dd_opt.m68k.pd_typ; + } + else + { + pd_sct = LSN0.dd_opt.m6809.pd_sct; + pd_cyl = LSN0.dd_opt.m6809.pd_cyl; + pd_sid = LSN0.dd_opt.m6809.pd_sid; + pd_typ = LSN0.dd_opt.m6809.pd_typ; + } + *startlsn = hwtype->startlsn; + if (*startlsn == 2) + { + /* Check to make sure the disk image has minimum of 18 sectors per track */ + if (int2(pd_sct) < 18) + { + return (1); + } + } + else + { + /* If special startLSN for boottrack is set then set startlsn to */ + /* the value stored in specialStartLSN */ + if (specialStartLSN > 0) + { + *startlsn = specialStartLSN; + } + else + { + /* Check to see if disk image is a HDD image if so set for default */ + /* startLSN of 612 for the boottrack for use with CoCoSDC and DriveWire HDD images */ + if (int1(pd_typ) == 0x80) + { + *startlsn = 612; + } + else + { + /* Check to make sure the disk image has minimum of 18 sectors per track */ + if (int2(pd_sct) < 18) + { + return (1); + } + /* Check to make sure the disk image has minimum of 35 tracks */ + if (int2(pd_cyl) < 35) + { + return (1); + } + /* Use real floppy disk geometry to figure out real startLSN for boottrack */ + *startlsn = 34 * int2(pd_sct) * int1(pd_sid); + } + } + } + + return 0; +} + + /* ------------------------------------------------------------------ * bitmap * ------------------------------------------------------------------ */ @@ -301,13 +412,40 @@ static void describe_bitmap_offset(os9_path_id path, reveal_target *tgt) * ------------------------------------------------------------------ */ static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, - const char *pathname, reveal_target *tgt) + 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); @@ -345,16 +483,39 @@ static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, byteOffset / sizeof(os9_dir_entry); unsigned int entryByte = byteOffset % sizeof(os9_dir_entry); - unsigned int localIndex = - entryIndex % entriesPerSector; - os9_dir_entry entbuf[64]; + unsigned int localIndex; + os9_dir_entry *entbuf; u_char *entname = 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]); + /* 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", @@ -372,6 +533,11 @@ static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, entname ? entname : (u_char *)"", entname ? "\")" : "", entryByte - D_NAMELEN + 1); + + if (entname != NULL) + free(entname); + if (entbuf != NULL) + free(entbuf); } else { @@ -402,6 +568,17 @@ static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, 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++) { @@ -414,8 +591,6 @@ static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, for (s = 0; s < seg_num && entriesSeen < totalEntries; s++) { - os9_dir_entry entbuf[64]; - int nEntries = path->bps / sizeof(os9_dir_entry); int e; /* read_lsn() returns bytes read, not an @@ -472,15 +647,31 @@ static int reveal_examine_fd(os9_path_id path, unsigned int fd_lsn, 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); + childpath, tgt, + depth + 1); free(childpath); if (found) + { + free(entbuf); return (1); + } } } } + + free(entbuf); } return (0); @@ -496,12 +687,72 @@ 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); @@ -518,7 +769,7 @@ static void reveal(os9_path_id path, reveal_target *tgt) rootDirLsn = int3(l0->dd_dir); - if (!reveal_examine_fd(path, rootDirLsn, "/", tgt)) + if (!reveal_examine_fd(path, rootDirLsn, "/", tgt, 0)) { int allocated = _os9_ckbit(path->bitmap, tgt->target_lsn); @@ -526,7 +777,32 @@ static void reveal(os9_path_id path, reveal_target *tgt) format_num(tgt->target_lsn)); if (allocated) - printf("The bitmap marks it as ALLOCATED, though -- it likely belongs to boottrack.\n"); + { + int startlsn = 0; + error_code btec = get_boottrack_lsn(*l0, tgt->hwtype, &startlsn); + 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"); } @@ -578,6 +854,7 @@ int os9reveal(int argc, char *argv[]) char *targetspec = NULL; os9_path_id path; reveal_target tgt; + struct personality *hwtype = &coco; for (i = 1; i < argc; i++) { @@ -598,6 +875,10 @@ int os9reveal(int argc, char *argv[]) byte_mode = 1; break; + case 'd': + hwtype = &dragon; + break; + default: fprintf(stderr, "%s: unknown option '%c'\n", @@ -633,7 +914,34 @@ int os9reveal(int argc, char *argv[]) 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); From 165e57397430ea30d0dcc4cc64ccece092f08904 Mon Sep 17 00:00:00 2001 From: tim lindner Date: Sat, 25 Jul 2026 21:52:30 -0700 Subject: [PATCH 5/7] add documentation --- doc/ToolShed.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/doc/ToolShed.md b/doc/ToolShed.md index 67125bee..af84ab44 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. From ee4ad8aba39a03682059930d292aa0b4af45fb7c Mon Sep 17 00:00:00 2001 From: tim lindner Date: Tue, 28 Jul 2026 08:58:13 -0700 Subject: [PATCH 6/7] rebased. removed duplicate function --- include/rbfutil.h | 9 +++++- os9/os9gen.c | 38 ++++++++++++---------- os9/os9reveal.c | 80 ++--------------------------------------------- 3 files changed, 33 insertions(+), 94 deletions(-) diff --git a/include/rbfutil.h b/include/rbfutil.h index 44856714..74f5cea0 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/os9/os9gen.c b/os9/os9gen.c index ff4bc111..bc060b80 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 index beaa617b..3c85e091 100644 --- a/os9/os9reveal.c +++ b/os9/os9reveal.c @@ -23,6 +23,7 @@ #include #include #include +#include /* ------------------------------------------------------------------ @@ -31,7 +32,7 @@ /* full definition is further down, near get_boottrack_lsn(); a * pointer is all reveal_target needs to carry it that far */ -struct personality; +// struct personality; typedef struct { @@ -309,84 +310,9 @@ static void describe_fd_offset(const char *pathname, unsigned int offset) * option and the ALLOCATED-but-unmapped-LSN case in reveal()). * ------------------------------------------------------------------ */ -struct personality -{ - int startlsn; -}; - static struct personality coco = { 18 * 34 }; static struct personality dragon = { 2 }; -/* No CLI override for this yet (os9gen.c has one; reveal doesn't - * expose an equivalent option), so it's always "unset". */ -static int specialStartLSN = 0; - -error_code get_boottrack_lsn(lsn0_sect LSN0, struct personality *hwtype, int *startlsn) -{ - int is_osk; - u_char *pd_sct, *pd_cyl, *pd_sid, *pd_typ; - is_osk = (memcmp(LSN0.dd_sync, "Cruz", 4) == 0); - if (is_osk != 0) - { - pd_sct = LSN0.dd_opt.m68k.pd_sct; - pd_cyl = LSN0.dd_opt.m68k.pd_cyl; - pd_sid = LSN0.dd_opt.m68k.pd_sid; - pd_typ = LSN0.dd_opt.m68k.pd_typ; - } - else - { - pd_sct = LSN0.dd_opt.m6809.pd_sct; - pd_cyl = LSN0.dd_opt.m6809.pd_cyl; - pd_sid = LSN0.dd_opt.m6809.pd_sid; - pd_typ = LSN0.dd_opt.m6809.pd_typ; - } - *startlsn = hwtype->startlsn; - if (*startlsn == 2) - { - /* Check to make sure the disk image has minimum of 18 sectors per track */ - if (int2(pd_sct) < 18) - { - return (1); - } - } - else - { - /* If special startLSN for boottrack is set then set startlsn to */ - /* the value stored in specialStartLSN */ - if (specialStartLSN > 0) - { - *startlsn = specialStartLSN; - } - else - { - /* Check to see if disk image is a HDD image if so set for default */ - /* startLSN of 612 for the boottrack for use with CoCoSDC and DriveWire HDD images */ - if (int1(pd_typ) == 0x80) - { - *startlsn = 612; - } - else - { - /* Check to make sure the disk image has minimum of 18 sectors per track */ - if (int2(pd_sct) < 18) - { - return (1); - } - /* Check to make sure the disk image has minimum of 35 tracks */ - if (int2(pd_cyl) < 35) - { - return (1); - } - /* Use real floppy disk geometry to figure out real startLSN for boottrack */ - *startlsn = 34 * int2(pd_sct) * int1(pd_sid); - } - } - } - - return 0; -} - - /* ------------------------------------------------------------------ * bitmap * ------------------------------------------------------------------ */ @@ -779,7 +705,7 @@ static void reveal(os9_path_id path, reveal_target *tgt) if (allocated) { int startlsn = 0; - error_code btec = get_boottrack_lsn(*l0, tgt->hwtype, &startlsn); + error_code btec = get_boottrack_lsn(*l0, tgt->hwtype, &startlsn, 0); int in_boot_track = 0; if (btec == 0) From a41e0a46cff34b350ff06c582f38f56e61932ddf Mon Sep 17 00:00:00 2001 From: tim lindner Date: Tue, 28 Jul 2026 10:45:53 -0700 Subject: [PATCH 7/7] needed more entries in the circular buffer --- os9/os9reveal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/os9/os9reveal.c b/os9/os9reveal.c index 3c85e091..c0237ac0 100644 --- a/os9/os9reveal.c +++ b/os9/os9reveal.c @@ -89,7 +89,7 @@ static long get_image_byte_size(os9_path_id path) /* 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 4 +#define NUM_NUM_BUFS 8 static char num_bufs[NUM_NUM_BUFS][32]; static int num_buf_idx = 0; @@ -115,7 +115,7 @@ static const char *format_num(unsigned long n) return (buf); } -#define ORD_NUM_BUFS 4 +#define ORD_NUM_BUFS 8 static char ord_bufs[ORD_NUM_BUFS][24]; static int ord_buf_idx = 0;