commit c9e91a9

xplshn  ·  2026-07-02 15:33:38 +0000 UTC
parent c54d5a0
sh: handle CTRL+C,S,Q,Z properly

Signed-off-by: xplshn <anto@xplshn.com.ar>
14 files changed,  +940, -560
+20, -38
 1@@ -519,7 +519,7 @@ $(OBJ) $(POSIX_BIN_ALL) $(LINUX_BIN_ALL) $(NET_BIN_ALL) $(XSI_BIN_ALL) $(PSEUDO_
 2 	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ $< $(LIB) $(LDLIBS)
 3 
 4 cmd/posix/bc.c: cmd/posix/bc.y
 5-	$(YACC) -d -o $@ cmd/posix/bc.y
 6+	YACC='$(YACC)' sh scripts/genconfig.sh bc
 7 
 8 cmd/posix/bc: cmd/posix/bc.c
 9 	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) -o $@ cmd/posix/bc.c $(LIB) $(LDLIBS)
10@@ -547,7 +547,7 @@ shared/libutil/libutil.a: $(LIBUTILOBJ)
11 cmd/posix/getconf: cmd/posix/getconf.h
12 
13 cmd/posix/getconf.h:
14-	scripts/getconf.sh > $@ || { rm -f $@; exit 1; }
15+	sh scripts/genconfig.sh getconf
16 
17 box: $(LIB)
18 	CC='$(CC)' CPPFLAGS='$(CPPFLAGS)' CFLAGS='$(CFLAGS)' \
19@@ -623,19 +623,13 @@ clean:
20 	@rm -f $(LIB)
21 	@printf "  CLEAN compiled binaries\n"
22 	@rm -f $(POSIX_BIN_ALL) $(LINUX_BIN_ALL) $(NET_BIN_ALL) $(XSI_BIN_ALL) $(PSEUDO_BIN_ALL)
23-	@printf "  CLEAN generated headers\n"
24-	@rm -f cmd/posix/getconf.h cmd/posix/bc.c cmd/posix/bc.h
25-	@rm -f cmd/posix/awk/awkgram.tab.c cmd/posix/awk/awkgram.tab.h
26-	@rm -f cmd/posix/sh/token.h cmd/posix/sh/syntax.c cmd/posix/sh/syntax.h
27-	@rm -f cmd/posix/sh/nodes.c cmd/posix/sh/nodes.h cmd/posix/sh/builtins.c cmd/posix/sh/builtins.h
28-	@printf "  CLEAN generated sources\n"
29-	@rm -f cmd/posix/awk/proctab.c
30+	@sh scripts/genconfig.sh clean
31 	@printf "  CLEAN build tools\n"
32-	@rm -f cmd/posix/awk/maketab cmd/posix/sh/mknodes cmd/posix/sh/mksyntax
33 	@rm -f scripts/mkman/mkman
34 	@rm -f scripts/mk/config.kv
35 	@rm -f scripts/mk/config.mk
36 	@rm -f scripts/mk/rules.mk
37+
38 	@printf "  CLEAN dev artifacts\n"
39 	@rm -f cmd/dev/cc/cc1 cmd/dev/cc/cpp cmd/dev/as/as cmd/dev/ld/ld cmd/dev/ar/ar shared/libaruuelf.so
40 	@rm -f cmd/dev/config.h cmd/dev/cc/config.h cmd/dev/version.h
41@@ -710,41 +704,29 @@ YAPOBJ =\
42 	cmd/extra/yap/prompt.o\
43 	cmd/extra/yap/term.o
44 
45-cmd/posix/awk/awkgram.tab.c cmd/posix/awk/awkgram.tab.h: cmd/posix/awk/awkgram.y
46-	$(YACC) -d -o cmd/posix/awk/awkgram.tab.c cmd/posix/awk/awkgram.y
47-	@if [ ! -f cmd/posix/awk/awkgram.tab.h ]; then \
48-		if [ -f y.tab.h ]; then mv y.tab.h cmd/posix/awk/awkgram.tab.h; \
49-		elif [ -f cmd/posix/awk/y.tab.h ]; then mv cmd/posix/awk/y.tab.h cmd/posix/awk/awkgram.tab.h; fi; \
50-	fi
51-
52-cmd/posix/awk/maketab: cmd/posix/awk/maketab.c cmd/posix/awk/awkgram.tab.h
53-	$(CC) $(CFLAGS) -o $@ cmd/posix/awk/maketab.c
54+cmd/posix/awk/awkgram.tab.c: cmd/posix/awk/awkgram.y cmd/posix/awk/maketab.c
55+	CC='$(CC)' CFLAGS='$(CFLAGS)' YACC='$(YACC)' sh scripts/genconfig.sh awk
56 
57-cmd/posix/awk/proctab.c: cmd/posix/awk/maketab
58-	cmd/posix/awk/maketab cmd/posix/awk/awkgram.tab.h > $@
59+cmd/posix/awk/awkgram.tab.h: cmd/posix/awk/awkgram.tab.c ;
60+cmd/posix/awk/maketab: cmd/posix/awk/awkgram.tab.c ;
61+cmd/posix/awk/proctab.c: cmd/posix/awk/awkgram.tab.c ;
62 
63 $(AWKOBJ): cmd/posix/awk/awk.h cmd/posix/awk/awkgram.tab.h cmd/posix/awk/proto.h
64 
65 cmd/posix/awk/awk: $(AWKOBJ) $(LIB)
66 	$(CC) $(LDFLAGS) -o $@ $(AWKOBJ) $(LIB) $(LDLIBS) -lm
67 
68-cmd/posix/sh/mknodes: cmd/posix/sh/mknodes.c
69-	$(CC) $(CFLAGS) -o $@ cmd/posix/sh/mknodes.c
70-
71-cmd/posix/sh/mksyntax: cmd/posix/sh/mksyntax.c
72-	$(CC) $(CPPFLAGS) -Icmd/posix/sh $(CFLAGS) -o $@ cmd/posix/sh/mksyntax.c
73-
74-cmd/posix/sh/syntax.c cmd/posix/sh/syntax.h: cmd/posix/sh/mksyntax
75-	cd cmd/posix/sh && ./mksyntax
76-
77-cmd/posix/sh/nodes.c cmd/posix/sh/nodes.h: cmd/posix/sh/mknodes cmd/posix/sh/nodetypes cmd/posix/sh/nodes.c.pat
78-	cd cmd/posix/sh && ./mknodes nodetypes nodes.c.pat
79-
80-cmd/posix/sh/builtins.c cmd/posix/sh/builtins.h: cmd/posix/sh/mkbuiltins cmd/posix/sh/builtins.def cmd/posix/sh/shell.h scripts/mk/config.kv
81-	cd cmd/posix/sh && sh mkbuiltins .
82-
83-cmd/posix/sh/token.h: cmd/posix/sh/mktokens
84-	cd cmd/posix/sh && sh mktokens
85+cmd/posix/sh/builtins.c: cmd/posix/sh/mknodes.c cmd/posix/sh/mksyntax.c cmd/posix/sh/nodetypes cmd/posix/sh/nodes.c.pat cmd/posix/sh/mkbuiltins cmd/posix/sh/builtins.def cmd/posix/sh/shell.h cmd/posix/sh/mktokens
86+	CC='$(CC)' CFLAGS='$(CFLAGS)' CPPFLAGS='$(CPPFLAGS)' sh scripts/genconfig.sh sh
87+
88+cmd/posix/sh/syntax.c: cmd/posix/sh/builtins.c ;
89+cmd/posix/sh/syntax.h: cmd/posix/sh/builtins.c ;
90+cmd/posix/sh/nodes.c: cmd/posix/sh/builtins.c ;
91+cmd/posix/sh/nodes.h: cmd/posix/sh/builtins.c ;
92+cmd/posix/sh/builtins.h: cmd/posix/sh/builtins.c ;
93+cmd/posix/sh/token.h: cmd/posix/sh/builtins.c ;
94+cmd/posix/sh/mknodes: cmd/posix/sh/builtins.c ;
95+cmd/posix/sh/mksyntax: cmd/posix/sh/builtins.c ;
96 
97 $(SHOBJ): $(SH_GENHDRS)
98 
+47, -164
  1@@ -25,14 +25,39 @@ while [ $# -gt 0 ]; do
  2         shift
  3 done
  4 
  5+# timestamp comparison
  6+#
  7+mtime() {
  8+        stat -c '%Y' "$1" 2>/dev/null || stat -f '%m' "$1" 2>/dev/null || echo 0
  9+}
 10+newer_than() {
 11+        local s t
 12+        [ -e "$2" ] || return 0
 13+        s=$(mtime "$1"); t=$(mtime "$2")
 14+        [ "$s" -gt "$t" ]
 15+}
 16+any_newer_than() {
 17+        local target="$1"; shift
 18+        for src do
 19+                newer_than "$src" "$target" && return 0
 20+        done
 21+        return 1
 22+}
 23+
 24 [ -f build.cfg ] || { printf 'build.cfg not found; see the repository README\n' >&2; exit 1; }
 25+
 26+# generate config files if missing or build.cfg is newer
 27+if [ ! -f scripts/mk/config.kv ] || newer_than build.cfg scripts/mk/config.kv; then
 28+        sh scripts/genconfig.sh
 29+fi
 30+
 31 # shellcheck disable=SC1091
 32 . ./build.cfg
 33 
 34 # load config.kv if it exists to override and provide detected TLS settings
 35 if [ -f scripts/mk/config.kv ]; then
 36         # shellcheck disable=SC1091
 37-        . scripts/mk/config.kv
 38+        . ./scripts/mk/config.kv
 39 fi
 40 
 41 # cppflags derived from build.cfg; any feature_* added there is picked up automatically
 42@@ -69,25 +94,6 @@ LIB="shared/libredline/libredline.a shared/libutil/libutil.a shared/libutf/libut
 43 # set by multi-file builders for generated headers, cleared after use
 44 EXTRA_HDR=
 45 
 46-# timestamp comparison
 47-#
 48-mtime() {
 49-        stat -c '%Y' "$1" 2>/dev/null || stat -f '%m' "$1" 2>/dev/null || echo 0
 50-}
 51-newer_than() {
 52-        local s t
 53-        [ -e "$2" ] || return 0
 54-        s=$(mtime "$1"); t=$(mtime "$2")
 55-        [ "$s" -gt "$t" ]
 56-}
 57-any_newer_than() {
 58-        local target="$1"; shift
 59-        for src do
 60-                newer_than "$src" "$target" && return 0
 61-        done
 62-        return 1
 63-}
 64-
 65 # parallel job queue
 66 # objs_for must not run in a subshell, enqueue writes _qn/_queue in the current shell
 67 #
 68@@ -238,59 +244,28 @@ cfg_enabled() {
 69 }
 70 
 71 build_simple_tools() {
 72-        local cat="$1" src base var staged=""
 73+        local cat="$1" src base var
 74         for src in cmd/"$cat"/*.c; do
 75                 [ -f "$src" ] || continue
 76                 base=${src##*/}; base=${base%.c}
 77                 var=$(cfgvar "$cat" "$base")
 78                 cfg_enabled "$var" || continue
 79-                compile_c "$src" "${src%.c}.o"
 80-                staged="$staged ${src%.c}"
 81-        done
 82-        drain
 83-        local bin
 84-        for bin in $staged; do
 85-                if [ "${bin##*/}" = "diff" ]; then
 86-                        link_bin "$bin" "${bin}.o" -- $LIB -lm
 87+                local bin="cmd/$cat/$base"
 88+                # shellcheck disable=SC2086
 89+                any_newer_than "$bin" "$src" $HDR $EXTRA_HDR $LIB || continue
 90+                if [ "$base" = "diff" ]; then
 91+                        enqueue "  CC  $bin" "$CC $src $CPPFLAGS $CFLAGS $LDFLAGS -o $bin $LIB -lm $LDLIBS"
 92                 else
 93-                        link_bin "$bin" "${bin}.o" -- $LIB
 94+                        enqueue "  CC  $bin" "$CC $src $CPPFLAGS $CFLAGS $LDFLAGS -o $bin $LIB $LDLIBS"
 95                 fi
 96         done
 97+        drain
 98 }
 99 
