diff options
79 files changed, 2007 insertions, 900 deletions
diff --git a/.editorconfig b/.editorconfig index f907b33..7aadb15 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,6 +15,8 @@ indent_style = tab insert_final_newline = true trim_trailing_whitespace = true +# By well-known name ########################################################### + [*.{c,h}] _mode = c @@ -27,20 +29,6 @@ _mode = cmake [*.md] _mode = markdown -[*.9p{,.wip}] -_mode = 9p - -[{lib9p/tests/test_server/static.h.gen,build-aux/embed-sources.h.gen,build-aux/lint-{generic,unknown},lib9p/tests/test_compile.c.gen}] -_mode = sh - -[{build-aux/lint-h,build-aux/lint-bin,build-aux/get-dscname,build-aux/linux-errno.txt.gen,libusb/include/libusb/tusb_helpers.h.gen,lib9p/tests/runtest}] -_mode = bash - -[{lib9p/proto.gen,lib9p/include/lib9p/linux-errno.h.gen,build-aux/stack.c.gen}] -_mode = python3 -indent_style = space -indent_size = 4 - [*.py] _mode = python3 indent_style = space @@ -49,11 +37,43 @@ indent_size = 4 [requirements.txt] _mode = pip -[**/Documentation/**.txt] -_mode = man-cat - [{.editorconfig,.gitmodules,.pylintrc}] _mode = ini [.gitignore] _mode = gitignore + +# By specific filename (non-lib9p) ############################################# + +[{build-aux/lint-{generic,unknown},build-aux/embed-sources.h.gen}] +_mode = sh + +[{build-aux/lint-{bin,h},build-aux/get-dscname,build-aux/valgrind,libusb/include/libusb/tusb_helpers.h.gen}] +_mode = bash + +[build-aux/stack.c.gen] +_mode = python3 +indent_style = space +indent_size = 4 + +[**/Documentation/**.txt] +_mode = man-cat + +# By specific filename (lib9p) ################################################# + +[lib9p/idl/*.9p{,.wip}] +_mode = 9p-idl + +[lib9p/tests/*.explog] +_mode = 9p-log + +[{lib9p/tests/test_server/static.h.gen,lib9p/tests/test_compile.c.gen}] +_mode = sh + +[{lib9p/linux-errno.txt.gen,lib9p/tests/runtest,lib9p/tests/testclient-p9p}] +_mode = bash + +[{lib9p/proto.gen,lib9p/include/lib9p/linux-errno.h.gen}] +_mode = python3 +indent_style = space +indent_size = 4 diff --git a/3rd-party/linux-errno.txt b/3rd-party/linux-errno.txt index 839efc2..5b450f0 100644 --- a/3rd-party/linux-errno.txt +++ b/3rd-party/linux-errno.txt @@ -1,4 +1,4 @@ -# 3rd-party/linux-errno.txt - Generated from build-aux/linux-errno.txt.gen and linux.git v6.7. DO NOT EDIT! +# 3rd-party/linux-errno.txt - Generated from lib9p/linux-errno.txt.gen and linux.git v6.14. DO NOT EDIT! 1 EPERM Operation not permitted 2 ENOENT No such file or directory 3 ESRCH No such process diff --git a/CMakeLists.txt b/CMakeLists.txt index 22756c1..9fa048f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,7 +112,7 @@ function(add_lib_test arg_libname arg_testname) target_include_directories("${arg_testname}" PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests) add_test( NAME "${arg_libname}/${arg_testname}" - COMMAND valgrind --error-exitcode=2 "./${arg_testname}" + COMMAND "${CMAKE_SOURCE_DIR}/build-aux/valgrind" "./${arg_testname}" ) endif() endfunction() diff --git a/GNUmakefile b/GNUmakefile index f6ff6c3..904977d 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -34,7 +34,7 @@ generate/files += 3rd-party/COPYING.newlib.txt cp $< $@ generate/files += 3rd-party/linux-errno.txt -3rd-party/linux-errno.txt: build-aux/linux-errno.txt.gen +3rd-party/linux-errno.txt: lib9p/linux-errno.txt.gen $< $(linux.git) $@ generate/files += lib9p/include/lib9p/linux-errno.h @@ -78,9 +78,6 @@ generate-clean: platforms := rp2040 host # $(shell sed -nE 's/if *\(PICO_PLATFORM STREQUAL "(.*)"\)/\1/p' cmd/*/CMakeLists.txt) build_types = Debug Release RelWithDebInfo MinSizeRel -export CTEST_PARALLEL_LEVEL = 0 -export CTEST_OUTPUT_ON_FAILURE = 1 - build: $(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/build)) .PHONY: build @@ -91,12 +88,12 @@ $(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/build)): build/% $(MAKE) -C $(<D) .PHONY: $(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/build)) -check: build - $(MAKE) -j1 -k INNER=t $(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/check)) +check: + $(MAKE) -k INNER=t $(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/check)) .PHONY: check -$(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/check)): build/%/check: build/%/Makefile - $(MAKE) -C $(<D) test +$(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/check)): build/%/check: build/%/build + +cd $(@D) && ctest --output-on-failure $(if $(filter --jobserver-auth=%,$(MAKEFLAGS)),--parallel) .PHONY: $(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/check)) # `lint` and `format` ########################################################## @@ -109,15 +106,25 @@ build-aux/venv: build-aux/requirements.txt $@/bin/pip install -r $< touch --no-create $@ -# `lint` ########### +# `lint` ############################### +# generic ########## lint: $(MAKE) -k INNER=t $(patsubst sources_%,lint/%,$(filter sources_%,$(.VARIABLES))) # Only lint binaries if the build scripts pass lint. $(MAKE) INNER=t lint/bin lint/bin: build build-aux/lint-bin ./build-aux/lint-bin $(foreach t,$(build_types),build/rp2040-$t/cmd/sbc_harness/sbc_harness.elf) +lint/all: lint/%: build-aux/lint-generic build-aux/get-dscname + ./build-aux/lint-generic '%s\n' $(sources_$*) +lint/unknown: lint/%: build-aux/lint-unknown + ./build-aux/lint-unknown $(sources_$*) +.PHONY: lint lint/% +# specific ######### +lint/c: lint/%: build-aux/lint-h build-aux/get-dscname + ./build-aux/lint-h $(filter %.h,$(sources_$*)) lint/sh lint/bash: lint/%: shellcheck $(sources_$*) + shfmt --diff --case-indent --simplify $(sources_$*) lint/python3: lint/%: build-aux/venv ./build-aux/venv/bin/mypy --strict --scripts-are-modules $(sources_$*) ./build-aux/venv/bin/black --check $(sources_$*) @@ -125,28 +132,21 @@ lint/python3: lint/%: build-aux/venv ./build-aux/venv/bin/pylint $(sources_$*) ! grep -nh 'SPECIAL$$' -- lib9p/proto.gen lib9p/protogen/*.py ./build-aux/venv/bin/pytest $(foreach f,$(sources_python3),$(if $(filter test_%.py,$(notdir $f)),$f)) -lint/c: lint/%: build-aux/lint-h build-aux/get-dscname - ./build-aux/lint-h $(filter %.h,$(sources_$*)) -lint/make lint/cmake lint/gitignore lint/ini lint/9p lint/markdown lint/pip lint/man-cat: lint/%: - @: -lint/unknown: lint/%: build-aux/lint-unknown - ./build-aux/lint-unknown $(sources_$*) -lint/all: lint/%: build-aux/lint-generic build-aux/get-dscname - ./build-aux/lint-generic '%s\n' $(sources_$*) -.PHONY: lint lint/% +lint/make lint/cmake lint/gitignore lint/ini lint/9p-idl lint/9p-log lint/markdown lint/pip lint/man-cat: lint/%: + @: TODO: Write/adopt linters for these file types -# `format` ######### +# `format` ############################# +# generic ########## format: - $(MAKE) -k INNER=t $(patsubst sources_%,format/%,$(filter-out sources_all,$(filter sources_%,$(.VARIABLES)))) + $(MAKE) -k INNER=t $(patsubst sources_%,format/%,$(filter-out sources_all sources_unknown,$(filter sources_%,$(.VARIABLES)))) +.PHONY: format format/% +# specific ######### +format/c: format/%: + @: TODO: Adopt a C code-formatter +format/sh format/bash: format/%: + shfmt --write --case-indent --simplify $(sources_$*) format/python3: format/%: ./build-aux/venv ./build-aux/venv/bin/black $(sources_$*) ./build-aux/venv/bin/isort $(sources_$*) -format/sh format/bash: format/% - @: -format/c: format/%: - @: TODO: Adopt a C code-formatter -format/make format/cmake format/gitignore format/ini format/9p format/markdown format/pip format/man-cat: format/%: - @: -format/unknown: format/%: - @: -.PHONY: format format/% +format/make format/cmake format/gitignore format/ini format/9p-idl format/9p-log format/markdown format/pip format/man-cat: format/%: + @: TODO: Write/adopt formatters for these file types @@ -1,10 +1,20 @@ <!-- PLAN.md - Misc planning notes - Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> SPDX-License-Identifier: AGPL-3.0-or-later --> +- [ ] syslog on 9p +- [ ] better 9p-shutdown +- [ ] PIO-based USB +- [ ] wire the USB keyboard to 9P +- [ ] wire uart to 9p + +- SDcard to 9p +- PicoDVI +- solder up the bus switch + - with hardware I have: 1. [X] type "hello world" as a USB keyboard 2. [ ] get networking up (ping) @@ -19,5 +29,3 @@ 1. [ ] PicoDVI hello-world 2. [ ] "PiciDVI" to network 3. [ ] reverse the flow of PicoDVI - -https://hackaday.com/2022/08/26/bit-banged-ethernet-on-the-raspberry-pi-pico/ diff --git a/build-aux/embed-sources.h.gen b/build-aux/embed-sources.h.gen index 0329496..ee9eb42 100755 --- a/build-aux/embed-sources.h.gen +++ b/build-aux/embed-sources.h.gen @@ -6,5 +6,5 @@ nm --format=posix "$@" | sed -n -E \ - -e 's/(.*_(end|start)) [DR] .*/extern char \1[];/p' \ - -e 's/(.*_size) A .*/extern size_t \1;/p' + -e 's/(.*_(end|start)) [DR] .*/extern char \1[];/p' \ + -e 's/(.*_size) A .*/extern size_t \1;/p' diff --git a/build-aux/lint-bin b/build-aux/lint-bin index 78ed19f..91f1612 100755 --- a/build-aux/lint-bin +++ b/build-aux/lint-bin @@ -62,7 +62,7 @@ lint_globals() { cd "$rel_base" total=0 while read -r symbol addr size source; do - if (( addr == 0 )); then + if ((addr == 0)); then continue fi case "$source" in @@ -103,8 +103,9 @@ lint_stack() { fi done < <( comm -3 \ - <(sed -En 's/^included: (.*:)?//p' "${in_elffile%.elf}_stack.c" | sort -u) \ - <(readelf_funcs "$in_elffile" | sed -E -e 's/\.part\.[0-9]*$//' -e 's/^__(.*)_veneer$/\1/' | sort -u)) + <(sed -En 's/^included: (.*:)?//p' "${in_elffile%.elf}_stack.c" | sort -u) \ + <(readelf_funcs "$in_elffile" | sed -E -e 's/\.part\.[0-9]*$//' -e 's/^__(.*)_veneer$/\1/' | sort -u) + ) } lint_func_blocklist() { @@ -118,8 +119,8 @@ lint_func_blocklist() { while read -r func; do err "$in_elffile" "Contains blocklisted function: ${func}" done < <(readelf --syms --wide -- "$in_elffile" | - awk '$4 == "FUNC" { print $8 }' | - grep -Fx "${blocklist[@]/#/-e}") + awk '$4 == "FUNC" { print $8 }' | + grep -Fx "${blocklist[@]/#/-e}") } main() { @@ -130,8 +131,8 @@ main() { { echo 'Global variables:' lint_globals "${elf}.map" | sed 's/^/ /' - } > "${elf%.elf}.lint.globals" - (lint_stack "$elf") &> "${elf%.elf}.lint.stack" + } >"${elf%.elf}.lint.globals" + (lint_stack "$elf") &>"${elf%.elf}.lint.stack" lint_func_blocklist "$elf" done diff --git a/build-aux/lint-generic b/build-aux/lint-generic index 58a65d2..70e814a 100755 --- a/build-aux/lint-generic +++ b/build-aux/lint-generic @@ -25,12 +25,17 @@ for filename in "$@"; do if [ -x "$filename" ] && [ -z "$shebang" ]; then err "$filename" 'is executable but does not have a shebang' elif [ -n "$shebang" ] && ! [ -x "$filename" ]; then - err "$filename" 'has a shebang but is executable' + err "$filename" 'has a shebang but is not executable' fi if ! grep -E -q 'Copyright \(C\) 202[4-9]((-|, )202[5-9])* Luke T. Shumaker' "$filename"; then err "$filename" 'is missing a copyright statement' fi + if test -e .git && ! git diff --quiet milestone/2025-01-01 HEAD -- "$filename"; then + if ! grep -E -q 'Copyright \(C\) .*2025 Luke T. Shumaker' "$filename"; then + err "$filename" 'has an outdated copyright statement' + fi + fi if ! grep -q '\sSPDX-License-Identifier[:] ' "$filename"; then err "$filename" 'is missing an SPDX-License-Identifier' fi diff --git a/build-aux/lint-h b/build-aux/lint-h index 26ac13d..7459032 100755 --- a/build-aux/lint-h +++ b/build-aux/lint-h @@ -18,9 +18,9 @@ for filename in "$@"; do guard=${dscname//'/'/'_'} guard=${guard//'.'/'_'} guard="_${guard^^}_" - if ! { grep -Fxq "#ifndef ${guard}" "$filename" && - grep -Fxq "#define ${guard}" "$filename" && - grep -Fxq "#endif /* ${guard} */" "$filename"; }; then + if ! { grep -Fxq "#ifndef ${guard}" "$filename" && + grep -Fxq "#define ${guard}" "$filename" && + grep -Fxq "#endif /* ${guard} */" "$filename"; }; then err "$filename" "does not have ${guard} guard" fi done diff --git a/build-aux/measurestack/app_plugins.py b/build-aux/measurestack/app_plugins.py index 36e661b..6eeb35b 100644 --- a/build-aux/measurestack/app_plugins.py +++ b/build-aux/measurestack/app_plugins.py @@ -247,8 +247,8 @@ class LibCRIPCPlugin: return None if "/chan.c:" in loc and "front->dequeue(" in line: return [ - QName("_cr_chan_dequeue"), - QName("_cr_select_dequeue"), + QName("cr_chan_dequeue"), + QName("cr_select_dequeue"), ], False return None diff --git a/build-aux/valgrind b/build-aux/valgrind new file mode 100755 index 0000000..7ad2712 --- /dev/null +++ b/build-aux/valgrind @@ -0,0 +1,16 @@ +#!/bin/env bash +# build-aux/valgrind - Wrapper around valgrind to keep flags consistent +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +exec \ + valgrind \ + --fair-sched=yes \ + --error-exitcode=2 \ + --leak-check=full \ + --show-leak-kinds=all \ + --errors-for-leak-kinds=all \ + --show-error-list=all \ + -- \ + "$@" diff --git a/cmd/sbc_harness/fs_harness_flash_bin.c b/cmd/sbc_harness/fs_harness_flash_bin.c index bdb8da4..bcdf296 100644 --- a/cmd/sbc_harness/fs_harness_flash_bin.c +++ b/cmd/sbc_harness/fs_harness_flash_bin.c @@ -4,6 +4,8 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +#include <string.h> + #include <hardware/flash.h> #include <hardware/watchdog.h> diff --git a/cmd/sbc_harness/main.c b/cmd/sbc_harness/main.c index 25b122c..7765ca8 100644 --- a/cmd/sbc_harness/main.c +++ b/cmd/sbc_harness/main.c @@ -234,4 +234,5 @@ int main() { infof("==================================================================="); coroutine_add("init", init_cr, NULL); coroutine_main(); + assert_notreached("all coroutines exited"); } diff --git a/cmd/sbc_harness/usb_keyboard.h b/cmd/sbc_harness/usb_keyboard.h index 210014d..cf8483b 100644 --- a/cmd/sbc_harness/usb_keyboard.h +++ b/cmd/sbc_harness/usb_keyboard.h @@ -1,6 +1,6 @@ /* sbc_harness/usb_keyboard.h - Implementation of a USB keyboard device * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -12,7 +12,7 @@ #include <libcr/coroutine.h> /* for COROUTINE */ #include <libcr_ipc/rpc.h> /* for CR_RPC_DECLARE */ -CR_RPC_DECLARE(usb_keyboard_rpc, uint32_t, int) +CR_RPC_DECLARE(usb_keyboard_rpc, uint32_t, int); void usb_keyboard_init(void); COROUTINE usb_keyboard_cr(void *arg); diff --git a/lib9p/9p.generated.c b/lib9p/9p.generated.c index b58a485..284a0a6 100644 --- a/lib9p/9p.generated.c +++ b/lib9p/9p.generated.c @@ -2019,11 +2019,11 @@ 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 */ } @@ -2353,7 +2353,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) { @@ -3154,7 +3154,7 @@ static bool marshal_Rattach(struct lib9p_ctx *ctx, struct lib9p_msg_Rattach *val } 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,11 +3172,11 @@ 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; @@ -3715,7 +3715,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 +4665,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" @@ -5141,11 +5140,11 @@ static void lib9p_msg_Rerror_format(struct lib9p_msg_Rerror *self, struct fmt_st 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, " }"); } @@ -5273,7 +5272,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 +5297,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 +6773,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 +7107,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 +7334,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 +7366,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, " }"); } 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/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..652660c 100644 --- a/lib9p/idl/2010-9P2000.L.9p +++ b/lib9p/idl/2010-9P2000.L.9p @@ -169,7 +169,7 @@ num lock_status = 1 "GRACE=3" #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 7e901a3..a53f117 100644 --- a/lib9p/include/lib9p/9p.generated.h +++ b/lib9p/include/lib9p/9p.generated.h @@ -7,7 +7,7 @@ #include <stdint.h> /* for uint{n}_t types */ #include <libfmt/fmt.h> /* for fmt_formatter */ -#include <libhw/generic/net.h> /* for struct iovec */ +#include <libhw/generic/io.h> /* for struct iovec */ /* config *********************************************************************/ @@ -975,9 +975,9 @@ LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Topenfd, lib9p_msg_Topenfd); /* 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); @@ -987,7 +987,7 @@ LO_IMPLEMENTATION_H(fmt_formatter, struct lib9p_msg_Rerror, lib9p_msg_Rerror); /* 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/9p.h b/lib9p/include/lib9p/9p.h index 5919260..42381cf 100644 --- a/lib9p/include/lib9p/9p.h +++ b/lib9p/include/lib9p/9p.h @@ -166,7 +166,7 @@ static inline void lib9p_stat_assert(struct lib9p_stat stat) { * @return whether there was an error */ bool lib9p_stat_validate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, - uint32_t *ret_net_size, ssize_t *ret_host_size); + uint32_t *ret_net_size, size_t *ret_host_size); /** * Unmarshal the 9P `net_bytes` into the C struct `ret_obj`. diff --git a/lib9p/include/lib9p/linux-errno.h b/lib9p/include/lib9p/linux-errno.h index e7c74f5..e864fb6 100644 --- a/lib9p/include/lib9p/linux-errno.h +++ b/lib9p/include/lib9p/linux-errno.h @@ -1,5 +1,5 @@ /* lib9p/linux-errno.h - Generated by `lib9p/include/lib9p/linux-errno.h.gen 3rd-party/linux-errno.txt`. DO NOT EDIT! */ -/* 3rd-party/linux-errno.txt - Generated from build-aux/linux-errno.txt.gen and linux.git v6.7. DO NOT EDIT! */ +/* 3rd-party/linux-errno.txt - Generated from lib9p/linux-errno.txt.gen and linux.git v6.14. DO NOT EDIT! */ #ifndef _LIB9P_LINUX_ERRNO_H_ #define _LIB9P_LINUX_ERRNO_H_ diff --git a/lib9p/include/lib9p/srv.h b/lib9p/include/lib9p/srv.h index 070cf5a..85fc6bd 100644 --- a/lib9p/include/lib9p/srv.h +++ b/lib9p/include/lib9p/srv.h @@ -19,7 +19,7 @@ /* context ********************************************************************/ -CR_CHAN_DECLARE(_lib9p_srv_flushch, bool) +CR_CHAN_DECLARE(_lib9p_srv_flushch, bool); struct lib9p_srv_ctx { struct lib9p_ctx basectx; @@ -33,7 +33,7 @@ struct lib9p_srv_ctx { bool lib9p_srv_flush_requested(struct lib9p_srv_ctx *ctx); -int lib9p_srv_acknowledge_flush(struct lib9p_srv_ctx *ctx); +void lib9p_srv_acknowledge_flush(struct lib9p_srv_ctx *ctx); /* interface definitions ******************************************************/ @@ -132,12 +132,13 @@ 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_req *, bool); 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 */ /* For internal use */ BEGIN_PRIVATE(LIB9P_SRV_H); diff --git a/build-aux/linux-errno.txt.gen b/lib9p/linux-errno.txt.gen index f94178f..687e58b 100755 --- a/build-aux/linux-errno.txt.gen +++ b/lib9p/linux-errno.txt.gen @@ -1,7 +1,7 @@ #!/usr/bin/env bash -# build-aux/linux-errno.txt.gen - Generate a listing of Linux kernel errnos +# lib9p/linux-errno.txt.gen - Generate a listing of Linux kernel errnos # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later set -e 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/protogen/h.py b/lib9p/protogen/h.py index 3b33419..8f7fba2 100644 --- a/lib9p/protogen/h.py +++ b/lib9p/protogen/h.py @@ -166,7 +166,7 @@ def gen_h(versions: set[str], typs: list[idl.UserType]) -> str: #include <stdint.h> /* for uint{{n}}_t types */ #include <libfmt/fmt.h> /* for fmt_formatter */ -#include <libhw/generic/net.h> /* for struct iovec */ +#include <libhw/generic/io.h> /* for struct iovec */ """ id2typ: dict[int, idl.Message] = {} diff --git a/lib9p/srv.c b/lib9p/srv.c index a425dc9..ea8b932 100644 --- a/lib9p/srv.c +++ b/lib9p/srv.c @@ -6,8 +6,10 @@ #include <alloca.h> #include <inttypes.h> /* for PRI* */ -#include <stddef.h> /* for size_t */ #include <limits.h> /* for SSIZE_MAX, not set by newlib */ +#include <stddef.h> /* for size_t */ +#include <stdlib.h> /* for malloc() */ +#include <string.h> /* for memcpy() */ #ifndef SSIZE_MAX #define SSIZE_MAX (SIZE_MAX >> 1) #endif @@ -15,7 +17,6 @@ #include <libcr/coroutine.h> #include <libcr_ipc/chan.h> #include <libcr_ipc/mutex.h> -#include <libcr_ipc/select.h> #include <libmisc/assert.h> #include <libmisc/endian.h> #include <libhw/generic/net.h> @@ -56,12 +57,11 @@ bool lib9p_srv_flush_requested(struct lib9p_srv_ctx *ctx) { return _lib9p_srv_flushch_can_send(&ctx->_flushch); } -int lib9p_srv_acknowledge_flush(struct lib9p_srv_ctx *ctx) { +void lib9p_srv_acknowledge_flush(struct lib9p_srv_ctx *ctx) { assert(ctx); 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); - return -1; } /* structs ********************************************************************/ @@ -160,6 +160,21 @@ struct _lib9p_srv_req { /* base utilities *************************************************************/ +static void msglog(struct _lib9p_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->ctx, 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->ctx.basectx, typ, hostmsg)); +#pragma GCC diagnostic pop +} + #define nonrespond_errorf errorf static ssize_t write_Rmsg(struct _lib9p_srv_req *req, struct lib9p_Rmsg_send_buf *resp) { @@ -179,10 +194,10 @@ static void respond_error(struct _lib9p_srv_req *req) { ssize_t r; struct lib9p_msg_Rerror host = { .tag = req->tag, - .ename = lib9p_strn(req->ctx.basectx.err_msg, + .errstr = lib9p_strn(req->ctx.basectx.err_msg, CONFIG_9P_MAX_ERR_SIZE), #if CONFIG_9P_ENABLE_9P2000_u - .errno = req->ctx.basectx.err_num, + .errnum = req->ctx.basectx.err_num, #endif }; @@ -190,8 +205,8 @@ static void respond_error(struct _lib9p_srv_req *req) { /* 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) + sess->rerror_overhead > sess->max_msg_size) + host.errstr.len = sess->max_msg_size - sess->rerror_overhead; struct lib9p_Rmsg_send_buf net; @@ -199,11 +214,7 @@ static void respond_error(struct _lib9p_srv_req *req) { 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 + msglog(req, LIB9P_TYP_Rerror, &host); r = write_Rmsg(req, &net); if (r < 0) nonrespond_errorf("write: %s", net_strerror(-r)); @@ -429,11 +440,7 @@ static void handle_message(struct _lib9p_srv_req *ctx) { 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 + msglog(ctx, typ, host_req); /* Handle it. */ tmessage_handlers[typ](ctx, (void *)host_req, (void *)host_resp); @@ -447,11 +454,7 @@ static void handle_message(struct _lib9p_srv_req *ctx) { 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 + msglog(ctx, typ+1, &host_resp); write_Rmsg(ctx, &net_resp); } if (host_req) @@ -553,10 +556,13 @@ static inline struct _srv_fidinfo *srv_util_fidsave(struct _lib9p_srv_req *ctx, 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); + if (srv_util_pathisdir(old_pathinfo)) { + if (!LO_IS_NULL(fidinfo->dir.io)) + LO_CALL(fidinfo->dir.io, iofree); + } else { + if (!LO_IS_NULL(fidinfo->file.io)) + LO_CALL(fidinfo->file.io, iofree); + } srv_util_pathfree(ctx, fidinfo->path); } else { lib9p_error(&ctx->ctx.basectx, @@ -816,13 +822,6 @@ static void handle_Twalk(struct _lib9p_srv_req *ctx, 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); } else { diff --git a/lib9p/tables.c b/lib9p/tables.c index 271b17b..86e3298 100644 --- a/lib9p/tables.c +++ b/lib9p/tables.c @@ -96,6 +96,7 @@ void _lib9p_unmarshal(const struct _lib9p_recv_tentry xxx_table[LIB9P_VER_NUM][0 enum lib9p_msg_type typ = net_bytes[4]; *ret_typ = typ; struct _lib9p_recv_tentry tentry = xxx_table[ctx->version][typ/2]; + assert(tentry.unmarshal); tentry.unmarshal(ctx, net_bytes, ret_body); } @@ -124,8 +125,9 @@ bool _lib9p_marshal(const struct _lib9p_send_tentry xxx_table[LIB9P_VER_NUM][0x8 .net_copied_size = 0, .net_copied = ret_copied, }; - struct _lib9p_send_tentry tentry = xxx_table[ctx->version][typ/2]; + assert(tentry.marshal); + bool ret_erred = tentry.marshal(ctx, body, &ret); if (ret_iov[ret.net_iov_cnt-1].iov_len == 0) ret.net_iov_cnt--; @@ -154,12 +156,12 @@ bool lib9p_Rmsg_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *bo /* `struct lib9p_stat` helpers ************************************************/ bool lib9p_stat_validate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, - uint32_t *ret_net_size, ssize_t *ret_host_size) { + uint32_t *ret_net_size, size_t *ret_host_size) { ssize_t host_size = _lib9p_stat_validate(ctx, net_size, net_bytes, ret_net_size); if (host_size < 0) return true; if (ret_host_size) - *ret_host_size = host_size; + *ret_host_size = (size_t)host_size; return false; } 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 379ea6d..6883391 100755 --- a/lib9p/tests/runtest +++ b/lib9p/tests/runtest @@ -1,65 +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 -port=$(python -c 'import socket; s=socket.socket(); s.bind(("", 0)); print(s.getsockname()[1]); s.close()') -valgrind --error-exitcode=2 ./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}" +build_aux=$(realpath --canonicalize-missing -- "${BASH_SOURCE[0]}/../../../build-aux") -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/4 2>/dev/null)" != socket:* ]]; do sleep 0.1; done - -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' + 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 'Documentation/') -expect_lines \ - '--r--r--r-- M 0 root root 166 Oct 7 2024 x' +logfile=$(mktemp -t lib9p-log.XXXXXXXXXX) +cleanup+=("rm -f -- ${logfile@Q}") -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 5313917..eb16165 100644 --- a/lib9p/tests/test_server/CMakeLists.txt +++ b/lib9p/tests/test_server/CMakeLists.txt @@ -9,6 +9,8 @@ if (PICO_PLATFORM STREQUAL "host") add_library(test_server_objs OBJECT main.c + fs_shutdown.c + fs_slowread.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 new file mode 100644 index 0000000..3f88985 --- /dev/null +++ b/lib9p/tests/test_server/fs_shutdown.c @@ -0,0 +1,93 @@ +/* lib9p/tests/test_server/fs_shutdown.c - /shutdown API endpoint + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#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); + +/* srv_file *******************************************************************/ + +static void shutdown_file_free(struct shutdown_file *self) { + assert(self); +} +static struct lib9p_qid shutdown_file_qid(struct shutdown_file *self) { + assert(self); + return (struct lib9p_qid){ + .type = LIB9P_QT_FILE, + .vers = 1, + .path = self->pathnum, + }; +} + +static struct lib9p_stat shutdown_file_stat(struct shutdown_file *self, struct lib9p_srv_ctx *ctx) { + assert(self); + assert(ctx); + return (struct lib9p_stat){ + .kern_type = 0, + .kern_dev = 0, + .file_qid = shutdown_file_qid(self), + .file_mode = 0222, + .file_atime = UTIL9P_ATIME, + .file_mtime = UTIL9P_MTIME, + .file_size = 0, + .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 shutdown_file_wstat(struct shutdown_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 shutdown_file_remove(struct shutdown_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 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); +} + +/* srv_fio ********************************************************************/ + +static void shutdown_file_iofree(struct shutdown_file *self) { + assert(self); +} + +static uint32_t shutdown_file_iounit(struct shutdown_file *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)) { + 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); + 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)) { + assert_notreached("not readable"); +} diff --git a/lib9p/tests/test_server/fs_shutdown.h b/lib9p/tests/test_server/fs_shutdown.h new file mode 100644 index 0000000..65956db --- /dev/null +++ b/lib9p/tests/test_server/fs_shutdown.h @@ -0,0 +1,23 @@ +/* lib9p/tests/test_server/fs_shutdown.h - /shutdown 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_SHUTDOWN_H_ +#define _LIB9P_TESTS_TEST_SERVER_FS_SHUTDOWN_H_ + +#include <util9p/static.h> +#include <libhw/host_net.h> + +struct shutdown_file { + char *name; + uint64_t pathnum; + + struct hostnet_tcp_listener *listeners; + size_t nlisteners; +}; +LO_IMPLEMENTATION_H(lib9p_srv_file, struct shutdown_file, shutdown_file); +#define lo_box_shutdown_file_as_lib9p_srv_file(obj) util9p_box(shutdown_file, obj) + +#endif /* _LIB9P_TESTS_TEST_SERVER_FS_SHUTDOWN_H_ */ diff --git a/lib9p/tests/test_server/fs_slowread.c b/lib9p/tests/test_server/fs_slowread.c new file mode 100644 index 0000000..520edd2 --- /dev/null +++ b/lib9p/tests/test_server/fs_slowread.c @@ -0,0 +1,101 @@ +/* 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 "fs_slowread.h" + +LO_IMPLEMENTATION_C(lib9p_srv_file, struct slowread_file, slowread_file, static); + +LO_IMPLEMENTATION_H(lib9p_srv_fio, struct slowread_file, slowread_file); +LO_IMPLEMENTATION_C(lib9p_srv_fio, struct slowread_file, slowread_file, 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); + return lo_box_slowread_file_as_lib9p_srv_fio(self); +} + +/* srv_fio ********************************************************************/ + +static void slowread_file_iofree(struct slowread_file *self) { + assert(self); +} + +static uint32_t slowread_file_iounit(struct slowread_file *self) { + assert(self); + return 0; +} + +static uint32_t slowread_file_pwrite(struct slowread_file *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_file_pread(struct slowread_file *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->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/main.c b/lib9p/tests/test_server/main.c index a31c083..2743629 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> @@ -17,6 +19,8 @@ #include <util9p/static.h> #include "static.h" +#include "fs_shutdown.h" +#include "fs_slowread.h" /* configuration **************************************************************/ @@ -36,121 +40,42 @@ 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, }, }; -/* api ************************************************************************/ - -struct api_file { - uint64_t pathnum; -}; -LO_IMPLEMENTATION_H(lib9p_srv_file, struct api_file, api); -LO_IMPLEMENTATION_H(lib9p_srv_fio, struct api_file, api); - -LO_IMPLEMENTATION_C(lib9p_srv_file, struct api_file, api, static); -LO_IMPLEMENTATION_C(lib9p_srv_fio, struct api_file, api, static); - -static void api_free(struct api_file *self) { - assert(self); -} -static struct lib9p_qid api_qid(struct api_file *self) { - assert(self); - return (struct lib9p_qid){ - .type = LIB9P_QT_FILE, - .vers = 1, - .path = self->pathnum, - }; -} - -static struct lib9p_stat api_stat(struct api_file *self, struct lib9p_srv_ctx *ctx) { - assert(self); - assert(ctx); - return (struct lib9p_stat){ - .kern_type = 0, - .kern_dev = 0, - .file_qid = api_qid(self), - .file_mode = 0222, - .file_atime = UTIL9P_ATIME, - .file_mtime = UTIL9P_MTIME, - .file_size = 0, - .file_name = lib9p_str("shutdown"), - .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 api_wstat(struct api_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 api_remove(struct api_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 api_file, api) - -static lo_interface lib9p_srv_fio api_fopen(struct api_file *self, struct lib9p_srv_ctx *ctx, bool, bool, bool) { - assert(self); - assert(ctx); - return lo_box_api_as_lib9p_srv_fio(self); -} - -static void api_iofree(struct api_file *self) { - assert(self); -} - -static uint32_t api_iounit(struct api_file *self) { - assert(self); - return 0; -} - -static uint32_t api_pwrite(struct api_file *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 (int i = 0; i < CONFIG_SRV9P_NUM_CONNS; i++) - LO_CALL(lo_box_hostnet_tcplist_as_net_stream_listener(&globals.listeners[i]), close); - return byte_count; -} -static void api_pread(struct api_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)) { - assert_notreached("not readable"); -} - -#define lo_box_api_as_lib9p_srv_file(obj) util9p_box(api, obj) - /* 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(N, STRNAME, SYMNAME, ...) \ + lo_box_##SYMNAME##_file_as_lib9p_srv_file(&((struct SYMNAME##_file){ \ + .name = STRNAME, \ + .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), - lo_box_api_as_lib9p_srv_file(&(struct api_file){.pathnum = PATH_COUNTER}), + 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), ); static lo_interface lib9p_srv_file get_root(struct lib9p_srv_ctx *LM_UNUSED(ctx), struct lib9p_s LM_UNUSED(treename)) { @@ -189,15 +114,41 @@ static COROUTINE init_cr(void *) { 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..81a7e50 --- /dev/null +++ b/lib9p/tests/testclient-p9p @@ -0,0 +1,61 @@ +#!/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=(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' + +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..3bfb0b0 --- /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="lukeshu" aname="" n_uid=0 } +< Rerror { tag=0 errstr="authentication not required" errnum=95 } +> Tattach { tag=0 fid=0 afid=NOFID uname="lukeshu" 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=361 data=<bytedata> } +> Tread { tag=0 fid=1 offset=361 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="lukeshu" aname="" n_uid=0 } +< Rerror { tag=0 errstr="authentication not required" errnum=95 } +> Tattach { tag=0 fid=0 afid=NOFID uname="lukeshu" 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="lukeshu" aname="" n_uid=0 } +< Rerror { tag=0 errstr="authentication not required" errnum=95 } +> Tattach { tag=0 fid=0 afid=NOFID uname="lukeshu" 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="lukeshu" aname="" n_uid=0 } +< Rerror { tag=0 errstr="authentication not required" errnum=95 } +> Tattach { tag=0 fid=0 afid=NOFID uname="lukeshu" 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="lukeshu" aname="" n_uid=0 } +< Rerror { tag=0 errstr="authentication not required" errnum=95 } +> Tattach { tag=0 fid=0 afid=NOFID uname="lukeshu" 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="lukeshu" aname="" n_uid=0 } +< Rerror { tag=0 errstr="authentication not required" errnum=95 } +> Tattach { tag=0 fid=0 afid=NOFID uname="lukeshu" 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..423dc2c --- /dev/null +++ b/lib9p/tests/testclient-sess.c @@ -0,0 +1,144 @@ +/* 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 ********************************************************/ + send9p(Tversion, .tag=0, .max_msg_size=57, .version=lib9p_str("9P2000.u")); + recv9p(); /* Rversion */ + ctx.version = LIB9P_VER_9P2000_u; + + /* main session *******************************************************/ + 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..b1f3085 --- /dev/null +++ b/lib9p/tests/testclient-sess.explog @@ -0,0 +1,58 @@ +# 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 ################################################################## +> Tversion { tag=0 max_msg_size=57 version="9P2000.u" } +< Rversion { tag=0 max_msg_size=57 version="9P2000.u" } + +# main session ################################################################# +> 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 } diff --git a/libcr/CMakeLists.txt b/libcr/CMakeLists.txt index 80a4ece..2e66020 100644 --- a/libcr/CMakeLists.txt +++ b/libcr/CMakeLists.txt @@ -30,7 +30,7 @@ function(add_libcr_matrix_test n defs) if ("CONFIG_COROUTINE_VALGRIND=1" IN_LIST defs) add_test( NAME "libcr/test_matrix${n}" - COMMAND valgrind --error-exitcode=2 "./test_matrix${n}" + COMMAND "${CMAKE_SOURCE_DIR}/build-aux/valgrind" "./test_matrix${n}" ) else() add_test( diff --git a/libcr_ipc/CMakeLists.txt b/libcr_ipc/CMakeLists.txt index 8545798..60d3f2d 100644 --- a/libcr_ipc/CMakeLists.txt +++ b/libcr_ipc/CMakeLists.txt @@ -7,18 +7,22 @@ add_library(libcr_ipc INTERFACE) target_include_directories(libcr_ipc PUBLIC INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include) target_sources(libcr_ipc INTERFACE chan.c - select.c + mutex.c + rpc.c + rwmutex.c + sema.c ) target_link_libraries(libcr_ipc INTERFACE libcr + libmisc ) set(ipc_tests chan - #select - #mutex - #owned_mutex + mutex rpc + rwmutex + select sema ) foreach(test IN LISTS ipc_tests) diff --git a/libcr_ipc/chan.c b/libcr_ipc/chan.c index 7dd1132..6ccfa44 100644 --- a/libcr_ipc/chan.c +++ b/libcr_ipc/chan.c @@ -4,11 +4,29 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +#include <alloca.h> /* for alloca() */ +#include <string.h> /* for memcpy() */ + +#include <libcr/coroutine.h> /* for cid_t, cr_* */ +#include <libmisc/assert.h> +#include <libmisc/rand.h> + #include <libcr_ipc/chan.h> -void _cr_chan_dequeue(void *_ch, size_t) { +/* base channels **************************************************************/ + +struct cr_chan_waiter { + lm_dll_node; + cid_t cid; + void *val_ptr; + void (*dequeue)(void *, size_t); + void *dequeue_arg1; + size_t dequeue_arg2; +}; + +void cr_chan_dequeue(void *_ch, size_t) { struct _cr_chan *ch = _ch; - _cr_ipc_dll_pop_from_front(&ch->waiters); + lm_dll_pop_from_front(&ch->waiters); } void _cr_chan_xfer(enum _cr_chan_waiter_typ self_typ, struct _cr_chan *ch, void *val_ptr, size_t val_size) { @@ -17,7 +35,7 @@ void _cr_chan_xfer(enum _cr_chan_waiter_typ self_typ, struct _cr_chan *ch, void if (ch->waiters.front && ch->waiter_typ != self_typ) { /* non-blocking fast-path */ /* Copy. */ - struct _cr_chan_waiter *front = _cr_ipc_dll_node_cast(struct _cr_chan_waiter, ch->waiters.front); + struct cr_chan_waiter *front = lm_dll_node_cast(struct cr_chan_waiter, ch->waiters.front); if (self_typ == _CR_CHAN_SENDER) memcpy(front->val_ptr, val_ptr, val_size); else @@ -27,14 +45,123 @@ void _cr_chan_xfer(enum _cr_chan_waiter_typ self_typ, struct _cr_chan *ch, void front->dequeue_arg2); cr_yield(); } else { /* blocking slow-path */ - struct _cr_chan_waiter self = { + struct cr_chan_waiter self = { .cid = cr_getcid(), .val_ptr = val_ptr, - .dequeue = _cr_chan_dequeue, + .dequeue = cr_chan_dequeue, .dequeue_arg1 = ch, }; - _cr_ipc_dll_push_to_rear(&ch->waiters, &self); + lm_dll_push_to_rear(&ch->waiters, &self); ch->waiter_typ = self_typ; cr_pause_and_yield(); } } + +/* select *********************************************************************/ + +enum cr_select_class { + CR_SELECT_CLASS_DEFAULT, + CR_SELECT_CLASS_BLOCKING, + CR_SELECT_CLASS_NONBLOCK, +}; + +struct cr_select_waiters { + size_t cnt; + struct cr_select_arg *args; + struct cr_chan_waiter *nodes; +}; + +static inline enum cr_select_class cr_select_getclass(struct cr_select_arg arg) { + switch (arg.op) { + case _CR_SELECT_OP_RECV: + if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_SENDER) + return CR_SELECT_CLASS_NONBLOCK; + else + return CR_SELECT_CLASS_BLOCKING; + case _CR_SELECT_OP_SEND: + if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_RECVER) + return CR_SELECT_CLASS_NONBLOCK; + else + return CR_SELECT_CLASS_BLOCKING; + case _CR_SELECT_OP_DEFAULT: + return CR_SELECT_CLASS_DEFAULT; + default: + assert_notreached("invalid arg.op"); + } +} + +void cr_select_dequeue(void *_waiters, size_t idx) { + struct cr_select_waiters *waiters = _waiters; + for (size_t i = 0; i < waiters->cnt; i++) + lm_dll_remove(&(waiters->args[i].ch->waiters), + &(waiters->nodes[i])); + waiters->cnt = idx; +} + +size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) { + size_t cnt_blocking = 0; + size_t cnt_nonblock = 0; + size_t cnt_default = 0; + + assert(arg_cnt); + assert(arg_vec); + cr_assert_in_coroutine(); + + for (size_t i = 0; i < arg_cnt; i++) { + switch (cr_select_getclass(arg_vec[i])) { + case CR_SELECT_CLASS_BLOCKING: + cnt_blocking++; + break; + case CR_SELECT_CLASS_NONBLOCK: + cnt_nonblock++; + break; + case CR_SELECT_CLASS_DEFAULT: + cnt_default++; + break; + } + } + + if (cnt_nonblock) { + size_t choice = rand_uint63n(cnt_nonblock); + for (size_t i = 0, seen = 0; i < arg_cnt; i++) { + if (cr_select_getclass(arg_vec[i]) == CR_SELECT_CLASS_NONBLOCK) { + if (seen == choice) { + _cr_chan_xfer(arg_vec[i].op == _CR_SELECT_OP_RECV + ? _CR_CHAN_RECVER + : _CR_CHAN_SENDER, + arg_vec[i].ch, + arg_vec[i].val_ptr, + arg_vec[i].val_siz); + return i; + } + seen++; + } + } + assert_notreached("should have returned from inside for() loop"); + } + + if (cnt_default) { + for (size_t i = 0; i < arg_cnt; i++) + if (cr_select_getclass(arg_vec[i]) == CR_SELECT_CLASS_DEFAULT) + return i; + assert_notreached("should have returned from inside for() loop"); + } + + struct cr_select_waiters waiters = { + .cnt = arg_cnt, + .args = arg_vec, + .nodes = alloca(sizeof(struct cr_chan_waiter) * arg_cnt), + }; + for (size_t i = 0; i < arg_cnt; i++) { + waiters.nodes[i] = (struct cr_chan_waiter){ + .cid = cr_getcid(), + .val_ptr = arg_vec[i].val_ptr, + .dequeue = cr_select_dequeue, + .dequeue_arg1 = &waiters, + .dequeue_arg2 = i, + }; + lm_dll_push_to_rear(&arg_vec[i].ch->waiters, &waiters.nodes[i]); + } + cr_pause_and_yield(); + return waiters.cnt; +} diff --git a/libcr_ipc/include/libcr_ipc/_linkedlist.h b/libcr_ipc/include/libcr_ipc/_linkedlist.h deleted file mode 100644 index 543e058..0000000 --- a/libcr_ipc/include/libcr_ipc/_linkedlist.h +++ /dev/null @@ -1,99 +0,0 @@ -/* libcr_ipc/_linkedlist.h - Common low-level linked lists for use in libcr_ipc - * - * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#ifndef _LIBCR_IPC__LINKEDLIST_H_ -#define _LIBCR_IPC__LINKEDLIST_H_ - -#include <libmisc/assert.h> - -/* singly linked list *********************************************************/ - -typedef struct __cr_ipc_sll_node { - struct __cr_ipc_sll_node *rear; -} _cr_ipc_sll_node; - -typedef struct { - _cr_ipc_sll_node *front, *rear; -} _cr_ipc_sll_root; - -#define _cr_ipc_sll_node_cast(node_typ, node_ptr) \ - ({ \ - static_assert(_Generic(node_ptr, _cr_ipc_sll_node *: 1, default: 0), \ - "typeof("#node_ptr") != _cr_ipc_sll_node *"); \ - assert(node_ptr); \ - static_assert(offsetof(node_typ, _cr_ipc_sll_node) == 0); \ - ((node_typ*)(node_ptr)); \ - }) - -static inline void _cr_ipc_sll_push_to_rear(_cr_ipc_sll_root *root, _cr_ipc_sll_node *node) { - assert(root); - node->rear = NULL; - if (root->rear) - root->rear->rear = node; - else - root->front = node; - root->rear = node; -} - -static inline void _cr_ipc_sll_pop_from_front(_cr_ipc_sll_root *root) { - assert(root); - assert(root->front); - root->front = root->front->rear; - if (!root->front) - root->rear = NULL; -} - -/* doubly linked list *********************************************************/ - -typedef struct __cr_ipc_dll_node { - struct __cr_ipc_dll_node *front, *rear; -} _cr_ipc_dll_node; - -typedef struct { - _cr_ipc_dll_node *front, *rear; -} _cr_ipc_dll_root; - -#define _cr_ipc_dll_node_cast(node_typ, node_ptr) \ - ({ \ - static_assert(_Generic(node_ptr, _cr_ipc_dll_node *: 1, default: 0), \ - "typeof("#node_ptr") != _cr_ipc_dll_node *"); \ - assert(node_ptr); \ - static_assert(offsetof(node_typ, _cr_ipc_dll_node) == 0); \ - ((node_typ*)(node_ptr)); \ - }) - -static inline void _cr_ipc_dll_push_to_rear(_cr_ipc_dll_root *root, _cr_ipc_dll_node *node) { - assert(root); - assert(node); - node->front = root->rear; - node->rear = NULL; - if (root->rear) - root->rear->rear = node; - else - root->front = node; - root->rear = node; -} - -static inline void _cr_ipc_dll_remove(_cr_ipc_dll_root *root, _cr_ipc_dll_node *node) { - assert(root); - assert(node); - if (node->front) - node->front->rear = node->rear; - else - root->front = node->rear; - if (node->rear) - node->rear->front = node->front; - else - root->rear = node->front; -} - -static inline void _cr_ipc_dll_pop_from_front(_cr_ipc_dll_root *root) { - assert(root); - assert(root->front); - _cr_ipc_dll_remove(root, root->front); -} - -#endif /* _LIBCR_IPC__LINKEDLIST_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/chan.h b/libcr_ipc/include/libcr_ipc/chan.h index dafc92d..80acdb8 100644 --- a/libcr_ipc/include/libcr_ipc/chan.h +++ b/libcr_ipc/include/libcr_ipc/chan.h @@ -9,11 +9,11 @@ #include <stdbool.h> /* for bool */ #include <stddef.h> /* for size_t */ -#include <string.h> /* for memcpy */ -#include <libcr/coroutine.h> /* for cid_t, cr_* */ +#include <libmisc/linkedlist.h> /* for lm_dll_root */ +#include <libmisc/macro.h> /* for LM_CAT2_() */ -#include <libcr_ipc/_linkedlist.h> +/* base channels **************************************************************/ /** * CR_CHAN_DECLARE(NAME, VAL_T) declares the following type and @@ -78,51 +78,97 @@ struct _cr_chan core; \ VAL_T vals[0]; \ } NAME##_t; \ - \ + \ static inline void NAME##_send(NAME##_t *ch, VAL_T val) { \ cr_assert_in_coroutine(); \ _cr_chan_xfer(_CR_CHAN_SENDER, &ch->core, &val, sizeof(val)); \ } \ - \ + \ static inline VAL_T NAME##_recv(NAME##_t *ch) { \ cr_assert_in_coroutine(); \ VAL_T val; \ _cr_chan_xfer(_CR_CHAN_RECVER, &ch->core, &val, sizeof(val)); \ return val; \ } \ - \ + \ static inline bool NAME##_can_send(NAME##_t *ch) { \ cr_assert_in_coroutine(); \ return ch->core.waiters.front && \ ch->core.waiter_typ == _CR_CHAN_RECVER; \ } \ - \ + \ static inline bool NAME##_can_recv(NAME##_t *ch) { \ cr_assert_in_coroutine(); \ return ch->core.waiters.front && \ ch->core.waiter_typ == _CR_CHAN_SENDER; \ - } + } \ + \ + extern int LM_CAT2_(_CR_CHAN_FORCE_SEMICOLON_, __COUNTER__) enum _cr_chan_waiter_typ { _CR_CHAN_SENDER, _CR_CHAN_RECVER, }; -struct _cr_chan_waiter { - _cr_ipc_dll_node; - cid_t cid; - void *val_ptr; - void (*dequeue)(void *, size_t); - void *dequeue_arg1; - size_t dequeue_arg2; -}; - struct _cr_chan { enum _cr_chan_waiter_typ waiter_typ; - _cr_ipc_dll_root waiters; + lm_dll_root waiters; }; -void _cr_chan_dequeue(void *_ch, size_t); void _cr_chan_xfer(enum _cr_chan_waiter_typ self_typ, struct _cr_chan *ch, void *val_ptr, size_t val_size); +/* cr_select arguments ********************************************************/ + +/** + * Do not populate cr_select_arg yourself; use the + * CR_SELECT_{RECV,SEND,DEFAULT} macros. + */ +struct cr_select_arg { + enum { + _CR_SELECT_OP_RECV, + _CR_SELECT_OP_SEND, + _CR_SELECT_OP_DEFAULT, + } op; + struct _cr_chan *ch; + void *val_ptr; + size_t val_siz; +}; + +#define CR_SELECT_RECV(CH, VALP) \ + /* The _valp temporary variable is to get the compiler to check that \ + * the types are compatible. */ \ + ((struct cr_select_arg){ \ + .op = _CR_SELECT_OP_RECV, \ + .ch = &((CH)->core), \ + .val_ptr = ({ typeof((CH)->vals[0]) *_valp = VALP; _valp; }), \ + .val_siz = sizeof((CH)->vals[0]), \ + }) +/* BUG: It's bogus that CR_SELECT_SEND takes VALP instead of VAL, but + * since we need an address, taking VAL would introduce uncomfortable + * questions about where VAL sits on the stack. */ +#define CR_SELECT_SEND(CH, VALP) \ + /* The _valp temporary variable is to get the compiler to check that \ + * the types are compatible. */ \ + ((struct cr_select_arg){ \ + .op = _CR_SELECT_OP_SEND, \ + .ch = &((CH)->core), \ + .val_ptr = ({ typeof((CH)->vals[0]) *_valp = VALP; _valp; }), \ + .val_siz = sizeof((CH)->vals[0]), \ + }) +#define CR_SELECT_DEFAULT \ + ((struct cr_select_arg){ \ + .op = _CR_SELECT_OP_DEFAULT, \ + }) + +/* cr_select_v(arg_cnt, arg_vec) **********************************************/ + +size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]); + +/* cr_select_l(arg1, arg2, arg3, ...) ******************************************/ + +#define cr_select_l(...) ({ \ + struct cr_select_arg _cr_select_args[] = { __VA_ARGS__ }; \ + cr_select_v(sizeof(_cr_select_args)/sizeof(_cr_select_args[0])); \ +}) + #endif /* _LIBCR_IPC_CHAN_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/mutex.h b/libcr_ipc/include/libcr_ipc/mutex.h index cba8c19..0f3c9c2 100644 --- a/libcr_ipc/include/libcr_ipc/mutex.h +++ b/libcr_ipc/include/libcr_ipc/mutex.h @@ -1,6 +1,6 @@ /* libcr_ipc/mutex.h - Simple mutexes for libcr * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -9,14 +9,8 @@ #include <stdbool.h> /* for bool */ -#include <libcr/coroutine.h> /* for cid_t, cr_* */ - -#include <libcr_ipc/_linkedlist.h> - -struct _cr_mutex_waiter { - _cr_ipc_sll_node; - cid_t cid; -}; +#include <libmisc/linkedlist.h> +#include <libmisc/private.h> /** * A cr_mutex_t is a fair mutex. @@ -27,8 +21,10 @@ struct _cr_mutex_waiter { * first place, then it has no business unlocking one. */ typedef struct { + BEGIN_PRIVATE(LIBCR_IPC_MUTEX_H); bool locked; - _cr_ipc_sll_root waiters; + lm_sll_root waiters; + END_PRIVATE(LIBCR_IPC_MUTEX_H); } cr_mutex_t; /** @@ -38,21 +34,7 @@ typedef struct { * @cr_pauses maybe * @cr_yields maybe */ -static inline void cr_mutex_lock(cr_mutex_t *mu) { - assert(mu); - cr_assert_in_coroutine(); - - if (!mu->locked) /* non-blocking fast-path */ - mu->locked = true; - else { /* blocking slow-path */ - struct _cr_mutex_waiter self = { - .cid = cr_getcid(), - }; - _cr_ipc_sll_push_to_rear(&mu->waiters, &self); - cr_pause_and_yield(); - } - assert(mu->locked); -} +void cr_mutex_lock(cr_mutex_t *mu); /** * Unlock the mutex. Unblocks a coroutine that is blocked on @@ -62,16 +44,6 @@ static inline void cr_mutex_lock(cr_mutex_t *mu) { * @cr_pauses never * @cr_yields never */ -static inline void cr_mutex_unlock(cr_mutex_t *mu) { - assert(mu); - cr_assert_in_coroutine(); - - assert(mu->locked); - if (mu->waiters.front) { - cr_unpause(_cr_ipc_sll_node_cast(struct _cr_mutex_waiter, mu->waiters.front)->cid); - _cr_ipc_sll_pop_from_front(&mu->waiters); - } else - mu->locked = false; -} +void cr_mutex_unlock(cr_mutex_t *mu); #endif /* _LIBCR_IPC_MUTEX_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/owned_mutex.h b/libcr_ipc/include/libcr_ipc/owned_mutex.h deleted file mode 100644 index c66240f..0000000 --- a/libcr_ipc/include/libcr_ipc/owned_mutex.h +++ /dev/null @@ -1,79 +0,0 @@ -/* libcr_ipc/owned_mutex.h - Owned mutexes for libcr - * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#ifndef _LIBCR_IPC_OWNED_MUTEX_H_ -#define _LIBCR_IPC_OWNED_MUTEX_H_ - -#include <stdbool.h> /* for bool */ - -#include <libcr/coroutine.h> /* for cid_t, cr_unpause(), cr_pause_and_yield() */ - -#include <libcr_ipc/_linkedlist.h> - -struct _cr_owned_mutex_waiter { - _cr_ipc_sll_node; - cid_t cid; -}; - -/** - * A cr_owned_mutex_t is a fair mutex that tracks not just whether it - * is locked, but which coroutine has it locked. - * - * None of the methods have `_from_intrhandler` variants because (1) - * an interrupt handler can't block, so it shouldn't ever lock a mutex - * because that can block; and (2) if it can't lock a mutex in the - * first place, then it has no business unlocking one. - */ -typedef struct { - cid_t owner; - _cr_ipc_sll_root waiters; -} cr_owned_mutex_t; - -/** - * Lock the mutex. Blocks if it is already locked. - * - * @runs_in coroutine - * @cr_pauses maybe - * @cr_yields maybe - */ -static inline void cr_owned_mutex_lock(cr_owned_mutex_t *mu) { - assert(mu); - - if (!mu->owner) /* non-blocking fast-path */ - mu->owner = cr_getcid(); - else { /* blocking slow-path */ - struct _cr_owned_mutex_waiter self = { - .cid = cr_getcid(), - }; - _cr_ipc_sll_push_to_rear(&mu->waiters, &self); - cr_pause_and_yield(); - } - assert(mu->owner == cr_getcid()); -} - -/** - * Unlock the mutex. Unblocks a coroutine that is blocked on - * cr_owned_mutex_lock(). Must be the called from the same coroutine - * that called cr_owned_mutex_lock(). - * - * @runs_in coroutine - * @cr_pauses never - * @cr_yields never - */ -static inline void cr_owned_mutex_unlock(cr_owned_mutex_t *mu) { - assert(mu); - - assert(mu->owner == cr_getcid()); - if (mu->waiters.front) { - cid_t waiter = _cr_ipc_sll_node_cast(struct _cr_owned_mutex_waiter, mu->waiters.front)->cid; - cr_unpause(waiter); - mu->owner = waiter; - _cr_ipc_sll_pop_from_front(&mu->waiters); - } else - mu->owner = 0; -} - -#endif /* _LIBCR_IPC_OWNED_MUTEX_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/rpc.h b/libcr_ipc/include/libcr_ipc/rpc.h index 0600399..f091685 100644 --- a/libcr_ipc/include/libcr_ipc/rpc.h +++ b/libcr_ipc/include/libcr_ipc/rpc.h @@ -8,11 +8,9 @@ #define _LIBCR_IPC_RPC_H_ #include <stdbool.h> /* for bool */ -#include <string.h> /* for memcpy() */ -#include <libcr/coroutine.h> /* for cid_t, cr_* */ - -#include <libcr_ipc/_linkedlist.h> +#include <libmisc/linkedlist.h> /* for lm_sll_root */ +#include <libmisc/macro.h> /* for LM_CAT2_() */ /** * CR_RPC_DECLARE(NAME, REQ_T, RESP_T) declares the following types @@ -136,95 +134,21 @@ *(req._resp) = resp; \ cr_unpause(req._requester); \ cr_yield(); \ - } + } \ + \ + extern int LM_CAT2_(_CR_RPC_FORCE_SEMICOLON_, __COUNTER__) enum _cr_rpc_waiter_typ { _CR_RPC_REQUESTER, _CR_RPC_RESPONDER, }; -struct _cr_rpc_requester { - _cr_ipc_sll_node; - cid_t cid; - void *req_ptr; /* where to read req from */ - void *resp_ptr; /* where to write resp to */ -}; - -struct _cr_rpc_responder { - _cr_ipc_sll_node; - /* before enqueued | after dequeued */ - /* -------------------+-------------------- */ - cid_t cid; /* responder cid | requester cid */ - void *ptr; /* where to write req | where to write resp */ -}; - struct _cr_rpc { enum _cr_rpc_waiter_typ waiter_typ; - _cr_ipc_sll_root waiters; + lm_sll_root waiters; }; -static inline void _cr_rpc_send_req(struct _cr_rpc *ch, - void *req_ptr, size_t req_size, - void *resp_ptr) -{ - assert(ch); - assert(req_ptr); - assert(resp_ptr); - - if (ch->waiters.front && ch->waiter_typ != _CR_RPC_REQUESTER) { /* fast-path (still blocks) */ - struct _cr_rpc_responder *responder = - _cr_ipc_sll_node_cast(struct _cr_rpc_responder, ch->waiters.front); - _cr_ipc_sll_pop_from_front(&ch->waiters); - /* Copy the req to the responder's stack. */ - memcpy(responder->ptr, req_ptr, req_size); - /* Notify the responder that we have done so. */ - cr_unpause(responder->cid); - responder->cid = cr_getcid(); - responder->ptr = resp_ptr; - /* Wait for the responder to set `*resp_ptr`. */ - cr_pause_and_yield(); - } else { /* blocking slow-path */ - struct _cr_rpc_requester self = { - .cid = cr_getcid(), - .req_ptr = req_ptr, - .resp_ptr = resp_ptr, - }; - _cr_ipc_sll_push_to_rear(&ch->waiters, &self); - /* Wait for a responder to both copy our req and sed - * `*resp_ptr`. */ - cr_pause_and_yield(); - } -} - -static inline void _cr_rpc_recv_req(struct _cr_rpc *ch, - void *req_ptr, size_t req_size, - void **ret_resp_ptr, - cid_t *ret_requester) -{ - assert(ch); - assert(req_ptr); - assert(ret_resp_ptr); - assert(ret_requester); - - if (ch->waiters.front && ch->waiter_typ != _CR_RPC_RESPONDER) { /* non-blocking fast-path */ - struct _cr_rpc_requester *requester = - _cr_ipc_sll_node_cast(struct _cr_rpc_requester, ch->waiters.front); - _cr_ipc_sll_pop_from_front(&ch->waiters); - - memcpy(req_ptr, requester->req_ptr, req_size); - *ret_requester = requester->cid; - *ret_resp_ptr = requester->resp_ptr; - } else { /* blocking slow-path */ - struct _cr_rpc_responder self = { - .cid = cr_getcid(), - .ptr = req_ptr, - }; - _cr_ipc_sll_push_to_rear(&ch->waiters, &self); - ch->waiter_typ = _CR_RPC_RESPONDER; - cr_pause_and_yield(); - *ret_requester = self.cid; - *ret_resp_ptr = self.ptr; - } -} +void _cr_rpc_send_req(struct _cr_rpc *ch, void *req_ptr, size_t req_size, void *resp_ptr); +void _cr_rpc_recv_req(struct _cr_rpc *ch, void *req_ptr, size_t req_size, void **ret_resp_ptr, cid_t *ret_requester); #endif /* _LIBCR_IPC_RPC_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/rwmutex.h b/libcr_ipc/include/libcr_ipc/rwmutex.h new file mode 100644 index 0000000..d48abe9 --- /dev/null +++ b/libcr_ipc/include/libcr_ipc/rwmutex.h @@ -0,0 +1,76 @@ +/* libcr_ipc/rwmutex.h - Simple read/write mutexes for libcr + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBCR_IPC_RWMUTEX_H_ +#define _LIBCR_IPC_RWMUTEX_H_ + +#include <stdbool.h> + +#include <libmisc/linkedlist.h> +#include <libmisc/private.h> + +/** + * A cr_rwmutex_t is a fair read/write mutex. + * + * A waiting writer blocks any new readers; this ensures that the + * writer is able to eventually get the lock. + * + * None of the methods have `_from_intrhandler` variants because (1) + * an interrupt handler can't block, so it shouldn't ever lock a mutex + * because that can block; and (2) if it can't lock a mutex in the + * first place, then it has no business unlocking one. + */ +typedef struct { + BEGIN_PRIVATE(LIBCR_IPC_RWMUTEX_H); + unsigned nreaders; + bool locked; + bool unpausing; + lm_sll_root waiters; + END_PRIVATE(LIBCR_IPC_RWMUTEX_H); +} cr_rwmutex_t; + +/** + * Lock the mutex for writing. Blocks if it is already locked. + * + * @runs_in coroutine + * @cr_pauses maybe + * @cr_yields maybe + */ +void cr_rwmutex_lock(cr_rwmutex_t *mu); + +/** + * Undo a single cr_rwmutex_lock() call. Unblocks either a single + * coroutine that is blocked on cr_rwmutex_lock() or arbitrarily many + * coroutines that are blocked on cr_rwmutex_rlock(). + * + * @runs_in coroutine + * @cr_pauses never + * @cr_yields never + */ +void cr_rwmutex_unlock(cr_rwmutex_t *mu); + +/** + * Lock the mutex for reading. Blocks if it is already locked for + * writing. + * + * @runs_in coroutine + * @cr_pauses maybe + * @cr_yields maybe + */ +void cr_rwmutex_rlock(cr_rwmutex_t *mu); + +/** + * Undo a single cr_rwmutext_rock() call. If the reader count is + * reduced to zero, unblocks a single coroutine that is blocked on + * cr_rwmutex_lock(). + * + * @runs_in coroutine + * @cr_pauses never + * @cr_yields never + */ +void cr_rwmutex_runlock(cr_rwmutex_t *mu); + +#endif /* _LIBCR_IPC_RWMUTEX_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/select.h b/libcr_ipc/include/libcr_ipc/select.h deleted file mode 100644 index 0e35351..0000000 --- a/libcr_ipc/include/libcr_ipc/select.h +++ /dev/null @@ -1,85 +0,0 @@ -/* libcr_ipc/select.h - Select between channels - * - * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#include <alloca.h> /* for alloca() */ -#include <stddef.h> /* for size_t */ - -#include <libmisc/assert.h> -#include <libmisc/rand.h> - -#include <libcr_ipc/chan.h> - -#ifndef _LIBCR_IPC_SELECT_H_ -#define _LIBCR_IPC_SELECT_H_ - -/* arguments ******************************************************************/ - -/** - * Do not populate cr_select_arg yourself; use the - * CR_SELECT_{RECV,SEND,DEFAULT} macros. - */ -struct cr_select_arg { - enum { - _CR_SELECT_OP_RECV, - _CR_SELECT_OP_SEND, - _CR_SELECT_OP_DEFAULT, - } op; - struct _cr_chan *ch; - void *val_ptr; - size_t val_siz; -}; - -#define CR_SELECT_RECV(CH, VALP) ({ \ - /* The _valp indirection is to get the \ - * compiler to check that the types are \ - * compatible. */ \ - typeof((CH)->vals[0]) *_valp = VALP; \ - ((struct cr_select_arg){ \ - .op = _CR_SELECT_OP_RECV, \ - .ch = &((CH)->core), \ - .val_ptr = _valp, \ - .val_siz = sizeof((CH)->vals[0]), \ - }); \ - }) -#define CR_SELECT_SEND(CH, VAL) ({ \ - typeof((CH)->vals[0]) val_lvalue = VAL; \ - ((struct cr_select_arg){ \ - .op = _CR_SELECT_OP_SEND, \ - .ch = &((CH)->core), \ - .val_ptr = &val_lvalue;, \ - .val_siz = sizeof((CH)->vals[0]), \ - }); \ - }) -#define CR_SELECT_DEFAULT \ - ((struct cr_select_arg){ \ - .op = _CR_SELECT_OP_DEFAULT, \ - }) - -/* cr_select_v(arg_cnt, arg_vec) **********************************************/ - -enum _cr_select_class { - _CR_SELECT_CLASS_DEFAULT, - _CR_SELECT_CLASS_BLOCKING, - _CR_SELECT_CLASS_NONBLOCK, -}; - -struct _cr_select_waiters { - size_t cnt; - struct cr_select_arg *args; - struct _cr_chan_waiter *nodes; -}; - -void _cr_select_dequeue(void *_waiters, size_t idx); -size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]); - -/* cr_select_l(arg1, arg2, arg3, ...) ******************************************/ - -#define cr_select_l(...) ({ \ - struct cr_select_arg _cr_select_args[] = { __VA_ARGS__ }; \ - cr_select_v(sizeof(_cr_select_args)/sizeof(_cr_select_args[0])); \ -}) - -#endif /* _LIBCR_IPC_SELECT_H_ */ diff --git a/libcr_ipc/include/libcr_ipc/sema.h b/libcr_ipc/include/libcr_ipc/sema.h index fcabf28..cc387f4 100644 --- a/libcr_ipc/include/libcr_ipc/sema.h +++ b/libcr_ipc/include/libcr_ipc/sema.h @@ -9,14 +9,8 @@ #include <stdbool.h> -#include <libcr/coroutine.h> /* for cid_t, cr_* */ - -#include <libcr_ipc/_linkedlist.h> - -struct _cr_sema_waiter { - _cr_ipc_sll_node; - cid_t cid; -}; +#include <libmisc/linkedlist.h> +#include <libmisc/private.h> /** * A cr_sema_t is a fair unbounded[1] counting semaphore. @@ -24,9 +18,11 @@ struct _cr_sema_waiter { * [1]: Well, UINT_MAX */ typedef struct { + BEGIN_PRIVATE(LIBCR_IPC_SEMA_H); unsigned int cnt; bool unpausing; - _cr_ipc_sll_root waiters; + lm_sll_root waiters; + END_PRIVATE(LIBCR_IPC_SEMA_H); } cr_sema_t; /** @@ -36,36 +32,14 @@ typedef struct { * @cr_pauses never * @cr_yields never */ -static inline void cr_sema_signal(cr_sema_t *sema) { - assert(sema); - cr_assert_in_coroutine(); - - bool saved = cr_save_and_disable_interrupts(); - sema->cnt++; - if (sema->waiters.front && !sema->unpausing) { - cr_unpause( - _cr_ipc_sll_node_cast(struct _cr_sema_waiter, sema->waiters.front)->cid); - sema->unpausing = true; - } - cr_restore_interrupts(saved); -} +void cr_sema_signal(cr_sema_t *sema); /** * Like cr_sema_signal(), but for use from an interrupt handler. * * @runs_in intrhandler */ -static inline void cr_sema_signal_from_intrhandler(cr_sema_t *sema) { - assert(sema); - cr_assert_in_intrhandler(); - - sema->cnt++; - if (sema->waiters.front && !sema->unpausing) { - cr_unpause_from_intrhandler( - _cr_ipc_sll_node_cast(struct _cr_sema_waiter, sema->waiters.front)->cid); - sema->unpausing = true; - } -} +void cr_sema_signal_from_intrhandler(cr_sema_t *sema); /** * Wait until the semaphore is >0, then decrement it. @@ -74,27 +48,6 @@ static inline void cr_sema_signal_from_intrhandler(cr_sema_t *sema) { * @cr_pauses maybe * @cr_yields maybe */ -static inline void cr_sema_wait(cr_sema_t *sema) { - assert(sema); - cr_assert_in_coroutine(); - - bool saved = cr_save_and_disable_interrupts(); - - struct _cr_sema_waiter self = { - .cid = cr_getcid(), - }; - _cr_ipc_sll_push_to_rear(&sema->waiters, &self); - if (sema->waiters.front != &self._cr_ipc_sll_node || !sema->cnt) - cr_pause_and_yield(); - assert(sema->waiters.front == &self._cr_ipc_sll_node && sema->cnt); - _cr_ipc_sll_pop_from_front(&sema->waiters); - sema->cnt--; - if (sema->cnt && sema->waiters.front) - cr_unpause( - _cr_ipc_sll_node_cast(struct _cr_sema_waiter, sema->waiters.front)->cid); - else - sema->unpausing = false; - cr_restore_interrupts(saved); -} +void cr_sema_wait(cr_sema_t *sema); #endif /* _LIBCR_IPC_SEMA_H_ */ diff --git a/libcr_ipc/mutex.c b/libcr_ipc/mutex.c new file mode 100644 index 0000000..b0ebe05 --- /dev/null +++ b/libcr_ipc/mutex.c @@ -0,0 +1,43 @@ +/* libcr_ipc/mutex.c - Simple mutexes for libcr + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr/coroutine.h> /* for cid_t, cr_* */ + +#define IMPLEMENTATION_FOR_LIBCR_IPC_MUTEX_H YES +#include <libcr_ipc/mutex.h> + +struct cr_mutex_waiter { + lm_sll_node; + cid_t cid; +}; + +void cr_mutex_lock(cr_mutex_t *mu) { + assert(mu); + cr_assert_in_coroutine(); + + if (!mu->locked) /* non-blocking fast-path */ + mu->locked = true; + else { /* blocking slow-path */ + struct cr_mutex_waiter self = { + .cid = cr_getcid(), + }; + lm_sll_push_to_rear(&mu->waiters, &self); + cr_pause_and_yield(); + } + assert(mu->locked); +} + +void cr_mutex_unlock(cr_mutex_t *mu) { + assert(mu); + cr_assert_in_coroutine(); + + assert(mu->locked); + if (mu->waiters.front) { + cr_unpause(lm_sll_node_cast(struct cr_mutex_waiter, mu->waiters.front)->cid); + lm_sll_pop_from_front(&mu->waiters); + } else + mu->locked = false; +} diff --git a/libcr_ipc/rpc.c b/libcr_ipc/rpc.c new file mode 100644 index 0000000..6d9422f --- /dev/null +++ b/libcr_ipc/rpc.c @@ -0,0 +1,83 @@ +/* libcr_ipc/rpc.c - Simple request/response system for libcr + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> /* for memcpy() */ + +#include <libcr/coroutine.h> /* for cid_t, cr_* */ + +#include <libcr_ipc/rpc.h> + +struct cr_rpc_requester { + lm_sll_node; + cid_t cid; + void *req_ptr; /* where to read req from */ + void *resp_ptr; /* where to write resp to */ +}; + +struct cr_rpc_responder { + lm_sll_node; + /* before enqueued | after dequeued */ + /* -------------------+-------------------- */ + cid_t cid; /* responder cid | requester cid */ + void *ptr; /* where to write req | where to write resp */ +}; + +void _cr_rpc_send_req(struct _cr_rpc *ch, void *req_ptr, size_t req_size, void *resp_ptr) { + assert(ch); + assert(req_ptr); + assert(resp_ptr); + + if (ch->waiters.front && ch->waiter_typ != _CR_RPC_REQUESTER) { /* fast-path (still blocks) */ + struct cr_rpc_responder *responder = + lm_sll_node_cast(struct cr_rpc_responder, ch->waiters.front); + lm_sll_pop_from_front(&ch->waiters); + /* Copy the req to the responder's stack. */ + memcpy(responder->ptr, req_ptr, req_size); + /* Notify the responder that we have done so. */ + cr_unpause(responder->cid); + responder->cid = cr_getcid(); + responder->ptr = resp_ptr; + /* Wait for the responder to set `*resp_ptr`. */ + cr_pause_and_yield(); + } else { /* blocking slow-path */ + struct cr_rpc_requester self = { + .cid = cr_getcid(), + .req_ptr = req_ptr, + .resp_ptr = resp_ptr, + }; + lm_sll_push_to_rear(&ch->waiters, &self); + /* Wait for a responder to both copy our req and sed + * `*resp_ptr`. */ + cr_pause_and_yield(); + } +} + +void _cr_rpc_recv_req(struct _cr_rpc *ch, void *req_ptr, size_t req_size, void **ret_resp_ptr, cid_t *ret_requester) { + assert(ch); + assert(req_ptr); + assert(ret_resp_ptr); + assert(ret_requester); + + if (ch->waiters.front && ch->waiter_typ != _CR_RPC_RESPONDER) { /* non-blocking fast-path */ + struct cr_rpc_requester *requester = + lm_sll_node_cast(struct cr_rpc_requester, ch->waiters.front); + lm_sll_pop_from_front(&ch->waiters); + + memcpy(req_ptr, requester->req_ptr, req_size); + *ret_requester = requester->cid; + *ret_resp_ptr = requester->resp_ptr; + } else { /* blocking slow-path */ + struct cr_rpc_responder self = { + .cid = cr_getcid(), + .ptr = req_ptr, + }; + lm_sll_push_to_rear(&ch->waiters, &self); + ch->waiter_typ = _CR_RPC_RESPONDER; + cr_pause_and_yield(); + *ret_requester = self.cid; + *ret_resp_ptr = self.ptr; + } +} diff --git a/libcr_ipc/rwmutex.c b/libcr_ipc/rwmutex.c new file mode 100644 index 0000000..4c5da81 --- /dev/null +++ b/libcr_ipc/rwmutex.c @@ -0,0 +1,100 @@ +/* libcr_ipc/rwmutex.c - Simple read/write mutexes for libcr + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr/coroutine.h> /* for cid_t, cr_* */ + +#define IMPLEMENTATION_FOR_LIBCR_IPC_RWMUTEX_H YES +#include <libcr_ipc/rwmutex.h> + +struct cr_rwmutex_waiter { + lm_sll_node; + bool is_reader; + cid_t cid; +}; + +void cr_rwmutex_lock(cr_rwmutex_t *mu) { + assert(mu); + cr_assert_in_coroutine(); + + struct cr_rwmutex_waiter self = { + .is_reader = false, + .cid = cr_getcid(), + }; + lm_sll_push_to_rear(&mu->waiters, &self); + if (mu->waiters.front != &self.lm_sll_node || mu->locked) + cr_pause_and_yield(); + assert(mu->waiters.front == &self.lm_sll_node); + + /* We now hold the lock (and are mu->waiters.front). */ + lm_sll_pop_from_front(&mu->waiters); + assert(mu->nreaders == 0); + mu->locked = true; + mu->unpausing = false; +} + +void cr_rwmutex_rlock(cr_rwmutex_t *mu) { + assert(mu); + cr_assert_in_coroutine(); + + struct cr_rwmutex_waiter self = { + .is_reader = true, + .cid = cr_getcid(), + }; + lm_sll_push_to_rear(&mu->waiters, &self); + if (mu->waiters.front != &self.lm_sll_node || (mu->locked && mu->nreaders == 0)) + cr_pause_and_yield(); + assert(mu->waiters.front == &self.lm_sll_node); + + /* We now hold the lock (and are mu->waiters.front). */ + lm_sll_pop_from_front(&mu->waiters); + mu->nreaders++; + mu->locked = true; + struct cr_rwmutex_waiter *waiter = + lm_sll_node_cast(struct cr_rwmutex_waiter, mu->waiters.front); + if (waiter && waiter->is_reader) { + assert(mu->unpausing); + cr_unpause(waiter->cid); + } else { + mu->unpausing = false; + } +} + +void cr_rwmutex_unlock(cr_rwmutex_t *mu) { + assert(mu); + cr_assert_in_coroutine(); + + assert(mu->locked); + assert(mu->nreaders == 0); + assert(!mu->unpausing); + if (mu->waiters.front) { + struct cr_rwmutex_waiter *waiter = + lm_sll_node_cast(struct cr_rwmutex_waiter, mu->waiters.front); + mu->unpausing = true; + cr_unpause(waiter->cid); + } else { + mu->locked = false; + } +} + +void cr_rwmutex_runlock(cr_rwmutex_t *mu) { + assert(mu); + cr_assert_in_coroutine(); + + assert(mu->locked); + assert(mu->nreaders > 0); + mu->nreaders--; + if (mu->nreaders == 0 && !mu->unpausing) { + if (mu->waiters.front) { + struct cr_rwmutex_waiter *waiter = + lm_sll_node_cast(struct cr_rwmutex_waiter, mu->waiters.front); + assert(!waiter->is_reader); + mu->unpausing = true; + cr_unpause(waiter->cid); + } else { + mu->locked = false; + } + } +} diff --git a/libcr_ipc/select.c b/libcr_ipc/select.c deleted file mode 100644 index 4bd9067..0000000 --- a/libcr_ipc/select.c +++ /dev/null @@ -1,102 +0,0 @@ -/* libcr_ipc/select.c - Select between channels - * - * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -#include <libcr_ipc/select.h> - -static inline enum _cr_select_class _cr_select_getclass(struct cr_select_arg arg) { - switch (arg.op) { - case _CR_SELECT_OP_RECV: - if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_SENDER) - return _CR_SELECT_CLASS_NONBLOCK; - else - return _CR_SELECT_CLASS_BLOCKING; - case _CR_SELECT_OP_SEND: - if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_RECVER) - return _CR_SELECT_CLASS_NONBLOCK; - else - return _CR_SELECT_CLASS_BLOCKING; - case _CR_SELECT_OP_DEFAULT: - return _CR_SELECT_CLASS_DEFAULT; - default: - assert_notreached("invalid arg.op"); - } -} - -void _cr_select_dequeue(void *_waiters, size_t idx) { - struct _cr_select_waiters *waiters = _waiters; - for (size_t i = 0; i < waiters->cnt; i++) - _cr_ipc_dll_remove(&(waiters->args[i].ch->waiters), - &(waiters->nodes[i])); - waiters->cnt = idx; -} - -size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) { - size_t cnt_blocking = 0; - size_t cnt_nonblock = 0; - size_t cnt_default = 0; - - assert(arg_cnt); - assert(arg_vec); - cr_assert_in_coroutine(); - - for (size_t i = 0; i < arg_cnt; i++) { - switch (_cr_select_getclass(arg_vec[i])) { - case _CR_SELECT_CLASS_BLOCKING: - cnt_blocking++; - break; - case _CR_SELECT_CLASS_NONBLOCK: - cnt_nonblock++; - break; - case _CR_SELECT_CLASS_DEFAULT: - cnt_default++; - break; - } - } - - if (cnt_nonblock) { - size_t choice = rand_uint63n(cnt_nonblock); - for (size_t i = 0, seen = 0; i < arg_cnt; i++) { - if (_cr_select_getclass(arg_vec[i]) == _CR_SELECT_CLASS_NONBLOCK) { - if (seen == choice) { - _cr_chan_xfer(arg_vec[i].op == _CR_SELECT_OP_RECV - ? _CR_CHAN_RECVER - : _CR_CHAN_SENDER, - arg_vec[i].ch, - arg_vec[i].val_ptr, - arg_vec[i].val_siz); - return i; - } - seen++; - } - } - assert_notreached("should have returned from inside for() loop"); - } - - if (cnt_default) { - for (size_t i = 0; i < arg_cnt; i++) - if (_cr_select_getclass(arg_vec[i]) == _CR_SELECT_CLASS_DEFAULT) - return i; - assert_notreached("should have returned from inside for() loop"); - } - - struct _cr_select_waiters waiters = { - .cnt = arg_cnt, - .args = arg_vec, - .nodes = alloca(sizeof(struct _cr_chan_waiter) * arg_cnt), - }; - for (size_t i = 0; i < arg_cnt; i++) { - waiters.nodes[i] = (struct _cr_chan_waiter){ - .cid = cr_getcid(), - .val_ptr = arg_vec[i].val_ptr, - .dequeue = _cr_select_dequeue, - .dequeue_arg1 = &waiters, - .dequeue_arg2 = i, - }; - _cr_ipc_dll_push_to_rear(&arg_vec[i].ch->waiters, &waiters.nodes[i]); - } - cr_pause_and_yield(); - return waiters.cnt; -} diff --git a/libcr_ipc/sema.c b/libcr_ipc/sema.c new file mode 100644 index 0000000..cb984b6 --- /dev/null +++ b/libcr_ipc/sema.c @@ -0,0 +1,64 @@ +/* libcr_ipc/sema.c - Simple semaphores for libcr + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr/coroutine.h> /* for cid_t, cr_* */ + +#define IMPLEMENTATION_FOR_LIBCR_IPC_SEMA_H YES +#include <libcr_ipc/sema.h> + +struct cr_sema_waiter { + lm_sll_node; + cid_t cid; +}; + +void cr_sema_signal(cr_sema_t *sema) { + assert(sema); + cr_assert_in_coroutine(); + + bool saved = cr_save_and_disable_interrupts(); + sema->cnt++; + if (sema->waiters.front && !sema->unpausing) { + cr_unpause( + lm_sll_node_cast(struct cr_sema_waiter, sema->waiters.front)->cid); + sema->unpausing = true; + } + cr_restore_interrupts(saved); +} + +void cr_sema_signal_from_intrhandler(cr_sema_t *sema) { + assert(sema); + cr_assert_in_intrhandler(); + + sema->cnt++; + if (sema->waiters.front && !sema->unpausing) { + cr_unpause_from_intrhandler( + lm_sll_node_cast(struct cr_sema_waiter, sema->waiters.front)->cid); + sema->unpausing = true; + } +} + +void cr_sema_wait(cr_sema_t *sema) { + assert(sema); + cr_assert_in_coroutine(); + + bool saved = cr_save_and_disable_interrupts(); + + struct cr_sema_waiter self = { + .cid = cr_getcid(), + }; + lm_sll_push_to_rear(&sema->waiters, &self); + if (sema->waiters.front != &self.lm_sll_node || !sema->cnt) + cr_pause_and_yield(); + assert(sema->waiters.front == &self.lm_sll_node && sema->cnt); + lm_sll_pop_from_front(&sema->waiters); + sema->cnt--; + if (sema->cnt && sema->waiters.front) + cr_unpause( + lm_sll_node_cast(struct cr_sema_waiter, sema->waiters.front)->cid); + else + sema->unpausing = false; + cr_restore_interrupts(saved); +} diff --git a/libcr_ipc/tests/config.h b/libcr_ipc/tests/config.h index b00508c..a648589 100644 --- a/libcr_ipc/tests/config.h +++ b/libcr_ipc/tests/config.h @@ -7,9 +7,9 @@ #ifndef _CONFIG_H_ #define _CONFIG_H_ -#define CONFIG_COROUTINE_STACK_SIZE_DEFAULT (4*1024) +#define CONFIG_COROUTINE_STACK_SIZE_DEFAULT (8*1024) #define CONFIG_COROUTINE_NAME_LEN 16 -#define CONFIG_COROUTINE_NUM 8 +#define CONFIG_COROUTINE_NUM 16 #define CONFIG_COROUTINE_MEASURE_STACK 1 #define CONFIG_COROUTINE_PROTECT_STACK 1 diff --git a/libcr_ipc/tests/test_chan.c b/libcr_ipc/tests/test_chan.c index 9d6eecf..9b6f018 100644 --- a/libcr_ipc/tests/test_chan.c +++ b/libcr_ipc/tests/test_chan.c @@ -1,6 +1,6 @@ /* libcr_ipc/tests/test_chan.c - Tests for <libcr_ipc/chan.h> * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -9,7 +9,7 @@ #include "test.h" -CR_CHAN_DECLARE(intchan, int) +CR_CHAN_DECLARE(intchan, int); COROUTINE cr_producer(void *_ch) { intchan_t *ch = _ch; diff --git a/libcr_ipc/tests/test_mutex.c b/libcr_ipc/tests/test_mutex.c new file mode 100644 index 0000000..43714c9 --- /dev/null +++ b/libcr_ipc/tests/test_mutex.c @@ -0,0 +1,37 @@ +/* libcr_ipc/tests/test_mutex.c - Tests for <libcr_ipc/mutex.h> + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr/coroutine.h> +#include <libcr_ipc/mutex.h> + +#include "test.h" + +int counter = 0; + +COROUTINE cr_worker(void *_mu) { + cr_mutex_t *mu = _mu; + cr_begin(); + + for (int i = 0; i < 100; i++) { + cr_mutex_lock(mu); + int a = counter; + cr_yield(); + counter = a + 1; + cr_mutex_unlock(mu); + cr_yield(); + } + + cr_end(); +} + +int main() { + cr_mutex_t mu = {}; + coroutine_add("a", cr_worker, &mu); + coroutine_add("b", cr_worker, &mu); + coroutine_main(); + test_assert(counter == 200); + return 0; +} diff --git a/libcr_ipc/tests/test_rpc.c b/libcr_ipc/tests/test_rpc.c index 4aff5ca..1e3c471 100644 --- a/libcr_ipc/tests/test_rpc.c +++ b/libcr_ipc/tests/test_rpc.c @@ -9,7 +9,7 @@ #include "test.h" -CR_RPC_DECLARE(intrpc, int, int) +CR_RPC_DECLARE(intrpc, int, int); /* Test that the RPC is fair, have worker1 start waiting first, and * ensure that it gets the first request. */ diff --git a/libcr_ipc/tests/test_rwmutex.c b/libcr_ipc/tests/test_rwmutex.c new file mode 100644 index 0000000..77e8c7c --- /dev/null +++ b/libcr_ipc/tests/test_rwmutex.c @@ -0,0 +1,88 @@ +/* libcr_ipc/tests/test_rwmutex.c - Tests for <libcr_ipc/rwmutex.h> + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> + +#include <libcr/coroutine.h> +#include <libcr_ipc/rwmutex.h> + +#include "test.h" + +cr_rwmutex_t mu = {}; +char out[10] = {0}; +size_t len = 0; + +COROUTINE cr1_reader(void *_mu) { + cr_rwmutex_t *mu = _mu; + cr_begin(); + + cr_rwmutex_rlock(mu); + out[len++] = 'r'; + cr_yield(); + cr_rwmutex_runlock(mu); + + cr_end(); +} + +COROUTINE cr1_writer(void *_mu) { + cr_rwmutex_t *mu = _mu; + cr_begin(); + + cr_rwmutex_lock(mu); + out[len++] = 'w'; + cr_yield(); + cr_rwmutex_unlock(mu); + + cr_end(); +} + +COROUTINE cr2_waiter(void *_ch) { + char ch = *(char *)_ch; + cr_begin(); + + cr_rwmutex_rlock(&mu); + out[len++] = ch; + cr_rwmutex_runlock(&mu); + + cr_end(); +} + +COROUTINE cr2_init(void *) { + cr_begin(); + + char ch; + cr_rwmutex_lock(&mu); + ch = 'a'; coroutine_add("wait-a", cr2_waiter, &ch); + ch = 'b'; coroutine_add("wait-b", cr2_waiter, &ch); + cr_yield(); + ch = 'c'; coroutine_add("wait-c", cr2_waiter, &ch); + cr_rwmutex_unlock(&mu); + + cr_end(); +} + +int main() { + printf("== test 1 =========================================\n"); + coroutine_add("r1", cr1_reader, &mu); + coroutine_add("r2", cr1_reader, &mu); + coroutine_add("w", cr1_writer, &mu); + coroutine_add("r3", cr1_reader, &mu); + coroutine_add("r4", cr1_reader, &mu); + coroutine_main(); + test_assert(len == 5); + test_assert(strcmp(out, "rrwrr") == 0); + + printf("== test 2 =========================================\n"); + mu = (cr_rwmutex_t){}; + len = 0; + memset(out, 0, sizeof(out)); + coroutine_add("init", cr2_init, NULL); + coroutine_main(); + test_assert(len == 3); + test_assert(strcmp(out, "abc") == 0); + + return 0; +} diff --git a/libcr_ipc/tests/test_select.c b/libcr_ipc/tests/test_select.c new file mode 100644 index 0000000..1db645b --- /dev/null +++ b/libcr_ipc/tests/test_select.c @@ -0,0 +1,90 @@ +/* libcr_ipc/tests/test_select.c - Tests for <libcr_ipc/select.h> + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libcr/coroutine.h> +#include <libcr_ipc/chan.h> + +#include "test.h" + +CR_CHAN_DECLARE(intchan, int); + +intchan_t ch[10] = {0}; +intchan_t fch = {0}; + +COROUTINE cr_consumer(void *) { + cr_begin(); + + struct cr_select_arg args[11]; + + bool chdone[10] = {0}; + int arg2ch[10]; + for (;;) { + int ret_ch; + int i_arg = 0; + for (int i_ch = 0; i_ch < 10; i_ch++) { + if (!chdone[i_ch]) { + args[i_arg] = CR_SELECT_RECV(&ch[i_ch], &ret_ch); + arg2ch[i_arg] = i_ch; + i_arg++; + } + } + if (!i_arg) + break; + args[i_arg] = CR_SELECT_DEFAULT; /* check that default doesn't trigger */ + test_assert(i_arg <= 10); + int ret_arg = cr_select_v(i_arg+1, args); + test_assert(ret_arg < i_arg); + test_assert(arg2ch[ret_arg] == ret_ch); + chdone[ret_ch] = true; + } + int ret_ch, ret_arg; + args[0] = CR_SELECT_RECV(&ch[0], &ret_ch); + args[1] = CR_SELECT_DEFAULT; + ret_arg = cr_select_v(2, args); + test_assert(ret_arg == 1); + + int send = 567; + args[0] = CR_SELECT_SEND(&fch, &send); + args[1] = CR_SELECT_DEFAULT; + ret_arg = cr_select_v(2, args); + test_assert(ret_arg == 0); + + send = 890; + args[0] = CR_SELECT_SEND(&fch, &send); + args[1] = CR_SELECT_DEFAULT; + ret_arg = cr_select_v(2, args); + test_assert(ret_arg == 1); + + cr_end(); +} + +COROUTINE cr_producer(void *_n) { + int n = *(int *)_n; + cr_begin(); + + intchan_send(&ch[n], n); + + cr_end(); +} + +COROUTINE cr_final(void *) { + cr_begin(); + + int ret = intchan_recv(&fch); + printf("ret=%d\n", ret); + test_assert(ret == 567); + + cr_end(); +} + +int main() { + for (int i = 0; i < 10; i++) + coroutine_add("producer", cr_producer, &i); + coroutine_add("consumer", cr_consumer, NULL); + coroutine_add("final", cr_final, NULL); + coroutine_main(); + return 0; +} diff --git a/libcr_ipc/tests/test_sema.c b/libcr_ipc/tests/test_sema.c index 3208237..e5b22a5 100644 --- a/libcr_ipc/tests/test_sema.c +++ b/libcr_ipc/tests/test_sema.c @@ -5,6 +5,8 @@ */ #include <libcr/coroutine.h> + +#define IMPLEMENTATION_FOR_LIBCR_IPC_SEMA_H YES /* so we can access .cnt */ #include <libcr_ipc/sema.h> #include "test.h" @@ -54,7 +56,7 @@ COROUTINE cr_consumer(void *_sema) { } int main() { - cr_sema_t sema = {0}; + cr_sema_t sema = {}; printf("== test 1 =========================================\n"); coroutine_add("first", cr_first, &sema); diff --git a/libhw_cr/host_util.c b/libhw_cr/host_util.c index 958ed9c..7b3200c 100644 --- a/libhw_cr/host_util.c +++ b/libhw_cr/host_util.c @@ -1,6 +1,6 @@ /* libhw_cr/host_util.c - Utilities for GNU/Linux hosts * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ diff --git a/libhw_cr/host_util.h b/libhw_cr/host_util.h index 8c53fab..02c04dc 100644 --- a/libhw_cr/host_util.h +++ b/libhw_cr/host_util.h @@ -1,6 +1,6 @@ /* libhw_cr/host_util.h - Utilities for GNU/Linux hosts * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ diff --git a/libhw_cr/rp2040_hwspi.c b/libhw_cr/rp2040_hwspi.c index 646d8ba..d181650 100644 --- a/libhw_cr/rp2040_hwspi.c +++ b/libhw_cr/rp2040_hwspi.c @@ -130,7 +130,7 @@ void _rp2040_hwspi_init(struct rp2040_hwspi *self, self->dma_tx_data = dma3; self->dma_rx_data = dma4; self->dead_until_ns = 0; - self->sema = (cr_sema_t){0}; + self->sema = (cr_sema_t){}; /* Initialize the interrupt handler. */ /* We do this on (just) the rx channel, because the way the diff --git a/libhw_cr/rp2040_include/libhw/w5500.h b/libhw_cr/rp2040_include/libhw/w5500.h index 8db6a58..8dda1a1 100644 --- a/libhw_cr/rp2040_include/libhw/w5500.h +++ b/libhw_cr/rp2040_include/libhw/w5500.h @@ -17,7 +17,7 @@ #include <libhw/generic/net.h> #include <libhw/generic/spi.h> -CR_CHAN_DECLARE(_w5500_sockintr_ch, uint8_t) +CR_CHAN_DECLARE(_w5500_sockintr_ch, uint8_t); struct _w5500_socket { BEGIN_PRIVATE(LIBHW_W5500_H); diff --git a/libmisc/CMakeLists.txt b/libmisc/CMakeLists.txt index 4599ead..c80e060 100644 --- a/libmisc/CMakeLists.txt +++ b/libmisc/CMakeLists.txt @@ -8,6 +8,7 @@ target_include_directories(libmisc PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/ target_sources(libmisc INTERFACE assert.c intercept.c + linkedlist.c log.c ) target_compile_options(libmisc INTERFACE "$<$<COMPILE_LANGUAGE:C>:-fplan9-extensions>") diff --git a/libmisc/include/libmisc/linkedlist.h b/libmisc/include/libmisc/linkedlist.h new file mode 100644 index 0000000..8adef66 --- /dev/null +++ b/libmisc/include/libmisc/linkedlist.h @@ -0,0 +1,46 @@ +/* libmisc/linkedlist.h - Singly- and doubly- linked lists + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _LIBMISC_LINKEDLIST_H_ +#define _LIBMISC_LINKEDLIST_H_ + +#include <libmisc/assert.h> +#include <libmisc/macro.h> + +/* singly linked list *********************************************************/ + +typedef struct _lm_sll_node { + struct _lm_sll_node *rear; +} lm_sll_node; + +typedef struct { + lm_sll_node *front, *rear; +} lm_sll_root; + +#define lm_sll_node_cast(node_typ, node_ptr) \ + LM_CAST_FIELD_TO_STRUCT(node_typ, lm_sll_node, node_ptr) + +void lm_sll_push_to_rear(lm_sll_root *root, lm_sll_node *node); +void lm_sll_pop_from_front(lm_sll_root *root); + +/* doubly linked list *********************************************************/ + +typedef struct _lm_dll_node { + struct _lm_dll_node *front, *rear; +} lm_dll_node; + +typedef struct { + lm_dll_node *front, *rear; +} lm_dll_root; + +#define lm_dll_node_cast(node_typ, node_ptr) \ + LM_CAST_FIELD_TO_STRUCT(node_typ, lm_dll_node, node_ptr) + +void lm_dll_push_to_rear(lm_dll_root *root, lm_dll_node *node); +void lm_dll_remove(lm_dll_root *root, lm_dll_node *node); +void lm_dll_pop_from_front(lm_dll_root *root); + +#endif /* _LIBMISC_LINKEDLIST_H_ */ diff --git a/libmisc/include/libmisc/macro.h b/libmisc/include/libmisc/macro.h index b3e235c..6cb15fb 100644 --- a/libmisc/include/libmisc/macro.h +++ b/libmisc/include/libmisc/macro.h @@ -14,9 +14,20 @@ #define LM_NEVER_INLINE [[gnu::noinline]] #define LM_FLATTEN [[gnu::flatten]] -/* numeric */ +/* types */ #define LM_ARRAY_LEN(ary) (sizeof(ary)/sizeof((ary)[0])) + +#define LM_CAST_FIELD_TO_STRUCT(STRUCT_TYP, FIELD_NAME, PTR_TO_FIELD) ({ \ + /* The _fptr assignment is to get the compiler to do type checking. */ \ + typeof(((STRUCT_TYP *)NULL)->FIELD_NAME) *_fptr = (PTR_TO_FIELD); \ + _fptr \ + ? ((STRUCT_TYP*)( ((void*)_fptr) - offsetof(STRUCT_TYP, FIELD_NAME))) \ + : NULL; \ +}) + +/* numeric */ + #define LM_CEILDIV(n, d) ( ((n)+(d)-1) / (d) ) /** Return ceil(n/d) */ #define LM_ROUND_UP(n, d) ( LM_CEILDIV(n, d) * (d) ) /** Return `n` rounded up to the nearest multiple of `d` */ #define LM_ROUND_DOWN(n, d) ( ((n)/(d)) * (d) ) /** Return `n` rounded down to the nearest multiple of `d` */ diff --git a/libmisc/include/libmisc/rand.h b/libmisc/include/libmisc/rand.h index bb1ec0b..7ef238b 100644 --- a/libmisc/include/libmisc/rand.h +++ b/libmisc/include/libmisc/rand.h @@ -1,6 +1,6 @@ /* libmisc/rand.h - Non-crytpographic random-number utilities * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ diff --git a/libmisc/linkedlist.c b/libmisc/linkedlist.c new file mode 100644 index 0000000..5fe0977 --- /dev/null +++ b/libmisc/linkedlist.c @@ -0,0 +1,62 @@ +/* libmisc/linkedlist.c - Singly- and doubly- linked lists + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <stddef.h> /* for NULL */ + +#include <libmisc/linkedlist.h> + +/* singly linked list *********************************************************/ + +void lm_sll_push_to_rear(lm_sll_root *root, lm_sll_node *node) { + assert(root); + node->rear = NULL; + if (root->rear) + root->rear->rear = node; + else + root->front = node; + root->rear = node; +} + +void lm_sll_pop_from_front(lm_sll_root *root) { + assert(root); + assert(root->front); + root->front = root->front->rear; + if (!root->front) + root->rear = NULL; +} + +/* doubly linked list *********************************************************/ + +void lm_dll_push_to_rear(lm_dll_root *root, lm_dll_node *node) { + assert(root); + assert(node); + node->front = root->rear; + node->rear = NULL; + if (root->rear) + root->rear->rear = node; + else + root->front = node; + root->rear = node; +} + +void lm_dll_remove(lm_dll_root *root, lm_dll_node *node) { + assert(root); + assert(node); + if (node->front) + node->front->rear = node->rear; + else + root->front = node->rear; + if (node->rear) + node->rear->front = node->front; + else + root->rear = node->front; +} + +void lm_dll_pop_from_front(lm_dll_root *root) { + assert(root); + assert(root->front); + lm_dll_remove(root, root->front); +} diff --git a/libmisc/tests/test_private.c b/libmisc/tests/test_private.c index 9b39932..024dddb 100644 --- a/libmisc/tests/test_private.c +++ b/libmisc/tests/test_private.c @@ -1,6 +1,6 @@ /* libmisc/tests/test_private.c - Tests for <libmisc/private.h> * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ diff --git a/libusb/CMakeLists.txt b/libusb/CMakeLists.txt index 9be44ac..b11e798 100644 --- a/libusb/CMakeLists.txt +++ b/libusb/CMakeLists.txt @@ -1,6 +1,6 @@ # libusb/CMakeLists.txt - Build script for libusb support library # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later add_library(libusb INTERFACE) diff --git a/libusb/include/libusb/tusb_helpers.h b/libusb/include/libusb/tusb_helpers.h index 1b4e48e..0c35f62 100644 --- a/libusb/include/libusb/tusb_helpers.h +++ b/libusb/include/libusb/tusb_helpers.h @@ -1,7 +1,7 @@ /* Generated by `libusb/include/libusb/tusb_helpers.h.gen `. DO NOT EDIT! */ /* libusb/tusb_helpers.h - Preprocessor macros that I think should be included in TinyUSB * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * * SPDX-License-Identifier: MIT * diff --git a/libusb/include/libusb/tusb_helpers.h.gen b/libusb/include/libusb/tusb_helpers.h.gen index 5a5d1ac..1de1d09 100755 --- a/libusb/include/libusb/tusb_helpers.h.gen +++ b/libusb/include/libusb/tusb_helpers.h.gen @@ -7,7 +7,7 @@ echo "/* Generated by \`$0 $*\`. DO NOT EDIT! */" cat <<'EOT' /* libusb/tusb_helpers.h - Preprocessor macros that I think should be included in TinyUSB * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * * SPDX-License-Identifier: MIT * @@ -70,12 +70,19 @@ fi if [[ ! -f 3rd-party/MS-LCID.txt || 3rd-party/MS-LCID.txt -ot 3rd-party/MS-LCID.pdf ]]; then pdftotext -layout 3rd-party/MS-LCID.pdf fi -<3rd-party/MS-LCID.txt \ - grep -E '^\s*0x[0-9A-F]{4}\s+[a-z]' | sed 's/,.*//' | grep -v reserved | # find the lines we're interested in - sed -E 's/^\s*0x(..)(..)\s+(\S.*)/\2 \1 \3/p' | tr '[:lower:]-' '[:upper:]_' | # format them as 'PRIhex SUBhex UPPER_STR' - sort | - sed -E 's/(..) (..) (.*)/#define LANGID_\3 0x\2\1/' | # format them as '#define LANGID_UPPER_STR 0xSUBPRI' - column --table --output-separator=' ' +{ + { + # find the lines we're interested in + grep -E '^\s*0x[0-9A-F]{4}\s+[a-z]' | sed 's/,.*//' | grep -v reserved + } | { + # format them as 'PRIhex SUBhex UPPER_STR' + sed -E 's/^\s*0x(..)(..)\s+(\S.*)/\2 \1 \3/p' | tr '[:lower:]-' '[:upper:]_' + } | sort | { + # format them as '#define LANGID_UPPER_STR 0xSUBPRI' + sed -E 's/(..) (..) (.*)/#define LANGID_\3 0x\2\1/' | + column --table --output-separator=' ' + } +} <3rd-party/MS-LCID.txt cat <<'EOT' /** USB 2.0 ยง9.6.6 "Endpoint", field bEndpointAddress, bit 7 */ @@ -187,3 +187,7 @@ OpenBMC - Sipeed NanoKVM - https://github.com/stefanklug/picoKVM (~$20 Arduino, ~$5 HDMI capture), non-IP + +---- + +https://hackaday.com/2022/08/26/bit-banged-ethernet-on-the-raspberry-pi-pico/ |