├── lib └── freebsd │ ├── Makefile.inc │ ├── errno │ ├── Makefile │ └── lua_errno.c │ ├── sys │ ├── stat │ │ ├── Makefile │ │ └── lua_stat.c │ ├── wait │ │ ├── Makefile │ │ └── lua_wait.c │ ├── access │ │ ├── Makefile │ │ └── lua_access.c │ ├── chdir │ │ ├── Makefile │ │ └── lua_chdir.c │ ├── execve │ │ ├── Makefile │ │ └── lua_execve.c │ ├── getcwd │ │ ├── Makefile │ │ └── lua_getcwd.c │ ├── getuid │ │ ├── Makefile │ │ └── lua_getuid.c │ ├── mkdir │ │ ├── Makefile │ │ └── lua_mkdir.c │ ├── sysctl │ │ ├── Makefile │ │ └── lua_sysctl.c │ ├── symlink │ │ ├── Makefile │ │ └── lua_symlink.c │ ├── open │ │ ├── Makefile │ │ └── lua_open.c │ ├── pipe │ │ ├── Makefile │ │ └── lua_pipe.c │ ├── poll │ │ ├── Makefile │ │ └── lua_poll.c │ ├── read │ │ ├── Makefile │ │ └── lua_read.c │ ├── socket │ │ ├── Makefile │ │ └── lua_socket.c │ ├── unlink │ │ ├── Makefile │ │ └── lua_unlink.c │ └── Makefile │ ├── uname │ ├── Makefile │ └── lua_uname.c │ ├── sysconf │ ├── Makefile │ └── lua_sysconf.c │ ├── glob │ ├── Makefile │ └── lua_glob.c │ ├── libgen │ ├── Makefile │ └── lua_libgen.c │ ├── mktemp │ ├── Makefile │ └── lua_mktemp.c │ ├── getaddrinfo │ ├── Makefile │ └── lua_getaddrinfo.c │ ├── posix_spawn │ ├── Makefile │ └── lua_posix_spawn.c │ ├── lua_freebsd_meta.h │ ├── Makefile │ ├── lua_freebsd_meta.c │ └── sys.lua ├── contrib └── orch │ ├── lib │ ├── .gitignore │ ├── orch │ │ ├── CMakeLists.txt │ │ ├── context.lua │ │ ├── matchers.lua │ │ ├── direct.lua │ │ ├── actions.lua │ │ ├── process.lua │ │ └── scripter.lua │ ├── CMakeLists.txt │ ├── core │ │ ├── CMakeLists.txt │ │ ├── orch_compat.c │ │ ├── orch_spawn.c │ │ ├── orch_ipc.c │ │ └── orch_tty.c │ ├── orch.lua │ └── orch_lib.h │ ├── tests │ ├── timeout_basic.orch │ ├── my_cat │ ├── timeout_global.orch │ ├── timeout_test.orch │ ├── ipc_reopen.orch │ ├── simple_eof.orch │ ├── simple_match.orch │ ├── spawn_simple.orch │ ├── spawn_exec_firstline_comment.orch │ ├── simple_callback.orch │ ├── simple_anchored.orch │ ├── shebang_simple.orch │ ├── CMakeLists.txt │ ├── spawn_exec_firstline.orch │ ├── simple_match_sequence.orch │ ├── simple_nonmatch_fail.orch │ ├── matcher_posix.orch │ ├── simple_eof_fail.orch │ ├── simple_fail_handler.orch │ ├── simple_callback_fail.orch │ ├── simple_match_sequence_fail.orch │ ├── timeout_sleep.orch │ ├── spawn_multi_match.orch │ ├── simple_enqueue.orch │ ├── simple_noecho_fail.orch │ ├── spawn_multi.orch │ ├── simple_match_sequence_twice_fail.orch │ ├── matcher_plain.orch │ ├── matcher_switch.orch │ ├── spawn_stty.orch │ ├── simple_raw.orch │ ├── one_basic.orch │ ├── one_callback_fail.orch │ └── basic_test.sh │ ├── .gitignore │ ├── examples │ ├── CMakeLists.txt │ ├── nc.orch │ └── cat.orch │ ├── man │ ├── CMakeLists.txt │ ├── orch.1 │ └── orch.5 │ ├── src │ ├── orch_bin.h │ ├── CMakeLists.txt │ ├── orch.c │ └── orch_interp.c │ ├── .cirrus.yml │ ├── .github │ └── workflows │ │ ├── lint.yml │ │ └── build.yml │ ├── include │ └── orch.h │ ├── LICENSE │ ├── README.md │ └── CMakeLists.txt ├── .gitignore ├── .luarc.json ├── bricoler.1 ├── Makefile ├── NOTES ├── bricoler-completion.bash ├── run-kyua ├── README.md └── class.lua /lib/freebsd/Makefile.inc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /contrib/orch/lib/.gitignore: -------------------------------------------------------------------------------- 1 | !orch/ 2 | *.so 3 | *.so.* 4 | *.pico 5 | -------------------------------------------------------------------------------- /contrib/orch/tests/timeout_basic.orch: -------------------------------------------------------------------------------- 1 | -- TIMEOUT: 10 2 | 3 | match "Nothing" 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pico 2 | *.so 3 | *.so.debug 4 | *.so.full 5 | contrib/orch/install 6 | -------------------------------------------------------------------------------- /.luarc.json: -------------------------------------------------------------------------------- 1 | { 2 | "Lua.runtime.special": { 3 | "errx": "error" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /contrib/orch/tests/my_cat: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | while read line; do 4 | echo "$line" 5 | done 6 | -------------------------------------------------------------------------------- /contrib/orch/tests/timeout_global.orch: -------------------------------------------------------------------------------- 1 | -- TIMEOUT: 3 2 | 3 | timeout(3) 4 | match "Nothing" 5 | -------------------------------------------------------------------------------- /contrib/orch/tests/timeout_test.orch: -------------------------------------------------------------------------------- 1 | -- TIMEOUT: 3 2 | 3 | match "Nothing" { 4 | timeout = 3 5 | } 6 | -------------------------------------------------------------------------------- /contrib/orch/tests/ipc_reopen.orch: -------------------------------------------------------------------------------- 1 | 2 | spawn("cat") 3 | 4 | spawn("cat") 5 | 6 | write "Hello\r" 7 | match "Hello" 8 | -------------------------------------------------------------------------------- /contrib/orch/tests/simple_eof.orch: -------------------------------------------------------------------------------- 1 | -- VEOF on an empty line should terminate cat, send us EOF. 2 | write "^D" 3 | eof() 4 | -------------------------------------------------------------------------------- /contrib/orch/tests/simple_match.orch: -------------------------------------------------------------------------------- 1 | -- What we write to cat(1) should come straight back to us. 2 | write "Hello\r" 3 | match "Hello" 4 | -------------------------------------------------------------------------------- /contrib/orch/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .*.swp 3 | *.o 4 | *.bak 5 | *.core 6 | *.debug 7 | *.full 8 | *.gz 9 | .depend* 10 | orch 11 | ktrace.out 12 | -------------------------------------------------------------------------------- /contrib/orch/examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(EXAMPLES 3 | cat.orch 4 | nc.orch 5 | ) 6 | 7 | install(FILES ${EXAMPLES} DESTINATION 8 | ${ORCHLUA_EXAMPLESDIR}) 9 | -------------------------------------------------------------------------------- /contrib/orch/tests/spawn_simple.orch: -------------------------------------------------------------------------------- 1 | 2 | spawn("my_cat") 3 | 4 | -- What we write to cat(1) should come straight back to us. 5 | write "Hello\r" 6 | match "Hello" 7 | -------------------------------------------------------------------------------- /contrib/orch/tests/spawn_exec_firstline_comment.orch: -------------------------------------------------------------------------------- 1 | -- We had a bug where the first line was joined to the second 2 | spawn("cat") 3 | write "Hello\r" 4 | match "Hello" 5 | -------------------------------------------------------------------------------- /contrib/orch/tests/simple_callback.orch: -------------------------------------------------------------------------------- 1 | 2 | write "Hello\r" 3 | match "Hello" { 4 | callback = function() 5 | write "There!\r" 6 | end 7 | } 8 | 9 | match "There" 10 | -------------------------------------------------------------------------------- /contrib/orch/lib/orch/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB share_orch_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.lua) 2 | install(FILES ${share_orch_SOURCES} 3 | DESTINATION "${LUA_MODSHAREDIR}/orch") 4 | -------------------------------------------------------------------------------- /contrib/orch/tests/simple_anchored.orch: -------------------------------------------------------------------------------- 1 | 2 | -- Write a simple string and anchor the result. 3 | write "Hello\r" 4 | match "^Hello\r\n$" 5 | 6 | write "Hello^D" 7 | match "Hello$" 8 | -------------------------------------------------------------------------------- /lib/freebsd/errno/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= errno.so 2 | 3 | SRCS+= lua_errno.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 6 | 7 | WARNS?= 6 8 | 9 | .include 10 | -------------------------------------------------------------------------------- /lib/freebsd/sys/stat/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= stat.so 2 | 3 | SRCS+= lua_stat.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 6 | 7 | WARNS?= 6 8 | 9 | .include 10 | -------------------------------------------------------------------------------- /lib/freebsd/sys/wait/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= wait.so 2 | 3 | SRCS+= lua_wait.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 6 | 7 | WARNS?= 6 8 | 9 | .include 10 | -------------------------------------------------------------------------------- /lib/freebsd/uname/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= uname.so 2 | 3 | SRCS+= lua_uname.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 6 | 7 | WARNS?= 6 8 | 9 | .include 10 | -------------------------------------------------------------------------------- /contrib/orch/tests/shebang_simple.orch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S orch -f 2 | spawn("cat") 3 | 4 | -- What we write to cat(1) should come straight back to us. 5 | write "Hello\r" 6 | match "Hello" 7 | -------------------------------------------------------------------------------- /lib/freebsd/sys/access/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= access.so 2 | 3 | SRCS+= lua_access.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 6 | 7 | WARNS?= 6 8 | 9 | .include 10 | -------------------------------------------------------------------------------- /lib/freebsd/sys/chdir/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= chdir.so 2 | 3 | SRCS+= lua_chdir.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 6 | 7 | WARNS?= 6 8 | 9 | .include 10 | -------------------------------------------------------------------------------- /lib/freebsd/sys/execve/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= execve.so 2 | 3 | SRCS+= lua_execve.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 6 | 7 | WARNS?= 6 8 | 9 | .include 10 | -------------------------------------------------------------------------------- /lib/freebsd/sys/getcwd/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= getcwd.so 2 | 3 | SRCS+= lua_getcwd.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 6 | 7 | WARNS?= 6 8 | 9 | .include 10 | -------------------------------------------------------------------------------- /lib/freebsd/sys/getuid/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= getuid.so 2 | 3 | SRCS+= lua_getuid.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 6 | 7 | WARNS?= 6 8 | 9 | .include 10 | -------------------------------------------------------------------------------- /lib/freebsd/sys/mkdir/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= mkdir.so 2 | 3 | SRCS+= lua_mkdir.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 6 | 7 | WARNS?= 6 8 | 9 | .include 10 | -------------------------------------------------------------------------------- /lib/freebsd/sys/sysctl/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= sysctl.so 2 | 3 | SRCS+= lua_sysctl.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 6 | 7 | WARNS?= 6 8 | 9 | .include 10 | -------------------------------------------------------------------------------- /lib/freebsd/sysconf/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= sysconf.so 2 | 3 | SRCS+= lua_sysconf.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 6 | 7 | WARNS?= 6 8 | 9 | .include 10 | -------------------------------------------------------------------------------- /lib/freebsd/sys/symlink/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= symlink.so 2 | 3 | SRCS+= lua_symlink.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 6 | 7 | WARNS?= 6 8 | 9 | .include 10 | -------------------------------------------------------------------------------- /contrib/orch/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_custom_target(check 2 | COMMAND env ORCHBIN="${CMAKE_BINARY_DIR}/src/orch" ORCHLUA_PATH="${CMAKE_SOURCE_DIR}/lib" sh "${CMAKE_CURRENT_SOURCE_DIR}/basic_test.sh") 3 | -------------------------------------------------------------------------------- /contrib/orch/tests/spawn_exec_firstline.orch: -------------------------------------------------------------------------------- 1 | spawn("cat") 2 | 3 | -- Making sure the first line doesn't get stripped when we have no shebang on 4 | -- the first line. 5 | write "Hello\r" 6 | match "Hello" 7 | -------------------------------------------------------------------------------- /lib/freebsd/glob/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= glob.so 2 | 3 | SRCS+= lua_glob.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 \ 6 | -I${.CURDIR}/../ 7 | 8 | WARNS?= 6 9 | 10 | .include 11 | -------------------------------------------------------------------------------- /contrib/orch/tests/simple_match_sequence.orch: -------------------------------------------------------------------------------- 1 | 2 | write "Fish Dog\r" 3 | 4 | -- We chop 'fish' off of the buffer, then we should still have ' Dog' available 5 | -- for matching. 6 | match "Fish" 7 | match "Dog" 8 | -------------------------------------------------------------------------------- /lib/freebsd/libgen/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= libgen.so 2 | 3 | SRCS+= lua_libgen.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 \ 6 | -I${.CURDIR}/../ 7 | 8 | WARNS?= 6 9 | 10 | .include 11 | -------------------------------------------------------------------------------- /lib/freebsd/mktemp/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= mktemp.so 2 | 3 | SRCS+= lua_mktemp.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 \ 6 | -I${.CURDIR}/../ 7 | 8 | WARNS?= 6 9 | 10 | .include 11 | -------------------------------------------------------------------------------- /lib/freebsd/sys/open/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= open.so 2 | 3 | SRCS+= lua_open.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 \ 6 | -I${.CURDIR}/../../ 7 | 8 | WARNS?= 6 9 | 10 | .include 11 | -------------------------------------------------------------------------------- /lib/freebsd/sys/pipe/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= pipe.so 2 | 3 | SRCS+= lua_pipe.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 \ 6 | -I${.CURDIR}/../../ 7 | 8 | WARNS?= 6 9 | 10 | .include 11 | -------------------------------------------------------------------------------- /lib/freebsd/sys/poll/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= poll.so 2 | 3 | SRCS+= lua_poll.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 \ 6 | -I${.CURDIR}/../../ 7 | 8 | WARNS?= 6 9 | 10 | .include 11 | -------------------------------------------------------------------------------- /lib/freebsd/sys/read/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= read.so 2 | 3 | SRCS+= lua_read.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 \ 6 | -I${.CURDIR}/../../ 7 | 8 | WARNS?= 6 9 | 10 | .include 11 | -------------------------------------------------------------------------------- /lib/freebsd/sys/socket/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= socket.so 2 | 3 | SRCS+= lua_socket.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 \ 6 | -I${.CURDIR}/../../ 7 | 8 | WARNS?= 6 9 | 10 | .include 11 | -------------------------------------------------------------------------------- /lib/freebsd/sys/unlink/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= unlink.so 2 | 3 | SRCS+= lua_unlink.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 \ 6 | -I${.CURDIR}/../../ 7 | 8 | WARNS?= 6 9 | 10 | .include 11 | -------------------------------------------------------------------------------- /contrib/orch/tests/simple_nonmatch_fail.orch: -------------------------------------------------------------------------------- 1 | -- This will timeout since Hello != Biscuits 2 | 3 | timeout(1) 4 | fail(function() 5 | exit(0) 6 | end) 7 | 8 | write "Hello\r" 9 | match "Biscuits" 10 | exit(1) 11 | -------------------------------------------------------------------------------- /lib/freebsd/getaddrinfo/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= getaddrinfo.so 2 | 3 | SRCS+= lua_getaddrinfo.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 \ 6 | -I${.CURDIR}/../ 7 | 8 | WARNS?= 6 9 | 10 | .include 11 | -------------------------------------------------------------------------------- /lib/freebsd/posix_spawn/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= posix_spawn.so 2 | 3 | SRCS+= lua_posix_spawn.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 \ 6 | -I${.CURDIR}/../ 7 | 8 | WARNS?= 6 9 | 10 | .include 11 | -------------------------------------------------------------------------------- /contrib/orch/tests/matcher_posix.orch: -------------------------------------------------------------------------------- 1 | 2 | timeout(1) 3 | matcher("posix") 4 | 5 | -- What we write to cat(1) should come straight back to us. 6 | write "Hello\rHotdog\r" 7 | 8 | match "He[[:alpha:]]{2}o" 9 | match "Hot" 10 | match "dog" 11 | -------------------------------------------------------------------------------- /contrib/orch/tests/simple_eof_fail.orch: -------------------------------------------------------------------------------- 1 | timeout(3) 2 | 3 | -- What we write to cat(1) should come straight back to us. 4 | write "Hello\r" 5 | match "Hello" 6 | 7 | fail(function() 8 | exit(0) 9 | end) 10 | 11 | eof() 12 | 13 | exit(1) 14 | -------------------------------------------------------------------------------- /contrib/orch/tests/simple_fail_handler.orch: -------------------------------------------------------------------------------- 1 | timeout(1) 2 | 3 | -- Normally a non-match will trigger failure, but here we'll setup a failure 4 | -- handler to exit successfully for test purposes. 5 | fail(function() 6 | exit(0) 7 | end) 8 | 9 | match "Hello" 10 | -------------------------------------------------------------------------------- /contrib/orch/tests/simple_callback_fail.orch: -------------------------------------------------------------------------------- 1 | timeout(1) 2 | 3 | write "Hello\r" 4 | match "Hello" { 5 | callback = function() 6 | write "Monkies!\r" 7 | end 8 | } 9 | 10 | 11 | fail(function() 12 | exit(0) 13 | end) 14 | 15 | match "There" 16 | 17 | exit(1) 18 | -------------------------------------------------------------------------------- /contrib/orch/tests/simple_match_sequence_fail.orch: -------------------------------------------------------------------------------- 1 | timeout(1) 2 | write "Fish Dog\r" 3 | 4 | -- Dog fast forwards the buffer to the end of Dog, then Fish should fail. 5 | match "Dog" 6 | 7 | fail(function() 8 | exit(0) 9 | end) 10 | 11 | match "Fish" 12 | 13 | exit(1) 14 | -------------------------------------------------------------------------------- /lib/freebsd/sys/Makefile: -------------------------------------------------------------------------------- 1 | SUBDIR= access \ 2 | chdir \ 3 | execve \ 4 | getcwd \ 5 | getuid \ 6 | mkdir \ 7 | open \ 8 | pipe \ 9 | poll \ 10 | read \ 11 | socket \ 12 | stat \ 13 | symlink \ 14 | sysctl \ 15 | unlink \ 16 | wait 17 | 18 | .include 19 | -------------------------------------------------------------------------------- /contrib/orch/man/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | set(MANSECTIONS 1 5) 3 | 4 | set(MAN1 orch.1) 5 | set(MAN5 orch.5) 6 | 7 | foreach(sect IN LISTS MANSECTIONS) 8 | foreach(page IN LISTS MAN${sect}) 9 | install(FILES ${page} DESTINATION "share/man/man${sect}") 10 | endforeach() 11 | endforeach() 12 | -------------------------------------------------------------------------------- /contrib/orch/tests/timeout_sleep.orch: -------------------------------------------------------------------------------- 1 | -- TIMEOUT: 4 2 | 3 | spawn("cat") 4 | 5 | write "Hello\r" 6 | match "Hello" 7 | sleep(4) 8 | 9 | -- We're abusing the timeout_* pattern to test our sleep() implementation. Is 10 | -- it a little ugly? Maybe, but we're doing it anyways. 11 | exit(1) 12 | -------------------------------------------------------------------------------- /contrib/orch/src/orch_bin.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2024 Kyle Evans 3 | * 4 | * SPDX-License-Identifier: BSD-2-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | /* orch_interp.c */ 12 | int orch_interp(const char *, const char *, int, const char * const []); 13 | -------------------------------------------------------------------------------- /contrib/orch/tests/spawn_multi_match.orch: -------------------------------------------------------------------------------- 1 | timeout(1) 2 | 3 | spawn("cat") 4 | write "Hello\rWorld\r" 5 | 6 | match "Hello\r" 7 | 8 | -- This should come with a new buffer, so we shouldn't be able to match anything 9 | -- from the previous process. 10 | spawn("cat") 11 | 12 | write "Cat\r" 13 | match "Cat\r" 14 | -------------------------------------------------------------------------------- /contrib/orch/tests/simple_enqueue.orch: -------------------------------------------------------------------------------- 1 | 2 | local did_thing = false 3 | write "Hello\r" 4 | 5 | one(function() 6 | match "Hello" { 7 | callback = function() 8 | did_thing = true 9 | end 10 | } 11 | match "There" 12 | end) 13 | 14 | enqueue(function() 15 | if did_thing then 16 | exit(0) 17 | end 18 | end) 19 | 20 | exit(1) 21 | -------------------------------------------------------------------------------- /contrib/orch/tests/simple_noecho_fail.orch: -------------------------------------------------------------------------------- 1 | -- Our pts shouldn't be in non-canonical mode, so we shouldn't see this at all 2 | -- unless the tty is configured to echo. That would invalidate a good chunk of 3 | -- our tests. 4 | timeout(1) 5 | fail(function() 6 | exit(0) 7 | end) 8 | 9 | write "Hello" 10 | match "Hello" 11 | 12 | exit(1) 13 | -------------------------------------------------------------------------------- /contrib/orch/tests/spawn_multi.orch: -------------------------------------------------------------------------------- 1 | timeout(1) 2 | 3 | spawn("cat") 4 | write "Hello\rWorld\r" 5 | 6 | match "Hello\r" 7 | 8 | -- This should come with a new buffer, so we shouldn't be able to match anything 9 | -- from the previous process. 10 | spawn("cat") 11 | 12 | fail(function() 13 | exit(0) 14 | end) 15 | 16 | match "World" 17 | 18 | exit(1) 19 | -------------------------------------------------------------------------------- /contrib/orch/tests/simple_match_sequence_twice_fail.orch: -------------------------------------------------------------------------------- 1 | timeout(1) 2 | write "Fish Dog\r" 3 | 4 | -- We chop 'fish' off of the buffer, then we should still have ' Dog' available 5 | -- for matching. 6 | match "Fish" 7 | 8 | fail(function() 9 | exit(0) 10 | end) 11 | 12 | -- So if we try Fish again, it should be gone. 13 | match "Fish" 14 | 15 | exit(1) 16 | -------------------------------------------------------------------------------- /lib/freebsd/lua_freebsd_meta.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #ifndef _LUA_FREEBSD_META_H_ 8 | #define _LUA_FREEBSD_META_H_ 9 | 10 | #define FREEBSD_SYS_FD_REGISTRY_KEY "freebsd_sys_fd" 11 | #define FREEBSD_SYS_SOCKADDR_REGISTRY_KEY "freebsd_sys_sockaddr" 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /bricoler.1: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright (c) Mark Johnston 3 | .\" 4 | .\" SPDX-License-Identifier: BSD-2-Clause 5 | .\" 6 | .Dd May 25, 2024 7 | .Dt bricoler 1 8 | .Os 9 | .Sh NAME 10 | .Nm bricoler 11 | .Nd A utility to simplify FreeBSD development tasks 12 | .Sh SYNOPSIS 13 | .Nm 14 | .Op Ar command Op Ar arguments ... 15 | .Nm 16 | .Op Fl h 17 | .Sh DESCRIPTION 18 | -------------------------------------------------------------------------------- /lib/freebsd/Makefile: -------------------------------------------------------------------------------- 1 | SHLIB_NAME= freebsd_meta.so 2 | 3 | SRCS+= lua_freebsd_meta.c 4 | 5 | CFLAGS+= -I/usr/local/include/lua54 \ 6 | -I${.CURDIR} 7 | 8 | WARNS?= 6 9 | 10 | SUBDIR= errno \ 11 | getaddrinfo \ 12 | glob \ 13 | libgen \ 14 | mktemp \ 15 | posix_spawn \ 16 | sys \ 17 | sysconf \ 18 | uname 19 | 20 | .include 21 | .include 22 | -------------------------------------------------------------------------------- /contrib/orch/tests/matcher_plain.orch: -------------------------------------------------------------------------------- 1 | 2 | timeout(1) 3 | matcher("plain") 4 | 5 | -- What we write to cat(1) should come straight back to us. 6 | write "Hello\r" 7 | 8 | local ok = false 9 | fail(function() 10 | ok = true 11 | end) 12 | 13 | match "H.llo" 14 | 15 | fail(nil) 16 | 17 | enqueue(function() 18 | if not ok then 19 | exit(1) 20 | end 21 | end) 22 | 23 | match "Hello" 24 | -------------------------------------------------------------------------------- /contrib/orch/tests/matcher_switch.orch: -------------------------------------------------------------------------------- 1 | 2 | timeout(1) 3 | matcher("plain") 4 | 5 | -- What we write to cat(1) should come straight back to us. 6 | write "Hello\r" 7 | 8 | local ok = false 9 | fail(function() 10 | ok = true 11 | end) 12 | 13 | match "H.llo" 14 | 15 | fail(nil) 16 | 17 | enqueue(function() 18 | if not ok then 19 | exit(1) 20 | end 21 | end) 22 | 23 | matcher("default") 24 | match "H.llo" 25 | -------------------------------------------------------------------------------- /contrib/orch/tests/spawn_stty.orch: -------------------------------------------------------------------------------- 1 | 2 | timeout(3) 3 | 4 | spawn("cat") 5 | -- Set -ICANON; this will timeout if canonicalization is on, since we haven't 6 | -- sent an EOF. 7 | stty("lflag", 0, tty.lflag.ICANON) 8 | 9 | write "Hello the" 10 | match "^Hello the$" 11 | 12 | -- Also test cc changes 13 | spawn("cat") 14 | 15 | stty("cc", { 16 | VEOF = "^F" 17 | }) 18 | write "Hello the^F" 19 | 20 | match "^Hello the$" 21 | -------------------------------------------------------------------------------- /contrib/orch/examples/nc.orch: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S orch -f 2 | 3 | -- On the same machine, open up `nc -l 9999` and play with it. 4 | spawn("nc", "localhost", "9999") 5 | 6 | write "Hello from the other side\r" 7 | 8 | -- Write any response on the listening side (don't forget to hit return), and... 9 | match "." { 10 | callback = function() 11 | -- ...we'll shoot a debug message when it comes over. 12 | debug("We received a response!") 13 | end 14 | } 15 | -------------------------------------------------------------------------------- /contrib/orch/lib/orch/context.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright (c) 2024 Kyle Evans 3 | -- 4 | -- SPDX-License-Identifier: BSD-2-Clause 5 | -- 6 | 7 | local Context = {} 8 | function Context:new(def) 9 | local ctx = setmetatable(def or {}, self) 10 | self.__index = self 11 | 12 | return ctx 13 | end 14 | function Context.execute(_) 15 | return true 16 | end 17 | function Context.fail(_) 18 | return false 19 | end 20 | 21 | return Context 22 | -------------------------------------------------------------------------------- /contrib/orch/tests/simple_raw.orch: -------------------------------------------------------------------------------- 1 | timeout(3) 2 | 3 | -- We're in canonical mode, so it will be obvious if our ^D didn't get escaped 4 | -- since we'll timeout. 5 | raw(true) 6 | write "Hello^D\r" 7 | match "Hello^D" 8 | 9 | 10 | -- With this one, read(2) on the tty should flush Hello and strip VEOF from the 11 | -- buffer. 12 | raw(false) 13 | write "Hello^D" 14 | match "Hello$" 15 | 16 | -- Finally, test escaping 17 | write "Hello\\^D\r" 18 | match "Hello^D\r" 19 | -------------------------------------------------------------------------------- /contrib/orch/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | file(GLOB orch_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.c) 3 | 4 | add_compile_definitions(ORCHLUA_PATH="${ORCHLUA_PATH}") 5 | 6 | add_executable(orch ${orch_SOURCES}) 7 | 8 | set(orch_INCDIRS "${CMAKE_SOURCE_DIR}/include" "${LUA_INCLUDE_DIR}") 9 | target_include_directories(orch PRIVATE ${orch_INCDIRS}) 10 | target_link_libraries(orch core_static "${LUA_LIBRARIES}") 11 | 12 | install(TARGETS orch 13 | DESTINATION "${ORCHLUA_BINDIR}") 14 | -------------------------------------------------------------------------------- /contrib/orch/lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(core) 2 | add_subdirectory(orch) 3 | 4 | install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/orch.lua" 5 | DESTINATION "${LUA_MODSHAREDIR}") 6 | file(GLOB lua_SOURCES 7 | "${CMAKE_CURRENT_SOURCE_DIR}/*.lua" 8 | "${CMAKE_CURRENT_SOURCE_DIR}/orch/*.lua") 9 | 10 | add_custom_target(lint 11 | COMMAND echo LINTING FOR LUA 5.3 12 | COMMAND luacheck --std=lua53 ${lua_SOURCES} 13 | COMMAND echo LINTING FOR LUA 5.4 14 | COMMAND luacheck --std=lua54 ${lua_SOURCES} 15 | ) 16 | -------------------------------------------------------------------------------- /contrib/orch/.cirrus.yml: -------------------------------------------------------------------------------- 1 | build_task: 2 | matrix: 3 | - name: FreeBSD 13 4 | freebsd_instance: 5 | image: freebsd-13-2-release-amd64 6 | - name: FreeBSD 14 7 | freebsd_instance: 8 | image: freebsd-14-0-release-amd64-ufs 9 | setup_script: 10 | sudo pkg install -y lua54 cmake 11 | configure_script: 12 | - mkdir build 13 | - cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. 14 | build_script: 15 | make -C build 16 | test_script: 17 | make -C build check 18 | -------------------------------------------------------------------------------- /contrib/orch/tests/one_basic.orch: -------------------------------------------------------------------------------- 1 | timeout(1) 2 | 3 | -- Demonstrate that only one of the matches will proceed. 4 | write "ZOO\r" 5 | one(function() 6 | match "OO" 7 | match "ZOO" { 8 | callback = function() 9 | -- Will fail and timeout 10 | match "Monkies" 11 | end 12 | } 13 | end) 14 | 15 | -- Demonstrate that it's always the first one, regardless of length or position. 16 | write "ZOO\r" 17 | one(function() 18 | match "ZOO" 19 | match "OO" { 20 | callback = function() 21 | -- Will fail and timeout 22 | match "Monkies" 23 | end 24 | } 25 | end) 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: lib orch 2 | 3 | lib: .PHONY 4 | ${MAKE} -C lib/freebsd 5 | 6 | install: 7 | @echo "No install target is supported, run in-place" 8 | @false 9 | 10 | ORCHSRC= ${.CURDIR}/contrib/orch 11 | 12 | .if ${MACHINE} == "arm64" && ${MACHINE_ARCH} == "aarch64c" 13 | ORCHLIBS=-DLUA_LIBRARIES=/usr/local/lib/liblua-5.4.so 14 | ORCHINCS=-DLUA_INCLUDE_DIR=/usr/local/include/lua54 15 | .endif 16 | 17 | orch: .PHONY 18 | mkdir -p ${ORCHSRC}/build ${ORCHSRC}/install 19 | cmake -DCMAKE_INSTALL_PREFIX=${ORCHSRC}/install \ 20 | ${ORCHLIBS} ${ORCHINCS} \ 21 | -S ${ORCHSRC} -B ${ORCHSRC}/build 22 | ${MAKE} -C ${ORCHSRC}/build all install 23 | -------------------------------------------------------------------------------- /contrib/orch/tests/one_callback_fail.orch: -------------------------------------------------------------------------------- 1 | timeout(1) 2 | 3 | -- Demonstrate that only one of the matches will proceed. 4 | write "ZOO\r" 5 | one(function() 6 | match "OO" { 7 | callback = function() 8 | -- First demonstrate that it works 9 | write "Monkies\r" 10 | match "Monkies" 11 | end 12 | } 13 | match "ZOO" 14 | end) 15 | 16 | write "ZOO\r" 17 | one(function() 18 | match "OO" { 19 | callback = function() 20 | fail(function() 21 | exit(0) 22 | end) 23 | 24 | -- Now do it again, but fail. 25 | match "Monkies" 26 | end 27 | } 28 | match "ZOO" { 29 | callback = function() 30 | exit(1) 31 | end 32 | } 33 | end) 34 | -------------------------------------------------------------------------------- /NOTES: -------------------------------------------------------------------------------- 1 | Todo: 2 | - Task for running ZFS test suite, persistent devvm 3 | - man page 4 | - Nicer formatting for "-s". 5 | - Figure out a saner system for importing C functions. 6 | - Figure out a saner syntax for running subtasks than "self.foo = self.foo:run()" 7 | - Implement some kind of lockfile-based mutual exclusion. 8 | - Add some kind of tmux integration. 9 | 10 | Longer-term questions/tasks: 11 | - Add a timeout to the syzkaller task. 12 | - mkimg can be made much faster on systems with block cloning, but this 13 | requires some work. 14 | - Want some way to avoid needless work when inputs don't change. 15 | 16 | Notes for the talk: 17 | - Want to demo plain VM booting+ssh/gdb 18 | - Want to demo stabweek branch 19 | - Mention future plans 20 | - lua-language-server in ports 21 | -------------------------------------------------------------------------------- /contrib/orch/examples/cat.orch: -------------------------------------------------------------------------------- 1 | -- Executed like `./orch -f cat.orch -- cat` -- this one won't spawn anything 2 | -- itself, so it must be done on the command line. 3 | 4 | -- We get released on first match anyways, so this doesn't need to be called: 5 | -- release() 6 | 7 | write "Send One\r" 8 | one(function() 9 | match "One" { 10 | callback = function() 11 | write "LOL\r" 12 | 13 | debug "Matched one" 14 | match "LOL" { 15 | callback = function() 16 | debug "lol" 17 | write "Foo\r" 18 | end 19 | } 20 | -- Also valid: 21 | -- write "Foo" 22 | match "Foo" { 23 | callback = function() 24 | debug "foo matched too" 25 | end 26 | } 27 | end 28 | } 29 | match "Two" { 30 | callback = function() 31 | -- debug "Called two" 32 | end 33 | } 34 | end) 35 | -------------------------------------------------------------------------------- /contrib/orch/.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint orch.lua 2 | on: 3 | push: 4 | branches: ['**'] 5 | pull_request: 6 | types: [opened, reopened, edited, synchronize] 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | build: 13 | name: Lint 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: checkout 17 | uses: actions/checkout@v4 18 | - name: install system packages 19 | run: | 20 | sudo apt-get update --quiet || true 21 | sudo apt-get -yq --no-install-suggests --no-install-recommends install cmake luarocks 22 | - name: install lua packages 23 | run: sudo luarocks install luacheck 24 | - name: configure 25 | run: | 26 | mkdir build 27 | cd build && cmake .. 28 | - name: run linter 29 | run: make -C build lint 30 | -------------------------------------------------------------------------------- /lib/freebsd/sys/getcwd/lua_getcwd.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | static int 17 | l_getcwd(lua_State *L) 18 | { 19 | char buf[PATH_MAX]; 20 | 21 | if (getcwd(buf, sizeof(buf)) == NULL) { 22 | lua_pushnil(L); 23 | lua_pushstring(L, strerror(errno)); 24 | lua_pushinteger(L, errno); 25 | return (3); 26 | } 27 | 28 | lua_pushstring(L, buf); 29 | return (1); 30 | } 31 | 32 | static const struct luaL_Reg l_getcwdtab[] = { 33 | { "getcwd", l_getcwd }, 34 | { NULL, NULL }, 35 | }; 36 | 37 | int luaopen_getcwd(lua_State *L); 38 | 39 | int 40 | luaopen_getcwd(lua_State *L) 41 | { 42 | lua_newtable(L); 43 | luaL_setfuncs(L, l_getcwdtab, 0); 44 | return (1); 45 | } 46 | -------------------------------------------------------------------------------- /lib/freebsd/sys/chdir/lua_chdir.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | static int 16 | l_chdir(lua_State *L) 17 | { 18 | const char *path; 19 | int ret; 20 | 21 | path = luaL_checkstring(L, 1); 22 | 23 | ret = chdir(path); 24 | if (ret == -1) { 25 | lua_pushnil(L); 26 | lua_pushstring(L, strerror(errno)); 27 | lua_pushinteger(L, errno); 28 | return (3); 29 | } 30 | 31 | lua_pushboolean(L, 1); 32 | return (1); 33 | } 34 | 35 | static const struct luaL_Reg l_chdirtab[] = { 36 | {"chdir", l_chdir}, 37 | {NULL, NULL}, 38 | }; 39 | 40 | int luaopen_chdir(lua_State *L); 41 | 42 | int 43 | luaopen_chdir(lua_State *L) 44 | { 45 | lua_newtable(L); 46 | 47 | luaL_setfuncs(L, l_chdirtab, 0); 48 | 49 | return (1); 50 | } 51 | -------------------------------------------------------------------------------- /lib/freebsd/sys/mkdir/lua_mkdir.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | static int 17 | l_mkdir(lua_State *L) 18 | { 19 | const char *path; 20 | mode_t mode; 21 | int ret; 22 | 23 | path = luaL_checkstring(L, 1); 24 | mode = luaL_checkinteger(L, 2); 25 | 26 | ret = mkdir(path, mode); 27 | if (ret == -1) { 28 | lua_pushnil(L); 29 | lua_pushstring(L, strerror(errno)); 30 | lua_pushinteger(L, errno); 31 | return (3); 32 | } 33 | 34 | lua_pushboolean(L, 1); 35 | return (1); 36 | } 37 | 38 | static const struct luaL_Reg l_mkdirtab[] = { 39 | {"mkdir", l_mkdir}, 40 | {NULL, NULL}, 41 | }; 42 | 43 | int luaopen_mkdir(lua_State *L); 44 | 45 | int 46 | luaopen_mkdir(lua_State *L) 47 | { 48 | lua_newtable(L); 49 | luaL_setfuncs(L, l_mkdirtab, 0); 50 | return (1); 51 | } 52 | -------------------------------------------------------------------------------- /lib/freebsd/lua_freebsd_meta.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | 16 | static int 17 | l_freebsd_sys_fd_gc(lua_State *L) 18 | { 19 | int error, *fdp; 20 | 21 | fdp = luaL_checkudata(L, 1, FREEBSD_SYS_FD_REGISTRY_KEY); 22 | if (*fdp != -1) { 23 | error = close(*fdp); 24 | assert(error == 0); 25 | *fdp = -1; 26 | } 27 | return (0); 28 | } 29 | 30 | static const struct luaL_Reg l_freebsd_sys_fd[] = { 31 | { "__gc", l_freebsd_sys_fd_gc }, 32 | { NULL, NULL }, 33 | }; 34 | 35 | int luaopen_freebsd_meta(lua_State *L); 36 | 37 | int 38 | luaopen_freebsd_meta(lua_State *L) 39 | { 40 | int ret; 41 | 42 | ret = luaL_newmetatable(L, FREEBSD_SYS_FD_REGISTRY_KEY); 43 | assert(ret == 1); 44 | luaL_setfuncs(L, l_freebsd_sys_fd, 0); 45 | 46 | lua_newtable(L); 47 | 48 | return (1); 49 | } 50 | -------------------------------------------------------------------------------- /contrib/orch/include/orch.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2024 Kyle Evans 3 | * 4 | * SPDX-License-Identifier: BSD-2-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | 15 | #define ORCHLUA_MODNAME "orch.core" 16 | #define ORCHTTY_MODNAME "orch.tty" 17 | 18 | /* sys/cdefs.h */ 19 | #ifndef __unused 20 | #define __unused __attribute__((unused)) 21 | #endif 22 | #ifndef __printflike 23 | #define __printflike(fmtarg, firstvararg) \ 24 | __attribute__((__format__ (__printf__, fmtarg, firstvararg))) 25 | #endif 26 | 27 | /* orch_compat.c */ 28 | #ifdef __linux__ 29 | size_t strlcpy(char * __restrict, const char * __restrict, size_t); 30 | size_t strlcat(char * __restrict, const char * __restrict, size_t); 31 | #endif 32 | #if defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || \ 33 | defined(__NetBSD__) 34 | int tcsetsid(int, int); 35 | #endif 36 | 37 | /* orch_lua.c */ 38 | int luaopen_orch_core(lua_State *); 39 | int luaopen_orch_tty(lua_State *); 40 | -------------------------------------------------------------------------------- /lib/freebsd/sys/open/lua_open.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | static int 19 | l_close(lua_State *L) 20 | { 21 | int error, *fdp; 22 | 23 | fdp = luaL_checkudata(L, 1, FREEBSD_SYS_FD_REGISTRY_KEY); 24 | assert(*fdp != -1); 25 | error = close(*fdp); 26 | if (error == -1) { 27 | lua_pushnil(L); 28 | lua_pushstring(L, strerror(errno)); 29 | lua_pushinteger(L, errno); 30 | return (3); 31 | } 32 | *fdp = -1; 33 | lua_pushboolean(L, 1); 34 | return (1); 35 | } 36 | 37 | static const luaL_Reg l_opentab[] = { 38 | { "close", l_close }, 39 | { NULL, NULL }, 40 | }; 41 | 42 | int luaopen_open(lua_State *L); 43 | 44 | int 45 | luaopen_open(lua_State *L) 46 | { 47 | lua_newtable(L); 48 | luaL_setfuncs(L, l_opentab, 0); 49 | return (1); 50 | } 51 | -------------------------------------------------------------------------------- /contrib/orch/lib/core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | file(GLOB core_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/*.c) 3 | 4 | if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") 5 | add_compile_options(-D_GNU_SOURCE) 6 | endif() 7 | 8 | add_library(core SHARED ${core_SOURCES}) 9 | set_target_properties(core PROPERTIES 10 | PREFIX "") 11 | add_library(core_static STATIC ${core_SOURCES}) 12 | 13 | set(core_INCDIRS 14 | "${CMAKE_SOURCE_DIR}/include" 15 | "${CMAKE_SOURCE_DIR}/lib" 16 | "${LUA_INCLUDE_DIR}") 17 | 18 | target_include_directories(core PRIVATE ${core_INCDIRS}) 19 | # orch(1) will link against the static lib 20 | target_include_directories(core_static PRIVATE ${core_INCDIRS}) 21 | 22 | target_link_libraries(core "${LUA_LIBRARIES}") 23 | 24 | # Disable all sanitizers for the dynamic library, because that requires us to 25 | # also have a lua built with them enabled. For our purposes, it's sufficient to 26 | # just roll with a sanitizer-enabled static build since that's what we'll be 27 | # using to run our tests. 28 | target_compile_options(core PUBLIC -fno-sanitize=all) 29 | target_link_options(core PUBLIC -fno-sanitize=all) 30 | 31 | install(TARGETS core 32 | DESTINATION "${LUA_MODLIBDIR}/orch") 33 | -------------------------------------------------------------------------------- /lib/freebsd/sys/getuid/lua_getuid.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | static int 14 | l_getuid(lua_State *L) 15 | { 16 | lua_pushinteger(L, getuid()); 17 | return (1); 18 | } 19 | 20 | static int 21 | l_geteuid(lua_State *L) 22 | { 23 | lua_pushinteger(L, geteuid()); 24 | return (1); 25 | } 26 | 27 | static int 28 | l_getgid(lua_State *L) 29 | { 30 | lua_pushinteger(L, getgid()); 31 | return (1); 32 | } 33 | 34 | static int 35 | l_getegid(lua_State *L) 36 | { 37 | lua_pushinteger(L, getegid()); 38 | return (1); 39 | } 40 | 41 | static int 42 | l_issetugid(lua_State *L) 43 | { 44 | lua_pushboolean(L, issetugid() != 0); 45 | return (1); 46 | } 47 | 48 | static const struct luaL_Reg l_getuidtab[] = { 49 | { "getuid", l_getuid }, 50 | { "geteuid", l_geteuid }, 51 | { "getgid", l_getgid }, 52 | { "getegid", l_getegid }, 53 | { "issetugid", l_issetugid }, 54 | { NULL, NULL }, 55 | }; 56 | 57 | int luaopen_getuid(lua_State *L); 58 | 59 | int 60 | luaopen_getuid(lua_State *L) 61 | { 62 | lua_newtable(L); 63 | luaL_setfuncs(L, l_getuidtab, 0); 64 | return (1); 65 | } 66 | -------------------------------------------------------------------------------- /lib/freebsd/uname/lua_uname.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | static int 17 | l_uname(lua_State *L) 18 | { 19 | struct utsname name; 20 | int error; 21 | 22 | luaL_argcheck(L, lua_gettop(L) == 0, 1, "too many arguments"); 23 | 24 | error = uname(&name); 25 | if (error != 0) { 26 | error = errno; 27 | lua_pushnil(L); 28 | lua_pushstring(L, strerror(error)); 29 | lua_pushinteger(L, error); 30 | return (3); 31 | } 32 | 33 | lua_newtable(L); 34 | #define ADDFIELD(f) do { \ 35 | lua_pushstring(L, name.f); \ 36 | lua_setfield(L, -2, #f); \ 37 | } while (0) 38 | ADDFIELD(sysname); 39 | ADDFIELD(nodename); 40 | ADDFIELD(release); 41 | ADDFIELD(version); 42 | ADDFIELD(machine); 43 | #undef ADDFIELD 44 | 45 | return (1); 46 | } 47 | 48 | static const struct luaL_Reg l_unametab[] = { 49 | { "uname", l_uname }, 50 | { NULL, NULL }, 51 | }; 52 | 53 | int luaopen_uname(lua_State *L); 54 | 55 | int 56 | luaopen_uname(lua_State *L) 57 | { 58 | lua_newtable(L); 59 | luaL_setfuncs(L, l_unametab, 0); 60 | return (1); 61 | } 62 | -------------------------------------------------------------------------------- /contrib/orch/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Kyle Evans 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15 | ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21 | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22 | SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /lib/freebsd/libgen/lua_libgen.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | static int 17 | l_basename(lua_State *L) 18 | { 19 | char *path; 20 | 21 | path = strdup(luaL_checkstring(L, 1)); 22 | if (path == NULL) { 23 | lua_pushnil(L); 24 | lua_pushstring(L, strerror(errno)); 25 | lua_pushinteger(L, errno); 26 | return (3); 27 | } 28 | 29 | lua_pushstring(L, basename(path)); 30 | free(path); 31 | return (1); 32 | } 33 | 34 | static int 35 | l_dirname(lua_State *L) 36 | { 37 | char *path; 38 | 39 | path = strdup(luaL_checkstring(L, 1)); 40 | if (path == NULL) { 41 | lua_pushnil(L); 42 | lua_pushstring(L, strerror(errno)); 43 | lua_pushinteger(L, errno); 44 | return (3); 45 | } 46 | 47 | lua_pushstring(L, dirname(path)); 48 | free(path); 49 | return (1); 50 | } 51 | 52 | static const struct luaL_Reg l_libgentab[] = { 53 | { "basename", l_basename }, 54 | { "dirname", l_dirname }, 55 | { NULL, NULL } 56 | }; 57 | 58 | int luaopen_libgen(lua_State *L); 59 | 60 | int 61 | luaopen_libgen(lua_State *L) 62 | { 63 | lua_newtable(L); 64 | luaL_setfuncs(L, l_libgentab, 0); 65 | return (1); 66 | } 67 | -------------------------------------------------------------------------------- /contrib/orch/README.md: -------------------------------------------------------------------------------- 1 | # orch 2 | 3 | Orch is a program orchestration tool, inspired by expect(1) but scripted with 4 | lua. This utility allows scripted manipulation of programs for, e.g., testing 5 | or automation purposes. Orch drives spawn processes over a pts(4) 6 | pseudo-terminal, which allows for a broader range of interactions with a program 7 | under orchestration. 8 | 9 | The authoritative source for this software is located at 10 | https://git.kevans.dev/kevans/orch, but it's additionally mirrored to 11 | [GitHub](https://github.com/kevans91/orch) for user-facing interactions. Pull 12 | requests and Issues are open on GitHub. 13 | 14 | orch(1) strives to be portable. Currently supported platforms: 15 | - FreeBSD 16 | - OpenBSD 17 | - NetBSD 18 | - macOS 19 | - Linux (tested on Ubuntu only) 20 | 21 | ## Notes for porting 22 | 23 | We build on all of the above platforms. To build and actually use orch, one 24 | needs: 25 | 26 | - cmake 27 | - liblua + headers (orch(1) supports 5.2+) 28 | - a compiler 29 | - this source tree 30 | 31 | CMake's built-in FindLua support will be used, but you may need to tweak the 32 | following variables for install: 33 | 34 | - LUA_MODLIBDIR (default: /usr/local/lib/lua/MAJOR.MINOR) - path to install lua 35 | shared library modules 36 | - LUA_MODSHAREDIR (default: /usr/local/share/lua/MAJOR.MINOR) - path to install 37 | lua .lua modules 38 | -------------------------------------------------------------------------------- /contrib/orch/lib/orch/matchers.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright (c) 2024 Kyle Evans 3 | -- 4 | -- SPDX-License-Identifier: BSD-2-Clause 5 | -- 6 | 7 | local core = require('orch.core') 8 | local matchers = {} 9 | 10 | local PatternMatcher = {} 11 | function PatternMatcher:new() 12 | local obj = setmetatable({}, self) 13 | self.__index = self 14 | return obj 15 | end 16 | function PatternMatcher.match() 17 | -- All matchers should return start, last of match 18 | return false 19 | end 20 | 21 | local LuaMatcher = PatternMatcher:new() 22 | function LuaMatcher.match(pattern, buffer) 23 | return buffer:find(pattern) 24 | end 25 | 26 | local PlainMatcher = PatternMatcher:new() 27 | function PlainMatcher.match(pattern, buffer) 28 | return buffer:find(pattern, nil, true) 29 | end 30 | 31 | local PosixMatcher = PatternMatcher:new() 32 | function PosixMatcher.compile(pattern) 33 | return assert(core.regcomp(pattern)) 34 | end 35 | function PosixMatcher.match(pattern, buffer) 36 | return pattern:find(buffer) 37 | end 38 | 39 | -- Exported: the base for making new matchers, as well as a table of available 40 | -- matchers. 41 | matchers.PatternMatcher = PatternMatcher 42 | 43 | -- default will be configurable via `matcher()` 44 | matchers.available = { 45 | default = LuaMatcher, 46 | lua = LuaMatcher, 47 | plain = PlainMatcher, 48 | posix = PosixMatcher, 49 | } 50 | 51 | return matchers 52 | -------------------------------------------------------------------------------- /lib/freebsd/mktemp/lua_mktemp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | static int 18 | l_mkstemp(lua_State *L) 19 | { 20 | const char *template; 21 | char *name; 22 | int fd, *fdp; 23 | 24 | template = luaL_checkstring(L, 1); 25 | name = strdup(template); 26 | if (name == NULL) { 27 | lua_pushnil(L); 28 | lua_pushstring(L, strerror(errno)); 29 | lua_pushinteger(L, errno); 30 | return (3); 31 | } 32 | 33 | fd = mkstemp(name); 34 | if (fd == -1) { 35 | lua_pushnil(L); 36 | lua_pushstring(L, strerror(errno)); 37 | lua_pushinteger(L, errno); 38 | free(name); 39 | return (3); 40 | } 41 | 42 | fdp = lua_newuserdata(L, sizeof(int)); 43 | *fdp = fd; 44 | luaL_getmetatable(L, FREEBSD_SYS_FD_REGISTRY_KEY); 45 | lua_setmetatable(L, -2); 46 | lua_pushstring(L, name); 47 | free(name); 48 | return (2); 49 | } 50 | 51 | static const struct luaL_Reg l_mktemptab[] = { 52 | { "mkstemp", l_mkstemp }, 53 | { NULL, NULL }, 54 | }; 55 | 56 | int luaopen_mktemp(lua_State *L); 57 | 58 | int 59 | luaopen_mktemp(lua_State *L) 60 | { 61 | lua_newtable(L); 62 | luaL_setfuncs(L, l_mktemptab, 0); 63 | return (1); 64 | } 65 | -------------------------------------------------------------------------------- /contrib/orch/src/orch.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2024 Kyle Evans 3 | * 4 | * SPDX-License-Identifier: BSD-2-Clause 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "orch.h" 12 | #include "orch_bin.h" 13 | 14 | #ifndef __dead2 15 | #define __dead2 __attribute__((noreturn)) 16 | #endif 17 | 18 | static void __dead2 19 | usage(const char *name, int error) 20 | { 21 | FILE *f; 22 | 23 | if (error == 0) 24 | f = stdout; 25 | else 26 | f = stderr; 27 | 28 | fprintf(f, "usage: %s [-f file] [command [argument ...]]\n", name); 29 | exit(error); 30 | } 31 | 32 | int 33 | main(int argc, char *argv[]) 34 | { 35 | const char *invoke_path = argv[0]; 36 | const char *scriptf = "-"; /* stdin */ 37 | int ch; 38 | 39 | while ((ch = getopt(argc, argv, "f:h")) != -1) { 40 | switch (ch) { 41 | case 'f': 42 | scriptf = optarg; 43 | break; 44 | case 'h': 45 | usage(invoke_path, 0); 46 | default: 47 | usage(invoke_path, 1); 48 | } 49 | } 50 | 51 | argc -= optind; 52 | argv += optind; 53 | 54 | /* 55 | * If we have a command supplied, we'll spawn() it for the script just to 56 | * simplify things. If we didn't, then the script just needs to make sure 57 | * that it spawns something before a match/one block. 58 | */ 59 | return (orch_interp(scriptf, invoke_path, argc, 60 | (const char * const *)argv)); 61 | } 62 | -------------------------------------------------------------------------------- /lib/freebsd/sys.lua: -------------------------------------------------------------------------------- 1 | -- SPDX-License-Identifier: BSD-2-Clause 2 | -- 3 | -- Copyright (c) Mark Johnston 4 | 5 | local scriptdir = arg[0]:match("(.+)/[^/]+$") 6 | local oldcpath = package.cpath 7 | package.cpath = oldcpath .. ";" .. scriptdir .. "/lib/freebsd/sys/?/?.so" .. 8 | ";" .. scriptdir .. "/lib/freebsd/?/?.so" .. 9 | ";" .. scriptdir .. "/lib/freebsd/?.so" 10 | 11 | -- Register some udata metatables in the registry. 12 | require 'freebsd_meta' 13 | 14 | local M = { 15 | access = require 'access', 16 | chdir = require 'chdir', 17 | execve = require 'execve', 18 | getcwd = require 'getcwd', 19 | getuid = require 'getuid', 20 | mkdir = require 'mkdir', 21 | open = require 'open', 22 | pipe = require 'pipe', 23 | poll = require 'poll', 24 | read = require 'read', 25 | socket = require 'socket', 26 | stat = require 'stat', 27 | symlink = require 'symlink', 28 | sysctl = require 'sysctl', 29 | unlink = require 'unlink', 30 | wait = require 'wait', 31 | 32 | errno = require 'errno', 33 | getaddrinfo = require 'getaddrinfo', 34 | glob = require 'glob', 35 | libgen = require 'libgen', 36 | mktemp = require 'mktemp', 37 | posix_spawn = require 'posix_spawn', 38 | sysconf = require 'sysconf', 39 | uname = require 'uname', 40 | } 41 | 42 | package.cpath = oldcpath 43 | 44 | return M 45 | -------------------------------------------------------------------------------- /lib/freebsd/sys/access/lua_access.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | static int 16 | l_access1(lua_State *L, int (*fn)(const char *, int)) 17 | 18 | { 19 | const char *path; 20 | int mode, ret; 21 | 22 | path = luaL_checkstring(L, 1); 23 | mode = luaL_checkinteger(L, 2); 24 | 25 | ret = fn(path, mode); 26 | if (ret == -1) { 27 | lua_pushnil(L); 28 | lua_pushstring(L, strerror(errno)); 29 | lua_pushinteger(L, errno); 30 | return (3); 31 | } 32 | 33 | lua_pushboolean(L, 1); 34 | return (1); 35 | } 36 | 37 | static int 38 | l_access(lua_State *L) 39 | { 40 | return (l_access1(L, access)); 41 | } 42 | 43 | static int 44 | l_eaccess(lua_State *L) 45 | { 46 | return (l_access1(L, eaccess)); 47 | } 48 | 49 | static const struct luaL_Reg l_accesstab[] = { 50 | {"access", l_access}, 51 | {"eaccess", l_eaccess}, 52 | {NULL, NULL}, 53 | }; 54 | 55 | int luaopen_access(lua_State *L); 56 | 57 | int 58 | luaopen_access(lua_State *L) 59 | { 60 | lua_newtable(L); 61 | 62 | luaL_setfuncs(L, l_accesstab, 0); 63 | 64 | lua_pushinteger(L, R_OK); 65 | lua_setfield(L, -2, "R_OK"); 66 | lua_pushinteger(L, W_OK); 67 | lua_setfield(L, -2, "W_OK"); 68 | lua_pushinteger(L, X_OK); 69 | lua_setfield(L, -2, "X_OK"); 70 | 71 | return (1); 72 | } 73 | -------------------------------------------------------------------------------- /lib/freebsd/sys/symlink/lua_symlink.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | static int 16 | l_symlink(lua_State *L) 17 | { 18 | const char *target, *linkpath; 19 | int error; 20 | 21 | target = luaL_checkstring(L, 1); 22 | linkpath = luaL_checkstring(L, 2); 23 | 24 | error = symlink(target, linkpath); 25 | if (error == -1) { 26 | lua_pushnil(L); 27 | lua_pushstring(L, strerror(errno)); 28 | lua_pushinteger(L, errno); 29 | return (3); 30 | } 31 | 32 | lua_pushboolean(L, 1); 33 | return (1); 34 | } 35 | 36 | static int 37 | l_readlink(lua_State *L) 38 | { 39 | const char *path; 40 | char buf[PATH_MAX]; 41 | ssize_t len; 42 | 43 | path = luaL_checkstring(L, 1); 44 | 45 | len = readlink(path, buf, sizeof(buf)); 46 | if (len == -1) { 47 | lua_pushnil(L); 48 | lua_pushstring(L, strerror(errno)); 49 | lua_pushinteger(L, errno); 50 | return (3); 51 | } 52 | 53 | lua_pushlstring(L, buf, len); 54 | return (1); 55 | } 56 | 57 | static const struct luaL_Reg l_symlinktab[] = { 58 | { "symlink", l_symlink }, 59 | { "readlink", l_readlink }, 60 | { NULL, NULL }, 61 | }; 62 | 63 | int luaopen_symlink(lua_State *L); 64 | 65 | int 66 | luaopen_symlink(lua_State *L) 67 | { 68 | lua_newtable(L); 69 | luaL_setfuncs(L, l_symlinktab, 0); 70 | return (1); 71 | } 72 | -------------------------------------------------------------------------------- /contrib/orch/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18) 2 | 3 | project(orch) 4 | 5 | find_package("Lua") 6 | 7 | if(NOT LUA_FOUND) 8 | message(FATAL_ERROR "No Lua installation found; please install Lua") 9 | endif() 10 | 11 | set(LUA_MODLIBDIR "lib/lua/${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}" 12 | CACHE PATH "Path to install lua dynamic library modules into") 13 | set(LUA_MODSHAREDIR "share/lua/${LUA_VERSION_MAJOR}.${LUA_VERSION_MINOR}" 14 | CACHE PATH "Path to install lua modules into") 15 | if(IS_ABSOLUTE "${LUA_MODSHAREDIR}") 16 | set(ORCHLUA_PATH "${LUA_MODSHAREDIR}" 17 | CACHE PATH "Path to install orch.lua into") 18 | else() 19 | set(ORCHLUA_PATH "${CMAKE_INSTALL_PREFIX}/${LUA_MODSHAREDIR}" 20 | CACHE PATH "Path to install orch.lua into") 21 | endif() 22 | set(ORCHLUA_BINDIR "bin" 23 | CACHE PATH "Path to install orch(1) into") 24 | set(ORCHLUA_EXAMPLESDIR "share/examples/${CMAKE_PROJECT_NAME}" 25 | CACHE PATH "Path to install .orch examples into") 26 | 27 | option(EXAMPLES "Install examples" ON) 28 | option(MANPAGES "Install manpages" ON) 29 | option(BUILD_DRIVER "Build the orch(1) driver" ON) 30 | 31 | set(warnings "-Wall -Wextra -Werror") 32 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 33 | add_compile_options(-fsanitize=address,undefined -fstrict-aliasing) 34 | add_link_options(-fsanitize=address,undefined -fstrict-aliasing) 35 | endif() 36 | 37 | if(EXAMPLES) 38 | add_subdirectory(examples) 39 | endif() 40 | add_subdirectory(lib) 41 | if(MANPAGES) 42 | add_subdirectory(man) 43 | endif() 44 | if(BUILD_DRIVER) 45 | add_subdirectory(src) 46 | endif() 47 | add_subdirectory(tests) 48 | -------------------------------------------------------------------------------- /contrib/orch/.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build orch 2 | on: 3 | push: 4 | branches: ['**'] 5 | pull_request: 6 | types: [opened, reopened, edited, synchronize] 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | build: 13 | name: Build ${{ matrix.os }} (lua${{ matrix.lua }}) 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-20.04, ubuntu-22.04, macos-latest] 18 | lua: [5.3, 5.4] 19 | include: 20 | - os: ubuntu-20.04 21 | - os: ubuntu-22.04 22 | - os: macos-latest 23 | lua: 5.4 24 | pkgs: cmake coreutils lua@5.4 25 | exclude: 26 | - os: ubuntu-20.04 27 | lua: 5.4 28 | - os: macos-latest 29 | lua: 5.3 30 | steps: 31 | - name: checkout 32 | uses: actions/checkout@v4 33 | - name: install system packages (Ubuntu) 34 | if: runner.os == 'Linux' 35 | run: | 36 | sudo apt-get update --quiet || true 37 | sudo apt-get -yq --no-install-suggests --no-install-recommends install cmake liblua${{ matrix.lua }}-dev 38 | - name: install system packages (macOS) 39 | if: runner.os == 'macOS' 40 | run: | 41 | brew update --quiet || true 42 | brew install ${{ matrix.pkgs }} 43 | - name: configure 44 | run: | 45 | mkdir build 46 | cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. 47 | - name: build orch(1) 48 | run: make -C build 49 | - name: Run self-tests 50 | run: make -C build check 51 | -------------------------------------------------------------------------------- /contrib/orch/lib/orch.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright (c) 2024 Kyle Evans 3 | -- 4 | -- SPDX-License-Identifier: BSD-2-Clause 5 | -- 6 | 7 | local core = require("orch.core") 8 | local direct = require("orch.direct") 9 | local scripter = require("orch.scripter") 10 | local orch = {} 11 | 12 | -- env: table of values that will be exposed to the orch script's environment. 13 | -- A user of this library may add to the orch.env before calling 14 | -- orch.run_script() and see their changes in the script's environment. 15 | orch.env = scripter.env 16 | 17 | -- run_script(scriptfile[, config]): run `scriptfile` as an .orch script, with 18 | -- an optional configuration table that may be supplied. 19 | -- 20 | -- The currently recognized configuration items are `alter_path` (boolean) that 21 | -- indicates that the script's directory should be added to PATH, and `command` 22 | -- (table) to indicate the argv of a process to spawn before running the script. 23 | orch.run_script = scripter.run_script 24 | 25 | -- sleep(duration): sleep for the given duration, in seconds. Fractional 26 | -- seconds are supported; core uses nanosleep(2) to implement sleep(), so this 27 | -- is at least somewhat high resolution. 28 | orch.sleep = core.sleep 29 | 30 | -- spawn(cmd...): spawn the given command, returning a process that may be 31 | -- manipulated as needed. 32 | orch.spawn = direct.spawn 33 | 34 | -- Reset all of the state; this largely means resetting the scripting bits, as 35 | -- a user of this lib won't really need to reset anything. 36 | function orch.reset() 37 | scripter.reset() 38 | assert(core.reset()) 39 | end 40 | 41 | return orch 42 | -------------------------------------------------------------------------------- /lib/freebsd/sys/unlink/lua_unlink.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | static int 20 | l_unlink(lua_State *L) 21 | { 22 | const char *path; 23 | int error; 24 | 25 | path = luaL_checkstring(L, 1); 26 | 27 | error = unlink(path); 28 | if (error == -1) { 29 | lua_pushnil(L); 30 | lua_pushstring(L, strerror(errno)); 31 | lua_pushinteger(L, errno); 32 | return (3); 33 | } 34 | 35 | lua_pushinteger(L, 0); 36 | return (1); 37 | } 38 | 39 | static int 40 | l_unlinkat(lua_State *L) 41 | { 42 | const char *path; 43 | int *dfdp, error, flags; 44 | 45 | dfdp = luaL_checkudata(L, 1, FREEBSD_SYS_FD_REGISTRY_KEY); 46 | assert(*dfdp != -1); 47 | path = luaL_checkstring(L, 2); 48 | flags = luaL_optinteger(L, 3, 0); 49 | 50 | error = unlinkat(*dfdp, path, flags); 51 | if (error == -1) { 52 | lua_pushnil(L); 53 | lua_pushstring(L, strerror(errno)); 54 | lua_pushinteger(L, errno); 55 | return (3); 56 | } 57 | 58 | lua_pushinteger(L, 0); 59 | return (1); 60 | } 61 | 62 | static const struct luaL_Reg l_unlinktab[] = { 63 | { "unlink", l_unlink }, 64 | { "unlinkat", l_unlinkat }, 65 | { NULL, NULL }, 66 | }; 67 | 68 | int luaopen_unlink(lua_State *L); 69 | 70 | int 71 | luaopen_unlink(lua_State *L) 72 | { 73 | lua_newtable(L); 74 | luaL_setfuncs(L, l_unlinktab, 0); 75 | #define ADDFLAG(v) do { \ 76 | lua_pushinteger(L, v); \ 77 | lua_setfield(L, -2, #v); \ 78 | } while (0) 79 | ADDFLAG(AT_REMOVEDIR); 80 | ADDFLAG(AT_RESOLVE_BENEATH); 81 | #undef ADDFLAG 82 | return (1); 83 | } 84 | -------------------------------------------------------------------------------- /bricoler-completion.bash: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-License-Identifier: BSD-2-Clause 3 | # 4 | # Copyright (c) Mark Johnston 5 | # 6 | 7 | _bricoler_completions() 8 | { 9 | local cmd completes cur params prev subcmds tasks 10 | 11 | cmd=$1 12 | subcmds="list run" 13 | tasks=$("$cmd" list 2>/dev/null) 14 | 15 | cur="${COMP_WORDS[COMP_CWORD]}" 16 | prev="${COMP_WORDS[COMP_CWORD - 1]}" 17 | # Magic to ensure that := can be completed as I want. 18 | # bash includes ":" in COMP_WORDBREAKS, so this doesn't work otherwise. 19 | _get_comp_words_by_ref -n ':=' cur prev 20 | 21 | if [ $COMP_CWORD -eq 1 ]; then 22 | # Complete the verb. 23 | COMPREPLY=($(compgen -W "$subcmds" -- "$cur")) 24 | return 0 25 | fi 26 | 27 | params="" 28 | if [ $COMP_CWORD -ge 2 -a ${COMP_WORDS[1]} = "run" ]; then 29 | # Assume that the argument following "run" is the task name. 30 | # This doesn't have to be true, maybe we should try harder... 31 | params=$("$cmd" list ${COMP_WORDS[2]} 2>/dev/null | awk '{print $1"="}') 32 | options="--show --param --workdir" 33 | fi 34 | 35 | case $prev in 36 | -p|--param) 37 | compopt -o nospace 38 | COMPREPLY=($(compgen -W "$params" -- "$cur")) 39 | # Complements the _get_comp_words_by_ref magic above. 40 | __ltrim_colon_completions "$cur" 41 | return 0 42 | ;; 43 | -w|--workdir) 44 | COMPREPLY=($(compgen -d -- "$cur")) 45 | return 0 46 | ;; 47 | run) 48 | COMPREPLY=($(compgen -W "$tasks" -- "$cur")) 49 | return 0 50 | ;; 51 | *) 52 | COMPREPLY=($(compgen -W "$options" -- "$cur")) 53 | return 0 54 | ;; 55 | esac 56 | } 57 | 58 | complete -F _bricoler_completions bricoler 59 | -------------------------------------------------------------------------------- /lib/freebsd/sys/pipe/lua_pipe.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | static int 20 | _l_pipe2(lua_State *L, int flags) 21 | { 22 | int fds[2], *fdp; 23 | 24 | if (pipe2(fds, flags) == -1) { 25 | lua_pushnil(L); 26 | lua_pushstring(L, strerror(errno)); 27 | lua_pushinteger(L, errno); 28 | return (3); 29 | } 30 | 31 | fdp = lua_newuserdata(L, sizeof(int)); 32 | *fdp = fds[0]; 33 | luaL_getmetatable(L, FREEBSD_SYS_FD_REGISTRY_KEY); 34 | lua_setmetatable(L, -2); 35 | fdp = lua_newuserdata(L, sizeof(int)); 36 | *fdp = fds[1]; 37 | luaL_getmetatable(L, FREEBSD_SYS_FD_REGISTRY_KEY); 38 | lua_setmetatable(L, -2); 39 | return (2); 40 | } 41 | 42 | static int 43 | l_pipe(lua_State *L) 44 | { 45 | return (_l_pipe2(L, 0)); 46 | } 47 | 48 | static int 49 | l_pipe2(lua_State *L) 50 | { 51 | lua_Integer lflags; 52 | 53 | lflags = luaL_checkinteger(L, 1); 54 | if (lflags < INT_MIN || lflags > INT_MAX) { 55 | lua_pushnil(L); 56 | lua_pushstring(L, "argument out of range"); 57 | return (2); 58 | } 59 | return (_l_pipe2(L, lflags)); 60 | } 61 | 62 | static const struct luaL_Reg l_pipetab[] = { 63 | { "pipe", l_pipe }, 64 | { "pipe2", l_pipe2 }, 65 | { NULL, NULL }, 66 | }; 67 | 68 | int luaopen_pipe(lua_State *L); 69 | 70 | int 71 | luaopen_pipe(lua_State *L) 72 | { 73 | lua_newtable(L); 74 | luaL_setfuncs(L, l_pipetab, 0); 75 | 76 | lua_pushinteger(L, O_CLOEXEC); 77 | lua_setfield(L, -2, "O_CLOEXEC"); 78 | lua_pushinteger(L, O_NONBLOCK); 79 | lua_setfield(L, -2, "O_NONBLOCK"); 80 | 81 | return (1); 82 | } 83 | -------------------------------------------------------------------------------- /lib/freebsd/glob/lua_glob.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | static int 15 | l_glob(lua_State *L) 16 | { 17 | const char *pattern; 18 | glob_t pglob; 19 | lua_Integer lflags; 20 | int flags, ret; 21 | 22 | pattern = luaL_checkstring(L, 1); 23 | lflags = luaL_optinteger(L, 2, 0); 24 | if (lflags < INT_MIN || lflags > INT_MAX) { 25 | return (luaL_error(L, "flags too large: %jx", 26 | (uintmax_t)lflags)); 27 | } 28 | flags = (int)lflags; 29 | 30 | /* For now we don't support errfunc, though that might be handy. */ 31 | ret = glob(pattern, flags, NULL, &pglob); 32 | if (ret != 0) { 33 | lua_pushnil(L); 34 | lua_pushinteger(L, ret); 35 | return (2); 36 | } 37 | 38 | lua_newtable(L); 39 | for (size_t i = 0; i < pglob.gl_pathc; i++) { 40 | lua_pushstring(L, pglob.gl_pathv[i]); 41 | lua_rawseti(L, -2, i + 1); 42 | } 43 | globfree(&pglob); 44 | return (1); 45 | } 46 | 47 | static const struct luaL_Reg l_globtab[] = { 48 | { "glob", l_glob }, 49 | { NULL, NULL } 50 | }; 51 | 52 | int luaopen_glob(lua_State *L); 53 | 54 | int 55 | luaopen_glob(lua_State *L) 56 | { 57 | lua_newtable(L); 58 | luaL_setfuncs(L, l_globtab, 0); 59 | #define ADDFLAG(c) \ 60 | lua_pushinteger(L, c); \ 61 | lua_setfield(L, -2, #c) 62 | /* Standard flags. */ 63 | ADDFLAG(GLOB_APPEND); 64 | ADDFLAG(GLOB_DOOFFS); 65 | ADDFLAG(GLOB_ERR); 66 | ADDFLAG(GLOB_MARK); 67 | ADDFLAG(GLOB_NOCHECK); 68 | ADDFLAG(GLOB_NOESCAPE); 69 | ADDFLAG(GLOB_NOSORT); 70 | /* Nonstandard flags. */ 71 | ADDFLAG(GLOB_ALTDIRFUNC); 72 | ADDFLAG(GLOB_BRACE); 73 | ADDFLAG(GLOB_MAGCHAR); 74 | ADDFLAG(GLOB_NOMAGIC); 75 | ADDFLAG(GLOB_TILDE); 76 | ADDFLAG(GLOB_LIMIT); 77 | /* Return values. */ 78 | ADDFLAG(GLOB_ABORTED); 79 | ADDFLAG(GLOB_NOMATCH); 80 | ADDFLAG(GLOB_NOSPACE); 81 | #undef ADDFLAG 82 | return (1); 83 | } 84 | -------------------------------------------------------------------------------- /lib/freebsd/sys/wait/lua_wait.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | /* 8 | * Wrappers for wait.h functions. 9 | * 10 | * Currently only waitpid(2) is supported. 11 | */ 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | static int 24 | l_waitpid(lua_State *L) 25 | { 26 | int error, options, status; 27 | pid_t pid; 28 | 29 | pid = luaL_checkinteger(L, 1); 30 | options = luaL_optinteger(L, 2, 0); 31 | 32 | error = waitpid(pid, &status, options); 33 | if (error == -1) { 34 | lua_pushnil(L); 35 | lua_pushstring(L, strerror(errno)); 36 | lua_pushinteger(L, errno); 37 | return (3); 38 | } else if (error == 0) { 39 | /* WNOHANG was specified. */ 40 | lua_pushinteger(L, error); 41 | lua_pushstring(L, "running"); 42 | return (2); 43 | } else { 44 | lua_pushinteger(L, error); 45 | if (WIFEXITED(status)) { 46 | lua_pushstring(L, "exited"); 47 | lua_pushinteger(L, WEXITSTATUS(status)); 48 | } else if (WIFSIGNALED(status)) { 49 | lua_pushstring(L, "signaled"); 50 | lua_pushinteger(L, WTERMSIG(status)); 51 | } else if (WIFSTOPPED(status)) { 52 | lua_pushstring(L, "stopped"); 53 | lua_pushinteger(L, WSTOPSIG(status)); 54 | } else { 55 | assert(0); 56 | } 57 | return (3); 58 | } 59 | } 60 | 61 | static const struct luaL_Reg l_waittab[] = { 62 | { "waitpid", l_waitpid }, 63 | { NULL, NULL } 64 | }; 65 | 66 | int luaopen_wait(lua_State *L); 67 | 68 | int 69 | luaopen_wait(lua_State *L) 70 | { 71 | lua_newtable(L); 72 | 73 | luaL_setfuncs(L, l_waittab, 0); 74 | #define ADDFLAG(c) \ 75 | lua_pushinteger(L, c); \ 76 | lua_setfield(L, -2, #c) 77 | ADDFLAG(WNOHANG); 78 | ADDFLAG(WUNTRACED); 79 | ADDFLAG(WTRAPPED); 80 | ADDFLAG(WEXITED); 81 | ADDFLAG(WSTOPPED); 82 | ADDFLAG(WNOWAIT); 83 | #undef ADDFLAG 84 | 85 | return (1); 86 | } 87 | -------------------------------------------------------------------------------- /contrib/orch/lib/orch/direct.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright (c) 2024 Kyle Evans 3 | -- 4 | -- SPDX-License-Identifier: BSD-2-Clause 5 | -- 6 | 7 | local actions = require('orch.actions') 8 | local context = require('orch.context') 9 | local matchers = require('orch.matchers') 10 | local process = require('orch.process') 11 | 12 | local direct = {} 13 | 14 | direct.defaults = { 15 | timeout = 10, 16 | } 17 | 18 | local direct_ctx = context:new() 19 | function direct_ctx.execute(_, callback) 20 | callback() 21 | end 22 | function direct_ctx:fail(_, contents) 23 | if self.fail_handler then 24 | self.fail_handler(contents) 25 | end 26 | return false 27 | end 28 | 29 | -- Wraps a process, provide everything we offer in actions.defined as a wrapper 30 | local DirectProcess = {} 31 | function DirectProcess:new(cmd, ctx) 32 | local pwrap = setmetatable({}, self) 33 | self.__index = self 34 | 35 | pwrap._process = process:new(cmd, ctx) 36 | pwrap.ctx = ctx 37 | pwrap.timeout = direct.defaults.timeout 38 | 39 | ctx.process = pwrap._process 40 | 41 | return pwrap 42 | end 43 | function DirectProcess:match(pattern, matcher) 44 | matcher = matcher or matchers.available.default 45 | 46 | local action = actions.MatchAction:new("match") 47 | action.timeout = self.timeout 48 | action.pattern = pattern 49 | action.matcher = matcher 50 | 51 | if matcher.compile then 52 | action.pattern_obj = action.matcher.compile(pattern) 53 | end 54 | 55 | return self._process:match(action) 56 | end 57 | for name, def in pairs(actions.defined) do 58 | -- Each of these gets a function that generates the action and then 59 | -- subsequently executes it. 60 | DirectProcess[name] = function(pwrap, ...) 61 | local action = actions.MatchAction:new(name, def.execute) 62 | local args = { ... } 63 | 64 | action.ctx = pwrap.ctx 65 | 66 | if def.init then 67 | def.init(action, args) 68 | end 69 | 70 | return action:execute() 71 | end 72 | end 73 | 74 | function direct.spawn(...) 75 | local fresh_ctx = {} 76 | 77 | for k, v in pairs(direct_ctx) do 78 | fresh_ctx[k] = v 79 | end 80 | 81 | return DirectProcess:new({...}, fresh_ctx) 82 | end 83 | 84 | return direct 85 | -------------------------------------------------------------------------------- /lib/freebsd/sys/socket/lua_socket.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | static int 19 | l_socket(lua_State *L) 20 | { 21 | int domain, type, protocol; 22 | int fd, *fdp; 23 | 24 | domain = luaL_checkinteger(L, 1); 25 | type = luaL_checkinteger(L, 2); 26 | protocol = luaL_optinteger(L, 3, 0); 27 | 28 | fd = socket(domain, type, protocol); 29 | if (fd == -1) { 30 | lua_pushnil(L); 31 | lua_pushstring(L, strerror(errno)); 32 | lua_pushinteger(L, errno); 33 | return (3); 34 | } 35 | 36 | fdp = lua_newuserdata(L, sizeof(int)); 37 | *fdp = fd; 38 | luaL_getmetatable(L, FREEBSD_SYS_FD_REGISTRY_KEY); 39 | lua_setmetatable(L, -2); 40 | return (1); 41 | } 42 | 43 | static int 44 | l_bind(lua_State *L) 45 | { 46 | const struct sockaddr_storage *ss; 47 | size_t sslen; 48 | int error, fd, *fdp; 49 | 50 | fdp = luaL_checkudata(L, 1, FREEBSD_SYS_FD_REGISTRY_KEY); 51 | fd = *fdp; 52 | ss = (const void *)luaL_checklstring(L, 2, &sslen); 53 | 54 | error = bind(fd, (const struct sockaddr *)ss, sslen); 55 | if (error == -1) { 56 | lua_pushnil(L); 57 | lua_pushstring(L, strerror(errno)); 58 | lua_pushinteger(L, errno); 59 | return (3); 60 | } 61 | lua_pushboolean(L, 1); 62 | return (1); 63 | } 64 | 65 | static const struct luaL_Reg l_sockettab[] = { 66 | { "socket", l_socket }, 67 | { "bind", l_bind }, 68 | { NULL, NULL } 69 | }; 70 | 71 | int luaopen_socket(lua_State *L); 72 | 73 | int 74 | luaopen_socket(lua_State *L) 75 | { 76 | lua_newtable(L); 77 | luaL_setfuncs(L, l_sockettab, 0); 78 | #define ADDCONST(c) do { \ 79 | lua_pushinteger(L, c); \ 80 | lua_setfield(L, -2, #c); \ 81 | } while (0) 82 | ADDCONST(PF_LOCAL); 83 | ADDCONST(PF_INET); 84 | ADDCONST(PF_INET6); 85 | ADDCONST(SOCK_STREAM); 86 | ADDCONST(SOCK_DGRAM); 87 | ADDCONST(SOCK_RAW); 88 | ADDCONST(SOCK_SEQPACKET); 89 | ADDCONST(SOCK_CLOEXEC); 90 | ADDCONST(SOCK_NONBLOCK); 91 | #undef ADDCONST 92 | return (1); 93 | } 94 | -------------------------------------------------------------------------------- /contrib/orch/man/orch.1: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright (c) 2024 Kyle Evans 3 | .\" 4 | .\" SPDX-License-Identifier: BSD-2-Clause 5 | .\" 6 | .Dd January 21, 2024 7 | .Dt ORCH 1 8 | .Os 9 | .Sh NAME 10 | .Nm orch 11 | .Nd Utility to orchestrate command line tools 12 | .Sh SYNOPSIS 13 | .Nm 14 | .Op Fl f Ar scriptfile 15 | .Op Ar command Op Ar argument .. 16 | .Nm 17 | .Op Fl h 18 | .Sh DESCRIPTION 19 | The 20 | .Nm 21 | utility allows scripted interactions with other command line tools. 22 | .Pp 23 | All uses of 24 | .Nm 25 | follow a specific pattern: spawn a command, optionally write to its stdin, check 26 | for patterns in its stdout. 27 | The last two steps may be interleaved in interesting ways based on program 28 | behavior and interaction. 29 | When 30 | .Nm 31 | spawns a command, it sets up a new 32 | .Xr pts 4 33 | and disables input echo on it. 34 | The command is not executed immediately, but instead waits for a release from 35 | the user script before processing. 36 | This is done so that the 37 | .Ar scriptfile 38 | may queue up some input to the program before it begins execution. 39 | See 40 | .Xr orch 5 41 | for more detail on 42 | .Nm 43 | scripts. 44 | .Pp 45 | The following options are available: 46 | .Bl -tag -width indent 47 | .It Fl f Ar scriptfile 48 | Uses the named 49 | .Ar scriptfile 50 | as the script to execute. 51 | Specifying 52 | .Dq - 53 | directs 54 | .Nm 55 | to read the script from stdin, and is the default behavior. 56 | .It Fl h 57 | Show a usage statement. 58 | .El 59 | .Pp 60 | If a 61 | .Ar command 62 | is specified, then 63 | .Nm 64 | will spawn it before executing the specified 65 | .Ar scriptfile . 66 | Execution will still be stalled until released, as described above. 67 | .Sh EXIT STATUS 68 | The 69 | .Nm 70 | utility exits 0 on success, and >0 if an error occurs. 71 | A 72 | .Fn match 73 | block failing is considered an error, unless it's within a 74 | .Fn one 75 | block. 76 | All blocks in a 77 | .Fn one 78 | block must fail to be considered an error. 79 | .Sh SEE ALSO 80 | .Xr expect 1 , 81 | .Xr pts 4 , 82 | .Xr orch 5 83 | .Sh AUTHORS 84 | .Nm 85 | was written by 86 | .An Kyle Evans Aq Mt kevans@FreeBSD.org 87 | for the express purpose of testing 88 | .Xr tty 4 89 | behavior. 90 | -------------------------------------------------------------------------------- /lib/freebsd/sys/execve/lua_execve.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | static int 17 | l_execve(lua_State *L) 18 | { 19 | const char *cmd; 20 | char **argv, **envp; 21 | unsigned int argc, envc; 22 | int error; 23 | 24 | argv = envp = NULL; 25 | 26 | luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, 27 | "bad argument type, expected a string"); 28 | luaL_argcheck(L, lua_type(L, 2) == LUA_TTABLE, 1, 29 | "bad argument type, expected a table"); 30 | luaL_argcheck(L, lua_type(L, 3) == LUA_TTABLE, 1, 31 | "bad argument type, expected a table"); 32 | 33 | cmd = lua_tostring(L, 1); 34 | 35 | lua_pushnil(L); 36 | for (argc = 0; lua_next(L, 2); argc++) 37 | lua_pop(L, 1); 38 | lua_pushnil(L); 39 | for (envc = 0; lua_next(L, 3); envc++) 40 | lua_pop(L, 1); 41 | 42 | argv = calloc(argc + 1, sizeof(char *)); 43 | if (argv == NULL) 44 | goto error; 45 | envp = calloc(envc + 1, sizeof(char *)); 46 | if (envp == NULL) 47 | goto error; 48 | 49 | lua_pushnil(L); 50 | for (size_t i = 0; lua_next(L, 2) != 0; i++) { 51 | argv[i] = __DECONST(char *, lua_tostring(L, -1)); 52 | lua_pop(L, 1); 53 | } 54 | lua_pushnil(L); 55 | for (size_t i = 0; lua_next(L, 3) != 0; i++) { 56 | envp[i] = __DECONST(char *, lua_tostring(L, -1)); 57 | lua_pop(L, 1); 58 | } 59 | 60 | (void)execve(cmd, argv, envp); 61 | 62 | error: 63 | error = errno; 64 | free(argv); 65 | free(envp); 66 | 67 | lua_pushnil(L); 68 | lua_pushstring(L, strerror(error)); 69 | lua_pushinteger(L, error); 70 | 71 | return (3); 72 | } 73 | 74 | static int 75 | l_fexecve(lua_State *L) 76 | { 77 | int error; 78 | 79 | lua_pushnil(L); 80 | error = ENOSYS; 81 | lua_pushstring(L, strerror(error)); 82 | lua_pushinteger(L, error); 83 | 84 | return (3); 85 | } 86 | 87 | static const struct luaL_Reg l_execvetab[] = { 88 | {"execve", l_execve}, 89 | {"fexecve", l_fexecve}, 90 | {NULL, NULL}, 91 | }; 92 | 93 | int luaopen_execve(lua_State *L); 94 | 95 | int 96 | luaopen_execve(lua_State *L) 97 | { 98 | lua_newtable(L); 99 | 100 | luaL_setfuncs(L, l_execvetab, 0); 101 | 102 | return (1); 103 | } 104 | -------------------------------------------------------------------------------- /run-kyua: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright (c) 2025 Mark Johnston 4 | # 5 | # SPDX-License-Identifier: BSD-2-Clause 6 | # 7 | 8 | usage() 9 | { 10 | cat <<__EOF__ >&2 11 | usage: run-kyua [-c ] [-j ] [-r ] 12 | [ [ ...]] 13 | 14 | Run the tests under , relative to /usr/tests, or if no test paths 15 | are specified, run the entire regression test suite. 16 | 17 | This is a helper script copied into VM images by bricoler in order to run the 18 | test suite. 19 | 20 | Options: 21 | -c Run the test suite times, stop if any failures occur 22 | default is 1. 23 | -d Provide the specified disks as scratch space for tests. 24 | -j Run up to tests in parallel, default is the 25 | number of CPUs in the system. 26 | -r Store the test results in the specified report DB, default 27 | is ./kyua.db. 28 | __EOF__ 29 | } 30 | 31 | COUNT=1 32 | DISK_LIST=" " 33 | PARALLELISM=$(sysctl -n hw.ncpu) 34 | REPORT_DB=./kyua.db 35 | REPORT_LOG=./kyua-report.txt 36 | 37 | while getopts "c:d:f:j:o:r:" opt; do 38 | case $opt in 39 | c) 40 | COUNT=$OPTARG 41 | ;; 42 | d) 43 | DISK_LIST=$OPTARG 44 | ;; 45 | f) 46 | RESULTS_FILTER="--results-filter $OPTARG" 47 | ;; 48 | j) 49 | PARALLELISM=$OPTARG 50 | ;; 51 | o) 52 | REPORT_LOG=$OPTARG 53 | ;; 54 | r) 55 | REPORT_DB=$OPTARG 56 | ;; 57 | *) 58 | usage 59 | exit 1 60 | ;; 61 | esac 62 | done 63 | shift $((OPTIND - 1)) 64 | TEST_PATH=$@ 65 | 66 | FIBS=$(seq 0 $(($(sysctl -n net.fibs) - 1))) 67 | 68 | for i in $(jot $COUNT); do 69 | # kyua refuses to overwrite its output database. 70 | rm -f $REPORT_DB 71 | 72 | kyua -v parallelism=$PARALLELISM \ 73 | -v test_suites.FreeBSD.unprivileged_user=tests \ 74 | -v test_suites.FreeBSD.allow_sysctl_side_effects=1 \ 75 | -v test_suites.FreeBSD.fibs="$FIBS" \ 76 | -v test_suites.FreeBSD.disks="$DISK_LIST" \ 77 | test -k /usr/tests/Kyuafile -r $REPORT_DB $TEST_PATH 78 | status=$? 79 | 80 | kyua report --verbose \ 81 | $RESULTS_FILTER \ 82 | --output=$REPORT_LOG -r $REPORT_DB 83 | 84 | if [ $status -ne 0 ]; then 85 | exit $? 86 | fi 87 | done 88 | 89 | exit 0 90 | -------------------------------------------------------------------------------- /contrib/orch/tests/basic_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | scriptdir=$(dirname $(realpath "$0")) 4 | if [ -n "$ORCHBIN" ]; then 5 | orchbin="$ORCHBIN" 6 | else 7 | orchbin="$scriptdir/../src/orch" 8 | if [ ! -x "$orchbin" ]; then 9 | orchbin="$(which orch)" 10 | fi 11 | fi 12 | if [ ! -x "$orchbin" ]; then 13 | 1>&2 echo "Failed to find a usable orch binary" 14 | exit 1 15 | fi 16 | 17 | orchdir="$(dirname "$orchbin")" 18 | 19 | if [ -n "$ORCHLUA_PATH" ]; then 20 | cd "$ORCHLUA_PATH" 21 | fi 22 | 23 | 1>&2 echo "Using binary: $orchbin" 24 | 25 | fails=0 26 | testid=1 27 | 28 | if [ $# -ge 1 ]; then 29 | tests="" 30 | 31 | for test in "$@"; do 32 | tests="$tests $scriptdir/$test.orch" 33 | done 34 | 35 | set -- $tests 36 | else 37 | set -- "$scriptdir"/*.orch 38 | fi 39 | 40 | echo "1..$#" 41 | 42 | ok() 43 | { 44 | echo "ok $testid - $f" 45 | testid=$((testid + 1)) 46 | } 47 | 48 | not_ok() 49 | { 50 | msg="$1" 51 | 52 | echo "not ok $testid - $f: $msg" 53 | testid=$((testid + 1)) 54 | fails=$((fails + 1)) 55 | } 56 | 57 | for f in "$@" ;do 58 | f=$(basename "$f" .orch) 59 | testf="$scriptdir/$f.orch" 60 | expected_rc=0 61 | spawn="cat" 62 | 63 | case "$f" in 64 | timeout_*) 65 | expected_rc=1 66 | expected_timeout=$(grep '^-- TIMEOUT:' "$testf" | grep -Eo '[0-9]+') 67 | if [ "$expected_timeout" -le 0 ]; then 68 | not_ok "invalid timeout $expected_timeout" 69 | continue 70 | fi 71 | ;; 72 | spawn_*) 73 | spawn="" 74 | ;; 75 | esac 76 | 77 | start=$(date +"%s") 78 | if [ -x "$testf" ]; then 79 | env PATH="$orchdir":"$PATH" "$testf" 80 | else 81 | "$orchbin" -f "$testf" -- $spawn 82 | fi 83 | rc="$?" 84 | end=$(date +"%s") 85 | 86 | if [ "$rc" -ne "$expected_rc" ]; then 87 | not_ok "expected $expected_rc, exited with $rc" 88 | continue 89 | fi 90 | 91 | case "$f" in 92 | timeout_*) 93 | ;; 94 | *) 95 | ok 96 | continue 97 | ;; 98 | esac 99 | 100 | elapsed=$((end - start)) 101 | if [ "$elapsed" -lt "$expected_timeout" ]; then 102 | not_ok "expected $expected_timeout seconds, finished in $elapsed" 103 | continue 104 | fi 105 | 106 | # Also make sure it wasn't excessively long... this could be flakey. 107 | excessive_timeout=$((expected_timeout + 3)) 108 | if [ "$elapsed" -ge "$excessive_timeout" ]; then 109 | not_ok "expected $expected_timeout seconds, finished excessively in $elapsed" 110 | continue 111 | fi 112 | 113 | ok 114 | done 115 | 116 | exit "$fails" 117 | -------------------------------------------------------------------------------- /lib/freebsd/sys/read/lua_read.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | static int 19 | l_read(lua_State *L) 20 | { 21 | char *buf; 22 | ssize_t n; 23 | int *fdp; 24 | 25 | fdp = luaL_checkudata(L, 1, FREEBSD_SYS_FD_REGISTRY_KEY); 26 | n = luaL_checkinteger(L, 2); 27 | buf = malloc(n); 28 | if (buf == NULL) { 29 | lua_pushnil(L); 30 | lua_pushstring(L, strerror(errno)); 31 | lua_pushinteger(L, errno); 32 | return (3); 33 | } 34 | 35 | n = read(*fdp, buf, n); 36 | if (n == -1) { 37 | lua_pushnil(L); 38 | lua_pushstring(L, strerror(errno)); 39 | lua_pushinteger(L, errno); 40 | free(buf); 41 | return (3); 42 | } 43 | 44 | lua_pushlstring(L, buf, n); 45 | free(buf); 46 | return (1); 47 | } 48 | 49 | static int 50 | l_pread(lua_State *L) 51 | { 52 | char *buf; 53 | off_t off; 54 | ssize_t n; 55 | int *fdp; 56 | 57 | fdp = luaL_checkudata(L, 1, FREEBSD_SYS_FD_REGISTRY_KEY); 58 | n = luaL_checkinteger(L, 2); 59 | off = luaL_checkinteger(L, 3); 60 | buf = malloc(n); 61 | if (buf == NULL) { 62 | lua_pushnil(L); 63 | lua_pushstring(L, strerror(errno)); 64 | lua_pushinteger(L, errno); 65 | return (3); 66 | } 67 | 68 | n = pread(*fdp, buf, n, off); 69 | if (n == -1) { 70 | lua_pushnil(L); 71 | lua_pushstring(L, strerror(errno)); 72 | lua_pushinteger(L, errno); 73 | free(buf); 74 | return (3); 75 | } 76 | 77 | lua_pushlstring(L, buf, n); 78 | free(buf); 79 | return (1); 80 | } 81 | 82 | static int 83 | l_write(lua_State *L) 84 | { 85 | const char *buf; 86 | ssize_t n; 87 | int *fdp; 88 | 89 | fdp = luaL_checkudata(L, 1, FREEBSD_SYS_FD_REGISTRY_KEY); 90 | buf = luaL_checklstring(L, 2, &n); 91 | 92 | n = write(*fdp, buf, n); 93 | if (n == -1) { 94 | lua_pushnil(L); 95 | lua_pushstring(L, strerror(errno)); 96 | lua_pushinteger(L, errno); 97 | return (3); 98 | } 99 | 100 | lua_pushinteger(L, n); 101 | return (1); 102 | } 103 | 104 | static const struct luaL_Reg l_readtab[] = { 105 | { "read", l_read }, 106 | { "pread", l_pread }, 107 | { "write", l_write }, 108 | { NULL, NULL }, 109 | }; 110 | 111 | int luaopen_read(lua_State *L); 112 | 113 | int 114 | luaopen_read(lua_State *L) 115 | { 116 | lua_newtable(L); 117 | luaL_setfuncs(L, l_readtab, 0); 118 | return (1); 119 | } 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a utility for running FreeBSD src development workflows. It is still 2 | in its infancy and its interfaces will change. 3 | 4 | The basic idea is to simplify common src development tasks by provding a light 5 | framework to wrap operations like: 6 | - building a FreeBSD src tree 7 | - constructing a VM image from the output of a build 8 | - booting the VM image (using QEMU or bhyve) 9 | - running things in the guest once it has booted. 10 | 11 | For example, to run the FreeBSD regression test suite against an existing clone 12 | of the FreeBSD src tree, run: 13 | 14 | ``` 15 | $ bricoler run freebsd-src-regression-suite -p freebsd-src:url=/path/to/clone 16 | ``` 17 | 18 | Add "--show" to list the available parameters. Run "bricoler run" without any 19 | other arguments to list available tasks. 20 | 21 | ## Compatibility 22 | 23 | This project only runs on FreeBSD. It is written in Lua as part of an experiment 24 | to try and build up a set of libraries exposing low-level interfaces in FreeBSD, 25 | starting with system calls and eventually up to higher-level components like 26 | pf, jails, bhyve, ZFS, etc.. This makes cross-platform support difficult. If 27 | it becomes tempting to run bricoler on other platforms, a rewrite in Python or 28 | similar would likely be necessary. 29 | 30 | ## Building 31 | 32 | It requires Lua 5.4 and cmake. 33 | 34 | To build it, run "make" from the root directory. 35 | 36 | ## FreeBSD regression suite quickstart 37 | 38 | On FreeBSD main, run the following: 39 | 40 | ``` 41 | $ git clone https://github.com/markjdb/bricoler 42 | $ cd bricoler && make 43 | ``` 44 | 45 | If you run bash, try sourcing `bricoler-completion.bash`. 46 | It auto-completes parameter names; I find it very very useful. 47 | 48 | Then cd to the src dir you want to build and run the command below. 49 | It assumes you are testing `main`, and that QEMU is installed. 50 | 51 | ``` 52 | $ cd /usr/src 53 | $ bricoler run freebsd-src-regression-suite --param freebsd-src:url=$(pwd) --param freebsd-src:branch=main 54 | ``` 55 | 56 | Add `--show` to the end of that invocation to see all of the possible parameters. 57 | For example, you can add `--param freebsd-src-build:clean=true` to request a clean build. 58 | 59 | Specific tests can be chosen with `freebsd-src-regression-suite:tests`: 60 | 61 | 62 | ``` 63 | $ bricoler run freebsd-src-regression-suite --param freebsd-src-regression-suite:tests="sys/netpfil/pf sbin/pfctl" 64 | ``` 65 | 66 | Once the regression suite finishes, you can look at the results with: 67 | 68 | ``` 69 | $ bricoler run freebsd-src-regression-suite report 70 | ``` 71 | 72 | If the guest panics it is possible to attach kgdb and interactively debug with: 73 | 74 | ``` 75 | $ bricoler run freebsd-src-regression-suite gdb 76 | ``` 77 | -------------------------------------------------------------------------------- /lib/freebsd/sys/poll/lua_poll.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | static int 20 | l_poll(lua_State *L) 21 | { 22 | struct pollfd *fds; 23 | int nfds, res, timeout; 24 | lua_Integer ltimeout; 25 | 26 | luaL_checktype(L, 1, LUA_TTABLE); 27 | nfds = lua_rawlen(L, 1); 28 | 29 | fds = calloc(nfds, sizeof(*fds)); 30 | if (fds == NULL) { 31 | lua_pushnil(L); 32 | lua_pushstring(L, strerror(errno)); 33 | lua_pushinteger(L, errno); 34 | return (3); 35 | } 36 | for (int i = 0; i < nfds; i++) { 37 | lua_Integer events; 38 | int *fdp; 39 | 40 | lua_rawgeti(L, 1, i + 1); 41 | luaL_checktype(L, -1, LUA_TTABLE); 42 | lua_getfield(L, -1, "fd"); 43 | fdp = luaL_checkudata(L, -1, FREEBSD_SYS_FD_REGISTRY_KEY); 44 | lua_pop(L, 1); 45 | lua_getfield(L, -1, "events"); 46 | events = luaL_checkinteger(L, -1); 47 | lua_pop(L, 1); 48 | lua_pop(L, 1); 49 | 50 | if (events > SHRT_MAX || events < SHRT_MIN) { 51 | lua_pushnil(L); 52 | lua_pushstring(L, "events too large"); 53 | lua_pushinteger(L, EINVAL); 54 | free(fds); 55 | return (3); 56 | } 57 | fds[i].fd = *fdp; 58 | fds[i].events = (short)events; 59 | } 60 | 61 | ltimeout = luaL_optinteger(L, 2, -1); 62 | if (ltimeout > INT_MAX) { 63 | lua_pushnil(L); 64 | lua_pushstring(L, "timeout too large"); 65 | lua_pushinteger(L, EINVAL); 66 | return (3); 67 | } 68 | timeout = (int)ltimeout; 69 | 70 | res = poll(fds, nfds, timeout); 71 | if (res == -1) { 72 | lua_pushnil(L); 73 | lua_pushstring(L, strerror(errno)); 74 | lua_pushinteger(L, errno); 75 | free(fds); 76 | return (3); 77 | } 78 | 79 | for (int i = 0; i < nfds; i++) { 80 | lua_rawgeti(L, 1, i + 1); 81 | luaL_checktype(L, -1, LUA_TTABLE); 82 | lua_pushinteger(L, fds[i].revents); 83 | lua_setfield(L, -2, "revents"); 84 | } 85 | free(fds); 86 | lua_pushinteger(L, res); 87 | return (1); 88 | } 89 | 90 | static const struct luaL_Reg l_polltab[] = { 91 | { "poll", l_poll }, 92 | { NULL, NULL }, 93 | }; 94 | 95 | int luaopen_poll(lua_State *L); 96 | 97 | int 98 | luaopen_poll(lua_State *L) 99 | { 100 | lua_newtable(L); 101 | luaL_setfuncs(L, l_polltab, 0); 102 | #define ADDCONST(c) do { \ 103 | lua_pushinteger(L, c); \ 104 | lua_setfield(L, -2, #c); \ 105 | } while (0) 106 | ADDCONST(POLLIN); 107 | ADDCONST(POLLOUT); 108 | ADDCONST(POLLRDNORM); 109 | ADDCONST(POLLRDBAND); 110 | ADDCONST(POLLWRNORM); 111 | ADDCONST(POLLWRBAND); 112 | ADDCONST(POLLERR); 113 | ADDCONST(POLLHUP); 114 | ADDCONST(POLLRDHUP); 115 | ADDCONST(POLLNVAL); 116 | #undef ADDCONST 117 | return (1); 118 | } 119 | -------------------------------------------------------------------------------- /lib/freebsd/sys/stat/lua_stat.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | static int 17 | l_stat1(lua_State *L, int (*fn)(const char *, struct stat *)) 18 | { 19 | const char *path; 20 | struct stat sb; 21 | int error; 22 | 23 | path = luaL_checkstring(L, 1); 24 | 25 | error = fn(path, &sb); 26 | if (error == -1) { 27 | lua_pushnil(L); 28 | lua_pushstring(L, strerror(errno)); 29 | lua_pushinteger(L, errno); 30 | return (3); 31 | } 32 | 33 | lua_newtable(L); 34 | #define SETFIELD(name) do { \ 35 | lua_pushstring(L, #name); \ 36 | lua_pushinteger(L, sb.st_##name); \ 37 | lua_settable(L, -3); \ 38 | } while (0) 39 | SETFIELD(dev); 40 | SETFIELD(ino); 41 | SETFIELD(mode); 42 | SETFIELD(nlink); 43 | SETFIELD(uid); 44 | SETFIELD(gid); 45 | SETFIELD(rdev); 46 | SETFIELD(size); 47 | SETFIELD(blocks); 48 | SETFIELD(blksize); 49 | #undef SETFIELD 50 | return (1); 51 | } 52 | 53 | static int 54 | l_stat(lua_State *L) 55 | { 56 | return (l_stat1(L, stat)); 57 | } 58 | 59 | static int 60 | l_fstat(lua_State *L) 61 | { 62 | return (l_stat1(L, lstat)); 63 | } 64 | 65 | #define l_S_PRED(name) \ 66 | static int \ 67 | l_S_##name(lua_State *L) \ 68 | { \ 69 | lua_pushboolean(L, S_##name(luaL_checkinteger(L, 1))); \ 70 | return (1); \ 71 | } 72 | l_S_PRED(ISBLK) 73 | l_S_PRED(ISCHR) 74 | l_S_PRED(ISDIR) 75 | l_S_PRED(ISFIFO) 76 | l_S_PRED(ISREG) 77 | l_S_PRED(ISLNK) 78 | l_S_PRED(ISSOCK) 79 | #undef l_S_PRED 80 | 81 | static const struct luaL_Reg l_stattab[] = { 82 | { "stat", l_stat }, 83 | { "lstat", l_fstat }, 84 | 85 | { "S_ISBLK", l_S_ISBLK }, 86 | { "S_ISCHR", l_S_ISCHR }, 87 | { "S_ISDIR", l_S_ISDIR }, 88 | { "S_ISFIFO", l_S_ISFIFO }, 89 | { "S_ISREG", l_S_ISREG }, 90 | { "S_ISLNK", l_S_ISLNK }, 91 | { "S_ISSOCK", l_S_ISSOCK }, 92 | { NULL, NULL }, 93 | }; 94 | 95 | int luaopen_stat(lua_State *L); 96 | 97 | int 98 | luaopen_stat(lua_State *L) 99 | { 100 | lua_newtable(L); 101 | luaL_setfuncs(L, l_stattab, 0); 102 | #define ADDFLAG(c) do { \ 103 | lua_pushinteger(L, c); \ 104 | lua_setfield(L, -2, #c); \ 105 | } while (0) 106 | ADDFLAG(S_IFMT); 107 | ADDFLAG(S_IFIFO); 108 | ADDFLAG(S_IFCHR); 109 | ADDFLAG(S_IFDIR); 110 | ADDFLAG(S_IFBLK); 111 | ADDFLAG(S_IFREG); 112 | ADDFLAG(S_IFLNK); 113 | ADDFLAG(S_IFSOCK); 114 | ADDFLAG(S_IFWHT); 115 | ADDFLAG(S_ISUID); 116 | ADDFLAG(S_ISGID); 117 | ADDFLAG(S_ISVTX); 118 | ADDFLAG(S_IRWXU); 119 | ADDFLAG(S_IRUSR); 120 | ADDFLAG(S_IWUSR); 121 | ADDFLAG(S_IXUSR); 122 | ADDFLAG(S_IRWXG); 123 | ADDFLAG(S_IRGRP); 124 | ADDFLAG(S_IWGRP); 125 | ADDFLAG(S_IXGRP); 126 | ADDFLAG(S_IRWXO); 127 | ADDFLAG(S_IROTH); 128 | ADDFLAG(S_IWOTH); 129 | ADDFLAG(S_IXOTH); 130 | #undef ADDFLAG 131 | return (1); 132 | } 133 | -------------------------------------------------------------------------------- /contrib/orch/lib/orch_lib.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2024 Kyle Evans 3 | * 4 | * SPDX-License-Identifier: BSD-2-Clause 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | /* We only support Lua 5.3+ */ 18 | 19 | /* Introduced in Lua 5.4 */ 20 | #ifndef luaL_pushfail 21 | #define luaL_pushfail(L) lua_pushnil(L) 22 | #endif 23 | 24 | struct orch_ipc_msg; 25 | typedef struct orch_ipc *orch_ipc_t; 26 | 27 | struct orch_term; 28 | 29 | enum orch_ipc_tag { 30 | IPC_NOXMIT = 0, 31 | IPC_RELEASE, /* Bidrectional */ 32 | IPC_ERROR, /* Child -> Parent */ 33 | IPC_TERMIOS_INQUIRY, /* Parent -> Child */ 34 | IPC_TERMIOS_SET, /* Bidirectional */ 35 | IPC_TERMIOS_ACK, /* Child -> Parent */ 36 | IPC_LAST, 37 | }; 38 | 39 | struct orch_process { 40 | lua_State *L; 41 | struct orch_term *term; 42 | orch_ipc_t ipc; 43 | int cmdsock; 44 | pid_t pid; 45 | int status; 46 | int termctl; 47 | bool raw; 48 | bool released; 49 | bool eof; 50 | bool buffered; 51 | bool error; 52 | }; 53 | 54 | struct orch_term { 55 | struct termios term; 56 | struct orch_process *proc; 57 | bool initialized; 58 | }; 59 | 60 | struct orchlua_tty_cntrl { 61 | int cntrl_idx; 62 | const char *cntrl_name; 63 | int cntrl_flags; 64 | }; 65 | 66 | struct orchlua_tty_mode { 67 | int mode_mask; 68 | const char *mode_name; 69 | }; 70 | 71 | #define CNTRL_CANON 0x01 72 | #define CNTRL_NCANON 0x02 73 | #define CNTRL_BOTH 0x03 74 | #define CNTRL_LITERAL 0x04 75 | 76 | /* orch_ipc.c */ 77 | typedef int (orch_ipc_handler)(orch_ipc_t, struct orch_ipc_msg *, void *); 78 | int orch_ipc_close(orch_ipc_t); 79 | orch_ipc_t orch_ipc_open(int); 80 | bool orch_ipc_okay(orch_ipc_t); 81 | int orch_ipc_recv(orch_ipc_t, struct orch_ipc_msg **); 82 | struct orch_ipc_msg *orch_ipc_msg_alloc(enum orch_ipc_tag, size_t, void **); 83 | void *orch_ipc_msg_payload(struct orch_ipc_msg *, size_t *); 84 | enum orch_ipc_tag orch_ipc_msg_tag(struct orch_ipc_msg *); 85 | void orch_ipc_msg_free(struct orch_ipc_msg *); 86 | int orch_ipc_register(orch_ipc_t, enum orch_ipc_tag, orch_ipc_handler *, void *); 87 | int orch_ipc_send(orch_ipc_t, struct orch_ipc_msg *); 88 | int orch_ipc_send_nodata(orch_ipc_t, enum orch_ipc_tag); 89 | int orch_ipc_wait(orch_ipc_t, bool *); 90 | 91 | /* orch_spawn.c */ 92 | int orch_release(orch_ipc_t); 93 | int orch_spawn(int, const char *[], struct orch_process *, orch_ipc_handler *); 94 | 95 | /* orch_tty.c */ 96 | int orchlua_setup_tty(lua_State *); 97 | int orchlua_tty_alloc(lua_State *, const struct orch_term *, 98 | struct orch_term **); 99 | 100 | extern const struct orchlua_tty_cntrl orchlua_cntrl_chars[]; 101 | extern const struct orchlua_tty_mode orchlua_input_modes[]; 102 | extern const struct orchlua_tty_mode orchlua_output_modes[]; 103 | extern const struct orchlua_tty_mode orchlua_cntrl_modes[]; 104 | extern const struct orchlua_tty_mode orchlua_local_modes[]; 105 | -------------------------------------------------------------------------------- /lib/freebsd/sysconf/lua_sysconf.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | static int 16 | l_sysconf(lua_State *L) 17 | { 18 | lua_Integer lname; 19 | long value; 20 | 21 | lname = luaL_checkinteger(L, 1); 22 | if (lname < INT_MIN || lname > INT_MAX) { 23 | lua_pushnil(L); 24 | lua_pushstring(L, "argument out of range"); 25 | return (2); 26 | } 27 | 28 | value = sysconf((int)lname); 29 | if (value == -1) { 30 | lua_pushnil(L); 31 | lua_pushstring(L, strerror(errno)); 32 | lua_pushinteger(L, errno); 33 | return (3); 34 | } 35 | 36 | lua_pushinteger(L, value); 37 | return (1); 38 | } 39 | 40 | static const struct luaL_Reg l_sysconftab[] = { 41 | { "sysconf", l_sysconf }, 42 | { NULL, NULL }, 43 | }; 44 | 45 | int luaopen_sysconf(lua_State *L); 46 | 47 | int 48 | luaopen_sysconf(lua_State *L) 49 | { 50 | lua_newtable(L); 51 | luaL_setfuncs(L, l_sysconftab, 0); 52 | #define ADDVALUE(v) do { \ 53 | lua_pushinteger(L, v); \ 54 | lua_setfield(L, -2, #v); \ 55 | } while (0) 56 | ADDVALUE(_SC_ARG_MAX); 57 | ADDVALUE(_SC_CHILD_MAX); 58 | ADDVALUE(_SC_CLK_TCK); 59 | ADDVALUE(_SC_IOV_MAX); 60 | ADDVALUE(_SC_NGROUPS_MAX); 61 | ADDVALUE(_SC_NPROCESSORS_CONF); 62 | ADDVALUE(_SC_NPROCESSORS_ONLN); 63 | ADDVALUE(_SC_OPEN_MAX); 64 | ADDVALUE(_SC_PAGESIZE); 65 | ADDVALUE(_SC_PAGE_SIZE); 66 | ADDVALUE(_SC_STREAM_MAX); 67 | ADDVALUE(_SC_TZNAME_MAX); 68 | ADDVALUE(_SC_JOB_CONTROL); 69 | ADDVALUE(_SC_SAVED_IDS); 70 | ADDVALUE(_SC_VERSION); 71 | ADDVALUE(_SC_BC_BASE_MAX); 72 | ADDVALUE(_SC_BC_DIM_MAX); 73 | ADDVALUE(_SC_BC_SCALE_MAX); 74 | ADDVALUE(_SC_BC_STRING_MAX); 75 | ADDVALUE(_SC_COLL_WEIGHTS_MAX); 76 | ADDVALUE(_SC_EXPR_NEST_MAX); 77 | ADDVALUE(_SC_LINE_MAX); 78 | ADDVALUE(_SC_RE_DUP_MAX); 79 | ADDVALUE(_SC_2_VERSION); 80 | ADDVALUE(_SC_2_C_BIND); 81 | ADDVALUE(_SC_2_C_DEV); 82 | ADDVALUE(_SC_2_CHAR_TERM); 83 | ADDVALUE(_SC_2_FORT_DEV); 84 | ADDVALUE(_SC_2_FORT_RUN); 85 | ADDVALUE(_SC_2_LOCALEDEF); 86 | ADDVALUE(_SC_2_SW_DEV); 87 | ADDVALUE(_SC_2_UPE); 88 | ADDVALUE(_SC_AIO_LISTIO_MAX); 89 | ADDVALUE(_SC_AIO_MAX); 90 | ADDVALUE(_SC_AIO_PRIO_DELTA_MAX); 91 | ADDVALUE(_SC_DELAYTIMER_MAX); 92 | ADDVALUE(_SC_MQ_OPEN_MAX); 93 | ADDVALUE(_SC_RTSIG_MAX); 94 | ADDVALUE(_SC_SEM_NSEMS_MAX); 95 | ADDVALUE(_SC_SEM_VALUE_MAX); 96 | ADDVALUE(_SC_SIGQUEUE_MAX); 97 | ADDVALUE(_SC_TIMER_MAX); 98 | ADDVALUE(_SC_GETGR_R_SIZE_MAX); 99 | ADDVALUE(_SC_GETPW_R_SIZE_MAX); 100 | ADDVALUE(_SC_HOST_NAME_MAX); 101 | ADDVALUE(_SC_LOGIN_NAME_MAX); 102 | ADDVALUE(_SC_THREAD_STACK_MIN); 103 | ADDVALUE(_SC_THREAD_THREADS_MAX); 104 | ADDVALUE(_SC_TTY_NAME_MAX); 105 | ADDVALUE(_SC_SYMLOOP_MAX); 106 | ADDVALUE(_SC_ATEXIT_MAX); 107 | ADDVALUE(_SC_XOPEN_VERSION); 108 | ADDVALUE(_SC_XOPEN_XCU_VERSION); 109 | ADDVALUE(_SC_CPUSET_SIZE); 110 | ADDVALUE(_SC_PHYS_PAGES); 111 | #undef ADDVALUE 112 | return (1); 113 | } 114 | -------------------------------------------------------------------------------- /lib/freebsd/errno/lua_errno.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | int luaopen_errno(lua_State *L); 14 | 15 | int 16 | luaopen_errno(lua_State *L) 17 | { 18 | lua_newtable(L); 19 | 20 | #define ADDERRNO(name) \ 21 | lua_pushinteger(L, name); \ 22 | lua_setfield(L, -2, #name) 23 | ADDERRNO(EPERM); 24 | ADDERRNO(ENOENT); 25 | ADDERRNO(ESRCH); 26 | ADDERRNO(EINTR); 27 | ADDERRNO(EIO); 28 | ADDERRNO(ENXIO); 29 | ADDERRNO(E2BIG); 30 | ADDERRNO(ENOEXEC); 31 | ADDERRNO(EBADF); 32 | ADDERRNO(ECHILD); 33 | ADDERRNO(EDEADLK); 34 | ADDERRNO(ENOMEM); 35 | ADDERRNO(EACCES); 36 | ADDERRNO(EFAULT); 37 | ADDERRNO(ENOTBLK); 38 | ADDERRNO(EBUSY); 39 | ADDERRNO(EEXIST); 40 | ADDERRNO(EXDEV); 41 | ADDERRNO(ENODEV); 42 | ADDERRNO(ENOTDIR); 43 | ADDERRNO(EISDIR); 44 | ADDERRNO(EINVAL); 45 | ADDERRNO(ENFILE); 46 | ADDERRNO(EMFILE); 47 | ADDERRNO(ENOTTY); 48 | ADDERRNO(ETXTBSY); 49 | ADDERRNO(EFBIG); 50 | ADDERRNO(ENOSPC); 51 | ADDERRNO(ESPIPE); 52 | ADDERRNO(EROFS); 53 | ADDERRNO(EMLINK); 54 | ADDERRNO(EPIPE); 55 | ADDERRNO(EDOM); 56 | ADDERRNO(ERANGE); 57 | ADDERRNO(EAGAIN); 58 | ADDERRNO(EWOULDBLOCK); 59 | ADDERRNO(EINPROGRESS); 60 | ADDERRNO(EALREADY); 61 | ADDERRNO(ENOTSOCK); 62 | ADDERRNO(EDESTADDRREQ); 63 | ADDERRNO(EMSGSIZE); 64 | ADDERRNO(EPROTOTYPE); 65 | ADDERRNO(ENOPROTOOPT); 66 | ADDERRNO(EPROTONOSUPPORT); 67 | ADDERRNO(ESOCKTNOSUPPORT); 68 | ADDERRNO(EOPNOTSUPP); 69 | ADDERRNO(ENOTSUP); 70 | ADDERRNO(EPFNOSUPPORT); 71 | ADDERRNO(EAFNOSUPPORT); 72 | ADDERRNO(EADDRINUSE); 73 | ADDERRNO(EADDRNOTAVAIL); 74 | ADDERRNO(ENETDOWN); 75 | ADDERRNO(ENETUNREACH); 76 | ADDERRNO(ENETRESET); 77 | ADDERRNO(ECONNABORTED); 78 | ADDERRNO(ECONNRESET); 79 | ADDERRNO(ENOBUFS); 80 | ADDERRNO(EISCONN); 81 | ADDERRNO(ENOTCONN); 82 | ADDERRNO(ESHUTDOWN); 83 | ADDERRNO(ETOOMANYREFS); 84 | ADDERRNO(ETIMEDOUT); 85 | ADDERRNO(ECONNREFUSED); 86 | ADDERRNO(ELOOP); 87 | ADDERRNO(ENAMETOOLONG); 88 | ADDERRNO(EHOSTDOWN); 89 | ADDERRNO(EHOSTUNREACH); 90 | ADDERRNO(ENOTEMPTY); 91 | ADDERRNO(EPROCLIM); 92 | ADDERRNO(EUSERS); 93 | ADDERRNO(EDQUOT); 94 | ADDERRNO(ESTALE); 95 | ADDERRNO(EREMOTE); 96 | ADDERRNO(EBADRPC); 97 | ADDERRNO(ERPCMISMATCH); 98 | ADDERRNO(EPROGUNAVAIL); 99 | ADDERRNO(EPROGMISMATCH); 100 | ADDERRNO(EPROCUNAVAIL); 101 | ADDERRNO(ENOLCK); 102 | ADDERRNO(ENOSYS); 103 | ADDERRNO(EFTYPE); 104 | ADDERRNO(EAUTH); 105 | ADDERRNO(ENEEDAUTH); 106 | ADDERRNO(EIDRM); 107 | ADDERRNO(ENOMSG); 108 | ADDERRNO(EOVERFLOW); 109 | ADDERRNO(ECANCELED); 110 | ADDERRNO(EILSEQ); 111 | ADDERRNO(ENOATTR); 112 | ADDERRNO(EDOOFUS); 113 | ADDERRNO(EBADMSG); 114 | ADDERRNO(EMULTIHOP); 115 | ADDERRNO(ENOLINK); 116 | ADDERRNO(EPROTO); 117 | ADDERRNO(ENOTCAPABLE); 118 | ADDERRNO(ECAPMODE); 119 | ADDERRNO(ENOTRECOVERABLE); 120 | ADDERRNO(EOWNERDEAD); 121 | ADDERRNO(EINTEGRITY); 122 | ADDERRNO(ELAST); 123 | #undef ADDERRNO 124 | 125 | return (1); 126 | } 127 | -------------------------------------------------------------------------------- /contrib/orch/src/orch_interp.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2024 Kyle Evans 3 | * 4 | * SPDX-License-Identifier: BSD-2-Clause 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "orch.h" 15 | #include "orch_bin.h" 16 | 17 | #include 18 | #include 19 | 20 | static const char orchlua_path[] = ORCHLUA_PATH; 21 | 22 | static const char * 23 | orch_interp_script(const char *orch_invoke_path) 24 | { 25 | static char buf[MAXPATHLEN]; 26 | 27 | /* Populate buf first... we cache the script path */ 28 | if (buf[0] == '\0') { 29 | const char *path; 30 | 31 | path = getenv("ORCHLUA_PATH"); 32 | if (path != NULL && (path[0] == '\0' || path[0] != '/')) { 33 | fprintf(stderr, 34 | "Ignoring empty or relative ORCHLUA_PATH in the environment ('%s')\n", 35 | path); 36 | path = NULL; 37 | } 38 | 39 | /* Fallback to what's built-in, if no env override. */ 40 | if (path == NULL) 41 | path = orchlua_path; 42 | 43 | /* If ORCHLUA_PATH is empty, it's in the same path as our binary. */ 44 | if (path[0] == '\0') { 45 | char *slash; 46 | 47 | if (realpath(orch_invoke_path, buf) == NULL) 48 | err(1, "realpath %s", orch_invoke_path); 49 | 50 | /* buf now a path to our binary, strip it. */ 51 | slash = strrchr(buf, '/'); 52 | if (slash == NULL) 53 | errx(1, "failed to resolve orch binary path"); 54 | 55 | slash++; 56 | assert(*slash != '\0'); 57 | *slash = '\0'; 58 | } else { 59 | strlcpy(buf, path, sizeof(buf)); 60 | } 61 | 62 | strlcat(buf, "/orch.lua", sizeof(buf)); 63 | } 64 | 65 | return (&buf[0]); 66 | } 67 | 68 | static int 69 | orch_interp_error(lua_State *L) 70 | { 71 | const char *err; 72 | 73 | err = lua_tostring(L, -1); 74 | if (err == NULL) 75 | err = "unknown"; 76 | 77 | fprintf(stderr, "%s\n", err); 78 | return (1); 79 | } 80 | 81 | int 82 | orch_interp(const char *scriptf, const char *orch_invoke_path, 83 | int argc, const char * const argv[]) 84 | { 85 | lua_State *L; 86 | int status; 87 | 88 | L = luaL_newstate(); 89 | if (L == NULL) 90 | errx(1, "luaL_newstate: out of memory"); 91 | 92 | /* Open lua's standard library */ 93 | luaL_openlibs(L); 94 | 95 | /* As well as our internal library */ 96 | luaL_requiref(L, ORCHLUA_MODNAME, luaopen_orch_core, 0); 97 | lua_pop(L, 1); 98 | 99 | if (luaL_dofile(L, orch_interp_script(orch_invoke_path)) != LUA_OK) { 100 | status = orch_interp_error(L); 101 | } else { 102 | /* 103 | * orch table is now at the top of stack, fetch run_script() 104 | * and call it. run_script(scriptf[, config]) 105 | */ 106 | lua_getfield(L, -1, "run_script"); 107 | lua_pushstring(L, scriptf); 108 | 109 | /* config */ 110 | lua_createtable(L, 0, 1); 111 | 112 | /* config.alter_path */ 113 | lua_pushboolean(L, 1); 114 | lua_setfield(L, -2, "alter_path"); 115 | 116 | if (argc > 0) { 117 | /* config.command */ 118 | lua_createtable(L, argc, 0); 119 | for (int i = 0; i < argc; i++) { 120 | lua_pushstring(L, argv[i]); 121 | lua_rawseti(L, -2, i + 1); 122 | } 123 | 124 | lua_setfield(L, -2, "command"); 125 | } 126 | 127 | if (lua_pcall(L, 2, 1, 0) == LUA_OK) 128 | status = lua_toboolean(L, -1) ? 0 : 1; 129 | else 130 | status = orch_interp_error(L); 131 | } 132 | 133 | lua_close(L); 134 | return (status); 135 | } 136 | -------------------------------------------------------------------------------- /contrib/orch/lib/core/orch_compat.c: -------------------------------------------------------------------------------- 1 | /* See inline for copyright notices. */ 2 | #include 3 | #include 4 | 5 | #ifdef __linux__ 6 | #include 7 | #endif 8 | 9 | #include 10 | 11 | #include "orch.h" 12 | 13 | #ifdef __linux__ 14 | /* $OpenBSD: strlcpy.c,v 1.12 2015/01/15 03:54:12 millert Exp $ */ 15 | 16 | /* 17 | * Copyright (c) 1998, 2015 Todd C. Miller 18 | * 19 | * Permission to use, copy, modify, and distribute this software for any 20 | * purpose with or without fee is hereby granted, provided that the above 21 | * copyright notice and this permission notice appear in all copies. 22 | * 23 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 24 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 25 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 26 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 27 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 28 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 29 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 30 | */ 31 | 32 | /* 33 | * Copy string src to buffer dst of size dsize. At most dsize-1 34 | * chars will be copied. Always NUL terminates (unless dsize == 0). 35 | * Returns strlen(src); if retval >= dsize, truncation occurred. 36 | */ 37 | size_t 38 | strlcpy(char * __restrict dst, const char * __restrict src, size_t dsize) 39 | { 40 | const char *osrc = src; 41 | size_t nleft = dsize; 42 | 43 | /* Copy as many bytes as will fit. */ 44 | if (nleft != 0) { 45 | while (--nleft != 0) { 46 | if ((*dst++ = *src++) == '\0') 47 | break; 48 | } 49 | } 50 | 51 | /* Not enough room in dst, add NUL and traverse rest of src. */ 52 | if (nleft == 0) { 53 | if (dsize != 0) 54 | *dst = '\0'; /* NUL-terminate dst */ 55 | while (*src++) 56 | ; 57 | } 58 | 59 | return(src - osrc - 1); /* count does not include NUL */ 60 | } 61 | 62 | /* $OpenBSD: strlcat.c,v 1.15 2015/03/02 21:41:08 millert Exp $ */ 63 | /* Identical copyright */ 64 | 65 | /* 66 | * Appends src to string dst of size dsize (unlike strncat, dsize is the 67 | * full size of dst, not space left). At most dsize-1 characters 68 | * will be copied. Always NUL terminates (unless dsize <= strlen(dst)). 69 | * Returns strlen(src) + MIN(dsize, strlen(initial dst)). 70 | * If retval >= dsize, truncation occurred. 71 | */ 72 | size_t 73 | strlcat(char * __restrict dst, const char * __restrict src, size_t dsize) 74 | { 75 | const char *odst = dst; 76 | const char *osrc = src; 77 | size_t n = dsize; 78 | size_t dlen; 79 | 80 | /* Find the end of dst and adjust bytes left but don't go past end. */ 81 | while (n-- != 0 && *dst != '\0') 82 | dst++; 83 | dlen = dst - odst; 84 | n = dsize - dlen; 85 | 86 | if (n-- == 0) 87 | return(dlen + strlen(src)); 88 | while (*src != '\0') { 89 | if (n != 0) { 90 | *dst++ = *src; 91 | n--; 92 | } 93 | src++; 94 | } 95 | *dst = '\0'; 96 | 97 | return(dlen + (src - osrc)); /* count does not include NUL */ 98 | } 99 | #endif /* __linux__ */ 100 | #if defined(__linux__) || defined(__APPLE__) || defined(__OpenBSD__) || \ 101 | defined(__NetBSD__) 102 | /* Not associated with the above... incredibly simple. */ 103 | 104 | int 105 | tcsetsid(int tty, int sess __unused) 106 | { 107 | 108 | return (ioctl(tty, TIOCSCTTY, NULL)); 109 | } 110 | #endif 111 | -------------------------------------------------------------------------------- /lib/freebsd/getaddrinfo/lua_getaddrinfo.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | static int 19 | get_int_field(lua_State *L, const char *name, int *valp) 20 | { 21 | lua_Integer lval; 22 | 23 | lua_getfield(L, 3, name); 24 | lval = luaL_optinteger(L, -1, 0); 25 | if (lval < INT_MIN || lval > INT_MAX) { 26 | lua_pushnil(L); 27 | lua_pushfstring(L, "%s too large", name); 28 | lua_pushinteger(L, EINVAL); 29 | return (3); 30 | } 31 | *valp = lval; 32 | return (0); 33 | } 34 | 35 | static int 36 | l_getaddrinfo(lua_State *L) 37 | { 38 | const char *hostname, *servname; 39 | struct addrinfo *ai, hints, *res; 40 | int error, i; 41 | 42 | hostname = luaL_checkstring(L, 1); 43 | if (strcmp(hostname, "") == 0) 44 | hostname = NULL; 45 | servname = luaL_checkstring(L, 2); 46 | if (strcmp(servname, "") == 0) 47 | servname = NULL; 48 | 49 | /* Let the caller optionally pass a table of hints. */ 50 | memset(&hints, 0, sizeof(hints)); 51 | if (!lua_isnoneornil(L, 3)) { 52 | luaL_checktype(L, 3, LUA_TTABLE); 53 | error = get_int_field(L, "flags", &hints.ai_flags); 54 | if (error != 0) 55 | return (error); 56 | error = get_int_field(L, "family", &hints.ai_family); 57 | if (error != 0) 58 | return (error); 59 | error = get_int_field(L, "socktype", &hints.ai_socktype); 60 | if (error != 0) 61 | return (error); 62 | error = get_int_field(L, "protocol", &hints.ai_protocol); 63 | if (error != 0) 64 | return (error); 65 | } 66 | 67 | error = getaddrinfo(hostname, servname, &hints, &res); 68 | if (error != 0) { 69 | lua_pushnil(L); 70 | lua_pushstring(L, gai_strerror(error)); 71 | lua_pushinteger(L, error); 72 | return (3); 73 | } 74 | 75 | /* Convert the linked list of results into an array of tables. */ 76 | lua_newtable(L); 77 | for (ai = res, i = 1; ai != NULL; ai = ai->ai_next, i++) { 78 | lua_pushnumber(L, i); 79 | lua_newtable(L); 80 | lua_pushstring(L, "flags"); 81 | lua_pushinteger(L, ai->ai_flags); 82 | lua_settable(L, -3); 83 | lua_pushstring(L, "family"); 84 | lua_pushinteger(L, ai->ai_family); 85 | lua_settable(L, -3); 86 | lua_pushstring(L, "socktype"); 87 | lua_pushinteger(L, ai->ai_socktype); 88 | lua_settable(L, -3); 89 | lua_pushstring(L, "protocol"); 90 | lua_pushinteger(L, ai->ai_protocol); 91 | lua_settable(L, -3); 92 | lua_pushstring(L, "addrlen"); 93 | lua_pushinteger(L, ai->ai_addrlen); 94 | lua_settable(L, -3); 95 | lua_pushstring(L, "addr"); 96 | lua_pushlstring(L, (const char *)ai->ai_addr, ai->ai_addrlen); 97 | lua_settable(L, -3); 98 | if (ai->ai_canonname != NULL) { 99 | lua_pushstring(L, "canonname"); 100 | lua_pushstring(L, ai->ai_canonname); 101 | lua_settable(L, -3); 102 | } 103 | 104 | lua_settable(L, -3); 105 | } 106 | freeaddrinfo(res); 107 | return (1); 108 | } 109 | 110 | static const luaL_Reg l_getaddrinfotab[] = { 111 | { "getaddrinfo", l_getaddrinfo }, 112 | { NULL, NULL } 113 | }; 114 | 115 | int luaopen_getaddrinfo(lua_State *L); 116 | 117 | int 118 | luaopen_getaddrinfo(lua_State *L) 119 | { 120 | lua_newtable(L); 121 | luaL_setfuncs(L, l_getaddrinfotab, 0); 122 | #define ADDCONST(c) do { \ 123 | lua_pushinteger(L, c); \ 124 | lua_setfield(L, -2, #c); \ 125 | } while (0) 126 | ADDCONST(AI_ADDRCONFIG); 127 | ADDCONST(AI_ALL); 128 | ADDCONST(AI_CANONNAME); 129 | ADDCONST(AI_NUMERICHOST); 130 | ADDCONST(AI_NUMERICSERV); 131 | ADDCONST(AI_PASSIVE); 132 | ADDCONST(AI_V4MAPPED); 133 | #undef ADDCONST 134 | return (1); 135 | } 136 | -------------------------------------------------------------------------------- /class.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (c) Mark Johnston 2 | -- 3 | -- SPDX-License-Identifier: BSD-2-Clause 4 | 5 | -- Hand-rolled classes. Define one with something like this: 6 | -- 7 | -- local Foo = Class({}, {}) 8 | -- 9 | -- function Foo:_ctor(props) 10 | -- -- "self" has properties assigned already. 11 | -- ... 12 | -- return self 13 | -- end 14 | -- 15 | -- function Foo:method() 16 | -- ... 17 | -- end 18 | -- 19 | -- Where is a table of property names and their types, e.g., 20 | -- 21 | -- { 22 | -- foo = "string", 23 | -- bar = "function", 24 | -- baz = "*", -- Any value is OK. 25 | -- } 26 | -- 27 | -- Then create an instance with: 28 | -- 29 | -- local foo = Foo{} 30 | -- 31 | -- "foo" will be instantiated with a copy of the prototype and any 32 | -- caller-supplied properties. Foo's _ctor function, if defined, 33 | -- is invoked after the properties are copied into the new object. 34 | -- 35 | -- All properties and prototype fields are public. Prefix them with an 36 | -- underscore to indicate that they're private. 37 | 38 | local function class(proto, props) 39 | local builtins = { "string", "number", "boolean", "table", "function", "*" } 40 | 41 | local function deepcopy(from, to) 42 | for k, v in pairs(from) do 43 | if type(v) == "table" then 44 | v = deepcopy(v, {}) 45 | end 46 | to[k] = v 47 | end 48 | return to 49 | end 50 | 51 | -- Does the value "v" belong to the type "t"? 52 | local function valid(v, t) 53 | for _, candidate in ipairs(builtins) do 54 | if t == candidate then 55 | if t == "*" then 56 | return true 57 | else 58 | return type(v) == t 59 | end 60 | end 61 | end 62 | return false 63 | end 64 | 65 | -- A class is its own metatable. This __index metamethod ensures that 66 | -- any defined property can be accessed even if it hasn't been set. 67 | local c = {} 68 | c.__index = function (t, key) 69 | local mt = getmetatable(t) 70 | if rawget(mt, key) then 71 | return mt[key] 72 | elseif mt._props[key] then 73 | return nil 74 | end 75 | error("Unknown class property '" .. key .. "'") 76 | end 77 | 78 | for k, v in pairs(props) do 79 | if type(v) == "string" then 80 | for _, candidate in ipairs(builtins) do 81 | if v == candidate then 82 | break 83 | end 84 | if _ == #builtins then 85 | error("Property '" .. k .. "' must have a type in " .. table.concat(builtins, ", ")) 86 | end 87 | end 88 | elseif type(v) ~= "function" then 89 | error("Property '" .. k .. "' must be a string or a function") 90 | end 91 | end 92 | c._proto = proto 93 | c._props = props 94 | -- Let the consumer use the prototype fields as static class fields. 95 | for k, v in pairs(proto) do 96 | c[k] = v 97 | end 98 | 99 | -- Provide a default constructor so that classes can override it. 100 | c._ctor = function() 101 | end 102 | 103 | -- Instantiate a new object when the class is called. The prototype is 104 | -- copied into the new object and properties given to the constructor are 105 | -- checked and set. Finally the object-specific constructor, if any, is 106 | -- called. 107 | c.__call = function(self, ...) 108 | local object = deepcopy(self._proto, {}) 109 | if select("#", ...) ~= 1 then 110 | error("Constructors take a single table parameter") 111 | end 112 | local t = select(1, ...) 113 | if type(t) ~= "table" then 114 | error("Constructor parameter must be a table") 115 | end 116 | for k, v in pairs(t) do 117 | if self._props[k] then 118 | if type(self._props[k]) == "string" then 119 | if not valid(v, self._props[k]) then 120 | error("Property '" .. k .. "' must have type " .. self._props[k]) 121 | end 122 | elseif type(self._props[k]) == "function" then 123 | if not self._props[k](v) then 124 | error("Property '" .. k .. "' value '" .. v .. "' is invalid") 125 | end 126 | else 127 | error("Unknown property type '" .. type(self._props[k]) .. "'") 128 | end 129 | object[k] = v 130 | else 131 | error("Unknown class property '" .. k .. "'") 132 | end 133 | end 134 | object = setmetatable(object, self) 135 | object:_ctor(t) 136 | return object 137 | end 138 | return setmetatable(c, c) 139 | end 140 | 141 | return class 142 | -------------------------------------------------------------------------------- /lib/freebsd/sys/sysctl/lua_sysctl.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | /* 8 | * Wrappers for the sysctl(2) and sysctlbyname(2) system calls. Currently the 9 | * interfaces only support getting values. 10 | */ 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | static int 24 | fmtval(lua_State *L, int *oidp, size_t oidlen, char *buf, size_t buflen) 25 | { 26 | int fmtbuf[128]; 27 | int oid[CTL_MAXNAME + 2]; 28 | size_t fmtlen; 29 | unsigned int ctltype; 30 | int error; 31 | 32 | memcpy(oid + 2, oidp, oidlen * sizeof(int)); 33 | 34 | oid[0] = CTL_SYSCTL; 35 | oid[1] = CTL_SYSCTL_OIDFMT; 36 | fmtlen = sizeof(fmtbuf); 37 | error = sysctl(oid, oidlen + 2, fmtbuf, &fmtlen, NULL, 0); 38 | if (error != 0) 39 | return (luaL_error(L, "failed to resolve OIDFMT")); 40 | ctltype = *fmtbuf & CTLTYPE; 41 | 42 | switch (ctltype) { 43 | case CTLTYPE_STRING: 44 | assert(strnlen(buf, buflen) < buflen); 45 | lua_pushstring(L, buf); 46 | return (1); 47 | #define FMTVAL_INT(vt) do { \ 48 | lua_Integer lval; \ 49 | vt val; \ 50 | \ 51 | assert(buflen == sizeof(val)); \ 52 | memcpy(&val, buf, sizeof(val)); \ 53 | lval = val; \ 54 | lua_pushinteger(L, lval); \ 55 | return (1); \ 56 | } while (0) 57 | case CTLTYPE_U8: FMTVAL_INT(uint8_t); 58 | case CTLTYPE_S8: FMTVAL_INT(int8_t); 59 | case CTLTYPE_U16: FMTVAL_INT(uint16_t); 60 | case CTLTYPE_S16: FMTVAL_INT(int16_t); 61 | case CTLTYPE_U32: FMTVAL_INT(uint32_t); 62 | case CTLTYPE_S32: FMTVAL_INT(int32_t); 63 | case CTLTYPE_U64: FMTVAL_INT(uint64_t); 64 | case CTLTYPE_S64: FMTVAL_INT(int64_t); 65 | case CTLTYPE_UINT: FMTVAL_INT(unsigned int); 66 | case CTLTYPE_INT: FMTVAL_INT(int); 67 | case CTLTYPE_ULONG: FMTVAL_INT(unsigned long); 68 | case CTLTYPE_LONG: FMTVAL_INT(long); 69 | #undef FMTVAL_INT 70 | default: 71 | lua_pushnil(L); 72 | return (1); 73 | } 74 | } 75 | 76 | static int 77 | l_sysctl(lua_State *L) 78 | { 79 | int oid[CTL_MAXNAME]; 80 | char *oldp; 81 | size_t oldlen; 82 | unsigned int oidlen; 83 | int error; 84 | 85 | luaL_argcheck(L, lua_type(L, 1) == LUA_TTABLE, 1, 86 | "bad argument type, expected an array"); 87 | 88 | lua_pushnil(L); 89 | for (oidlen = 0; lua_next(L, 1) != 0; oidlen++) 90 | lua_pop(L, 1); 91 | if (oidlen > CTL_MAXNAME) { 92 | lua_pushnil(L); 93 | lua_pushstring(L, "sysctl OID is longer than CTLMAXNAME"); 94 | return (2); 95 | } 96 | lua_pushnil(L); 97 | for (int i = 0; lua_next(L, 1) != 0; i++) { 98 | oid[i] = lua_tonumber(L, -1); 99 | lua_pop(L, 1); 100 | } 101 | 102 | oldlen = 0; 103 | error = sysctl(oid, oidlen, NULL, &oldlen, NULL, 0); 104 | if (error != 0) { 105 | error = errno; 106 | lua_pushnil(L); 107 | lua_pushstring(L, strerror(error)); 108 | return (2); 109 | } 110 | assert(oldlen > 0); 111 | oldlen *= 2; /* what sysctl(8) does */ 112 | oldp = malloc(oldlen); 113 | if (oldp == NULL) 114 | return (luaL_error(L, "malloc: %s", strerror(errno))); 115 | error = sysctl(oid, oidlen, oldp, &oldlen, NULL, 0); 116 | if (error != 0) { 117 | error = errno; 118 | free(oldp); 119 | lua_pushnil(L); 120 | lua_pushstring(L, strerror(error)); 121 | return (2); 122 | } 123 | 124 | return (fmtval(L, oid, oidlen, oldp, oldlen)); 125 | } 126 | 127 | static int 128 | l_sysctlbyname(lua_State *L) 129 | { 130 | int oid[CTL_MAXNAME]; 131 | const char *name; 132 | char *oldp; 133 | size_t oidlen, oldlen; 134 | int error; 135 | 136 | luaL_argcheck(L, lua_type(L, 1) == LUA_TSTRING, 1, 137 | "bad argument type, expected a string"); 138 | 139 | name = lua_tostring(L, 1); 140 | 141 | oldlen = 0; 142 | error = sysctlbyname(name, NULL, &oldlen, NULL, 0); 143 | if (error != 0) { 144 | error = errno; 145 | lua_pushnil(L); 146 | lua_pushstring(L, strerror(error)); 147 | return (2); 148 | } 149 | oldlen *= 2; /* what sysctl(8) does */ 150 | oldp = malloc(oldlen); 151 | if (oldp == NULL) 152 | return (luaL_error(L, "malloc: %s", strerror(errno))); 153 | error = sysctlbyname(name, oldp, &oldlen, NULL, 0); 154 | if (error != 0) { 155 | error = errno; 156 | lua_pushnil(L); 157 | lua_pushstring(L, strerror(error)); 158 | return (2); 159 | } 160 | 161 | oidlen = sizeof(oid) / sizeof(oid[0]); 162 | error = sysctlnametomib(name, oid, &oidlen); 163 | error = sysctlbyname(name, oldp, &oldlen, NULL, 0); 164 | if (error != 0) { 165 | error = errno; 166 | lua_pushnil(L); 167 | lua_pushstring(L, strerror(error)); 168 | return (2); 169 | } 170 | 171 | return (fmtval(L, oid, oidlen, oldp, oldlen)); 172 | } 173 | 174 | static const struct luaL_Reg l_sysctltab[] = { 175 | { "sysctl", l_sysctl }, 176 | { "sysctlbyname", l_sysctlbyname }, 177 | { NULL, NULL }, 178 | }; 179 | 180 | int luaopen_sysctl(lua_State *L); 181 | 182 | int 183 | luaopen_sysctl(lua_State *L) 184 | { 185 | lua_newtable(L); 186 | 187 | luaL_setfuncs(L, l_sysctltab, 0); 188 | 189 | return (1); 190 | } 191 | -------------------------------------------------------------------------------- /contrib/orch/lib/orch/actions.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright (c) 2024 Kyle Evans 3 | -- 4 | -- SPDX-License-Identifier: BSD-2-Clause 5 | -- 6 | 7 | local core = require("orch.core") 8 | 9 | local matchers = require("orch.matchers") 10 | local process = require("orch.process") 11 | local tty = core.tty 12 | 13 | local actions = {} 14 | 15 | actions.default_matcher = matchers.available.default 16 | actions.default_timeout = 10 17 | 18 | local MatchAction = {} 19 | function MatchAction:new(action, func) 20 | local obj = setmetatable({}, self) 21 | self.__index = self 22 | obj.type = action 23 | if action ~= "match" then 24 | obj.execute = assert(func, "Not implemented on type '" .. action .. "'") 25 | end 26 | obj.completed = false 27 | obj.matcher = actions.default_matcher 28 | return obj 29 | end 30 | function MatchAction:dump(level) 31 | local indent = " " 32 | local is_one = self.type == "one" 33 | 34 | print(indent:rep((level - 1) * 2) .. "MATCH OBJECT [" .. self.type .. "]:") 35 | for k, v in pairs(self) do 36 | if k == "type" or (is_one and k == "match_ctx") then 37 | goto continue 38 | end 39 | 40 | print(indent:rep(level * 2) .. k, v) 41 | ::continue:: 42 | end 43 | 44 | if is_one and self.match_ctx then 45 | self.match_ctx:dump(level + 1) 46 | end 47 | end 48 | function MatchAction:matches(buffer) 49 | local matcher_arg = self.pattern_obj or self.pattern 50 | 51 | return self.matcher.match(matcher_arg, buffer) 52 | end 53 | 54 | actions.MatchAction = MatchAction 55 | actions.defined = { 56 | cfg = { 57 | init = function(action, args) 58 | action.cfg = args[1] 59 | end, 60 | execute = function(action) 61 | local current_process = action.ctx.process 62 | 63 | if not current_process then 64 | error("cfg() called before process spawned.") 65 | end 66 | 67 | current_process:set(action.cfg) 68 | return true 69 | end, 70 | }, 71 | eof = { 72 | print_diagnostics = function(action) 73 | io.stderr:write(string.format("[%s]:%d: eof not observed\n", 74 | action.src, action.line)) 75 | end, 76 | init = function(action, args) 77 | action.timeout = args[1] or action.ctx.timeout 78 | end, 79 | execute = function(action) 80 | local ctx = action.ctx 81 | local buffer = ctx.process.buffer 82 | 83 | if buffer.eof then 84 | return true 85 | end 86 | 87 | local function discard() 88 | end 89 | 90 | buffer:refill(discard, action.timeout) 91 | if not buffer.eof then 92 | if not ctx:fail(action, buffer:contents()) then 93 | return false 94 | end 95 | end 96 | 97 | return true 98 | end, 99 | }, 100 | log = { 101 | init = function(action, args) 102 | local file = args[1] 103 | if type(file) == "string" then 104 | file = io.open(file, "a+") 105 | end 106 | 107 | action.file = file 108 | end, 109 | execute = function(action) 110 | local current_process = action.ctx.process 111 | 112 | if not current_process then 113 | error("execute() called before process spawned.") 114 | end 115 | 116 | current_process:logfile(action.file) 117 | return true 118 | end, 119 | }, 120 | raw = { 121 | init = function(action, args) 122 | action.value = args[1] 123 | end, 124 | execute = function(action) 125 | local current_process = action.ctx.process 126 | 127 | if not current_process then 128 | error("raw() called before process spawned.") 129 | end 130 | 131 | current_process:raw(action.value) 132 | return true 133 | end, 134 | }, 135 | release = { 136 | execute = function(action) 137 | local current_process = action.ctx.process 138 | if not current_process then 139 | error("release() called before process spawned.") 140 | end 141 | 142 | assert(current_process:release()) 143 | return true 144 | end, 145 | }, 146 | spawn = { 147 | init = function(action, args) 148 | action.cmd = args 149 | 150 | if type(action.cmd[1]) == "table" then 151 | if #action.cmd > 1 then 152 | error("spawn: bad mix of table and additional arguments") 153 | end 154 | action.cmd = table.unpack(action.cmd) 155 | end 156 | end, 157 | execute = function(action) 158 | local current_process = action.ctx.process 159 | if current_process then 160 | assert(current_process:close()) 161 | end 162 | 163 | action.ctx.process = process:new(action.cmd, action.ctx) 164 | return true 165 | end, 166 | }, 167 | stty = { 168 | init = function(action, args) 169 | local field = args[1] 170 | if not tty[field] then 171 | error("stty: not a valid field to set: " .. field) 172 | end 173 | 174 | action.field = field 175 | action.set = args[2] 176 | action.unset = args[3] 177 | end, 178 | execute = function(action) 179 | local field = action.field 180 | local set, unset = action.set, action.unset 181 | local current_process = action.ctx.process 182 | 183 | local value = current_process.term:fetch(field) 184 | if type(value) == "table" then 185 | set = set or {} 186 | 187 | -- cc 188 | for k, v in pairs(set) do 189 | value[k] = v 190 | end 191 | else 192 | set = set or 0 193 | unset = unset or 0 194 | 195 | -- *flag mask 196 | value = (value | set) & ~unset 197 | end 198 | 199 | assert(current_process.term:update({ 200 | [field] = value 201 | })) 202 | 203 | return true 204 | end, 205 | }, 206 | write = { 207 | init = function(action, args) 208 | action.value = args[1] 209 | action.cfg = args[2] 210 | end, 211 | execute = function(action) 212 | local current_process = action.ctx.process 213 | if not current_process then 214 | error("Script did not spawn process prior to writing") 215 | end 216 | 217 | assert(current_process:write(action.value, action.cfg)) 218 | return true 219 | end, 220 | }, 221 | } 222 | 223 | return actions 224 | -------------------------------------------------------------------------------- /contrib/orch/lib/orch/process.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright (c) 2024 Kyle Evans 3 | -- 4 | -- SPDX-License-Identifier: BSD-2-Clause 5 | -- 6 | 7 | local core = require("orch.core") 8 | local tty = core.tty 9 | 10 | local MatchBuffer = {} 11 | function MatchBuffer:new(process, ctx) 12 | local obj = setmetatable({}, self) 13 | self.__index = self 14 | self.buffer = "" 15 | self.ctx = ctx 16 | self.process = process 17 | self.eof = false 18 | return obj 19 | end 20 | function MatchBuffer:_matches(action) 21 | local first, last = action:matches(self.buffer) 22 | 23 | if not first then 24 | return false 25 | end 26 | 27 | -- On match, we need to trim the buffer and signal completion. 28 | action.completed = true 29 | self.buffer = self.buffer:sub(last + 1) 30 | 31 | -- Return value is not significant, ignored. 32 | if action.callback then 33 | self.ctx:execute(action.callback) 34 | end 35 | 36 | return true 37 | end 38 | function MatchBuffer:contents() 39 | return self.buffer 40 | end 41 | function MatchBuffer:empty() 42 | return #self.buffer == 0 43 | end 44 | function MatchBuffer:refill(action, timeout) 45 | assert(not self.eof) 46 | 47 | if not self.process:released() then 48 | self.process:release() 49 | end 50 | local function refill(input) 51 | if not input then 52 | self.eof = true 53 | return true 54 | end 55 | 56 | if self.process.log then 57 | self.process.log:write(input) 58 | end 59 | 60 | self.buffer = self.buffer .. input 61 | if type(action) == "table" then 62 | return self:_matches(action) 63 | else 64 | assert(type(action) == "function") 65 | 66 | return action() 67 | end 68 | end 69 | 70 | if timeout then 71 | assert(self.process:read(refill, timeout)) 72 | else 73 | assert(self.process:read(refill)) 74 | end 75 | end 76 | function MatchBuffer:match(action) 77 | if not self:_matches(action) and not self.eof then 78 | self:refill(action, action.timeout) 79 | end 80 | 81 | return action.completed 82 | end 83 | 84 | -- Wrap a process and perform operations on it. 85 | local Process = {} 86 | function Process:new(cmd, ctx) 87 | local pwrap = setmetatable({}, self) 88 | self.__index = self 89 | 90 | pwrap._process = assert(core.spawn(table.unpack(cmd))) 91 | pwrap.buffer = MatchBuffer:new(pwrap, ctx) 92 | pwrap.cfg = {} 93 | pwrap.ctx = ctx 94 | pwrap.is_raw = false 95 | 96 | pwrap.term = assert(pwrap._process:term()) 97 | local mask = pwrap.term:fetch("lflag") 98 | 99 | mask = mask & ~tty.lflag.ECHO 100 | assert(pwrap.term:update({ 101 | lflag = mask, 102 | })) 103 | 104 | return pwrap 105 | end 106 | -- Proxied through to the wrapped process 107 | function Process:released() 108 | return self._process:released() 109 | end 110 | function Process:release() 111 | return self._process:release() 112 | end 113 | function Process:read(func, timeout) 114 | if timeout then 115 | return self._process:read(func, timeout) 116 | else 117 | return self._process:read(func) 118 | end 119 | end 120 | function Process:raw(is_raw) 121 | local prev_raw = self.is_raw 122 | self.is_raw = is_raw 123 | return prev_raw 124 | end 125 | function Process:write(data, cfg) 126 | if not self.is_raw then 127 | -- Convert ^[A-Z] -> cntrl sequence 128 | local quoted = false 129 | for i = 1, #data do 130 | if i > #data then 131 | break 132 | end 133 | 134 | local ch = data:sub(i, i) 135 | 136 | if quoted then 137 | quoted = false 138 | elseif ch == "\\" then 139 | quoted = true 140 | data = data:sub(1, i - 1) .. data:sub(i + 1) 141 | elseif ch == "^" then 142 | if i == #data then 143 | error("Incomplete CNTRL character at end of buffer") 144 | end 145 | 146 | local esch = data:sub(i + 1, i + 1) 147 | local esc = string.byte(esch) 148 | if esc < 0x40 or esc > 0x5f then 149 | error("Invalid escape of '" .. esch .. "'") 150 | end 151 | 152 | esch = string.char(esc - 0x40) 153 | data = data:sub(1, i - 1) .. esch .. data:sub(i + 2) 154 | end 155 | end 156 | end 157 | if self.log then 158 | self.log:write(data) 159 | end 160 | 161 | local bytes, delay 162 | local function set_rate(which_cfg) 163 | if not which_cfg or not which_cfg.rate then 164 | return 165 | end 166 | 167 | local rate = which_cfg.rate 168 | 169 | if rate.bytes ~= nil then 170 | bytes = rate.bytes 171 | end 172 | if rate.delay ~= nil then 173 | delay = rate.delay 174 | end 175 | end 176 | 177 | -- Give process configuration a first go at it 178 | set_rate(self.cfg) 179 | set_rate(cfg) 180 | 181 | -- If we didn't have a configured rate, just send a single batch of all 182 | -- data without delay. 183 | if not bytes or bytes == 0 then 184 | bytes = #data 185 | delay = nil 186 | end 187 | 188 | local sent = 0 189 | local total = #data 190 | 191 | while sent < total do 192 | local bound = math.min(total, sent + bytes) 193 | 194 | assert(self._process:write(data:sub(sent + 1, bound))) 195 | sent = bound 196 | 197 | if delay and sent < total then 198 | core.sleep(delay) 199 | end 200 | end 201 | 202 | return sent 203 | end 204 | function Process:close() 205 | assert(self._process:close()) 206 | 207 | -- Flush output, close everything out 208 | self:logfile(nil) 209 | self._process = nil 210 | self.term = nil 211 | return true 212 | end 213 | -- Our own special salt 214 | function Process:logfile(file) 215 | if self.log then 216 | self.log:flush() 217 | self.log:close() 218 | end 219 | 220 | self.log = file 221 | end 222 | function Process:match(action) 223 | local buffer = self.buffer 224 | if not buffer:match(action) then 225 | if not self.ctx:fail(action, buffer:contents()) then 226 | return false 227 | end 228 | end 229 | 230 | return true 231 | end 232 | function Process:set(cfg) 233 | for k, v in pairs(cfg) do 234 | self.cfg[k] = v 235 | end 236 | end 237 | 238 | return Process 239 | -------------------------------------------------------------------------------- /contrib/orch/lib/core/orch_spawn.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2024 Kyle Evans 3 | * 4 | * SPDX-License-Identifier: BSD-2-Clause 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include "orch.h" 24 | #include "orch_lib.h" 25 | 26 | #ifdef __OpenBSD__ 27 | #define POSIX_OPENPT_FLAGS (O_RDWR | O_NOCTTY) 28 | #else 29 | #define POSIX_OPENPT_FLAGS (O_RDWR | O_NOCTTY | O_CLOEXEC) 30 | #endif 31 | 32 | /* A bit lazy, but meh. */ 33 | #if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK) 34 | #define SOCKPAIR_ATTRS (SOCK_CLOEXEC | SOCK_NONBLOCK) 35 | #elif defined(SOCK_CLOEXEC) 36 | #define SOCKPAIR_ATTRS (SOCK_CLOEXEC) 37 | #elif defined(SOCK_NONBLOCK) 38 | #define SOCKPAIR_ATTRS (SOCK_NONBLOCK) 39 | #else 40 | #define SOCKPAIR_ATTRS (0) 41 | #endif 42 | 43 | extern char **environ; 44 | 45 | /* Parent */ 46 | static int orch_newpt(void); 47 | 48 | /* Child */ 49 | static pid_t orch_newsess(orch_ipc_t); 50 | static void orch_usept(orch_ipc_t, pid_t, int, struct termios *); 51 | static void orch_child_error(orch_ipc_t, const char *, ...) __printflike(2, 3); 52 | static void orch_exec(orch_ipc_t, int, const char *[], struct termios *); 53 | 54 | /* Both */ 55 | static int orch_wait(orch_ipc_t); 56 | 57 | int 58 | orch_spawn(int argc, const char *argv[], struct orch_process *p, 59 | orch_ipc_handler *child_error_handler) 60 | { 61 | int cmdsock[2]; 62 | pid_t pid, sess; 63 | 64 | if (socketpair(AF_UNIX, SOCK_STREAM | SOCKPAIR_ATTRS, 0, 65 | &cmdsock[0]) == -1) 66 | err(1, "socketpair"); 67 | #if (SOCKPAIR_ATTRS & SOCK_CLOEXEC) == 0 68 | if (fcntl(cmdsock[0], F_SETFD, fcntl(cmdsock[0], F_GETFD) | 69 | FD_CLOEXEC) == -1) 70 | err(1, "fcntl"); 71 | if (fcntl(cmdsock[1], F_SETFD, fcntl(cmdsock[1], F_GETFD) | 72 | FD_CLOEXEC) == -1) 73 | err(1, "fcntl"); 74 | #endif 75 | #if (SOCKPAIR_ATTRS & SOCK_NONBLOCK) == 0 76 | if (fcntl(cmdsock[0], F_SETFL, fcntl(cmdsock[0], F_GETFL) | 77 | O_NONBLOCK) == -1) 78 | err(1, "fcntl"); 79 | if (fcntl(cmdsock[1], F_SETFL, fcntl(cmdsock[1], F_GETFL) | 80 | O_NONBLOCK) == -1) 81 | err(1, "fcntl"); 82 | #endif 83 | 84 | p->termctl = orch_newpt(); 85 | 86 | pid = fork(); 87 | if (pid == -1) { 88 | err(1, "fork"); 89 | } else if (pid == 0) { 90 | struct termios t; 91 | orch_ipc_t ipc; 92 | 93 | /* Child */ 94 | close(cmdsock[0]); 95 | ipc = orch_ipc_open(cmdsock[1]); 96 | if (ipc == NULL) { 97 | close(cmdsock[1]); 98 | fprintf(stderr, "child out of memory\n"); 99 | _exit(1); 100 | } 101 | 102 | sess = orch_newsess(ipc); 103 | 104 | orch_usept(ipc, sess, p->termctl, &t); 105 | assert(p->termctl >= 0); 106 | close(p->termctl); 107 | p->termctl = -1; 108 | 109 | orch_exec(ipc, argc, argv, &t); 110 | } 111 | 112 | p->released = false; 113 | p->pid = pid; 114 | p->ipc = orch_ipc_open(cmdsock[0]); 115 | 116 | /* Parent */ 117 | close(cmdsock[1]); 118 | 119 | if (p->ipc == NULL) { 120 | int status; 121 | 122 | assert(p->termctl >= 0); 123 | close(p->termctl); 124 | close(cmdsock[0]); 125 | 126 | kill(pid, SIGKILL); 127 | while (waitpid(pid, &status, 0) != pid) { 128 | continue; 129 | } 130 | 131 | errno = ENOMEM; 132 | return (-1); 133 | } 134 | 135 | orch_ipc_register(p->ipc, IPC_ERROR, child_error_handler, p); 136 | 137 | /* 138 | * Stalls until the tty is configured, completely side step races from 139 | * script writing to the tty before, e.g., echo is disabled. 140 | */ 141 | return (orch_wait(p->ipc)); 142 | } 143 | 144 | static int 145 | orch_wait(orch_ipc_t ipc) 146 | { 147 | struct orch_ipc_msg *msg; 148 | bool stop = false; 149 | 150 | while (!stop) { 151 | if (orch_ipc_wait(ipc, &stop) == -1) 152 | return (-1); 153 | else if (stop) 154 | break; 155 | 156 | if (orch_ipc_recv(ipc, &msg) != 0) 157 | return (-1); 158 | if (msg == NULL) 159 | continue; 160 | 161 | stop = orch_ipc_msg_tag(msg) == IPC_RELEASE; 162 | 163 | orch_ipc_msg_free(msg); 164 | msg = NULL; 165 | } 166 | 167 | return (0); 168 | } 169 | 170 | int 171 | orch_release(orch_ipc_t ipc) 172 | { 173 | 174 | return (orch_ipc_send_nodata(ipc, IPC_RELEASE)); 175 | } 176 | 177 | static void 178 | orch_child_error(orch_ipc_t ipc, const char *fmt, ...) 179 | { 180 | struct orch_ipc_msg *errmsg; 181 | char *str, *msgstr; 182 | va_list ap; 183 | int sz; 184 | 185 | errmsg = NULL; 186 | va_start(ap, fmt); 187 | if ((sz = vasprintf(&str, fmt, ap)) == -1) 188 | goto out; 189 | va_end(ap); 190 | 191 | errmsg = orch_ipc_msg_alloc(IPC_ERROR, sz + 1, (void **)&msgstr); 192 | if (errmsg == NULL) 193 | goto out; 194 | 195 | strlcpy(msgstr, str, sz + 1); 196 | 197 | free(str); 198 | str = NULL; 199 | 200 | orch_ipc_send(ipc, errmsg); 201 | 202 | out: 203 | orch_ipc_msg_free(errmsg); 204 | free(str); 205 | orch_ipc_close(ipc); 206 | _exit(1); 207 | } 208 | 209 | static int 210 | orch_child_termios_inquiry(orch_ipc_t ipc, struct orch_ipc_msg *inmsg __unused, 211 | void *cookie) 212 | { 213 | struct orch_ipc_msg *msg; 214 | struct termios *child_termios = cookie, *parent_termios; 215 | int error, serr; 216 | 217 | /* Send term attributes back over the wire. */ 218 | msg = orch_ipc_msg_alloc(IPC_TERMIOS_SET, sizeof(*child_termios), 219 | (void **)&parent_termios); 220 | if (msg == NULL) { 221 | errno = EINVAL; 222 | return (-1); 223 | } 224 | 225 | memcpy(parent_termios, child_termios, sizeof(*child_termios)); 226 | 227 | error = orch_ipc_send(ipc, msg); 228 | serr = errno; 229 | 230 | orch_ipc_msg_free(msg); 231 | if (error != 0) 232 | errno = serr; 233 | return (error); 234 | } 235 | 236 | static int 237 | orch_child_termios_set(orch_ipc_t ipc, struct orch_ipc_msg *msg, void *cookie) 238 | { 239 | struct termios *updated_termios; 240 | struct termios *current_termios = cookie; 241 | size_t datasz; 242 | 243 | updated_termios = orch_ipc_msg_payload(msg, &datasz); 244 | if (updated_termios == NULL || datasz != sizeof(*updated_termios)) { 245 | errno = EINVAL; 246 | return (-1); 247 | } 248 | 249 | /* 250 | * We don't need to keep track of the updated state, but we do so 251 | * anyways. 252 | */ 253 | memcpy(current_termios, updated_termios, sizeof(*updated_termios)); 254 | 255 | if (tcsetattr(STDIN_FILENO, TCSANOW, current_termios) == -1) 256 | orch_child_error(ipc, "tcsetattr"); 257 | 258 | return (orch_ipc_send_nodata(ipc, IPC_TERMIOS_ACK)); 259 | } 260 | 261 | static void 262 | orch_exec(orch_ipc_t ipc, int argc __unused, const char *argv[], 263 | struct termios *t) 264 | { 265 | int error; 266 | 267 | signal(SIGINT, SIG_DFL); 268 | 269 | /* 270 | * Register a couple of events that the script may want to use: 271 | * - IPC_TERMIOS_INQUIRY: sent our terminal attributes back over. 272 | * - IPC_TERMIOS_SET: update our terminal attributes 273 | */ 274 | orch_ipc_register(ipc, IPC_TERMIOS_INQUIRY, orch_child_termios_inquiry, 275 | t); 276 | orch_ipc_register(ipc, IPC_TERMIOS_SET, orch_child_termios_set, t); 277 | 278 | /* Let the script commence. */ 279 | if (orch_release(ipc) != 0) 280 | _exit(1); 281 | 282 | /* 283 | * The child waits here for the script to release it. It will typically be 284 | * released on first match, but we provide an explicit release() function to 285 | * do it manually in case the script doesn't want to queue up input before 286 | * execution starts for some reason. 287 | * 288 | * For now this is just a simple int, in the future it may grow a more 289 | * extensive protocol so that the script can, e.g., reconfigure the tty. 290 | */ 291 | error = orch_wait(ipc); 292 | orch_ipc_close(ipc); 293 | 294 | if (error != 0) 295 | _exit(1); 296 | 297 | execvp(argv[0], (char * const *)(const void *)argv); 298 | 299 | _exit(1); 300 | } 301 | 302 | static int 303 | orch_newpt(void) 304 | { 305 | int newpt; 306 | 307 | newpt = posix_openpt(POSIX_OPENPT_FLAGS); 308 | if (newpt == -1) 309 | err(1, "posix_openpt"); 310 | #if (POSIX_OPENPT_FLAGS & O_CLOEXEC) == 0 311 | if (fcntl(newpt, F_SETFD, fcntl(newpt, F_GETFD) | FD_CLOEXEC) == -1) 312 | err(1, "fcntl"); 313 | #endif 314 | 315 | if (grantpt(newpt) == -1) 316 | err(1, "grantpt"); 317 | if (unlockpt(newpt) == -1) 318 | err(1, "unlockpt"); 319 | 320 | return (newpt); 321 | } 322 | 323 | static pid_t 324 | orch_newsess(orch_ipc_t ipc) 325 | { 326 | pid_t sess; 327 | 328 | sess = setsid(); 329 | if (sess == -1) 330 | orch_child_error(ipc, "setsid"); 331 | 332 | return (sess); 333 | } 334 | 335 | static void 336 | orch_usept(orch_ipc_t ipc, pid_t sess, int termctl, struct termios *t) 337 | { 338 | const char *name; 339 | int target; 340 | 341 | name = ptsname(termctl); 342 | if (name == NULL) 343 | orch_child_error(ipc, "ptsname: %s", strerror(errno)); 344 | 345 | target = open(name, O_RDWR); 346 | if (target == -1) 347 | orch_child_error(ipc, "open %s: %s", name, strerror(errno)); 348 | 349 | if (tcsetsid(target, sess) == -1) 350 | orch_child_error(ipc, "tcsetsid"); 351 | 352 | if (tcgetattr(target, t) == -1) 353 | orch_child_error(ipc, "tcgetattr"); 354 | 355 | /* XXX Accept mask, buffering? */ 356 | dup2(target, STDIN_FILENO); 357 | dup2(target, STDOUT_FILENO); 358 | dup2(target, STDERR_FILENO); 359 | if (target > STDERR_FILENO) 360 | close(target); 361 | } 362 | -------------------------------------------------------------------------------- /contrib/orch/lib/core/orch_ipc.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2024 Kyle Evans 3 | * 4 | * SPDX-License-Identifier: BSD-2-Clause 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "orch.h" 17 | #include "orch_lib.h" 18 | 19 | struct orch_ipc_header { 20 | size_t size; 21 | enum orch_ipc_tag tag; 22 | }; 23 | 24 | struct orch_ipc_msg { 25 | struct orch_ipc_header hdr; 26 | /* Non-wire contents between hdr and data */ 27 | _Alignas(max_align_t) unsigned char data[]; 28 | }; 29 | 30 | /* 31 | * We'll start making a distinction between things that need the size of an 32 | * orch_ipc_msg and things that need the size we would see on the wire. The 33 | * header contains the latter, but the orch_ipc_msg may have more contents that 34 | * are used for internal book-keeping. 35 | */ 36 | #define IPC_MSG_SIZE(payloadsz) \ 37 | (sizeof(struct orch_ipc_msg) + payloadsz) 38 | #define IPC_MSG_HDR_SIZE(payloadsz) \ 39 | (sizeof(struct orch_ipc_header) + payloadsz) 40 | 41 | #define IPC_MSG_PAYLOAD_SIZE(msg) \ 42 | ((msg)->hdr.size - sizeof(msg->hdr)) 43 | 44 | struct orch_ipc_msgq { 45 | struct orch_ipc_msg *msg; 46 | struct orch_ipc_msgq *next; 47 | }; 48 | 49 | struct orch_ipc_register { 50 | orch_ipc_handler *handler; 51 | void *cookie; 52 | }; 53 | 54 | struct orch_ipc { 55 | struct orch_ipc_register callbacks[IPC_LAST - 1]; 56 | struct orch_ipc_msgq *head; 57 | struct orch_ipc_msgq *tail; 58 | int sockfd; 59 | }; 60 | 61 | static int orch_ipc_drain(orch_ipc_t); 62 | static int orch_ipc_pop(orch_ipc_t, struct orch_ipc_msg **); 63 | static int orch_ipc_poll(orch_ipc_t, bool *); 64 | 65 | int 66 | orch_ipc_close(orch_ipc_t ipc) 67 | { 68 | int error; 69 | 70 | if (ipc == NULL) 71 | return (0); 72 | 73 | error = 0; 74 | if (ipc->sockfd != -1) { 75 | shutdown(ipc->sockfd, SHUT_WR); 76 | 77 | /* 78 | * orch_ipc_drain() should hit EOF then close the socket. This 79 | * will just drain the socket, a follow-up orch_ipc_pop() will 80 | * drain the read queue and invoke callbacks. 81 | */ 82 | while (ipc->sockfd != -1 && error == 0) { 83 | orch_ipc_wait(ipc, NULL); 84 | 85 | error = orch_ipc_drain(ipc); 86 | } 87 | 88 | if (ipc->sockfd != -1) { 89 | close(ipc->sockfd); 90 | ipc->sockfd = -1; 91 | } 92 | } 93 | 94 | /* 95 | * We may have hit EOF at an inopportune time, just cope with it 96 | * and free the queue. 97 | */ 98 | error = orch_ipc_pop(ipc, NULL); 99 | assert(ipc->head == NULL); 100 | 101 | free(ipc); 102 | 103 | return (error); 104 | } 105 | 106 | orch_ipc_t 107 | orch_ipc_open(int fd) 108 | { 109 | orch_ipc_t hdl; 110 | 111 | hdl = malloc(sizeof(*hdl)); 112 | if (hdl == NULL) 113 | return (NULL); 114 | 115 | memset(&hdl->callbacks[0], 0, sizeof(hdl->callbacks)); 116 | hdl->head = hdl->tail = NULL; 117 | hdl->sockfd = fd; 118 | return (hdl); 119 | } 120 | 121 | bool 122 | orch_ipc_okay(orch_ipc_t ipc) 123 | { 124 | 125 | return (ipc->sockfd >= 0); 126 | } 127 | 128 | struct orch_ipc_msg * 129 | orch_ipc_msg_alloc(enum orch_ipc_tag tag, size_t payloadsz, void **payload) 130 | { 131 | struct orch_ipc_msg *msg; 132 | size_t msgsz; 133 | 134 | assert(payloadsz >= 0); 135 | assert(payloadsz == 0 || payload != NULL); 136 | assert(tag != IPC_NOXMIT); 137 | 138 | msg = calloc(1, IPC_MSG_SIZE(payloadsz)); 139 | if (msg == NULL) 140 | return (NULL); 141 | 142 | msg->hdr.tag = tag; 143 | msg->hdr.size = IPC_MSG_HDR_SIZE(payloadsz); 144 | 145 | if (payloadsz != 0) 146 | *payload = msg + 1; 147 | 148 | return (msg); 149 | } 150 | 151 | void * 152 | orch_ipc_msg_payload(struct orch_ipc_msg *msg, size_t *odatasz) 153 | { 154 | size_t datasz = IPC_MSG_PAYLOAD_SIZE(msg); 155 | 156 | /* 157 | * orch_ipc_drain() should have rejected negative payload indications. 158 | */ 159 | assert(datasz >= 0); 160 | if (odatasz != NULL) 161 | *odatasz = datasz; 162 | if (datasz == 0) 163 | return (NULL); 164 | return (msg + 1); 165 | } 166 | 167 | enum orch_ipc_tag 168 | orch_ipc_msg_tag(struct orch_ipc_msg *msg) 169 | { 170 | 171 | return (msg->hdr.tag); 172 | } 173 | 174 | void 175 | orch_ipc_msg_free(struct orch_ipc_msg *msg) 176 | { 177 | 178 | free(msg); 179 | } 180 | 181 | static int 182 | orch_ipc_drain(orch_ipc_t ipc) 183 | { 184 | struct orch_ipc_header hdr; 185 | struct orch_ipc_msg *msg; 186 | struct orch_ipc_msgq *msgq; 187 | ssize_t readsz; 188 | size_t off, resid; 189 | 190 | if (!orch_ipc_okay(ipc)) 191 | return (0); 192 | 193 | for (;;) { 194 | readsz = read(ipc->sockfd, &hdr, sizeof(hdr)); 195 | if (readsz == -1) { 196 | if (errno == EAGAIN) 197 | break; 198 | return (-1); 199 | } else if (readsz == 0) { 200 | goto eof; 201 | } 202 | 203 | /* 204 | * We might have an empty payload, but we should never have less 205 | * than a header's worth of data. 206 | */ 207 | if (hdr.size < sizeof(hdr) || hdr.tag == IPC_NOXMIT) { 208 | errno = EINVAL; 209 | return (-1); 210 | } 211 | 212 | msg = malloc(IPC_MSG_SIZE(hdr.size)); 213 | if (msg == NULL) 214 | return (-1); 215 | 216 | msgq = malloc(sizeof(*msgq)); 217 | if (msgq == NULL) { 218 | free(msg); 219 | errno = ENOMEM; 220 | return (-1); 221 | } 222 | 223 | msgq->msg = msg; 224 | msgq->next = NULL; 225 | 226 | msg->hdr = hdr; 227 | 228 | off = 0; 229 | resid = IPC_MSG_PAYLOAD_SIZE(msg); 230 | 231 | while (resid != 0) { 232 | readsz = read(ipc->sockfd, &msg->data[off], resid); 233 | if (readsz == -1) { 234 | if (errno != EAGAIN) { 235 | free(msg); 236 | return (-1); 237 | } 238 | 239 | if (orch_ipc_poll(ipc, NULL) == -1) { 240 | free(msg); 241 | return (-1); 242 | } 243 | 244 | continue; 245 | } else if (readsz == 0) { 246 | free(msg); 247 | msg = NULL; 248 | 249 | goto eof; 250 | } 251 | 252 | off += readsz; 253 | resid -= readsz; 254 | } 255 | 256 | if (ipc->head == NULL) { 257 | ipc->head = ipc->tail = msgq; 258 | } else { 259 | ipc->tail->next = msgq; 260 | ipc->tail = msgq; 261 | } 262 | 263 | msg = NULL; 264 | msgq = NULL; 265 | } 266 | 267 | return (0); 268 | eof: 269 | 270 | assert(ipc->sockfd >= 0); 271 | close(ipc->sockfd); 272 | ipc->sockfd = -1; 273 | 274 | return (0); 275 | } 276 | 277 | static int 278 | orch_ipc_pop(orch_ipc_t ipc, struct orch_ipc_msg **omsg) 279 | { 280 | struct orch_ipc_register *reg; 281 | struct orch_ipc_msgq *msgq; 282 | struct orch_ipc_msg *msg; 283 | int error; 284 | 285 | error = 0; 286 | while (ipc->head != NULL) { 287 | /* Dequeue a msg */ 288 | msgq = ipc->head; 289 | ipc->head = ipc->head->next; 290 | 291 | /* Free the container */ 292 | msg = msgq->msg; 293 | 294 | free(msgq); 295 | msgq = NULL; 296 | 297 | /* Do we have a handler for it? */ 298 | reg = &ipc->callbacks[msg->hdr.tag - 1]; 299 | if (reg->handler != NULL) { 300 | int serr; 301 | 302 | error = (*reg->handler)(ipc, msg, reg->cookie); 303 | if (error != 0) 304 | serr = errno; 305 | 306 | free(msg); 307 | msg = NULL; 308 | 309 | if (error != 0) { 310 | errno = serr; 311 | error = -1; 312 | break; 313 | } 314 | 315 | /* 316 | * Try to dequeue another one... the handler is allowed 317 | * to shut down IPC, so let's be careful here. 318 | */ 319 | continue; 320 | } 321 | 322 | /* 323 | * No handler, potentially tap this one out. If we don't have 324 | * an omsg, we're just draining so we'll free the msg here. 325 | */ 326 | if (omsg == NULL) { 327 | free(msg); 328 | msg = NULL; 329 | 330 | continue; 331 | } 332 | 333 | *omsg = msg; 334 | break; 335 | } 336 | 337 | return (error); 338 | } 339 | 340 | int 341 | orch_ipc_recv(orch_ipc_t ipc, struct orch_ipc_msg **omsg) 342 | { 343 | struct orch_ipc_msg *rcvmsg; 344 | int error; 345 | 346 | if (orch_ipc_drain(ipc) != 0) 347 | return (-1); 348 | 349 | rcvmsg = NULL; 350 | error = orch_ipc_pop(ipc, &rcvmsg); 351 | if (error == 0) 352 | *omsg = rcvmsg; 353 | return (error); 354 | } 355 | 356 | int 357 | orch_ipc_register(orch_ipc_t ipc, enum orch_ipc_tag tag, 358 | orch_ipc_handler *handler, void *cookie) 359 | { 360 | struct orch_ipc_register *reg = &ipc->callbacks[tag - 1]; 361 | 362 | reg->handler = handler; 363 | reg->cookie = cookie; 364 | return (0); 365 | } 366 | 367 | int 368 | orch_ipc_send(orch_ipc_t ipc, struct orch_ipc_msg *msg) 369 | { 370 | ssize_t writesz; 371 | size_t off, resid; 372 | 373 | retry: 374 | if (orch_ipc_drain(ipc) != 0) 375 | return (-1); 376 | 377 | writesz = write(ipc->sockfd, &msg->hdr, sizeof(msg->hdr)); 378 | if (writesz == -1) { 379 | if (errno != EAGAIN) 380 | return (-1); 381 | goto retry; 382 | } else if ((size_t)writesz < sizeof(msg->hdr)) { 383 | errno = EIO; 384 | return (-1); 385 | } 386 | 387 | off = 0; 388 | resid = IPC_MSG_PAYLOAD_SIZE(msg); 389 | while (resid != 0) { 390 | writesz = write(ipc->sockfd, &msg->data[off], resid); 391 | if (writesz == -1) { 392 | if (errno != EAGAIN) 393 | return (-1); 394 | continue; 395 | } 396 | 397 | off += writesz; 398 | resid -= writesz; 399 | } 400 | 401 | return (0); 402 | } 403 | 404 | int 405 | orch_ipc_send_nodata(orch_ipc_t ipc, enum orch_ipc_tag tag) 406 | { 407 | struct orch_ipc_msg msg = { 0 }; 408 | 409 | msg.hdr.tag = tag; 410 | msg.hdr.size = IPC_MSG_HDR_SIZE(0); 411 | 412 | return (orch_ipc_send(ipc, &msg)); 413 | } 414 | 415 | static int 416 | orch_ipc_poll(orch_ipc_t ipc, bool *eof_seen) 417 | { 418 | fd_set rfd; 419 | int error; 420 | 421 | if (eof_seen != NULL) 422 | *eof_seen = false; 423 | 424 | FD_ZERO(&rfd); 425 | do { 426 | if (ipc->sockfd == -1) { 427 | if (eof_seen != NULL) 428 | *eof_seen = true; 429 | return (0); 430 | } 431 | 432 | FD_SET(ipc->sockfd, &rfd); 433 | 434 | error = select(ipc->sockfd + 1, &rfd, NULL, NULL, NULL); 435 | } while (error == -1 && errno == EINTR); 436 | 437 | return (error); 438 | } 439 | 440 | int 441 | orch_ipc_wait(orch_ipc_t ipc, bool *eof_seen) 442 | { 443 | 444 | /* 445 | * If we have any messages in the queue, don't bother polling; recv 446 | * will return something. 447 | */ 448 | if (ipc->head != NULL) 449 | return (0); 450 | 451 | return (orch_ipc_poll(ipc, eof_seen)); 452 | } 453 | -------------------------------------------------------------------------------- /contrib/orch/lib/core/orch_tty.c: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright (c) 2024 Kyle Evans 3 | * 4 | * SPDX-License-Identifier: BSD-2-Clause 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "orch.h" 14 | #include "orch_lib.h" 15 | 16 | #include 17 | #include 18 | 19 | #define ORCHLUA_TERMHANDLE "orch_term" 20 | 21 | #define CNTRL_ENTRY(c, m) { c, #c, m } 22 | const struct orchlua_tty_cntrl orchlua_cntrl_chars[] = { 23 | CNTRL_ENTRY(VEOF, CNTRL_CANON), 24 | CNTRL_ENTRY(VEOL, CNTRL_CANON), 25 | CNTRL_ENTRY(VERASE, CNTRL_CANON), 26 | CNTRL_ENTRY(VINTR, CNTRL_BOTH), 27 | CNTRL_ENTRY(VKILL, CNTRL_CANON), 28 | CNTRL_ENTRY(VMIN, CNTRL_NCANON | CNTRL_LITERAL), 29 | CNTRL_ENTRY(VQUIT, CNTRL_BOTH), 30 | CNTRL_ENTRY(VSUSP, CNTRL_BOTH), 31 | CNTRL_ENTRY(VTIME, CNTRL_NCANON | CNTRL_LITERAL), 32 | CNTRL_ENTRY(VSTART, CNTRL_BOTH), 33 | CNTRL_ENTRY(VSTOP, CNTRL_BOTH), 34 | #ifdef VSTATUS 35 | CNTRL_ENTRY(VSTATUS, CNTRL_CANON), 36 | #endif 37 | { 0, NULL, 0 }, 38 | }; 39 | 40 | /* 41 | * I only care about local modes personally, but the other tables are present to 42 | * avoid putting up any barriers if more modes are useful to someone else. 43 | */ 44 | #define MODE_ENTRY(c) { c, #c } 45 | const struct orchlua_tty_mode orchlua_input_modes[] = { 46 | { 0, NULL }, 47 | }; 48 | 49 | const struct orchlua_tty_mode orchlua_output_modes[] = { 50 | { 0, NULL }, 51 | }; 52 | 53 | const struct orchlua_tty_mode orchlua_cntrl_modes[] = { 54 | { 0, NULL }, 55 | }; 56 | 57 | const struct orchlua_tty_mode orchlua_local_modes[] = { 58 | MODE_ENTRY(ECHO), 59 | MODE_ENTRY(ECHOE), 60 | MODE_ENTRY(ECHOK), 61 | MODE_ENTRY(ECHONL), 62 | MODE_ENTRY(ICANON), 63 | MODE_ENTRY(IEXTEN), 64 | MODE_ENTRY(ISIG), 65 | MODE_ENTRY(NOFLSH), 66 | MODE_ENTRY(TOSTOP), 67 | { 0, NULL }, 68 | }; 69 | 70 | static void 71 | orchlua_term_fetch_cc(lua_State *L, struct termios *term) 72 | { 73 | const struct orchlua_tty_cntrl *iter; 74 | cc_t cc; 75 | 76 | for (iter = &orchlua_cntrl_chars[0]; iter->cntrl_name != NULL; iter++) { 77 | cc = term->c_cc[iter->cntrl_idx]; 78 | 79 | if ((iter->cntrl_flags & CNTRL_LITERAL) != 0) 80 | lua_pushinteger(L, cc); 81 | else if (cc == _POSIX_VDISABLE) 82 | lua_pushstring(L, ""); 83 | else if (cc == 0177) 84 | lua_pushstring(L, "^?"); 85 | else 86 | lua_pushfstring(L, "^%c", cc + 0x40); 87 | 88 | lua_setfield(L, -2, iter->cntrl_name); 89 | } 90 | } 91 | 92 | static int 93 | orchlua_term_fetch(lua_State *L) 94 | { 95 | struct orch_term *self; 96 | const char *which; 97 | int retvals = 0, top; 98 | 99 | self = luaL_checkudata(L, 1, ORCHLUA_TERMHANDLE); 100 | if ((top = lua_gettop(L)) < 2) { 101 | lua_pushnil(L); 102 | return (1); 103 | } 104 | 105 | for (int i = 1; i < top; i++) { 106 | which = luaL_checkstring(L, i + 1); 107 | 108 | if (strcmp(which, "iflag") == 0) { 109 | lua_pushnumber(L, self->term.c_iflag); 110 | } else if (strcmp(which, "oflag") == 0) { 111 | lua_pushnumber(L, self->term.c_oflag); 112 | } else if (strcmp(which, "cflag") == 0) { 113 | lua_pushnumber(L, self->term.c_cflag); 114 | } else if (strcmp(which, "lflag") == 0) { 115 | lua_pushnumber(L, self->term.c_lflag); 116 | } else if (strcmp(which, "cc") == 0) { 117 | lua_newtable(L); 118 | orchlua_term_fetch_cc(L, &self->term); 119 | } else { 120 | lua_pushnil(L); 121 | } 122 | 123 | retvals++; 124 | } 125 | 126 | return (retvals); 127 | } 128 | 129 | static int 130 | orchlua_term_update_cc(lua_State *L, struct termios *term) 131 | { 132 | const struct orchlua_tty_cntrl *iter; 133 | int type; 134 | cc_t cc; 135 | 136 | for (iter = &orchlua_cntrl_chars[0]; iter->cntrl_name != NULL; iter++) { 137 | type = lua_getfield(L, -1, iter->cntrl_name); 138 | if (type == LUA_TNIL) { 139 | lua_pop(L, 1); 140 | continue; 141 | } 142 | 143 | if ((iter->cntrl_flags & CNTRL_LITERAL) != 0) { 144 | int valid; 145 | 146 | cc = lua_tonumberx(L, -1, &valid); 147 | if (!valid) { 148 | luaL_pushfail(L); 149 | lua_pushfstring(L, "expected number for cc '%s'", 150 | iter->cntrl_name); 151 | return (2); 152 | } 153 | } else { 154 | const char *str; 155 | size_t len; 156 | 157 | if (type != LUA_TSTRING) { 158 | luaL_pushfail(L); 159 | lua_pushfstring(L, "expected string for cc '%s'", 160 | iter->cntrl_name); 161 | return (2); 162 | } 163 | 164 | str = lua_tolstring(L, -1, &len); 165 | if (len == 0) { 166 | cc = _POSIX_VDISABLE; 167 | } else if (len != 2 || str[0] != '^') { 168 | luaL_pushfail(L); 169 | lua_pushfstring(L, 170 | "malformed value for cc '%s': %s", 171 | iter->cntrl_name, str); 172 | return (2); 173 | } else if (str[1] != '?' && 174 | (str[1] < 0x40 || str[1] > 0x5f)) { 175 | luaL_pushfail(L); 176 | lua_pushfstring(L, 177 | "cntrl char for cc '%s' out of bounds: %c", 178 | iter->cntrl_name, str[1]); 179 | return (2); 180 | } else { 181 | if (str[1] == '?') 182 | cc = 0177; 183 | else 184 | cc = str[1] - 0x40; 185 | } 186 | } 187 | 188 | term->c_cc[iter->cntrl_idx] = cc; 189 | lua_pop(L, 1); 190 | } 191 | 192 | return (0); 193 | } 194 | 195 | static int 196 | orchlua_term_update(lua_State *L) 197 | { 198 | const char *fields[] = { "iflag", "oflag", "lflag", "cc", NULL }; 199 | struct orch_term *self; 200 | struct orch_ipc_msg *msg; 201 | const char **fieldp, *field; 202 | struct termios *msgterm, updated; 203 | size_t msgsz; 204 | int error, mask, type, valid; 205 | 206 | self = luaL_checkudata(L, 1, ORCHLUA_TERMHANDLE); 207 | if (!lua_istable(L, 2)) { 208 | luaL_pushfail(L); 209 | lua_pushstring(L, "argument #2 must be table of fields to update"); 210 | return (2); 211 | } 212 | 213 | lua_settop(L, 2); 214 | 215 | updated = self->term; 216 | for (fieldp = &fields[0]; *fieldp != NULL; fieldp++) { 217 | field = *fieldp; 218 | 219 | type = lua_getfield(L, -1, field); 220 | if (type == LUA_TNIL) { 221 | lua_pop(L, 1); 222 | continue; 223 | } 224 | 225 | if (strcmp(field, "iflag") == 0) { 226 | updated.c_iflag = lua_tonumberx(L, -1, &valid); 227 | if (!valid) { 228 | luaL_pushfail(L); 229 | lua_pushstring(L, "iflag must be a numeric mask"); 230 | return (2); 231 | } 232 | } else if (strcmp(field, "oflag") == 0) { 233 | updated.c_oflag = lua_tonumberx(L, -1, &valid); 234 | if (!valid) { 235 | luaL_pushfail(L); 236 | lua_pushstring(L, "oflag must be a numeric mask"); 237 | return (2); 238 | } 239 | } else if (strcmp(field, "cflag") == 0) { 240 | updated.c_cflag = lua_tonumberx(L, -1, &valid); 241 | if (!valid) { 242 | luaL_pushfail(L); 243 | lua_pushstring(L, "cflag must be a numeric mask"); 244 | return (2); 245 | } 246 | } else if (strcmp(field, "lflag") == 0) { 247 | updated.c_lflag = lua_tonumberx(L, -1, &valid); 248 | if (!valid) { 249 | luaL_pushfail(L); 250 | lua_pushstring(L, "lflag must be a numeric mask"); 251 | return (2); 252 | } 253 | } else if (strcmp(field, "cc") == 0) { 254 | if (type != LUA_TTABLE) { 255 | luaL_pushfail(L); 256 | lua_pushstring(L, "cc must be a table of characters to remap"); 257 | return (2); 258 | } 259 | 260 | if ((error = orchlua_term_update_cc(L, &updated)) != 0) 261 | return (error); 262 | } 263 | 264 | lua_pop(L, 1); 265 | } 266 | 267 | self->term = updated; 268 | 269 | msg = orch_ipc_msg_alloc(IPC_TERMIOS_SET, sizeof(self->term), 270 | (void **)&msgterm); 271 | if (msg == NULL) { 272 | luaL_pushfail(L); 273 | lua_pushstring(L, strerror(ENOMEM)); 274 | return (2); 275 | } 276 | 277 | memcpy(msgterm, &self->term, sizeof(self->term)); 278 | error = orch_ipc_send(self->proc->ipc, msg); 279 | if (error != 0) 280 | error = errno; 281 | 282 | orch_ipc_msg_free(msg); 283 | msg = NULL; 284 | 285 | if (error != 0) { 286 | luaL_pushfail(L); 287 | lua_pushstring(L, strerror(error)); 288 | return (2); 289 | } 290 | 291 | /* Wait for ack */ 292 | if (orch_ipc_wait(self->proc->ipc, NULL) == -1) { 293 | error = errno; 294 | goto err; 295 | } 296 | 297 | if (orch_ipc_recv(self->proc->ipc, &msg) != 0) { 298 | error = errno; 299 | goto err; 300 | } else if (msg == NULL) { 301 | luaL_pushfail(L); 302 | lua_pushstring(L, "unknown unexpected message received"); 303 | return (2); 304 | } else if (orch_ipc_msg_tag(msg) != IPC_TERMIOS_ACK) { 305 | luaL_pushfail(L); 306 | lua_pushfstring(L, "unexpected message type '%d'", 307 | orch_ipc_msg_tag(msg)); 308 | orch_ipc_msg_free(msg); 309 | return (2); 310 | } 311 | 312 | orch_ipc_msg_free(msg); 313 | 314 | lua_pushboolean(L, 1); 315 | return (1); 316 | err: 317 | luaL_pushfail(L); 318 | lua_pushstring(L, strerror(errno)); 319 | return (2); 320 | } 321 | 322 | #define ORCHTERM_SIMPLE(n) { #n, orchlua_term_ ## n } 323 | static const luaL_Reg orchlua_term[] = { 324 | ORCHTERM_SIMPLE(fetch), 325 | ORCHTERM_SIMPLE(update), 326 | { NULL, NULL }, 327 | }; 328 | 329 | static const luaL_Reg orchlua_term_meta[] = { 330 | { "__index", NULL }, /* Set during registratino */ 331 | /* Nothing to __gc / __close just yet. */ 332 | { NULL, NULL }, 333 | }; 334 | 335 | static void 336 | register_term_metatable(lua_State *L) 337 | { 338 | luaL_newmetatable(L, ORCHLUA_TERMHANDLE); 339 | luaL_setfuncs(L, orchlua_term_meta, 0); 340 | 341 | luaL_newlibtable(L, orchlua_term); 342 | luaL_setfuncs(L, orchlua_term, 0); 343 | lua_setfield(L, -2, "__index"); 344 | 345 | lua_pop(L, 1); 346 | } 347 | 348 | static void 349 | orchlua_tty_add_cntrl(lua_State *L, const char *name, 350 | const struct orchlua_tty_cntrl *mcntrl) 351 | { 352 | const struct orchlua_tty_cntrl *iter; 353 | 354 | lua_newtable(L); 355 | for (iter = mcntrl; iter->cntrl_name != NULL; iter++) { 356 | lua_pushboolean(L, 1); 357 | lua_setfield(L, -2, iter->cntrl_name); 358 | } 359 | 360 | lua_setfield(L, -2, name); 361 | } 362 | 363 | static void 364 | orchlua_tty_add_modes(lua_State *L, const char *name, 365 | const struct orchlua_tty_mode *mtable) 366 | { 367 | const struct orchlua_tty_mode *iter; 368 | 369 | lua_newtable(L); 370 | for (iter = mtable; iter->mode_mask != 0; iter++) { 371 | lua_pushinteger(L, iter->mode_mask); 372 | lua_setfield(L, -2, iter->mode_name); 373 | } 374 | 375 | lua_setfield(L, -2, name); 376 | } 377 | 378 | int 379 | orchlua_setup_tty(lua_State *L) 380 | { 381 | 382 | /* Module is on the stack. */ 383 | lua_newtable(L); 384 | 385 | /* tty.iflag, tty.oflag, tty.cflag, tty.lflag */ 386 | orchlua_tty_add_modes(L, "iflag", &orchlua_input_modes[0]); 387 | orchlua_tty_add_modes(L, "oflag", &orchlua_output_modes[0]); 388 | orchlua_tty_add_modes(L, "cflag", &orchlua_cntrl_modes[0]); 389 | orchlua_tty_add_modes(L, "lflag", &orchlua_local_modes[0]); 390 | 391 | /* tty.cc */ 392 | orchlua_tty_add_cntrl(L, "cc", &orchlua_cntrl_chars[0]); 393 | 394 | lua_setfield(L, -2, "tty"); 395 | 396 | register_term_metatable(L); 397 | 398 | return (1); 399 | } 400 | 401 | int 402 | orchlua_tty_alloc(lua_State *L, const struct orch_term *copy, 403 | struct orch_term **otermp) 404 | { 405 | struct orch_term *term; 406 | 407 | term = lua_newuserdata(L, sizeof(*term)); 408 | memcpy(term, copy, sizeof(*copy)); 409 | 410 | *otermp = term; 411 | 412 | luaL_setmetatable(L, ORCHLUA_TERMHANDLE); 413 | return (1); 414 | } 415 | -------------------------------------------------------------------------------- /lib/freebsd/posix_spawn/lua_posix_spawn.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-License-Identifier: BSD-2-Clause 3 | * 4 | * Copyright (c) Mark Johnston 5 | */ 6 | 7 | /* 8 | * Wrappers for posix_spawn(3) and friends. 9 | * 10 | * posix_spawn() and posix_spawnp() return a PID upon success, otherwise: nil, 11 | * an error message, an error number. 12 | * 13 | * If the second parameter is a udata created by 14 | * posix_spawn_file_actions_init(), the file actions are used for the spawn, 15 | * otherwise the second parameter should be an array of command-line parameters. 16 | * 17 | * Spawn attribute support is not yet implemented. 18 | * 19 | * Example: 20 | * 21 | * posix_spawn("ls", { "ls", "-l", "/tmp" }, { "TERM=xterm", "CLICOLOR=1" }) 22 | */ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | 38 | #define POSIX_SPAWN_FILE_ACTIONS_KEY "freebsd_posix_spawn_file_actions" 39 | #define POSIX_SPAWNATTR_KEY "freebsd_posix_spawnattr" 40 | 41 | extern char **environ; 42 | 43 | static int 44 | l_posix_spawn1(lua_State *L, bool path) 45 | { 46 | posix_spawn_file_actions_t file_actions, *file_actionsp; 47 | posix_spawnattr_t attr, *attrp; 48 | const char *file; 49 | const char **argv; 50 | const char **envp; 51 | char *const *_argv; 52 | char *const *_envp; 53 | int argi, argc, envc, ret, ret1; 54 | pid_t pid; 55 | 56 | argi = 1; 57 | file = luaL_checkstring(L, argi++); 58 | 59 | /* 60 | * File actions and spawn attributes are optional. They must be 61 | * followed by a table. 62 | */ 63 | attrp = NULL; 64 | file_actionsp = NULL; 65 | if (lua_isuserdata(L, argi)) { 66 | lua_getmetatable(L, argi); 67 | luaL_getmetatable(L, POSIX_SPAWN_FILE_ACTIONS_KEY); 68 | if (lua_rawequal(L, -1, -2)) { 69 | file_actionsp = luaL_checkudata(L, argi, 70 | POSIX_SPAWN_FILE_ACTIONS_KEY); 71 | argi++; 72 | } 73 | lua_pop(L, 2); 74 | } 75 | if (lua_isuserdata(L, argi)) { 76 | void *val; 77 | 78 | val = luaL_checkudata(L, argi, POSIX_SPAWNATTR_KEY); 79 | if (val != NULL) { 80 | attrp = val; 81 | argi++; 82 | } 83 | } 84 | luaL_checktype(L, argi, LUA_TTABLE); 85 | 86 | /* 87 | * If the caller didn't provide a file actions or spawn attributes, set 88 | * up a no-op default. 89 | */ 90 | if (file_actionsp == NULL) { 91 | ret = posix_spawn_file_actions_init(&file_actions); 92 | if (ret != 0) { 93 | lua_pushnil(L); 94 | lua_pushstring(L, strerror(ret)); 95 | lua_pushinteger(L, ret); 96 | return (3); 97 | } 98 | file_actionsp = &file_actions; 99 | } 100 | if (attrp == NULL) { 101 | ret = posix_spawnattr_init(&attr); 102 | if (ret != 0) { 103 | lua_pushnil(L); 104 | lua_pushstring(L, strerror(ret)); 105 | lua_pushinteger(L, ret); 106 | return (3); 107 | } 108 | attrp = &attr; 109 | } 110 | 111 | argc = lua_rawlen(L, argi); 112 | argv = calloc(argc + 1, sizeof(char *)); 113 | if (argv == NULL) { 114 | lua_pushnil(L); 115 | lua_pushstring(L, strerror(errno)); 116 | lua_pushinteger(L, errno); 117 | return (3); 118 | } 119 | for (int i = 0; i < argc; i++) { 120 | lua_rawgeti(L, argi, i + 1); 121 | argv[i] = lua_tostring(L, -1); 122 | lua_pop(L, 1); 123 | } 124 | argi++; 125 | 126 | /* 127 | * POSIX doesn't appear to specify what happens if the envp is NULL. 128 | * FreeBSD treats it as meaning that the environment is to be inherited, 129 | * which seems sensible. Follow that behaviour if the caller didn't 130 | * specify a final parameter. 131 | */ 132 | if (lua_gettop(L) >= argi && lua_type(L, argi) != LUA_TNIL) { 133 | luaL_checktype(L, argi, LUA_TTABLE); 134 | envc = lua_rawlen(L, argi); 135 | envp = calloc(envc + 1, sizeof(char *)); 136 | if (envp == NULL) { 137 | lua_pushnil(L); 138 | lua_pushstring(L, strerror(errno)); 139 | lua_pushinteger(L, errno); 140 | return (3); 141 | } 142 | for (int i = 0; i < envc; i++) { 143 | lua_rawgeti(L, argi, i + 1); 144 | envp[i] = lua_tostring(L, -1); 145 | lua_pop(L, 1); 146 | } 147 | argi++; 148 | } else { 149 | envp = __DECONST(const char **, environ); 150 | } 151 | 152 | _argv = __DECONST(char * const *, argv); 153 | _envp = __DECONST(char * const *, envp); 154 | ret = path ? 155 | posix_spawnp(&pid, file, file_actionsp, attrp, _argv, _envp) : 156 | posix_spawn(&pid, file, file_actionsp, attrp, _argv, _envp); 157 | 158 | if (file_actionsp == &file_actions) { 159 | ret1 = posix_spawn_file_actions_destroy(file_actionsp); 160 | assert(ret1 == 0); 161 | } 162 | if (attrp == &attr) { 163 | ret1 = posix_spawnattr_destroy(attrp); 164 | assert(ret1 == 0); 165 | } 166 | 167 | free(argv); 168 | if (envp != __DECONST(const char **, environ)) 169 | free(envp); 170 | 171 | if (ret != 0) { 172 | lua_pushnil(L); 173 | lua_pushstring(L, strerror(ret)); 174 | lua_pushinteger(L, ret); 175 | return (3); 176 | } 177 | 178 | lua_pushinteger(L, pid); 179 | return (1); 180 | } 181 | 182 | static int 183 | l_posix_spawn(lua_State *L) 184 | { 185 | return (l_posix_spawn1(L, false)); 186 | } 187 | 188 | static int 189 | l_posix_spawnp(lua_State *L) 190 | { 191 | return (l_posix_spawn1(L, true)); 192 | } 193 | 194 | static int 195 | l_posix_spawn_file_actions_init(lua_State *L) 196 | { 197 | posix_spawn_file_actions_t *file_actions; 198 | int ret; 199 | 200 | file_actions = lua_newuserdata(L, sizeof(posix_spawn_file_actions_t)); 201 | ret = posix_spawn_file_actions_init(file_actions); 202 | if (ret != 0) { 203 | lua_pushnil(L); 204 | lua_pushstring(L, strerror(ret)); 205 | lua_pushinteger(L, ret); 206 | return (3); 207 | } 208 | 209 | luaL_getmetatable(L, POSIX_SPAWN_FILE_ACTIONS_KEY); 210 | lua_setmetatable(L, -2); 211 | 212 | return (1); 213 | } 214 | 215 | static int 216 | l_posix_spawn_file_actions_addopen(lua_State *L) 217 | { 218 | posix_spawn_file_actions_t *file_actions; 219 | const char *path; 220 | int error, *fdp, oflags; 221 | mode_t mode; 222 | 223 | file_actions = luaL_checkudata(L, 1, POSIX_SPAWN_FILE_ACTIONS_KEY); 224 | fdp = luaL_checkudata(L, 2, FREEBSD_SYS_FD_REGISTRY_KEY); 225 | path = luaL_checkstring(L, 3); 226 | oflags = luaL_checkinteger(L, 4); 227 | mode = luaL_optinteger(L, 5, 0); 228 | 229 | assert(fcntl(*fdp, F_GETFD) != -1); 230 | 231 | error = posix_spawn_file_actions_addopen(file_actions, *fdp, path, 232 | oflags, mode); 233 | if (error != 0) { 234 | lua_pushnil(L); 235 | lua_pushstring(L, strerror(error)); 236 | lua_pushinteger(L, error); 237 | return (3); 238 | } 239 | 240 | lua_pushboolean(L, 1); 241 | return (1); 242 | } 243 | 244 | static int 245 | l_posix_spawn_file_actions_adddup2(lua_State *L) 246 | { 247 | posix_spawn_file_actions_t *file_actions; 248 | int error, *oldfdp, newfd; 249 | 250 | file_actions = luaL_checkudata(L, 1, POSIX_SPAWN_FILE_ACTIONS_KEY); 251 | oldfdp = luaL_checkudata(L, 2, FREEBSD_SYS_FD_REGISTRY_KEY); 252 | newfd = luaL_checkinteger(L, 3); 253 | 254 | assert(fcntl(*oldfdp, F_GETFD) != -1); 255 | 256 | error = posix_spawn_file_actions_adddup2(file_actions, *oldfdp, newfd); 257 | if (error != 0) { 258 | lua_pushnil(L); 259 | lua_pushstring(L, strerror(error)); 260 | lua_pushinteger(L, error); 261 | return (3); 262 | } 263 | 264 | lua_pushboolean(L, 1); 265 | return (1); 266 | } 267 | 268 | static int 269 | l_posix_spawn_file_actions_addclose(lua_State *L) 270 | { 271 | posix_spawn_file_actions_t *file_actions; 272 | int error, *fdp; 273 | 274 | file_actions = luaL_checkudata(L, 1, POSIX_SPAWN_FILE_ACTIONS_KEY); 275 | fdp = luaL_checkudata(L, 2, FREEBSD_SYS_FD_REGISTRY_KEY); 276 | 277 | assert(fcntl(*fdp, F_GETFD) != -1); 278 | 279 | error = posix_spawn_file_actions_addclose(file_actions, *fdp); 280 | if (error != 0) { 281 | lua_pushnil(L); 282 | lua_pushstring(L, strerror(error)); 283 | lua_pushinteger(L, error); 284 | return (3); 285 | } 286 | 287 | lua_pushboolean(L, 1); 288 | return (1); 289 | } 290 | 291 | 292 | static int 293 | l_posix_spawn_file_actions_addclosefrom_np(lua_State *L) 294 | { 295 | posix_spawn_file_actions_t *file_actions; 296 | int error, from; 297 | 298 | file_actions = luaL_checkudata(L, 1, POSIX_SPAWN_FILE_ACTIONS_KEY); 299 | from = luaL_checkinteger(L, 2); 300 | 301 | error = posix_spawn_file_actions_addclosefrom_np(file_actions, from); 302 | if (error != 0) { 303 | lua_pushnil(L); 304 | lua_pushstring(L, strerror(error)); 305 | lua_pushinteger(L, error); 306 | return (3); 307 | } 308 | 309 | lua_pushboolean(L, 1); 310 | return (1); 311 | } 312 | 313 | static int 314 | l_posix_spawn_file_actions_addchdir_np(lua_State *L) 315 | { 316 | posix_spawn_file_actions_t *file_actions; 317 | const char *path; 318 | int error; 319 | 320 | file_actions = luaL_checkudata(L, 1, POSIX_SPAWN_FILE_ACTIONS_KEY); 321 | path = luaL_checkstring(L, 2); 322 | 323 | error = posix_spawn_file_actions_addchdir_np(file_actions, path); 324 | if (error != 0) { 325 | lua_pushnil(L); 326 | lua_pushstring(L, strerror(error)); 327 | lua_pushinteger(L, error); 328 | return (3); 329 | } 330 | 331 | lua_pushboolean(L, 1); 332 | return (1); 333 | } 334 | 335 | static int 336 | l_posix_spawn_file_actions_addfchdir_np(lua_State *L) 337 | { 338 | posix_spawn_file_actions_t *file_actions; 339 | int error, *fdp; 340 | 341 | file_actions = luaL_checkudata(L, 1, POSIX_SPAWN_FILE_ACTIONS_KEY); 342 | fdp = luaL_checkudata(L, 2, FREEBSD_SYS_FD_REGISTRY_KEY); 343 | 344 | assert(fcntl(*fdp, F_GETFD) != -1); 345 | 346 | error = posix_spawn_file_actions_addfchdir_np(file_actions, *fdp); 347 | if (error != 0) { 348 | lua_pushnil(L); 349 | lua_pushstring(L, strerror(error)); 350 | lua_pushinteger(L, error); 351 | return (3); 352 | } 353 | 354 | lua_pushboolean(L, 1); 355 | return (1); 356 | } 357 | 358 | static int 359 | l_posix_spawnattr_init(lua_State *L) 360 | { 361 | posix_spawnattr_t *attr; 362 | int error; 363 | 364 | attr = lua_newuserdata(L, sizeof(posix_spawnattr_t)); 365 | 366 | error = posix_spawnattr_init(attr); 367 | if (error != 0) { 368 | lua_pushnil(L); 369 | lua_pushstring(L, strerror(error)); 370 | lua_pushinteger(L, error); 371 | return (3); 372 | } 373 | 374 | luaL_getmetatable(L, POSIX_SPAWNATTR_KEY); 375 | lua_setmetatable(L, -2); 376 | return (1); 377 | } 378 | 379 | static int 380 | l_posix_spawnattr_getflags(lua_State *L) 381 | { 382 | posix_spawnattr_t *attr; 383 | short flags; 384 | int error; 385 | 386 | attr = luaL_checkudata(L, 1, POSIX_SPAWNATTR_KEY); 387 | 388 | error = posix_spawnattr_getflags(attr, &flags); 389 | if (error != 0) { 390 | lua_pushnil(L); 391 | lua_pushstring(L, strerror(error)); 392 | lua_pushinteger(L, error); 393 | return (3); 394 | } 395 | 396 | lua_pushinteger(L, flags); 397 | return (1); 398 | } 399 | 400 | static int 401 | l_posix_spawnattr_setflags(lua_State *L) 402 | { 403 | posix_spawnattr_t *attr; 404 | lua_Integer lflags; 405 | short flags; 406 | int error; 407 | 408 | attr = luaL_checkudata(L, 1, POSIX_SPAWNATTR_KEY); 409 | lflags = luaL_checkinteger(L, 2); 410 | if (lflags > SHRT_MAX || lflags < SHRT_MIN) { 411 | return (luaL_error(L, "flags too large: %jx", 412 | (uintmax_t)lflags)); 413 | } 414 | flags = (short)lflags; 415 | 416 | error = posix_spawnattr_setflags(attr, flags); 417 | assert(error == 0); 418 | lua_pushboolean(L, 1); 419 | return (1); 420 | } 421 | 422 | static int 423 | l_posix_spawnattr_getpgroup(lua_State *L) 424 | { 425 | posix_spawnattr_t *attr; 426 | pid_t pgroup; 427 | int error; 428 | 429 | attr = luaL_checkudata(L, 1, POSIX_SPAWNATTR_KEY); 430 | 431 | error = posix_spawnattr_getpgroup(attr, &pgroup); 432 | assert(error == 0); 433 | lua_pushinteger(L, pgroup); 434 | return (1); 435 | } 436 | 437 | static int 438 | l_posix_spawnattr_setpgroup(lua_State *L) 439 | { 440 | posix_spawnattr_t *attr; 441 | lua_Integer lpgrp; 442 | pid_t pgrp; 443 | int error; 444 | 445 | attr = luaL_checkudata(L, 1, POSIX_SPAWNATTR_KEY); 446 | lpgrp = luaL_checkinteger(L, 2); 447 | if (lpgrp > INT_MAX || lpgrp < INT_MIN) 448 | return (luaL_error(L, "pgrp too large: %jd", (intmax_t)lpgrp)); 449 | pgrp = (pid_t)lpgrp; 450 | 451 | error = posix_spawnattr_setpgroup(attr, pgrp); 452 | assert(error == 0); 453 | lua_pushboolean(L, 1); 454 | return (1); 455 | } 456 | 457 | static const struct luaL_Reg l_posix_spawntab[] = { 458 | { "posix_spawn", l_posix_spawn }, 459 | { "posix_spawnp", l_posix_spawnp }, 460 | 461 | { "posix_spawn_file_actions_init", 462 | l_posix_spawn_file_actions_init }, 463 | { "posix_spawn_file_actions_addopen", 464 | l_posix_spawn_file_actions_addopen }, 465 | { "posix_spawn_file_actions_adddup2", 466 | l_posix_spawn_file_actions_adddup2 }, 467 | { "posix_spawn_file_actions_addclose", 468 | l_posix_spawn_file_actions_addclose }, 469 | { "posix_spawn_file_actions_addclosefrom_np", 470 | l_posix_spawn_file_actions_addclosefrom_np }, 471 | { "posix_spawn_file_actions_addchdir_np", 472 | l_posix_spawn_file_actions_addchdir_np }, 473 | { "posix_spawn_file_actions_addfchdir_np", 474 | l_posix_spawn_file_actions_addfchdir_np }, 475 | 476 | { "posix_spawnattr_init", l_posix_spawnattr_init }, 477 | { "posix_spawnattr_getflags", l_posix_spawnattr_getflags }, 478 | { "posix_spawnattr_setflags", l_posix_spawnattr_setflags }, 479 | { "posix_spawnattr_getpgroup", l_posix_spawnattr_getpgroup }, 480 | { "posix_spawnattr_setpgroup", l_posix_spawnattr_setpgroup }, 481 | #ifdef notyet 482 | { "posix_spawnattr_getsigdefault", l_posix_spawnattr_getsigdefault }, 483 | { "posix_spawnattr_setsigdefault", l_posix_spawnattr_setsigdefault }, 484 | { "posix_spawnattr_getsigmask", l_posix_spawnattr_getsigmask }, 485 | { "posix_spawnattr_setsigmask", l_posix_spawnattr_setsigmask }, 486 | { "posix_spawnattr_getschedparam", l_posix_spawnattr_getschedparam }, 487 | { "posix_spawnattr_setschedparam", l_posix_spawnattr_setschedparam }, 488 | { "posix_spawnattr_getschedpolicy", l_posix_spawnattr_getschedpolicy }, 489 | { "posix_spawnattr_setschedpolicy", l_posix_spawnattr_setschedpolicy }, 490 | #endif 491 | 492 | { NULL, NULL } 493 | }; 494 | 495 | static int 496 | l_posix_spawn_file_actions_destroy(lua_State *L) 497 | { 498 | posix_spawn_file_actions_t *file_actions; 499 | 500 | file_actions = luaL_checkudata(L, 1, POSIX_SPAWN_FILE_ACTIONS_KEY); 501 | posix_spawn_file_actions_destroy(file_actions); 502 | return (0); 503 | } 504 | 505 | static const struct luaL_Reg l_posix_spawn_file_actions_mt[] = { 506 | { "__gc", l_posix_spawn_file_actions_destroy }, 507 | { NULL, NULL } 508 | }; 509 | 510 | static int 511 | l_posix_spawnattr_destroy(lua_State *L) 512 | { 513 | posix_spawnattr_t *attr; 514 | 515 | attr = luaL_checkudata(L, 1, POSIX_SPAWNATTR_KEY); 516 | posix_spawnattr_destroy(attr); 517 | return (0); 518 | } 519 | 520 | static const struct luaL_Reg l_posix_spawnattr_mt[] = { 521 | { "__gc", l_posix_spawnattr_destroy }, 522 | { NULL, NULL } 523 | }; 524 | 525 | int luaopen_posix_spawn(lua_State *L); 526 | 527 | int 528 | luaopen_posix_spawn(lua_State *L) 529 | { 530 | int ret; 531 | 532 | ret = luaL_newmetatable(L, POSIX_SPAWN_FILE_ACTIONS_KEY); 533 | assert(ret == 1); 534 | luaL_setfuncs(L, l_posix_spawn_file_actions_mt, 0); 535 | ret = luaL_newmetatable(L, POSIX_SPAWNATTR_KEY); 536 | assert(ret == 1); 537 | luaL_setfuncs(L, l_posix_spawnattr_mt, 0); 538 | 539 | lua_newtable(L); 540 | luaL_setfuncs(L, l_posix_spawntab, 0); 541 | #define ADDFLAG(c) \ 542 | lua_pushinteger(L, c); \ 543 | lua_setfield(L, -2, #c) 544 | ADDFLAG(POSIX_SPAWN_RESETIDS); 545 | ADDFLAG(POSIX_SPAWN_SETPGROUP); 546 | ADDFLAG(POSIX_SPAWN_SETSIGDEF); 547 | ADDFLAG(POSIX_SPAWN_SETSIGMASK); 548 | ADDFLAG(POSIX_SPAWN_SETSCHEDPARAM); 549 | ADDFLAG(POSIX_SPAWN_SETSCHEDULER); 550 | #ifdef POSIX_SPAWN_DISABLE_ASLR_NP 551 | ADDFLAG(POSIX_SPAWN_DISABLE_ASLR_NP); 552 | #endif 553 | #undef ADDFLAG 554 | 555 | return (1); 556 | } 557 | -------------------------------------------------------------------------------- /contrib/orch/man/orch.5: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" Copyright (c) 2024 Kyle Evans 3 | .\" 4 | .\" SPDX-License-Identifier: BSD-2-Clause 5 | .\" 6 | .Dd February 10, 2024 7 | .Dt ORCH 5 8 | .Os 9 | .Sh NAME 10 | .Nm orch 11 | .Nd orchestration script file format 12 | .Sh DESCRIPTION 13 | The 14 | .Xr orch 1 15 | program spawns other command-line utilities and drives them via a 16 | .Xr pts 4 , 17 | using scripts referred to as 18 | .Nm 19 | scripts. 20 | .Sh SCRIPT FILES 21 | .Nm 22 | script files are written in Lua with a limited environment available. 23 | Notably, absolutely none of the standard environment is included. 24 | .Nm 25 | scripts are built around constructing 26 | .Fn match 27 | blocks, sometimes in conjunction with multiplexing 28 | .Fn one 29 | blocks. 30 | .Pp 31 | .Fn match 32 | blocks in the same context are executed in the specified order. 33 | When a match is successful, the beginning of the program output buffer is 34 | trimmed up to the character just after the successful match. 35 | Future 36 | .Fn match 37 | blocks are left with the trimmed output buffer to match against. 38 | The default timeout for a match to succeed is 10 seconds, and may be changed 39 | by specifying a 40 | .Ar timeout 41 | parameter in the match configuration. 42 | The timeout may also be changed globally for future match blocks with the 43 | .Fn timeout 44 | function described below. 45 | .Fn match 46 | blocks may also have a 47 | .Ar callback 48 | specified, which itself may contain any number of 49 | .Fn match 50 | or 51 | .Fn one 52 | blocks. 53 | .Pp 54 | .Nm 55 | imports only some parts of the Lua standard library for use in the 56 | .Ar scriptfile , 57 | to minimize the chance of being able to conduct actions that are inherently 58 | incompatible with the queueing model that scripts are executed with. 59 | Currently, only the 60 | .Fn assert 61 | and 62 | .Fn type 63 | functions, and the 64 | .Dq string 65 | and 66 | .Dq table 67 | modules are exposed. 68 | .Pp 69 | The following functions exist for usage within the 70 | .Ar scriptfile : 71 | .Bl -tag -width indent 72 | .It Fn cfg "cfg" 73 | Set configuration for the current process using the 74 | .Fa cfg 75 | table. 76 | The specified 77 | .Fa cfg 78 | is merged into the current configuration. 79 | Currently, the only recognized configuration items are those described for the 80 | .Fn write 81 | function. 82 | .It Fn debug "string" 83 | Writes 84 | .Fa string 85 | out to the stderr of 86 | .Nm , 87 | with 88 | .Dq DEBUG: 89 | prepended and a newline appended to it. 90 | .Pp 91 | This directive is enqueued, not processed immediately unless it's called from 92 | within a failure handler. 93 | .It Fn enqueue "function" 94 | Add 95 | .Fa function 96 | to the queue to be executed in script order. 97 | If 98 | .Fn enqueue 99 | is used in a context that doesn't execute the queue, such as from another 100 | enqueued callback or from a failure context, then it will immediately call the 101 | function instead of enqueueing it. 102 | .It Fn eof "timeout" 103 | Check for eof from the process. 104 | If the process has closed its side because it was killed by signal, then 105 | .Nm 106 | will crash with an assertion. 107 | If 108 | .Fa timeout 109 | is not specified, the default timeout will be used. 110 | .Pp 111 | This directive is enqueued, not processed immediately. 112 | .It Fn exit "status" 113 | Exit with the designed 114 | .Fa status 115 | code. 116 | .Pp 117 | This directive is enqueued, not processed immediately unless it's called from 118 | within a failure handler. 119 | .It Fn fail "function" 120 | Sets up a failure handler. 121 | Pass 122 | .Li nil 123 | to reset the failure handler. 124 | The 125 | .Fa function 126 | will receive one argument, the contents of the buffer, to aide in debugging. 127 | By default, 128 | .Nm 129 | will exit with a status of 1 when a match block fails. 130 | The failure handler prevents the default exit, but may itself call 131 | .Fn exit . 132 | The return value is ignored. 133 | .Pp 134 | This directive is enqueued, not processed immediately. 135 | The 136 | .Fn fail 137 | function is not available as a direct child of a 138 | .Fn one 139 | block. 140 | .It Fn hexdump "string" 141 | Writes an 142 | .Xr hd 1 143 | style hexdump output to stderr of the input 144 | .Fa string . 145 | .Pp 146 | This directive is always processed immediately, and is intended to be used 147 | within a failure context to aide in analysis of why a match failed. 148 | .It Fn log "logfile" 149 | Sets a logfile for subsequent input and output to the process. 150 | The 151 | .Fa logfile 152 | may be a string specifying a filename or an open file (for lib users). 153 | If a filename is specified, then 154 | .Nm 155 | will open it in write-append mode and preserve existing contents. 156 | .It Fn matcher "type" 157 | Changes the default matcher for subsequent match blocks to the type described 158 | by 159 | .Fa type . 160 | The default 161 | .Fa type 162 | is the 163 | .Dq lua 164 | matcher, which interprets match patterns as lua patterns and executes them 165 | accordingly. 166 | Note that Lua string processing still applies for every pattern matcher. 167 | e.g., 168 | .Dq \\\\r 169 | will be interpreted as a carriage return for these non-default pattern matchers. 170 | .Pp 171 | The following matcher options are available: 172 | .Bl -tag -width indent 173 | .It Dq lua 174 | Uses Lua pattern matching to match patterns. 175 | .It Dq plain 176 | Treats the pattern as a plain old string; no characters are special. 177 | .It Dq posix 178 | Treats the pattern as a POSIX extended regular expression. 179 | See 180 | .Xr re_format 7 181 | for more details. 182 | .It Dq default 183 | An alias for the 184 | .Dq lua 185 | matcher. 186 | .El 187 | .It Fn stty "field" "set" "unset" 188 | Change the specified 189 | .Fa field 190 | as described by 191 | .Fa set 192 | and 193 | .Fa unset . 194 | .Fa field 195 | should be one of 196 | .Dq cflag , 197 | .Dq iflag , 198 | .Dq lflag , 199 | .Dq oflag , 200 | or 201 | .Dq cc , 202 | corresponding to the similarly named fields in 203 | .Xr termios 4 . 204 | For the flag fields, the bits in 205 | .Fa set 206 | will be set in the new mask, and the bits in 207 | .Fa unset 208 | will be unset in the new mask. 209 | Either may be 0 or nil to indicate no bits to be set or unset respectively. 210 | The masks for each field may be found in the 211 | .Dq tty 212 | table in the script's global environment. 213 | For instance, ICANON's mask may be referenced as 214 | .Dq tty.lflag.ICANON . 215 | .Pp 216 | For 217 | .Dq cc , 218 | the 219 | .Fa unset 220 | argument is ignored, and 221 | .Fa set 222 | should be a table whose keys correspond to a defined 223 | .Dq V* 224 | constant, and whose values are either the empty string to indicate that the 225 | field should be disabled, an integer for VMIN and VTIME, or a string of the form 226 | .Dq ^X 227 | to indicate ctrl-X. 228 | .Pp 229 | Supported entries may be found in the 230 | .Dq tty 231 | table in the script's global environment. 232 | The 233 | .Dq tty.cc 234 | table's keys correspond to supported characters, e.g., 235 | .Dq tty.cc.VEOF , 236 | and the associated values are all truthy to indicate that they are supported. 237 | .Pp 238 | This directive is enqueued, not processed immediately. 239 | .It Fn raw "boolean" 240 | Changes the raw 241 | .Fn write 242 | state on the process. 243 | .It Fn release 244 | Releases a spawned process for execution. 245 | This is done implicitly when a 246 | .Fn match 247 | block is first encountered. 248 | .Pp 249 | This directive is enqueued, not processed immediately. 250 | .It Fn sleep "duration" 251 | Sleeps for at least the specified 252 | .Fa duration , 253 | in seconds. 254 | Fractional seconds are supported. 255 | As implemented, 256 | .Nm 257 | may delay execution for a little longer than the specified 258 | .Fa duration , 259 | but not for any less time. 260 | .Pp 261 | This directive is enqueued, not processed immediately unless it's called from 262 | within a failure handler. 263 | .It Fn spawn "..." 264 | Spawns a new process. 265 | The arguments to 266 | .Fn spawn 267 | are in the traditional argv style. 268 | They may either be specified directly as arguments to the function, or they may 269 | instead be constructed as a single table. 270 | .Nm 271 | will execute a standard 272 | .Ev PATH 273 | search via 274 | .Xr execvp 3 . 275 | Note that the script's directory is added to 276 | .Ev PATH 277 | before execution begins. 278 | The spawned process will inherit the running environment. 279 | .Pp 280 | If the process cannot be spawned, then 281 | .Nm 282 | will exit. 283 | Note that only one process at a time may be matched against. 284 | If a new process is spawned, then the previous process will be killed and 285 | subsequent matches will be against the new process. 286 | .Pp 287 | This directive is enqueued, not processed immediately. 288 | .It Fn timeout "val" 289 | Adjust the default timeout to 290 | .Fa val 291 | seconds for subsequent 292 | .Fn match 293 | blocks. 294 | The default timeout at script start is 10 seconds. 295 | .Pp 296 | This directive is processed immediately. 297 | .It Fn write "str" "cfg" 298 | Write 299 | .Fa str 300 | to stdin of the spawned process. 301 | If the process is in 302 | .Fn raw 303 | mode, then 304 | .Fn write 305 | will write the entire 306 | .Fn str 307 | out as given. 308 | If the process is not in 309 | .Fn raw 310 | mode, which is the default, then escape sequences and control characters will be 311 | processed. 312 | Note that lua strings are naturally escape-processed in addition to any escaping 313 | done by 314 | .Nm . 315 | For example, if one wants to send a literal 316 | .Dq "^D" 317 | in non-raw mode, then 318 | .\" This isn't not ironic at all. 319 | .Dq "\\\\\\\\^D" 320 | is the correct sequence to do so. 321 | The first backslash escapes the second backslash, then 322 | .Nm 323 | sees just a single backslash preceding the circumflex. 324 | .Pp 325 | This directive is enqueued, not processed immediately. 326 | Execution does not continue to the next command until the 327 | .Fa str 328 | has been completely written. 329 | .Pp 330 | The 331 | .Fa cfg 332 | argument is a table of configuration items for the current send. 333 | The following elements are supported: 334 | .Bl -tag -width indent 335 | .It Va rate 336 | The rate at which to send 337 | .Fa str . 338 | This is specified as a table with, at a minimum, a 339 | .Va bytes 340 | item to describe how many bytes to send in a single batch. 341 | .Nm 342 | also accepts a 343 | .Va delay 344 | item to describe how long to wait in between each batch, in seconds. 345 | As with the 346 | .Fn sleep 347 | function, fractional seconds are supported. 348 | With a 349 | .Va delay 350 | of 0, 351 | .Nm 352 | will still call into 353 | .Fn sleep 354 | with no delay. 355 | With no 356 | .Va delay , 357 | .Nm 358 | will send each batch with no delay in between them. 359 | .El 360 | .Sh BLOCK PRIMITIVES 361 | .Ss Match Blocks 362 | The 363 | .Dq match 364 | blocks are the core primitive of 365 | .Nm 366 | scripts. 367 | Setting them up sounds complicated, but some Lua-supplied sugar actually makes 368 | construction of 369 | .Fn match 370 | blocks relatively elegant. 371 | More on this will be demonstrated in the 372 | .Sx EXAMPLES 373 | section. 374 | .Pp 375 | The 376 | .Fn match 377 | function takes exactly one argument: a pattern to match against. 378 | These patterns are Lua patterns, used without modification to check the output 379 | buffer. 380 | The 381 | .Fn match 382 | returns an anonymous function that may be called again with a table to describe 383 | the properties of the 384 | .Fn match 385 | block. 386 | .Pp 387 | The following properties are available: 388 | .Bl -tag -width indent 389 | .It Va callback 390 | Specifies a function to call if the match succeeds. 391 | The 392 | .Va callback 393 | function may itself construct additional 394 | .Dq match / 395 | .Dq any 396 | blocks, that will then be used for output matching before proceeding after the 397 | successfully matched 398 | .Fn match 399 | block. 400 | .It Va timeout 401 | Overrides the current global timeout. 402 | The 403 | .Va timeout 404 | value is measured in seconds. 405 | .El 406 | .Ss One Blocks 407 | Constructing a 408 | Dq one 409 | block is as simple as calling 410 | .Fn one . 411 | The 412 | .Fn one 413 | function takes a callback as its argument, and this function should setup two or 414 | more 415 | .Fn match 416 | blocks to multiplex between. 417 | The first matching pattern, as specified in script order, will be used and the 418 | rest of the block discarded. 419 | The usual rules of 420 | .Fn match 421 | blocks apply at this point; the callback will be executed, and the callback may 422 | also do further matching. 423 | .Pp 424 | Note that 425 | .Va timeout 426 | likely does work in a 427 | .Fn one 428 | block as you might expect. 429 | .Nm 430 | will effectively wait the full length of the longest timeout for any of the 431 | .Fn match 432 | blocks that it contains. 433 | If some blocks have shorter timeouts than others, then 434 | .Nm 435 | will timeout after the shortest timeout it sees in the block at the time. 436 | If the shorter timeout block still doesn't match, it will be removed from 437 | consideration and we will wait up until the next shortest timeout would have 438 | expired. 439 | That is, a match will not be granted if the matching output comes in after the 440 | timeout would have elapsed, even if we are still waiting on input for other 441 | blocks. 442 | .Sh EXAMPLES 443 | This listing demonstrates the basic features: 444 | .Bd -literal -offset indent 445 | -- Literally spawns a new command: "Hello there", that we will be examining. 446 | spawn("echo", "Hello there") 447 | 448 | -- Sets a new default for subsequent match blocks 449 | timeout(3) 450 | 451 | -- Just matches the initial "Hello", output buffer now contains " there" to 452 | -- match against. 453 | match "Hello" 454 | 455 | -- You're also welcome to do this, if it feels more natural to you: 456 | match("t") 457 | 458 | -- This is effectively ignored since the only match block after it specifies an 459 | -- explicit timeout. If we had another match block after that one, though, then 460 | -- it would use a one second timeout by default. 461 | timeout(1) 462 | 463 | -- This one will fail to match, but we've configured a higher timeout than the 464 | -- global timeout we configured above (one second). 465 | match "Friend" { 466 | timeout = 5, 467 | } 468 | .Ed 469 | .Pp 470 | This block demonstrates bidirectional communication: 471 | .Bd -literal -offset indent 472 | spawn("cat") 473 | 474 | -- The tty we setup is in canonical mode by default, so the trailing \\r is 475 | -- necessary for the spawned process to read it (unless the process turns off 476 | -- canonical mode). 477 | write "Hello there\\r" 478 | 479 | match "Hello" { 480 | callback = function() 481 | debug("Hello matched") 482 | end 483 | } 484 | .Ed 485 | .Pp 486 | This block demonstrates more complex nested match blocks: 487 | .Bd -literal -offset indent 488 | spawn("cat") 489 | 490 | write "Hello world\\r" 491 | 492 | match "Hello" { 493 | callback = function() 494 | -- This will match the world sent above... 495 | match "world" { 496 | callback = function() 497 | -- ... and additionally write "FRIENDS" out 498 | write "FRIENDS\\r" 499 | end 500 | } 501 | end 502 | } 503 | 504 | match "FRIENDS" { 505 | callback = function() 506 | debug "FRIENDS seen!" 507 | end 508 | } 509 | .Ed 510 | .Pp 511 | This block demonstrates one blocks: 512 | .Bd -literal -offset indent 513 | spawn("cat") 514 | 515 | write "One\\r" 516 | 517 | -- These might feel a little bit awkward 518 | one(function() 519 | -- This match block will end up used because it's specified first. 520 | match "ne" { 521 | callback = function() 522 | debug("This one will be called.") 523 | 524 | -- Script execution continues after the one() block that contains 525 | -- this match. 526 | 527 | write "One\\r" 528 | end 529 | } 530 | 531 | -- This match block will effectively be thrown away. 532 | match "One" { 533 | callback = function() 534 | debug("This one will not be called") 535 | end 536 | } 537 | end) 538 | 539 | -- This one will match, because the "ne" block's callback wrote it out. 540 | match "One" 541 | .Ed 542 | .Pp 543 | More examples can be found in 544 | .Pa /usr/share/orch/examples . 545 | .Sh SEE ALSO 546 | .Xr orch 1 , 547 | .Xr pts 4 , 548 | .Xr termios 4 549 | -------------------------------------------------------------------------------- /contrib/orch/lib/orch/scripter.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Copyright (c) 2024 Kyle Evans 3 | -- 4 | -- SPDX-License-Identifier: BSD-2-Clause 5 | -- 6 | 7 | local core = require("orch.core") 8 | 9 | local context = require("orch.context") 10 | local actions = require("orch.actions") 11 | local matchers = require("orch.matchers") 12 | local process = require("orch.process") 13 | local tty = core.tty 14 | local scripter = {env = {}} 15 | 16 | local CTX_QUEUE = 1 17 | local CTX_FAIL = 2 18 | local CTX_CALLBACK = 3 19 | 20 | local current_ctx 21 | 22 | local match_valid_cfg = { 23 | callback = true, 24 | timeout = true, 25 | } 26 | 27 | -- Sometimes a queue, sometimes a stack. Oh well. 28 | local Queue = {} 29 | function Queue:push(item) 30 | self.elements[#self.elements + 1] = item 31 | end 32 | function Queue:back() 33 | return self.elements[#self.elements] 34 | end 35 | function Queue:clear() 36 | self.elements = {} 37 | end 38 | function Queue:remove(elem) 39 | for k, v in ipairs(self.elements) do 40 | if v == elem then 41 | for nk = k + 1, #self.elements do 42 | self.elements[nk - 1] = self.elements[nk] 43 | end 44 | 45 | self.elements[#self.elements] = nil 46 | 47 | return true 48 | end 49 | end 50 | end 51 | function Queue:pop() 52 | local item = self.elements[#self.elements] 53 | self.elements[#self.elements] = nil 54 | return item 55 | end 56 | function Queue:count() 57 | return #self.elements 58 | end 59 | function Queue:empty() 60 | return #self.elements == 0 61 | end 62 | function Queue:each(cb) 63 | for _, v in ipairs(self.elements) do 64 | cb(v) 65 | end 66 | end 67 | function Queue:items() 68 | return self.elements 69 | end 70 | 71 | local MatchContext = setmetatable({}, { __index = Queue }) 72 | function MatchContext:new() 73 | local obj = setmetatable({}, self) 74 | self.__index = self 75 | obj.elements = {} 76 | obj.last_processed = 0 77 | obj.errors = false 78 | return obj 79 | end 80 | function MatchContext:dump(level) 81 | level = level or 1 82 | self:each(function(action) 83 | action:dump(level) 84 | end) 85 | end 86 | function MatchContext:error() 87 | return self.errors 88 | end 89 | function MatchContext:process() 90 | local latest = self.last_processed 91 | local ctx_actions = self:items() 92 | 93 | for idx, action in ipairs(ctx_actions) do 94 | if idx <= latest then 95 | goto skip 96 | end 97 | 98 | self.last_processed = idx 99 | if action.type == "match" then 100 | local ctx_cnt = current_ctx.match_ctx_stack:count() 101 | local current_process = current_ctx.process 102 | 103 | if not current_process then 104 | error("Script did not spawn process prior to matching") 105 | end 106 | 107 | -- Another action in this context could have swapped out the process 108 | -- from underneath us, so pull the buffer at the last possible 109 | -- minute. 110 | if not current_process:match(action) then 111 | self.errors = true 112 | return false 113 | end 114 | 115 | -- Even if this is the last element, doesn't matter; we're finished 116 | -- here. 117 | if current_ctx.match_ctx_stack:count() ~= ctx_cnt then 118 | break 119 | end 120 | elseif not action:execute() then 121 | return false 122 | end 123 | 124 | ::skip:: 125 | end 126 | 127 | return self.last_processed == #ctx_actions 128 | end 129 | function MatchContext:process_one() 130 | local ctx_actions = self:items() 131 | local elapsed = 0 132 | local current_process = current_ctx.process 133 | 134 | if not current_process then 135 | error("Script did not spawn process prior to matching") 136 | end 137 | 138 | -- Return low, high timeout of current batch 139 | local function get_timeout() 140 | local low 141 | 142 | for _, action in ipairs(ctx_actions) do 143 | if action.timeout <= elapsed then 144 | goto skip 145 | end 146 | if low == nil then 147 | low = action.timeout 148 | goto skip 149 | end 150 | 151 | low = math.min(low, action.timeout) 152 | 153 | ::skip:: 154 | end 155 | return low 156 | end 157 | 158 | -- The process can't be swapped out by an immediate descendant of a one() 159 | -- block, but it could be swapped out by a later block. We don't care, 160 | -- though, because we won't need the buffer anymore. 161 | local buffer = current_process.buffer 162 | 163 | local start = core.time() 164 | local matched 165 | 166 | local function match_any() 167 | local elapsed_now = core.time() - start 168 | for _, action in ipairs(ctx_actions) do 169 | if action.timeout >= elapsed_now and buffer:_matches(action) then 170 | matched = true 171 | return true 172 | end 173 | end 174 | 175 | return false 176 | end 177 | 178 | local tlo 179 | 180 | while not matched and not buffer.eof do 181 | -- We recalculate every iteration to rule out any actions that have 182 | -- timed out. Anything with a timeout lower than our current will be 183 | -- ignored for matching. 184 | elapsed = core.time() - start 185 | tlo = get_timeout() 186 | 187 | if tlo == nil then 188 | break 189 | end 190 | 191 | assert(tlo > elapsed) 192 | buffer:refill(match_any, tlo - elapsed) 193 | end 194 | 195 | if not matched then 196 | if not current_ctx:fail(self.action, buffer:contents()) then 197 | self.errors = true 198 | return false 199 | end 200 | end 201 | 202 | return true 203 | end 204 | 205 | local script_ctx = context:new({ 206 | match_ctx_stack = setmetatable({ elements = {} }, { __index = Queue }), 207 | }) 208 | 209 | function script_ctx.match_ctx_stack:dump() 210 | self:each(function(dctx) 211 | dctx:dump() 212 | end) 213 | end 214 | -- Execute a chunk; may either be a callback from a match block, or it may be 215 | -- an entire included file. Either way, each execution gets a new match context 216 | -- that we may or may not use. We'll act upon the latest in the stack no matter 217 | -- what happens. 218 | function script_ctx:execute(func, match_ctx) 219 | local match_ctx_stack = self.match_ctx_stack 220 | local prev_ctx = self.match_ctx 221 | self.match_ctx = match_ctx or MatchContext:new() 222 | 223 | assert(pcall(func)) 224 | 225 | -- If we created a new context for this, we may need to put it on the 226 | -- stack. We'll leave caller-supplied contexts alone. 227 | if not match_ctx then 228 | if not self.match_ctx:empty() then 229 | -- If it defined any queued items, we'll leave it as the 230 | -- currently open match ctx. 231 | match_ctx_stack:push(self.match_ctx) 232 | else 233 | self.match_ctx = match_ctx_stack:back() 234 | end 235 | else 236 | self.match_ctx = prev_ctx 237 | end 238 | end 239 | 240 | function script_ctx:fail(action, buffer) 241 | if self.fail_callback then 242 | local restore_ctx = self:state(CTX_FAIL) 243 | self.fail_callback(buffer) 244 | self:state(restore_ctx) 245 | 246 | return true 247 | else 248 | -- Print diagnostics if we can 249 | if action.print_diagnostics then 250 | action:print_diagnostics() 251 | end 252 | end 253 | 254 | return false 255 | end 256 | function script_ctx:reset() 257 | if self.process then 258 | assert(self.process:close()) 259 | end 260 | 261 | self.process = nil 262 | 263 | self.match_ctx_stack:clear() 264 | self.match_ctx = nil 265 | self._state = CTX_QUEUE 266 | self.timeout = actions.default_timeout 267 | end 268 | function script_ctx:state(new_state) 269 | local prev_state = self._state 270 | self._state = new_state or prev_state 271 | return prev_state 272 | end 273 | 274 | local function include_file(ctx, file, alter_path, env) 275 | local f = assert(core.open(file, alter_path)) 276 | local chunk = f:read("l") 277 | 278 | if not chunk then 279 | error(file .. " appears to be empty!") 280 | end 281 | 282 | if chunk:match("^#!") then 283 | chunk = "" 284 | else 285 | -- line-based read will strip the newline 286 | chunk = chunk .. "\n" 287 | end 288 | 289 | chunk = chunk .. assert(f:read("a")) 290 | local func = assert(load(chunk, "@" .. file, "t", env)) 291 | 292 | return ctx:execute(func) 293 | end 294 | 295 | local function grab_caller(level) 296 | local info = debug.getinfo(level + 1, "Sl") 297 | 298 | return info.short_src, info.currentline 299 | end 300 | 301 | -- Bits available to the sandbox; scripter.env functions are directly exposed, 302 | -- and may be added to via orch.env. 303 | function scripter.env.hexdump(str) 304 | if current_ctx:state() == CTX_QUEUE then 305 | error("hexdump may only be called in a non-queue context") 306 | end 307 | 308 | local output = "" 309 | 310 | local function append(left, right) 311 | if output ~= "" then 312 | output = output .. "\n" 313 | end 314 | 315 | left = string.format("%-50s", left) 316 | output = output .. "DEBUG: " .. left .. "\t|" .. right .. "|" 317 | end 318 | 319 | local lcol, rcol = "", "" 320 | for c = 1, #str do 321 | if (c - 1) % 16 == 0 then 322 | -- Flush output every 16th character 323 | if c ~= 1 then 324 | append(lcol, rcol) 325 | lcol = "" 326 | rcol = "" 327 | end 328 | else 329 | if (c - 1) % 8 == 0 then 330 | lcol = lcol .. " " 331 | else 332 | lcol = lcol .. " " 333 | end 334 | end 335 | 336 | local ch = str:sub(c, c) 337 | local byte = string.byte(ch) 338 | lcol = lcol .. string.format("%.02x", byte) 339 | if byte >= 0x20 and byte < 0x7f then 340 | rcol = rcol .. ch 341 | else 342 | rcol = rcol .. "." 343 | end 344 | end 345 | 346 | if lcol ~= "" then 347 | append(lcol, rcol) 348 | end 349 | 350 | io.stderr:write(output .. "\n") 351 | return true 352 | end 353 | 354 | function scripter.env.matcher(val) 355 | local matcher_obj 356 | 357 | for k, v in pairs(matchers.available) do 358 | if k == val then 359 | matcher_obj = v 360 | break 361 | end 362 | end 363 | 364 | if not matcher_obj then 365 | error("Unknown matcher '" .. val .. "'") 366 | end 367 | 368 | actions.default_matcher = matcher_obj 369 | 370 | return true 371 | end 372 | 373 | function scripter.env.timeout(val) 374 | if val == nil or val < 0 then 375 | error("Timeout must be >= 0") 376 | end 377 | current_ctx.timeout = val 378 | end 379 | 380 | local extra_actions = { 381 | debug = { 382 | allow_direct = true, 383 | init = function(action, args) 384 | action.message = args[1] 385 | end, 386 | execute = function(action) 387 | io.stderr:write("DEBUG: " .. action.message .. "\n") 388 | return true 389 | end, 390 | }, 391 | enqueue = { 392 | allow_direct = true, 393 | init = function(action, args) 394 | action.callback = args[1] 395 | end, 396 | execute = function(action) 397 | local ctx = action.ctx 398 | local restore_ctx = ctx:state(CTX_CALLBACK) 399 | 400 | ctx:execute(action.callback) 401 | 402 | ctx:state(restore_ctx) 403 | return true 404 | end, 405 | }, 406 | exit = { 407 | allow_direct = true, 408 | init = function(action, args) 409 | action.code = args[1] 410 | end, 411 | execute = function(action) 412 | os.exit(action.code) 413 | end, 414 | }, 415 | fail = { 416 | init = function(action, args) 417 | action.callback = args[1] 418 | end, 419 | execute = function(action) 420 | action.ctx.fail_callback = action.callback 421 | return true 422 | end, 423 | }, 424 | match = { 425 | print_diagnostics = function(action) 426 | io.stderr:write(string.format("[%s]:%d: match (pattern '%s') failed\n", 427 | action.src, action.line, action.pattern)) 428 | end, 429 | init = function(action, args) 430 | local pattern = args[1] 431 | 432 | action.pattern = pattern 433 | action.timeout = action.ctx.timeout 434 | 435 | if action.matcher.compile then 436 | action.pattern_obj = action.matcher.compile(pattern) 437 | end 438 | 439 | local function set_cfg(cfg) 440 | for k, v in pairs(cfg) do 441 | if not match_valid_cfg[k] then 442 | error(k .. " is not a valid cfg field") 443 | end 444 | 445 | action[k] = v 446 | end 447 | end 448 | 449 | return set_cfg 450 | end, 451 | }, 452 | one = { 453 | -- This does its own queue management 454 | auto_queue = false, 455 | init = function(action, args) 456 | local func = args[1] 457 | local parent_ctx = action.ctx.match_ctx 458 | 459 | parent_ctx:push(action) 460 | 461 | action.match_ctx = MatchContext:new() 462 | action.match_ctx.process = action.match_ctx.process_one 463 | action.match_ctx.action = action 464 | 465 | -- Now execute it 466 | script_ctx:execute(func, action.match_ctx) 467 | 468 | -- Sanity check the script 469 | for _, chaction in ipairs(action.match_ctx:items()) do 470 | if chaction.type ~= "match" then 471 | error("Type '" .. chaction.type .. "' not legal in a one() block") 472 | end 473 | end 474 | end, 475 | execute = function(action) 476 | action.ctx.match_ctx_stack:push(action.match_ctx) 477 | return false 478 | end, 479 | }, 480 | sleep = { 481 | allow_direct = true, 482 | init = function(action, args) 483 | action.duration = args[1] 484 | end, 485 | execute = function(action) 486 | assert(core.sleep(action.duration)) 487 | return true 488 | end, 489 | }, 490 | } 491 | 492 | -- Valid config options: 493 | -- * alter_path: boolean, add script's directory to $PATH (default: false) 494 | -- * command: argv table to pass to spawn 495 | function scripter.run_script(scriptfile, config) 496 | local done 497 | 498 | script_ctx:reset() 499 | current_ctx = script_ctx 500 | 501 | -- Make a copy of scripter.env at the time of script execution. The 502 | -- environment is effectively immutable from the driver's perspective 503 | -- after execution starts, and we want to avoid a script from corrupting 504 | -- future executions when we eventually support that. 505 | local current_env = {} 506 | for k, v in pairs(scripter.env) do 507 | current_env[k] = v 508 | end 509 | 510 | local function generate_handler(name, def) 511 | return function(...) 512 | local action = actions.MatchAction:new(name, def.execute) 513 | local args = { ... } 514 | local ret, state 515 | 516 | action.ctx = current_ctx 517 | action.src, action.line = grab_caller(2) 518 | 519 | action.print_diagnostics = def.print_diagnostics 520 | 521 | if def.init then 522 | -- We preserve the return value of init() in case 523 | -- the action wanted to, e.g., return a callback 524 | -- for some good old fashion chaining like with 525 | -- match "foo" { config }. 526 | ret = def.init(action, args) 527 | end 528 | 529 | state = current_ctx:state() 530 | if state ~= CTX_QUEUE then 531 | if not def.allow_direct then 532 | error(name .. " may not be called in a direct context") 533 | end 534 | 535 | return action:execute() 536 | end 537 | 538 | -- Defaults to true if unset. 539 | local auto_queue = def.auto_queue 540 | if auto_queue == nil then 541 | auto_queue = true 542 | end 543 | 544 | if auto_queue then 545 | current_ctx.match_ctx:push(action) 546 | end 547 | return ret or true 548 | end 549 | end 550 | for name, def in pairs(actions.defined) do 551 | current_env[name] = generate_handler(name, def) 552 | end 553 | for name, def in pairs(extra_actions) do 554 | current_env[name] = generate_handler(name, def) 555 | end 556 | 557 | -- Note that the orch(1) driver will setup alter_path == true; scripts 558 | -- importing orch.lua are expected to be more explicit. 559 | include_file(script_ctx, scriptfile, config and config.alter_path, current_env) 560 | --current_ctx.match_ctx_stack:dump() 561 | 562 | if config and config.command then 563 | current_ctx.process = process:new(config.command, current_ctx) 564 | end 565 | 566 | if current_ctx.match_ctx_stack:empty() then 567 | error("script did not define any actions") 568 | end 569 | 570 | -- To run the script, we'll grab the back of the context stack and process 571 | -- that. 572 | while not done do 573 | local run_ctx = current_ctx.match_ctx_stack:back() 574 | 575 | if run_ctx:process() then 576 | current_ctx.match_ctx_stack:remove(run_ctx) 577 | done = current_ctx.match_ctx_stack:empty() 578 | elseif run_ctx:error() then 579 | return false 580 | end 581 | end 582 | 583 | return true 584 | end 585 | 586 | -- Inherited from our environment 587 | scripter.env.assert = assert 588 | scripter.env.string = string 589 | scripter.env.table = table 590 | scripter.env.tty = tty 591 | scripter.env.type = type 592 | 593 | function scripter.reset() 594 | script_ctx:reset() 595 | end 596 | 597 | return scripter 598 | --------------------------------------------------------------------------------