Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ SRCDIR := src
OBJDIR := obj
SOURCES := main.c args.c output.c timer.c content.c ring.c ops.c buf.c \
engine.c stripe.c stats.c worker.c audit.c invariant.c quirks.c \
scsicmd.c removable.c soft64.c
scsicmd.c removable.c soft64.c dosdev.c
OBJECTS := $(SOURCES:%.c=$(OBJDIR)/%.o)
TARGET := devsoak

Expand Down
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,13 @@ dependency. If you change the toolchain flags, re-check with

```
devsoak DEVICE UNIT -d -r START,LEN [options]
devsoak NAME: -d [options] (partition mode; see below)

-d destructive (required)
-r START,LEN test range in sectors (required); K/M/G suffixes multiply
by 1024 (sector counts, not bytes); 0x hex accepted
by 1024 (sector counts, not bytes); 0x hex accepted.
Partition mode: optional, a sub-range relative to the
partition's own start instead of the whole test range.
-t DURATION e.g. 30s, 20m, 8h (default 60s)
-w N worker tasks (default 4, max 8)
-q N outstanding requests per worker (default 4, max 8)
Expand Down Expand Up @@ -80,6 +83,37 @@ devsoak refuses to run without both `-d` and `-r`, prints the device
geometry and the range, and asks for confirmation unless `-y` is given.
It never writes outside the range (the §8 bounds probes included).

### Partition mode

`devsoak DH1: -d [options]` — a single positional argument ending in `:`
is taken as a mounted DOS device name instead of `DEVICE UNIT`. devsoak
looks it up in the DosList, resolves it to the underlying exec
device/unit and OpenDevice flags, and turns the partition's own extent
(from its `DosEnvec`: cylinders, surfaces, blocks/track) into the test
range — printed as `DH1: = scsi.device unit 0, partition sectors
1004832..1209455`, same as the ordinary `-r` banner. `-r START,LEN` is
still accepted in this mode, but is reinterpreted as a sub-range
*relative to the partition's start* (for low-RAM machines that can't
soak the whole thing); devsoak still never writes outside that range, so
device-end probes stay read-only and can never spill past the partition
either.

Before any test traffic, devsoak sends `ACTION_INHIBIT` to the
partition's handler so the filesystem stops touching the device for the
run (uninhibited again on every exit path, including errors — a crash
is the one case that can't clean this up; `--resume` says so). If the
handler was never started there is nothing to inhibit and devsoak says
so and continues.

The destructive-run confirmation escalates by what's actually on the
partition: an empty or unrecognised partition gets the normal `y/N`
prompt; a recognised filesystem signature with no live volume mounted
adds a warning line ahead of the same prompt; a **live mounted volume**
requires typing the volume's name back (case-insensitive) instead of
`y` — and `-y` does *not* bypass that last tier, so a live volume
forces an interactive run: a misconfigured CI job must not be able to
destroy a filesystem someone still has open.

### Exit codes

| code | meaning |
Expand Down
43 changes: 31 additions & 12 deletions src/args.c
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,9 @@ LONG
args_parse(int argc, char **argv)
{
int i;
int optstart;
ULONG u;
size_t l;

memset(&cfg, 0, sizeof(cfg));
cfg.duration_s = 60;
Expand All @@ -212,19 +214,31 @@ args_parse(int argc, char **argv)
cfg.watchdog_s = 5;
cfg.outmode = OUT_CON;

if (argc < 3) {
seterr("usage: devsoak DEVICE UNIT -d -r START,LEN [options]", NULL);
return 1;
}

cfg.device = argv[1];
if (parse_ulong(argv[2], &u) != 0) {
seterr("bad UNIT: ", argv[2]);
return 1;
/* second form: `devsoak DH1: -d [options]` -- a single positional
* argument ending in ':' is a DOS device name; device/unit/range are
* resolved from the mount later (dosdev.c, main.c -- args_parse must
* never print, so no resolution happens here). */
l = (argc >= 2) ? strlen(argv[1]) : 0;
if (l > 1 && argv[1][l - 1] == ':') {
cfg.partition = 1;
cfg.dosdev = argv[1];
optstart = 2;
} else {
if (argc < 3) {
seterr("usage: devsoak DEVICE UNIT -d -r START,LEN [options], "
"or: devsoak NAME: -d [options]", NULL);
return 1;
}
cfg.device = argv[1];
if (parse_ulong(argv[2], &u) != 0) {
seterr("bad UNIT: ", argv[2]);
return 1;
}
cfg.unit = (LONG)u;
optstart = 3;
}
cfg.unit = (LONG)u;

for (i = 3; i < argc; i++) {
for (i = optstart; i < argc; i++) {
char *arg = argv[i];

if (strcmp(arg, "-d") == 0) {
Expand Down Expand Up @@ -349,7 +363,10 @@ args_parse(int argc, char **argv)
seterr("-d is required (devsoak is destructive by design)", NULL);
return 1;
}
if (!cfg.have_range) {
/* partition mode: -r is optional (a relative sub-range of the
* partition, resolved once the mount is looked up); otherwise
* it is the whole test range and is required as before. */
if (!cfg.partition && !cfg.have_range) {
seterr("-r START,LEN is required", NULL);
return 1;
}
Expand All @@ -362,9 +379,11 @@ void
args_usage(void)
{
out_printf("devsoak DEVICE UNIT -d -r START,LEN [options]");
out_printf("devsoak NAME: -d [options] (partition mode; see README)");
out_printf("");
out_printf(" -d destructive (required)");
out_printf(" -r START,LEN test range in sectors (required); LEN may use K/M/G suffix");
out_printf(" partition mode: optional, relative to the partition start");
out_printf(" -t DURATION e.g. 30s, 20m, 8h (default 60s)");
out_printf(" -w N worker tasks (default 4)");
out_printf(" -q N outstanding requests per worker (default 4)");
Expand Down
40 changes: 39 additions & 1 deletion src/devsoak.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,10 @@ struct NSDQueryResult {
struct Config {
char *device; /* device name, e.g. "uaehf.device" */
LONG unit;
U64 range_start; /* -r, in sectors */
UBYTE partition; /* positional arg was NAME: (partition mode) */
char *dosdev; /* the raw NAME: argument, incl. ':' */
U64 range_start; /* -r, in sectors (partition mode: relative
to the partition start until resolved) */
U64 range_len; /* -r, in sectors */
ULONG duration_s; /* -t, seconds (default 60) */
ULONG workers; /* -w (default 4) */
Expand Down Expand Up @@ -149,6 +152,41 @@ struct DevUnderTest {
extern struct Config cfg;
extern struct DevUnderTest dev;

/* ---- dosdev.c: partition mode (`devsoak DH1: -d [options]`) ----
* Resolves a DOS device name (a single positional argument ending in ':')
* to the exec device/unit and partition extent it is mounted on, by
* walking the DosList: LockDosList()/FindDosEntry() on V36+, a manual
* Forbid()-protected walk of DOSBase->dl_Root->rn_Info->di_DevInfo on
* Kickstart 1.3 (same struct DosList layout either way -- BCPL BPTR/BSTR
* fields throughout, hence BADDR() everywhere below). Every field is
* validated before use (§ args_parse must never print, so all of this
* runs from main() after out_init(), and prints its own one-line reason
* on failure). Callers treat a nonzero return as RC_FATAL. */

struct PartInfo {
char devname[64]; /* exec device name, e.g. "scsi.device" */
LONG unit; /* fssm_Unit */
ULONG opendevice_flags; /* fssm_Flags, passed to OpenDevice() */
ULONG sizeblock; /* de_SizeBlock, in LONGWORDS */
ULONG surfaces; /* de_Surfaces */
ULONG blockspertrack;/* de_BlocksPerTrack */
ULONG lowcyl; /* de_LowCyl */
ULONG highcyl; /* de_HighCyl */
struct MsgPort *handler; /* dol_Task; NULL = handler never started */
UBYTE have_volume; /* a DLT_VOLUME entry names this handler */
char volname[64]; /* if have_volume: its BSTR name, as C */
};

LONG dosdev_resolve(const char *name, struct PartInfo *out);
/* ACTION_INHIBIT the handler (dp_Arg1 = DOSTRUE); DoPkt() on V36+, a
* hand-rolled struct StandardPacket on Kickstart 1.3. Prints its own
* failure reason and returns nonzero (RC_FATAL) on refusal. *inhibited is
* set on success, left 0 if handler is NULL (nothing to inhibit) or the
* packet failed; pass the same variable to dosdev_uninhibit() unchanged
* on every exit path -- it is a no-op unless *inhibited is set. */
LONG dosdev_inhibit(struct MsgPort *handler, UBYTE *inhibited);
void dosdev_uninhibit(struct MsgPort *handler, UBYTE *inhibited);

/* ---- PRNG: xorshift32, one state per task (§13) ---- */

static __inline ULONG xs32(ULONG *s)
Expand Down
Loading