100 build_awk() {
101         cfg_enabled BUILD_POSIX_AWK || return 0
102         local dir=cmd/posix/awk
103-
104-        any_newer_than "$dir/awkgram.tab.c" "$dir/awkgram.y" && {
105-                if   command -v yacc  >/dev/null 2>&1; then
106-                        printf '  YACC  %s/awkgram.tab.c\n' "$dir"
107-                        yacc  -d -o "$dir/awkgram.tab.c" "$dir/awkgram.y"
108-                        if [ ! -f "$dir/awkgram.tab.h" ]; then
109-                                if [ -f y.tab.h ]; then
110-                                        mv y.tab.h "$dir/awkgram.tab.h"
111-                                elif [ -f "$dir/y.tab.h" ]; then
112-                                        mv "$dir/y.tab.h" "$dir/awkgram.tab.h"
113-                                fi
114-                        fi
115-                elif command -v bison >/dev/null 2>&1; then
116-                        printf '  BISON %s/awkgram.tab.c\n' "$dir"
117-                        bison -d -o "$dir/awkgram.tab.c" "$dir/awkgram.y"
118-                else
119-                        printf 'awk: no yacc/bison; using pre-generated awkgram.tab.c\n' >&2
120-                fi
121-        }
122-
123-        any_newer_than "$dir/maketab" "$dir/maketab.c" "$dir/awkgram.tab.h" && {
124-                printf '  CC    %s/maketab\n' "$dir"
125-                eval "$CC $CFLAGS -o $dir/maketab $dir/maketab.c"
126-        }
127-
128-        any_newer_than "$dir/proctab.c" "$dir/maketab" && {
129-                printf '  GEN   %s/proctab.c\n' "$dir"
130-                "$dir/maketab" "$dir/awkgram.tab.h" > "$dir/proctab.c"
131-        }
132-
133+        CC="$CC" CFLAGS="$CFLAGS" YACC="$YACC" sh scripts/genconfig.sh awk
134         EXTRA_HDR="$dir/awk.h $dir/awkgram.tab.h $dir/proto.h"
135         objs_for "$dir" maketab.c "-I$dir"
136         EXTRA_HDR=
137@@ -300,37 +275,7 @@ build_awk() {
138 build_sh() {
139         cfg_enabled BUILD_POSIX_SH || return 0
140         local dir=cmd/posix/sh
141-
142-        any_newer_than "$dir/mknodes" "$dir/mknodes.c" && {
143-                printf '  CC    %s/mknodes\n' "$dir"
144-                eval "$CC $CFLAGS -o $dir/mknodes $dir/mknodes.c"
145-        }
146-
147-        any_newer_than "$dir/mksyntax" "$dir/mksyntax.c" && {
148-                printf '  CC    %s/mksyntax\n' "$dir"
149-                eval "$CC $CPPFLAGS -I$dir $CFLAGS -o $dir/mksyntax $dir/mksyntax.c"
150-        }
151-
152-        any_newer_than "$dir/token.h" "$dir/mktokens" && {
153-                printf '  GEN   %s/token.h\n' "$dir"
154-                (cd "$dir" && sh mktokens)
155-        }
156-
157-        any_newer_than "$dir/syntax.c" "$dir/mksyntax" && {
158-                printf '  GEN   %s/syntax.c\n' "$dir"
159-                (cd "$dir" && ./mksyntax)
160-        }
161-
162-        any_newer_than "$dir/nodes.c" "$dir/mknodes" "$dir/nodetypes" "$dir/nodes.c.pat" && {
163-                printf '  GEN   %s/nodes.c\n' "$dir"
164-                (cd "$dir" && ./mknodes nodetypes nodes.c.pat)
165-        }
166-
167-        any_newer_than "$dir/builtins.c" "$dir/mkbuiltins" "$dir/builtins.def" "$dir/shell.h" scripts/mk/config.kv && {
168-                printf '  GEN   %s/builtins.c\n' "$dir"
169-                (cd "$dir" && sh mkbuiltins .)
170-        }
171-
172+        CC="$CC" CFLAGS="$CFLAGS" CPPFLAGS="$CPPFLAGS" sh scripts/genconfig.sh sh
173         EXTRA_HDR="$dir/syntax.h $dir/nodes.h $dir/builtins.h $dir/token.h"
174         objs_for "$dir" "mknodes.c mksyntax.c" "-DSHELL -I$dir"
175         EXTRA_HDR=
176@@ -349,28 +294,15 @@ build_make() {
177 build_bc() {
178         cfg_enabled BUILD_POSIX_BC || return 0
179         local src=cmd/posix/bc.c
180-
181-        any_newer_than "$src" cmd/posix/bc.y && {
182-                printf '  GEN   %s\n' "$src"
183-                if command -v yacc >/dev/null 2>&1; then
184-                        yacc -o "$src" cmd/posix/bc.y
185-                elif command -v bison >/dev/null 2>&1; then
186-                        bison -o "$src" cmd/posix/bc.y
187-                else
188-                        printf 'bc: no yacc/bison found\n' >&2; return 1
189-                fi
190-        }
191-
192-        compile_c "$src" "${src%.c}.o"
193-        drain
194-        link_bin cmd/posix/bc "${src%.c}.o" -- $LIB
195+        YACC="$YACC" sh scripts/genconfig.sh bc
196+        # shellcheck disable=SC2086
197+        any_newer_than cmd/posix/bc "$src" $HDR $EXTRA_HDR $LIB || return 0
198+        printf '  CC  cmd/posix/bc\n'
199+        eval "$CC $src $CPPFLAGS $CFLAGS $LDFLAGS -o cmd/posix/bc $LIB $LDLIBS"
200 }
201 
202 build_posix() {
203-        cfg_enabled BUILD_POSIX_GETCONF && [ ! -f cmd/posix/getconf.h ] && {
204-                printf '  GEN   cmd/posix/getconf.h\n'
205-                scripts/getconf.sh > cmd/posix/getconf.h || { rm -f cmd/posix/getconf.h; exit 1; }
206-        }
207+        cfg_enabled BUILD_POSIX_GETCONF && sh scripts/genconfig.sh getconf
208         build_simple_tools posix
209         build_awk
210         build_bc
211@@ -521,49 +453,9 @@ do_regen() {
212                 . scripts/mk/config.kv
213         fi
214 
215-        # 3. clean the generated files to force regeneration
216-        rm -f cmd/posix/getconf.h cmd/posix/bc.c cmd/posix/bc.h
217-        rm -f cmd/posix/awk/awkgram.tab.c cmd/posix/awk/awkgram.tab.h cmd/posix/awk/proctab.c cmd/posix/awk/maketab
218-        rm -f cmd/posix/sh/mknodes cmd/posix/sh/mksyntax cmd/posix/sh/token.h cmd/posix/sh/syntax.c cmd/posix/sh/syntax.h cmd/posix/sh/nodes.c cmd/posix/sh/nodes.h cmd/posix/sh/builtins.c cmd/posix/sh/builtins.h
219-
220-        # 4. run the generation steps
221-        # for getconf.h
222-        printf '  GEN   cmd/posix/getconf.h\n'
223-        scripts/getconf.sh > cmd/posix/getconf.h || { rm -f cmd/posix/getconf.h; exit 1; }
224-
225-        # for bc.c from bc.y
226-        printf '  GEN   cmd/posix/bc.c\n'
227-        if command -v yacc >/dev/null 2>&1; then
228-                yacc -o cmd/posix/bc.c cmd/posix/bc.y
229-        elif command -v bison >/dev/null 2>&1; then
230-                bison -o cmd/posix/bc.c cmd/posix/bc.y
231-        fi
232-
233-        # for awk
234-        local adir=cmd/posix/awk
235-        if command -v yacc >/dev/null 2>&1; then
236-                yacc -d -o "$adir/awkgram.tab.c" "$adir/awkgram.y"
237-                if [ ! -f "$adir/awkgram.tab.h" ]; then
238-                        if [ -f y.tab.h ]; then
239-                                mv y.tab.h "$adir/awkgram.tab.h"
240-                        elif [ -f "$adir/y.tab.h" ]; then
241-                                mv "$adir/y.tab.h" "$adir/awkgram.tab.h"
242-                        fi
243-                fi
244-        elif command -v bison >/dev/null 2>&1; then
245-                bison -d -o "$adir/awkgram.tab.c" "$adir/awkgram.y"
246-        fi
247-        eval "$CC $CFLAGS -o $adir/maketab $adir/maketab.c"
248-        "$adir/maketab" "$adir/awkgram.tab.h" > "$adir/proctab.c"
249-
250-        # for sh
251-        local sdir=cmd/posix/sh
252-        eval "$CC $CFLAGS -o $sdir/mknodes $sdir/mknodes.c"
253-        eval "$CC $CPPFLAGS -I$sdir $CFLAGS -o $sdir/mksyntax $sdir/mksyntax.c"
254-        (cd "$sdir" && sh mktokens)
255-        (cd "$sdir" && ./mksyntax)
256-        (cd "$sdir" && ./mknodes nodetypes nodes.c.pat)
257-        (cd "$sdir" && sh mkbuiltins .)
258+        # 3. clean and generate files using scripts/genconfig.sh
259+        CC="$CC" CFLAGS="$CFLAGS" CPPFLAGS="$CPPFLAGS" YACC="$YACC" sh scripts/genconfig.sh clean
260+        CC="$CC" CFLAGS="$CFLAGS" CPPFLAGS="$CPPFLAGS" YACC="$YACC" sh scripts/genconfig.sh all
261 }
262 
263 do_clean() {
264@@ -573,18 +465,9 @@ do_clean() {
265         find shared -name '*.a' -exec rm -f {} +
266         printf '  CLEAN compiled binaries\n'
267         find cmd -type f ! -name '*.*' -perm -100 -exec rm -f {} +
268-        printf '  CLEAN generated headers\n'
269-        rm -f cmd/posix/getconf.h
270-        rm -f cmd/posix/awk/awkgram.tab.c cmd/posix/awk/awkgram.tab.h
271-        rm -f cmd/posix/sh/token.h cmd/posix/sh/syntax.c cmd/posix/sh/syntax.h
272-        rm -f cmd/posix/sh/nodes.c cmd/posix/sh/nodes.h
273-        rm -f cmd/posix/sh/builtins.c cmd/posix/sh/builtins.h
274-        printf '  CLEAN generated sources\n'
275-        rm -f cmd/posix/awk/proctab.c
276-        rm -f cmd/posix/bc.c
277+        sh scripts/genconfig.sh clean
278+        rm -f scripts/mk/config.mk scripts/mk/config.kv scripts/mk/rules.mk shared/libutil/nofork_list.h
279         printf '  CLEAN build tools\n'
280-        rm -f cmd/posix/awk/maketab
281-        rm -f cmd/posix/sh/mknodes cmd/posix/sh/mksyntax
282         rm -f scripts/mkman/mkman
283         printf '  CLEAN dev artifacts\n'
284         rm -f cmd/dev/cc/cc1 cmd/dev/cc/cpp cmd/dev/as/as cmd/dev/ld/ld cmd/dev/ar/ar
+31, -21
 1@@ -107,9 +107,10 @@ all)
 2     FEATURE_SORT_BIG=1;               FEATURE_SORT_STABLE=1
 3     FEATURE_OD_ENDIAN=1;
 4     FEATURE_SH_EASTER=1;              FEATURE_SH_HISTEDIT=1
 5-    FEATURE_INPROC_EXEC=0;            FEATURE_SH_LOCAL=1
 6-    FEATURE_SH_LET=1;                 FEATURE_SH_ULIMIT=1
 7-    FEATURE_SH_SETVAR=1;              FEATURE_SH_WORDEXP=1
 8+    FEATURE_SH_LOCAL=1                FEATURE_NOEXEC=0;
 9+    FEATURE_NOFORK=0;                 FEATURE_SH_LET=1;
10+    FEATURE_SH_ULIMIT=1               FEATURE_SH_SETVAR=1;
11+    FEATURE_SH_WORDEXP=1
12     FEATURE_CAL_EXT=1
13     FEATURE_LS_COLOR=1
14     FEATURE_DIFF3_MERGE=1;            FEATURE_DIFF3_EDSCRIPT=1
15@@ -158,30 +159,39 @@ unset _p
16 # BUILD_DEV_AS=1         # enable a tool whose group isnt in PRESET_UTILS
17 FEATURE_SH_HISTEDIT=1
18 
19-# FEATURE_INPROC_EXEC: when set to 1, wexecvp/wexecv/wpopen/wsystem call
20-# registered tool mains directly instead of fork+exec. this is only meaningful
21-# inside aruu-box, where every tool has registered its main at startup
22+# FEATURE_NOEXEC: when set to 1, wexec/wpopen/wsystem can execute registered
23+# tool mains directly inside the forked child process (skipping execve)
24+# This is equivalent to Busybox noexec applet support.
25+# This applies to ALL commands contained within aruu-box, you may also
26+# specify if some specific command is not an attack vector to also be nofork
27+# via enabling FEATURE_NOFORK=1 and specifying those commands in NOFORK_LIST
28 #
29-# the analogy is busybox: a single binary where sh can call grep, sed, awk, etc.
30-# without spawning a new process for each one. scripts run faster and the whole
31-# system needs no external PATH at all. aruu-box built with this flag is fully
32-# self-contained, exactly like a busybox applet collection
33+#FEATURE_NOEXEC=1
34 #
35-# this is optional. set to 0 to build the box without inproc dispatch (each
36-# tool call still fork+execs). for individual standalone tool binaries this
37-# flag has no practical effect because no other mains are registered
38+# FEATURE_NOEXEC_OVERRIDE: only meaningful when FEATURE_NOEXEC=1
39+# when set to 1, ARUU_NOEXEC=0 in the environment forces fork+exec at runtime
40 #
41-FEATURE_INPROC_EXEC=1
42+#FEATURE_NOEXEC_OVERRIDE=1
43 #
44-# FEATURE_INPROC_EXEC_OVERRIDE: only meaningful when FEATURE_INPROC_EXEC=1
45-# when set to 1, ARUU_INPROC_EXEC=0 in the environment forces fork+exec at
46-# runtime, letting a person reach an external tool that shares a name with
47-# a builtin without giving its full path. when set to 0, that env var is
48-# never read at all and inproc dispatch cannot be bypassed except by a tool
49-# explicitly calling wexec_set_inproc()
50+# FEATURE_NOFORK: when set to 1, wexecvp/wexecv can run simple commands
51+# (like cat, echo, pwd, test) directly in the current process without spawning
52+# a subprocess (noexec+nofork). This is equivalent to Busybox nofork applet support
53 #
54-FEATURE_INPROC_EXEC_OVERRIDE=1
55+FEATURE_NOFORK=1
56 #
57+# FEATURE_NOFORK_OVERRIDE: only meaningful when FEATURE_NOFORK=1
58+# when set to 1, ARUU_NOFORK=0 in the environment forces fork for nofork commands
59+# even tho they will still not do execvp() call at runtime
60+# unless FEATURE_NOEXEC_OVERRIDE was enabled at build time and ARUU_NOEXEC=0
61+#
62+#FEATURE_NOFORK_OVERRIDE=1
63+#
64+# NOFORK_LIST: list of builtins that can run without fork (noexec+nofork)
65+#
66+NOFORK_LIST="true false echo pwd test [ basename dirname cat clear yes"
67+#
68+
69+BUILD_EXTRA_YAP=0
70 
71 # tls backend: set exactly one to 1 to enable tls
72 #
+49, -1
 1@@ -43,6 +43,7 @@
 2  */
 3 
 4 #include "../../../shared/paths.h"
 5+#include "../../../shared/wexec.h"
 6 #include "builtins.h"
 7 #include "error.h"
 8 #include "eval.h"
 9@@ -999,7 +1000,8 @@ evalcommand(union node *cmd, int flags, struct backcmd *backcmd)
10   }
11 
12   /* Fork off a child process if necessary. */
13-  if (((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN)
14+  if (((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN
15+        || (cmdentry.cmdtype == CMDWEXEC && !(wexec_get_nofork() && wexec_is_nofork(argv[0]))))
16        && ((flags & EV_EXIT) == 0 || have_traps()))
17       || ((flags & EV_BACKCMD) != 0
18           && (cmdentry.cmdtype != CMDBUILTIN || !safe_builtin(cmdentry.u.index, argc, argv)))) {
19@@ -1094,6 +1096,52 @@ evalcommand(union node *cmd, int flags, struct backcmd *backcmd)
20     }
21     if (jp)
22       exitshell(exitstatus);
23+  } else if (cmdentry.cmdtype == CMDWEXEC) {
24+    savecmdname = commandname;
25+    savetopfile = getcurrentfile();
26+    cmdenviron  = &varlist;
27+    e           = -1;
28+    savehandler = handler;
29+    if (setjmp(jmploc.loc)) {
30+      e = exception;
31+      if (e == EXINT)
32+        exitstatus = SIGINT + 128;
33+      goto cmddone_wexec;
34+    }
35+    handler = &jmploc;
36+    mode    = REDIR_PUSH;
37+    if (flags == EV_BACKCMD) {
38+      memout.nextc = memout.buf;
39+      mode |= REDIR_BACKQ;
40+    }
41+    redirect(cmd->ncmd.redirect, mode);
42+    outclearerror(out1);
43+    listsetvar(cmdenviron, VNOSET);
44+    commandname = argv[0];
45+    exitstatus  = wexecvp(argv[0], argv);
46+    flushall();
47+    if (outiserror(out1)) {
48+      warning("write error on stdout");
49+      if (exitstatus == 0 || exitstatus == 1)
50+        exitstatus = 2;
51+    }
52+  cmddone_wexec:
53+    cmdenviron = NULL;
54+    out1       = &output;
55+    out2       = &errout;
56+    freestdout();
57+    handler     = savehandler;
58+    commandname = savecmdname;
59+    if (jp)
60+      exitshell(exitstatus);
61+    if (flags == EV_BACKCMD) {
62+      backcmd->buf   = memout.buf;
63+      backcmd->nleft = memout.buf != NULL ? memout.nextc - memout.buf : 0;
64+      memout.buf     = NULL;
65+      memout.nextc   = NULL;
66+      memout.bufend  = NULL;
67+      memout.bufsize = 64;
68+    }
69   } else if (cmdentry.cmdtype == CMDBUILTIN) {
70 #ifdef DEBUG
71     trputs("builtin command:  ");
+45, -15
  1@@ -34,6 +34,7 @@
  2 
  3 #include <errno.h>
  4 #include <fcntl.h>
  5+#include <signal.h>
  6 #include <stdlib.h>
  7 #include <sys/stat.h>
  8 #include <sys/types.h>
  9@@ -102,17 +103,35 @@ shellexec(char **argv, char **envp, const char *path, int idx)
 10   char       *cmdname;
 11   const char *opt;
 12   int         e;
 13+#if FEATURE_NOEXEC
 14+  struct sigaction sa;
 15+  int              i;
 16+  int              ret;
 17+#endif
 18 
 19-#if FEATURE_INPROC_EXEC
 20+#if FEATURE_NOEXEC
 21   {
 22     /* basename of argv[0] for slash-free lookup */
 23-    const char *base = strrchr(argv[0], '/');
 24-    base = base ? base + 1 : argv[0];
 25-    if (wexec_get_inproc() && wexec_is_builtin(base)) {
 26-      int ret;
 27+    const char   *base = strrchr(argv[0], '/');
 28+    extern char **environ;
 29+    base    = base ? base + 1 : argv[0];
 30+    environ = envp;
 31+    if (wexec_get_noexec() && wexec_is_builtin(base)) {
 32+      /* reset caught signal handlers to default for inproc command */
 33+      for (i = 1; i < NSIG; i++) {
 34+        if (sigaction(i, NULL, &sa) == 0) {
 35+          if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
 36+            sa.sa_handler = SIG_DFL;
 37+            sa.sa_flags   = 0;
 38+            sigemptyset(&sa.sa_mask);
 39+            sigaction(i, &sa, NULL);
 40+          }
 41+        }
 42+      }
 43+
 44       fflush(stdout);
 45       fflush(stderr);
 46-      ret = wexecvp(base, argv);
 47+      ret = wexec_call_builtin(base, argv);
 48       fflush(stdout);
 49       fflush(stderr);
 50       _exit(ret);
 51@@ -331,9 +350,9 @@ printentry(struct tblentry *cmdp, int verbose)
 52       ckfree(name);
 53       INTON;
 54     }
 55-#if FEATURE_INPROC_EXEC
 56+#if FEATURE_NOEXEC || FEATURE_NOFORK
 57   } else if (cmdp->cmdtype == CMDWEXEC) {
 58-    out1fmt("inproc %s", cmdp->cmdname);
 59+    out1fmt("builtin %s", cmdp->cmdname);
 60 #endif
 61 #ifdef DEBUG
 62   } else {
 63@@ -392,10 +411,10 @@ find_command(const char *name, struct cmdentry *entry, int act, const char *path
 64     goto success;
 65   }
 66 
 67-#if FEATURE_INPROC_EXEC
 68-  /* when inproc dispatch is active, registered wexec builtins are available
 69+#if FEATURE_NOEXEC || FEATURE_NOFORK
 70+  /* when builtin dispatch is active, registered wexec builtins are available
 71    * even if they are not present on the filesystem */
 72-  if (wexec_get_inproc() && wexec_is_builtin(name)) {
 73+  if ((wexec_get_noexec() || wexec_get_nofork()) && wexec_is_builtin(name)) {
 74     INTOFF;
 75     cmdp = cmdlookup(name, 1);
 76     if (cmdp->cmdtype == CMDFUNCTION)
 77@@ -821,12 +840,23 @@ typecmd_impl(int argc, char **argv, int cmd, const char *path)
 78           out1fmt("%s is a shell builtin\n", argv[i]);
 79         break;
 80 
 81-#if FEATURE_INPROC_EXEC
 82+#if FEATURE_NOEXEC || FEATURE_NOFORK
 83       case CMDWEXEC:
 84-        if (cmd == TYPECMD_SMALLV)
 85+        if (cmd == TYPECMD_SMALLV) {
 86           out1fmt("%s\n", argv[i]);
 87-        else
 88-          out1fmt("%s is an inproc builtin\n", argv[i]);
 89+        } else {
 90+          if (wexec_get_nofork() && wexec_is_nofork(argv[i])) {
 91+            out1fmt(
 92+                "%s is embedded via wexec and registered by mkbox & genconfig.sh as "
 93+                "noexec+nofork\n",
 94+                argv[i]
 95+            );
 96+          } else if (wexec_get_noexec()) {
 97+            out1fmt("%s is embedded via wexec and registered by mkbox as noexec\n", argv[i]);
 98+          } else {
 99+            out1fmt("%s is a shell builtin\n", argv[i]);
100+          }
101+        }
102         break;
103 #endif
104 
+1, -1
1@@ -39,7 +39,7 @@
2 #define CMDNORMAL   0  /* command is an executable program */
3 #define CMDBUILTIN  1  /* command is a shell builtin */
4 #define CMDFUNCTION 2  /* command is a shell function */
5-#define CMDWEXEC    3  /* command is a registered inproc builtin */
6+#define CMDWEXEC    3  /* command is a registered builtin applet */
7 
8 /* values for typecmd_impl's third parameter */
9 enum {
+2, -0
 1@@ -42,6 +42,7 @@
 2 #include <unistd.h>
 3 
 4 #include "../../../shared/paths.h"
 5+#include "../../../shared/wexec.h"
 6 #include "builtins.h"
 7 #include "cd.h"
 8 #include "error.h"
 9@@ -100,6 +101,7 @@ main(int argc, char *argv[])
10   int                     login;
11 
12   (void)setlocale(LC_ALL, "");
13+  wexec_register_env_lookup(lookupvar);
14   initcharset();
15   state = 0;
16   if (setjmp(main_handler.loc)) {
+78, -76
  1@@ -1,7 +1,7 @@
  2 /* See LICENSE file for copyright and license details. */
  3-#include "wexec.h"
  4-#include "util.h"
  5 #include "arg.h"
  6+#include "util.h"
  7+#include "wexec.h"
  8 
  9 #include <fcntl.h>
 10 #include <limits.h>
 11@@ -17,56 +17,56 @@ static int aflag;
 12 static int
 13 canexec(int fd, const char *name)
 14 {
 15-	struct stat st;
 16+  struct stat st;
 17 
 18-	if (fstatat(fd, name, &st, 0) < 0 || !S_ISREG(st.st_mode))
 19-		return 0;
 20-	return faccessat(fd, name, X_OK, AT_EACCESS) == 0;
 21+  if (fstatat(fd, name, &st, 0) < 0 || !S_ISREG(st.st_mode))
 22+    return 0;
 23+  return faccessat(fd, name, X_OK, AT_EACCESS) == 0;
 24 }
 25 
 26 static int
 27 which(const char *path, const char *name)
 28 {
 29-	char  *ptr, *p;
 30-	size_t i, len;
 31-	int    dirfd, found = 0;
 32+  char  *ptr, *p;
 33+  size_t i, len;
 34+  int    dirfd, found = 0;
 35 
 36-	if (strchr(name, '/')) {
 37-		found = canexec(AT_FDCWD, name);
 38-		if (found)
 39-			puts(name);
 40-		return found;
 41-	}
 42+  if (strchr(name, '/')) {
 43+    found = canexec(AT_FDCWD, name);
 44+    if (found)
 45+      puts(name);
 46+    return found;
 47+  }
 48 
 49-	ptr = p = enstrdup(3, path);
 50-	len     = strlen(p);
 51-	for (i = 0; i < len + 1; i++) {
 52-		if (ptr[i] != ':' && ptr[i] != '\0')
 53-			continue;
 54-		ptr[i] = '\0';
 55-		if ((dirfd = open(p, O_RDONLY)) >= 0) {
 56-			if (canexec(dirfd, name)) {
 57-				found = 1;
 58-				fputs(p, stdout);
 59-				if (i && ptr[i - 1] != '/')
 60-					fputc('/', stdout);
 61-				puts(name);
 62-			}
 63-			close(dirfd);
 64-			if (!aflag && found)
 65-				break;
 66-		}
 67-		p = ptr + i + 1;
 68-	}
 69-	free(ptr);
 70+  ptr = p = enstrdup(3, path);
 71+  len     = strlen(p);
 72+  for (i = 0; i < len + 1; i++) {
 73+    if (ptr[i] != ':' && ptr[i] != '\0')
 74+      continue;
 75+    ptr[i] = '\0';
 76+    if ((dirfd = open(p, O_RDONLY)) >= 0) {
 77+      if (canexec(dirfd, name)) {
 78+        found = 1;
 79+        fputs(p, stdout);
 80+        if (i && ptr[i - 1] != '/')
 81+          fputc('/', stdout);
 82+        puts(name);
 83+      }
 84+      close(dirfd);
 85+      if (!aflag && found)
 86+        break;
 87+    }
 88+    p = ptr + i + 1;
 89+  }
 90+  free(ptr);
 91 
 92-	return found;
 93+  return found;
 94 }
 95 
 96 static void
 97 usage(void)
 98 {
 99-	eprintf("usage: %s [-a] name ...\n", argv0);
100+  eprintf("usage: %s [-a] name ...\n", argv0);
101 }
102 
103 // ?man which: locate a command
104@@ -75,49 +75,51 @@ usage(void)
105 int
106 main(int argc, char *argv[])
107 {
108-	char *path;
109-	int   found = 0, foundall = 1;
110+  char *path;
111+  int   found = 0, foundall = 1;
112 
113-	ARGBEGIN {
114-	// ?man -a: print or show all entries
115-	case 'a':
116-		aflag = 1;
117-		break;
118-	default:
119-		usage();
120-	} ARGEND
121+  ARGBEGIN
122+  {
123+    // ?man -a: print or show all entries
124+    case 'a':
125+      aflag = 1;
126+      break;
127+    default:
128+      usage();
129+  }
130+  ARGEND
131 
132-	if (!argc)
133-		usage();
134+  if (!argc)
135+    usage();
136 
137-	if (!(path = getenv("PATH")))
138-		enprintf(3, "$PATH is not set\n");
139+  if (!(path = getenv("PATH")))
140+    enprintf(3, "$PATH is not set\n");
141 
142-	for (; *argv; argc--, argv++) {
143-#if FEATURE_INPROC_EXEC
144-		/* inproc builtins are function pointers with no filesystem path */
145-		if (wexec_get_inproc() && wexec_is_builtin(*argv)) {
146-			printf("%s: inproc builtin\n", *argv);
147-			found = 1;
148-			if (!aflag)
149-				continue;
150-		}
151+  for (; *argv; argc--, argv++) {
152+#if FEATURE_NOEXEC
153+    /* builtins are function pointers with no filesystem path */
154+    if (wexec_get_noexec() && wexec_is_builtin(*argv)) {
155+      printf("%s: builtin applet\n", *argv);
156+      found = 1;
157+      if (!aflag)
158+        continue;
159+    }
160 #endif
161-		if (which(path, *argv)) {
162-			found = 1;
163-		} else {
164-#if FEATURE_INPROC_EXEC
165-			/* already reported above when inproc was active */
166-			if (!wexec_get_inproc() || !wexec_is_builtin(*argv))
167+    if (which(path, *argv)) {
168+      found = 1;
169+    } else {
170+#if FEATURE_NOEXEC
171+      /* already reported above when builtin was active */
172+      if (!wexec_get_noexec() || !wexec_is_builtin(*argv))
173 #endif
174-			{
175-				weprintf("%s: not an external command\n", *argv);
176-				foundall = 0;
177-			}
178-		}
179-	}
180+      {
181+        weprintf("%s: not an external command\n", *argv);
182+        foundall = 0;
183+      }
184+    }
185+  }
186 
187-	if (fshut(stdout, "<stdout>"))
188-		return 2;
189-	return found ? foundall ? 0 : 1 : 2;
190+  if (fshut(stdout, "<stdout>"))
191+    return 2;
192+  return found ? foundall ? 0 : 1 : 2;
193 }
+302, -148
  1@@ -3,6 +3,8 @@
  2 
  3 set -e
  4 
  5+target=${1:-config}
  6+
  7 # load build.cfg
  8 if [ -f build.cfg ]; then
  9         . ./build.cfg
 10@@ -11,45 +13,128 @@ else
 11         exit 1
 12 fi
 13 
 14-mkdir -p scripts/mk
 15-
 16-mk_file="scripts/mk/config.mk"
 17-kv_file="scripts/mk/config.kv"
 18-
 19-# clear existing files
 20-: > "${mk_file}"
 21-: > "${kv_file}"
 22-
 23-# write version prefix manprefix
 24-echo "VERSION = ${VERSION}" >> "${mk_file}"
 25-echo "VERSION=\"${VERSION}\"" >> "${kv_file}"
 26-
 27-echo "PREFIX = ${PREFIX}" >> "${mk_file}"
 28-echo "PREFIX=\"${PREFIX}\"" >> "${kv_file}"
 29+# helper to check if source is newer than target
 30+newer_than() {
 31+  [ -e "$2" ] || return 0
 32+  local s t
 33+  s=$(stat -c '%Y' "$1" 2>/dev/null || stat -f '%m' "$1" 2>/dev/null || echo 0)
 34+  t=$(stat -c '%Y' "$2" 2>/dev/null || stat -f '%m' "$2" 2>/dev/null || echo 0)
 35+  [ "$s" -gt "$t" ]
 36+}
 37 
 38-# default manprefix to prefix/share/man if empty
 39-man_pref="${MANPREFIX:-/usr/local/share/man}"
 40-echo "MANPREFIX = ${man_pref}" >> "${mk_file}"
 41-echo "MANPREFIX=\"${man_pref}\"" >> "${kv_file}"
 42+gen_getconf() {
 43+  if newer_than scripts/getconf.sh cmd/posix/getconf.h; then
 44+    printf "  GEN   cmd/posix/getconf.h\n"
 45+    scripts/getconf.sh > cmd/posix/getconf.h || { rm -f cmd/posix/getconf.h; exit 1; }
 46+  fi
 47+}
 48 
 49-# toolchain defaults
 50-echo "RANLIB = ${RANLIB:-ranlib}" >> "${mk_file}"
 51-echo "RANLIB=\"${RANLIB:-ranlib}\"" >> "${kv_file}"
 52+gen_bc() {
 53+  if newer_than cmd/posix/bc.y cmd/posix/bc.c; then
 54+    printf "  GEN   cmd/posix/bc.c\n"
 55+    CC=${CC:-cc}
 56+    YACC=${YACC:-}
 57+    if [ -z "$YACC" ]; then
 58+      if command -v yacc >/dev/null 2>&1; then
 59+        YACC="yacc"
 60+      elif command -v bison >/dev/null 2>&1; then
 61+        YACC="bison -y"
 62+      else
 63+        YACC="yacc"
 64+      fi
 65+    fi
 66+    if [ "$YACC" = "bison -y" ]; then
 67+      bison -o cmd/posix/bc.c cmd/posix/bc.y
 68+    else
 69+      $YACC -o cmd/posix/bc.c cmd/posix/bc.y
 70+    fi
 71+  fi
 72+}
 73 
 74-echo "AR = ${AR:-ar}" >> "${mk_file}"
 75-echo "AR=\"${AR:-ar}\"" >> "${kv_file}"
 76+gen_awk() {
 77+  local dir="cmd/posix/awk"
 78+  CC=${CC:-cc}
 79+  YACC=${YACC:-}
 80+  if [ -z "$YACC" ]; then
 81+    if command -v yacc >/dev/null 2>&1; then
 82+      YACC="yacc"
 83+    elif command -v bison >/dev/null 2>&1; then
 84+      YACC="bison -y"
 85+    else
 86+      YACC="yacc"
 87+    fi
 88+  fi
 89+
 90+  if newer_than "$dir/awkgram.y" "$dir/awkgram.tab.c" || newer_than "$dir/awkgram.y" "$dir/awkgram.tab.h"; then
 91+    printf "  YACC  $dir/awkgram.tab.c\n"
 92+    if [ "$YACC" = "bison -y" ]; then
 93+      bison -d -o "$dir/awkgram.tab.c" "$dir/awkgram.y"
 94+    else
 95+      $YACC -d -o "$dir/awkgram.tab.c" "$dir/awkgram.y"
 96+      if [ ! -f "$dir/awkgram.tab.h" ]; then
 97+        if [ -f y.tab.h ]; then
 98+          mv y.tab.h "$dir/awkgram.tab.h"
 99+        elif [ -f "$dir/y.tab.h" ]; then
100+          mv "$dir/y.tab.h" "$dir/awkgram.tab.h"
101+        fi
102+      fi
103+    fi
104+  fi
105+
106+  if newer_than "$dir/maketab.c" "$dir/maketab" || newer_than "$dir/awkgram.tab.h" "$dir/maketab"; then
107+    printf "  CC    $dir/maketab\n"
108+    $CC -o "$dir/maketab" "$dir/maketab.c"
109+  fi
110+
111+  if newer_than "$dir/maketab" "$dir/proctab.c" || newer_than "$dir/awkgram.tab.h" "$dir/proctab.c"; then
112+    printf "  GEN   $dir/proctab.c\n"
113+    "$dir/maketab" "$dir/awkgram.tab.h" > "$dir/proctab.c"
114+  fi
115+}
116 
117-echo "ARFLAGS = ${ARFLAGS:-rc}" >> "${mk_file}"
118-echo "ARFLAGS=\"${ARFLAGS:-rc}\"" >> "${kv_file}"
119+gen_sh() {
120+  local dir="cmd/posix/sh"
121+  CC=${CC:-cc}
122+
123+  if newer_than "$dir/mknodes.c" "$dir/mknodes"; then
124+    printf "  CC    $dir/mknodes\n"
125+    $CC -o "$dir/mknodes" "$dir/mknodes.c"
126+  fi
127+
128+  if newer_than "$dir/mksyntax.c" "$dir/mksyntax"; then
129+    printf "  CC    $dir/mksyntax\n"
130+    $CC -Ishared -I"$dir" -o "$dir/mksyntax" "$dir/mksyntax.c"
131+  fi
132+
133+  if newer_than "$dir/mktokens" "$dir/token.h"; then
134+    printf "  GEN   $dir/token.h\n"
135+    (cd "$dir" && sh mktokens)
136+  fi
137+
138+  if newer_than "$dir/mksyntax" "$dir/syntax.c" || newer_than "$dir/mksyntax" "$dir/syntax.h"; then
139+    printf "  GEN   $dir/syntax.c\n"
140+    (cd "$dir" && ./mksyntax)
141+  fi
142+
143+  if newer_than "$dir/mknodes" "$dir/nodes.c" || newer_than "$dir/nodetypes" "$dir/nodes.c" || newer_than "$dir/nodes.c.pat" "$dir/nodes.c"; then
144+    printf "  GEN   $dir/nodes.c\n"
145+    (cd "$dir" && ./mknodes nodetypes nodes.c.pat)
146+  fi
147+
148+  if newer_than "$dir/mkbuiltins" "$dir/builtins.c" || newer_than "$dir/builtins.def" "$dir/builtins.c" || newer_than "$dir/shell.h" "$dir/builtins.c" || [ ! -f "$dir/builtins.c" ]; then
149+    printf "  GEN   $dir/builtins.c\n"
150+    (cd "$dir" && sh mkbuiltins .)
151+  fi
152+}
153 
154-# build toggles
155-echo "" >> "${mk_file}"
156-echo "# build toggles" >> "${mk_file}"
157-for g in BUILD_POSIX BUILD_XSI BUILD_NET BUILD_PSEUDO BUILD_EXTRA BUILD_LINUX BUILD_DEV; do
158-        eval "val=\${$g:-0}"
159-        echo "$g = $val" >> "${mk_file}"
160-        echo "$g=\"$val\"" >> "${kv_file}"
161-done
162+clean() {
163+  printf "  CLEAN generated files\n"
164+  rm -f cmd/posix/getconf.h
165+  rm -f cmd/posix/bc.c cmd/posix/bc.h
166+  rm -f cmd/posix/awk/awkgram.tab.c cmd/posix/awk/awkgram.tab.h cmd/posix/awk/proctab.c cmd/posix/awk/maketab
167+  rm -f cmd/posix/sh/mknodes cmd/posix/sh/mksyntax cmd/posix/sh/token.h cmd/posix/sh/syntax.c cmd/posix/sh/syntax.h cmd/posix/sh/nodes.c cmd/posix/sh/nodes.h cmd/posix/sh/builtins.c cmd/posix/sh/builtins.h
168+  rm -f scripts/mk/config.mk scripts/mk/config.kv scripts/mk/rules.mk shared/libutil/nofork_list.h
169+}
170 
171 # dynamic tool discovery function
172 list_tools() {
173@@ -100,110 +185,6 @@ write_subtarget() {
174         fi
175 }
176 
177-# write subtargets for each category dynamically
178-for catg in posix xsi net pseudo extra linux dev; do
179-        echo "" >> "${mk_file}"
180-        echo "# ${catg} subtargets" >> "${mk_file}"
181-        tools=$(list_tools "$catg")
182-        for t in $tools; do
183-                write_subtarget "$catg" "$t"
184-        done
185-
186-        # build the enabled list using lowercase tools
187-        enabled=""
188-        for t in $tools; do
189-                u_group=$(echo "$catg" | tr '[:lower:]' '[:upper:]')
190-                u_tool=$(echo "$t" | tr 'a-z-' 'A-Z_')
191-
192-                # special cases for dev tools mapping to different variables
193-                if [ "$catg" = "dev" ] && [ "$u_tool" = "AS" ]; then
194-                        var_name="BUILD_DEV_CC"
195-                else
196-                        var_name="BUILD_${u_group}_${u_tool}"
197-                fi
198-
199-                eval "has_override=\${$var_name+yes}"
200-                eval "val=\${$var_name:-}"
201-                if [ "$has_override" != "yes" ]; then
202-                        if [ "$catg" = "dev" ] && [ "$u_tool" = "AS" ]; then
203-                                eval "val=\$BUILD_DEV"
204-                        else
205-                                eval "val=\$BUILD_${u_group}"
206-                        fi
207-                fi
208-
209-                if [ "$val" = "1" ]; then
210-                        # map to the correct path
211-                        case "$catg/$t" in
212-                                posix/awk)  path="cmd/posix/awk/awk" ;;
213-                                posix/sh)   path="cmd/posix/sh/sh" ;;
214-                                posix/make) path="cmd/posix/make/make" ;;
215-                                extra/yap)  path="cmd/extra/yap/yap" ;;
216-                                dev/ar)     path="cmd/dev/ar/ar" ;;
217-                                dev/as)     path="cmd/dev/as/as" ;;
218-                                dev/ld)     path="cmd/dev/ld/ld" ;;
219-                                dev/cc)     path="cmd/dev/cc/cc" ;;
220-                                *)          path="cmd/$catg/$t" ;;
221-                        esac
222-                        enabled="$enabled $path"
223-                fi
224-        done
225-        u_catg=$(echo "$catg" | tr '[:lower:]' '[:upper:]')
226-        # write the list of enabled binaries
227-        echo "${u_catg}_BIN_CONFIG =${enabled}" >> "${mk_file}"
228-done
229-
230-# write feature flags
231-echo "" >> "${mk_file}"
232-echo "# feature flags" >> "${mk_file}"
233-features=$(set | grep '^FEATURE_[A-Za-z0-9_]*=' | cut -d= -f1 | sort)
234-for f in $features; do
235-        eval "val=\$$f"
236-        echo "$f = $val" >> "${mk_file}"
237-        echo "$f=\"$val\"" >> "${kv_file}"
238-done
239-
240-# write TLS variables to config files
241-echo "" >> "${mk_file}"
242-echo "CPPFLAGS_TLS = ${CPPFLAGS_TLS}" >> "${mk_file}"
243-echo "CPPFLAGS_TLS=\"${CPPFLAGS_TLS}\"" >> "${kv_file}"
244-
245-echo "LDFLAGS_TLS = ${LDFLAGS_TLS}" >> "${mk_file}"
246-echo "LDFLAGS_TLS=\"${LDFLAGS_TLS}\"" >> "${kv_file}"
247-
248-echo "LDLIBS_TLS = ${LDLIBS_TLS}" >> "${mk_file}"
249-echo "LDLIBS_TLS=\"${LDLIBS_TLS}\"" >> "${kv_file}"
250-
251-# write CPPFLAGS with all feature macros
252-echo "" >> "${mk_file}"
253-echo "CPPFLAGS =\\" >> "${mk_file}"
254-echo "        -Ishared\\" >> "${mk_file}"
255-echo "        -DPREFIX=\\\"\\\$(PREFIX)\\\"\\" >> "${mk_file}"
256-echo "        -D_DEFAULT_SOURCE\\" >> "${mk_file}"
257-echo "        -D_GNU_SOURCE\\" >> "${mk_file}"
258-echo "        -D_NETBSD_SOURCE\\" >> "${mk_file}"
259-echo "        -D_BSD_SOURCE\\" >> "${mk_file}"
260-echo "        -D_XOPEN_SOURCE=700\\" >> "${mk_file}"
261-echo "        -D_FILE_OFFSET_BITS=64\\" >> "${mk_file}"
262-for f in $features; do
263-        if [ "$f" = "FEATURE_USE_SSL" ]; then
264-                continue
265-        fi
266-        echo "        -D$f=\$($f)\\" >> "${mk_file}"
267-done
268-echo "        \$(CPPFLAGS_TLS)" >> "${mk_file}"
269-
270-# write toolchain configuration
271-echo "" >> "${mk_file}"
272-echo "CC = ${CC:-cc}" >> "${mk_file}"
273-echo "CFLAGS = ${CFLAGS:-}" >> "${mk_file}"
274-echo "LDFLAGS = ${LDFLAGS:-} \$(LDFLAGS_TLS)" >> "${mk_file}"
275-echo "LDLIBS = ${LDLIBS:-} \$(LDLIBS_TLS)" >> "${mk_file}"
276-
277-# write compilation rules for subdirectories that need special flags or nested structures
278-rules_file="scripts/mk/rules.mk"
279-echo "# autogenerated compilation rules" > "${rules_file}"
280-
281 gen_dir_rules() {
282         local dir="$1" flags="$2" extra_files="$3" f obj files uniq_files
283         [ -d "$dir" ] || return 0
284@@ -224,13 +205,186 @@ gen_dir_rules() {
285         done
286 }
287 
288-echo "" >> "${rules_file}"
289-gen_dir_rules "cmd/posix/awk" "-Icmd/posix/awk" "cmd/posix/awk/awkgram.tab.c cmd/posix/awk/proctab.c"
290-gen_dir_rules "cmd/posix/sh" "-DSHELL -Icmd/posix/sh" "cmd/posix/sh/syntax.c cmd/posix/sh/nodes.c cmd/posix/sh/builtins.c"
291-gen_dir_rules "cmd/extra/yap" "" ""
292-gen_dir_rules "cmd/dev/ar" "-Icmd/dev/ar" ""
293-gen_dir_rules "cmd/dev/xcutil" "-Icmd/dev/xcutil" ""
294-gen_dir_rules "cmd/dev/ld" "-Icmd/dev/xcutil -Icmd/dev/ld" ""
295-gen_dir_rules "cmd/dev/as" "-Icmd/dev/xcutil -Icmd/dev/as" ""
296-gen_dir_rules "cmd/dev/as/arch/x64" "-Icmd/dev/xcutil -Icmd/dev/as -Icmd/dev/as/arch/x64" ""
297-gen_dir_rules "cmd/dev/cc" "-Icmd/dev/cc" ""
298+case "$target" in
299+        getconf) gen_getconf ;;
300+        bc)      gen_bc ;;
301+        awk)     gen_awk ;;
302+        sh)      gen_sh ;;
303+        clean)   clean ;;
304+        all)
305+                gen_getconf
306+                gen_bc
307+                gen_awk
308+                gen_sh
309+                ;;
310+        config)
311+                mkdir -p scripts/mk
312+
313+                mk_file="scripts/mk/config.mk"
314+                kv_file="scripts/mk/config.kv"
315+
316+                # clear existing files
317+                : > "${mk_file}"
318+                : > "${kv_file}"
319+
320+                # write version prefix manprefix
321+                echo "VERSION = ${VERSION}" >> "${mk_file}"
322+                echo "VERSION=\"${VERSION}\"" >> "${kv_file}"
323+
324+                echo "PREFIX = ${PREFIX}" >> "${mk_file}"
325+                echo "PREFIX=\"${PREFIX}\"" >> "${kv_file}"
326+
327+                # default manprefix to prefix/share/man if empty
328+                man_pref="${MANPREFIX:-/usr/local/share/man}"
329+                echo "MANPREFIX = ${man_pref}" >> "${mk_file}"
330+                echo "MANPREFIX=\"${man_pref}\"" >> "${kv_file}"
331+
332+                # toolchain defaults
333+                echo "RANLIB = ${RANLIB:-ranlib}" >> "${mk_file}"
334+                echo "RANLIB=\"${RANLIB:-ranlib}\"" >> "${kv_file}"
335+
336+                echo "AR = ${AR:-ar}" >> "${mk_file}"
337+                echo "AR=\"${AR:-ar}\"" >> "${kv_file}"
338+
339+                echo "ARFLAGS = ${ARFLAGS:-rc}" >> "${mk_file}"
340+                echo "ARFLAGS=\"${ARFLAGS:-rc}\"" >> "${kv_file}"
341+
342+                # build toggles
343+                echo "" >> "${mk_file}"
344+                echo "# build toggles" >> "${mk_file}"
345+                for g in BUILD_POSIX BUILD_XSI BUILD_NET BUILD_PSEUDO BUILD_EXTRA BUILD_LINUX BUILD_DEV; do
346+                        eval "val=\${$g:-0}"
347+                        echo "$g = $val" >> "${mk_file}"
348+                        echo "$g=\"$val\"" >> "${kv_file}"
349+                done
350+
351+                # write subtargets for each category dynamically
352+                for catg in posix xsi net pseudo extra linux dev; do
353+                        echo "" >> "${mk_file}"
354+                        echo "# ${catg} subtargets" >> "${mk_file}"
355+                        tools=$(list_tools "$catg")
356+                        for t in $tools; do
357+                                write_subtarget "$catg" "$t"
358+                        done
359+
360+                        # build the enabled list using lowercase tools
361+                        enabled=""
362+                        for t in $tools; do
363+                                u_group=$(echo "$catg" | tr '[:lower:]' '[:upper:]')
364+                                u_tool=$(echo "$t" | tr 'a-z-' 'A-Z_')
365+
366+                                # special cases for dev tools mapping to different variables
367+                                if [ "$catg" = "dev" ] && [ "$u_tool" = "AS" ]; then
368+                                        var_name="BUILD_DEV_CC"
369+                                else
370+                                        var_name="BUILD_${u_group}_${u_tool}"
371+                                fi
372+
373+                                eval "has_override=\${$var_name+yes}"
374+                                eval "val=\${$var_name:-}"
375+                                if [ "$has_override" != "yes" ]; then
376+                                        if [ "$catg" = "dev" ] && [ "$u_tool" = "AS" ]; then
377+                                                eval "val=\$BUILD_DEV"
378+                                        else
379+                                                eval "val=\$BUILD_${u_group}"
380+                                        fi
381+                                fi
382+
383+                                if [ "$val" = "1" ]; then
384+                                        # map to the correct path
385+                                        case "$catg/$t" in
386+                                                posix/awk)  path="cmd/posix/awk/awk" ;;
387+                                                posix/sh)   path="cmd/posix/sh/sh" ;;
388+                                                posix/make) path="cmd/posix/make/make" ;;
389+                                                extra/yap)  path="cmd/extra/yap/yap" ;;
390+                                                dev/ar)     path="cmd/dev/ar/ar" ;;
391+                                                dev/as)     path="cmd/dev/as/as" ;;
392+                                                dev/ld)     path="cmd/dev/ld/ld" ;;
393+                                                dev/cc)     path="cmd/dev/cc/cc" ;;
394+                                                *)          path="cmd/$catg/$t" ;;
395+                                        esac
396+                                        enabled="$enabled $path"
397+                                fi
398+                        done
399+                        u_catg=$(echo "$catg" | tr '[:lower:]' '[:upper:]')
400+                        # write the list of enabled binaries
401+                        echo "${u_catg}_BIN_CONFIG =${enabled}" >> "${mk_file}"
402+                done
403+
404+                # write feature flags
405+                echo "" >> "${mk_file}"
406+                echo "# feature flags" >> "${mk_file}"
407+                features=$(set | grep '^FEATURE_[A-Za-z0-9_]*=' | cut -d= -f1 | sort)
408+                for f in $features; do
409+                        eval "val=\$$f"
410+                        echo "$f = $val" >> "${mk_file}"
411+                        echo "$f=\"$val\"" >> "${kv_file}"
412+                done
413+
414+                # write TLS variables to config files
415+                echo "" >> "${mk_file}"
416+                echo "CPPFLAGS_TLS = ${CPPFLAGS_TLS}" >> "${mk_file}"
417+                echo "CPPFLAGS_TLS=\"${CPPFLAGS_TLS}\"" >> "${kv_file}"
418+
419+                echo "LDFLAGS_TLS = ${LDFLAGS_TLS}" >> "${mk_file}"
420+                echo "LDFLAGS_TLS=\"${LDFLAGS_TLS}\"" >> "${kv_file}"
421+
422+                echo "LDLIBS_TLS = ${LDLIBS_TLS}" >> "${mk_file}"
423+                echo "LDLIBS_TLS=\"${LDLIBS_TLS}\"" >> "${kv_file}"
424+
425+                # write CPPFLAGS with all feature macros
426+                echo "" >> "${mk_file}"
427+                echo "CPPFLAGS =\\" >> "${mk_file}"
428+                echo "        -Ishared\\" >> "${mk_file}"
429+                echo "        -DPREFIX=\\\"\\\$(PREFIX)\\\"\\" >> "${mk_file}"
430+                echo "        -D_DEFAULT_SOURCE\\" >> "${mk_file}"
431+                echo "        -D_GNU_SOURCE\\" >> "${mk_file}"
432+                echo "        -D_NETBSD_SOURCE\\" >> "${mk_file}"
433+                echo "        -D_BSD_SOURCE\\" >> "${mk_file}"
434+                echo "        -D_XOPEN_SOURCE=700\\" >> "${mk_file}"
435+                echo "        -D_FILE_OFFSET_BITS=64\\" >> "${mk_file}"
436+                for f in $features; do
437+                        if [ "$f" = "FEATURE_USE_SSL" ]; then
438+                                continue
439+                        fi
440+                        echo "        -D$f=\$($f)\\" >> "${mk_file}"
441+                done
442+                echo "        \$(CPPFLAGS_TLS)" >> "${mk_file}"
443+
444+                # write toolchain configuration
445+                echo "" >> "${mk_file}"
446+                echo "CC = ${CC:-cc}" >> "${mk_file}"
447+                echo "CFLAGS = ${CFLAGS:-}" >> "${mk_file}"
448+                echo "LDFLAGS = ${LDFLAGS:-} \$(LDFLAGS_TLS)" >> "${mk_file}"
449+                echo "LDLIBS = ${LDLIBS:-} \$(LDLIBS_TLS)" >> "${mk_file}"
450+
451+                # write compilation rules for subdirectories that need special flags or nested structures
452+                rules_file="scripts/mk/rules.mk"
453+                echo "# autogenerated compilation rules" > "${rules_file}"
454+
455+                echo "" >> "${rules_file}"
456+                gen_dir_rules "cmd/posix/awk" "-Icmd/posix/awk" "cmd/posix/awk/awkgram.tab.c cmd/posix/awk/proctab.c"
457+                gen_dir_rules "cmd/posix/sh" "-DSHELL -Icmd/posix/sh" "cmd/posix/sh/syntax.c cmd/posix/sh/nodes.c cmd/posix/sh/builtins.c"
458+                gen_dir_rules "cmd/extra/yap" "" ""
459+                gen_dir_rules "cmd/dev/ar" "-Icmd/dev/ar" ""
460+                gen_dir_rules "cmd/dev/xcutil" "-Icmd/dev/xcutil" ""
461+                gen_dir_rules "cmd/dev/ld" "-Icmd/dev/xcutil -Icmd/dev/ld" ""
462+                gen_dir_rules "cmd/dev/as" "-Icmd/dev/xcutil -Icmd/dev/as" ""
463+                gen_dir_rules "cmd/dev/as/arch/x64" "-Icmd/dev/xcutil -Icmd/dev/as -Icmd/dev/as/arch/x64" ""
464+                gen_dir_rules "cmd/dev/cc" "-Icmd/dev/cc" ""
465+
466+                # generate nofork_list.h
467+                nofork_h="shared/libutil/nofork_list.h"
468+                echo "/* autogenerated by genconfig.sh */" > "${nofork_h}"
469+                echo "static const char *nofork_list[] = {" >> "${nofork_h}"
470+                for app in ${NOFORK_LIST}; do
471+                        echo "  \"${app}\"," >> "${nofork_h}"
472+                done
473+                echo "  NULL" >> "${nofork_h}"
474+                echo "};" >> "${nofork_h}"
475+                ;;
476+        *)
477+                printf "unknown target: %s\n" "$target" >&2
478+                exit 1
479+                ;;
480+esac
+10, -4
 1@@ -118,9 +118,15 @@
 2 #ifndef FEATURE_PATCH_STRIP_CR
 3 #define FEATURE_PATCH_STRIP_CR 1
 4 #endif
 5-#ifndef FEATURE_INPROC_EXEC
 6-#define FEATURE_INPROC_EXEC 0
 7+#ifndef FEATURE_NOEXEC
 8+#define FEATURE_NOEXEC 0
 9 #endif
