fix(v4.0.0): three defects -- a refused device write, a failed starting state, a case that QUITs
A block write the device refuses after the kernel has taken the node's copy: the node was told "Storage refused" while the kernel's cache kept the new data, to be read back and perhaps written later. The kernel now puts its own copy back as it was. Tested with a device whose writes can be made to fail; it failed first. POST's runner: a case whose starting state could not be set says so, with what the node said, where it showed nothing; and a case that ends with QUIT fails, as it did when the cases were a capsule, where the runner had taken it for a completed line. Both tests failed first. The v4 boot says "Artemis: virtio-blk attached" only when the attach worked. make -C v4 test, sanitize and hosted-check pass; amd64, aarch64 and riscv64 boot, POST 538 of 538, dict_hash 0x5f0a949a6fc8ef2b on all six: logs/20261007-135741, -140004, -140329. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
bc6deab3f5
commit
193523d873
@@ -34,7 +34,8 @@
|
||||
* by SAVE-BUFFERS or when its slot is wanted for another block. (v3's
|
||||
* UPDATE copies the slot to the kernel at once; the standard is followed.)
|
||||
* A slot whose block is not marked is taken before one whose block is. A
|
||||
* block that storage would not take is let go of: its slot is empty.
|
||||
* block that storage would not take is let go of: its slot is empty, and
|
||||
* the kernel keeps the block as it was before, not what was refused.
|
||||
*
|
||||
* When the kernel's chain of devices changes, everything in the slots is
|
||||
* let go and nothing of it is written, as v3 does (blk_vm_check_epoch).
|
||||
|
||||
@@ -36,7 +36,10 @@ extern const unsigned v4_post_case_count;
|
||||
* case's lines one at a time, keeping what the node prints and showing none
|
||||
* of it; and judges. A case passes when it ended in an error exactly if
|
||||
* one was expected and, if none was, its stack and its printing are what
|
||||
* the table has. It has ended in an error if any of its lines did. What
|
||||
* the table has. It has ended in an error if any of its lines did. A
|
||||
* case one of whose lines ends with QUIT fails: it went back to the
|
||||
* terminal and did not complete. So does one whose starting state could
|
||||
* not be set, and what the node said of that is shown. What
|
||||
* the cases define is still in the node's dictionary when the runner
|
||||
* returns: whoever runs POST puts the dictionary back afterwards, as v3
|
||||
* does after each of its suites (v3/src/test_runner/test_common.c,
|
||||
|
||||
+15
-1
@@ -30,12 +30,14 @@ static v4_cell slot_addr(const v4_blocks *w, unsigned s)
|
||||
* no longer marked. */
|
||||
static int write_slot(v4_blocks *w, v4_node *n, unsigned s)
|
||||
{
|
||||
static uint8_t was[V4_BLOCK_CELLS * 4u]; /* the block as the kernel had it, in case it is refused */
|
||||
uint8_t *b = blk_get_buffer(w->block[s], 1);
|
||||
v4_cell at = slot_addr(w, s);
|
||||
unsigned i;
|
||||
|
||||
w->changed[s] = 0;
|
||||
if (!b) return V4_BLOCK_REFUSED;
|
||||
for (i = 0; i < V4_BLOCK_CELLS * 4u; i++) was[i] = b[i];
|
||||
for (i = 0; i < V4_BLOCK_CELLS; i++) {
|
||||
v4_ucell c = (v4_ucell)n->mem[at + (v4_cell)i];
|
||||
b[4u * i] = (uint8_t)(c & 0xFFu);
|
||||
@@ -43,7 +45,19 @@ static int write_slot(v4_blocks *w, v4_node *n, unsigned s)
|
||||
b[4u * i + 2u] = (uint8_t)((c >> 16) & 0xFFu);
|
||||
b[4u * i + 3u] = (uint8_t)((c >> 24) & 0xFFu);
|
||||
}
|
||||
return (blk_update(w->block[s]) == BLK_OK && blk_flush(w->block[s]) == BLK_OK) ? V4_BLOCK_OK : V4_BLOCK_REFUSED;
|
||||
if (blk_update(w->block[s]) == BLK_OK && blk_flush(w->block[s]) == BLK_OK) return V4_BLOCK_OK;
|
||||
|
||||
/* Storage would not have it. The node is told so, and the kernel does
|
||||
* not keep what was refused: its own copy is put back as it was, so
|
||||
* that the block reads as before and what was refused is not written
|
||||
* later. The buffer is asked for again, since the failed write may
|
||||
* have moved it. */
|
||||
b = blk_get_buffer(w->block[s], 1);
|
||||
if (b) {
|
||||
for (i = 0; i < V4_BLOCK_CELLS * 4u; i++) b[i] = was[i];
|
||||
(void)blk_update(w->block[s]);
|
||||
}
|
||||
return V4_BLOCK_REFUSED;
|
||||
}
|
||||
|
||||
/* A slot to put a block in: an empty one, or one whose block is not marked,
|
||||
|
||||
+10
-4
@@ -44,13 +44,16 @@ int v4_post_run(const v4_post_host *h, const v4_post_case *cases, unsigned count
|
||||
for (i = 0; i < count; i++) {
|
||||
const v4_post_case *c = &cases[i];
|
||||
unsigned kept = 0, total = 0, len = 0, depth;
|
||||
int errored = 0, came_back = 1, lost = 0, ok, how;
|
||||
int errored = 0, came_back = 1, lost = 0, no_state = 0, quit = 0, ok, how;
|
||||
|
||||
/* every case starts from the same state */
|
||||
v4_dstack_reset(&h->n->ds);
|
||||
how = h->line(h->self, state_line, (unsigned)sizeof state_line - 1u, out, 0u, &len);
|
||||
how = h->line(h->self, state_line, (unsigned)sizeof state_line - 1u, out, V4_POST_OUT, &len);
|
||||
if (how < 0) lost = 1;
|
||||
if (how != V4_TEXT_COMPLETED) came_back = 0;
|
||||
if (how != V4_TEXT_COMPLETED) { /* what the node said of it is what is shown */
|
||||
came_back = 0; no_state = 1;
|
||||
total = len; kept = len < V4_POST_OUT ? len : V4_POST_OUT;
|
||||
}
|
||||
v4_dstack_reset(&h->n->ds);
|
||||
|
||||
for (k = 0; came_back && k < c->line_count; k++) {
|
||||
@@ -58,6 +61,7 @@ int v4_post_run(const v4_post_host *h, const v4_post_case *cases, unsigned count
|
||||
how = h->line(h->self, c->lines[k], length(c->lines[k]), out + kept, V4_POST_OUT - kept, &len);
|
||||
if (how < 0) { came_back = 0; lost = 1; break; }
|
||||
if (how == V4_TEXT_ERROR) errored = 1;
|
||||
if (how == V4_TEXT_QUIT) quit = 1; /* it went back to the terminal: it did not complete */
|
||||
total += len;
|
||||
kept = total < V4_POST_OUT ? total : V4_POST_OUT;
|
||||
}
|
||||
@@ -70,7 +74,7 @@ int v4_post_run(const v4_post_host *h, const v4_post_case *cases, unsigned count
|
||||
}
|
||||
v4_dstack_reset(&h->n->ds);
|
||||
|
||||
if (!came_back) {
|
||||
if (!came_back || quit) {
|
||||
ok = 0;
|
||||
} else if (c->expect == V4_POST_ERROR) {
|
||||
ok = errored;
|
||||
@@ -92,6 +96,8 @@ int v4_post_run(const v4_post_host *h, const v4_post_case *cases, unsigned count
|
||||
if (ok) { tally->pass++; continue; }
|
||||
tally->fail++;
|
||||
say(h, "POST FAIL: "); say(h, c->name);
|
||||
if (no_state) say(h, " could not be put in its starting state:");
|
||||
if (quit) say(h, " ended with QUIT:");
|
||||
say(h, " out<"); h->say(h->self, out, kept < SHOWN_OUT ? kept : SHOWN_OUT);
|
||||
say(h, "> stack<");
|
||||
for (k = depth < SHOWN_STACK ? depth : SHOWN_STACK; k-- > 0; ) { say_number(h, (long long)left[k]); say(h, " "); }
|
||||
|
||||
@@ -52,6 +52,25 @@ static v4_cell tell(v4_cell request)
|
||||
static int in_window(v4_cell a) { return a >= WINDOW && a < WINDOW + (v4_cell)(V4_BLOCK_SLOTS * V4_BLOCK_CELLS) && (a - WINDOW) % (v4_cell)V4_BLOCK_CELLS == 0; }
|
||||
static unsigned char *chain_block(unsigned num) { return num < 2048u ? ram + ((size_t)num + BLK_FORTH_SYS_RESERVED) * 1024u : dev + (size_t)(num - 2048u) * 1024u; }
|
||||
|
||||
/* A device whose writes can be made to fail: 2048 blocks of RAM behind v3's
|
||||
* device interface (v3/include/blkio.h). */
|
||||
#define FAILING_BLOCKS 2048u
|
||||
static unsigned char failing_bytes[FAILING_BLOCKS * 1024u];
|
||||
static int failing;
|
||||
static int f_open(struct blkio_dev *d, const blkio_params_t *p) { (void)d; (void)p; return BLKIO_OK; }
|
||||
static int f_close(struct blkio_dev *d) { (void)d; return BLKIO_OK; }
|
||||
static int f_read(struct blkio_dev *d, uint32_t b, void *dst) { (void)d; memcpy(dst, failing_bytes + (size_t)b * 1024u, 1024); return BLKIO_OK; }
|
||||
static int f_write(struct blkio_dev *d, uint32_t b, const void *src)
|
||||
{
|
||||
(void)d;
|
||||
if (failing) return BLKIO_EIO;
|
||||
memcpy(failing_bytes + (size_t)b * 1024u, src, 1024);
|
||||
return BLKIO_OK;
|
||||
}
|
||||
static int f_flush(struct blkio_dev *d) { (void)d; return failing ? BLKIO_EIO : BLKIO_OK; }
|
||||
static int f_info(struct blkio_dev *d, blkio_info_t *out) { (void)d; memset(out, 0, sizeof *out); return BLKIO_OK; }
|
||||
static const blkio_vtable_t failing_vt = { f_open, f_close, f_read, f_write, f_flush, f_info };
|
||||
|
||||
int main(void)
|
||||
{
|
||||
unsigned i;
|
||||
@@ -165,6 +184,30 @@ int main(void)
|
||||
CHECK(ask(V4_REQ_BLOCK, 2070) == V4_BLOCK_OK && (n.mem[got_addr] & 0xFF) == 'N', "and the block is read again");
|
||||
}
|
||||
|
||||
/* ---- a write that fails on the device leaves the block as it was ---- */
|
||||
{
|
||||
static blkio_dev_t fdev;
|
||||
blkio_params_t params;
|
||||
uint32_t first = 0, n_blocks = 0;
|
||||
params.forth_block_size = 0; params.total_blocks = FAILING_BLOCKS; params.opaque = 0;
|
||||
CHECK(blkio_open(&fdev, &failing_vt, ¶ms) == BLKIO_OK && blk_subsys_attach_device(&fdev) == BLK_OK
|
||||
&& blk_get_device_range(&fdev, &first, &n_blocks) == BLK_OK && n_blocks > 8 && blk_subsys_confirm_format(first) == BLK_OK,
|
||||
"a disk joins the chain and is formatted: %u blocks from %u", n_blocks, first);
|
||||
CHECK(ask(V4_REQ_BLOCK, (v4_cell)first + 3) == V4_BLOCK_OK, "one of its blocks is asked for");
|
||||
for (i = 0; i < V4_BLOCK_CELLS; i++) n.mem[got_addr + (v4_cell)i] = (v4_cell)0x4F4F4F4F;
|
||||
CHECK(tell(V4_REQ_UPDATE) == V4_BLOCK_OK && tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK, "and written: all O");
|
||||
CHECK(ask(V4_REQ_BLOCK, (v4_cell)first + 3) == V4_BLOCK_OK, "it is asked for again");
|
||||
for (i = 0; i < V4_BLOCK_CELLS; i++) n.mem[got_addr + (v4_cell)i] = (v4_cell)0x4E4E4E4E;
|
||||
failing = 1;
|
||||
CHECK(tell(V4_REQ_UPDATE) == V4_BLOCK_OK && tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_REFUSED, "with the device failing, a write of all N is refused");
|
||||
failing = 0;
|
||||
(void)tell(V4_REQ_EMPTY_BUFFERS);
|
||||
CHECK(ask(V4_REQ_BLOCK, (v4_cell)first + 3) == V4_BLOCK_OK && n.mem[got_addr] == (v4_cell)0x4F4F4F4F && n.mem[got_addr + 255] == (v4_cell)0x4F4F4F4F,
|
||||
"and the block is what it was before: the kernel does not keep what the device refused: %lx", (unsigned long)n.mem[got_addr]);
|
||||
CHECK(blk_flush(0) == BLK_OK && ask(V4_REQ_BLOCK, (v4_cell)first + 3) == V4_BLOCK_OK && n.mem[got_addr] == (v4_cell)0x4F4F4F4F,
|
||||
"nor does it write it later, when the device works again");
|
||||
}
|
||||
|
||||
/* ---- what is not a block request ---- */
|
||||
v4_dstack_reset(&n.ds);
|
||||
v4_dstack_push(&n.ds, 11); v4_dstack_push(&n.ds, 22);
|
||||
|
||||
@@ -1250,9 +1250,17 @@ int main(void)
|
||||
CHECK(is(say("1 2 3 HEX\n"), " ok\nok> "), "the node is left in HEX with three values on its stack");
|
||||
CHECK(post_one("state", "10 .", 0, V4_POST_STACK, 0, 0, "10 ") == 1, "a case still starts in decimal with nothing on the stack");
|
||||
post_state_fails = 1;
|
||||
CHECK(post_one("state.bad", "1 2 +", 0, V4_POST_STACK, three, 1, "") == 0 && strstr(post_said, "POST FAIL: state.bad ") != NULL, "if the state cannot be set the case fails, by name");
|
||||
CHECK(post_one("state.bad", "1 2 +", 0, V4_POST_STACK, three, 1, "") == 0 && strstr(post_said, "POST FAIL: state.bad ") != NULL
|
||||
&& strstr(post_said, "could not be put in its starting state") != NULL && strstr(post_said, "NOSUCHWORD-FOR-STATE") != NULL,
|
||||
"if the state cannot be set the case fails, by name, and says that and what the node said: \"%s\"", post_said);
|
||||
post_state_fails = 0;
|
||||
|
||||
/* a case that ends with QUIT is not a case that completed */
|
||||
CHECK(post_one("quit.plain", "QUIT", 0, V4_POST_STACK, 0, 0, "\n") == 0 && strstr(post_said, "POST FAIL: quit.plain ") != NULL,
|
||||
"a case that ends with QUIT fails, though it left and printed what the table has: \"%s\"", post_said);
|
||||
CHECK(post_one("quit.err", "QUIT", 0, V4_POST_ERROR, 0, 0, "") == 0, "and it has not ended in an error either");
|
||||
CHECK(post_one("after.quit", "1 2 +", 0, V4_POST_STACK, three, 1, "") == 1, "the case after it is judged as usual");
|
||||
|
||||
/* a case that prints more than is kept */
|
||||
CHECK(is(say(": MANY 2000 0 DO 65 EMIT LOOP ;\n"), " ok\nok> "), "a word that prints 2,000 characters");
|
||||
memset(big, 'A', 2000); big[2000] = 0;
|
||||
|
||||
Reference in New Issue
Block a user