summaryrefslogtreecommitdiff
path: root/lib9p
diff options
context:
space:
mode:
Diffstat (limited to 'lib9p')
-rw-r--r--lib9p/9p.generated.c121
-rw-r--r--lib9p/CMakeLists.txt26
-rw-r--r--lib9p/idl/1992-9P0.9p.wip2
-rw-r--r--lib9p/idl/1996-Styx.9p.wip2
-rw-r--r--lib9p/idl/2002-9P2000.9p2
-rw-r--r--lib9p/idl/2003-9P2000.p9p.9p2
-rw-r--r--lib9p/idl/2005-9P2000.u.9p2
-rw-r--r--lib9p/idl/2010-9P2000.L.9p24
-rw-r--r--lib9p/include/lib9p/9p.generated.h15
-rw-r--r--lib9p/include/lib9p/srv.h91
-rw-r--r--lib9p/map.h114
-rw-r--r--lib9p/protogen/c.py8
-rw-r--r--lib9p/protogen/c_format.py37
-rw-r--r--lib9p/srv.c1142
-rw-r--r--lib9p/tests/client_config/config.h40
-rwxr-xr-xlib9p/tests/runtest81
-rw-r--r--lib9p/tests/test_server/CMakeLists.txt2
-rw-r--r--lib9p/tests/test_server/fs_shutdown.c37
-rw-r--r--lib9p/tests/test_server/fs_slowread.c116
-rw-r--r--lib9p/tests/test_server/fs_slowread.h22
-rw-r--r--lib9p/tests/test_server/fs_whoami.c156
-rw-r--r--lib9p/tests/test_server/fs_whoami.h20
-rw-r--r--lib9p/tests/test_server/main.c79
-rwxr-xr-xlib9p/tests/testclient-p9p62
-rw-r--r--lib9p/tests/testclient-p9p.explog106
-rw-r--r--lib9p/tests/testclient-sess.c160
-rw-r--r--lib9p/tests/testclient-sess.explog74
27 files changed, 1759 insertions, 784 deletions
diff --git a/lib9p/9p.generated.c b/lib9p/9p.generated.c
index b58a485..914a612 100644
--- a/lib9p/9p.generated.c
+++ b/lib9p/9p.generated.c
@@ -37,7 +37,11 @@ LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tauth, lib9p_msg_Tauth, stat
LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rauth, lib9p_msg_Rauth, static);
LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tattach, lib9p_msg_Tattach, static);
LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rattach, lib9p_msg_Rattach, static);
+#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */
+#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u
LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rerror, lib9p_msg_Rerror, static);
+#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */
+#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u
LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Tflush, lib9p_msg_Tflush, static);
LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Rflush, lib9p_msg_Rflush, static);
LO_IMPLEMENTATION_C(fmt_formatter, struct lib9p_msg_Twalk, lib9p_msg_Twalk, static);
@@ -507,6 +511,8 @@ static ssize_t validate_Rattach(struct lib9p_ctx *ctx, uint32_t net_size, uint8_
return (ssize_t)host_size;
}
+#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */
+#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u
static ssize_t validate_Rerror(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) {
uint32_t net_offset = 0;
ssize_t host_size = sizeof(struct lib9p_msg_Rerror);
@@ -529,6 +535,8 @@ static ssize_t validate_Rerror(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t
return (ssize_t)host_size;
}
+#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */
+#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u
static ssize_t validate_Tflush(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes) {
uint32_t net_offset = 0;
ssize_t host_size = sizeof(struct lib9p_msg_Tflush);
@@ -2012,6 +2020,8 @@ static void unmarshal_Rattach([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *ne
UNMARSHAL_U64LE(ctx, out->qid.path);
}
+#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */
+#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u
static void unmarshal_Rerror([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) {
struct lib9p_msg_Rerror *out = out_buf;
[[gnu::unused]] void *extra = &out[1];
@@ -2019,15 +2029,17 @@ static void unmarshal_Rerror([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net
net_offset += 4;
net_offset += 1;
UNMARSHAL_U16LE(ctx, out->tag);
- UNMARSHAL_U16LE(ctx, out->ename.len);
- UNMARSHAL_BYTES(ctx, out->ename.utf8, out->ename.len);
+ UNMARSHAL_U16LE(ctx, out->errstr.len);
+ UNMARSHAL_BYTES(ctx, out->errstr.utf8, out->errstr.len);
#if CONFIG_9P_ENABLE_9P2000_u
if (is_ver(ctx, 9P2000_u)) {
- UNMARSHAL_U32LE(ctx, out->errno);
+ UNMARSHAL_U32LE(ctx, out->errnum);
}
#endif /* CONFIG_9P_ENABLE_9P2000_u */
}
+#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */
+#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u
static void unmarshal_Tflush([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) {
struct lib9p_msg_Tflush *out = out_buf;
[[gnu::unused]] void *extra = &out[1];
@@ -2353,7 +2365,7 @@ static void unmarshal_Rlerror([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *ne
net_offset += 4;
net_offset += 1;
UNMARSHAL_U16LE(ctx, out->tag);
- UNMARSHAL_U32LE(ctx, out->ecode);
+ UNMARSHAL_U32LE(ctx, out->errnum);
}
static void unmarshal_Tstatfs([[gnu::unused]] struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out_buf) {
@@ -3153,8 +3165,10 @@ static bool marshal_Rattach(struct lib9p_ctx *ctx, struct lib9p_msg_Rattach *val
return false;
}
+#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */
+#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u
static bool marshal_Rerror(struct lib9p_ctx *ctx, struct lib9p_msg_Rerror *val, struct _marshal_ret *ret) {
- uint32_t needed_size = 9 + val->ename.len;
+ uint32_t needed_size = 9 + val->errstr.len;
#if CONFIG_9P_ENABLE_9P2000_u
if is_ver(ctx, 9P2000_u) {
needed_size += 4;
@@ -3172,16 +3186,18 @@ static bool marshal_Rerror(struct lib9p_ctx *ctx, struct lib9p_msg_Rerror *val,
MARSHAL_U32LE(ctx, offsetof_end - offsetof_size);
MARSHAL_U8LE(ctx, 107);
MARSHAL_U16LE(ctx, val->tag);
- MARSHAL_U16LE(ctx, val->ename.len);
- MARSHAL_BYTES_ZEROCOPY(ctx, val->ename.utf8, val->ename.len);
+ MARSHAL_U16LE(ctx, val->errstr.len);
+ MARSHAL_BYTES_ZEROCOPY(ctx, val->errstr.utf8, val->errstr.len);
#if CONFIG_9P_ENABLE_9P2000_u
if (is_ver(ctx, 9P2000_u)) {
- MARSHAL_U32LE(ctx, val->errno);
+ MARSHAL_U32LE(ctx, val->errnum);
}
#endif /* CONFIG_9P_ENABLE_9P2000_u */
return false;
}
+#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */
+#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u
static bool marshal_Tflush(struct lib9p_ctx *ctx, struct lib9p_msg_Tflush *val, struct _marshal_ret *ret) {
uint32_t needed_size = 9;
if (needed_size > ctx->max_msg_size) {
@@ -3715,7 +3731,7 @@ static bool marshal_Rlerror(struct lib9p_ctx *ctx, struct lib9p_msg_Rlerror *val
MARSHAL_U32LE(ctx, offsetof_end - offsetof_size);
MARSHAL_U8LE(ctx, 7);
MARSHAL_U16LE(ctx, val->tag);
- MARSHAL_U32LE(ctx, val->ecode);
+ MARSHAL_U32LE(ctx, val->errnum);
return false;
}
@@ -4665,7 +4681,6 @@ static void lib9p_fid_format(lib9p_fid_t *self, struct fmt_state *state) {
}
static void lib9p_s_format(struct lib9p_s *self, struct fmt_state *state) {
- /* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47781 */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat"
#pragma GCC diagnostic ignored "-Wformat-extra-args"
@@ -5137,19 +5152,23 @@ static void lib9p_msg_Rattach_format(struct lib9p_msg_Rattach *self, struct fmt_
fmt_state_puts(state, " }");
}
+#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */
+#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u
static void lib9p_msg_Rerror_format(struct lib9p_msg_Rerror *self, struct fmt_state *state) {
fmt_state_puts(state, "Rerror {");
fmt_state_puts(state, " tag=");
lib9p_tag_format(&self->tag, state);
- fmt_state_puts(state, " ename=");
- lib9p_s_format(&self->ename, state);
+ fmt_state_puts(state, " errstr=");
+ lib9p_s_format(&self->errstr, state);
#if CONFIG_9P_ENABLE_9P2000_u
- fmt_state_puts(state, " errno=");
- lib9p_errno_format(&self->errno, state);
+ fmt_state_puts(state, " errnum=");
+ lib9p_errno_format(&self->errnum, state);
#endif /* CONFIG_9P_ENABLE_9P2000_u */
fmt_state_puts(state, " }");
}
+#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */
+#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u
static void lib9p_msg_Tflush_format(struct lib9p_msg_Tflush *self, struct fmt_state *state) {
fmt_state_puts(state, "Tflush {");
fmt_state_puts(state, " tag=");
@@ -5273,7 +5292,18 @@ static void lib9p_msg_Rread_format(struct lib9p_msg_Rread *self, struct fmt_stat
lib9p_tag_format(&self->tag, state);
fmt_state_puts(state, " count=");
fmt_state_printf(state, "%"PRIu32, self->count);
- fmt_state_puts(state, " data=<bytedata>");
+ if (is_valid_utf8_without_nul((uint8_t *)self->data, (size_t)self->count)) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat"
+#pragma GCC diagnostic ignored "-Wformat-extra-args"
+ fmt_state_printf(state, " data=%.*q%s",
+ (int)(self->count < 50 ? self->count : 50),
+ (char *)self->data,
+ self->count < 50 ? "" : "...");
+#pragma GCC diagnostic pop
+ } else {
+ fmt_state_puts(state, " data=<bytedata>");
+ }
fmt_state_puts(state, " }");
}
@@ -5287,7 +5317,18 @@ static void lib9p_msg_Twrite_format(struct lib9p_msg_Twrite *self, struct fmt_st
fmt_state_printf(state, "%"PRIu64, self->offset);
fmt_state_puts(state, " count=");
fmt_state_printf(state, "%"PRIu32, self->count);
- fmt_state_puts(state, " data=<bytedata>");
+ if (is_valid_utf8_without_nul((uint8_t *)self->data, (size_t)self->count)) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat"
+#pragma GCC diagnostic ignored "-Wformat-extra-args"
+ fmt_state_printf(state, " data=%.*q%s",
+ (int)(self->count < 50 ? self->count : 50),
+ (char *)self->data,
+ self->count < 50 ? "" : "...");
+#pragma GCC diagnostic pop
+ } else {
+ fmt_state_puts(state, " data=<bytedata>");
+ }
fmt_state_puts(state, " }");
}
@@ -6752,8 +6793,8 @@ static void lib9p_msg_Rlerror_format(struct lib9p_msg_Rlerror *self, struct fmt_
fmt_state_puts(state, "Rlerror {");
fmt_state_puts(state, " tag=");
lib9p_tag_format(&self->tag, state);
- fmt_state_puts(state, " ecode=");
- lib9p_errno_format(&self->ecode, state);
+ fmt_state_puts(state, " errnum=");
+ lib9p_errno_format(&self->errnum, state);
fmt_state_puts(state, " }");
}
@@ -7086,7 +7127,18 @@ static void lib9p_msg_Rreaddir_format(struct lib9p_msg_Rreaddir *self, struct fm
lib9p_tag_format(&self->tag, state);
fmt_state_puts(state, " count=");
fmt_state_printf(state, "%"PRIu32, self->count);
- fmt_state_puts(state, " data=<bytedata>");
+ if (is_valid_utf8_without_nul((uint8_t *)self->data, (size_t)self->count)) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat"
+#pragma GCC diagnostic ignored "-Wformat-extra-args"
+ fmt_state_printf(state, " data=%.*q%s",
+ (int)(self->count < 50 ? self->count : 50),
+ (char *)self->data,
+ self->count < 50 ? "" : "...");
+#pragma GCC diagnostic pop
+ } else {
+ fmt_state_puts(state, " data=<bytedata>");
+ }
fmt_state_puts(state, " }");
}
@@ -7302,7 +7354,18 @@ static void lib9p_msg_Rsread_format(struct lib9p_msg_Rsread *self, struct fmt_st
lib9p_tag_format(&self->tag, state);
fmt_state_puts(state, " count=");
fmt_state_printf(state, "%"PRIu32, self->count);
- fmt_state_puts(state, " data=<bytedata>");
+ if (is_valid_utf8_without_nul((uint8_t *)self->data, (size_t)self->count)) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat"
+#pragma GCC diagnostic ignored "-Wformat-extra-args"
+ fmt_state_printf(state, " data=%.*q%s",
+ (int)(self->count < 50 ? self->count : 50),
+ (char *)self->data,
+ self->count < 50 ? "" : "...");
+#pragma GCC diagnostic pop
+ } else {
+ fmt_state_puts(state, " data=<bytedata>");
+ }
fmt_state_puts(state, " }");
}
@@ -7323,7 +7386,18 @@ static void lib9p_msg_Tswrite_format(struct lib9p_msg_Tswrite *self, struct fmt_
fmt_state_puts(state, " ]");
fmt_state_puts(state, " count=");
fmt_state_printf(state, "%"PRIu32, self->count);
- fmt_state_puts(state, " data=<bytedata>");
+ if (is_valid_utf8_without_nul((uint8_t *)self->data, (size_t)self->count)) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat"
+#pragma GCC diagnostic ignored "-Wformat-extra-args"
+ fmt_state_printf(state, " data=%.*q%s",
+ (int)(self->count < 50 ? self->count : 50),
+ (char *)self->data,
+ self->count < 50 ? "" : "...");
+#pragma GCC diagnostic pop
+ } else {
+ fmt_state_puts(state, " data=<bytedata>");
+ }
fmt_state_puts(state, " }");
}
@@ -7345,7 +7419,7 @@ const struct _lib9p_ver_tentry _lib9p_table_ver[LIB9P_VER_NUM] = {
[LIB9P_VER_9P2000] = {.name="9P2000", .min_msg_size=9},
#endif /* CONFIG_9P_ENABLE_9P2000 */
#if CONFIG_9P_ENABLE_9P2000_L
- [LIB9P_VER_9P2000_L] = {.name="9P2000.L", .min_msg_size=9},
+ [LIB9P_VER_9P2000_L] = {.name="9P2000.L", .min_msg_size=11},
#endif /* CONFIG_9P_ENABLE_9P2000_L */
#if CONFIG_9P_ENABLE_9P2000_e
[LIB9P_VER_9P2000_e] = {.name="9P2000.e", .min_msg_size=9},
@@ -7446,7 +7520,6 @@ const struct _lib9p_msg_tentry _lib9p_table_msg[LIB9P_VER_NUM][0x100] = {
_MSG(Rauth),
_MSG(Tattach),
_MSG(Rattach),
- _MSG(Rerror),
_MSG(Tflush),
_MSG(Rflush),
_MSG(Twalk),
@@ -7730,7 +7803,6 @@ const struct _lib9p_recv_tentry _lib9p_table_Rmsg_recv[LIB9P_VER_NUM][0x80] = {
_MSG_RECV(Rversion),
_MSG_RECV(Rauth),
_MSG_RECV(Rattach),
- _MSG_RECV(Rerror),
_MSG_RECV(Rflush),
_MSG_RECV(Rwalk),
_MSG_RECV(Rread),
@@ -7957,7 +8029,6 @@ const struct _lib9p_send_tentry _lib9p_table_Rmsg_send[LIB9P_VER_NUM][0x80] = {
_MSG_SEND(Rversion),
_MSG_SEND(Rauth),
_MSG_SEND(Rattach),
- _MSG_SEND(Rerror),
_MSG_SEND(Rflush),
_MSG_SEND(Rwalk),
_MSG_SEND(Rread),
diff --git a/lib9p/CMakeLists.txt b/lib9p/CMakeLists.txt
index d433a12..949b6d6 100644
--- a/lib9p/CMakeLists.txt
+++ b/lib9p/CMakeLists.txt
@@ -20,10 +20,28 @@ target_link_libraries(lib9p INTERFACE
if (ENABLE_TESTS)
add_subdirectory(tests/test_server)
- add_test(
- NAME "lib9p/runtest"
- COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tests/runtest"
- )
+
+ function(add_lib9p_executable arg_testname)
+ add_executable("${arg_testname}" "tests/${arg_testname}.c")
+ target_link_libraries("${arg_testname}" lib9p)
+ target_include_directories("${arg_testname}" PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}/tests
+ ${CMAKE_CURRENT_SOURCE_DIR}/tests/client_config
+ )
+ endfunction()
+ function(add_lib9p_test arg_testscript)
+ get_filename_component(tmp_basename "${arg_testscript}" "NAME")
+ add_test(
+ NAME "lib9p/${tmp_basename}"
+ COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tests/runtest" "${arg_testscript}" "${CMAKE_CURRENT_SOURCE_DIR}/tests/${tmp_basename}.explog"
+ )
+ endfunction()
+
+ add_lib9p_test("${CMAKE_CURRENT_SOURCE_DIR}/tests/testclient-p9p")
+
+ add_lib9p_executable("testclient-sess")
+ add_lib9p_test("./testclient-sess")
+
add_lib_test(lib9p test_compile)
target_include_directories(test_compile PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_compile_config)
endif()
diff --git a/lib9p/idl/1992-9P0.9p.wip b/lib9p/idl/1992-9P0.9p.wip
index a434ba2..c9432c9 100644
--- a/lib9p/idl/1992-9P0.9p.wip
+++ b/lib9p/idl/1992-9P0.9p.wip
@@ -107,7 +107,7 @@ msg Rnop = "typ[1,val=51] tag[tag,val=0xFFFF]"
msg Tsession = "typ[1,val=52] tag[tag,val=0xFFFF]"
msg Rsession = "typ[1,val=53] tag[tag,val=0xFFFF]"
#msg Terror = "typ[1,val=54] illegal"
-msg Rerror = "typ[1,val=55] tag[tag] ename[errstr]"
+msg Rerror = "typ[1,val=55] tag[tag] errstr[errstr]"
msg Tflush = "typ[1,val=56] tag[tag] oldtag[tag]"
msg Rflush = "typ[1,val=57] tag[tag]"
msg Tattach = "typ[1,val=58] tag[tag] fid[fid] uid[name] aname[name] auth[auth_ticket] 13*(pad[1])" # Pad to allow auth_tickets up to 28 bytes.
diff --git a/lib9p/idl/1996-Styx.9p.wip b/lib9p/idl/1996-Styx.9p.wip
index 3cb3774..143be83 100644
--- a/lib9p/idl/1996-Styx.9p.wip
+++ b/lib9p/idl/1996-Styx.9p.wip
@@ -28,7 +28,7 @@ from ./1992-9P1.9p import tag, fid, qid, name, errstr, o, ch, stat
msg Tnop = "typ[1,val=0] tag[tag,val=0xFFFF]"
msg Rnop = "typ[1,val=1] tag[tag,val=0xFFFF]"
#msg Terror = "typ[1,val=2] illegal"
-msg Rerror = "typ[1,val=3] tag[tag] ename[errstr]"
+msg Rerror = "typ[1,val=3] tag[tag] errstr[errstr]"
msg Tflush = "typ[1,val=4] tag[tag] oldtag[tag]"
msg Rflush = "typ[1,val=5] tag[tag]"
msg Tclone = "typ[1,val=6] tag[tag] fid[fid] newfid[fid]"
diff --git a/lib9p/idl/2002-9P2000.9p b/lib9p/idl/2002-9P2000.9p
index 2b51612..13393c6 100644
--- a/lib9p/idl/2002-9P2000.9p
+++ b/lib9p/idl/2002-9P2000.9p
@@ -137,7 +137,7 @@ msg Rauth = "size[4,val=end-&size] typ[1,val=103] tag[tag] aqid[qid]"
msg Tattach = "size[4,val=end-&size] typ[1,val=104] tag[tag] fid[fid] afid[fid] uname[s] aname[s]"
msg Rattach = "size[4,val=end-&size] typ[1,val=105] tag[tag] qid[qid]"
#msg Terror = "size[4,val=end-&size] typ[1,val=106] tag[tag] illegal"
-msg Rerror = "size[4,val=end-&size] typ[1,val=107] tag[tag] ename[s]"
+msg Rerror = "size[4,val=end-&size] typ[1,val=107] tag[tag] errstr[s]"
msg Tflush = "size[4,val=end-&size] typ[1,val=108] tag[tag] oldtag[2]"
msg Rflush = "size[4,val=end-&size] typ[1,val=109] tag[tag]"
msg Twalk = "size[4,val=end-&size] typ[1,val=110] tag[tag] fid[fid] newfid[fid] nwname[2,max=16] nwname*(wname[s])"
diff --git a/lib9p/idl/2003-9P2000.p9p.9p b/lib9p/idl/2003-9P2000.p9p.9p
index 3f6a524..1d1c307 100644
--- a/lib9p/idl/2003-9P2000.p9p.9p
+++ b/lib9p/idl/2003-9P2000.p9p.9p
@@ -27,7 +27,7 @@ from ./2002-9P2000.9p import *
# e.g. you replace syscall:`open()` with lib9pclient:`fsopen()`).
#
# "Unfortunately", programs in plan9port must deal both with 9P files
-# and native "Unix" files; and need to turn an 9P FID into a native
+# and native "Unix" files; and need to turn a 9P FID into a native
# file descriptor. To do this, the `9pserve` program and lib9pclient
# add an extension call to 9P2000: Topenfd/Ropenfd/fsopenfd().
#
diff --git a/lib9p/idl/2005-9P2000.u.9p b/lib9p/idl/2005-9P2000.u.9p
index 6c2f2dc..1d630f9 100644
--- a/lib9p/idl/2005-9P2000.u.9p
+++ b/lib9p/idl/2005-9P2000.u.9p
@@ -25,7 +25,7 @@ struct stat += "file_extension[s]"
msg Tauth += "n_uid[nuid]"
msg Tattach += "n_uid[nuid]"
-msg Rerror += "errno[errno]"
+msg Rerror += "errnum[errno]"
bitfield dm += "bit 23=DEVICE"
"bit 21=PIPE"
diff --git a/lib9p/idl/2010-9P2000.L.9p b/lib9p/idl/2010-9P2000.L.9p
index d81a15b..b88b537 100644
--- a/lib9p/idl/2010-9P2000.L.9p
+++ b/lib9p/idl/2010-9P2000.L.9p
@@ -8,11 +8,12 @@
# https://github.com/chaos/diod/blob/master/src/libnpfs/protocol.h
version "9P2000.L"
+# low-level types ##############################################################
+
from ./2002-9P2000.9p import tag, fid, s, qt, qid
-from ./2002-9P2000.9p import Rerror
-from ./2002-9P2000.9p import Tversion, Rversion, Tflush, Rflush, Twalk, Rwalk, Tread, Rread, Twrite, Rwrite, Tclunk, Rclunk, Tremove, Rremove
-from ./2005-9P2000.u.9p import nuid, errno, Tauth, Rauth, Tattach, Rattach
+from ./2005-9P2000.u.9p import nuid, errno
+# https://github.com/chaos/diod/issues/35
#num errno += # TODO
num super_magic = 4
@@ -168,8 +169,23 @@ num lock_status = 1
"ERROR=2"
"GRACE=3"
+# 9P2000 Operations (.L subset) ################################################
+
+from ./2002-9P2000.9p import Tversion, Rversion
+from ./2002-9P2000.9p import Tflush, Rflush
+from ./2002-9P2000.9p import Twalk, Rwalk
+from ./2002-9P2000.9p import Tread, Rread, Twrite, Rwrite
+from ./2002-9P2000.9p import Tclunk, Rclunk
+from ./2002-9P2000.9p import Tremove, Rremove
+
+# 9P2000.u Operations (.L subset) ##############################################
+
+from ./2005-9P2000.u.9p import Tattach, Rattach, Tauth, Rauth
+
+# 9P2000.L Operations ##########################################################
+
#msg Tlerror = "size[4,val=end-&size] typ[1,val=6] tag[tag] illegal" # analogous to 106/Terror
-msg Rlerror = "size[4,val=end-&size] typ[1,val=7] tag[tag] ecode[errno]" # analogous to 107/Rerror
+msg Rlerror = "size[4,val=end-&size] typ[1,val=7] tag[tag] errnum[errno]" # analogous to 107/Rerror
msg Tstatfs = "size[4,val=end-&size] typ[1,val=8] tag[tag] fid[fid]"
msg Rstatfs = "size[4,val=end-&size] typ[1,val=9] tag[tag]" # Description | statfs | statvfs
"type[super_magic]" # Type of filesystem | f_type | -
diff --git a/lib9p/include/lib9p/9p.generated.h b/lib9p/include/lib9p/9p.generated.h
index afd89ed..49b4818 100644
--- a/lib9p/include/lib9p/9p.generated.h
+++ b/lib9p/include/lib9p/9p.generated.h
@@ -118,7 +118,11 @@ enum lib9p_msg_type { /* uint8_t */
LIB9P_TYP_Rauth = 103,
LIB9P_TYP_Tattach = 104,
LIB9P_TYP_Rattach = 105,
+#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */
+#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u
LIB9P_TYP_Rerror = 107,
+#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */
+#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u
LIB9P_TYP_Tflush = 108,
LIB9P_TYP_Rflush = 109,
LIB9P_TYP_Twalk = 110,
@@ -967,27 +971,26 @@ struct lib9p_msg_Topenfd {
LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Topenfd, lib9p_msg_Topenfd);
#endif /* CONFIG_9P_ENABLE_9P2000_p9p */
-#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u
+#if CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u
/* LIB9P_VER_9P2000 : min_size = 9 ; exp_size = 36 ; max_size = 65,544 ; max_iov = 2 ; max_copy = 9 */
-/* LIB9P_VER_9P2000_L : min_size = 9 ; exp_size = 36 ; max_size = 65,544 ; max_iov = 2 ; max_copy = 9 */
/* LIB9P_VER_9P2000_e : min_size = 9 ; exp_size = 36 ; max_size = 65,544 ; max_iov = 2 ; max_copy = 9 */
/* LIB9P_VER_9P2000_p9p: min_size = 9 ; exp_size = 36 ; max_size = 65,544 ; max_iov = 2 ; max_copy = 9 */
/* LIB9P_VER_9P2000_u : min_size = 13 ; exp_size = 40 ; max_size = 65,548 ; max_iov = 3 ; max_copy = 13 */
struct lib9p_msg_Rerror {
lib9p_tag_t tag;
- struct lib9p_s ename;
+ struct lib9p_s errstr;
#if CONFIG_9P_ENABLE_9P2000_u
- lib9p_errno_t errno;
+ lib9p_errno_t errnum;
#endif /* CONFIG_9P_ENABLE_9P2000_u */
};
LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rerror, lib9p_msg_Rerror);
-#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_L || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */
+#endif /* CONFIG_9P_ENABLE_9P2000 || CONFIG_9P_ENABLE_9P2000_e || CONFIG_9P_ENABLE_9P2000_p9p || CONFIG_9P_ENABLE_9P2000_u */
#if CONFIG_9P_ENABLE_9P2000_L
/* size = 11 ; max_iov = 1 ; max_copy = 11 */
struct lib9p_msg_Rlerror {
lib9p_tag_t tag;
- lib9p_errno_t ecode;
+ lib9p_errno_t errnum;
};
LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rlerror, lib9p_msg_Rlerror);
diff --git a/lib9p/include/lib9p/srv.h b/lib9p/include/lib9p/srv.h
index 7ad1b19..bb5efb9 100644
--- a/lib9p/include/lib9p/srv.h
+++ b/lib9p/include/lib9p/srv.h
@@ -21,13 +21,24 @@
CR_CHAN_DECLARE(_lib9p_srv_flushch, bool);
-struct lib9p_srv_ctx {
- struct lib9p_ctx basectx;
- uint32_t uid;
+struct lib9p_srv_authinfo {
+ lib9p_nuid_t uid;
struct lib9p_s uname;
BEGIN_PRIVATE(LIB9P_SRV_H);
- _lib9p_srv_flushch_t _flushch;
+ unsigned int refcount;
+ END_PRIVATE(LIB9P_SRV_H);
+};
+
+struct lib9p_srv_ctx {
+ struct lib9p_ctx basectx;
+ struct lib9p_srv_authinfo *authinfo;
+
+ BEGIN_PRIVATE(LIB9P_SRV_H);
+ struct _lib9p_srv_sess *parent_sess;
+ lib9p_tag_t tag;
+ uint8_t *net_bytes;
+ _lib9p_srv_flushch_t flushch;
END_PRIVATE(LIB9P_SRV_H);
};
@@ -132,12 +143,24 @@ LO_INTERFACE(lib9p_srv_dio);
/* main server entrypoints ****************************************************/
-CR_RPC_DECLARE(_lib9p_srv_reqch, struct _lib9p_srv_req *, bool);
+CR_RPC_DECLARE(_lib9p_srv_reqch, struct lib9p_srv_ctx *, bool);
+
+#if CONFIG_9P_ENABLE_9P2000_p9p
+#define net_stream_conn_unix_LO_IFACE \
+ LO_NEST(net_stream_conn) \
+ /** Returns 0 on success, -errno on error. */ \
+ LO_FUNC(int, send_unix_fd, int fd)
+LO_INTERFACE(net_stream_conn_unix);
+#endif
struct lib9p_srv {
/* Things you provide */
void /*TODO*/ (*auth )(struct lib9p_srv_ctx *, struct lib9p_s treename); /* optional */
lo_interface lib9p_srv_file (*rootdir)(struct lib9p_srv_ctx *, struct lib9p_s treename);
+ void (*msglog )(struct lib9p_srv_ctx *, enum lib9p_msg_type, void *hostmsg); /* optional */
+#if CONFIG_9P_ENABLE_9P2000_p9p
+ lo_interface net_stream_conn_unix (*type_assert_unix)(lo_interface net_stream_conn); /* optional */
+#endif
/* For internal use */
BEGIN_PRIVATE(LIB9P_SRV_H);
@@ -148,32 +171,56 @@ struct lib9p_srv {
};
/**
- * In an infinite loop, accept a connection and read messages from it until
- * close; dispatching requests to a pool of lib9p_srv_write_cr() coroutines
- * with the same `srv`.
+ * In a loop loop, accept a connection call lib9p_srv_read() on it.
+ * If LO_CALL(listener, accept) fails, then the function returns.
*
- * Will just close the connection if a T-message has a size[4] <7.
+ * When the last lib9p_srv_accept_and_read_loop() instance for a given
+ * `srv` returns, it will signal all lib9p_srv_worker_loop() calls to
+ * return.
+ *
+ * @param srv: The server configuration and state; has an associated
+ * pool of lib9p_srv_worker_loop() coroutines.
*
- * @param srv: The server configuration and state; has an associated pool of
- * lib9p_srv_write_cr() coroutines.
* @param listener: The listener object to accept connections from.
+ */
+void lib9p_srv_accept_and_read_loop(struct lib9p_srv *srv, lo_interface net_stream_listener listener);
+
+/**
+ * You should probably not call this directly; you should probably use
+ * lib9p_srv_accept_and_read_loop().
+ *
+ * Given an already-established stream connection (i.e. a TCP
+ * connection), service that connection; return once the connection is
+ * closed. Requests are dispatched to a pool of
+ * lib9p_srv_worker_loop() coroutines with the same `srv`.
+ *
+ * Will just close the connection if a T-message has a size[4] <7.
*
- * @errno LINUX_EMSGSIZE T-message has size[4] bigger than max_msg_size
- * @errno LINUX_EDOM Tversion specified an impossibly small max_msg_size
+ * @param srv: The server configuration and state; has an associated
+ * pool of lib9p_srv_worker_loop() coroutines.
+ *
+ * @param conn: The listener object to accept connections from.
+ *
+ * Errors that this function itself may send to clients:
+ *
+ * @errno LINUX_EMSGSIZE T-message has size[4] bigger than max_msg_size
+ * @errno LINUX_EDOM Tversion specified an impossibly small max_msg_size
* @errno LINUX_EOPNOTSUPP T-message has an R-message type, or an unrecognized T-message type
- * @errno LINUX_EBADMSG T-message has wrong size[4] for its content, or has invalid UTF-8
- * @errno LINUX_ERANGE R-message does not fit into max_msg_size
+ * @errno LINUX_EBADMSG T-message has wrong size[4] for its content, or has invalid UTF-8
+ * @errno LINUX_ERANGE R-message does not fit into max_msg_size
*/
-[[noreturn]] void lib9p_srv_read_cr(struct lib9p_srv *srv, lo_interface net_stream_listener listener);
+void lib9p_srv_read(struct lib9p_srv *srv, lo_interface net_stream_conn conn);
+
+
/**
- * Service requests to the `struct lib9p_srv *srv` argument that have been
- * read by lib9p_srv_read_cr().
+ * In a loop, service requests to the `struct lib9p_srv *srv` argument
+ * that have been read by lib9p_srv_accept_and_read_loop() /
+ * lib9p_srv_read(). A "NULL" request causes the function to return.
*
- * @param struct lib9p_srv *srv: The server configuration and state; has an
- * associated pool of lib9p_srv_read_cr()
- * coroutines.
+ * @param srv: The server configuration and state; has an associated
+ * pool of lib9p_srv_accept_and_read_loop() coroutines.
*/
-COROUTINE lib9p_srv_write_cr(void *_srv);
+void lib9p_srv_worker_loop(struct lib9p_srv *srv);
#endif /* _LIB9P_SRV_H_ */
diff --git a/lib9p/map.h b/lib9p/map.h
deleted file mode 100644
index c5eab0f..0000000
--- a/lib9p/map.h
+++ /dev/null
@@ -1,114 +0,0 @@
-/* lib9p/map.h - A really dumb map/dict data structure
- *
- * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-/**
- * `#define` `NAME`, `KEY_T`, `VAL_T`, and `CAP`; then `#include
- * "map.h".
- */
-
-#ifndef NAME
- #error NAME must be defined
-#endif
-#ifndef KEY_T
- #error KEY_T must be defined
-#endif
-#ifndef VAL_T
- #error VAL_T must be defined
-#endif
-#ifndef CAP
- #error CAP must be defined
-#endif
-
-#ifndef MAP_KEY
-#define MAP_KV(TNAME) LM_CAT3(_,TNAME,_kv)
-#define MAP_METHOD(TNAME, MNAME) LM_CAT3(TNAME,_,MNAME)
-#define MAP_FOREACH(m, k, v) \
- for (size_t i = 0; i < LM_ARRAY_LEN((m)->items); i++) \
- if ( ({ k = (m)->items[i].key; v = &(m)->items[i].val; (m)->items[i].set; }) )
-#endif
-
-/* This implementation is just an array that we brute-force search
- * over for a slot. I don't want to use the heap, which means
- * statically-sized maps, and I'm probably going to choose a low
- * static size, so this is fine. */
-
-struct MAP_KV(NAME) {
- bool set;
- KEY_T key;
- VAL_T val;
-};
-
-struct NAME {
- size_t len;
- struct MAP_KV(NAME) items[CAP];
-};
-
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wunused-function"
-
-/**
- * Load an item from the map; return a pointer to the in-map value, or
- * NULL if the item is not in the map.
- */
-static VAL_T *MAP_METHOD(NAME,load)(struct NAME *m, KEY_T k) {
- if (!m->len)
- return NULL;
- for (size_t i = 0; i < LM_ARRAY_LEN(m->items); i++)
- if (m->items[i].set && m->items[i].key == k)
- return &(m->items[i].val);
- return NULL;
-}
-
-/**
- * Store an item into the map, perhaps replacing an existing value.
- * Return a pointer to the in-map value, or NULL if the map is full.
- */
-static VAL_T *MAP_METHOD(NAME,store)(struct NAME *m, KEY_T k, VAL_T v) {
- VAL_T *old = MAP_METHOD(NAME,load)(m, k);
- if (old) {
- *old = v;
- return old;
- }
- if (m->len == LM_ARRAY_LEN(m->items))
- return NULL;
- for (size_t i = 0; i < LM_ARRAY_LEN(m->items); i++)
- if (!m->items[i].set) {
- m->len++;
- m->items[i].set = true;
- m->items[i].key = k;
- m->items[i].val = v;
- return &(m->items[i].val);
- }
- assert_notreached("should have returned from inside for() loop");
-}
-
-/**
- * Delete an item from the map. Returns true if an item was deleted,
- * false if no such item was in the map.
- */
-static bool MAP_METHOD(NAME,del)(struct NAME *m, KEY_T k) {
- if (!m->len)
- return NULL;
- for (size_t i = 0; i < LM_ARRAY_LEN(m->items); i++)
- if (m->items[i].set && m->items[i].key == k) {
- m->items[i].set = false;
- m->len--;
- return true;
- }
- return false;
-}
-
-#pragma GCC diagnostic pop
-
-#undef NAME
-#undef KEY_T
-#undef VAL_T
-#undef CAP
-
-/* Keep the linter happy. */
-#ifndef _LIB9P_MAP_H_
-#define _LIB9P_MAP_H_
-#endif /* _LIB9P_MAP_H_ */
diff --git a/lib9p/protogen/c.py b/lib9p/protogen/c.py
index a6824ce..530bdb6 100644
--- a/lib9p/protogen/c.py
+++ b/lib9p/protogen/c.py
@@ -122,11 +122,19 @@ def gen_c(versions: set[str], typs: list[idl.UserType]) -> str:
ret += f"const struct {c9util.ident('_ver_tentry')} {c9util.ident('_table_ver')}[{c9util.ver_enum('NUM')}] = {{\n"
rerror = next(typ for typ in typs if typ.typname == "Rerror")
for ver in ["unknown", *sorted(versions)]:
+ # XXX: There are good arguments that min_msg_size should be
+ # something larger than rerror.min_size().
+ # srv.c:respond_error() assumes that min_msg_size is
+ # rerror.min_size(); if you do change min_msg_size to
+ # something larger, then be sure to update respond_error().
if ver == "unknown":
min_msg_size = rerror.min_size("9P2000") # SPECIAL (initialization)
else:
ret += cutil.ifdef_push(1, c9util.ver_ifdef({ver}))
min_msg_size = rerror.min_size(ver)
+ if ver == "9P2000.L": # SPECIAL (9P2000.L)
+ rlerror = next(typ for typ in typs if typ.typname == "Rlerror")
+ min_msg_size = rlerror.min_size(ver)
ret += f'\t[{c9util.ver_enum(ver)}] = {{.name="{ver}", .min_msg_size={min_msg_size}}},\n'
ret += cutil.ifdef_pop(0)
ret += "};\n"
diff --git a/lib9p/protogen/c_format.py b/lib9p/protogen/c_format.py
index a1bcbf3..4a809d1 100644
--- a/lib9p/protogen/c_format.py
+++ b/lib9p/protogen/c_format.py
@@ -21,6 +21,19 @@ def bf_numname(typ: idl.Bitfield, num: idl.BitNum, base: str) -> str:
return c9util.Ident(c9util.add_prefix(prefix, base))
+def ext_printf(line: str) -> str:
+ assert line.startswith("\t")
+ assert line.endswith("\n")
+ # It sucks that %v trips -Wformat and -Wformat-extra-args
+ # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47781
+ ret = "#pragma GCC diagnostic push\n"
+ ret += '#pragma GCC diagnostic ignored "-Wformat"\n'
+ ret += '#pragma GCC diagnostic ignored "-Wformat-extra-args"\n'
+ ret += line
+ ret += "#pragma GCC diagnostic pop\n"
+ return ret
+
+
def gen_c_format(versions: set[str], typs: list[idl.UserType]) -> str:
ret = """
/* *_format *******************************************************************/
@@ -93,12 +106,9 @@ def gen_c_format(versions: set[str], typs: list[idl.UserType]) -> str:
ret += "\t\tfmt_state_putchar(state, '0');\n"
ret += "\tfmt_state_putchar(state, ')');\n"
case idl.Struct(typname="s"): # SPECIAL(string)
- ret += "\t/* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47781 */\n"
- ret += "#pragma GCC diagnostic push\n"
- ret += '#pragma GCC diagnostic ignored "-Wformat"\n'
- ret += '#pragma GCC diagnostic ignored "-Wformat-extra-args"\n'
- ret += '\tfmt_state_printf(state, "%.*q", self->len, self->utf8);\n'
- ret += "#pragma GCC diagnostic pop\n"
+ ret += ext_printf(
+ '\tfmt_state_printf(state, "%.*q", self->len, self->utf8);\n'
+ )
case idl.Struct(): # and idl.Message():
if isinstance(typ, idl.Message):
ret += f'\tfmt_state_puts(state, "{typ.typname} {{");\n'
@@ -109,15 +119,24 @@ def gen_c_format(versions: set[str], typs: list[idl.UserType]) -> str:
continue
ret += cutil.ifdef_push(2, c9util.ver_ifdef(member.in_versions))
if member.cnt:
- if member.typ.static_size == 1: # SPECIAL (data)
- ret += f'\tfmt_state_puts(state, " {member.membname}=<bytedata>");\n'
- continue
if isinstance(member.cnt, int):
cnt_str = str(member.cnt)
cnt_typ = "size_t"
else:
cnt_str = f"self->{member.cnt.membname}"
cnt_typ = c9util.typename(member.cnt.typ)
+ if member.typ.static_size == 1: # SPECIAL (data)
+ ret += f"\tif (is_valid_utf8_without_nul((uint8_t *)self->{member.membname}, (size_t){cnt_str})) {{\n"
+ ret += ext_printf(
+ f'\t\tfmt_state_printf(state, " {member.membname}=%.*q%s",\n'
+ f"\t\t\t(int)({cnt_str} < 50 ? {cnt_str} : 50),\n"
+ f"\t\t\t(char *)self->{member.membname},\n"
+ f'\t\t\t{cnt_str} < 50 ? "" : "...");\n'
+ )
+ ret += "\t} else {\n"
+ ret += f'\t\tfmt_state_puts(state, " {member.membname}=<bytedata>");\n'
+ ret += "\t}\n"
+ continue
ret += f'\tfmt_state_puts(state, " {member.membname}=[");\n'
ret += f"\tfor ({cnt_typ} i = 0; i < {cnt_str}; i++) {{\n"
ret += "\t\tif (i)\n"
diff --git a/lib9p/srv.c b/lib9p/srv.c
index b3b9c4c..0a33e03 100644
--- a/lib9p/srv.c
+++ b/lib9p/srv.c
@@ -19,6 +19,7 @@
#include <libcr_ipc/mutex.h>
#include <libmisc/assert.h>
#include <libmisc/endian.h>
+#include <libmisc/map.h>
#include <libhw/generic/net.h>
#define LOG_NAME 9P_SRV
@@ -54,22 +55,31 @@ static_assert(CONFIG_9P_SRV_MAX_HOSTMSG_SIZE <= SSIZE_MAX);
bool lib9p_srv_flush_requested(struct lib9p_srv_ctx *ctx) {
assert(ctx);
- return _lib9p_srv_flushch_can_send(&ctx->_flushch);
+ return _lib9p_srv_flushch_can_send(&ctx->flushch);
}
void lib9p_srv_acknowledge_flush(struct lib9p_srv_ctx *ctx) {
assert(ctx);
- assert(_lib9p_srv_flushch_can_send(&ctx->_flushch));
+ assert(_lib9p_srv_flushch_can_send(&ctx->flushch));
lib9p_error(&ctx->basectx, LINUX_ECANCELED, "request canceled by flush");
- _lib9p_srv_flushch_send(&ctx->_flushch, true);
+ _lib9p_srv_flushch_send(&ctx->flushch, true);
}
/* structs ********************************************************************/
+enum srv_filetype {
+ SRV_FILETYPE_FILE,
+ SRV_FILETYPE_DIR,
+ SRV_FILETYPE_AUTH,
+};
+
+/* path *****************************************/
+
typedef typeof( ((struct lib9p_qid){}).path ) srv_path_t;
struct srv_pathinfo {
lo_interface lib9p_srv_file file;
+ enum srv_filetype type;
srv_path_t parent_dir;
/* References from other srv_pathinfos (via .parent_dir) or
@@ -79,21 +89,18 @@ struct srv_pathinfo {
unsigned int io_refcount;
};
-#define NAME pathmap
-#define KEY_T srv_path_t
-#define VAL_T struct srv_pathinfo
-/* ( naive ) + ( working space for walk() ) */
-#define CAP ( (CONFIG_9P_SRV_MAX_FIDS*CONFIG_9P_SRV_MAX_DEPTH) + (CONFIG_9P_SRV_MAX_REQS*2) )
-#include "map.h"
+/* fid ******************************************/
#define FIDFLAG_OPEN_R (1<<0)
#define FIDFLAG_OPEN_W (1<<1)
#define FIDFLAG_RCLOSE (1<<2)
#define FIDFLAG_OPEN (FIDFLAG_OPEN_R|FIDFLAG_OPEN_W)
-struct _srv_fidinfo {
+struct srv_fidinfo {
srv_path_t path;
+ struct lib9p_srv_authinfo *authinfo;
uint8_t flags;
+ enum srv_filetype type;
union {
struct {
lo_interface lib9p_srv_fio io;
@@ -103,30 +110,24 @@ struct _srv_fidinfo {
size_t idx;
uint64_t off;
} dir;
+ struct {
+ struct lib9p_s aname;
+ bool completed;
+ } auth;
};
};
-#define NAME fidmap
-#define KEY_T lib9p_fid_t
-#define VAL_T struct _srv_fidinfo
-#define CAP CONFIG_9P_SRV_MAX_FIDS
-#include "map.h"
-
-#define NAME reqmap
-#define KEY_T lib9p_tag_t
-#define VAL_T struct _lib9p_srv_req *
-#define CAP CONFIG_9P_SRV_MAX_REQS
-#include "map.h"
-
-/* The hierarchy of concepts is:
+/* contexts **************************************
+ *
+ * The hierarchy of contexts is:
*
* server -> connection -> session -> request
*
*/
-/* struct _srv_srv {} is defined in <lib9p/srv.h> */
+/* struct lib9p_srv {} is defined in <lib9p/srv.h> */
-struct _srv_conn {
+struct srv_conn {
/* immutable */
struct lib9p_srv *parent_srv;
lo_interface net_stream_conn fd;
@@ -135,34 +136,223 @@ struct _srv_conn {
cr_mutex_t writelock;
};
-struct _srv_sess {
+#define srv_sess _lib9p_srv_sess
+MAP_DECLARE(srv_pathmap, srv_path_t, struct srv_pathinfo);
+MAP_DECLARE(srv_fidmap, lib9p_fid_t, struct srv_fidinfo);
+MAP_DECLARE(srv_reqmap, lib9p_tag_t, struct lib9p_srv_ctx *);
+struct srv_sess {
/* immutable */
- struct _srv_conn *parent_conn;
+ struct srv_conn *parent_conn;
enum lib9p_version version;
uint32_t max_msg_size;
- uint32_t rerror_overhead;
/* mutable */
bool initialized;
bool closing;
- struct pathmap paths; /* srv_path_t => lib9p_srv_file + metadata */
- struct fidmap fids; /* lib9p_fid_t => lib9p_srv_{fio,dio} + metadata */
- struct reqmap reqs; /* lib9p_tag_t => *_lib9p_srv_req */
+ struct srv_pathmap paths; /* srv_path_t => `lib9p_srv_file` + metadata */
+ struct srv_fidmap fids; /* lib9p_fid_t => `lib9p_srv_{fio,dio}` + metadata */
+ struct srv_reqmap reqs; /* lib9p_tag_t => `struct srv_req *` */
};
-struct _lib9p_srv_req {
- /* immutable */
- struct _srv_sess *parent_sess;
- uint16_t tag;
- uint8_t *net_bytes;
- /* mutable */
- struct lib9p_srv_ctx ctx;
-};
+#define srv_req lib9p_srv_ctx /* struct lib9p_srv_ctx {} is defined in <lib9p/srv.h> */
+
+/* utilities for the above types **********************************************/
+
+static inline enum srv_filetype srv_qid_filetype(struct lib9p_qid qid) {
+ if (qid.type & LIB9P_QT_AUTH)
+ return SRV_FILETYPE_AUTH;
+ if (qid.type & LIB9P_QT_DIR)
+ return SRV_FILETYPE_DIR;
+ return SRV_FILETYPE_FILE;
+}
+
+static inline bool srv_check_perm(struct srv_req *ctx, struct lib9p_stat *stat, uint8_t action) {
+ assert(ctx);
+ assert(stat);
+ assert(action);
+
+ /* TODO actually check user and group instead of just assuming "other". */
+ uint8_t mode = (uint8_t)(stat->file_mode & 07);
+
+ return mode & action;
+}
+
+struct lib9p_srv_authinfo *srv_authinfo_new(struct lib9p_s uname, lib9p_nuid_t uid) {
+ struct lib9p_srv_authinfo *ret = malloc(sizeof(struct lib9p_srv_authinfo) + uname.len);
+ if (!ret)
+ return NULL;
+ ret->uid = uid;
+ ret->uname.len = uname.len;
+ ret->uname.utf8 = (void *)&ret[1];
+ memcpy(ret->uname.utf8, uname.utf8, uname.len);
+ ret->refcount = 1;
+ return ret;
+}
+
+struct lib9p_srv_authinfo *srv_authinfo_decref(struct lib9p_srv_authinfo *authinfo) {
+ assert(authinfo);
+ assert(authinfo->refcount);
+ authinfo->refcount--;
+ if (!authinfo->refcount) {
+ free(authinfo);
+ return NULL;
+ }
+ return authinfo;
+}
+
+struct lib9p_srv_authinfo *srv_authinfo_incref(struct lib9p_srv_authinfo *authinfo) {
+ assert(authinfo);
+ authinfo->refcount++;
+ return authinfo;
+}
+
+/**
+ * Ensures that `file` is saved into the pathmap, and increments the
+ * gc_refcount by 1 (for presumptive insertion into the fidmap).
+ * parent_path's gc_refcount is also incremented as appropriate.
+ *
+ * Returns a pointer to the stored pathinfo.
+ */
+static inline struct srv_pathinfo *srv_path_save(struct srv_req *ctx,
+ lo_interface lib9p_srv_file file,
+ srv_path_t parent_path) {
+ assert(ctx);
+ assert(!LO_IS_NULL(file));
+
+ struct lib9p_qid qid = LO_CALL(file, qid);
+ struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, qid.path);
+ if (pathinfo)
+ assert(LO_EQ(pathinfo->file, file));
+ else {
+ pathinfo = map_store(&ctx->parent_sess->paths, qid.path,
+ (struct srv_pathinfo){
+ .file = file,
+ .type = srv_qid_filetype(qid),
+ .parent_dir = parent_path,
+ .gc_refcount = 0,
+ .io_refcount = 0,
+ });
+ assert(pathinfo);
+ if (parent_path != qid.path) {
+ struct srv_pathinfo *parent = map_load(&ctx->parent_sess->paths, parent_path);
+ assert(parent);
+ parent->gc_refcount++;
+ }
+ }
+ pathinfo->gc_refcount++;
+ return pathinfo;
+}
+
+/**
+ * Decrement the path's gc_refcount, and trigger garbage collection as
+ * appropriate.
+ */
+static inline void srv_path_decref(struct srv_req *ctx, srv_path_t path) {
+ assert(ctx);
+
+ struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, path);
+ assert(pathinfo);
+ pathinfo->gc_refcount--;
+ if (pathinfo->gc_refcount == 0) {
+ if (pathinfo->parent_dir != path)
+ srv_path_decref(ctx, pathinfo->parent_dir);
+ LO_CALL(pathinfo->file, free);
+ map_del(&ctx->parent_sess->paths, path);
+ }
+}
+
+static inline void srv_fid_del(struct srv_req *ctx, lib9p_fid_t fid, bool remove) {
+ struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, fid);
+ assert(fidinfo);
+ if (fidinfo->flags & FIDFLAG_RCLOSE)
+ remove = true;
+ struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path);
+ assert(pathinfo);
+
+ if (remove) {
+ if (pathinfo->parent_dir == fidinfo->path) {
+ lib9p_errorf(&ctx->basectx,
+ LINUX_EBUSY, "cannot remove root");
+ goto clunk;
+ }
+ struct srv_pathinfo *parent = map_load(&ctx->parent_sess->paths, pathinfo->parent_dir);
+ assert(parent);
+ struct lib9p_stat parent_stat = LO_CALL(parent->file, stat, ctx);
+ if (!srv_check_perm(ctx, &parent_stat, 0b010)) {
+ lib9p_error(&ctx->basectx,
+ LINUX_EACCES, "remove: you do not have write permission on the parent directory");
+ goto clunk;
+ }
+ LO_CALL(pathinfo->file, remove, ctx);
+ }
+
+ clunk:
+ if (fidinfo->flags & FIDFLAG_OPEN) {
+ switch (fidinfo->type) {
+ case SRV_FILETYPE_DIR:
+ LO_CALL(fidinfo->dir.io, iofree);
+ break;
+ case SRV_FILETYPE_FILE:
+ LO_CALL(fidinfo->file.io, iofree);
+ break;
+ case SRV_FILETYPE_AUTH:
+ assert_notreached("TODO: auth not yet implemented");
+ break;
+ }
+ pathinfo->io_refcount--;
+ }
+ fidinfo->authinfo = srv_authinfo_decref(fidinfo->authinfo);
+ srv_path_decref(ctx, fidinfo->path);
+ map_del(&ctx->parent_sess->fids, fid);
+}
+
+/**
+ * Store fid as pointing to pathinfo. Assumes that
+ * pathinfo->gc_refcount has already been incremented; does *not*
+ * decrement it on failure.
+ */
+static struct srv_fidinfo *srv_fid_store(struct srv_req *ctx, lib9p_fid_t fid, struct srv_pathinfo *pathinfo, bool overwrite) {
+ assert(ctx);
+ assert(fid != LIB9P_FID_NOFID);
+ assert(pathinfo);
+
+ struct lib9p_qid qid = LO_CALL(pathinfo->file, qid);
+
+ if (map_load(&ctx->parent_sess->fids, fid)) {
+ if (overwrite) {
+ srv_fid_del(ctx, fid, false);
+ } else {
+ lib9p_error(&ctx->basectx,
+ LINUX_EBADF, "FID already in use");
+ return NULL;
+ }
+ }
+ struct srv_fidinfo *fidinfo = map_store(&ctx->parent_sess->fids, fid, (struct srv_fidinfo){
+ .path = qid.path,
+ .type = srv_qid_filetype(qid),
+ .authinfo = srv_authinfo_incref(ctx->authinfo),
+ });
+ assert(fidinfo);
+ return fidinfo;
+}
/* base utilities *************************************************************/
-#define nonrespond_errorf errorf
+static void srv_msglog(struct srv_req *req, enum lib9p_msg_type typ, void *hostmsg) {
+ struct lib9p_srv *srv = req->parent_sess->parent_conn->parent_srv;
+ if (srv->msglog) {
+ srv->msglog(req, typ, hostmsg);
+ return;
+ }
+ /* It sucks that %v trips -Wformat and -Wformat-extra-args
+ * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47781 */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat"
+#pragma GCC diagnostic ignored "-Wformat-extra-args"
+ infof("%c %v", typ % 2 ? '<' : '>', lo_box_lib9p_msg_as_fmt_formatter(&req->basectx, typ, hostmsg));
+#pragma GCC diagnostic pop
+}
-static ssize_t write_Rmsg(struct _lib9p_srv_req *req, struct lib9p_Rmsg_send_buf *resp) {
+static ssize_t srv_write_Rmsg(struct srv_req *req, struct lib9p_Rmsg_send_buf *resp) {
ssize_t r;
cr_mutex_lock(&req->parent_sess->parent_conn->writelock);
r = io_writev(req->parent_sess->parent_conn->fd, resp->iov, resp->iov_cnt);
@@ -170,59 +360,62 @@ static ssize_t write_Rmsg(struct _lib9p_srv_req *req, struct lib9p_Rmsg_send_buf
return r;
}
-static void respond_error(struct _lib9p_srv_req *req) {
+#define srv_nonrespond_errorf errorf
+
+static void srv_respond_error(struct srv_req *req) {
#if CONFIG_9P_ENABLE_9P2000_u
- assert(req->ctx.basectx.err_num);
+ assert(req->basectx.err_num);
#endif
- assert(req->ctx.basectx.err_msg[0]);
+ assert(req->basectx.err_msg[0]);
ssize_t r;
struct lib9p_msg_Rerror host = {
.tag = req->tag,
- .ename = lib9p_strn(req->ctx.basectx.err_msg,
- CONFIG_9P_MAX_ERR_SIZE),
+ .errstr = lib9p_strn(req->basectx.err_msg,
+ CONFIG_9P_MAX_ERR_SIZE),
#if CONFIG_9P_ENABLE_9P2000_u
- .errno = req->ctx.basectx.err_num,
+ .errnum = req->basectx.err_num,
#endif
};
- struct _srv_sess *sess = req->parent_sess;
+ struct srv_sess *sess = req->parent_sess;
+
+ /* XXX: This assumes that a version's min_msg_size is the
+ * Rerror overhead. That's true for the current
+ * implementation of protogen, but is a sneaky assumption. */
+ uint32_t overhead = lib9p_version_min_msg_size(sess->version);
/* Truncate the error-string if necessary to avoid needing to
* return LINUX_ERANGE. */
- if (((uint32_t)host.ename.len) + sess->rerror_overhead > sess->max_msg_size)
- host.ename.len = sess->max_msg_size - sess->rerror_overhead;
+ if (((uint32_t)host.errstr.len) + overhead > sess->max_msg_size)
+ host.errstr.len = sess->max_msg_size - overhead;
struct lib9p_Rmsg_send_buf net;
- lib9p_Rmsg_marshal(&req->ctx.basectx,
+ lib9p_Rmsg_marshal(&req->basectx,
LIB9P_TYP_Rerror, &host,
&net);
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wformat"
-#pragma GCC diagnostic ignored "-Wformat-extra-args"
- infof("< %v", lo_box_lib9p_msg_Rerror_as_fmt_formatter(&host));
-#pragma GCC diagnostic pop
- r = write_Rmsg(req, &net);
+ srv_msglog(req, LIB9P_TYP_Rerror, &host);
+ r = srv_write_Rmsg(req, &net);
if (r < 0)
- nonrespond_errorf("write: %s", net_strerror(-r));
+ srv_nonrespond_errorf("write: %s", net_strerror(-r));
}
/* read coroutine *************************************************************/
-static bool read_exactly(lo_interface net_stream_conn fd, uint8_t *buf, size_t goal, size_t *done) {
+static inline bool srv_read_exactly(lo_interface net_stream_conn fd, uint8_t *buf, size_t goal, size_t *done) {
assert(buf);
assert(goal);
assert(done);
while (*done < goal) {
ssize_t r = io_read(fd, &buf[*done], goal - *done);
if (r < 0) {
- nonrespond_errorf("read: %s", net_strerror(-r));
+ srv_nonrespond_errorf("read: %s", net_strerror(-r));
return true;
} else if (r == 0) {
if (*done != 0)
- nonrespond_errorf("read: unexpected EOF");
+ srv_nonrespond_errorf("read: unexpected EOF");
return true;
}
*done += r;
@@ -230,112 +423,132 @@ static bool read_exactly(lo_interface net_stream_conn fd, uint8_t *buf, size_t g
return false;
}
-static void handle_message(struct _lib9p_srv_req *ctx);
-
-[[noreturn]] void lib9p_srv_read_cr(struct lib9p_srv *srv, lo_interface net_stream_listener listener) {
+void lib9p_srv_accept_and_read_loop(struct lib9p_srv *srv, lo_interface net_stream_listener listener) {
assert(srv);
assert(srv->rootdir);
assert(!LO_IS_NULL(listener));
srv->readers++;
- uint32_t initial_rerror_overhead = lib9p_version_min_msg_size(LIB9P_VER_unknown);
-
for (;;) {
- struct _srv_conn conn = {
- .parent_srv = srv,
- .fd = LO_CALL(listener, accept),
- .reader = cr_getcid(),
- };
- if (LO_IS_NULL(conn.fd)) {
- nonrespond_errorf("accept: error");
+ lo_interface net_stream_conn conn = LO_CALL(listener, accept);
+ if (LO_IS_NULL(conn)) {
+ srv_nonrespond_errorf("accept: error");
srv->readers--;
if (srv->readers == 0)
while (srv->writers > 0)
_lib9p_srv_reqch_send_req(&srv->_reqch, NULL);
- cr_exit();
+ return;
}
+ lib9p_srv_read(srv, conn);
+ }
+}
- struct _srv_sess sess = {
- .parent_conn = &conn,
- .version = LIB9P_VER_unknown,
- .max_msg_size = CONFIG_9P_SRV_MAX_MSG_SIZE,
- .rerror_overhead = initial_rerror_overhead,
- .initialized = false,
- };
- for (;;) {
- nextmsg:
- /* Read the message. */
- size_t done = 0;
- uint8_t buf[7];
- if (read_exactly(conn.fd, buf, 4, &done))
- goto close;
- size_t goal = uint32le_decode(buf);
- if (goal < 7) {
- nonrespond_errorf("T-message is impossibly small");
- goto close;
- }
- if (read_exactly(conn.fd, buf, 7, &done))
- goto close;
- struct _lib9p_srv_req req = {
- .parent_sess = &sess,
- .tag = uint16le_decode(&buf[5]),
- .net_bytes = buf,
- .ctx = {
- .basectx = {
- .version = sess.version,
- .max_msg_size = sess.max_msg_size,
- },
- },
- };
- if (goal > sess.max_msg_size) {
- lib9p_errorf(&req.ctx.basectx,
- LINUX_EMSGSIZE, "T-message larger than %s limit (%zu > %"PRIu32")",
- sess.initialized ? "negotiated" : "server",
- goal,
- sess.max_msg_size);
- respond_error(&req);
- goto nextmsg;
- }
- req.net_bytes = malloc(goal);
- assert(req.net_bytes);
- memcpy(req.net_bytes, buf, done);
- if (read_exactly(conn.fd, req.net_bytes, goal, &done)) {
- free(req.net_bytes);
- goto close;
- }
+static void handle_message(struct srv_req *ctx);
+
+void lib9p_srv_read(struct lib9p_srv *srv, lo_interface net_stream_conn _conn) {
+ assert(srv);
+ assert(srv->rootdir);
+ assert(!LO_IS_NULL(_conn));
- /* Handle the message... */
- if (req.net_bytes[4] == LIB9P_TYP_Tversion)
- /* ...in this coroutine for Tversion, */
- handle_message(&req);
- else
- /* ...but usually in another coroutine. */
- _lib9p_srv_reqch_send_req(&srv->_reqch, &req);
+ struct srv_conn conn = {
+ .parent_srv = srv,
+ .fd = _conn,
+ .reader = cr_getcid(),
+ };
+ struct srv_sess sess = {
+ .parent_conn = &conn,
+ .version = LIB9P_VER_unknown,
+ .max_msg_size = CONFIG_9P_SRV_MAX_MSG_SIZE,
+ .initialized = false,
+ };
+ for (;;) {
+ /* Read the message. */
+ size_t done = 0;
+ uint8_t buf[7];
+ if (srv_read_exactly(conn.fd, buf, 4, &done))
+ break;
+ size_t goal = uint32le_decode(buf);
+ if (goal < 7) {
+ srv_nonrespond_errorf("T-message is impossibly small");
+ break;
}
- close:
- if (sess.reqs.len == 0)
- io_close(conn.fd);
- else {
- io_close_read(conn.fd);
- sess.closing = true;
- cr_pause_and_yield();
- assert(sess.reqs.len == 0);
- io_close_write(conn.fd);
+ if (srv_read_exactly(conn.fd, buf, 7, &done))
+ break;
+ struct srv_req req = {
+ .basectx = {
+ .version = sess.version,
+ .max_msg_size = sess.max_msg_size,
+ },
+
+ .parent_sess = &sess,
+ .tag = uint16le_decode(&buf[5]),
+ .net_bytes = buf,
+ };
+ if (goal > sess.max_msg_size) {
+ lib9p_errorf(&req.basectx,
+ LINUX_EMSGSIZE, "T-message larger than %s limit (%zu > %"PRIu32")",
+ sess.initialized ? "negotiated" : "server",
+ goal,
+ sess.max_msg_size);
+ srv_respond_error(&req);
+ continue;
}
+ req.net_bytes = malloc(goal);
+ assert(req.net_bytes);
+ memcpy(req.net_bytes, buf, done);
+ if (srv_read_exactly(conn.fd, req.net_bytes, goal, &done)) {
+ free(req.net_bytes);
+ break;
+ }
+
+ /* Handle the message... */
+ if (req.net_bytes[4] == LIB9P_TYP_Tversion)
+ /* ...in this coroutine for Tversion, */
+ handle_message(&req);
+ else
+ /* ...but usually in another coroutine. */
+ _lib9p_srv_reqch_send_req(&srv->_reqch, &req);
}
+ if (map_len(&sess.reqs) == 0)
+ io_close(conn.fd);
+ else {
+ io_close_read(conn.fd);
+ sess.closing = true;
+ cr_pause_and_yield();
+ assert(map_len(&sess.reqs) == 0);
+ io_close_write(conn.fd);
+ }
+
+ assert(map_len(&sess.reqs) == 0);
+ map_free(&sess.reqs);
+
+ MAP_FOREACH(&sess.fids, fid, fidinfo) {
+ struct srv_req req = {
+ .basectx = {
+ .version = sess.version,
+ .max_msg_size = sess.max_msg_size,
+ },
+ .parent_sess = &sess,
+ };
+ srv_fid_del(&req, fid, false);
+ if (lib9p_ctx_has_error(&req.basectx))
+ errorf("clunk: %.*s", CONFIG_9P_MAX_ERR_SIZE, req.basectx.err_msg);
+ }
+ map_free(&sess.fids);
+
+ assert(map_len(&sess.paths) == 0);
+ map_free(&sess.paths);
}
/* write coroutine ************************************************************/
-COROUTINE lib9p_srv_write_cr(void *_srv) {
- struct _lib9p_srv_req req;
+void lib9p_srv_worker_loop(struct lib9p_srv *srv) {
+ struct srv_req req;
_lib9p_srv_reqch_req_t rpc_handle;
- struct lib9p_srv *srv = _srv;
assert(srv);
assert(srv->rootdir);
- cr_begin();
srv->writers++;
@@ -345,13 +558,14 @@ COROUTINE lib9p_srv_write_cr(void *_srv) {
if (!rpc_handle.req) {
srv->writers--;
_lib9p_srv_reqch_send_resp(rpc_handle, 0);
- cr_exit();
+ return;
}
/* Copy the request from the reader coroutine's
* stack to our stack. */
req = *rpc_handle.req;
/* Record that we have it. */
- reqmap_store(&req.parent_sess->reqs, req.tag, &req);
+ struct srv_req **reqpp = map_store(&req.parent_sess->reqs, req.tag, &req);
+ assert(reqpp && *reqpp == &req);
/* Notify the reader coroutine that we're done with
* its data. */
_lib9p_srv_reqch_send_resp(rpc_handle, 0);
@@ -360,18 +574,16 @@ COROUTINE lib9p_srv_write_cr(void *_srv) {
handle_message(&req);
/* Release resources. ****************************************/
- while (_lib9p_srv_flushch_can_send(&req.ctx._flushch))
- _lib9p_srv_flushch_send(&req.ctx._flushch, false);
- reqmap_del(&req.parent_sess->reqs, req.tag);
- if (req.parent_sess->closing && !req.parent_sess->reqs.len)
+ while (_lib9p_srv_flushch_can_send(&req.flushch))
+ _lib9p_srv_flushch_send(&req.flushch, false);
+ map_del(&req.parent_sess->reqs, req.tag);
+ if (req.parent_sess->closing && !map_len(&req.parent_sess->reqs))
cr_unpause(req.parent_sess->parent_conn->reader);
}
-
- cr_end();
}
#define _HANDLER_PROTO(typ) \
- static void handle_T##typ(struct _lib9p_srv_req *, \
+ static void handle_T##typ(struct srv_req *, \
struct lib9p_msg_T##typ *, \
struct lib9p_msg_R##typ *)
_HANDLER_PROTO(version);
@@ -387,13 +599,16 @@ _HANDLER_PROTO(clunk);
_HANDLER_PROTO(remove);
_HANDLER_PROTO(stat);
_HANDLER_PROTO(wstat);
+#if CONFIG_9P_ENABLE_9P2000_p9p
+_HANDLER_PROTO(openfd);
+#endif
#if CONFIG_9P_ENABLE_9P2000_e
_HANDLER_PROTO(session);
_HANDLER_PROTO(sread);
_HANDLER_PROTO(swrite);
#endif
-typedef void (*tmessage_handler)(struct _lib9p_srv_req *, void *, void *);
+typedef void (*tmessage_handler)(struct srv_req *, void *, void *);
static tmessage_handler tmessage_handlers[0x100] = {
[LIB9P_TYP_Tversion] = (tmessage_handler)handle_Tversion,
@@ -409,6 +624,9 @@ static tmessage_handler tmessage_handlers[0x100] = {
[LIB9P_TYP_Tremove] = (tmessage_handler)handle_Tremove,
[LIB9P_TYP_Tstat] = (tmessage_handler)handle_Tstat,
[LIB9P_TYP_Twstat] = (tmessage_handler)handle_Twstat,
+#if CONFIG_9P_ENABLE_9P2000_p9p
+ [LIB9P_TYP_Topenfd] = (tmessage_handler)handle_Topenfd,
+#endif
#if CONFIG_9P_ENABLE_9P2000_e
[LIB9P_TYP_Tsession] = (tmessage_handler)handle_Tsession,
[LIB9P_TYP_Tsread] = (tmessage_handler)handle_Tsread,
@@ -416,172 +634,54 @@ static tmessage_handler tmessage_handlers[0x100] = {
#endif
};
-static void handle_message(struct _lib9p_srv_req *ctx) {
+static void handle_message(struct srv_req *ctx) {
uint8_t *host_req = NULL;
uint8_t host_resp[CONFIG_9P_SRV_MAX_HOSTMSG_SIZE];
/* Unmarshal it. */
- ssize_t host_size = lib9p_Tmsg_validate(&ctx->ctx.basectx, ctx->net_bytes);
+ ssize_t host_size = lib9p_Tmsg_validate(&ctx->basectx, ctx->net_bytes);
if (host_size < 0)
goto write;
host_req = calloc(1, host_size);
assert(host_req);
enum lib9p_msg_type typ;
- lib9p_Tmsg_unmarshal(&ctx->ctx.basectx, ctx->net_bytes,
- &typ, host_req);
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wformat"
-#pragma GCC diagnostic ignored "-Wformat-extra-args"
- infof("> %v", lo_box_lib9p_msg_as_fmt_formatter(&ctx->ctx.basectx, typ, host_req));
-#pragma GCC diagnostic pop
+ lib9p_Tmsg_unmarshal(&ctx->basectx, ctx->net_bytes,
+ &typ, host_req);
+ srv_msglog(ctx, typ, host_req);
/* Handle it. */
tmessage_handlers[typ](ctx, (void *)host_req, (void *)host_resp);
write:
- if (lib9p_ctx_has_error(&ctx->ctx.basectx))
- respond_error(ctx);
+ if (lib9p_ctx_has_error(&ctx->basectx))
+ srv_respond_error(ctx);
else {
struct lib9p_Rmsg_send_buf net_resp;
- if (lib9p_Rmsg_marshal(&ctx->ctx.basectx,
+ if (lib9p_Rmsg_marshal(&ctx->basectx,
typ+1, host_resp,
&net_resp))
goto write;
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wformat"
-#pragma GCC diagnostic ignored "-Wformat-extra-args"
- infof("< %v", lo_box_lib9p_msg_as_fmt_formatter(&ctx->ctx.basectx, typ+1, &host_resp));
-#pragma GCC diagnostic pop
- write_Rmsg(ctx, &net_resp);
+ srv_msglog(ctx, typ+1, &host_resp);
+ srv_write_Rmsg(ctx, &net_resp);
}
if (host_req)
free(host_req);
free(ctx->net_bytes);
}
-#define util_handler_common(ctx, req, resp) do { \
- assert(ctx); \
- assert(req); \
- assert(resp); \
- resp->tag = req->tag; \
- } while (0)
-
-static inline bool srv_util_check_perm(struct _lib9p_srv_req *ctx, struct lib9p_stat *stat, uint8_t action) {
- assert(ctx);
- assert(stat);
- assert(action);
-
- /* TODO actually check user and group instead of just assuming "other". */
- uint8_t mode = (uint8_t)(stat->file_mode & 07);
-
- return mode & action;
-}
-
-/**
- * Ensures that `file` is saved into the pathmap, and increments the
- * gc_refcount by 1 (for presumptive insertion into the fidmap).
- * parent_path's gc_refcount is also incremented as appropriate.
- *
- * Returns a pointer to the stored pathinfo.
- */
-static inline struct srv_pathinfo *srv_util_pathsave(struct _lib9p_srv_req *ctx,
- lo_interface lib9p_srv_file file,
- srv_path_t parent_path) {
- assert(ctx);
- assert(!LO_IS_NULL(file));
-
- struct lib9p_qid qid = LO_CALL(file, qid);
- struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, qid.path);
- if (pathinfo)
- assert(LO_EQ(pathinfo->file, file));
- else {
- pathinfo = pathmap_store(&ctx->parent_sess->paths, qid.path,
- (struct srv_pathinfo){
- .file = file,
- .parent_dir = parent_path,
- .gc_refcount = 0,
- .io_refcount = 0,
- });
- assert(pathinfo);
- if (parent_path != qid.path) {
- struct srv_pathinfo *parent = pathmap_load(&ctx->parent_sess->paths, parent_path);
- assert(parent);
- parent->gc_refcount++;
- }
- }
- pathinfo->gc_refcount++;
- return pathinfo;
-}
-
-/**
- * Decrement the path's gc_refcount, and trigger garbage collection as
- * appropriate.
- */
-static inline void srv_util_pathfree(struct _lib9p_srv_req *ctx, srv_path_t path) {
- assert(ctx);
-
- struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, path);
- assert(pathinfo);
- pathinfo->gc_refcount--;
- if (pathinfo->gc_refcount == 0) {
- if (pathinfo->parent_dir != path)
- srv_util_pathfree(ctx, pathinfo->parent_dir);
- LO_CALL(pathinfo->file, free);
- pathmap_del(&ctx->parent_sess->paths, path);
- }
-}
-
-static inline bool srv_util_pathisdir(struct srv_pathinfo *pathinfo) {
- assert(pathinfo);
- return LO_CALL(pathinfo->file, qid).type & LIB9P_QT_DIR;
-}
-
-/**
- * Store fid as pointing to pathinfo. Assumes that
- * pathinfo->gc_refcount has already been incremented; does *not*
- * decrement it on failure.
- */
-static inline struct _srv_fidinfo *srv_util_fidsave(struct _lib9p_srv_req *ctx, lib9p_fid_t fid, struct srv_pathinfo *pathinfo, bool overwrite) {
- assert(ctx);
- assert(fid != LIB9P_FID_NOFID);
- assert(pathinfo);
-
- struct lib9p_qid qid = LO_CALL(pathinfo->file, qid);
-
- struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, fid);
- if (fidinfo) {
- if (overwrite) {
- struct srv_pathinfo *old_pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path);
- assert(old_pathinfo);
- if (srv_util_pathisdir(old_pathinfo))
- LO_CALL(fidinfo->dir.io, iofree);
- else
- LO_CALL(fidinfo->file.io, iofree);
- srv_util_pathfree(ctx, fidinfo->path);
- } else {
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EBADF, "FID already in use");
- return NULL;
- }
- } else {
- fidinfo = fidmap_store(&ctx->parent_sess->fids, fid, (struct _srv_fidinfo){});
- if (!fidinfo) {
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EMFILE, "too many open files");
- return NULL;
- }
- }
- *fidinfo = (struct _srv_fidinfo){
- .path = qid.path,
- };
- return fidinfo;
-}
+/* handle_T* ******************************************************************/
+#define srv_handler_common(ctx, req, resp) do { \
+ assert(ctx); \
+ assert(req); \
+ assert(resp); \
+ resp->tag = req->tag; \
+ } while (0)
-static void handle_Tversion(struct _lib9p_srv_req *ctx,
+static void handle_Tversion(struct srv_req *ctx,
struct lib9p_msg_Tversion *req,
struct lib9p_msg_Rversion *resp) {
- util_handler_common(ctx, req, resp);
+ srv_handler_common(ctx, req, resp);
enum lib9p_version version = LIB9P_VER_unknown;
@@ -594,6 +694,11 @@ static void handle_Tversion(struct _lib9p_srv_req *ctx,
'0' <= req->version.utf8[5] && req->version.utf8[5] <= '9' &&
(req->version.len == 6 || req->version.utf8[6] == '.')) {
version = LIB9P_VER_9P2000;
+#if CONFIG_9P_ENABLE_9P2000_p9p
+ struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv;
+ if (srv->type_assert_unix && !LO_IS_NULL(srv->type_assert_unix(ctx->parent_sess->parent_conn->fd)))
+ version = LIB9P_VER_9P2000_p9p;
+#endif
#if CONFIG_9P_ENABLE_9P2000_u
if (lib9p_str_eq(lib9p_str_sliceleft(req->version, 6), lib9p_str(".u")))
version = LIB9P_VER_9P2000_u;
@@ -606,37 +711,38 @@ static void handle_Tversion(struct _lib9p_srv_req *ctx,
uint32_t min_msg_size = lib9p_version_min_msg_size(version);
if (req->max_msg_size < min_msg_size) {
- lib9p_errorf(&ctx->ctx.basectx,
+ lib9p_errorf(&ctx->basectx,
LINUX_EDOM, "requested max_msg_size is less than minimum for %s (%"PRIu32" < %"PRIu32")",
lib9p_version_str(version), req->max_msg_size, min_msg_size);
return;
}
resp->version = lib9p_str((char *)lib9p_version_str(version)); /* cast to discard "const" qualifier */
+#if CONFIG_9P_ENABLE_9P2000_p9p
+ if (version == LIB9P_VER_9P2000_p9p)
+ resp->version = lib9p_str("9P2000");
+#endif
resp->max_msg_size = (CONFIG_9P_SRV_MAX_MSG_SIZE < req->max_msg_size)
? CONFIG_9P_SRV_MAX_MSG_SIZE
: req->max_msg_size;
/* Close the old session. */
- if (ctx->parent_sess->reqs.len) {
+ if (map_len(&ctx->parent_sess->reqs)) {
/* Flush all in-progress requests, and wait for them
* to finish. */
- struct cr_select_arg *list = alloca(sizeof(struct cr_select_arg) * ctx->parent_sess->reqs.len);
- while (ctx->parent_sess->reqs.len) {
- uint16_t tag [[gnu::unused]];
- struct _lib9p_srv_req **reqpp;
+ struct cr_select_arg *list = alloca(sizeof(struct cr_select_arg) * map_len(&ctx->parent_sess->reqs));
+ while (map_len(&ctx->parent_sess->reqs)) {
size_t i = 0;
bool flushed;
MAP_FOREACH(&ctx->parent_sess->reqs, tag, reqpp) {
- list[i] = CR_SELECT_RECV(&((*reqpp)->ctx._flushch), &flushed);
+ list[i] = CR_SELECT_RECV(&((*reqpp)->flushch), &flushed);
}
+ assert(i == map_len(&ctx->parent_sess->reqs));
cr_select_v(i, list);
}
}
- if (ctx->parent_sess->fids.len) {
+ if (map_len(&ctx->parent_sess->fids)) {
/* Close all FIDs. */
- uint32_t fid;
- struct _srv_fidinfo *fidinfo [[gnu::unused]];
MAP_FOREACH(&ctx->parent_sess->fids, fid, fidinfo) {
handle_Tclunk(ctx,
&(struct lib9p_msg_Tclunk){.fid = fid},
@@ -647,132 +753,138 @@ static void handle_Tversion(struct _lib9p_srv_req *ctx,
/* Replace the old session with the new session. */
ctx->parent_sess->version = version;
ctx->parent_sess->max_msg_size = resp->max_msg_size;
- ctx->parent_sess->rerror_overhead = min_msg_size;
}
-static void handle_Tauth(struct _lib9p_srv_req *ctx,
+static void handle_Tauth(struct srv_req *ctx,
struct lib9p_msg_Tauth *req,
struct lib9p_msg_Rauth *resp) {
- util_handler_common(ctx, req, resp);
+ srv_handler_common(ctx, req, resp);
- ctx->ctx.uid = req->n_uid;
- ctx->ctx.uname = req->uname;
struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv;
-
if (!srv->auth) {
- lib9p_error(&ctx->ctx.basectx,
+ lib9p_error(&ctx->basectx,
LINUX_EOPNOTSUPP, "authentication not required");
return;
}
- srv->auth(&ctx->ctx, req->aname);
- lib9p_error(&ctx->ctx.basectx,
+ ctx->authinfo = srv_authinfo_new(req->uname, req->n_uid);
+
+ srv->auth(ctx, req->aname);
+
+ lib9p_error(&ctx->basectx,
LINUX_EOPNOTSUPP, "TODO: auth not implemented");
+
+ if (lib9p_ctx_has_error(&ctx->basectx))
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
-static void handle_Tattach(struct _lib9p_srv_req *ctx,
+static void handle_Tattach(struct srv_req *ctx,
struct lib9p_msg_Tattach *req,
struct lib9p_msg_Rattach *resp) {
- util_handler_common(ctx, req, resp);
+ srv_handler_common(ctx, req, resp);
- ctx->ctx.uid = req->n_uid;
- ctx->ctx.uname = req->uname;
- struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv;
+ if (req->fid == LIB9P_FID_NOFID) {
+ lib9p_error(&ctx->basectx,
+ LINUX_EBADF, "cannot assign to NOFID");
+ return;
+ }
+ struct lib9p_srv *srv = ctx->parent_sess->parent_conn->parent_srv;
if (srv->auth) {
- /*
- struct lib9p_srv_filehandle *fh = fidmap_get(req->afid);
- if (!fh)
- lib9p_error(&ctx->ctx.basectx,
+ struct srv_fidinfo *afid = map_load(&ctx->parent_sess->fids, req->afid);
+ if (!afid)
+ lib9p_error(&ctx->basectx,
LINUX_EACCES, "FID provided as auth-file is not a valid FID");
- else if (fh->type != FH_AUTH)
- lib9p_error(&ctx->ctx.basectx,
+ else if (afid->type != SRV_FILETYPE_AUTH)
+ lib9p_error(&ctx->basectx,
LINUX_EACCES, "FID provided as auth-file is not an auth-file");
- else if (!lib9p_str_eq(fh->data.auth.uname, req->uname))
- lib9p_errorf(&ctx->ctx.basectx,
+ else if (!lib9p_str_eq(afid->authinfo->uname, req->uname))
+ lib9p_errorf(&ctx->basectx,
LINUX_EACCES, "FID provided as auth-file is for user=\"%.*s\" and cannot be used for user=\"%.*s\"",
- fh->data.auth.uname.len, fh->data.auth.uname.utf8,
+ afid->authinfo->uname.len, afid->authinfo->uname.utf8,
req->uname.len, req->uname.utf8);
- else if (!lib9p_str_eq(fh->data.auth.aname, req->aname))
- lib9p_errorf(&ctx->ctx.basectx,
+#if CONFIG_9P_ENABLE_9P2000_u
+ else if (afid->authinfo->uid != req->n_uid)
+ lib9p_errorf(&ctx->basectx,
+ LINUX_EACCES, "FID provided as auth-file is for user=%"PRIu32" and cannot be used for user=%"PRIu32,
+ afid->authinfo->uid, req->n_uid);
+#endif
+ else if (!lib9p_str_eq(afid->auth.aname, req->aname))
+ lib9p_errorf(&ctx->basectx,
LINUX_EACCES, "FID provided as auth-file is for tree=\"%.*s\" and cannot be used for tree=\"%.*s\"",
- fh->data.auth.aname.len, fh->data.auth.aname.utf8,
+ afid->auth.aname.len, afid->auth.aname.utf8,
req->aname.len, req->aname.utf8);
- else if (!fh->data.auth.authenticated)
- lib9p_error(&ctx->ctx.basectx,
+ else if (!afid->auth.completed)
+ lib9p_error(&ctx->basectx,
LINUX_EACCES, "FID provided as auth-file has not completed authentication");
- fh->refcount--;
- if (lib9p_ctx_has_error(&ctx->ctx))
+ if (lib9p_ctx_has_error(&ctx->basectx))
return;
- */
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EOPNOTSUPP, "TODO: auth not (yet?) implemented");
- return;
+ ctx->authinfo = srv_authinfo_incref(afid->authinfo);
} else {
if (req->afid != LIB9P_FID_NOFID) {
- lib9p_error(&ctx->ctx.basectx,
+ lib9p_error(&ctx->basectx,
LINUX_EACCES, "FID provided as auth-file, but no auth-file is required");
return;
}
- }
-
- if (req->fid == LIB9P_FID_NOFID) {
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EBADF, "cannot assign to NOFID");
- return;
+ ctx->authinfo = srv_authinfo_new(req->uname, req->n_uid);
}
/* 1. File object */
- lo_interface lib9p_srv_file root_file = srv->rootdir(&ctx->ctx, req->aname);
- assert(LO_IS_NULL(root_file) == lib9p_ctx_has_error(&ctx->ctx.basectx));
- if (lib9p_ctx_has_error(&ctx->ctx.basectx))
+ lo_interface lib9p_srv_file root_file = srv->rootdir(ctx, req->aname);
+ assert(LO_IS_NULL(root_file) == lib9p_ctx_has_error(&ctx->basectx));
+ if (lib9p_ctx_has_error(&ctx->basectx)) {
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
return;
+ }
struct lib9p_qid root_qid = LO_CALL(root_file, qid);
- assert(root_qid.type & LIB9P_QT_DIR);
+ assert(srv_qid_filetype(root_qid) == SRV_FILETYPE_DIR);
/* 2. pathinfo */
- struct srv_pathinfo *root_pathinfo = srv_util_pathsave(ctx, root_file, root_qid.path);
+ struct srv_pathinfo *root_pathinfo = srv_path_save(ctx, root_file, root_qid.path);
/* 3. fidinfo */
- if (!srv_util_fidsave(ctx, req->fid, root_pathinfo, false)) {
- srv_util_pathfree(ctx, root_qid.path);
+ if (!srv_fid_store(ctx, req->fid, root_pathinfo, false)) {
+ srv_path_decref(ctx, root_qid.path);
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
return;
}
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
resp->qid = root_qid;
return;
}
-static void handle_Tflush(struct _lib9p_srv_req *ctx,
+static void handle_Tflush(struct srv_req *ctx,
struct lib9p_msg_Tflush *req,
struct lib9p_msg_Rflush *resp) {
- util_handler_common(ctx, req, resp);
+ srv_handler_common(ctx, req, resp);
- struct _lib9p_srv_req **oldreqp = reqmap_load(&ctx->parent_sess->reqs, req->oldtag);
+ struct srv_req **oldreqp = map_load(&ctx->parent_sess->reqs, req->oldtag);
if (oldreqp)
- _lib9p_srv_flushch_recv(&((*oldreqp)->ctx._flushch));
+ _lib9p_srv_flushch_recv(&((*oldreqp)->flushch));
}
-static void handle_Twalk(struct _lib9p_srv_req *ctx,
+static void handle_Twalk(struct srv_req *ctx,
struct lib9p_msg_Twalk *req,
struct lib9p_msg_Rwalk *resp) {
- util_handler_common(ctx, req, resp);
+ srv_handler_common(ctx, req, resp);
if (req->newfid == LIB9P_FID_NOFID) {
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EBADF, "cannot assign to NOFID");
- return;
+ lib9p_error(&ctx->basectx,
+ LINUX_EBADF, "cannot assign to NOFID");
+ return;
}
- struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid);
+ struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid);
if (!fidinfo) {
- lib9p_errorf(&ctx->ctx.basectx,
+ lib9p_errorf(&ctx->basectx,
LINUX_EBADF, "bad file number %"PRIu32, req->fid);
return;
}
+ ctx->authinfo = srv_authinfo_incref(fidinfo->authinfo);
- struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path);
+ struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path);
assert(pathinfo);
pathinfo->gc_refcount++;
@@ -780,115 +892,110 @@ static void handle_Twalk(struct _lib9p_srv_req *ctx,
for (resp->nwqid = 0; resp->nwqid < req->nwname; resp->nwqid++) {
struct srv_pathinfo *new_pathinfo;
if (lib9p_str_eq(req->wname[resp->nwqid], lib9p_str(".."))) {
- new_pathinfo = pathmap_load(&ctx->parent_sess->paths, pathinfo->parent_dir);
+ new_pathinfo = map_load(&ctx->parent_sess->paths, pathinfo->parent_dir);
assert(new_pathinfo);
new_pathinfo->gc_refcount++;
} else {
- if (!srv_util_pathisdir(pathinfo)) {
- lib9p_error(&ctx->ctx.basectx,
+ if (pathinfo->type != SRV_FILETYPE_DIR) {
+ lib9p_error(&ctx->basectx,
LINUX_ENOTDIR, "not a directory");
break;
}
- lo_interface lib9p_srv_file member_file = LO_CALL(pathinfo->file, dwalk, &ctx->ctx, req->wname[resp->nwqid]);
- assert(LO_IS_NULL(member_file) == lib9p_ctx_has_error(&ctx->ctx.basectx));
- if (lib9p_ctx_has_error(&ctx->ctx.basectx))
+ lo_interface lib9p_srv_file member_file = LO_CALL(pathinfo->file, dwalk, ctx, req->wname[resp->nwqid]);
+ assert(LO_IS_NULL(member_file) == lib9p_ctx_has_error(&ctx->basectx));
+ if (lib9p_ctx_has_error(&ctx->basectx))
break;
- new_pathinfo = srv_util_pathsave(ctx, member_file, LO_CALL(pathinfo->file, qid).path);
+ new_pathinfo = srv_path_save(ctx, member_file, LO_CALL(pathinfo->file, qid).path);
}
- if (srv_util_pathisdir(new_pathinfo)) {
- struct lib9p_stat stat = LO_CALL(new_pathinfo->file, stat, &ctx->ctx);
- if (lib9p_ctx_has_error(&ctx->ctx.basectx))
+ if (new_pathinfo->type == SRV_FILETYPE_DIR) {
+ struct lib9p_stat stat = LO_CALL(new_pathinfo->file, stat, ctx);
+ if (lib9p_ctx_has_error(&ctx->basectx))
break;
lib9p_stat_assert(stat);
- if (!srv_util_check_perm(ctx, &stat, 0b001)) {
- lib9p_error(&ctx->ctx.basectx,
+ if (!srv_check_perm(ctx, &stat, 0b001)) {
+ lib9p_error(&ctx->basectx,
LINUX_EACCES, "you do not have execute permission on that directory");
- srv_util_pathfree(ctx, LO_CALL(new_pathinfo->file, qid).path);
+ srv_path_decref(ctx, LO_CALL(new_pathinfo->file, qid).path);
break;
}
}
resp->wqid[resp->nwqid] = LO_CALL(new_pathinfo->file, qid);
- srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path);
+ srv_path_decref(ctx, LO_CALL(pathinfo->file, qid).path);
pathinfo = new_pathinfo;
}
if (resp->nwqid == req->nwname) {
- if (req->newfid == req->fid) {
- if (srv_util_pathisdir(pathinfo))
- LO_CALL(fidinfo->dir.io, iofree);
- else
- LO_CALL(fidinfo->file.io, iofree);
- fidinfo->flags = 0;
- }
- if (!srv_util_fidsave(ctx, req->newfid, pathinfo, req->newfid == req->fid))
- srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path);
+ if (!srv_fid_store(ctx, req->newfid, pathinfo, req->newfid == req->fid))
+ srv_path_decref(ctx, LO_CALL(pathinfo->file, qid).path);
} else {
- assert(lib9p_ctx_has_error(&ctx->ctx.basectx));
- srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path);
+ assert(lib9p_ctx_has_error(&ctx->basectx));
+ srv_path_decref(ctx, LO_CALL(pathinfo->file, qid).path);
if (resp->nwqid > 0)
- lib9p_ctx_clear_error(&ctx->ctx.basectx);
+ lib9p_ctx_clear_error(&ctx->basectx);
}
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
-static void handle_Topen(struct _lib9p_srv_req *ctx,
+static void handle_Topen(struct srv_req *ctx,
struct lib9p_msg_Topen *req,
struct lib9p_msg_Ropen *resp) {
- util_handler_common(ctx, req, resp);
+ srv_handler_common(ctx, req, resp);
/* Check that the FID is valid for this. */
- struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid);
+ struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid);
if (!fidinfo) {
- lib9p_errorf(&ctx->ctx.basectx,
+ lib9p_errorf(&ctx->basectx,
LINUX_EBADF, "bad file number %"PRIu32, req->fid);
return;
}
if (fidinfo->flags & FIDFLAG_OPEN) {
- lib9p_error(&ctx->ctx.basectx,
+ lib9p_error(&ctx->basectx,
LINUX_EALREADY, "FID is already open");
return;
}
- struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path);
- assert(pathinfo);
- if (srv_util_pathisdir(pathinfo)) {
+ if (fidinfo->type == SRV_FILETYPE_DIR) {
if ( ((req->mode & LIB9P_O_MODE_MASK) != LIB9P_O_MODE_READ) ||
(req->mode & LIB9P_O_TRUNC) ||
(req->mode & LIB9P_O_RCLOSE) ) {
- lib9p_error(&ctx->ctx.basectx,
+ lib9p_error(&ctx->basectx,
LINUX_EISDIR, "directories cannot be written, executed, truncated, or removed-on-close");
return;
}
}
+ ctx->authinfo = srv_authinfo_incref(fidinfo->authinfo);
/* Variables. */
- lib9p_o_t reqmode = req->mode;
- uint8_t fidflags = fidinfo->flags;
+ lib9p_o_t reqmode = req->mode;
+ uint8_t fidflags = fidinfo->flags;
+ struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path);
+ assert(pathinfo);
/* Check permissions. */
if (reqmode & LIB9P_O_RCLOSE) {
- struct srv_pathinfo *parent = pathmap_load(&ctx->parent_sess->paths, pathinfo->parent_dir);
+ struct srv_pathinfo *parent = map_load(&ctx->parent_sess->paths, pathinfo->parent_dir);
assert(parent);
- struct lib9p_stat parent_stat = LO_CALL(parent->file, stat, &ctx->ctx);
- if (lib9p_ctx_has_error(&ctx->ctx.basectx))
- return;
+ struct lib9p_stat parent_stat = LO_CALL(parent->file, stat, ctx);
+ if (lib9p_ctx_has_error(&ctx->basectx))
+ goto topen_return;
lib9p_stat_assert(parent_stat);
- if (!srv_util_check_perm(ctx, &parent_stat, 0b010)) {
- lib9p_error(&ctx->ctx.basectx,
+ if (!srv_check_perm(ctx, &parent_stat, 0b010)) {
+ lib9p_error(&ctx->basectx,
LINUX_EACCES, "permission denied to remove-on-close");
- return;
+ goto topen_return;
}
fidflags |= FIDFLAG_RCLOSE;
}
- struct lib9p_stat stat = LO_CALL(pathinfo->file, stat, &ctx->ctx);
- if (lib9p_ctx_has_error(&ctx->ctx.basectx))
- return;
+ struct lib9p_stat stat = LO_CALL(pathinfo->file, stat, ctx);
+ if (lib9p_ctx_has_error(&ctx->basectx))
+ goto topen_return;
lib9p_stat_assert(stat);
if ((stat.file_mode & LIB9P_DM_EXCL) && pathinfo->io_refcount) {
- lib9p_error(&ctx->ctx.basectx,
+ lib9p_error(&ctx->basectx,
LINUX_EEXIST, "exclusive file is already opened");
- return;
+ goto topen_return;
}
if (stat.file_mode & LIB9P_DM_APPEND)
reqmode = reqmode & ~LIB9P_O_TRUNC;
@@ -912,33 +1019,42 @@ static void handle_Topen(struct _lib9p_srv_req *ctx,
rd = true;
break;
}
- if (!srv_util_check_perm(ctx, &stat, perm_bits)) {
- lib9p_error(&ctx->ctx.basectx,
+ if (!srv_check_perm(ctx, &stat, perm_bits)) {
+ lib9p_error(&ctx->basectx,
LINUX_EACCES, "permission denied");
- return;
+ goto topen_return;
}
/* Actually make the call. */
uint32_t iounit;
struct lib9p_qid qid;
- if (srv_util_pathisdir(pathinfo)) {
- fidinfo->dir.io = LO_CALL(pathinfo->file, dopen, &ctx->ctx);
- assert(LO_IS_NULL(fidinfo->dir.io) == lib9p_ctx_has_error(&ctx->ctx.basectx));
- if (lib9p_ctx_has_error(&ctx->ctx.basectx))
- return;
+ switch (pathinfo->type) {
+ case SRV_FILETYPE_DIR:
+ fidinfo->dir.io = LO_CALL(pathinfo->file, dopen, ctx);
+ assert(LO_IS_NULL(fidinfo->dir.io) == lib9p_ctx_has_error(&ctx->basectx));
+ if (lib9p_ctx_has_error(&ctx->basectx))
+ goto topen_return;
fidinfo->dir.idx = 0;
fidinfo->dir.off = 0;
qid = LO_CALL(fidinfo->dir.io, qid);
iounit = 0;
- } else {
- fidinfo->file.io = LO_CALL(pathinfo->file, fopen, &ctx->ctx,
+ break;
+ case SRV_FILETYPE_FILE:
+ fidinfo->file.io = LO_CALL(pathinfo->file, fopen, ctx,
rd, wr,
reqmode & LIB9P_O_TRUNC);
- assert(LO_IS_NULL(fidinfo->file.io) == lib9p_ctx_has_error(&ctx->ctx.basectx));
- if (lib9p_ctx_has_error(&ctx->ctx.basectx))
- return;
+ assert(LO_IS_NULL(fidinfo->file.io) == lib9p_ctx_has_error(&ctx->basectx));
+ if (lib9p_ctx_has_error(&ctx->basectx))
+ goto topen_return;
qid = LO_CALL(fidinfo->file.io, qid);
iounit = LO_CALL(fidinfo->file.io, iounit);
+ break;
+ case SRV_FILETYPE_AUTH:
+ assert_notreached("TODO: auth not yet implemented");
+ break;
+ default:
+ assert_notreached("invalid srv_filetype");
+ break;
}
/* Success. */
@@ -950,43 +1066,43 @@ static void handle_Topen(struct _lib9p_srv_req *ctx,
fidinfo->flags = fidflags;
resp->qid = qid;
resp->iounit = iounit;
+ topen_return:
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
-static void handle_Tcreate(struct _lib9p_srv_req *ctx,
+static void handle_Tcreate(struct srv_req *ctx,
struct lib9p_msg_Tcreate *req,
struct lib9p_msg_Rcreate *resp) {
- util_handler_common(ctx, req, resp);
+ srv_handler_common(ctx, req, resp);
- lib9p_error(&ctx->ctx.basectx,
+ lib9p_error(&ctx->basectx,
LINUX_EOPNOTSUPP, "create not (yet?) implemented");
}
-static void handle_Tread(struct _lib9p_srv_req *ctx,
+static void handle_Tread(struct srv_req *ctx,
struct lib9p_msg_Tread *req,
struct lib9p_msg_Rread *resp) {
- util_handler_common(ctx, req, resp);
+ srv_handler_common(ctx, req, resp);
/* TODO: serialize simultaneous reads to the same FID */
/* Check that the FID is valid for this. */
- struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid);
+ struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid);
if (!fidinfo) {
- lib9p_errorf(&ctx->ctx.basectx,
+ lib9p_errorf(&ctx->basectx,
LINUX_EBADF, "bad file number %"PRIu32, req->fid);
return;
}
if (!(fidinfo->flags & FIDFLAG_OPEN_R)) {
- lib9p_error(&ctx->ctx.basectx,
+ lib9p_error(&ctx->basectx,
LINUX_EINVAL, "FID not open for reading");
return;
}
- /* Variables. */
- struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path);
- assert(pathinfo);
-
/* Do it. */
- if (srv_util_pathisdir(pathinfo)) {
+ ctx->authinfo = srv_authinfo_incref(fidinfo->authinfo);
+ switch (fidinfo->type) {
+ case SRV_FILETYPE_DIR:
/* Translate byte-offset to object-index. */
size_t idx;
if (req->offset == 0)
@@ -994,176 +1110,170 @@ static void handle_Tread(struct _lib9p_srv_req *ctx,
else if (req->offset == fidinfo->dir.off)
idx = fidinfo->dir.idx;
else {
- lib9p_errorf(&ctx->ctx.basectx,
+ lib9p_errorf(&ctx->basectx,
LINUX_EINVAL, "invalid offset (must be 0 or %"PRIu64"): %"PRIu64,
fidinfo->dir.off, req->offset);
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
return;
}
/* Do it. */
resp->data = (char *)(&resp[1]);
- size_t num = LO_CALL(fidinfo->dir.io, dread, &ctx->ctx, (uint8_t *)resp->data, req->count, idx);
+ size_t num = LO_CALL(fidinfo->dir.io, dread, ctx, (uint8_t *)resp->data, req->count, idx);
/* Translate object-count back to byte-count. */
uint32_t len = 0;
for (size_t i = 0; i < num; i++) {
uint32_t i_len;
- lib9p_stat_validate(&ctx->ctx.basectx, req->count, &((uint8_t *)resp->data)[len], &i_len, NULL);
+ lib9p_stat_validate(&ctx->basectx, req->count, &((uint8_t *)resp->data)[len], &i_len, NULL);
len += i_len;
}
resp->count = len;
/* Remember. */
fidinfo->dir.idx = idx+num;
fidinfo->dir.off = req->offset + len;
- } else {
+ break;
+ case SRV_FILETYPE_FILE:
struct iovec iov;
- LO_CALL(fidinfo->file.io, pread, &ctx->ctx, req->count, req->offset, &iov);
- if (!lib9p_ctx_has_error(&ctx->ctx.basectx)) {
+ LO_CALL(fidinfo->file.io, pread, ctx, req->count, req->offset, &iov);
+ if (!lib9p_ctx_has_error(&ctx->basectx)) {
resp->count = iov.iov_len;
resp->data = iov.iov_base;
if (resp->count > req->count)
resp->count = req->count;
}
+ break;
+ case SRV_FILETYPE_AUTH:
+ assert_notreached("TODO: auth not yet implemented");
+ break;
}
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
-static void handle_Twrite(struct _lib9p_srv_req *ctx,
+static void handle_Twrite(struct srv_req *ctx,
struct lib9p_msg_Twrite *req,
struct lib9p_msg_Rwrite *resp) {
- util_handler_common(ctx, req, resp);
+ srv_handler_common(ctx, req, resp);
/* TODO: serialize simultaneous writes to the same FID */
/* Check that the FID is valid for this. */
- struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid);
+ struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid);
if (!fidinfo) {
- lib9p_errorf(&ctx->ctx.basectx,
+ lib9p_errorf(&ctx->basectx,
LINUX_EBADF, "bad file number %"PRIu32, req->fid);
return;
}
if (!(fidinfo->flags & FIDFLAG_OPEN_W)) {
- lib9p_error(&ctx->ctx.basectx,
+ lib9p_error(&ctx->basectx,
LINUX_EINVAL, "FID not open for writing");
return;
}
- /* Variables. */
- struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path);
- assert(pathinfo);
-
/* Do it. */
- resp->count = LO_CALL(fidinfo->file.io, pwrite, &ctx->ctx, req->data, req->count, req->offset);
+ ctx->authinfo = srv_authinfo_incref(fidinfo->authinfo);
+ resp->count = LO_CALL(fidinfo->file.io, pwrite, ctx, req->data, req->count, req->offset);
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
-static void clunkremove(struct _lib9p_srv_req *ctx, lib9p_fid_t fid, bool remove) {
- struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, fid);
+static void handle_Tclunk(struct srv_req *ctx,
+ struct lib9p_msg_Tclunk *req,
+ struct lib9p_msg_Rclunk *resp) {
+ srv_handler_common(ctx, req, resp);
+
+ struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid);
if (!fidinfo) {
- lib9p_errorf(&ctx->ctx.basectx,
- LINUX_EBADF, "bad file number %"PRIu32, fid);
+ lib9p_errorf(&ctx->basectx,
+ LINUX_EBADF, "bad file number %"PRIu32, req->fid);
return;
}
- if (fidinfo->flags & FIDFLAG_RCLOSE)
- remove = true;
- struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path);
- assert(pathinfo);
-
- if (remove) {
- if (pathinfo->parent_dir == fidinfo->path) {
- lib9p_errorf(&ctx->ctx.basectx,
- LINUX_EBUSY, "cannot remove root");
- goto clunk;
- }
- struct srv_pathinfo *parent = pathmap_load(&ctx->parent_sess->paths, pathinfo->parent_dir);
- assert(parent);
- struct lib9p_stat parent_stat = LO_CALL(parent->file, stat, &ctx->ctx);
- if (!srv_util_check_perm(ctx, &parent_stat, 0b010)) {
- lib9p_error(&ctx->ctx.basectx,
- LINUX_EACCES, "you do not have write permission on the parent directory");
- goto clunk;
- }
- LO_CALL(pathinfo->file, remove, &ctx->ctx);
- }
-
- clunk:
- if (fidinfo->flags & FIDFLAG_OPEN) {
- if (srv_util_pathisdir(pathinfo))
- LO_CALL(fidinfo->dir.io, iofree);
- else
- LO_CALL(fidinfo->file.io, iofree);
- pathinfo->io_refcount--;
- }
- srv_util_pathfree(ctx, LO_CALL(pathinfo->file, qid).path);
- fidmap_del(&ctx->parent_sess->fids, fid);
-}
-
-static void handle_Tclunk(struct _lib9p_srv_req *ctx,
- struct lib9p_msg_Tclunk *req,
- struct lib9p_msg_Rclunk *resp) {
- util_handler_common(ctx, req, resp);
- clunkremove(ctx, req->fid, false);
+ ctx->authinfo = srv_authinfo_incref(fidinfo->authinfo);
+ srv_fid_del(ctx, req->fid, false);
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
-
-static void handle_Tremove(struct _lib9p_srv_req *ctx,
+static void handle_Tremove(struct srv_req *ctx,
struct lib9p_msg_Tremove *req,
struct lib9p_msg_Rremove *resp) {
- util_handler_common(ctx, req, resp);
+ srv_handler_common(ctx, req, resp);
+
+ struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid);
+ if (!fidinfo) {
+ lib9p_errorf(&ctx->basectx,
+ LINUX_EBADF, "bad file number %"PRIu32, req->fid);
+ return;
+ }
- clunkremove(ctx, req->fid, true);
+ ctx->authinfo = srv_authinfo_incref(fidinfo->authinfo);
+ srv_fid_del(ctx, req->fid, true);
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
-static void handle_Tstat(struct _lib9p_srv_req *ctx,
+static void handle_Tstat(struct srv_req *ctx,
struct lib9p_msg_Tstat *req,
struct lib9p_msg_Rstat *resp) {
- util_handler_common(ctx, req, resp);
+ srv_handler_common(ctx, req, resp);
- struct _srv_fidinfo *fidinfo = fidmap_load(&ctx->parent_sess->fids, req->fid);
+ struct srv_fidinfo *fidinfo = map_load(&ctx->parent_sess->fids, req->fid);
if (!fidinfo) {
- lib9p_errorf(&ctx->ctx.basectx,
+ lib9p_errorf(&ctx->basectx,
LINUX_EBADF, "bad file number %"PRIu32, req->fid);
return;
}
- struct srv_pathinfo *pathinfo = pathmap_load(&ctx->parent_sess->paths, fidinfo->path);
+ struct srv_pathinfo *pathinfo = map_load(&ctx->parent_sess->paths, fidinfo->path);
assert(pathinfo);
- resp->stat = LO_CALL(pathinfo->file, stat, &ctx->ctx);
- if (!lib9p_ctx_has_error(&ctx->ctx.basectx))
+ ctx->authinfo = srv_authinfo_incref(fidinfo->authinfo);
+ resp->stat = LO_CALL(pathinfo->file, stat, ctx);
+ if (!lib9p_ctx_has_error(&ctx->basectx))
lib9p_stat_assert(resp->stat);
+ ctx->authinfo = srv_authinfo_decref(ctx->authinfo);
}
-static void handle_Twstat(struct _lib9p_srv_req *ctx,
+static void handle_Twstat(struct srv_req *ctx,
struct lib9p_msg_Twstat *req,
struct lib9p_msg_Rwstat *resp) {
- util_handler_common(ctx, req, resp);
+ srv_handler_common(ctx, req, resp);
- lib9p_error(&ctx->ctx.basectx,
+ lib9p_error(&ctx->basectx,
LINUX_EOPNOTSUPP, "wstat not (yet?) implemented");
}
+#if CONFIG_9P_ENABLE_9P2000_p9p
+static void handle_Topenfd(struct srv_req *ctx,
+ struct lib9p_msg_Topenfd *req,
+ struct lib9p_msg_Ropenfd *resp) {
+ srv_handler_common(ctx, req, resp);
+
+ lib9p_error(&ctx->basectx,
+ LINUX_EOPNOTSUPP, "openfd not (yet?) implemented");
+}
+#endif
+
#if CONFIG_9P_ENABLE_9P2000_e
-static void handle_Tsession(struct _lib9p_srv_req *ctx,
+static void handle_Tsession(struct srv_req *ctx,
struct lib9p_msg_Tsession *req,
struct lib9p_msg_Rsession *resp) {
- util_handler_common(ctx, req, resp);
+ srv_handler_common(ctx, req, resp);
- lib9p_error(&ctx->ctx.basectx,
+ lib9p_error(&ctx->basectx,
LINUX_EOPNOTSUPP, "session not (yet?) implemented");
}
-static void handle_Tsread(struct _lib9p_srv_req *ctx,
+static void handle_Tsread(struct srv_req *ctx,
struct lib9p_msg_Tsread *req,
struct lib9p_msg_Rsread *resp) {
- util_handler_common(ctx, req, resp);
+ srv_handler_common(ctx, req, resp);
- lib9p_error(&ctx->ctx.basectx,
+ lib9p_error(&ctx->basectx,
LINUX_EOPNOTSUPP, "sread not (yet?) implemented");
}
-static void handle_Tswrite(struct _lib9p_srv_req *ctx,
+static void handle_Tswrite(struct srv_req *ctx,
struct lib9p_msg_Tswrite *req,
struct lib9p_msg_Rswrite *resp) {
- util_handler_common(ctx, req, resp);
+ srv_handler_common(ctx, req, resp);
- lib9p_error(&ctx->ctx.basectx,
+ lib9p_error(&ctx->basectx,
LINUX_EOPNOTSUPP, "swrite not (yet?) implemented");
}
#endif
diff --git a/lib9p/tests/client_config/config.h b/lib9p/tests/client_config/config.h
new file mode 100644
index 0000000..65ee9de
--- /dev/null
+++ b/lib9p/tests/client_config/config.h
@@ -0,0 +1,40 @@
+/* config.h - Compile-time configuration for lib9p test clients
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _CONFIG_H_
+#define _CONFIG_H_
+
+/* 9P *************************************************************************/
+
+#define CONFIG_9P_MAX_ERR_SIZE 128
+#define CONFIG_9P_MAX_9P2000_e_WELEM 16
+
+#define CONFIG_9P_ENABLE_9P2000 1 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_e 1 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_L 1 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_p9p 1 /* bool */
+
+/* 9P server (unused) *********************************************************/
+
+#define CONFIG_9P_SRV_MAX_MSG_SIZE ((4*1024)+24)
+#define CONFIG_9P_SRV_MAX_HOSTMSG_SIZE CONFIG_9P_SRV_MAX_MSG_SIZE+16
+#define CONFIG_9P_SRV_MAX_FIDS 16
+#define CONFIG_9P_SRV_MAX_REQS 2
+#define CONFIG_9P_SRV_MAX_DEPTH 3
+
+/* COROUTINE (unused) *********************************************************/
+
+#define CONFIG_COROUTINE_STACK_SIZE_DEFAULT (32*1024)
+#define CONFIG_COROUTINE_NAME_LEN 16
+#define CONFIG_COROUTINE_MEASURE_STACK 1 /* bool */
+#define CONFIG_COROUTINE_PROTECT_STACK 1 /* bool */
+#define CONFIG_COROUTINE_DEBUG 0 /* bool */
+#define CONFIG_COROUTINE_VALGRIND 1 /* bool */
+#define CONFIG_COROUTINE_GDB 1 /* bool */
+#define CONFIG_COROUTINE_NUM 2
+
+#endif /* _CONFIG_H_ */
diff --git a/lib9p/tests/runtest b/lib9p/tests/runtest
index fb66a43..6883391 100755
--- a/lib9p/tests/runtest
+++ b/lib9p/tests/runtest
@@ -1,67 +1,50 @@
#!/usr/bin/env bash
-# lib9p/tests/runtest - Simple tests for the 9P `test_server`
+# lib9p/tests/runtest - Test harness for the 9P `test_server`
#
# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
# SPDX-License-Identifier: AGPL-3.0-or-later
set -euE -o pipefail
-set -x
build_aux=$(realpath --canonicalize-missing -- "${BASH_SOURCE[0]}/../../../build-aux")
-port=$(python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()')
-"${build_aux}/valgrind" ./tests/test_server/test_server "$port" &
-server_pid=$!
-# shellcheck disable=SC2064
-trap "kill $server_pid || true; wait $server_pid || true" EXIT
-server_addr="localhost:${port}"
-
-client=(9p -a "$server_addr")
+if [[ $# != 2 ]]; then
+ echo >&2 "Usage: $0 CLIENTSCRIPT EXPLOG"
+ exit 2
+fi
+clientscript="$1"
+explog="$2"
-expect_lines() (
+cleanup=()
+cleanup() {
{ set +x; } &>/dev/null
- printf >&2 '+ diff -u expected.txt actual.txt\n'
- diff -u <(printf '%s\n' "$@") <(printf '%s\n' "$out")
-)
-
-while [[ -d /proc/$server_pid ]] && ! (readlink /proc/$server_pid/fd/* 2>/dev/null | grep -q ^socket:); do sleep 0.1; done
+ local i
+ for ((i = ${#cleanup[@]} - 1; i >= 0; i--)); do
+ eval "set -x; ${cleanup[$i]}"
+ { set +x; } &>/dev/null
+ done
+}
+trap cleanup EXIT
-out=$("${client[@]}" ls -l '')
-expect_lines \
- 'd-r-xr-xr-x M 0 root root 0 Oct 7 2024 Documentation' \
- '--r--r--r-- M 0 root root 166 Oct 7 2024 README.md' \
- '---w--w--w- M 0 root root 0 Oct 7 2024 shutdown'
+logfile=$(mktemp -t lib9p-log.XXXXXXXXXX)
+cleanup+=("rm -f -- ${logfile@Q}")
-out=$("${client[@]}" ls -l 'Documentation/')
-expect_lines \
- '--r--r--r-- M 0 root root 166 Oct 7 2024 x'
-
-out=$("${client[@]}" read 'README.md')
-expect_lines \
- '<!--' \
- ' README.md - test static file' \
- '' \
- ' Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>' \
- ' SPDX-License-Identifier: AGPL-3.0-or-later' \
- '-->' \
- 'Hello, world!'
+port=$(python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()')
-out=$("${client[@]}" read 'Documentation/x')
-expect_lines \
- '<!--' \
- ' Documentation/x.txt - test static file' \
- '' \
- ' Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>' \
- ' SPDX-License-Identifier: AGPL-3.0-or-later' \
- '-->' \
- 'foo'
+set -x
-out=$("${client[@]}" stat 'Documentation/x')
-expect_lines \
- "'x' 'root' 'root' 'root' q (0000000000000001 1 ) m 0444 at 1728337905 mt 1728337904 l 166 t 0 d 0"
+"${build_aux}/valgrind" ./tests/test_server/test_server "$port" "$logfile" &
+server_pid=$!
+cleanup+=("kill $server_pid || true; wait $server_pid || true")
+while [[ -d /proc/$server_pid ]] && ! (readlink /proc/$server_pid/fd/* 2>/dev/null | grep -q ^socket:); do sleep 0.1; done
-out=$("${client[@]}" write 'shutdown' <<<1)
-expect_lines ''
+if [[ "$(head -c2 -- "$clientscript")" == '#!' ]]; then
+ "$clientscript" "$port"
+else
+ "${build_aux}/valgrind" "$clientscript" "$port"
+fi
wait "$server_pid"
-trap - EXIT
+cleanup=("${cleanup[@]::1}")
+
+diff -u -- <(grep -e '^[<>]' -- "$explog") "$logfile"
diff --git a/lib9p/tests/test_server/CMakeLists.txt b/lib9p/tests/test_server/CMakeLists.txt
index 19c8edb..681e583 100644
--- a/lib9p/tests/test_server/CMakeLists.txt
+++ b/lib9p/tests/test_server/CMakeLists.txt
@@ -10,6 +10,8 @@ if (PICO_PLATFORM STREQUAL "host")
add_library(test_server_objs OBJECT
main.c
fs_shutdown.c
+ fs_slowread.c
+ fs_whoami.c
)
target_include_directories(test_server_objs PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/config)
target_include_directories(test_server_objs PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
diff --git a/lib9p/tests/test_server/fs_shutdown.c b/lib9p/tests/test_server/fs_shutdown.c
index 3f88985..26a8a10 100644
--- a/lib9p/tests/test_server/fs_shutdown.c
+++ b/lib9p/tests/test_server/fs_shutdown.c
@@ -4,12 +4,17 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
+#include <stdlib.h>
+
#include "fs_shutdown.h"
LO_IMPLEMENTATION_C(lib9p_srv_file, struct shutdown_file, shutdown_file, static);
-LO_IMPLEMENTATION_H(lib9p_srv_fio, struct shutdown_file, shutdown_file);
-LO_IMPLEMENTATION_C(lib9p_srv_fio, struct shutdown_file, shutdown_file, static);
+struct shutdown_fio {
+ struct shutdown_file *parent;
+};
+LO_IMPLEMENTATION_H(lib9p_srv_fio, struct shutdown_fio, shutdown_fio);
+LO_IMPLEMENTATION_C(lib9p_srv_fio, struct shutdown_fio, shutdown_fio, static);
/* srv_file *******************************************************************/
@@ -62,32 +67,42 @@ LIB9P_SRV_NOTDIR(struct shutdown_file, shutdown_file)
static lo_interface lib9p_srv_fio shutdown_file_fopen(struct shutdown_file *self, struct lib9p_srv_ctx *ctx, bool, bool, bool) {
assert(self);
assert(ctx);
- return lo_box_shutdown_file_as_lib9p_srv_fio(self);
+
+ struct shutdown_fio *ret = malloc(sizeof(struct shutdown_fio));
+ ret->parent = self;
+
+ return lo_box_shutdown_fio_as_lib9p_srv_fio(ret);
}
/* srv_fio ********************************************************************/
-static void shutdown_file_iofree(struct shutdown_file *self) {
+static void shutdown_fio_iofree(struct shutdown_fio *self) {
+ assert(self);
+ free(self);
+}
+
+static struct lib9p_qid shutdown_fio_qid(struct shutdown_fio *self) {
assert(self);
+ return shutdown_file_qid(self->parent);
}
-static uint32_t shutdown_file_iounit(struct shutdown_file *self) {
+static uint32_t shutdown_fio_iounit(struct shutdown_fio *self) {
assert(self);
return 0;
}
-static uint32_t shutdown_file_pwrite(struct shutdown_file *self, struct lib9p_srv_ctx *ctx, void *buf, uint32_t byte_count, uint64_t LM_UNUSED(offset)) {
+static uint32_t shutdown_fio_pwrite(struct shutdown_fio *self, struct lib9p_srv_ctx *ctx, void *buf, uint32_t byte_count, uint64_t LM_UNUSED(offset)) {
assert(self);
assert(ctx);
assert(buf);
if (byte_count == 0)
return 0;
- for (size_t i = 0; i < self->nlisteners; i++)
- LO_CALL(lo_box_hostnet_tcplist_as_net_stream_listener(&self->listeners[i]), close);
+ for (size_t i = 0; i < self->parent->nlisteners; i++)
+ LO_CALL(lo_box_hostnet_tcplist_as_net_stream_listener(&self->parent->listeners[i]), close);
return byte_count;
}
-static void shutdown_file_pread(struct shutdown_file *LM_UNUSED(self), struct lib9p_srv_ctx *LM_UNUSED(ctx),
- uint32_t LM_UNUSED(byte_count), uint64_t LM_UNUSED(byte_offset),
- struct iovec *LM_UNUSED(ret)) {
+static void shutdown_fio_pread(struct shutdown_fio *LM_UNUSED(self), struct lib9p_srv_ctx *LM_UNUSED(ctx),
+ uint32_t LM_UNUSED(byte_count), uint64_t LM_UNUSED(byte_offset),
+ struct iovec *LM_UNUSED(ret)) {
assert_notreached("not readable");
}
diff --git a/lib9p/tests/test_server/fs_slowread.c b/lib9p/tests/test_server/fs_slowread.c
new file mode 100644
index 0000000..c5db896
--- /dev/null
+++ b/lib9p/tests/test_server/fs_slowread.c
@@ -0,0 +1,116 @@
+/* lib9p/tests/test_server/fs_slowread.c - slowread API endpoint
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stdlib.h>
+
+#include "fs_slowread.h"
+
+LO_IMPLEMENTATION_C(lib9p_srv_file, struct slowread_file, slowread_file, static);
+
+struct slowread_fio {
+ struct slowread_file *parent;
+};
+LO_IMPLEMENTATION_H(lib9p_srv_fio, struct slowread_fio, slowread_fio);
+LO_IMPLEMENTATION_C(lib9p_srv_fio, struct slowread_fio, slowread_fio, static);
+
+/* srv_file *******************************************************************/
+
+static void slowread_file_free(struct slowread_file *self) {
+ assert(self);
+}
+static struct lib9p_qid slowread_file_qid(struct slowread_file *self) {
+ assert(self);
+ return (struct lib9p_qid){
+ .type = LIB9P_QT_FILE,
+ .vers = 1,
+ .path = self->pathnum,
+ };
+}
+
+static struct lib9p_stat slowread_file_stat(struct slowread_file *self, struct lib9p_srv_ctx *ctx) {
+ assert(self);
+ assert(ctx);
+ return (struct lib9p_stat){
+ .kern_type = 0,
+ .kern_dev = 0,
+ .file_qid = slowread_file_qid(self),
+ .file_mode = 0444,
+ .file_atime = UTIL9P_ATIME,
+ .file_mtime = UTIL9P_MTIME,
+ .file_size = 6,
+ .file_name = lib9p_str(self->name),
+ .file_owner_uid = lib9p_str("root"),
+ .file_owner_gid = lib9p_str("root"),
+ .file_last_modified_uid = lib9p_str("root"),
+ .file_extension = lib9p_str(NULL),
+ .file_owner_n_uid = 0,
+ .file_owner_n_gid = 0,
+ .file_last_modified_n_uid = 0,
+ };
+}
+static void slowread_file_wstat(struct slowread_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_stat) {
+ assert(self);
+ assert(ctx);
+ lib9p_error(&ctx->basectx, LINUX_EROFS, "cannot wstat API file");
+}
+static void slowread_file_remove(struct slowread_file *self, struct lib9p_srv_ctx *ctx) {
+ assert(self);
+ assert(ctx);
+ lib9p_error(&ctx->basectx, LINUX_EROFS, "cannot remove API file");
+}
+
+LIB9P_SRV_NOTDIR(struct slowread_file, slowread_file)
+
+static lo_interface lib9p_srv_fio slowread_file_fopen(struct slowread_file *self, struct lib9p_srv_ctx *ctx, bool, bool, bool) {
+ assert(self);
+ assert(ctx);
+
+ struct slowread_fio *ret = malloc(sizeof(struct slowread_fio));
+ ret->parent = self;
+
+ return lo_box_slowread_fio_as_lib9p_srv_fio(ret);
+}
+
+/* srv_fio ********************************************************************/
+
+static void slowread_fio_iofree(struct slowread_fio *self) {
+ assert(self);
+ free(self);
+}
+
+static struct lib9p_qid slowread_fio_qid(struct slowread_fio *self) {
+ assert(self);
+ return slowread_file_qid(self->parent);
+}
+
+static uint32_t slowread_fio_iounit(struct slowread_fio *self) {
+ assert(self);
+ return 0;
+}
+
+static uint32_t slowread_fio_pwrite(struct slowread_fio *LM_UNUSED(self),
+ struct lib9p_srv_ctx *LM_UNUSED(ctx),
+ void *LM_UNUSED(buf), uint32_t LM_UNUSED(byte_count),
+ uint64_t LM_UNUSED(offset)) {
+ assert_notreached("not writable");
+}
+static void slowread_fio_pread(struct slowread_fio *self, struct lib9p_srv_ctx *ctx,
+ uint32_t byte_count, uint64_t LM_UNUSED(byte_offset),
+ struct iovec *ret) {
+ assert(self);
+ assert(ctx);
+ assert(ret);
+
+ while (!lib9p_srv_flush_requested(ctx))
+ cr_yield();
+ if (self->parent->flushable)
+ lib9p_srv_acknowledge_flush(ctx);
+ else
+ *ret = (struct iovec){
+ .iov_base = "Sloth\n",
+ .iov_len = 6 < byte_count ? 6 : byte_count,
+ };
+}
diff --git a/lib9p/tests/test_server/fs_slowread.h b/lib9p/tests/test_server/fs_slowread.h
new file mode 100644
index 0000000..ef4b65f
--- /dev/null
+++ b/lib9p/tests/test_server/fs_slowread.h
@@ -0,0 +1,22 @@
+/* lib9p/tests/test_server/fs_slowread.h - slowread API endpoint
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIB9P_TESTS_TEST_SERVER_FS_SLOWREAD_H_
+#define _LIB9P_TESTS_TEST_SERVER_FS_SLOWREAD_H_
+
+#include <util9p/static.h>
+#include <libhw/host_net.h>
+
+struct slowread_file {
+ char *name;
+ uint64_t pathnum;
+
+ bool flushable;
+};
+LO_IMPLEMENTATION_H(lib9p_srv_file, struct slowread_file, slowread_file);
+#define lo_box_slowread_file_as_lib9p_srv_file(obj) util9p_box(slowread_file, obj)
+
+#endif /* _LIB9P_TESTS_TEST_SERVER_FS_SLOWREAD_H_ */
diff --git a/lib9p/tests/test_server/fs_whoami.c b/lib9p/tests/test_server/fs_whoami.c
new file mode 100644
index 0000000..ff6dd25
--- /dev/null
+++ b/lib9p/tests/test_server/fs_whoami.c
@@ -0,0 +1,156 @@
+/* lib9p/tests/test_server/fs_whoami.c - /whoami API endpoint
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stdio.h> /* for snprintf() */
+#include <stdlib.h> /* for malloc(), realloc(), free() */
+
+#include "fs_whoami.h"
+
+LO_IMPLEMENTATION_C(lib9p_srv_file, struct whoami_file, whoami_file, static);
+
+struct whoami_fio {
+ struct whoami_file *parent;
+ size_t buf_len;
+ char *buf;
+};
+LO_IMPLEMENTATION_H(lib9p_srv_fio, struct whoami_fio, whoami_fio);
+LO_IMPLEMENTATION_C(lib9p_srv_fio, struct whoami_fio, whoami_fio, static);
+
+size_t whoami_len(struct lib9p_srv_ctx *ctx) {
+ assert(ctx);
+ assert(ctx->authinfo);
+
+ size_t len = 0;
+ uint32_t uid = ctx->authinfo->uid;
+ while (uid) {
+ len++;
+ uid /= 10;
+ }
+ if (!len)
+ len++;
+ len += 2;
+ len += ctx->authinfo->uname.len;
+ return len;
+}
+
+/* srv_file *******************************************************************/
+
+static void whoami_file_free(struct whoami_file *self) {
+ assert(self);
+}
+static struct lib9p_qid whoami_file_qid(struct whoami_file *self) {
+ assert(self);
+ return (struct lib9p_qid){
+ .type = LIB9P_QT_FILE,
+ .vers = 1,
+ .path = self->pathnum,
+ };
+}
+
+static struct lib9p_stat whoami_file_stat(struct whoami_file *self, struct lib9p_srv_ctx *ctx) {
+ assert(self);
+ assert(ctx);
+
+ return (struct lib9p_stat){
+ .kern_type = 0,
+ .kern_dev = 0,
+ .file_qid = whoami_file_qid(self),
+ .file_mode = 0444,
+ .file_atime = UTIL9P_ATIME,
+ .file_mtime = UTIL9P_MTIME,
+ .file_size = whoami_len(ctx),
+ .file_name = lib9p_str(self->name),
+ .file_owner_uid = lib9p_str("root"),
+ .file_owner_gid = lib9p_str("root"),
+ .file_last_modified_uid = lib9p_str("root"),
+ .file_extension = lib9p_str(NULL),
+ .file_owner_n_uid = 0,
+ .file_owner_n_gid = 0,
+ .file_last_modified_n_uid = 0,
+ };
+}
+static void whoami_file_wstat(struct whoami_file *self, struct lib9p_srv_ctx *ctx, struct lib9p_stat) {
+ assert(self);
+ assert(ctx);
+ lib9p_error(&ctx->basectx, LINUX_EROFS, "cannot wstat API file");
+}
+static void whoami_file_remove(struct whoami_file *self, struct lib9p_srv_ctx *ctx) {
+ assert(self);
+ assert(ctx);
+ lib9p_error(&ctx->basectx, LINUX_EROFS, "cannot remove API file");
+}
+
+LIB9P_SRV_NOTDIR(struct whoami_file, whoami_file)
+
+static lo_interface lib9p_srv_fio whoami_file_fopen(struct whoami_file *self, struct lib9p_srv_ctx *ctx, bool, bool, bool) {
+ assert(self);
+ assert(ctx);
+
+ struct whoami_fio *ret = malloc(sizeof(struct whoami_fio));
+ ret->parent = self;
+ ret->buf_len = 0;
+ ret->buf = NULL;
+
+ return lo_box_whoami_fio_as_lib9p_srv_fio(ret);
+}
+
+/* srv_fio ********************************************************************/
+
+static void whoami_fio_iofree(struct whoami_fio *self) {
+ assert(self);
+ if (self->buf)
+ free(self->buf);
+ free(self);
+}
+
+static struct lib9p_qid whoami_fio_qid(struct whoami_fio *self) {
+ assert(self);
+ assert(self->parent);
+ return whoami_file_qid(self->parent);
+}
+
+static uint32_t whoami_fio_iounit(struct whoami_fio *self) {
+ assert(self);
+ return 0;
+}
+
+static uint32_t whoami_fio_pwrite(struct whoami_fio *LM_UNUSED(self),
+ struct lib9p_srv_ctx *LM_UNUSED(ctx),
+ void *LM_UNUSED(buf), uint32_t LM_UNUSED(byte_count),
+ uint64_t LM_UNUSED(offset)) {
+ assert_notreached("not writable");
+}
+static void whoami_fio_pread(struct whoami_fio *self, struct lib9p_srv_ctx *ctx,
+ uint32_t byte_count, uint64_t byte_offset,
+ struct iovec *ret) {
+ assert(self);
+ assert(ctx);
+ assert(ret);
+
+ size_t data_size = whoami_len(ctx);
+ if (self->buf_len < data_size+1) {
+ self->buf = realloc(self->buf, data_size+1);
+ self->buf_len = data_size+1;
+ }
+ snprintf(self->buf, self->buf_len, "%"PRIu32" %.*s\n",
+ ctx->authinfo->uid, ctx->authinfo->uname.len, ctx->authinfo->uname.utf8);
+
+ if (byte_offset > (uint64_t)data_size) {
+ lib9p_error(&ctx->basectx,
+ LINUX_EINVAL, "offset is past end-of-file length");
+ return;
+ }
+
+ size_t beg_off = (size_t)byte_offset;
+ size_t end_off = beg_off + (size_t)byte_count;
+ if (end_off > data_size)
+ end_off = data_size;
+
+ *ret = (struct iovec){
+ .iov_base = &self->buf[beg_off],
+ .iov_len = end_off-beg_off,
+ };
+}
diff --git a/lib9p/tests/test_server/fs_whoami.h b/lib9p/tests/test_server/fs_whoami.h
new file mode 100644
index 0000000..0d3d311
--- /dev/null
+++ b/lib9p/tests/test_server/fs_whoami.h
@@ -0,0 +1,20 @@
+/* lib9p/tests/test_server/fs_whoami.h - /whoami API endpoint
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIB9P_TESTS_TEST_SERVER_FS_WHOAMI_H_
+#define _LIB9P_TESTS_TEST_SERVER_FS_WHOAMI_H_
+
+#include <util9p/static.h>
+#include <libhw/host_net.h>
+
+struct whoami_file {
+ char *name;
+ uint64_t pathnum;
+};
+LO_IMPLEMENTATION_H(lib9p_srv_file, struct whoami_file, whoami_file);
+#define lo_box_whoami_file_as_lib9p_srv_file(obj) util9p_box(whoami_file, obj)
+
+#endif /* _LIB9P_TESTS_TEST_SERVER_FS_WHOAMI_H_ */
diff --git a/lib9p/tests/test_server/main.c b/lib9p/tests/test_server/main.c
index 8d22a04..e89a75e 100644
--- a/lib9p/tests/test_server/main.c
+++ b/lib9p/tests/test_server/main.c
@@ -5,6 +5,8 @@
*/
#include <error.h>
+#include <errno.h>
+#include <stdio.h>
#include <stdlib.h> /* for atoi() */
#include <lib9p/srv.h>
@@ -18,6 +20,8 @@
#include "static.h"
#include "fs_shutdown.h"
+#include "fs_slowread.h"
+#include "fs_whoami.h"
/* configuration **************************************************************/
@@ -37,6 +41,7 @@ struct {
uint16_t port;
struct hostnet_tcp_listener listeners[CONFIG_SRV9P_NUM_CONNS];
struct lib9p_srv srv;
+ FILE *logstream;
} globals = {
.srv = (struct lib9p_srv){
.rootdir = get_root,
@@ -45,32 +50,34 @@ struct {
/* file tree ******************************************************************/
-enum { PATH_BASE = __COUNTER__ };
-#define PATH_COUNTER __COUNTER__ - PATH_BASE
-
-#define STATIC_FILE(STRNAME, SYMNAME) \
- UTIL9P_STATIC_FILE(PATH_COUNTER, STRNAME, \
+#define STATIC_FILE(N, STRNAME, SYMNAME) \
+ UTIL9P_STATIC_FILE(N, STRNAME, \
.data_start = _binary_static_##SYMNAME##_start, \
.data_end = _binary_static_##SYMNAME##_end)
-#define STATIC_DIR(STRNAME, ...) \
- UTIL9P_STATIC_DIR(PATH_COUNTER, STRNAME, __VA_ARGS__)
+#define STATIC_DIR(N, STRNAME, ...) \
+ UTIL9P_STATIC_DIR(N, STRNAME, __VA_ARGS__)
-#define API_FILE(STRNAME, SYMNAME, ...) \
+#define API_FILE(N, STRNAME, SYMNAME, ...) \
lo_box_##SYMNAME##_file_as_lib9p_srv_file(&((struct SYMNAME##_file){ \
.name = STRNAME, \
- .pathnum = PATH_COUNTER \
+ .pathnum = N \
__VA_OPT__(,) __VA_ARGS__ \
}))
struct lib9p_srv_file root =
- STATIC_DIR("",
- STATIC_DIR("Documentation",
- STATIC_FILE("x", Documentation_x_txt),
+ STATIC_DIR(1, "",
+ STATIC_DIR(2, "Documentation",
+ STATIC_FILE(3, "x", Documentation_x_txt),
),
- STATIC_FILE("README.md", README_md),
- API_FILE("shutdown", shutdown,
+ STATIC_FILE(4, "README.md", README_md),
+ API_FILE(5, "shutdown", shutdown,
.listeners = globals.listeners,
.nlisteners = LM_ARRAY_LEN(globals.listeners)),
+ API_FILE(6, "slowread", slowread,
+ .flushable = false),
+ API_FILE(7, "slowread-flushable", slowread,
+ .flushable = true),
+ API_FILE(8, "whoami", whoami),
);
static lo_interface lib9p_srv_file get_root(struct lib9p_srv_ctx *LM_UNUSED(ctx), struct lib9p_s LM_UNUSED(treename)) {
@@ -85,7 +92,15 @@ static COROUTINE read_cr(void *_i) {
hostnet_tcp_listener_init(&globals.listeners[i], globals.port);
- lib9p_srv_read_cr(&globals.srv, lo_box_hostnet_tcplist_as_net_stream_listener(&globals.listeners[i]));
+ lib9p_srv_accept_and_read_loop(&globals.srv, lo_box_hostnet_tcplist_as_net_stream_listener(&globals.listeners[i]));
+
+ cr_end();
+}
+
+static COROUTINE write_cr(void *) {
+ cr_begin();
+
+ lib9p_srv_worker_loop(&globals.srv);
cr_end();
}
@@ -102,22 +117,48 @@ static COROUTINE init_cr(void *) {
}
for (int i = 0; i < 2*CONFIG_SRV9P_NUM_CONNS; i++) {
char name[] = {'w', 'r', 'i', 't', 'e', '-', hexdig[i], '\0'};
- if (!coroutine_add(name, lib9p_srv_write_cr, &globals.srv))
- error(1, 0, "coroutine_add(lib9p_srv_write_cr, &globals.srv)");
+ if (!coroutine_add(name, write_cr, NULL))
+ error(1, 0, "coroutine_add(write_cr, NULL)");
}
cr_exit();
}
+static void log_fct(char character, void *_stream) {
+ FILE *stream = _stream;
+ putc(character, stream);
+ putchar(character);
+}
+
+static void log_msg(struct lib9p_srv_ctx *ctx, enum lib9p_msg_type typ, void *hostmsg) {
+ /* It sucks that %v trips -Wformat and -Wformat-extra-args
+ * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47781 */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat"
+#pragma GCC diagnostic ignored "-Wformat-extra-args"
+ fmt_fctprintf(log_fct, globals.logstream,
+ "%c %v\n", typ % 2 ? '<' : '>',
+ lo_box_lib9p_msg_as_fmt_formatter(&ctx->basectx, typ, hostmsg));
+#pragma GCC diagnostic pop
+ fflush(globals.logstream);
+}
+
int main(int argc, char *argv[]) {
- if (argc != 2)
- error(2, 0, "usage: %s PORT_NUMBER", argv[0]);
+ if (argc != 3)
+ error(2, 0, "usage: %s PORT_NUMBER LOGFILE", argv[0]);
+
globals.port = atoi(argv[1]);
+ globals.logstream = fopen(argv[2], "w");
+ if (!globals.logstream)
+ error(2, errno, "fopen");
+ globals.srv.msglog = log_msg;
+
struct hostclock clock_monotonic = {
.clock_id = CLOCK_MONOTONIC,
};
bootclock = lo_box_hostclock_as_alarmclock(&clock_monotonic);
coroutine_add("init", init_cr, NULL);
coroutine_main();
+ fclose(globals.logstream);
return 0;
}
diff --git a/lib9p/tests/testclient-p9p b/lib9p/tests/testclient-p9p
new file mode 100755
index 0000000..9c9fb5e
--- /dev/null
+++ b/lib9p/tests/testclient-p9p
@@ -0,0 +1,62 @@
+#!/usr/bin/env bash
+# lib9p/tests/testclient-p9p - Test the 9P `test_server` against Plan 9 Port's `9p` utility
+#
+# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+set -euE -o pipefail
+
+if [[ $# != 1 ]]; then
+ echo >&2 "Usage: $0 SERVER_PORT"
+ echo >&2 "Usage: ./runtest $0 EXPLOG"
+ exit 2
+fi
+
+expect_lines() (
+ { set +x; } &>/dev/null
+ printf >&2 '+ diff -u expected.txt actual.txt\n'
+ diff -u <(printf '%s\n' "$@") <(printf '%s\n' "$out")
+)
+
+set -x
+client=(unshare --user 9p -a "localhost:${1}")
+
+out=$("${client[@]}" ls -l '')
+expect_lines \
+ 'd-r-xr-xr-x M 0 root root 0 Oct 7 2024 Documentation' \
+ '--r--r--r-- M 0 root root 166 Oct 7 2024 README.md' \
+ '---w--w--w- M 0 root root 0 Oct 7 2024 shutdown' \
+ '--r--r--r-- M 0 root root 6 Oct 7 2024 slowread' \
+ '--r--r--r-- M 0 root root 6 Oct 7 2024 slowread-flushable' \
+ '--r--r--r-- M 0 root root 9 Oct 7 2024 whoami'
+
+out=$("${client[@]}" ls -l 'Documentation/')
+expect_lines \
+ '--r--r--r-- M 0 root root 166 Oct 7 2024 x'
+
+out=$("${client[@]}" read 'README.md')
+expect_lines \
+ '<!--' \
+ ' README.md - test static file' \
+ '' \
+ ' Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>' \
+ ' SPDX-License-Identifier: AGPL-3.0-or-later' \
+ '-->' \
+ 'Hello, world!'
+
+out=$("${client[@]}" read 'Documentation/x')
+expect_lines \
+ '<!--' \
+ ' Documentation/x.txt - test static file' \
+ '' \
+ ' Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>' \
+ ' SPDX-License-Identifier: AGPL-3.0-or-later' \
+ '-->' \
+ 'foo'
+
+out=$("${client[@]}" stat 'Documentation/x')
+expect_lines \
+ "'x' 'root' 'root' 'root' q (0000000000000003 1 ) m 0444 at 1728337905 mt 1728337904 l 166 t 0 d 0"
+
+out=$("${client[@]}" write 'shutdown' <<<1)
+expect_lines ''
diff --git a/lib9p/tests/testclient-p9p.explog b/lib9p/tests/testclient-p9p.explog
new file mode 100644
index 0000000..45651a4
--- /dev/null
+++ b/lib9p/tests/testclient-p9p.explog
@@ -0,0 +1,106 @@
+# lib9p/tests/testclient-p9p.explog - Expected 9P logfile of testclient-p9p
+#
+# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+> Tversion { tag=NOTAG max_msg_size=8192 version="9P2000" }
+< Rversion { tag=NOTAG max_msg_size=4120 version="9P2000" }
+> Tauth { tag=0 afid=0 uname="nobody" aname="" n_uid=0 }
+< Rerror { tag=0 errstr="authentication not required" errnum=95 }
+> Tattach { tag=0 fid=0 afid=NOFID uname="nobody" aname="" n_uid=0 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=1 } }
+> Twalk { tag=0 fid=0 newfid=1 nwname=0 wname=[ ] }
+< Rwalk { tag=0 nwqid=0 wqid=[ ] }
+> Tstat { tag=0 fid=1 }
+< Rstat { tag=0 stat={ kern_type=0 kern_dev=0 file_qid={ type=(DIR) vers=1 path=1 } file_mode=(DIR|0555) file_atime=1728337905 file_mtime=1728337904 file_size=0 file_name="" file_owner_uid="root" file_owner_gid="root" file_last_modified_uid="root" file_extension="" file_owner_n_uid=0 file_owner_n_gid=0 file_last_modified_n_uid=0 } }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }
+> Twalk { tag=0 fid=0 newfid=1 nwname=0 wname=[ ] }
+< Rwalk { tag=0 nwqid=0 wqid=[ ] }
+> Topen { tag=0 fid=1 mode=(MODE_READ) }
+< Ropen { tag=0 qid={ type=(DIR) vers=1 path=1 } iounit=0 }
+> Tread { tag=0 fid=1 offset=0 count=4096 }
+< Rread { tag=0 count=428 data=<bytedata> }
+> Tread { tag=0 fid=1 offset=428 count=4096 }
+< Rread { tag=0 count=0 data="" }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }
+> Tversion { tag=NOTAG max_msg_size=8192 version="9P2000" }
+< Rversion { tag=NOTAG max_msg_size=4120 version="9P2000" }
+> Tauth { tag=0 afid=0 uname="nobody" aname="" n_uid=0 }
+< Rerror { tag=0 errstr="authentication not required" errnum=95 }
+> Tattach { tag=0 fid=0 afid=NOFID uname="nobody" aname="" n_uid=0 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=1 } }
+> Twalk { tag=0 fid=0 newfid=1 nwname=1 wname=["Documentation" ] }
+< Rwalk { tag=0 nwqid=1 wqid=[{ type=(DIR) vers=1 path=2 } ] }
+> Tstat { tag=0 fid=1 }
+< Rstat { tag=0 stat={ kern_type=0 kern_dev=0 file_qid={ type=(DIR) vers=1 path=2 } file_mode=(DIR|0555) file_atime=1728337905 file_mtime=1728337904 file_size=0 file_name="Documentation" file_owner_uid="root" file_owner_gid="root" file_last_modified_uid="root" file_extension="" file_owner_n_uid=0 file_owner_n_gid=0 file_last_modified_n_uid=0 } }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }
+> Twalk { tag=0 fid=0 newfid=1 nwname=1 wname=["Documentation" ] }
+< Rwalk { tag=0 nwqid=1 wqid=[{ type=(DIR) vers=1 path=2 } ] }
+> Topen { tag=0 fid=1 mode=(MODE_READ) }
+< Ropen { tag=0 qid={ type=(DIR) vers=1 path=2 } iounit=0 }
+> Tread { tag=0 fid=1 offset=0 count=4096 }
+< Rread { tag=0 count=62 data=<bytedata> }
+> Tread { tag=0 fid=1 offset=62 count=4096 }
+< Rread { tag=0 count=0 data="" }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }
+> Tversion { tag=NOTAG max_msg_size=8192 version="9P2000" }
+< Rversion { tag=NOTAG max_msg_size=4120 version="9P2000" }
+> Tauth { tag=0 afid=0 uname="nobody" aname="" n_uid=0 }
+< Rerror { tag=0 errstr="authentication not required" errnum=95 }
+> Tattach { tag=0 fid=0 afid=NOFID uname="nobody" aname="" n_uid=0 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=1 } }
+> Twalk { tag=0 fid=0 newfid=1 nwname=1 wname=["README.md" ] }
+< Rwalk { tag=0 nwqid=1 wqid=[{ type=(0) vers=1 path=4 } ] }
+> Topen { tag=0 fid=1 mode=(MODE_READ) }
+< Ropen { tag=0 qid={ type=(0) vers=1 path=4 } iounit=0 }
+> Tread { tag=0 fid=1 offset=0 count=4096 }
+< Rread { tag=0 count=166 data="<!--\n README.md - test static file\n\n Copyright ("... }
+> Tread { tag=0 fid=1 offset=166 count=4096 }
+< Rread { tag=0 count=0 data="" }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }
+> Tversion { tag=NOTAG max_msg_size=8192 version="9P2000" }
+< Rversion { tag=NOTAG max_msg_size=4120 version="9P2000" }
+> Tauth { tag=0 afid=0 uname="nobody" aname="" n_uid=0 }
+< Rerror { tag=0 errstr="authentication not required" errnum=95 }
+> Tattach { tag=0 fid=0 afid=NOFID uname="nobody" aname="" n_uid=0 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=1 } }
+> Twalk { tag=0 fid=0 newfid=1 nwname=2 wname=["Documentation", "x" ] }
+< Rwalk { tag=0 nwqid=2 wqid=[{ type=(DIR) vers=1 path=2 }, { type=(0) vers=1 path=3 } ] }
+> Topen { tag=0 fid=1 mode=(MODE_READ) }
+< Ropen { tag=0 qid={ type=(0) vers=1 path=3 } iounit=0 }
+> Tread { tag=0 fid=1 offset=0 count=4096 }
+< Rread { tag=0 count=166 data="<!--\n Documentation/x.txt - test static file\n\n C"... }
+> Tread { tag=0 fid=1 offset=166 count=4096 }
+< Rread { tag=0 count=0 data="" }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }
+> Tversion { tag=NOTAG max_msg_size=8192 version="9P2000" }
+< Rversion { tag=NOTAG max_msg_size=4120 version="9P2000" }
+> Tauth { tag=0 afid=0 uname="nobody" aname="" n_uid=0 }
+< Rerror { tag=0 errstr="authentication not required" errnum=95 }
+> Tattach { tag=0 fid=0 afid=NOFID uname="nobody" aname="" n_uid=0 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=1 } }
+> Twalk { tag=0 fid=0 newfid=1 nwname=2 wname=["Documentation", "x" ] }
+< Rwalk { tag=0 nwqid=2 wqid=[{ type=(DIR) vers=1 path=2 }, { type=(0) vers=1 path=3 } ] }
+> Tstat { tag=0 fid=1 }
+< Rstat { tag=0 stat={ kern_type=0 kern_dev=0 file_qid={ type=(0) vers=1 path=3 } file_mode=(0444) file_atime=1728337905 file_mtime=1728337904 file_size=166 file_name="x" file_owner_uid="root" file_owner_gid="root" file_last_modified_uid="root" file_extension="" file_owner_n_uid=0 file_owner_n_gid=0 file_last_modified_n_uid=0 } }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }
+> Tversion { tag=NOTAG max_msg_size=8192 version="9P2000" }
+< Rversion { tag=NOTAG max_msg_size=4120 version="9P2000" }
+> Tauth { tag=0 afid=0 uname="nobody" aname="" n_uid=0 }
+< Rerror { tag=0 errstr="authentication not required" errnum=95 }
+> Tattach { tag=0 fid=0 afid=NOFID uname="nobody" aname="" n_uid=0 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=1 } }
+> Twalk { tag=0 fid=0 newfid=1 nwname=1 wname=["shutdown" ] }
+< Rwalk { tag=0 nwqid=1 wqid=[{ type=(0) vers=1 path=5 } ] }
+> Topen { tag=0 fid=1 mode=(TRUNC|MODE_WRITE) }
+< Ropen { tag=0 qid={ type=(0) vers=1 path=5 } iounit=0 }
+> Twrite { tag=0 fid=1 offset=0 count=2 data="1\n" }
+< Rwrite { tag=0 count=2 }
+> Tclunk { tag=0 fid=1 }
+< Rclunk { tag=0 }
diff --git a/lib9p/tests/testclient-sess.c b/lib9p/tests/testclient-sess.c
new file mode 100644
index 0000000..437c489
--- /dev/null
+++ b/lib9p/tests/testclient-sess.c
@@ -0,0 +1,160 @@
+/* lib9p/tests/testclient-sess.c - Test the 9P `test_server`'s sessions
+ *
+ * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <arpa/inet.h> /* for htons(), inet_addr() */
+#include <errno.h>
+#include <error.h>
+#include <netinet/in.h> /* for struct sockaddr{,_in} */
+#include <stdlib.h> /* for atoi() */
+#include <sys/socket.h> /* for socket(), connect() */
+#include <sys/uio.h> /* for writev() */
+#include <unistd.h> /* for read() */
+
+#include <libmisc/assert.h>
+#include <libmisc/endian.h>
+#include <lib9p/9p.h>
+
+#define MAX_MSG_SIZE (8*1024)
+
+static void _send9p(int fd, struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body) {
+ struct lib9p_Tmsg_send_buf buf;
+ bool err = lib9p_Tmsg_marshal(ctx, typ, body, &buf);
+ assert(!err);
+ size_t exp = 0;
+ for (size_t i = 0; i < buf.iov_cnt; i++)
+ exp += buf.iov[i].iov_len;
+ ssize_t act = writev(fd, buf.iov, buf.iov_cnt);
+ if (act < 0)
+ error(1, errno, "writev");
+ assert((size_t)act == exp);
+}
+
+#define send9p(typ, ...) _send9p(fd, &ctx, LIB9P_TYP_##typ, &((struct lib9p_msg_##typ){ __VA_ARGS__ }))
+
+static void _recv9p(int fd) {
+ uint8_t buf[MAX_MSG_SIZE];
+ size_t goal = 4;
+ size_t done = 0;
+ while (done < goal) {
+ ssize_t n = read(fd, &buf[done], goal-done);
+ if (n < 0)
+ error(1, errno, "read");
+ done += n;
+ }
+ goal = uint32le_decode(buf);
+ assert(goal <= MAX_MSG_SIZE);
+ while (done < goal) {
+ ssize_t n = read(fd, &buf[done], goal-done);
+ if (n < 0)
+ error(1, errno, "read");
+ done += n;
+ }
+}
+
+#define recv9p() _recv9p(fd)
+
+int main(int argc, char *argv[]) {
+ if (argc != 2)
+ error(2, 0, "Usage: %s SERVER_PORT", argv[0]);
+ uint16_t server_port = atoi(argv[1]);
+
+ union {
+ struct sockaddr gen;
+ struct sockaddr_in in;
+ } server_addr = {};
+ server_addr.in.sin_family = AF_INET;
+ server_addr.in.sin_addr.s_addr = inet_addr("127.0.0.1");
+ server_addr.in.sin_port = htons(server_port);
+
+ int fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd < 0)
+ error(1, errno, "socket");
+ if (connect(fd, &server_addr.gen, sizeof(server_addr)) < 0)
+ error(1, errno, "connect");
+
+ struct lib9p_ctx ctx = {
+ .max_msg_size = 16*1024,
+ };
+
+ struct lib9p_s wname[1];
+
+ /* numeric downgrade, unknown ext *************************************/
+ send9p(Tversion, .tag=0, .max_msg_size=57, .version=lib9p_str("9P2025.x"));
+ recv9p(); /* Rversion */
+ ctx.version = LIB9P_VER_9P2000;
+
+ /* numeric downgrade, known ext ***************************************/
+ send9p(Tversion, .tag=0, .max_msg_size=57, .version=lib9p_str("9P2025.u"));
+ recv9p(); /* Rversion */
+ ctx.version = LIB9P_VER_9P2000_u;
+
+ /* ext version, users *************************************************/
+ send9p(Tversion, .tag=0, .max_msg_size=(8*1024), .version=lib9p_str("9P2000.u"));
+ recv9p(); /* Rversion */
+ ctx.version = LIB9P_VER_9P2000_u;
+ send9p(Tattach, .tag=0, .fid=0, .afid=LIB9P_FID_NOFID, .uname=lib9p_str("alice"), .n_uid=1000, .aname=lib9p_str(""));
+ recv9p(); /* Rattach */
+ send9p(Tattach, .tag=0, .fid=1, .afid=LIB9P_FID_NOFID, .uname=lib9p_str("bob"), .n_uid=1001, .aname=lib9p_str(""));
+ recv9p(); /* Rattach */
+ wname[0] = lib9p_str("whoami"); send9p(Twalk, .tag=0, .fid=0, .newfid=2, .nwname=1, .wname=wname);
+ recv9p(); /* Rwalk */
+ wname[0] = lib9p_str("whoami"); send9p(Twalk, .tag=0, .fid=1, .newfid=3, .nwname=1, .wname=wname);
+ recv9p(); /* Rwalk */
+ send9p(Topen, .tag=0, .fid=2, .mode=LIB9P_O_MODE_READ);
+ recv9p(); /* Ropen */
+ send9p(Topen, .tag=0, .fid=3, .mode=LIB9P_O_MODE_READ);
+ recv9p(); /* Ropen */
+ send9p(Tread, .tag=0, .fid=2, .offset=0, .count=100);
+ recv9p(); /* Rread */
+ send9p(Tread, .tag=0, .fid=3, .offset=0, .count=100);
+ recv9p(); /* Rread */
+
+ /* flush **************************************************************/
+ send9p(Tversion, .tag=0, .max_msg_size=(8*1024), .version=lib9p_str("9P2000"));
+ recv9p(); /* Rversion */
+ ctx.version = LIB9P_VER_9P2000;
+ send9p(Tattach, .tag=0, .fid=0, .afid=LIB9P_FID_NOFID, .uname=lib9p_str("nobody"), .aname=lib9p_str(""));
+ recv9p(); /* Rattach */
+
+ /* flush, but original response comes back first */
+ wname[0] = lib9p_str("slowread"); send9p(Twalk, .tag=0, .fid=0, .newfid=1, .nwname=1, .wname=wname);
+ recv9p(); /* Rwalk */
+ send9p(Topen, .tag=0, .fid=1, .mode=LIB9P_O_MODE_READ);
+ recv9p(); /* Ropen */
+ send9p(Tread, .tag=1, .fid=1, .offset=0, .count=6);
+ send9p(Tflush, .tag=2, .oldtag=1);
+ recv9p(); /* Rread */
+ recv9p(); /* Rflush */
+
+ /* flush, original request is aborted with error */
+ wname[0] = lib9p_str("slowread-flushable"); send9p(Twalk, .tag=1, .fid=0, .newfid=2, .nwname=1, .wname=wname);
+ recv9p(); /* Rwalk */
+ send9p(Topen, .tag=0, .fid=2, .mode=LIB9P_O_MODE_READ);
+ recv9p(); /* Ropen */
+ send9p(Tread, .tag=1, .fid=2, .offset=0, .count=6);
+ send9p(Tflush, .tag=2, .oldtag=1);
+ recv9p(); /* Rerror */
+ recv9p(); /* Rflush */
+
+ /* flush, unknown tag */
+ send9p(Tflush, .tag=0, .oldtag=99);
+ recv9p(); /* Rflush */
+
+ /* shutdown ***********************************************************/
+ send9p(Tversion, .tag=0, .max_msg_size=(8*1024), .version=lib9p_str("9P2000"));
+ recv9p(); /* Rversion */
+ ctx.version = LIB9P_VER_9P2000;
+ send9p(Tattach, .tag=0, .fid=0, .afid=LIB9P_FID_NOFID, .uname=lib9p_str("nobody"), .aname=lib9p_str(""));
+ recv9p(); /* Rattach */
+ /* check the newfid==fid case */
+ wname[0] = lib9p_str("shutdown"); send9p(Twalk, .tag=0, .fid=0, .newfid=0, .nwname=1, .wname=wname);
+ recv9p(); /* Rwalk */
+ send9p(Topen, .tag=0, .fid=0, .mode=LIB9P_O_MODE_WRITE);
+ recv9p(); /* Ropen */
+ send9p(Twrite, .tag=0, .fid=0, .offset=0, .count=2, .data="1\n");
+ recv9p(); /* Rwrite */
+ return 0;
+}
diff --git a/lib9p/tests/testclient-sess.explog b/lib9p/tests/testclient-sess.explog
new file mode 100644
index 0000000..6aab242
--- /dev/null
+++ b/lib9p/tests/testclient-sess.explog
@@ -0,0 +1,74 @@
+# lib9p/tests/testclient-sess.explog - Expected 9P logfile of testclient-sess.c
+#
+# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+# numeric downgrade, unknown ext ###############################################
+> Tversion { tag=0 max_msg_size=57 version="9P2025.x" }
+< Rversion { tag=0 max_msg_size=57 version="9P2000" }
+
+# numeric downgrade, known ext #################################################
+> Tversion { tag=0 max_msg_size=57 version="9P2025.u" }
+< Rversion { tag=0 max_msg_size=57 version="9P2000.u" }
+
+# ext version, users ###########################################################
+> Tversion { tag=0 max_msg_size=8192 version="9P2000.u" }
+< Rversion { tag=0 max_msg_size=4120 version="9P2000.u" }
+> Tattach { tag=0 fid=0 afid=NOFID uname="alice" aname="" n_uid=1000 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=1 } }
+> Tattach { tag=0 fid=1 afid=NOFID uname="bob" aname="" n_uid=1001 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=1 } }
+> Twalk { tag=0 fid=0 newfid=2 nwname=1 wname=["whoami" ] }
+< Rwalk { tag=0 nwqid=1 wqid=[{ type=(0) vers=1 path=8 } ] }
+> Twalk { tag=0 fid=1 newfid=3 nwname=1 wname=["whoami" ] }
+< Rwalk { tag=0 nwqid=1 wqid=[{ type=(0) vers=1 path=8 } ] }
+> Topen { tag=0 fid=2 mode=(MODE_READ) }
+< Ropen { tag=0 qid={ type=(0) vers=1 path=8 } iounit=0 }
+> Topen { tag=0 fid=3 mode=(MODE_READ) }
+< Ropen { tag=0 qid={ type=(0) vers=1 path=8 } iounit=0 }
+> Tread { tag=0 fid=2 offset=0 count=100 }
+< Rread { tag=0 count=11 data="1000 alice\n" }
+> Tread { tag=0 fid=3 offset=0 count=100 }
+< Rread { tag=0 count=9 data="1001 bob\n" }
+
+# flush ########################################################################
+> Tversion { tag=0 max_msg_size=8192 version="9P2000" }
+< Rversion { tag=0 max_msg_size=4120 version="9P2000" }
+> Tattach { tag=0 fid=0 afid=NOFID uname="nobody" aname="" n_uid=0 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=1 } }
+
+# flush, but original response comes back first
+> Twalk { tag=0 fid=0 newfid=1 nwname=1 wname=["slowread" ] }
+< Rwalk { tag=0 nwqid=1 wqid=[{ type=(0) vers=1 path=6 } ] }
+> Topen { tag=0 fid=1 mode=(MODE_READ) }
+< Ropen { tag=0 qid={ type=(0) vers=1 path=6 } iounit=0 }
+> Tread { tag=1 fid=1 offset=0 count=6 }
+> Tflush { tag=2 oldtag=1 }
+< Rread { tag=1 count=6 data="Sloth\n" }
+< Rflush { tag=2 }
+
+# flush, succeeds
+> Twalk { tag=1 fid=0 newfid=2 nwname=1 wname=["slowread-flushable" ] }
+< Rwalk { tag=1 nwqid=1 wqid=[{ type=(0) vers=1 path=7 } ] }
+> Topen { tag=0 fid=2 mode=(MODE_READ) }
+< Ropen { tag=0 qid={ type=(0) vers=1 path=7 } iounit=0 }
+> Tread { tag=1 fid=2 offset=0 count=6 }
+> Tflush { tag=2 oldtag=1 }
+< Rflush { tag=2 }
+< Rerror { tag=1 errstr="request canceled by flush" errnum=125 }
+
+# flush, unknown tag
+> Tflush { tag=0 oldtag=99 }
+< Rflush { tag=0 }
+
+# shutdown #####################################################################
+> Tversion { tag=0 max_msg_size=8192 version="9P2000" }
+< Rversion { tag=0 max_msg_size=4120 version="9P2000" }
+> Tattach { tag=0 fid=0 afid=NOFID uname="nobody" aname="" n_uid=0 }
+< Rattach { tag=0 qid={ type=(DIR) vers=1 path=1 } }
+> Twalk { tag=0 fid=0 newfid=0 nwname=1 wname=["shutdown" ] }
+< Rwalk { tag=0 nwqid=1 wqid=[{ type=(0) vers=1 path=5 } ] }
+> Topen { tag=0 fid=0 mode=(MODE_WRITE) }
+< Ropen { tag=0 qid={ type=(0) vers=1 path=5 } iounit=0 }
+> Twrite { tag=0 fid=0 offset=0 count=2 data="1\n" }
+< Rwrite { tag=0 count=2 }