summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig54
-rw-r--r--3rd-party/linux-errno.txt2
-rw-r--r--CMakeLists.txt2
-rw-r--r--GNUmakefile58
-rw-r--r--PLAN.md14
-rwxr-xr-xbuild-aux/embed-sources.h.gen4
-rwxr-xr-xbuild-aux/lint-bin15
-rwxr-xr-xbuild-aux/lint-generic7
-rwxr-xr-xbuild-aux/lint-h6
-rw-r--r--build-aux/measurestack/app_plugins.py4
-rwxr-xr-xbuild-aux/valgrind16
-rw-r--r--cmd/sbc_harness/fs_harness_flash_bin.c2
-rw-r--r--cmd/sbc_harness/main.c1
-rw-r--r--cmd/sbc_harness/usb_keyboard.h4
-rw-r--r--lib9p/9p.generated.c96
-rw-r--r--lib9p/CMakeLists.txt26
-rw-r--r--lib9p/idl/1992-9P0.9p.wip2
-rw-r--r--lib9p/idl/1996-Styx.9p.wip2
-rw-r--r--lib9p/idl/2002-9P2000.9p2
-rw-r--r--lib9p/idl/2005-9P2000.u.9p2
-rw-r--r--lib9p/idl/2010-9P2000.L.9p2
-rw-r--r--lib9p/include/lib9p/9p.generated.h8
-rw-r--r--lib9p/include/lib9p/9p.h2
-rw-r--r--lib9p/include/lib9p/linux-errno.h2
-rw-r--r--lib9p/include/lib9p/srv.h7
-rwxr-xr-xlib9p/linux-errno.txt.gen (renamed from build-aux/linux-errno.txt.gen)4
-rw-r--r--lib9p/protogen/c_format.py37
-rw-r--r--lib9p/protogen/h.py2
-rw-r--r--lib9p/srv.c67
-rw-r--r--lib9p/tables.c8
-rw-r--r--lib9p/tests/client_config/config.h40
-rwxr-xr-xlib9p/tests/runtest81
-rw-r--r--lib9p/tests/test_server/CMakeLists.txt2
-rw-r--r--lib9p/tests/test_server/fs_shutdown.c93
-rw-r--r--lib9p/tests/test_server/fs_shutdown.h23
-rw-r--r--lib9p/tests/test_server/fs_slowread.c101
-rw-r--r--lib9p/tests/test_server/fs_slowread.h22
-rw-r--r--lib9p/tests/test_server/main.c159
-rwxr-xr-xlib9p/tests/testclient-p9p61
-rw-r--r--lib9p/tests/testclient-p9p.explog106
-rw-r--r--lib9p/tests/testclient-sess.c144
-rw-r--r--lib9p/tests/testclient-sess.explog58
-rw-r--r--libcr/CMakeLists.txt2
-rw-r--r--libcr_ipc/CMakeLists.txt12
-rw-r--r--libcr_ipc/chan.c139
-rw-r--r--libcr_ipc/include/libcr_ipc/_linkedlist.h99
-rw-r--r--libcr_ipc/include/libcr_ipc/chan.h84
-rw-r--r--libcr_ipc/include/libcr_ipc/mutex.h44
-rw-r--r--libcr_ipc/include/libcr_ipc/owned_mutex.h79
-rw-r--r--libcr_ipc/include/libcr_ipc/rpc.h92
-rw-r--r--libcr_ipc/include/libcr_ipc/rwmutex.h76
-rw-r--r--libcr_ipc/include/libcr_ipc/select.h85
-rw-r--r--libcr_ipc/include/libcr_ipc/sema.h63
-rw-r--r--libcr_ipc/mutex.c43
-rw-r--r--libcr_ipc/rpc.c83
-rw-r--r--libcr_ipc/rwmutex.c100
-rw-r--r--libcr_ipc/select.c102
-rw-r--r--libcr_ipc/sema.c64
-rw-r--r--libcr_ipc/tests/config.h4
-rw-r--r--libcr_ipc/tests/test_chan.c4
-rw-r--r--libcr_ipc/tests/test_mutex.c37
-rw-r--r--libcr_ipc/tests/test_rpc.c2
-rw-r--r--libcr_ipc/tests/test_rwmutex.c88
-rw-r--r--libcr_ipc/tests/test_select.c90
-rw-r--r--libcr_ipc/tests/test_sema.c4
-rw-r--r--libhw_cr/host_util.c2
-rw-r--r--libhw_cr/host_util.h2
-rw-r--r--libhw_cr/rp2040_hwspi.c2
-rw-r--r--libhw_cr/rp2040_include/libhw/w5500.h2
-rw-r--r--libmisc/CMakeLists.txt1
-rw-r--r--libmisc/include/libmisc/linkedlist.h46
-rw-r--r--libmisc/include/libmisc/macro.h13
-rw-r--r--libmisc/include/libmisc/rand.h2
-rw-r--r--libmisc/linkedlist.c62
-rw-r--r--libmisc/tests/test_private.c2
-rw-r--r--libusb/CMakeLists.txt2
-rw-r--r--libusb/include/libusb/tusb_helpers.h2
-rwxr-xr-xlibusb/include/libusb/tusb_helpers.h.gen21
-rw-r--r--notes.md4
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
diff --git a/PLAN.md b/PLAN.md
index 2bae3fd..3201122 100644
--- a/PLAN.md
+++ b/PLAN.md
@@ -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 */
diff --git a/notes.md b/notes.md
index 8f2f44c..84c2e72 100644
--- a/notes.md
+++ b/notes.md
@@ -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/