diff options
Diffstat (limited to 'lib9p')
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 } |