10-#ifndef FEATURE_INPROC_EXEC_OVERRIDE
11-#define FEATURE_INPROC_EXEC_OVERRIDE 0
12+#ifndef FEATURE_NOEXEC_OVERRIDE
13+#define FEATURE_NOEXEC_OVERRIDE 0
14+#endif
15+#ifndef FEATURE_NOFORK
16+#define FEATURE_NOFORK 0
17+#endif
18+#ifndef FEATURE_NOFORK_OVERRIDE
19+#define FEATURE_NOFORK_OVERRIDE 0
20 #endif
+30, -1
 1@@ -38,6 +38,8 @@
 2 #define CTRL_U    21
 3 #define CTRL_W    23
 4 #define CTRL_Y    25
 5+#define CTRL_Z    26
 6+#define CTRL_QUIT 28
 7 #define BACKSPACE 127
 8 #define ESC       27
 9 
10@@ -73,6 +75,8 @@ static char                 *kill_buffer     = NULL;
11 static volatile sig_atomic_t winch_received  = 0;
12 static struct sigaction      orig_sigwinch;
13 
14+static void redlineEditStop(struct redlineState *l);
15+
16 static void (*completionCallback)(const char *, struct redlineCompletions *) = NULL;
17 
18 /* return the number of bytes that compose the utf-8 character starting at c */
19@@ -330,7 +334,7 @@ enableRawMode(int fd)
20     return -1;
21 
22   raw = orig_termios;
23-  raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
24+  raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP);
25   raw.c_oflag &= ~(OPOST);
26   raw.c_cflag |= (CS8);
27   raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
28@@ -1189,13 +1193,33 @@ redlineEditFeed(struct redlineState *l)
29   }
30 
31   switch (c) {
32+    case 10:
33     case ENTER:
34       if (mlmode)
35         redlineEditMoveEnd(l);
36       return strdup(l->buf);
37     case CTRL_C:
38+      if (write(l->ofd, "^C", 2) == -1) {}
39       errno = EAGAIN;
40       return NULL;
41+    case CTRL_Z:
42+      break;
43+    case CTRL_QUIT:
44+      if (write(l->ofd, "^\\", 2) == -1) {}
45+      redlineEditStop(l);
46+      fflush(stdout);
47+      {
48+        struct sigaction sa, osa;
49+        sa.sa_handler = SIG_DFL;
50+        sigemptyset(&sa.sa_mask);
51+        sa.sa_flags = 0;
52+        sigaction(SIGQUIT, &sa, &osa);
53+        kill(getpid(), SIGQUIT);
54+        sigaction(SIGQUIT, &osa, NULL);
55+      }
56+      enableRawMode(l->ifd);
57+      refreshLine(l);
58+      break;
59     case BACKSPACE:
60     case 8:
61       redlineEditBackspace(l);
62@@ -1321,6 +1345,8 @@ redlineEditFeed(struct redlineState *l)
63       }
64       break;
65     default:
66+      if (c < 32)
67+        break;
68       utf8len = utf8ByteLen(c);
69       utf8[0] = c;
70       if (utf8len > 1) {
71@@ -1441,6 +1467,9 @@ redline(const char *prompt)
72     free(history[history_len]);
73   }
74   free(l.buf);
75+  if (res == NULL && errno == EAGAIN) {
76+    kill(getpid(), SIGINT);
77+  }
78   return res;
79 }
80 
+295, -76
  1@@ -5,6 +5,7 @@
  2 #include "../util.h"
  3 
  4 #include <errno.h>
  5+#include <setjmp.h>
  6 #include <signal.h>
  7 #include <stdlib.h>
  8 #include <string.h>
  9@@ -12,7 +13,7 @@
 10 #include <sys/wait.h>
 11 #include <unistd.h>
 12 
 13-#define MAX_BUILTINS 128
 14+#define MAX_BUILTINS 256
 15 #define MAX_POPEN    32
 16 
 17 struct builtin {
 18@@ -31,6 +32,22 @@ struct popen_entry {
 19 static struct popen_entry popen_tab[MAX_POPEN];
 20 static int                npopen;
 21 
 22+static char *(*wexec_env_lookup)(const char *name) = NULL;
 23+
 24+void
 25+wexec_register_env_lookup(char *(*fn)(const char *name))
 26+{
 27+  wexec_env_lookup = fn;
 28+}
 29+
 30+static const char *
 31+wexec_getenv(const char *name)
 32+{
 33+  if (wexec_env_lookup)
 34+    return wexec_env_lookup(name);
 35+  return getenv(name);
 36+}
 37+
 38 int
 39 wpopen_track(FILE *fp, pid_t pid)
 40 {
 41@@ -85,49 +102,77 @@ wexec_is_builtin(const char *name)
 42   return 0;
 43 }
 44 
 45-#if FEATURE_INPROC_EXEC
 46-static int inproc_enabled  = 1;
 47+int
 48+wexec_is_nofork(const char *name)
 49+{
 50+#include "nofork_list.h"
 51+  int i;
 52+  for (i = 0; nofork_list[i]; i++) {
 53+    if (strcmp(nofork_list[i], name) == 0)
 54+      return 1;
 55+  }
 56+  return 0;
 57+}
 58 
 59-#if FEATURE_INPROC_EXEC_OVERRIDE
 60-static int inproc_env_read = 0;
 61+#if FEATURE_NOEXEC
 62+static int noexec_enabled = 1;
 63 
 64-/* ARUU_INPROC_EXEC=0 forces fork+exec at runtime, even though the box was
 65- * built with FEATURE_INPROC_EXEC=1. lets a person reach an external tool
 66- * that shares a name with a builtin without giving its full path. only
 67- * compiled in when the box is also built with FEATURE_INPROC_EXEC_OVERRIDE=1;
 68- * without that flag this env var is never read and inproc dispatch cannot be
 69- * bypassed except through an explicit wexec_set_inproc() call. read once
 70- * and cached, an explicit wexec_set_inproc() call overrides it */
 71+#if FEATURE_NOEXEC_OVERRIDE
 72 static void
 73-inproc_read_env(void)
 74+noexec_read_env(void)
 75 {
 76-  const char *e;
 77-
 78-  if (inproc_env_read)
 79-    return;
 80-  inproc_env_read = 1;
 81-  e = getenv("ARUU_INPROC_EXEC");
 82+  const char *e = wexec_getenv("ARUU_NOEXEC");
 83   if (e && strcmp(e, "0") == 0)
 84-    inproc_enabled = 0;
 85+    noexec_enabled = 0;
 86+  else
 87+    noexec_enabled = 1;
 88 }
 89 #endif
 90 
 91 void
 92-wexec_set_inproc(int enabled)
 93+wexec_set_noexec(int enabled)
 94+{
 95+  noexec_enabled = enabled;
 96+}
 97+
 98+int
 99+wexec_get_noexec(void)
100+{
101+#if FEATURE_NOEXEC_OVERRIDE
102+  noexec_read_env();
103+#endif
104+  return noexec_enabled;
105+}
106+#endif
107+
108+#if FEATURE_NOFORK
109+static int nofork_enabled = 1;
110+
111+#if FEATURE_NOFORK_OVERRIDE
112+static void
113+nofork_read_env(void)
114 {
115-#if FEATURE_INPROC_EXEC_OVERRIDE
116-  inproc_env_read = 1;
117+  const char *e = wexec_getenv("ARUU_NOFORK");
118+  if (e && strcmp(e, "0") == 0)
119+    nofork_enabled = 0;
120+  else
121+    nofork_enabled = 1;
122+}
123 #endif
124-  inproc_enabled = enabled;
125+
126+void
127+wexec_set_nofork(int enabled)
128+{
129+  nofork_enabled = enabled;
130 }
131 
132 int
133-wexec_get_inproc(void)
134+wexec_get_nofork(void)
135 {
136-#if FEATURE_INPROC_EXEC_OVERRIDE
137-  inproc_read_env();
138+#if FEATURE_NOFORK_OVERRIDE
139+  nofork_read_env();
140 #endif
141-  return inproc_enabled;
142+  return nofork_enabled;
143 }
144 #endif
145 
146@@ -137,11 +182,11 @@ wwhich(const char *name)
147   const char *path, *p, *end;
148   char       *buf;
149   size_t      dlen, nlen;
150-  struct stat  st;
151+  struct stat st;
152 
153-#if FEATURE_INPROC_EXEC
154-  /* when inproc dispatch is active, registered builtins have no filesystem path */
155-  if (inproc_enabled && wexec_is_builtin(name))
156+#if FEATURE_NOEXEC
157+  /* when builtin dispatch is active, registered builtins have no filesystem path */
158+  if (wexec_get_noexec() && wexec_is_builtin(name))
159     return NULL;
160 #endif
161 
162@@ -152,7 +197,7 @@ wwhich(const char *name)
163     return NULL;
164   }
165 
166-  path = getenv("PATH");
167+  path = wexec_getenv("PATH");
168   if (path == NULL)
169     path = "/usr/local/bin:/usr/bin:/bin";
170 
171@@ -163,9 +208,9 @@ wwhich(const char *name)
172     dlen = end ? (size_t)(end - p) : strlen(p);
173     /* skip empty components (current dir) for safety */
174     if (dlen > 0) {
175-      buf           = emalloc(dlen + 1 + nlen + 1);
176+      buf = emalloc(dlen + 1 + nlen + 1);
177       memcpy(buf, p, dlen);
178-      buf[dlen]     = '/';
179+      buf[dlen] = '/';
180       memcpy(buf + dlen + 1, name, nlen + 1);
181       if (stat(buf, &st) == 0 && S_ISREG(st.st_mode) && (st.st_mode & 0111))
182         return buf;
183@@ -178,13 +223,50 @@ wwhich(const char *name)
184   return NULL;
185 }
186 
187-#if FEATURE_INPROC_EXEC
188-static int
189-call_builtin(const char *name, char *const *argv)
190+#if FEATURE_NOEXEC || FEATURE_NOFORK
191+static sigjmp_buf            inproc_jmp;
192+static volatile sig_atomic_t inproc_active      = 0;
193+static volatile int          inproc_exit_status = 0;
194+
195+static void
196+inproc_sig_handler(int signo)
197+{
198+  if (inproc_active) {
199+    siglongjmp(inproc_jmp, signo);
200+  }
201+}
202+
203+int
204+wexec_call_builtin(const char *name, char *const *argv)
205 {
206-  int    i, argc;
207-  char **p;
208+  struct sigaction sa, osa_int, osa_quit, osa_pipe;
209+  int              i, argc, ret, sig;
210+  char           **p;
211+
212+  sig = sigsetjmp(inproc_jmp, 1);
213+  if (sig != 0) {
214+    inproc_active = 0;
215+    sigaction(SIGINT, &osa_int, NULL);
216+    sigaction(SIGQUIT, &osa_quit, NULL);
217+    sigaction(SIGPIPE, &osa_pipe, NULL);
218+    if (sig == 1)
219+      return inproc_exit_status;
220+    kill(getpid(), sig);
221+    return 128 + sig;
222+  }
223+
224+  sa.sa_handler = inproc_sig_handler;
225+  sigemptyset(&sa.sa_mask);
226+  sa.sa_flags = 0;
227+  sigaction(SIGINT, &sa, &osa_int);
228+  sigaction(SIGQUIT, &sa, &osa_quit);
229 
230+  sa.sa_handler = SIG_IGN;
231+  sigaction(SIGPIPE, &sa, &osa_pipe);
232+
233+  inproc_active = 1;
234+
235+  ret  = -1;
236   argc = 0;
237   for (p = (char **)argv; *p; p++)
238     argc++;
239@@ -192,10 +274,20 @@ call_builtin(const char *name, char *const *argv)
240     if (strcmp(builtins[i].name, name) == 0) {
241       fflush(stdout);
242       fflush(stderr);
243-      return builtins[i].fn(argc, (char **)argv);
244+      ret = builtins[i].fn(argc, (char **)argv);
245+      fflush(stdout);
246+      fflush(stderr);
247+      break;
248     }
249   }
250-  return -1;
251+
252+  inproc_active = 0;
253+
254+  sigaction(SIGINT, &osa_int, NULL);
255+  sigaction(SIGQUIT, &osa_quit, NULL);
256+  sigaction(SIGPIPE, &osa_pipe, NULL);
257+
258+  return ret;
259 }
260 #endif
261 
262@@ -211,6 +303,26 @@ fork_exec(const char *path, char *const *argv, int use_path)
263     return -1;
264   }
265   if (pid == 0) {
266+    const char *base = strrchr(path, '/');
267+    base             = base ? base + 1 : path;
268+#if FEATURE_NOEXEC
269+    if (wexec_get_noexec() && wexec_is_builtin(base)) {
270+      struct sigaction sa;
271+      int              i, ret;
272+      for (i = 1; i < NSIG; i++) {
273+        if (sigaction(i, NULL, &sa) == 0) {
274+          if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
275+            sa.sa_handler = SIG_DFL;
276+            sa.sa_flags   = 0;
277+            sigemptyset(&sa.sa_mask);
278+            sigaction(i, &sa, NULL);
279+          }
280+        }
281+      }
282+      ret = wexec_call_builtin(base, argv);
283+      _exit(ret);
284+    }
285+#endif
286     if (use_path)
287       execvp(path, argv);
288     else
289@@ -231,11 +343,11 @@ fork_exec(const char *path, char *const *argv, int use_path)
290 int
291 wexecvp(const char *name, char *const *argv)
292 {
293-#if FEATURE_INPROC_EXEC
294-  if (inproc_enabled && wexec_is_builtin(name)) {
295+#if FEATURE_NOFORK
296+  if (wexec_get_nofork() && wexec_is_builtin(name) && wexec_is_nofork(name)) {
297     fflush(stdout);
298     fflush(stderr);
299-    return call_builtin(name, argv);
300+    return wexec_call_builtin(name, argv);
301   }
302 #endif
303   return fork_exec(name, argv, 1);
304@@ -244,15 +356,13 @@ wexecvp(const char *name, char *const *argv)
305 int
306 wexecv(const char *path, char *const *argv)
307 {
308-#if FEATURE_INPROC_EXEC
309-  {
310-    const char *base = strrchr(path, '/');
311-    base             = base ? base + 1 : path;
312-    if (inproc_enabled && wexec_is_builtin(base)) {
313-      fflush(stdout);
314-      fflush(stderr);
315-      return call_builtin(base, argv);
316-    }
317+  const char *base = strrchr(path, '/');
318+  base             = base ? base + 1 : path;
319+#if FEATURE_NOFORK
320+  if (wexec_get_nofork() && wexec_is_builtin(base) && wexec_is_nofork(base)) {
321+    fflush(stdout);
322+    fflush(stderr);
323+    return wexec_call_builtin(base, argv);
324   }
325 #endif
326   return fork_exec(path, argv, 0);
327@@ -265,6 +375,10 @@ wpopen(const char *name, char *const *argv, const char *mode)
328   pid_t pid;
329   int   parent_end, child_end;
330   char *sh_argv[4];
331+#if FEATURE_NOEXEC
332+  struct sigaction sa;
333+  int              i;
334+#endif
335 
336   if (pipe(pfd) < 0) {
337     weprintf("pipe:");
338@@ -294,14 +408,27 @@ wpopen(const char *name, char *const *argv, const char *mode)
339       close(child_end);
340     }
341     if (argv) {
342-#if FEATURE_INPROC_EXEC
343-      if (inproc_enabled && wexec_is_builtin(name)) {
344+#if FEATURE_NOEXEC
345+      if (wexec_get_noexec() && wexec_is_builtin(name)) {
346         int    argc;
347         char **p;
348+
349+        /* reset caught signal handlers to default for inproc command */
350+        for (i = 1; i < NSIG; i++) {
351+          if (sigaction(i, NULL, &sa) == 0) {
352+            if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
353+              sa.sa_handler = SIG_DFL;
354+              sa.sa_flags   = 0;
355+              sigemptyset(&sa.sa_mask);
356+              sigaction(i, &sa, NULL);
357+            }
358+          }
359+        }
360+
361         argc = 0;
362         for (p = (char **)argv; *p; p++)
363           argc++;
364-        call_builtin(name, argv);
365+        wexec_call_builtin(name, argv);
366         fflush(stdout);
367         fflush(stderr);
368         _exit(0);
369@@ -314,9 +441,21 @@ wpopen(const char *name, char *const *argv, const char *mode)
370       sh_argv[1] = "-c";
371       sh_argv[2] = (char *)name;
372       sh_argv[3] = NULL;
373-#if FEATURE_INPROC_EXEC
374-      if (inproc_enabled && wexec_is_builtin("sh")) {
375-        call_builtin("sh", sh_argv);
376+#if FEATURE_NOEXEC
377+      if (wexec_get_noexec() && wexec_is_builtin("sh")) {
378+        /* reset caught signal handlers to default for inproc command */
379+        for (i = 1; i < NSIG; i++) {
380+          if (sigaction(i, NULL, &sa) == 0) {
381+            if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
382+              sa.sa_handler = SIG_DFL;
383+              sa.sa_flags   = 0;
384+              sigemptyset(&sa.sa_mask);
385+              sigaction(i, &sa, NULL);
386+            }
387+          }
388+        }
389+
390+        wexec_call_builtin("sh", sh_argv);
391         fflush(stdout);
392         fflush(stderr);
393         _exit(0);
394@@ -372,14 +511,50 @@ wpclose(FILE *fp)
395 int
396 wsystem(const char *cmd)
397 {
398-#if FEATURE_INPROC_EXEC
399-  if (inproc_enabled && wexec_is_builtin("sh")) {
400-    char *sh_argv[4];
401-    sh_argv[0] = "sh";
402-    sh_argv[1] = "-c";
403-    sh_argv[2] = (char *)cmd;
404-    sh_argv[3] = NULL;
405-    return wexecvp("sh", sh_argv);
406+#if FEATURE_NOEXEC
407+  if (wexec_get_noexec() && wexec_is_builtin("sh")) {
408+    pid_t pid;
409+    int   st;
410+
411+    pid = fork();
412+    if (pid < 0) {
413+      weprintf("fork:");
414+      return -1;
415+    }
416+    if (pid == 0) {
417+      char            *sh_argv[4];
418+      struct sigaction sa;
419+      int              i;
420+      int              ret;
421+
422+      /* reset caught signal handlers to default for inproc command */
423+      for (i = 1; i < NSIG; i++) {
424+        if (sigaction(i, NULL, &sa) == 0) {
425+          if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
426+            sa.sa_handler = SIG_DFL;
427+            sa.sa_flags   = 0;
428+            sigemptyset(&sa.sa_mask);
429+            sigaction(i, &sa, NULL);
430+          }
431+        }
432+      }
433+
434+      sh_argv[0] = "sh";
435+      sh_argv[1] = "-c";
436+      sh_argv[2] = (char *)cmd;
437+      sh_argv[3] = NULL;
438+      ret        = wexec_call_builtin("sh", sh_argv);
439+      _exit(ret);
440+    }
441+    if (waitpid(pid, &st, 0) < 0) {
442+      weprintf("waitpid:");
443+      return -1;
444+    }
445+    if (WIFEXITED(st))
446+      return WEXITSTATUS(st);
447+    if (WIFSIGNALED(st))
448+      return 128 + WTERMSIG(st);
449+    return -1;
450   }
451 #endif
452   {
453@@ -397,12 +572,27 @@ wsystem(const char *cmd)
454 void
455 wexecvp_self(const char *name, char *const *argv)
456 {
457-#if FEATURE_INPROC_EXEC
458-  if (inproc_enabled && wexec_is_builtin(name)) {
459-    int ret;
460+#if FEATURE_NOEXEC
461+  if (wexec_get_noexec() && wexec_is_builtin(name)) {
462+    struct sigaction sa;
463+    int              i;
464+    int              ret;
465+
466+    /* reset caught signal handlers to default for inproc command */
467+    for (i = 1; i < NSIG; i++) {
468+      if (sigaction(i, NULL, &sa) == 0) {
469+        if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
470+          sa.sa_handler = SIG_DFL;
471+          sa.sa_flags   = 0;
472+          sigemptyset(&sa.sa_mask);
473+          sigaction(i, &sa, NULL);
474+        }
475+      }
476+    }
477+
478     fflush(stdout);
479     fflush(stderr);
480-    ret = call_builtin(name, argv);
481+    ret = wexec_call_builtin(name, argv);
482     fflush(stdout);
483     fflush(stderr);
484     exit(ret);
485@@ -416,15 +606,30 @@ wexecvp_self(const char *name, char *const *argv)
486 void
487 wexecv_self(const char *path, char *const *argv)
488 {
489-#if FEATURE_INPROC_EXEC
490+#if FEATURE_NOEXEC
491   {
492     const char *base = strrchr(path, '/');
493     base             = base ? base + 1 : path;
494-    if (inproc_enabled && wexec_is_builtin(base)) {
495-      int ret;
496+    if (wexec_get_noexec() && wexec_is_builtin(base)) {
497+      struct sigaction sa;
498+      int              i;
499+      int              ret;
500+
501+      /* reset caught signal handlers to default for inproc command */
502+      for (i = 1; i < NSIG; i++) {
503+        if (sigaction(i, NULL, &sa) == 0) {
504+          if (sa.sa_handler != SIG_IGN && sa.sa_handler != SIG_DFL) {
505+            sa.sa_handler = SIG_DFL;
506+            sa.sa_flags   = 0;
507+            sigemptyset(&sa.sa_mask);
508+            sigaction(i, &sa, NULL);
509+          }
510+        }
511+      }
512+
513       fflush(stdout);
514       fflush(stderr);
515-      ret = call_builtin(base, argv);
516+      ret = wexec_call_builtin(base, argv);
517       fflush(stdout);
518       fflush(stderr);
519       exit(ret);
520@@ -435,3 +640,17 @@ wexecv_self(const char *path, char *const *argv)
521   weprintf("execv %s:", path);
522   exit((errno == ENOENT) ? 127 : 126);
523 }
524+
525+void(exit)(int);
526+
527+void
528+wexec_exit(int code)
529+{
530+#if FEATURE_NOEXEC || FEATURE_NOFORK
531+  if (inproc_active) {
532+    inproc_exit_status = code;
533+    siglongjmp(inproc_jmp, 1);
534+  }
535+#endif
536+  (exit)(code);
537+}
+6, -0
 1@@ -170,3 +170,9 @@ int net_del_route(const char *, const char *, const char *, const char *, int);
 2 int net_flush_addrs(const char *);
 3 int net_set_txqueuelen(const char *, int);
 4 int net_set_name(const char *, const char *);
 5+
 6+#if FEATURE_NOEXEC || FEATURE_NOFORK
 7+#include "wexec.h"
 8+#define exit(code)  wexec_exit(code)
 9+#define _exit(code) wexec_exit(code)
10+#endif
+24, -15
 1@@ -7,24 +7,33 @@
 2 
 3 typedef int (*wexec_fn)(int argc, char **argv);
 4 
 5-int wexec_register(const char *name, wexec_fn fn);
 6-int wexec_is_builtin(const char *name);
 7+int  wexec_register(const char *name, wexec_fn fn);
 8+int  wexec_is_builtin(const char *name);
 9+void wexec_register_env_lookup(char *(*fn)(const char *name));
10+#if FEATURE_NOEXEC || FEATURE_NOFORK
11+int  wexec_call_builtin(const char *name, char *const *argv);
12+void wexec_exit(int code) __attribute__((noreturn));
13+#endif
14 
15-/* search PATH for name; returns emalloc'd absolute path or NULL if not found.
16- * when FEATURE_INPROC_EXEC is compiled in and inproc dispatch is enabled,
17- * returns NULL for registered builtins (they have no filesystem path). */
18+/* search path for name, returns emallocd absolute path or null if not found
19+ * when feature_noexec is compiled in and builtin dispatch is enabled,
20+ * returns null for registered builtins (they have no filesystem path) */
21 char *wwhich(const char *name);
22 
23-#if FEATURE_INPROC_EXEC
24-/* runtime toggle: disable to force fork+exec even when builtins are registered.
25- * when the box is also built with FEATURE_INPROC_EXEC_OVERRIDE=1,
26- * wexec_get_inproc() additionally honors ARUU_INPROC_EXEC=0 from the
27- * environment, read once on first use, so a builtin can be bypassed without
28- * a rebuild. without that second flag, inproc dispatch is hardwired on for
29- * the whole process and only an explicit wexec_set_inproc() call can
30- * change it. */
31-void wexec_set_inproc(int enabled);
32-int  wexec_get_inproc(void);
33+int wexec_is_nofork(const char *name);
34+
35+#if FEATURE_NOEXEC
36+void wexec_set_noexec(int enabled);
37+int  wexec_get_noexec(void);
38+#else
39+#define wexec_get_noexec() 0
40+#endif
41+
42+#if FEATURE_NOFORK
43+void wexec_set_nofork(int enabled);
44+int  wexec_get_nofork(void);
45+#else
46+#define wexec_get_nofork() 0
47 #endif
48 
49 int wexecvp(const char *name, char *const *argv);