From cf714ed591811aa308e5ccbb29e9d887c8cdadb8 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Tue, 1 Apr 2025 18:52:23 -0500 Subject: [PATCH 01/21] Notify user when script is installed during a bundle installation. Lets user know what to add to PATH. --- src/boot/boot.janet | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 1a80b4b5..30881a9f 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4314,6 +4314,9 @@ (when check (do-hook module bundle-name :check man))) (print "installed " bundle-name) + (when (get man :has-bin-script) + (def binpath (string (dyn *syspath*) s "bin")) + (eprintf "executable scripts have been installed to %s" binpath)) bundle-name) (defn- bundle/pack @@ -4447,6 +4450,7 @@ (default dest (last (string/split s src))) (default chmod-mode 8r755) (os/mkdir (string (dyn *syspath*) s "bin")) + (put manifest :has-bin-script true) (bundle/add-file manifest src (string "bin" s dest) chmod-mode)) (defn bundle/update-all From 31920e574db3e4b71544589343c69613fa0ae4f1 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Tue, 1 Apr 2025 19:47:03 -0500 Subject: [PATCH 02/21] Add explicit Alpine Linux directions. Statically linking Janet is quite useful, especially for users who want to use jpm and janet-pm to build standalone binaries. --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 5a37618d..72c25d40 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,21 @@ make install-jpm-git Find out more about the available make targets by running `make help`. +### Alpine Linux + +To build a statically-linked build of Janet, Alpine Linux + MUSL is a good combination. Janet can also +be built inside a docker container or similar in this manner. + +```sh +docker run -it --rm alpine /bin/ash +$ apk add make gcc musl-dev git +$ git clone https://github.com/janet-lang/janet.git +$ cd janet +$ make -j10 +$ make test +$ make install +``` + ### 32-bit Haiku 32-bit Haiku build instructions are the same as the UNIX-like build instructions, From 363e32d4557c939c8dbcb069a0a7578b5b594daf Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Thu, 3 Apr 2025 20:52:40 -0500 Subject: [PATCH 03/21] Adopt docstring from #1574 --- src/boot/boot.janet | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 30881a9f..7e9543d7 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1084,8 +1084,14 @@ (map-aggregator ,maptype ,res (,f x ;call-buffer))))))) (defn map - `Map a function over every value in a data structure and - return an array of the results.` + ``` + Map a function `f` over every value in a data structure `ind` + and return an array of results, but only if no `inds` are + provided. Multiple data structures can be handled if each + `inds` is a data structure and `f` is a function of arity + one more than the number of `inds`. The resulting array has + a length that is the shortest of `ind` and each of `inds`. + ``` [f ind & inds] (def res @[]) (map-template :map res f ind inds) From d2ee4aa0744c13f74ba6691477a8396dbcae5ac5 Mon Sep 17 00:00:00 2001 From: sogaiu <983021772@users.noreply.github.com> Date: Fri, 4 Apr 2025 20:22:10 +0900 Subject: [PATCH 04/21] Remove unix domain socket after test --- test/suite-ev.janet | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/suite-ev.janet b/test/suite-ev.janet index cf040e2c..af8a6fff 100644 --- a/test/suite-ev.janet +++ b/test/suite-ev.janet @@ -501,8 +501,10 @@ # soreuseport on unix domain sockets (compwhen (or (= :macos (os/which)) (= :linux (os/which))) (assert-no-error "unix-domain socket reuseaddr" - (let [s (net/listen :unix "./unix-domain-socket" :stream)] - (:close s)))) + (let [uds-path "./unix-domain-socket"] + (defer (os/rm uds-path) + (let [s (net/listen :unix uds-path :stream)] + (:close s)))))) # net/accept-loop level triggering (gccollect) From e2cc8f2965a72a73cb4f8f16bfde1445c6514466 Mon Sep 17 00:00:00 2001 From: sogaiu <983021772@users.noreply.github.com> Date: Fri, 4 Apr 2025 21:56:31 +0900 Subject: [PATCH 05/21] Tweak doc for mapcat, count, keep, all, some --- src/boot/boot.janet | 59 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 7e9543d7..34a4a973 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1098,8 +1098,14 @@ res) (defn mapcat - ``Map a function over every element in an array or tuple and - use `array/concat` to concatenate the results.`` + ``` + Map a function `f` over every value in a data structure `ind` + and use `array/concat` to concatenate the results, but only if + no `inds` are provided. Multiple data structures can be handled + if each `inds` is a data structure and `f` is a function of + arity one more than the number of `inds`. The resulting array + has a length that is the shortest of `ind` and each of `inds`. + ``` [f ind & inds] (def res @[]) (map-template :mapcat res f ind inds) @@ -1116,18 +1122,30 @@ res) (defn count - ``Count the number of items in `ind` for which `(pred item)` - is true.`` + ``` + Count the number of values in a data structure `ind` for which + applying `pred` yields a truthy value, but only if no `inds` are + provided. Multiple data structures can be handled if each `inds` + is a data structure and `pred` is a function of arity one more + than the number of `inds`. Note that `pred` is only applied to + values at indeces up to the largest index of the shortest of + `ind` and each of `inds`. + ``` [pred ind & inds] (var res 0) (map-template :count res pred ind inds) res) (defn keep - ``Given a predicate `pred`, return a new array containing the truthy results - of applying `pred` to each element in the indexed collection `ind`. This is - different from `filter` which returns an array of the original elements where - the predicate is truthy.`` + ``` + Given a predicate `pred`, return a new array containing the + truthy results of applying `pred` to each value in the data + structure `ind`, but only if no `inds` are provided. Multiple + data structures can be handled if each `inds` is a data + structure and `pred` is a function of arity one more than the + number of `inds`. The resulting array has a length that is the + shortest of `ind` and each of `inds`. + ``` [pred ind & inds] (def res @[]) (map-template :keep res pred ind inds) @@ -2209,17 +2227,32 @@ ret) (defn all - ``Returns true if `(pred item)` is truthy for every item in `ind`. - Otherwise, returns the first falsey result encountered. - Returns true if `ind` is empty.`` + ``` + Returns true if applying `pred` to every value in a data + structure `ind` results in only truthy values, but only if no + `inds` are provided. Multiple data structures can be handled + if each `inds` is a data structure and `pred` is a function + of arity one more than the number of `inds`. Returns the first + falsey result encountered. Note that `pred` is only called as + many times as the length of the shortest of `ind` and each of + `inds`. If `ind` or any of `inds` are empty, returns true. + ``` [pred ind & inds] (var res true) (map-template :all res pred ind inds) res) (defn some - ``Returns nil if `(pred item)` is false or nil for every item in `ind`. - Otherwise, returns the first truthy result encountered.`` + ``` + Returns nil if applying `pred` to every value in a data + structure `ind` results in only falsey values, but only if no + `inds` are provided. Multiple data structures can be handled + if each `inds` is a data structure and `pred` is a function + of arity one more than the number of `inds`. Returns the first + truthy result encountered. Note that `pred` is only called as + many times as the length of the shortest of `ind` and each of + `inds`. If `ind` or any of `inds` are empty, returns nil. + ``` [pred ind & inds] (var res nil) (map-template :some res pred ind inds) From a9ff8b388ff12841883f51843b6cebf696036243 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 5 Apr 2025 16:20:17 -0500 Subject: [PATCH 06/21] Add os/getpid --- src/boot/boot.janet | 2 +- src/core/os.c | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index 34a4a973..b49c693f 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -4466,7 +4466,7 @@ (defn bundle/add "Add files and directories during a bundle install relative to `(dyn *syspath*)`. - Added paths will be recorded in the bundle manifest such that they are properly tracked + Added files and directories will be recorded in the bundle manifest such that they are properly tracked and removed during an upgrade or uninstall." [manifest src &opt dest chmod-mode] (default dest src) diff --git a/src/core/os.c b/src/core/os.c index fe1184e9..169a30f5 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -813,6 +813,18 @@ JANET_CORE_FN(os_proc_close, #endif } +JANET_CORE_FN(os_proc_getpid, + "(os/getpid)", + "Get the process ID of the current process.") { + janet_fixarity(argc, 0); + (void) argv; +#ifdef JANET_WINDOWS + return janet_wrap_number((double) _getpid()); +#else + return janet_wrap_number((double) getpid()); +#endif +} + static void swap_handles(JanetHandle *handles) { JanetHandle temp = handles[0]; handles[0] = handles[1]; @@ -2797,6 +2809,7 @@ void janet_lib_os(JanetTable *env) { JANET_CORE_REG("os/proc-wait", os_proc_wait), JANET_CORE_REG("os/proc-kill", os_proc_kill), JANET_CORE_REG("os/proc-close", os_proc_close), + JANET_CORE_REG("os/getpid", os_proc_getpid), #endif /* high resolution timers */ From bdab93c999f302a5b6954cbe229e29061fb3ab50 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 5 Apr 2025 17:30:46 -0500 Subject: [PATCH 07/21] Add `{:err :out}` option to os/spawn. This special case in the os/spawn interface allows easily redirecting both stderr and stdout to the same pipe. --- CHANGELOG.md | 2 ++ src/core/os.c | 11 ++++++++++- test/suite-ev.janet | 16 ++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da897093..0417c142 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? - Make `ffi/write` append to a buffer instead of insert at 0 by default. +- Add `os/getpid` to get the current process id. +- Add `:out` option to `os/spawn` to be able to redirect stderr to stdout with pipes. ## 1.38.0 - 2025-03-18 - Add `bundle/replace` diff --git a/src/core/os.c b/src/core/os.c index 169a30f5..1ef06860 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -816,6 +816,7 @@ JANET_CORE_FN(os_proc_close, JANET_CORE_FN(os_proc_getpid, "(os/getpid)", "Get the process ID of the current process.") { + janet_sandbox_assert(JANET_SANDBOX_SUBPROCESS); janet_fixarity(argc, 0); (void) argv; #ifdef JANET_WINDOWS @@ -1149,6 +1150,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { JanetAbstract orig_in = NULL, orig_out = NULL, orig_err = NULL; JanetHandle new_in = JANET_HANDLE_NONE, new_out = JANET_HANDLE_NONE, new_err = JANET_HANDLE_NONE; JanetHandle pipe_in = JANET_HANDLE_NONE, pipe_out = JANET_HANDLE_NONE, pipe_err = JANET_HANDLE_NONE; + int stderr_is_stdout = 0; int pipe_errflag = 0; /* Track errors setting up pipes */ int pipe_owner_flags = (is_spawn && (flags & 0x8)) ? JANET_PROC_ALLOW_ZOMBIE : 0; @@ -1173,6 +1175,8 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { if (is_spawn && janet_keyeq(maybe_stderr, "pipe")) { new_err = make_pipes(&pipe_err, 0, &pipe_errflag); pipe_owner_flags |= JANET_PROC_OWNS_STDERR; + } else if (is_spawn && janet_keyeq(maybe_stderr, "out")) { + stderr_is_stdout = 1; } else if (!janet_checktype(maybe_stderr, JANET_NIL)) { new_err = janet_getjstream(&maybe_stderr, 0, &orig_err); } @@ -1230,6 +1234,8 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { startupInfo.hStdError = pipe_err; } else if (new_err != NULL) { startupInfo.hStdError = new_err; + } else if (stderr_is_stdout) { + startupInfo.hStdError = startupInfo.hStdOut; } else { startupInfo.hStdError = (HANDLE) _get_osfhandle(2); } @@ -1321,6 +1327,8 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { } else if (new_err != JANET_HANDLE_NONE && new_err != 2) { posix_spawn_file_actions_adddup2(&actions, new_err, 2); posix_spawn_file_actions_addclose(&actions, new_err); + } else if (stderr_is_stdout) { + posix_spawn_file_actions_adddup2(&actions, 1, 2); } pid_t pid; @@ -1426,7 +1434,8 @@ JANET_CORE_FN(os_spawn, "`:pipe` may fail if there are too many active file descriptors. The caller is " "responsible for closing pipes created by `:pipe` (either individually or using " "`os/proc-close`). Similar to `os/execute`, the caller is responsible for ensuring " - "pipes do not cause the program to block and deadlock.") { + "pipes do not cause the program to block and deadlock. As a special case, the stream passed to `:err` " + "can be the keyword `:out` to redirect stderr to stdout in the subprocess.") { return os_execute_impl(argc, argv, JANET_EXECUTE_SPAWN); } diff --git a/test/suite-ev.janet b/test/suite-ev.janet index af8a6fff..cdd43ee8 100644 --- a/test/suite-ev.janet +++ b/test/suite-ev.janet @@ -556,4 +556,20 @@ (ev/deadline 0.01 nil f true) (assert-error "deadline expired" (resume f))) +# Use :err :stdout +(def- subproc-code '(do (eprint "hi") (eflush) (print "there") (flush))) +(defn ev/slurp + [f &opt buf] + (default buf @"") + (if (ev/read f 0x10000 buf) + (ev/slurp f buf) + buf)) +(def p (os/spawn [;run janet "-e" (string/format "%j" subproc-code)] :px {:out :pipe :err :out})) +(def [exit-code data] + (ev/gather + (os/proc-wait p) + (ev/slurp (p :out)))) +(assert (zero? exit-code) "subprocess ran") +(assert (deep= data @"hi\nthere\n") "output is correct") + (end-suite) From 2fedb67cb3a29fcc0619e75d21a4bab6dacd6e95 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 5 Apr 2025 17:32:07 -0500 Subject: [PATCH 08/21] formatting --- src/core/os.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/os.c b/src/core/os.c index 1ef06860..b2387979 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -814,8 +814,8 @@ JANET_CORE_FN(os_proc_close, } JANET_CORE_FN(os_proc_getpid, - "(os/getpid)", - "Get the process ID of the current process.") { + "(os/getpid)", + "Get the process ID of the current process.") { janet_sandbox_assert(JANET_SANDBOX_SUBPROCESS); janet_fixarity(argc, 0); (void) argv; From e8187fdee5ebb8ae8b7aae231649fc64e052926c Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 5 Apr 2025 20:26:57 -0500 Subject: [PATCH 09/21] Fix windows build --- src/core/os.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/os.c b/src/core/os.c index b2387979..dbb3695c 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1235,7 +1235,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { } else if (new_err != NULL) { startupInfo.hStdError = new_err; } else if (stderr_is_stdout) { - startupInfo.hStdError = startupInfo.hStdOut; + startupInfo.hStdError = startupInfo.hStdOutput; } else { startupInfo.hStdError = (HANDLE) _get_osfhandle(2); } From 38e841fc5cb48a9239ac24362e82ed88734c8fdf Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 5 Apr 2025 20:52:47 -0500 Subject: [PATCH 10/21] Get rid of test error. --- test/suite-ev.janet | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/test/suite-ev.janet b/test/suite-ev.janet index cdd43ee8..b2d294a6 100644 --- a/test/suite-ev.janet +++ b/test/suite-ev.janet @@ -430,13 +430,7 @@ # Now do our telnet chat (def bob (assert (net/connect test-host test-port :stream))) (expect-read bob "Whats your name?\n") -(if (= :mingw (os/which)) - (net/write bob "bob") - (do - (def fbob (ev/to-file bob)) - (file/write fbob "bob") - (file/flush fbob) - (:close fbob))) +(net/write bob "bob") (expect-read bob "Welcome bob\n") (def alice (assert (net/connect test-host test-port))) (expect-read alice "Whats your name?\n") @@ -569,7 +563,8 @@ (ev/gather (os/proc-wait p) (ev/slurp (p :out)))) +(def data (string/replace-all "\r" "" data)) (assert (zero? exit-code) "subprocess ran") -(assert (deep= data @"hi\nthere\n") "output is correct") +(assert (= data "hi\nthere\n") "output is correct") (end-suite) From 332f123abe71d333209bc5e01197566ec8417a07 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 5 Apr 2025 20:59:45 -0500 Subject: [PATCH 11/21] Replace if not errorlevel 0 with if errorlevel 1 The former is just bad syntax. --- build_win.bat | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/build_win.bat b/build_win.bat index 03829667..5a0da53e 100644 --- a/build_win.bat +++ b/build_win.bat @@ -41,34 +41,34 @@ if not exist build\boot mkdir build\boot @rem Build the bootstrap interpreter for %%f in (src\core\*.c) do ( %JANET_COMPILE% /DJANET_BOOTSTRAP /Fobuild\boot\%%~nf.obj %%f - @if not errorlevel 0 goto :BUILDFAIL + @if errorlevel 1 goto :BUILDFAIL ) for %%f in (src\boot\*.c) do ( %JANET_COMPILE% /DJANET_BOOTSTRAP /Fobuild\boot\%%~nf.obj %%f - @if not errorlevel 0 goto :BUILDFAIL + @if errorlevel 1 goto :BUILDFAIL ) %JANET_LINK% /out:build\janet_boot.exe build\boot\*.obj -@if not errorlevel 0 goto :BUILDFAIL +@if errorlevel 1 goto :BUILDFAIL build\janet_boot . > build\c\janet.c -@if not errorlevel 0 goto :BUILDFAIL +@if errorlevel 1 goto :BUILDFAIL @rem Build the sources %JANET_COMPILE% /Fobuild\janet.obj build\c\janet.c -@if not errorlevel 0 goto :BUILDFAIL +@if errorlevel 1 goto :BUILDFAIL %JANET_COMPILE% /Fobuild\shell.obj src\mainclient\shell.c -@if not errorlevel 0 goto :BUILDFAIL +@if errorlevel 1 goto :BUILDFAIL @rem Build the resources rc /nologo /fobuild\janet_win.res janet_win.rc -@if not errorlevel 0 goto :BUILDFAIL +@if errorlevel 1 goto :BUILDFAIL @rem Link everything to main client %JANET_LINK% /out:janet.exe build\janet.obj build\shell.obj build\janet_win.res -@if not errorlevel 0 goto :BUILDFAIL +@if errorlevel 1 goto :BUILDFAIL @rem Build static library (libjanet.lib) %JANET_LINK_STATIC% /out:build\libjanet.lib build\janet.obj -@if not errorlevel 0 goto :BUILDFAIL +@if errorlevel 1 goto :BUILDFAIL echo === Successfully built janet.exe for Windows === echo === Run 'build_win test' to run tests. == @@ -102,7 +102,7 @@ exit /b 0 :TEST for %%f in (test/suite*.janet) do ( janet.exe test\%%f - @if not errorlevel 0 goto TESTFAIL + @if errorlevel 1 goto TESTFAIL ) exit /b 0 From d493eaf48504972ccaeef55316e5ee498b5bc258 Mon Sep 17 00:00:00 2001 From: sogaiu <983021772@users.noreply.github.com> Date: Sun, 6 Apr 2025 20:48:52 +0900 Subject: [PATCH 12/21] Fix mapcat and keep docstrings --- src/boot/boot.janet | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/boot/boot.janet b/src/boot/boot.janet index b49c693f..ff2419da 100644 --- a/src/boot/boot.janet +++ b/src/boot/boot.janet @@ -1103,8 +1103,9 @@ and use `array/concat` to concatenate the results, but only if no `inds` are provided. Multiple data structures can be handled if each `inds` is a data structure and `f` is a function of - arity one more than the number of `inds`. The resulting array - has a length that is the shortest of `ind` and each of `inds`. + arity one more than the number of `inds`. Note that `f` is only + applied to values at indeces up to the largest index of the + shortest of `ind` and each of `inds`. ``` [f ind & inds] (def res @[]) @@ -1143,8 +1144,8 @@ structure `ind`, but only if no `inds` are provided. Multiple data structures can be handled if each `inds` is a data structure and `pred` is a function of arity one more than the - number of `inds`. The resulting array has a length that is the - shortest of `ind` and each of `inds`. + number of `inds`. The resulting array has a length that is no + longer than the shortest of `ind` and each of `inds`. ``` [pred ind & inds] (def res @[]) From 3edc4f35b2bc456354875790ea12b2eb59025ee3 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 6 Apr 2025 14:46:23 -0500 Subject: [PATCH 13/21] Add `:cd` argument to os/execute and os/spawn. --- src/core/os.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/core/os.c b/src/core/os.c index dbb3695c..a8871a85 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1150,6 +1150,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { JanetAbstract orig_in = NULL, orig_out = NULL, orig_err = NULL; JanetHandle new_in = JANET_HANDLE_NONE, new_out = JANET_HANDLE_NONE, new_err = JANET_HANDLE_NONE; JanetHandle pipe_in = JANET_HANDLE_NONE, pipe_out = JANET_HANDLE_NONE, pipe_err = JANET_HANDLE_NONE; + const char *chdir_path = NULL; int stderr_is_stdout = 0; int pipe_errflag = 0; /* Track errors setting up pipes */ int pipe_owner_flags = (is_spawn && (flags & 0x8)) ? JANET_PROC_ALLOW_ZOMBIE : 0; @@ -1182,6 +1183,17 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { } } + /* Optional working directory. Available for both os/execute and os/spawn. */ + if (argc > 2) { + JanetDictView tab = janet_getdictionary(argv, 2); + Janet workdir = janet_dictionary_get(tab.kvs, tab.cap, janet_ckeywordv("cd")); + if (janet_checktype(workdir, JANET_STRING)) { + chdir_path = (const char *) janet_unwrap_string(workdir); + } else if (!janet_checktype(workdir, JANET_NIL)) { + janet_panicf("expected string for `:cd` argumnet, got %v", workdir); + } + } + /* Clean up if any of the pipes have any issues */ if (pipe_errflag) { if (pipe_in != JANET_HANDLE_NONE) close_handle(pipe_in); @@ -1212,6 +1224,10 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { } const char *path = (const char *) janet_unwrap_string(exargs.items[0]); + if (chdir_path != NULL) { + startupInfo.lpCurrentDirectory = chdir_path; + } + /* Do IO redirection */ if (pipe_in != JANET_HANDLE_NONE) { @@ -1305,6 +1321,9 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { /* Posix spawn setup */ posix_spawn_file_actions_t actions; posix_spawn_file_actions_init(&actions); + if (chdir_path != NULL) { + posix_spawn_file_actions_addchdir_np(&actions, chdir_path); + } if (pipe_in != JANET_HANDLE_NONE) { posix_spawn_file_actions_adddup2(&actions, pipe_in, 0); posix_spawn_file_actions_addclose(&actions, pipe_in); From 34629ae3141e06b0d0c790d66008d91bc7eaaa09 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 6 Apr 2025 14:49:54 -0500 Subject: [PATCH 14/21] Fix signature on windows. --- src/core/os.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/os.c b/src/core/os.c index a8871a85..cf76bf0c 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1208,6 +1208,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { SECURITY_ATTRIBUTES saAttr; PROCESS_INFORMATION processInfo; STARTUPINFO startupInfo; + LPCSTR lpCurrentDirectory = NULL; memset(&saAttr, 0, sizeof(saAttr)); memset(&processInfo, 0, sizeof(processInfo)); memset(&startupInfo, 0, sizeof(startupInfo)); @@ -1225,7 +1226,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { const char *path = (const char *) janet_unwrap_string(exargs.items[0]); if (chdir_path != NULL) { - startupInfo.lpCurrentDirectory = chdir_path; + lpCurrentDirectory = chdir_path; } /* Do IO redirection */ @@ -1264,7 +1265,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { TRUE, /* handle inheritance */ 0, /* flags */ use_environ ? NULL : envp, /* pass in environment */ - NULL, /* use parents starting directory */ + lpCurrentDirectory, &startupInfo, &processInfo)) { cp_failed = 1; From 1ba077c87def48c55b73b6f5c854730f36a8a297 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 6 Apr 2025 16:48:32 -0500 Subject: [PATCH 15/21] Add ifdefs for various platforms to check for the support of addchdir. This will need to be expanded but should support most modern systems. --- src/core/os.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/core/os.c b/src/core/os.c index cf76bf0c..a4b2e99d 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -55,6 +55,7 @@ #include #include #include +#define JANET_SPAWN_CHDIR #else #include #include @@ -73,6 +74,14 @@ extern char **environ; #endif #endif +/* Detect availability of posix_spawn_file_actions_addchdir_np. Since + * this doesn't seem to follow any standard, just a common extension, we + * must enumerate supported systems for availability. */ +#ifdef __GLIBC__ +#define JANET_SPAWN_CHDIR +#endif + + /* Not POSIX, but all Unixes but Solaris have this function. */ #if defined(JANET_POSIX) && !defined(__sun) time_t timegm(struct tm *tm); @@ -1150,7 +1159,6 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { JanetAbstract orig_in = NULL, orig_out = NULL, orig_err = NULL; JanetHandle new_in = JANET_HANDLE_NONE, new_out = JANET_HANDLE_NONE, new_err = JANET_HANDLE_NONE; JanetHandle pipe_in = JANET_HANDLE_NONE, pipe_out = JANET_HANDLE_NONE, pipe_err = JANET_HANDLE_NONE; - const char *chdir_path = NULL; int stderr_is_stdout = 0; int pipe_errflag = 0; /* Track errors setting up pipes */ int pipe_owner_flags = (is_spawn && (flags & 0x8)) ? JANET_PROC_ALLOW_ZOMBIE : 0; @@ -1184,13 +1192,17 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { } /* Optional working directory. Available for both os/execute and os/spawn. */ + const char *chdir_path = NULL; if (argc > 2) { JanetDictView tab = janet_getdictionary(argv, 2); Janet workdir = janet_dictionary_get(tab.kvs, tab.cap, janet_ckeywordv("cd")); if (janet_checktype(workdir, JANET_STRING)) { chdir_path = (const char *) janet_unwrap_string(workdir); +#ifndef JANET_SPAWN_CHDIR + janet_panicf(":cd argument not supported on this system - %s", chdir_path); +#endif } else if (!janet_checktype(workdir, JANET_NIL)) { - janet_panicf("expected string for `:cd` argumnet, got %v", workdir); + janet_panicf("expected string for :cd argumnet, got %v", workdir); } } @@ -1322,9 +1334,15 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { /* Posix spawn setup */ posix_spawn_file_actions_t actions; posix_spawn_file_actions_init(&actions); +#ifdef JANET_SPAWN_CHDIR if (chdir_path != NULL) { +#ifdef JANET_SPAWN_CHDIR_NO_NP + posix_spawn_file_actions_addchdir(&actions, chdir_path); +#else posix_spawn_file_actions_addchdir_np(&actions, chdir_path); +#endif } +#endif if (pipe_in != JANET_HANDLE_NONE) { posix_spawn_file_actions_adddup2(&actions, pipe_in, 0); posix_spawn_file_actions_addclose(&actions, pipe_in); From 90a33bc88a5a50d45eed20d5f0705502b9f9255c Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 6 Apr 2025 16:55:22 -0500 Subject: [PATCH 16/21] Add some systems that support addchdir. --- src/core/os.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/core/os.c b/src/core/os.c index a4b2e99d..04836717 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -76,11 +76,17 @@ extern char **environ; /* Detect availability of posix_spawn_file_actions_addchdir_np. Since * this doesn't seem to follow any standard, just a common extension, we - * must enumerate supported systems for availability. */ + * must enumerate supported systems for availability. Define JANET_SPAWN_NO_CHDIR + * to disable this. */ +#ifndef JANET_SPAWN_NO_CHDIR #ifdef __GLIBC__ #define JANET_SPAWN_CHDIR +#elif defined(JANET_APPLE) /* Some older versions may not work here. */ +#define JANET_SPAWN_CHDIR +#elif defined(__FreeBSD__) /* Not all BSDs work, for example openBSD doesn't seem to support this */ +#define JANET_SPAWN_CHDIR +#endif #endif - /* Not POSIX, but all Unixes but Solaris have this function. */ #if defined(JANET_POSIX) && !defined(__sun) From 5705b2f6c7891142e177c0e23713e751b22b02ac Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Wed, 9 Apr 2025 10:00:14 +0900 Subject: [PATCH 17/21] Fix absence of pthread_cancel in Android --- src/core/ev.c | 21 +++++++++++++++++++++ src/include/janet.h | 5 +++++ 2 files changed, 26 insertions(+) diff --git a/src/core/ev.c b/src/core/ev.c index c6e99d6b..8f97b8ee 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -652,6 +652,12 @@ static VOID CALLBACK janet_timeout_stop(ULONG_PTR ptr) { UNREFERENCED_PARAMETER(ptr); ExitThread(0); } +#elif JANET_ANDROID +static void janet_timeout_stop(int sig_num) { + if(sig_num == SIGUSR1) { + pthread_exit(0); + } +} #endif static void janet_timeout_cb(JanetEVGenericMessage msg) { @@ -673,6 +679,14 @@ static DWORD WINAPI janet_timeout_body(LPVOID ptr) { } #else static void *janet_timeout_body(void *ptr) { +#ifdef JANET_ANDROID + struct sigaction action; + memset(&action, 0, sizeof(action)); + sigemptyset(&action.sa_mask); + action.sa_flags = 0; + action.sa_handler = &janet_timeout_stop; + sigaction(SIGUSR1, &action, NULL); +#endif JanetThreadedTimeout tto = *(JanetThreadedTimeout *)ptr; janet_free(ptr); struct timespec ts; @@ -1489,8 +1503,12 @@ JanetFiber *janet_loop1(void) { QueueUserAPC(janet_timeout_stop, to.worker, 0); WaitForSingleObject(to.worker, INFINITE); CloseHandle(to.worker); +#else +#ifdef JANET_ANDROID + pthread_kill(to.worker, SIGUSR1); #else pthread_cancel(to.worker); +#endif void *res; pthread_join(to.worker, &res); #endif @@ -3188,6 +3206,9 @@ JANET_CORE_FN(cfun_ev_deadline, to.is_error = 0; to.sched_id = to.fiber->sched_id; if (use_interrupt) { +#ifdef JANET_ANDROID + janet_sandbox_assert(JANET_SANDBOX_SIGNAL); +#endif JanetThreadedTimeout *tto = janet_malloc(sizeof(JanetThreadedTimeout)); if (NULL == tto) { JANET_OUT_OF_MEMORY; diff --git a/src/include/janet.h b/src/include/janet.h index 6aa912df..1954af7b 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -67,6 +67,11 @@ extern "C" { #define JANET_LINUX 1 #endif +/* Check for Android */ +#ifdef __ANDROID__ +#define JANET_ANDROID 1 +#endif + /* Check for Cygwin */ #if defined(__CYGWIN__) #define JANET_CYGWIN 1 From 6254fffad08d61b0d1fa7ac1e295eb161c1d45db Mon Sep 17 00:00:00 2001 From: Michael Camilleri Date: Wed, 9 Apr 2025 10:38:59 +0900 Subject: [PATCH 18/21] Update changelog to include change to `ev/deadline` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0417c142..d55b9d41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. - Make `ffi/write` append to a buffer instead of insert at 0 by default. - Add `os/getpid` to get the current process id. - Add `:out` option to `os/spawn` to be able to redirect stderr to stdout with pipes. + Add `interrupt?` argument to `ev/deadline` to use VM interruptions. ## 1.38.0 - 2025-03-18 - Add `bundle/replace` From 647e218bed90b27c2918387441f4dafc9dba73a7 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Wed, 9 Apr 2025 20:39:23 -0500 Subject: [PATCH 19/21] make format --- src/core/ev.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/ev.c b/src/core/ev.c index 8f97b8ee..bb71d7a4 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -654,7 +654,7 @@ static VOID CALLBACK janet_timeout_stop(ULONG_PTR ptr) { } #elif JANET_ANDROID static void janet_timeout_stop(int sig_num) { - if(sig_num == SIGUSR1) { + if (sig_num == SIGUSR1) { pthread_exit(0); } } From 92a852f2df9a7f0e1456db14ad74bf62f410205c Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 11 Apr 2025 21:10:04 -0500 Subject: [PATCH 20/21] Add ev/to-stream. This function is symmetrical to ev/to-file and can convert a blocking file to a stream (with caveats). --- CHANGELOG.md | 1 + src/core/ev.c | 38 +++++++++++++++++++++++++++++++++++--- src/include/janet.h | 1 + 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d55b9d41..8572e550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Add `ev/to-stream` - Make `ffi/write` append to a buffer instead of insert at 0 by default. - Add `os/getpid` to get the current process id. - Add `:out` option to `os/spawn` to be able to redirect stderr to stdout with pipes. diff --git a/src/core/ev.c b/src/core/ev.c index bb71d7a4..3fc3a495 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -353,21 +353,22 @@ JanetStream *janet_stream(JanetHandle handle, uint32_t flags, const JanetMethod static void janet_stream_close_impl(JanetStream *stream) { stream->flags |= JANET_STREAM_CLOSED; + int canclose = !(stream->flags & JANET_STREAM_NOT_CLOSEABLE); #ifdef JANET_WINDOWS if (stream->handle != INVALID_HANDLE_VALUE) { #ifdef JANET_NET if (stream->flags & JANET_STREAM_SOCKET) { - closesocket((SOCKET) stream->handle); + if (canclose) closesocket((SOCKET) stream->handle); } else #endif { - CloseHandle(stream->handle); + if (canclose) CloseHandle(stream->handle); } stream->handle = INVALID_HANDLE_VALUE; } #else if (stream->handle != -1) { - close(stream->handle); + if (canclose) close(stream->handle); stream->handle = -1; #ifdef JANET_EV_POLL uint32_t i = stream->index; @@ -3486,6 +3487,36 @@ JANET_CORE_FN(janet_cfun_ev_all_tasks, return janet_wrap_array(array); } +JANET_CORE_FN(janet_cfun_to_stream, + "(ev/to-stream file)", + "Convert a core/file to a core/stream. On POSIX operating systems, this will mark " + "the underlying open file description as non-blocking.") { + janet_fixarity(argc, 1); + int32_t flags = 0; + int32_t stream_flags = 0; + FILE *file = janet_getfile(argv, 0, &flags); + if (flags & JANET_FILE_READ) stream_flags |= JANET_STREAM_READABLE; + if (flags & JANET_FILE_WRITE) stream_flags |= JANET_STREAM_WRITABLE; + if (flags & JANET_FILE_NOT_CLOSEABLE) stream_flags |= JANET_STREAM_NOT_CLOSEABLE; + if (flags & JANET_FILE_CLOSED) janet_panic("file is closed"); +#ifdef JANET_WINDOWS + int fno = _fileno(file); + int dupped_fno = _dup(fno); + if (dupped_fno == -1) janet_panic(janet_strerror(errno)); + JanetStream *stream = janet_stream(_get_osfhandle(dupped_fno), stream_flags, NULL); +#else + int handle = fileno(file); + int dupped_handle = 0; + int status = 0; + RETRY_EINTR(dupped_handle, dup(handle)); + if (status == -1) janet_panic(janet_strerror(errno)); + RETRY_EINTR(status, fcntl(dupped_handle, F_SETFL, O_NONBLOCK)); + if (status == -1) janet_panic(janet_strerror(errno)); + JanetStream *stream = janet_stream(dupped_handle, stream_flags, NULL); +#endif + return janet_wrap_abstract(stream); +} + void janet_lib_ev(JanetTable *env) { JanetRegExt ev_cfuns_ext[] = { JANET_CORE_REG("ev/give", cfun_channel_push), @@ -3517,6 +3548,7 @@ void janet_lib_ev(JanetTable *env) { JANET_CORE_REG("ev/release-rlock", janet_cfun_rwlock_read_release), JANET_CORE_REG("ev/release-wlock", janet_cfun_rwlock_write_release), JANET_CORE_REG("ev/to-file", janet_cfun_to_file), + JANET_CORE_REG("ev/to-stream", janet_cfun_to_stream), JANET_CORE_REG("ev/all-tasks", janet_cfun_ev_all_tasks), JANET_REG_END }; diff --git a/src/include/janet.h b/src/include/janet.h index 1954af7b..672552d1 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -596,6 +596,7 @@ typedef void *JanetAbstract; #define JANET_STREAM_WRITABLE 0x400 #define JANET_STREAM_ACCEPTABLE 0x800 #define JANET_STREAM_UDPSERVER 0x1000 +#define JANET_STREAM_NOT_CLOSEABLE 0x2000 #define JANET_STREAM_TOCLOSE 0x10000 typedef enum { From b27c830d90e97beec9e6ded8b8c63b3b361b964c Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Fri, 11 Apr 2025 21:36:08 -0500 Subject: [PATCH 21/21] Use win32 DuplicatHandle instead of _dup There are constraints when using the posix API on win32 that aren't present with normal win32. --- src/core/ev.c | 13 ++++++++----- src/core/os.c | 6 +++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/core/ev.c b/src/core/ev.c index 3fc3a495..a39560a9 100644 --- a/src/core/ev.c +++ b/src/core/ev.c @@ -3490,7 +3490,7 @@ JANET_CORE_FN(janet_cfun_ev_all_tasks, JANET_CORE_FN(janet_cfun_to_stream, "(ev/to-stream file)", "Convert a core/file to a core/stream. On POSIX operating systems, this will mark " - "the underlying open file description as non-blocking.") { + "the underlying open file descriptor as non-blocking.") { janet_fixarity(argc, 1); int32_t flags = 0; int32_t stream_flags = 0; @@ -3500,10 +3500,13 @@ JANET_CORE_FN(janet_cfun_to_stream, if (flags & JANET_FILE_NOT_CLOSEABLE) stream_flags |= JANET_STREAM_NOT_CLOSEABLE; if (flags & JANET_FILE_CLOSED) janet_panic("file is closed"); #ifdef JANET_WINDOWS - int fno = _fileno(file); - int dupped_fno = _dup(fno); - if (dupped_fno == -1) janet_panic(janet_strerror(errno)); - JanetStream *stream = janet_stream(_get_osfhandle(dupped_fno), stream_flags, NULL); + HANDLE handle = (HANDLE) _get_osfhandle(_fileno(file)); + HANDLE prochandle = GetCurrentProcess(); + HANDLE dupped_handle = INVALID_HANDLE_VALUE; + if (!DuplicateHandle(prochandle, handle, prochandle, &dupped_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) { + janet_panic("cannot duplicate handle to file"); + } + JanetStream *stream = janet_stream(dupped_handle, stream_flags, NULL); #else int handle = fileno(file); int dupped_handle = 0; diff --git a/src/core/os.c b/src/core/os.c index 04836717..82a99aec 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -1254,7 +1254,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { } else if (new_in != JANET_HANDLE_NONE) { startupInfo.hStdInput = new_in; } else { - startupInfo.hStdInput = (HANDLE) _get_osfhandle(0); + startupInfo.hStdInput = (HANDLE) _get_osfhandle(_fileno(stdin)); } if (pipe_out != JANET_HANDLE_NONE) { @@ -1262,7 +1262,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { } else if (new_out != JANET_HANDLE_NONE) { startupInfo.hStdOutput = new_out; } else { - startupInfo.hStdOutput = (HANDLE) _get_osfhandle(1); + startupInfo.hStdOutput = (HANDLE) _get_osfhandle(_fileno(stdout)); } if (pipe_err != JANET_HANDLE_NONE) { @@ -1272,7 +1272,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, JanetExecuteMode mode) { } else if (stderr_is_stdout) { startupInfo.hStdError = startupInfo.hStdOutput; } else { - startupInfo.hStdError = (HANDLE) _get_osfhandle(2); + startupInfo.hStdError = (HANDLE) _get_osfhandle(_fileno(stderr)); } int cp_failed = 0;