summaryrefslogtreecommitdiff
path: root/9proc/test/adv_core_hostile.py
diff options
context:
space:
mode:
Diffstat (limited to '9proc/test/adv_core_hostile.py')
-rwxr-xr-x9proc/test/adv_core_hostile.py158
1 files changed, 82 insertions, 76 deletions
diff --git a/9proc/test/adv_core_hostile.py b/9proc/test/adv_core_hostile.py
index 464876f..febda0d 100755
--- a/9proc/test/adv_core_hostile.py
+++ b/9proc/test/adv_core_hostile.py
@@ -4,7 +4,7 @@
Complements adv_9proc_hostile.py in this directory (framing, tags, scratch, floods)
with attacks on the freestanding engine's own paths: the /vars tree and its
comptime renderers, snapshot slots, the static tree, the fid table at its
-configured maximum, directory-read offsets, msize 24, the ctl staging rule,
+configured maximum, directory-read offsets, the msize floor, the ctl staging rule,
and the demo's debug providers driven as black boxes.
Usage:
@@ -30,6 +30,8 @@ from adv_9proc_hostile import ( # noqa: E402
Tread, Rread, Twrite, Rwrite, Tclunk, Rclunk, Tremove, Rremove, Tstat, Rstat, Twstat, Rwstat,
Rerror, OREAD, OWRITE, ORDWR, OEXEC, OTRUNC, ORCLOSE, DMDIR,
Nine, frame, s16, mkstat, parse_stat, ok, healthy, expect_dead,
+ MSIZE_MIN, E_UNKNOWN_FID, E_FID_IN_USE, E_TOO_MANY_FIDS, E_BAD_USE, E_ALREADY_OPEN, E_BAD_OFFSET,
+ E_PERM, E_WSTAT, E_INVAL,
)
MAX_FIDS = 32768 # demo/main.zig cfg.max_fids
@@ -88,7 +90,7 @@ def attack_vars(path):
for nm in (b"0", b"-1", b"0x", b"0x0", b"state\x00", b"State", b" state", b"state ", b"a" * 255, b"a" * 65535, b"\xff\xfe", b"..\x00", b"f", b"value"):
n = c.walk_ok(0, 1, [b"vars", nm])
ok(f"walk /vars/{nm[:12]!r}{'...' if len(nm) > 12 else ''} is a partial walk (1)", n == 1, n)
- ok(" and newfid stays unbound", c.err(Tclunk, struct.pack("<I", 1)) == "unknown fid")
+ ok(" and newfid stays unbound", c.err(Tclunk, struct.pack("<I", 1)) == E_UNKNOWN_FID)
for nm in (b"0", b"F", b"f\x00", b"ticks", b"value ", b"raw\x00"):
n = c.walk_ok(0, 1, [b"vars", b"state", nm])
ok(f"walk /vars/state/{nm!r} is a partial walk (2)", n == 2, n)
@@ -151,7 +153,7 @@ def attack_vars(path):
for bad in (b"abc", b"99999999999999999999999", b"-1", b"1e3", b"", b" ", b"4\x002", b"1.5", b"0x", b"+", b"\xd9\xa1\xd9\xa2", b"12 34", b"0b102"):
e = c.err(Twrite, struct.pack("<IQI", 1, 0, len(bad)) + bad)
ok(f"write {bad!r} to u64 value is 'bad value'", e == "bad value", e)
- ok("read on the write-only value fid is 'file not open'", c.err(Tread, struct.pack("<IQI", 1, 0, 10)) == "file not open")
+ ok("read on the write-only value fid is 'file not open'", c.err(Tread, struct.pack("<IQI", 1, 0, 10)) == E_BAD_USE)
for good, want in ((b" 4200 \n", 4200), (b"0x10", 16), (b"+7", 7), (b"0b1010", 10), (b"0o17", 15), (b"1_000", 1000), (b"18446744073709551615", (1 << 64) - 1)):
rt, _, rb = c.write(1, (1 << 64) - 1, good) # offset is ignored for values
got = c.path_read([b"vars", b"state", b"f", b"ticks", b"value"])
@@ -200,9 +202,9 @@ def attack_vars(path):
p = b"/".join(names).decode()
ok(f"create in {p} is denied", c.err(base.Tcreate, struct.pack("<I", 1) + s16(b"x") + struct.pack("<IB", 0o644, OWRITE)) == "permission denied")
ok(f"wstat of {p} is denied", c.err(Twstat, struct.pack("<I", 1) + s16(mkstat(name=b"y"))) == "permission denied")
- ok(f"open {p} for write is 'is a directory'", c.err(Topen, struct.pack("<IB", 1, OWRITE)) == "is a directory")
+ ok(f"open {p} for write is refused by the engine", c.err(Topen, struct.pack("<IB", 1, OWRITE)) == E_PERM)
ok(f"remove {p} is denied", c.err(Tremove, struct.pack("<I", 1)) == "permission denied")
- ok(f" and the fid was clunked", c.err(Tclunk, struct.pack("<I", 1)) == "unknown fid")
+ ok(f" and the fid was clunked", c.err(Tclunk, struct.pack("<I", 1)) == E_UNKNOWN_FID)
for names in ([b"vars", b"state", b"value"], [b"vars", b"state", b"f", b"last_job", b"value"], [b"vars", b"state", b"type"]):
c.walk_ok(0, 1, names)
p = b"/".join(names).decode()
@@ -249,7 +251,7 @@ def attack_snapshots(path):
break
ok(f"exactly {SNAPSHOT_SLOTS} dynamic files open per connection", opened == SNAPSHOT_SLOTS, opened)
ok("the next open is 'too many open dynamic files'", err == "too many open dynamic files", err)
- ok("the refused fid is still unopened (read is 'file not open')", c.err(Tread, struct.pack("<IQI", 100 + opened, 0, 10)) == "file not open")
+ ok("the refused fid is still unopened (read is 'file not open')", c.err(Tread, struct.pack("<IQI", 100 + opened, 0, 10)) == E_BAD_USE)
# every held snapshot is still readable and consistent at offset 1
for i in range(opened):
rt, d = c.read(100 + i, 0, 8192)
@@ -278,11 +280,11 @@ def attack_snapshots(path):
c.walk_ok(0, 60, dyn[0])
rt, _, _ = c.open(60, OREAD)
ok("the failed-remove fid's slot was released", rt == Ropen, rt)
- # a clone of an open dynamic fid takes no slot and is unopened
- rt, _, _ = c.walk(60, 61, [])
- ok("clone of an open dynamic fid is allowed", rt == Rwalk, rt)
- ok("the clone is not open", c.err(Tread, struct.pack("<IQI", 61, 0, 10)) == "file not open")
- ok("the clone cannot open (slots exhausted again)", c.err(Topen, struct.pack("<IB", 61, OREAD)) == "too many open dynamic files")
+ # an open fid cannot be cloned; a fresh walk to the same file takes no slot and is unopened
+ ok("clone of an open dynamic fid is refused", c.err(Twalk, struct.pack("<IIH", 60, 61, 0)) == E_BAD_USE)
+ c.walk_ok(0, 61, dyn[0])
+ ok("the fresh fid is not open", c.err(Tread, struct.pack("<IQI", 61, 0, 10)) == E_BAD_USE)
+ ok("the fresh fid cannot open (slots exhausted again)", c.err(Topen, struct.pack("<IB", 61, OREAD)) == "too many open dynamic files")
# Tversion releases everything: 8 opens succeed again
rt, ms, _ = c.version(65536)
ok("mid-session Tversion", rt == base.Rversion)
@@ -310,12 +312,13 @@ def attack_static(path):
ok(f"create in {p} is denied", c.err(base.Tcreate, struct.pack("<I", 1) + s16(b"x") + struct.pack("<IB", 0o644, OWRITE)) == "permission denied")
ok(f"mkdir in {p} is denied", c.err(base.Tcreate, struct.pack("<I", 1) + s16(b"d") + struct.pack("<IB", DMDIR | 0o755, OREAD)) == "permission denied")
ok(f"wstat of {p} is denied", c.err(Twstat, struct.pack("<I", 1) + s16(mkstat(mtime=1))) == "permission denied")
- ok(f"wstat of {p} with all don't-care is denied too", c.err(Twstat, struct.pack("<I", 1) + s16(mkstat())) == "permission denied")
- ok(f"open {p} ORDWR is 'is a directory'", c.err(Topen, struct.pack("<IB", 1, ORDWR)) == "is a directory")
- ok(f"open {p} OEXEC works like OREAD", c.open(1, OEXEC)[0] == Ropen)
+ ok(f"wstat of {p} with all don't-care is a no-op the engine answers itself", c.wstat(1, mkstat())[0] == Rwstat)
+ ok(f"open {p} ORDWR is refused by the engine", c.err(Topen, struct.pack("<IB", 1, ORDWR)) == E_PERM)
+ ok(f"open {p} OEXEC is refused by the engine", c.err(Topen, struct.pack("<IB", 1, OEXEC)) == E_PERM)
+ ok(f"open {p} OREAD", c.open(1, OREAD)[0] == Ropen)
ok(f"write to open {p} is 'is a directory'", c.err(Twrite, struct.pack("<IQI", 1, 0, 1) + b"x") is not None)
ok(f"remove {p} is denied", c.err(Tremove, struct.pack("<I", 1)) == "permission denied")
- ok(f" and clunked", c.err(Tclunk, struct.pack("<I", 1)) == "unknown fid")
+ ok(f" and clunked", c.err(Tclunk, struct.pack("<I", 1)) == E_UNKNOWN_FID)
files = [[b"README"], [b"build", b"time"], [b"comptime", b"decls"], [b"comptime", b"types", b"Qid", b"fields"], [b"runtime", b"pid"], [b"runtime", b"fn", b"fib30"], [b"runtime", b"ctl"]]
for names in files:
p = "/" + b"/".join(names).decode()
@@ -324,7 +327,7 @@ def attack_static(path):
ok(f"'..' from {p} is 'not a directory'", c.err(Twalk, struct.pack("<IIH", 1, 2, 1) + s16(b"..")) == "not a directory")
ok(f"wstat of {p} is denied", c.err(Twstat, struct.pack("<I", 1) + s16(mkstat(length=0))) == "permission denied")
ok(f"remove {p} is denied", c.err(Tremove, struct.pack("<I", 1)) == "permission denied")
- ok(f" and clunked", c.err(Tclunk, struct.pack("<I", 1)) == "unknown fid")
+ ok(f" and clunked", c.err(Tclunk, struct.pack("<I", 1)) == E_UNKNOWN_FID)
if names[-1] != b"ctl":
c.walk_ok(0, 1, names)
ok(f"open {p} OWRITE is denied", c.err(Topen, struct.pack("<IB", 1, OWRITE)) == "permission denied")
@@ -563,11 +566,11 @@ def attack_fid_table(path):
good, bad, dt, dead = flood(c, ids, [b"scratch"])
ok(f"{n} walks with adversarial fid numbers all succeed", good == n and bad == 0 and not dead, (good, bad, dead))
print(f" {n} clones (provider handles) in {dt:.2f}s")
- ok("the next fid is 'too many fids'", c.err(Twalk, struct.pack("<IIH", 0, 7, 0)) == "too many fids")
- ok("attach at the limit is 'too many fids'", c.err(base.Tattach, struct.pack("<II", 7, base.NOFID) + s16(b"u") + s16(b"")) == "too many fids")
- ok("an existing id is 'fid in use'", c.err(Twalk, struct.pack("<IIH", 0, ids[12345], 0)) == "fid in use")
+ ok("the next fid is 'too many fids'", c.err(Twalk, struct.pack("<IIH", 0, 7, 0)) == E_TOO_MANY_FIDS)
+ ok("attach at the limit is 'too many fids'", c.err(base.Tattach, struct.pack("<II", 7, base.NOFID) + s16(b"u") + s16(b"")) == E_TOO_MANY_FIDS)
+ ok("an existing id is 'fid in use'", c.err(Twalk, struct.pack("<IIH", 0, ids[12345], 0)) == E_FID_IN_USE)
ok("a self-walk at the limit works", c.walk_ok(ids[5], ids[5], [b".."]) == 1)
- ok("an unknown fid at the limit is 'unknown fid'", c.err(Tstat, struct.pack("<I", 7)) == "unknown fid")
+ ok("an unknown fid at the limit is 'unknown fid'", c.err(Tstat, struct.pack("<I", 7)) == E_UNKNOWN_FID)
# clunk all, pipelined, in a hostile order (every third first, then the rest reversed)
order = ids[::3] + ids[1::3][::-1] + ids[2::3][::-1]
got = [0]
@@ -588,12 +591,12 @@ def attack_fid_table(path):
t.join(120)
ok(f"{n} clunks all answered", got[0] == n and not dead[0] and not t.is_alive(), (got[0], dead[0]))
print(f" {n} clunks in {time.time() - t0:.2f}s")
- ok("clunk of a clunked fid is 'unknown fid'", c.err(Tclunk, struct.pack("<I", ids[100])) == "unknown fid")
+ ok("clunk of a clunked fid is 'unknown fid'", c.err(Tclunk, struct.pack("<I", ids[100])) == E_UNKNOWN_FID)
# reuse: the whole table is available again with dense ids
good, bad, dt, dead = flood(c, list(range(1, n + 1)), [])
ok(f"{n} clones with dense ids after the churn all succeed", good == n and bad == 0 and not dead, (good, bad, dead))
print(f" {n} clones (reuse) in {dt:.2f}s")
- ok("still 'too many fids' at the limit", c.err(Twalk, struct.pack("<IIH", 0, n + 1, 0)) == "too many fids")
+ ok("still 'too many fids' at the limit", c.err(Twalk, struct.pack("<IIH", 0, n + 1, 0)) == E_TOO_MANY_FIDS)
rt, _, _ = c.version(65536)
ok("Tversion after the fid churn", rt == base.Rversion)
c.attach()
@@ -635,63 +638,66 @@ def attack_flush(path):
ok("server healthy after the flush storm", healthy(path))
-# --------------------------------------------------------------------------- msize 24
+# --------------------------------------------------------------------------- msize floor
-def attack_msize24(path):
- print("# msize 24: everything that fits is served, everything else is an Rerror that fits")
+def attack_msize_floor(path):
+ print(f"# msize floor: below {MSIZE_MIN} the connection dies; at {MSIZE_MIN} everything that fits is served")
+ for ms in (24, 64, MSIZE_MIN - 1):
+ c = Nine(path)
+ rt, _, _ = c.version(ms)
+ ok(f"Tversion msize {ms}: closed", rt is None or expect_dead(c), rt)
+ c.close()
c = Nine(path)
- rt, ms, ver = c.version(24)
- ok("Tversion 24", rt == base.Rversion and ms == 24, (rt, ms))
- rt, _, _ = c.attach(uname=b"u") # 20 bytes; Rattach is 20
- ok("Tattach at msize 24", rt == base.Rattach, rt)
- e = c.err(Tstat, struct.pack("<I", 0))
- ok("Tstat: Rerror truncated to 15 bytes ('reply too large')", e == "reply too large", e)
- rt, _, rb = c.walk(0, 1, [b"build"]) # Twalk 24, Rwalk 22
- ok("Twalk of one 5-byte name", rt == Rwalk, rt)
- e = c.err(Twalk, struct.pack("<IIH", 0, 2, 2) + s16(b".") + s16(b".")) # 23 bytes; Rwalk would be 35
- ok("Twalk of two names cannot be answered: 'reply too large'", e == "reply too large", e)
- ok("newfid unbound after the refused walk", c.err(Tclunk, struct.pack("<I", 2)) == "unknown fid")
- rt, _, _ = c.open(1, OREAD) # Ropen 24
- ok("Topen at msize 24", rt == Ropen, rt)
- rt, d = c.read(1, 0, 4096) # count clamped to msize - iohdrsz = 0
- ok("Tread of a directory at msize 24 answers an empty Rread (no split record)", rt == Rread and d == b"", (rt, d))
- e = c.err(Tread, struct.pack("<IQI", 1, 1, 4096))
- ok("dir read at offset 1 is 'bad offset'", e == "bad offset", e)
- rt, _, _ = c.clunk(1)
- ok("Tclunk at msize 24", rt == Rclunk, rt)
- rt, _, _ = c.walk(0, 1, [b"vars"]) # Twalk 23
- rt, _, _ = c.walk(1, 1, [b"state"]) # 23
- rt, _, _ = c.walk(1, 1, [b"value"]) # 23
- ok("walk to /vars/state/value in 3 self-walks", rt == Rwalk, rt)
+ rt, ms, ver = c.version(MSIZE_MIN)
+ ok(f"Tversion {MSIZE_MIN}", rt == base.Rversion and ms == MSIZE_MIN, (rt, ms))
+ rt, _, _ = c.attach(uname=b"u")
+ ok("Tattach at the floor", rt == base.Rattach, rt)
+ rt, st = c.stat(0)
+ ok("Tstat of the root fits", rt == Rstat and st["name"] == b"/", (rt, st))
+ rt, _, rb = c.walk(0, 1, [b"."] * 16)
+ ok("a full 16-element Rwalk is exactly the floor", rt == Rwalk and struct.unpack_from("<H", rb)[0] == 16, rt)
+ c.clunk(1)
+ # an Rstat that cannot fit is an Rerror, not a dead connection: a long name in /scratch
+ long_name = b"m" * 180 # Tcreate (18 + 180 bytes) fits; the Rstat (61 + 180) does not
+ c.walk_ok(0, 1, [b"scratch"])
+ rt, _, _ = c.create(1, long_name, 0o644, OREAD)
+ ok("create a long name at the floor", rt == Rcreate, rt)
+ e = c.err(Tstat, struct.pack("<I", 1))
+ ok("Tstat that does not fit is 'Invalid argument'", e == E_INVAL, e)
+ ok("remove it", c.remove(1)[0] == Rremove)
+ rt, _, _ = c.walk(0, 1, [b"build"])
rt, _, _ = c.open(1, OREAD)
- ok("open a dynamic file at msize 24", rt == Ropen, rt)
- rt, d = c.read(1, 0, 4096)
- ok("read of a dynamic file at msize 24 is an empty Rread", rt == Rread and d == b"", (rt, d))
+ ok("Topen at the floor", rt == Ropen, rt)
+ rt, d = c.read(1, 0, 4096) # count clamped to msize - 11
+ ok("a directory read at the floor yields whole records", rt == Rread and 0 < len(d) <= MSIZE_MIN - 11 and records(d), (rt, len(d) if d else d))
+ e = c.err(Tread, struct.pack("<IQI", 1, 1, 4096))
+ ok("dir read at offset 1 is a bad offset", e == E_BAD_OFFSET, e)
rt, _, _ = c.clunk(1)
- for nm in (b"vars", b"state", b"f", b"ticks", b"value"): # each Twalk <= 24 bytes
+ ok("Tclunk at the floor", rt == Rclunk, rt)
+ for nm in (b"vars", b"state", b"f", b"ticks", b"value"):
rt, _, _ = c.walk(0 if nm == b"vars" else 1, 1, [nm])
ok("walk to /vars/state/f/ticks/value in 5 self-walks", rt == Rwalk, rt)
rt, _, _ = c.open(1, OWRITE)
- ok("open a writable value at msize 24", rt == Ropen, rt)
- rt, _, _ = c.write(1, 0, b"5") # Twrite 24, Rwrite 11
- ok("Twrite of one byte at msize 24", rt == Rwrite, rt)
+ ok("open a writable value at the floor", rt == Ropen, rt)
+ rt, _, _ = c.write(1, 0, b"5")
+ ok("Twrite of one byte at the floor", rt == Rwrite, rt)
e = c.err(Twrite, struct.pack("<IQI", 1, 0, 0) + b"")
- ok("empty write to a value at msize 24 is 'bad value'", e == "bad value", e)
+ ok("empty write to a value at the floor is 'bad value'", e == "bad value", e)
rt, _, _ = c.clunk(1)
- ok("clunk at msize 24", rt == Rclunk, rt)
+ ok("clunk at the floor", rt == Rclunk, rt)
rt, ms, _ = c.version(65536)
ok("renegotiate a big msize on the same connection", rt == base.Rversion and ms == 65536, (rt, ms))
c.attach()
ok("normal service resumes", c.path_read([b"build", b"zig_version"]) not in (None, b""))
c.close()
- # frames larger than 24 after negotiating 24 kill the connection
+ # frames larger than the negotiated msize kill the connection
c = Nine(path)
- c.version(24)
- c.raw(frame(base.Tattach, 1, struct.pack("<II", 0, base.NOFID) + s16(b"longer-name") + s16(b"")))
- ok("a 30-byte Tattach at msize 24: connection closed", expect_dead(c))
+ c.version(MSIZE_MIN)
+ c.raw(frame(base.Tattach, 1, struct.pack("<II", 0, base.NOFID) + s16(b"u" * 210) + s16(b"")))
+ ok("an over-long Tattach at the floor: connection closed", expect_dead(c))
c.close()
- ok("server healthy after msize-24 attacks", healthy(path))
+ ok("server healthy after msize-floor attacks", healthy(path))
# --------------------------------------------------------------------------- directory offsets
@@ -727,15 +733,15 @@ def attack_dir_offsets(path):
rt, d1 = c.read(1, r0, r1)
ok(f"{p}: read at the record boundary returns the next record", rt == Rread and d1 == recs[1][1], (rt, len(d1) if d1 else d1))
e = c.err(Tread, struct.pack("<IQI", 1, r0 + r1 + 1, 65000))
- ok(f"{p}: offset boundary+1 is 'bad offset'", e == "bad offset", e)
+ ok(f"{p}: offset boundary+1 is 'bad offset'", e == E_BAD_OFFSET, e)
e = c.err(Tread, struct.pack("<IQI", 1, r0 + r1 - 1, 65000))
- ok(f"{p}: offset boundary-1 is 'bad offset'", e == "bad offset", e)
+ ok(f"{p}: offset boundary-1 is 'bad offset'", e == E_BAD_OFFSET, e)
e = c.err(Tread, struct.pack("<IQI", 1, r0, 65000))
- ok(f"{p}: re-reading an earlier boundary is 'bad offset'", e == "bad offset", e)
+ ok(f"{p}: re-reading an earlier boundary is 'bad offset'", e == E_BAD_OFFSET, e)
rt, rest = c.read(1, r0 + r1, 65000)
ok(f"{p}: after a bad offset the good boundary still continues", rt == Rread and rest == d[r0 + r1:], rt)
- rt, dd = c.read(1, 0, r0 - 1)
- ok(f"{p}: count one short of a record returns nothing (no split)", rt == Rread and dd == b"", (rt, dd))
+ e = c.err(Tread, struct.pack("<IQI", 1, 0, r0 - 1))
+ ok(f"{p}: count one short of a record is refused (no split)", e == E_INVAL, e)
rt, dd = c.read(1, 0, 65000)
ok(f"{p}: offset 0 restarts and yields the same bytes", rt == Rread and dd == d)
rt, dd = c.read(1, len(d), 65000)
@@ -817,17 +823,17 @@ def attack_fid_states(path):
c.walk_ok(0, 1, S)
rt, _, _ = c.create(1, b"f", 0o644, ORDWR)
ok("create f", rt == Rcreate)
- ok("open of an open fid is 'file already open'", c.err(Topen, struct.pack("<IB", 1, OREAD)) == "file already open")
- ok("walk with names from an open fid is 'file already open'", c.err(Twalk, struct.pack("<IIH", 1, 2, 1) + s16(b".")) == "file already open")
- ok("create on an open fid is 'file already open'", c.err(base.Tcreate, struct.pack("<I", 1) + s16(b"g") + struct.pack("<IB", 0o644, OWRITE)) == "file already open")
- rt, _, _ = c.walk(1, 2, [])
- ok("clone of an open fid is allowed", rt == Rwalk)
- ok("the clone is not open", c.err(Tread, struct.pack("<IQI", 2, 0, 10)) == "file not open")
+ ok("open of an open fid is 'file already open for I/O'", c.err(Topen, struct.pack("<IB", 1, OREAD)) == E_ALREADY_OPEN)
+ ok("walk with names from an open fid is 'bad use of fid'", c.err(Twalk, struct.pack("<IIH", 1, 2, 1) + s16(b".")) == E_BAD_USE)
+ ok("create on an open fid is 'file already open for I/O'", c.err(base.Tcreate, struct.pack("<I", 1) + s16(b"g") + struct.pack("<IB", 0o644, OWRITE)) == E_ALREADY_OPEN)
+ ok("clone of an open fid is refused", c.err(Twalk, struct.pack("<IIH", 1, 2, 0)) == E_BAD_USE)
+ ok("a fresh walk reaches the open file", c.walk_ok(0, 2, S + [b"f"]) == 3)
+ ok("the fresh fid is not open", c.err(Tread, struct.pack("<IQI", 2, 0, 10)) == E_BAD_USE)
rt, _, _ = c.open(2, OREAD)
- ok("the clone opens independently", rt == Ropen)
+ ok("the fresh fid opens independently", rt == Ropen)
c.write(1, 0, b"data")
rt, d = c.read(2, 0, 10)
- ok("the clone sees the write", rt == Rread and d == b"data", d)
+ ok("the second fid sees the write", rt == Rread and d == b"data", d)
rt, _, _ = c.wstat(2, mkstat(name=b"renamed"))
ok("wstat through an open fid works", rt == Rwstat)
rt, _, _ = c.remove(1)
@@ -982,7 +988,7 @@ def main():
attack_dir_offsets(path)
attack_ctl(path)
attack_fid_states(path)
- attack_msize24(path)
+ attack_msize_floor(path)
attack_flush(path)
attack_fid_table(path)
if not args.fast: