commit 5bb6c0e
xplshn
·
2026-06-16 22:23:04 +0000 UTC
parent 18fa4ce
SH build system - added manpages - large refactor Signed-off-by: xplshn <anto@xplshn.com.ar>
M
Makefile
+180,
-38
1@@ -25,7 +25,8 @@ HDR =\
2 shared/passwd.h\
3 shared/reboot.h\
4 shared/rtc.h\
5- shared/proc.h
6+ shared/proc.h\
7+ shared/tls.h
8
9 LIBUTFOBJ =\
10 shared/libutf/fgetrune.o\
11@@ -98,9 +99,15 @@ LIBUTILOBJ =\
12 shared/libutil/tty.o\
13 shared/libutil/fconcat.o\
14 shared/libutil/recurse_dir.o\
15- shared/libutil/sig.o
16+ shared/libutil/sig.o\
17+ shared/libutil/net.o\
18+ shared/libutil/sysinfo.o\
19+ shared/libutil/tls.o
20
21-LIB = shared/libutf/libutf.a shared/libutil/libutil.a
22+LIBREDLINEOBJ =\
23+ shared/libredline/redline.o
24+
25+LIB = shared/libredline/libredline.a shared/libutil/libutil.a shared/libutf/libutf.a
26
27 POSIX_BIN_ALL =\
28 cmd/posix/basename\
29@@ -179,48 +186,46 @@ POSIX_BIN_ALL =\
30 cmd/posix/xargs\
31 cmd/posix/awk/awk\
32 cmd/posix/sh/sh\
33- cmd/posix/pax
34+ cmd/posix/pax\
35+ cmd/posix/make/make
36
37 LINUX_BIN_ALL =\
38 cmd/linux/blkdiscard\
39 cmd/linux/chvt\
40 cmd/linux/ctrlaltdel\
41- cmd/linux/dmesg\
42 cmd/linux/eject\
43- cmd/linux/fallocate\
44- cmd/linux/free\
45 cmd/linux/freeramdisk\
46 cmd/linux/fsfreeze\
47 cmd/linux/hwclock\
48 cmd/linux/insmod\
49 cmd/linux/lsmod\
50+ cmd/linux/modprobe\
51+ cmd/linux/depmod\
52 cmd/linux/mkswap\
53 cmd/linux/mount\
54 cmd/linux/mountpoint\
55- cmd/linux/pidof\
56 cmd/linux/pivot_root\
57- cmd/linux/pwdx\
58 cmd/linux/readahead\
59 cmd/linux/rmmod\
60 cmd/linux/swaplabel\
61 cmd/linux/swapoff\
62 cmd/linux/swapon\
63 cmd/linux/switch_root\
64+ cmd/linux/tunctl\
65 cmd/linux/umount\
66 cmd/linux/unshare\
67- cmd/linux/uptime\
68 cmd/linux/vtallow
69
70 NET_BIN_ALL =\
71 cmd/net/netcat\
72 cmd/net/tftp\
73- cmd/net/tunctl\
74 cmd/net/wget\
75 cmd/net/ping\
76 cmd/net/sdhcp\
77 cmd/net/ifconfig\
78 cmd/net/host\
79- cmd/net/httpd
80+ cmd/net/httpd\
81+ cmd/net/ip
82
83 XSI_BIN_ALL =\
84 cmd/xsi/mknod\
85@@ -266,7 +271,17 @@ PSEUDO_BIN_ALL =\
86 cmd/pseudo/xinstall\
87 cmd/pseudo/yes\
88 cmd/pseudo/base64\
89- cmd/extra/b3sum
90+ cmd/extra/b3sum\
91+ cmd/dev/ar/ar\
92+ cmd/dev/as/as\
93+ cmd/dev/ld/ld\
94+ cmd/dev/cc/cc\
95+ cmd/pseudo/dmesg\
96+ cmd/pseudo/fallocate\
97+ cmd/pseudo/free\
98+ cmd/pseudo/pidof\
99+ cmd/pseudo/pwdx\
100+ cmd/pseudo/uptime
101
102 MAKEOBJ =\
103 cmd/posix/make/defaults.o\
104@@ -352,45 +367,43 @@ BIN_xargs_1 = cmd/posix/xargs
105 BIN_awk_1 = cmd/posix/awk/awk
106 BIN_sh_1 = cmd/posix/sh/sh
107 BIN_pax_1 = cmd/posix/pax
108+BIN_make_1 = cmd/posix/make/make
109
110 BIN_blkdiscard_1 = cmd/linux/blkdiscard
111 BIN_chvt_1 = cmd/linux/chvt
112 BIN_ctrlaltdel_1 = cmd/linux/ctrlaltdel
113-BIN_dmesg_1 = cmd/linux/dmesg
114 BIN_eject_1 = cmd/linux/eject
115-BIN_fallocate_1 = cmd/linux/fallocate
116-BIN_free_1 = cmd/linux/free
117 BIN_freeramdisk_1 = cmd/linux/freeramdisk
118 BIN_fsfreeze_1 = cmd/linux/fsfreeze
119 BIN_hwclock_1 = cmd/linux/hwclock
120 BIN_insmod_1 = cmd/linux/insmod
121 BIN_lsmod_1 = cmd/linux/lsmod
122+BIN_modprobe_1 = cmd/linux/modprobe
123+BIN_depmod_1 = cmd/linux/depmod
124 BIN_mkswap_1 = cmd/linux/mkswap
125 BIN_mount_1 = cmd/linux/mount
126 BIN_mountpoint_1 = cmd/linux/mountpoint
127-BIN_pidof_1 = cmd/linux/pidof
128 BIN_pivot_root_1 = cmd/linux/pivot_root
129-BIN_pwdx_1 = cmd/linux/pwdx
130 BIN_readahead_1 = cmd/linux/readahead
131 BIN_rmmod_1 = cmd/linux/rmmod
132 BIN_swaplabel_1 = cmd/linux/swaplabel
133 BIN_swapoff_1 = cmd/linux/swapoff
134 BIN_swapon_1 = cmd/linux/swapon
135 BIN_switch_root_1 = cmd/linux/switch_root
136+BIN_tunctl_1 = cmd/linux/tunctl
137 BIN_umount_1 = cmd/linux/umount
138 BIN_unshare_1 = cmd/linux/unshare
139-BIN_uptime_1 = cmd/linux/uptime
140 BIN_vtallow_1 = cmd/linux/vtallow
141
142 BIN_netcat_1 = cmd/net/netcat
143 BIN_tftp_1 = cmd/net/tftp
144-BIN_tunctl_1 = cmd/net/tunctl
145 BIN_wget_1 = cmd/net/wget
146 BIN_ping_1 = cmd/net/ping
147 BIN_sdhcp_1 = cmd/net/sdhcp
148 BIN_ifconfig_1 = cmd/net/ifconfig
149 BIN_host_1 = cmd/net/host
150 BIN_httpd_1 = cmd/net/httpd
151+BIN_ip_1 = cmd/net/ip
152
153 BIN_mknod_1 = cmd/xsi/mknod
154 BIN_passwd_1 = cmd/xsi/passwd
155@@ -435,8 +448,16 @@ BIN_xinstall_1 = cmd/pseudo/xinstall
156 BIN_yes_1 = cmd/pseudo/yes
157 BIN_base64_1 = cmd/pseudo/base64
158 BIN_b3sum_1 = cmd/extra/b3sum
159-
160-BIN_make_tool_1 = cmd/posix/make/make
161+BIN_ar_1 = cmd/dev/ar/ar
162+BIN_as_1 = cmd/dev/as/as
163+BIN_ld_1 = cmd/dev/ld/ld
164+BIN_cc_1 = cmd/dev/cc/cc
165+BIN_dmesg_1 = cmd/pseudo/dmesg
166+BIN_fallocate_1 = cmd/pseudo/fallocate
167+BIN_free_1 = cmd/pseudo/free
168+BIN_pidof_1 = cmd/pseudo/pidof
169+BIN_pwdx_1 = cmd/pseudo/pwdx
170+BIN_uptime_1 = cmd/pseudo/uptime
171
172 POSIX_BIN = \
173 $(BIN_basename_$(BUILD_POSIX_BASENAME)) \
174@@ -515,48 +536,46 @@ POSIX_BIN = \
175 $(BIN_xargs_$(BUILD_POSIX_XARGS)) \
176 $(BIN_awk_$(BUILD_POSIX_AWK)) \
177 $(BIN_sh_$(BUILD_POSIX_SH)) \
178- $(BIN_pax_$(BUILD_POSIX_PAX))
179+ $(BIN_pax_$(BUILD_POSIX_PAX)) \
180+ $(BIN_make_$(BUILD_POSIX_MAKE))
181
182 LINUX_BIN = \
183 $(BIN_blkdiscard_$(BUILD_LINUX_BLKDISCARD)) \
184 $(BIN_chvt_$(BUILD_LINUX_CHVT)) \
185 $(BIN_ctrlaltdel_$(BUILD_LINUX_CTRLALTDEL)) \
186- $(BIN_dmesg_$(BUILD_LINUX_DMESG)) \
187 $(BIN_eject_$(BUILD_LINUX_EJECT)) \
188- $(BIN_fallocate_$(BUILD_LINUX_FALLOCATE)) \
189- $(BIN_free_$(BUILD_LINUX_FREE)) \
190 $(BIN_freeramdisk_$(BUILD_LINUX_FREERAMDISK)) \
191 $(BIN_fsfreeze_$(BUILD_LINUX_FSFREEZE)) \
192 $(BIN_hwclock_$(BUILD_LINUX_HWCLOCK)) \
193 $(BIN_insmod_$(BUILD_LINUX_INSMOD)) \
194 $(BIN_lsmod_$(BUILD_LINUX_LSMOD)) \
195+ $(BIN_modprobe_$(BUILD_LINUX_MODPROBE)) \
196+ $(BIN_depmod_$(BUILD_LINUX_DEPMOD)) \
197 $(BIN_mkswap_$(BUILD_LINUX_MKSWAP)) \
198 $(BIN_mount_$(BUILD_LINUX_MOUNT)) \
199 $(BIN_mountpoint_$(BUILD_LINUX_MOUNTPOINT)) \
200- $(BIN_pidof_$(BUILD_LINUX_PIDOF)) \
201 $(BIN_pivot_root_$(BUILD_LINUX_PIVOT_ROOT)) \
202- $(BIN_pwdx_$(BUILD_LINUX_PWDX)) \
203 $(BIN_readahead_$(BUILD_LINUX_READAHEAD)) \
204 $(BIN_rmmod_$(BUILD_LINUX_RMMOD)) \
205 $(BIN_swaplabel_$(BUILD_LINUX_SWAPLABEL)) \
206 $(BIN_swapoff_$(BUILD_LINUX_SWAPOFF)) \
207 $(BIN_swapon_$(BUILD_LINUX_SWAPON)) \
208 $(BIN_switch_root_$(BUILD_LINUX_SWITCH_ROOT)) \
209+ $(BIN_tunctl_$(BUILD_LINUX_TUNCTL)) \
210 $(BIN_umount_$(BUILD_LINUX_UMOUNT)) \
211 $(BIN_unshare_$(BUILD_LINUX_UNSHARE)) \
212- $(BIN_uptime_$(BUILD_LINUX_UPTIME)) \
213 $(BIN_vtallow_$(BUILD_LINUX_VTALLOW))
214
215 NET_BIN = \
216 $(BIN_netcat_$(BUILD_NET_NETCAT)) \
217 $(BIN_tftp_$(BUILD_NET_TFTP)) \
218- $(BIN_tunctl_$(BUILD_NET_TUNCTL)) \
219 $(BIN_wget_$(BUILD_NET_WGET)) \
220 $(BIN_ping_$(BUILD_NET_PING)) \
221 $(BIN_sdhcp_$(BUILD_NET_SDHCP)) \
222 $(BIN_ifconfig_$(BUILD_NET_IFCONFIG)) \
223 $(BIN_host_$(BUILD_NET_HOST)) \
224- $(BIN_httpd_$(BUILD_NET_HTTPD))
225+ $(BIN_httpd_$(BUILD_NET_HTTPD)) \
226+ $(BIN_ip_$(BUILD_NET_IP))
227
228 XSI_BIN = \
229 $(BIN_mknod_$(BUILD_XSI_MKNOD)) \
230@@ -606,13 +625,17 @@ PSEUDO_BIN = \
231 $(BIN_ar_$(BUILD_DEV_AR)) \
232 $(BIN_as_$(BUILD_DEV_CC)) \
233 $(BIN_ld_$(BUILD_DEV_LD)) \
234- $(BIN_cc_$(BUILD_DEV_CC))
235-
236-MAKE_BIN = $(BIN_make_tool_$(BUILD_MAKE_MAKE))
237+ $(BIN_cc_$(BUILD_DEV_CC)) \
238+ $(BIN_dmesg_$(BUILD_PSEUDO_DMESG)) \
239+ $(BIN_fallocate_$(BUILD_PSEUDO_FALLOCATE)) \
240+ $(BIN_free_$(BUILD_PSEUDO_FREE)) \
241+ $(BIN_pidof_$(BUILD_PSEUDO_PIDOF)) \
242+ $(BIN_pwdx_$(BUILD_PSEUDO_PWDX)) \
243+ $(BIN_uptime_$(BUILD_PSEUDO_UPTIME))
244
245 OBJ = $(LIBUTFOBJ) $(LIBUTILOBJ) $(MAKEOBJ)
246
247-all: $(LIB) $(POSIX_BIN) $(LINUX_BIN) $(NET_BIN) $(XSI_BIN) $(PSEUDO_BIN) $(MAKE_BIN)
248+all: $(LIB) $(POSIX_BIN) $(LINUX_BIN) $(NET_BIN) $(XSI_BIN) $(PSEUDO_BIN)
249
250 $(POSIX_BIN_ALL) $(LINUX_BIN_ALL) $(NET_BIN_ALL) $(XSI_BIN_ALL) $(PSEUDO_BIN_ALL): $(LIB)
251
252@@ -642,6 +665,10 @@ shared/libutf/libutf.a: $(LIBUTFOBJ)
253 $(AR) $(ARFLAGS) $@ $?
254 $(RANLIB) $@
255
256+shared/libredline/libredline.a: $(LIBREDLINEOBJ)
257+ $(AR) $(ARFLAGS) $@ $?
258+ $(RANLIB) $@
259+
260 shared/libutil/libutil.a: $(LIBUTILOBJ)
261 $(AR) $(ARFLAGS) $@ $?
262 $(RANLIB) $@
263@@ -656,14 +683,39 @@ box: $(LIB)
264 LDFLAGS='$(LDFLAGS)' LDLIBS='$(LDLIBS)' OBJCOPY='$(OBJCOPY)' \
265 scripts/mkbox
266
267+.PHONY: man clean
268+
269+scripts/mkman/mkman: scripts/mkman/main.go scripts/mkman/troff.go
270+ cd scripts/mkman && go build -o mkman .
271+
272+man: scripts/mkman/mkman
273+ @for src in $(POSIX_BIN_ALL:=.c) $(PSEUDO_BIN_ALL:=.c); do \
274+ if [ -f "$$src" ] && grep -qE '!man|\?man' "$$src"; then \
275+ base=$$(basename $$src .c); \
276+ mkdir -p man/man1; \
277+ scripts/mkman/mkman -config config.mk -section 1 "$$src" > "man/man1/$$base.1"; \
278+ fi; \
279+ done
280+ @for src in $(LINUX_BIN_ALL:=.c) $(NET_BIN_ALL:=.c) $(XSI_BIN_ALL:=.c); do \
281+ if [ -f "$$src" ] && grep -qE '!man|\?man' "$$src"; then \
282+ base=$$(basename $$src .c); \
283+ mkdir -p man/man8; \
284+ scripts/mkman/mkman -config config.mk -section 8 "$$src" > "man/man8/$$base.8"; \
285+ fi; \
286+ done
287+
288 clean:
289- rm -f shared/libutf/*.o shared/libutil/*.o cmd/posix/make/*.o cmd/posix/awk/*.o cmd/posix/sh/*.o cmd/extra/*.o
290+ rm -f shared/libutf/*.o shared/libutil/*.o shared/libredline/*.o
291+ rm -f cmd/posix/*.o cmd/posix/make/*.o cmd/posix/awk/*.o cmd/posix/sh/*.o
292+ rm -f cmd/linux/*.o cmd/net/*.o cmd/xsi/*.o cmd/pseudo/*.o
293+ rm -f cmd/extra/*.o cmd/dev/ar/*.o cmd/dev/ld/*.o cmd/dev/cc/*.o cmd/dev/as/*.o
294 rm -f $(POSIX_BIN_ALL) $(LINUX_BIN_ALL) $(NET_BIN_ALL) $(XSI_BIN_ALL) $(PSEUDO_BIN_ALL) $(LIB)
295 rm -f cmd/posix/make/make cmd/posix/getconf.h cmd/posix/bc.c
296 rm -f cmd/posix/awk/awk cmd/posix/awk/maketab cmd/posix/awk/awkgram.tab.c cmd/posix/awk/awkgram.tab.h cmd/posix/awk/proctab.c
297 rm -f cmd/posix/sh/sh cmd/posix/sh/mknodes cmd/posix/sh/mksyntax
298 rm -f 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 cmd/posix/sh/token.h
299- rm -rf aruu-box .box
300+ rm -f cmd/dev/cc/cc1 cmd/dev/cc/cpp cmd/dev/as/as shared/libaruuelf.so
301+ rm -rf aruu-box .box man/man1 man/man8 scripts/mkman/mkman
302
303 AWKOBJ =\
304 cmd/posix/awk/b.o\
305@@ -693,7 +745,7 @@ SHOBJ =\
306 cmd/posix/sh/eval.o\
307 cmd/posix/sh/exec.o\
308 cmd/posix/sh/expand.o\
309- cmd/posix/sh/histedit.o\
310+ cmd/posix/sh/lineedit.o\
311 cmd/posix/sh/input.o\
312 cmd/posix/sh/jobs.o\
313 cmd/posix/sh/kill.o\
314@@ -757,3 +809,93 @@ cmd/posix/sh/%.o: cmd/posix/sh/%.c
315
316 cmd/posix/sh/sh: $(SHOBJ) $(LIB)
317 $(CC) $(LDFLAGS) -o $@ $(SHOBJ) $(LIB) $(LDLIBS)
318+
319+cmd/dev/ar/ar: cmd/dev/ar/ar.o $(LIB)
320+ $(CC) $(LDFLAGS) -o $@ cmd/dev/ar/ar.o $(LIB) $(LDLIBS)
321+
322+cmd/dev/ar/%.o: cmd/dev/ar/%.c
323+ $(CC) $(CPPFLAGS) -Icmd/dev/ar $(CFLAGS) -o $@ -c $<
324+
325+LD_OBJ =\
326+ cmd/dev/ld/ld.o
327+
328+cmd/dev/ld/%.o: cmd/dev/ld/%.c
329+ $(CC) $(CPPFLAGS) -DLD_TARGET_X86_64 -Icmd/dev/ld $(CFLAGS) -fPIC -o $@ -c $<
330+
331+shared/libaruuelf.so: cmd/dev/ld/elf.o cmd/dev/ld/x86_64.o cmd/dev/ld/ld_support.o
332+ $(CC) $(LDFLAGS) -shared -o $@ cmd/dev/ld/elf.o cmd/dev/ld/x86_64.o cmd/dev/ld/ld_support.o
333+
334+cmd/dev/ld/ld: $(LD_OBJ) shared/libaruuelf.so $(LIB)
335+ $(CC) $(LDFLAGS) -o $@ $(LD_OBJ) -Lshared -laruuelf $(LIB) $(LDLIBS) -Wl,-rpath,'$$ORIGIN/../../../shared'
336+
337+AS_OBJ =\
338+ cmd/dev/as/as.o\
339+ cmd/dev/as/asm_lex.o\
340+ cmd/dev/as/asm_parse.o\
341+ cmd/dev/as/asm_x86_64.o\
342+ cmd/dev/as/asm_elf.o
343+
344+cmd/dev/as/%.o: cmd/dev/as/%.c
345+ $(CC) $(CPPFLAGS) -Icmd/dev/as -Ishared $(CFLAGS) -o $@ -c $<
346+
347+cmd/dev/as/as: $(AS_OBJ) $(LIB)
348+ $(CC) $(LDFLAGS) -o $@ $(AS_OBJ) $(LIB) $(LDLIBS)
349+
350+cmd/dev/cc/driver.o: cmd/dev/cc/driver.c
351+ $(CC) -Icmd/dev/cc $(CPPFLAGS) $(CFLAGS) -o $@ -c $<
352+
353+cmd/dev/cc/cc: cmd/dev/cc/driver.o cmd/dev/cc/util.o $(LIB) cmd/dev/cc/cc1 cmd/dev/cc/cpp cmd/dev/as/as
354+ $(CC) $(LDFLAGS) -o $@ cmd/dev/cc/driver.o cmd/dev/cc/util.o $(LIB) $(LDLIBS)
355+
356+CC1_OBJ =\
357+ cmd/dev/cc/attr.o\
358+ cmd/dev/cc/decl.o\
359+ cmd/dev/cc/eval.o\
360+ cmd/dev/cc/expr.o\
361+ cmd/dev/cc/init.o\
362+ cmd/dev/cc/cc1.o\
363+ cmd/dev/cc/map.o\
364+ cmd/dev/cc/pp.o\
365+ cmd/dev/cc/qbe.o\
366+ cmd/dev/cc/scan.o\
367+ cmd/dev/cc/scope.o\
368+ cmd/dev/cc/stmt.o\
369+ cmd/dev/cc/targ.o\
370+ cmd/dev/cc/token.o\
371+ cmd/dev/cc/tree.o\
372+ cmd/dev/cc/type.o\
373+ cmd/dev/cc/utf.o\
374+ cmd/dev/cc/util.o
375+
376+CPP_OBJ =\
377+ cmd/dev/cc/attr.o\
378+ cmd/dev/cc/decl.o\
379+ cmd/dev/cc/eval.o\
380+ cmd/dev/cc/expr.o\
381+ cmd/dev/cc/init.o\
382+ cmd/dev/cc/map.o\
383+ cmd/dev/cc/pp.o\
384+ cmd/dev/cc/qbe.o\
385+ cmd/dev/cc/scan.o\
386+ cmd/dev/cc/scope.o\
387+ cmd/dev/cc/stmt.o\
388+ cmd/dev/cc/targ.o\
389+ cmd/dev/cc/token.o\
390+ cmd/dev/cc/tree.o\
391+ cmd/dev/cc/type.o\
392+ cmd/dev/cc/utf.o\
393+ cmd/dev/cc/util.o
394+
395+cmd/dev/cc/%.o: cmd/dev/cc/%.c
396+ $(CC) -Icmd/dev/cc $(CPPFLAGS) $(CFLAGS) -o $@ -c $<
397+
398+cmd/dev/cc/cc1: $(CC1_OBJ) $(LIB)
399+ $(CC) $(LDFLAGS) -o $@ $(CC1_OBJ) $(LIB) $(LDLIBS)
400+
401+cmd/dev/cc/cpp: cmd/dev/cc/cpp.o $(CPP_OBJ) $(LIB)
402+ $(CC) $(LDFLAGS) -o $@ cmd/dev/cc/cpp.o $(CPP_OBJ) $(LIB) $(LDLIBS)
403+
404+
405+
406+
407+
+522,
-0
1@@ -0,0 +1,522 @@
2+#!/bin/sh
3+# drives the build without needing a working make to bootstrap
4+#
5+set -e
6+
7+# per-run work directory, cleaned up on exit
8+#
9+WORKDIR="${TMPDIR:-/tmp}/aruu.$$"
10+mkdir -p "$WORKDIR"
11+trap 'rm -rf "$WORKDIR"' EXIT INT TERM HUP
12+
13+JOBS=1
14+LOAD=0
15+TARGET=all
16+
17+while [ $# -gt 0 ]; do
18+ case "$1" in
19+ -j) shift; JOBS="${1:?-j requires a number}" ;;
20+ -j*) JOBS="${1#-j}" ;;
21+ -l) shift; LOAD="${1:?-l requires a number}" ;;
22+ -l*) LOAD="${1#-l}" ;;
23+ -*) printf 'unknown flag: %s\n' "$1" >&2; exit 1 ;;
24+ *) TARGET="$1" ;;
25+ esac
26+ shift
27+done
28+
29+[ -f build.cfg ] || { printf 'build.cfg not found; see the repository README\n' >&2; exit 1; }
30+# shellcheck disable=SC1091
31+. ./build.cfg
32+
33+# cppflags derived from build.cfg; any feature_* added there is picked up automatically
34+_feature_flags=""
35+while IFS= read -r _line; do
36+ _trimmed=$(printf '%s' "$_line" | tr -d ' \t')
37+ case "$_trimmed" in
38+ FEATURE_*=*)
39+ _key=${_trimmed%%=*}
40+ eval "_val=\$$_key"
41+ _feature_flags="$_feature_flags -D${_key}=${_val}"
42+ ;;
43+ esac
44+done <<EOF
45+$(tr ';' '\n' < build.cfg)
46+EOF
47+unset _line _key _val _trimmed
48+CPPFLAGS="-Ishared -DPREFIX=\"$PREFIX\" -D_DEFAULT_SOURCE -D_GNU_SOURCE -D_NETBSD_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_FILE_OFFSET_BITS=64 -DSTD_NON_POSIX$_feature_flags"
49+if [ "$FEATURE_USE_BEARSSL" = "1" ]; then
50+ CPPFLAGS="$CPPFLAGS -IexternalRepos/BearSSL/inc"
51+ LDFLAGS="$LDFLAGS -LexternalRepos/BearSSL/build"
52+ LDLIBS="$LDLIBS externalRepos/BearSSL/build/libbearssl.a"
53+fi
54+if [ "$FEATURE_USE_LIBRESSL" = "1" ]; then
55+ LDLIBS="$LDLIBS -ltls"
56+fi
57+unset _feature_flags
58+
59+CC=${CC:-cc}
60+AR=${AR:-ar}
61+ARFLAGS=${ARFLAGS:-rc}
62+RANLIB=${RANLIB:-ranlib}
63+PREFIX=${PREFIX:-/usr/local}
64+MANPREFIX=${MANPREFIX:-$PREFIX/share/man}
65+
66+# shared headers; every object depends on these
67+HDR=$(echo shared/*.h)
68+LIB="shared/libredline/libredline.a shared/libutil/libutil.a shared/libutf/libutf.a"
69+
70+# set by multi-file builders for generated headers, cleared after use
71+EXTRA_HDR=
72+
73+# timestamp comparison
74+#
75+mtime() {
76+ stat -c '%Y' "$1" 2>/dev/null || stat -f '%m' "$1" 2>/dev/null || echo 0
77+}
78+
79+newer_than() {
80+ local s t
81+ [ -e "$2" ] || return 0
82+ s=$(mtime "$1"); t=$(mtime "$2")
83+ [ "$s" -gt "$t" ]
84+}
85+
86+any_newer_than() {
87+ local target="$1"; shift
88+ for src do
89+ newer_than "$src" "$target" && return 0
90+ done
91+ return 1
92+}
93+
94+# parallel job queue
95+#
96+# objs_for must not run in a subshell; enqueue writes _qn/_queue in the current shell
97+
98+_qn=0
99+_queue=""
100+_objs=""
101+
102+# wraps arg in single quotes, escaping embedded ones, for safe eval
103+shquote() {
104+ printf "'"
105+ printf '%s' "$1" | sed "s/'/'\\\\''/g"
106+ printf "'"
107+}
108+
109+enqueue() {
110+ local desc="$1" cmd="$2"
111+ _qn=$((_qn + 1))
112+ local s="$WORKDIR/j${_qn}.sh"
113+ {
114+ printf '#!/bin/sh\n'
115+ printf 'printf "%%s\\n" %s\n' "$(shquote "$desc")"
116+ printf 'eval %s\n' "$(shquote "$cmd")"
117+ } > "$s"
118+ _queue="$_queue $_qn"
119+}
120+
121+# silently skipped on systems without /proc/loadavg
122+_wait_for_load() {
123+ [ "$LOAD" -eq 0 ] && return 0
124+ local avg
125+ while true; do
126+ avg=$(awk '{printf "%d", int($1 + 0.5)}' /proc/loadavg 2>/dev/null) || return 0
127+ [ "$avg" -le "$LOAD" ] && return 0
128+ sleep 1
129+ done
130+}
131+
132+drain() {
133+ [ -z "$_queue" ] && return 0
134+ local n
135+ _wait_for_load
136+ if [ "$JOBS" -le 1 ]; then
137+ for n in $_queue; do
138+ sh "$WORKDIR/j${n}.sh" || { printf 'error: build step failed\n' >&2; exit 1; }
139+ done
140+ else
141+ # xargs -P (POSIX 2024), -n 1 so each path becomes its own sh invocation
142+ for n in $_queue; do
143+ printf '%s\n' "$WORKDIR/j${n}.sh"
144+ done | xargs -P "$JOBS" -n 1 sh || { printf 'error: build step failed\n' >&2; exit 1; }
145+ fi
146+ _queue=""
147+}
148+
149+# compile and link primitives
150+#
151+compile_c() {
152+ local src="$1" obj="$2"; shift 2
153+ # shellcheck disable=SC2086
154+ any_newer_than "$obj" "$src" $HDR $EXTRA_HDR || return 0
155+ enqueue " CC $obj" "$CC $CPPFLAGS $* $CFLAGS -o $obj -c $src"
156+}
157+
158+link_bin() {
159+ local bin="$1" objs="" libs="" sep=0; shift
160+ for a do
161+ if [ "$a" = "--" ]; then sep=1
162+ elif [ "$sep" = 0 ]; then objs="$objs $a"
163+ else libs="$libs $a"
164+ fi
165+ done
166+ drain
167+ # shellcheck disable=SC2086
168+ any_newer_than "$bin" $objs || return 0
169+ printf ' LD %s\n' "$bin"
170+ # shellcheck disable=SC2086
171+ eval "$CC $LDFLAGS -o $bin $objs $libs $LDLIBS"
172+}
173+
174+# writes to _objs rather than stdout so the caller avoids a subshell that
175+# would discard enqueued jobs
176+objs_for() {
177+ local dir="$1" skip=" $2 " flags="$3" src base
178+ _objs=""
179+ for src in "$dir"/*.c; do
180+ [ -f "$src" ] || continue
181+ base=${src##*/}
182+ case "$skip" in *" $base "*) continue ;; esac
183+ compile_c "$src" "${src%.c}.o" $flags
184+ _objs="$_objs ${src%.c}.o"
185+ done
186+}
187+
188+# static libraries
189+#
190+# both directories enqueued before the single drain so they compile in parallel
191+build_lib() {
192+ local utf_objs util_objs redline_objs
193+ objs_for shared/libutf "" ""
194+ utf_objs="$_objs"
195+ objs_for shared/libutil "" ""
196+ util_objs="$_objs"
197+ objs_for shared/libredline "" ""
198+ redline_objs="$_objs"
199+ drain
200+ # shellcheck disable=SC2086
201+ any_newer_than shared/libutf/libutf.a $utf_objs && {
202+ printf ' AR shared/libutf/libutf.a\n'
203+ $AR $ARFLAGS shared/libutf/libutf.a $utf_objs
204+ $RANLIB shared/libutf/libutf.a
205+ }
206+ # shellcheck disable=SC2086
207+ any_newer_than shared/libutil/libutil.a $util_objs && {
208+ printf ' AR shared/libutil/libutil.a\n'
209+ $AR $ARFLAGS shared/libutil/libutil.a $util_objs
210+ $RANLIB shared/libutil/libutil.a
211+ }
212+ # shellcheck disable=SC2086
213+ any_newer_than shared/libredline/libredline.a $redline_objs && {
214+ printf ' AR shared/libredline/libredline.a\n'
215+ $AR $ARFLAGS shared/libredline/libredline.a $redline_objs
216+ $RANLIB shared/libredline/libredline.a
217+ }
218+ return 0
219+}
220+
221+# per-tool category builder
222+#
223+cfgvar() {
224+ local cat="$1" base="$2"
225+ [ "$cat" = extra ] && cat=pseudo
226+ printf 'BUILD_%s_%s' "$cat" "$base" | tr 'a-z-' 'A-Z_'
227+}
228+
229+# individual flag overrides group flag
230+cfg_enabled() {
231+ local _var="$1" _v
232+ while [ -n "$_var" ]; do
233+ eval "_v=\${${_var}-UNSET}"
234+ if [ "$_v" = "1" ]; then
235+ return 0
236+ elif [ "$_v" = "0" ]; then
237+ return 1
238+ fi
239+ case "$_var" in
240+ *_[^_]*) _var=$(printf '%s' "$_var" | sed 's/_[^_]*$//') ;;
241+ *) break ;;
242+ esac
243+ done
244+ return 1
245+}
246+
247+build_simple_tools() {
248+ local cat="$1" src base var staged=""
249+ for src in cmd/"$cat"/*.c; do
250+ [ -f "$src" ] || continue
251+ base=${src##*/}; base=${base%.c}
252+ var=$(cfgvar "$cat" "$base")
253+ cfg_enabled "$var" || continue
254+ compile_c "$src" "${src%.c}.o"
255+ staged="$staged ${src%.c}"
256+ done
257+ drain
258+ local bin
259+ for bin in $staged; do
260+ link_bin "$bin" "${bin}.o" -- $LIB
261+ done
262+}
263+
264+# generated multi-file tools under cmd/posix
265+#
266+build_awk() {
267+ cfg_enabled BUILD_POSIX_AWK || return 0
268+ local dir=cmd/posix/awk
269+
270+ any_newer_than "$dir/awkgram.tab.c" "$dir/awkgram.y" && {
271+ if command -v yacc >/dev/null 2>&1; then
272+ printf ' YACC %s/awkgram.tab.c\n' "$dir"
273+ yacc -d -o "$dir/awkgram.tab.c" "$dir/awkgram.y"
274+ elif command -v bison >/dev/null 2>&1; then
275+ printf ' BISON %s/awkgram.tab.c\n' "$dir"
276+ bison -d -o "$dir/awkgram.tab.c" "$dir/awkgram.y"
277+ else
278+ printf 'awk: no yacc/bison; using pre-generated awkgram.tab.c\n' >&2
279+ fi
280+ }
281+
282+ any_newer_than "$dir/maketab" "$dir/maketab.c" "$dir/awkgram.tab.h" && {
283+ printf ' CC %s/maketab\n' "$dir"
284+ eval "$CC $CFLAGS -o $dir/maketab $dir/maketab.c"
285+ }
286+
287+ any_newer_than "$dir/proctab.c" "$dir/maketab" && {
288+ printf ' GEN %s/proctab.c\n' "$dir"
289+ "$dir/maketab" "$dir/awkgram.tab.h" > "$dir/proctab.c"
290+ }
291+
292+ EXTRA_HDR="$dir/awk.h $dir/awkgram.tab.h $dir/proto.h"
293+ objs_for "$dir" maketab.c "-I$dir"
294+ EXTRA_HDR=
295+ link_bin "$dir/awk" $_objs -- $LIB -lm
296+}
297+
298+build_sh() {
299+ cfg_enabled BUILD_POSIX_SH || return 0
300+ local dir=cmd/posix/sh
301+
302+ any_newer_than "$dir/mknodes" "$dir/mknodes.c" && {
303+ printf ' CC %s/mknodes\n' "$dir"
304+ eval "$CC $CFLAGS -o $dir/mknodes $dir/mknodes.c"
305+ }
306+
307+ any_newer_than "$dir/mksyntax" "$dir/mksyntax.c" && {
308+ printf ' CC %s/mksyntax\n' "$dir"
309+ eval "$CC $CPPFLAGS -I$dir $CFLAGS -o $dir/mksyntax $dir/mksyntax.c"
310+ }
311+
312+ any_newer_than "$dir/token.h" "$dir/mktokens" && {
313+ printf ' GEN %s/token.h\n' "$dir"
314+ (cd "$dir" && sh mktokens)
315+ }
316+
317+ any_newer_than "$dir/syntax.c" "$dir/mksyntax" && {
318+ printf ' GEN %s/syntax.c\n' "$dir"
319+ (cd "$dir" && ./mksyntax)
320+ }
321+
322+ any_newer_than "$dir/nodes.c" "$dir/mknodes" "$dir/nodetypes" "$dir/nodes.c.pat" && {
323+ printf ' GEN %s/nodes.c\n' "$dir"
324+ (cd "$dir" && ./mknodes nodetypes nodes.c.pat)
325+ }
326+
327+ any_newer_than "$dir/builtins.c" "$dir/mkbuiltins" "$dir/builtins.def" "$dir/shell.h" && {
328+ printf ' GEN %s/builtins.c\n' "$dir"
329+ (cd "$dir" && sh mkbuiltins .)
330+ }
331+
332+ EXTRA_HDR="$dir/syntax.h $dir/nodes.h $dir/builtins.h $dir/token.h"
333+ objs_for "$dir" "mknodes.c mksyntax.c" "-DSHELL -I$dir"
334+ EXTRA_HDR=
335+ link_bin "$dir/sh" $_objs -- $LIB
336+}
337+
338+build_make() {
339+ cfg_enabled BUILD_POSIX_MAKE || return 0
340+ local dir=cmd/posix/make
341+ EXTRA_HDR="$dir/make.h"
342+ objs_for "$dir" "" ""
343+ EXTRA_HDR=
344+ link_bin "$dir/make" $_objs -- $LIB
345+}
346+
347+build_posix() {
348+ cfg_enabled BUILD_POSIX_GETCONF && [ ! -f cmd/posix/getconf.h ] && {
349+ printf ' GEN cmd/posix/getconf.h\n'
350+ scripts/getconf.sh > cmd/posix/getconf.h || { rm -f cmd/posix/getconf.h; exit 1; }
351+ }
352+ build_simple_tools posix
353+ build_awk
354+ build_sh
355+ build_make
356+}
357+
358+# multi-file dev toolchain
359+#
360+build_ar() {
361+ cfg_enabled BUILD_DEV_AR || return 0
362+ local dir=cmd/dev/ar
363+ objs_for "$dir" tinyar.c "-I$dir"
364+ link_bin "$dir/ar" $_objs -- $LIB
365+}
366+
367+build_as() {
368+ cfg_enabled BUILD_DEV_AS || return 0
369+ local dir=cmd/dev/as
370+ objs_for "$dir" "" "-I$dir -Ishared"
371+ link_bin "$dir/as" $_objs -- $LIB
372+}
373+
374+# rpath embeds $ORIGIN so the installed ld finds libaruuelf.so next to itself
375+build_ld() {
376+ cfg_enabled BUILD_DEV_LD || return 0
377+ local dir="cmd/dev/ld"
378+ local cflags="-DLD_TARGET_X86_64 -I$dir -fPIC"
379+ local src
380+
381+ for src in "$dir"/*.c; do
382+ compile_c "$src" "${src%.c}.o" $cflags
383+ done
384+ drain
385+
386+ any_newer_than shared/libaruuelf.so "$dir/elf.o" "$dir/x86_64.o" "$dir/ld_support.o" && {
387+ printf ' LD shared/libaruuelf.so\n'
388+ eval "$CC $LDFLAGS -shared -o shared/libaruuelf.so $dir/elf.o $dir/x86_64.o $dir/ld_support.o"
389+ }
390+
391+ any_newer_than "$dir/ld" "$dir/ld.o" shared/libaruuelf.so && {
392+ printf ' LD %s/ld\n' "$dir"
393+ # $ORIGIN is a dynamic-linker token, not a shell variable
394+ eval "$CC $LDFLAGS -o $dir/ld $dir/ld.o -Lshared -laruuelf $LIB $LDLIBS -Wl,-rpath,'\$ORIGIN/../../../shared'"
395+ }
396+ return 0
397+}
398+
399+build_cc() {
400+ cfg_enabled BUILD_DEV_CC || return 0
401+ local dir="cmd/dev/cc"
402+ local cflags="-I$dir"
403+ local common
404+
405+ # compiled once, linked into both cc1 and cpp
406+ objs_for "$dir" "driver.c cc1.c cpp.c" "$cflags"
407+ common="$_objs"
408+ drain
409+
410+ compile_c "$dir/cc1.c" "$dir/cc1.o" $cflags
411+ compile_c "$dir/cpp.c" "$dir/cpp.o" $cflags
412+ compile_c "$dir/driver.c" "$dir/driver.o" $cflags
413+ drain
414+
415+ link_bin "$dir/cc1" "$dir/cc1.o" $common -- $LIB
416+ link_bin "$dir/cpp" "$dir/cpp.o" $common -- $LIB
417+ link_bin "$dir/cc" "$dir/driver.o" "$dir/util.o" -- $LIB
418+}
419+
420+build_dev() {
421+ build_ar
422+ build_as
423+ build_ld
424+ build_cc
425+}
426+
427+# manual page generation
428+#
429+man_section() {
430+ case "$1" in
431+ */linux/*|*/net/*|*/xsi/*) printf '8\n' ;;
432+ *) printf '1\n' ;;
433+ esac
434+}
435+
436+build_man_for() {
437+ local var="$1" src="$2" base sec out
438+ cfg_enabled "$var" || return 0
439+ [ -x scripts/mkman/mkman ] || { printf 'error: mkman not built\n' >&2; exit 1; }
440+ base=${src##*/}; base=${base%.c}
441+ sec=$(man_section "$src")
442+ out="man/man${sec}/${base}.${sec}"
443+ mkdir -p "man/man${sec}"
444+ any_newer_than "$out" "$src" build.cfg scripts/mkman/mkman || return 0
445+ printf ' MAN %s\n' "$out"
446+ scripts/mkman/mkman -config build.cfg -section "$sec" "$src" > "$out"
447+}
448+
449+build_man() {
450+ local dir cat src base
451+ if [ ! -x scripts/mkman/mkman ] || any_newer_than scripts/mkman/mkman scripts/mkman/main.go scripts/mkman/troff.go; then
452+ printf ' GO scripts/mkman/mkman\n'
453+ (cd scripts/mkman && go build -o mkman .)
454+ fi
455+ for dir in cmd/*; do
456+ [ -d "$dir" ] || continue
457+ cat=${dir##*/}
458+ for src in "$dir"/*.c; do
459+ [ -f "$src" ] || continue
460+ base=${src##*/}; base=${base%.c}
461+ build_man_for "$(cfgvar "$cat" "$base")" "$src"
462+ done
463+ done
464+}
465+
466+# install and clean
467+#
468+do_install() {
469+ local bin f sec
470+ mkdir -p "$PREFIX/bin" "$MANPREFIX/man1" "$MANPREFIX/man8"
471+ find cmd -type f ! -name '*.*' -perm -100 | while read -r bin; do
472+ printf ' INSTALL %s/bin/%s\n' "$PREFIX" "${bin##*/}"
473+ cp "$bin" "$PREFIX/bin/${bin##*/}"
474+ chmod 755 "$PREFIX/bin/${bin##*/}"
475+ done
476+ find man/man1 man/man8 -type f -name '*.[18]' 2>/dev/null | while read -r f; do
477+ sec=${f%/*}; sec=${sec##*/}
478+ cp "$f" "$MANPREFIX/${sec}/${f##*/}"
479+ done
480+}
481+
482+do_clean() {
483+ find shared cmd -name '*.o' -exec rm -f {} +
484+ find shared -name '*.a' -exec rm -f {} +
485+ find cmd -type f ! -name '*.*' -perm -100 -exec rm -f {} +
486+ rm -f shared/libaruuelf.so
487+ rm -f cmd/posix/bc.c cmd/posix/getconf.h
488+ rm -f cmd/posix/awk/maketab cmd/posix/awk/proctab.c
489+ rm -f cmd/posix/awk/awkgram.tab.c cmd/posix/awk/awkgram.tab.h
490+ rm -f cmd/posix/sh/mknodes cmd/posix/sh/mksyntax
491+ rm -f cmd/posix/sh/token.h cmd/posix/sh/syntax.c cmd/posix/sh/syntax.h
492+ rm -f cmd/posix/sh/nodes.c cmd/posix/sh/nodes.h
493+ rm -f cmd/posix/sh/builtins.c cmd/posix/sh/builtins.h
494+ rm -rf man/man1 man/man8
495+ rm -rf aruu-box .box
496+ rm -f scripts/mkman/mkman
497+}
498+
499+case "$TARGET" in
500+ all)
501+ build_lib
502+ build_posix
503+ build_dev
504+ build_simple_tools linux
505+ build_simple_tools net
506+ build_simple_tools xsi
507+ build_simple_tools pseudo
508+ build_simple_tools extra
509+ ;;
510+ lib) build_lib ;;
511+ posix) build_lib; build_posix ;;
512+ dev) build_lib; build_dev ;;
513+ make) build_lib; build_make ;;
514+ linux|net|xsi|pseudo|extra)
515+ build_lib; build_simple_tools "$TARGET" ;;
516+ man) build_man ;;
517+ install) do_install ;;
518+ clean) do_clean ;;
519+ *)
520+ printf 'usage: sh Makefile.sh [-jN] [-lN] [all|clean|install|man|lib|posix|linux|net|xsi|pseudo|extra|dev|make]\n' >&2
521+ exit 1
522+ ;;
523+esac
M
README
+2,
-2
1@@ -177,8 +177,8 @@ Copyright (c) 2016 Eivind Uggedal [eivind@uggedal.com](mailto:eivind@uggedal.com
2 Copyright (c) 2016 Mattias Andrée [m@maandree.se](mailto:m@maandree.se)
3 Copyright (c) 2026 xplshn [anto@xplshn.com.ar](mailto:anto@xplshn.com.ar) (https://github.com/xplshn)
4
5-Portions of this software (specifically utils under net/) are partially
6-based on code written by Rob Landley
7+Portions of this software (specifically utils under net/) are partially based on code written by Rob Landley
8+Our line editing library is partially based on work by Salvatore Sanfilippo (linenoise) and JC Wang (crossline)
9 The rest are based on SBASE/UBASE source code
10
11 =~= Argentino Roca Unix Utilities =~= https://github.com/xplshn/aruu =~=
+124,
-0
1@@ -0,0 +1,124 @@
2+# posix: POSIX 2024 only common: uncontroversial extras all: everything except external libs
3+PRESET_FEATURES=all
4+
5+# subset of: posix linux net xsi pseudo dev
6+PRESET_UTILS="posix linux net xsi pseudo"
7+
8+# installation paths
9+VERSION=2026-06-08T05-53-UTC-03
10+PREFIX=/usr/local
11+MANPREFIX=/usr/local/share/man
12+
13+# toolchain
14+CC=cc
15+CFLAGS="-std=c99 -Wall -Wextra -pedantic"
16+LDFLAGS=""
17+LDLIBS="-lcrypt -lresolv"
18+AR=ar
19+ARFLAGS="rc"
20+RANLIB=ranlib
21+
22+# --- preset expansion: do not edit below ---
23+
24+case "$PRESET_FEATURES" in
25+posix)
26+ FEATURE_FIND_DELETE=0; FEATURE_FIND_QUIT=0
27+ FEATURE_FIND_EMPTY=0; FEATURE_FIND_INUM=0
28+ FEATURE_FIND_SAMEFILE=0; FEATURE_FIND_MAXDEPTH=0
29+ FEATURE_FIND_MINDEPTH=0; FEATURE_FIND_MMIN=0
30+ FEATURE_FIND_AMIN=0; FEATURE_FIND_CMIN=0
31+ FEATURE_FIND_INAME=0; FEATURE_FIND_IPATH=0
32+ FEATURE_FIND_REGEX=0; FEATURE_FIND_PRINT0=0
33+ FEATURE_SED_INPLACE=0; FEATURE_SED_PRESERVE_NEWLINE=0
34+ FEATURE_GREP_CONTEXT=0; FEATURE_GREP_MAX_COUNT=0
35+ FEATURE_TAR_CREATE=0; FEATURE_TAR_EXCLUDE=0
36+ FEATURE_STAT_FILESYSTEM=0; FEATURE_STAT_FORMAT=0
37+ FEATURE_SORT_BIG=0; FEATURE_SORT_STABLE=0
38+ FEATURE_OD_ENDIAN=0;
39+ FEATURE_SH_HISTEDIT=0; FEATURE_SH_LOCAL=0
40+ FEATURE_SH_LET=0; FEATURE_SH_ULIMIT=0
41+ FEATURE_SH_SETVAR=0; FEATURE_SH_WORDEXP=0
42+ FEATURE_CAL_EXT=0
43+ FEATURE_LS_COLOR=0
44+ FEATURE_MODPROBE_SHOW_DEPENDS=0; FEATURE_MODPROBE_BLACKLIST=0
45+ FEATURE_MODPROBE_SYSLOG=0; FEATURE_MODPROBE_DIR_OVERRIDE=0
46+ FEATURE_DEPMOD_ALIAS=0; FEATURE_DEPMOD_SYMBOLS=0
47+ ;;
48+common)
49+ FEATURE_FIND_DELETE=1; FEATURE_FIND_QUIT=1
50+ FEATURE_FIND_EMPTY=1; FEATURE_FIND_INUM=1
51+ FEATURE_FIND_SAMEFILE=1; FEATURE_FIND_MAXDEPTH=1
52+ FEATURE_FIND_MINDEPTH=1; FEATURE_FIND_MMIN=1
53+ FEATURE_FIND_AMIN=1; FEATURE_FIND_CMIN=1
54+ FEATURE_FIND_INAME=1; FEATURE_FIND_IPATH=1
55+ FEATURE_FIND_REGEX=1; FEATURE_FIND_PRINT0=1
56+ FEATURE_SED_INPLACE=1; FEATURE_SED_PRESERVE_NEWLINE=1
57+ FEATURE_GREP_CONTEXT=1; FEATURE_GREP_MAX_COUNT=1
58+ FEATURE_TAR_CREATE=1; FEATURE_TAR_EXCLUDE=1
59+ FEATURE_STAT_FILESYSTEM=1; FEATURE_STAT_FORMAT=1
60+ FEATURE_SORT_BIG=0; FEATURE_SORT_STABLE=1
61+ FEATURE_OD_ENDIAN=0;
62+ FEATURE_SH_HISTEDIT=0; FEATURE_SH_LOCAL=1
63+ FEATURE_SH_LET=1; FEATURE_SH_ULIMIT=1
64+ FEATURE_SH_SETVAR=0; FEATURE_SH_WORDEXP=0
65+ FEATURE_CAL_EXT=1
66+ FEATURE_LS_COLOR=1
67+ FEATURE_MODPROBE_SHOW_DEPENDS=1; FEATURE_MODPROBE_BLACKLIST=1
68+ FEATURE_MODPROBE_SYSLOG=1; FEATURE_MODPROBE_DIR_OVERRIDE=1
69+ FEATURE_DEPMOD_ALIAS=1; FEATURE_DEPMOD_SYMBOLS=1
70+ ;;
71+all)
72+ # histedit now included via redline
73+ FEATURE_FIND_DELETE=1; FEATURE_FIND_QUIT=1
74+ FEATURE_FIND_EMPTY=1; FEATURE_FIND_INUM=1
75+ FEATURE_FIND_SAMEFILE=1; FEATURE_FIND_MAXDEPTH=1
76+ FEATURE_FIND_MINDEPTH=1; FEATURE_FIND_MMIN=1
77+ FEATURE_FIND_AMIN=1; FEATURE_FIND_CMIN=1
78+ FEATURE_FIND_INAME=1; FEATURE_FIND_IPATH=1
79+ FEATURE_FIND_REGEX=1; FEATURE_FIND_PRINT0=1
80+ FEATURE_SED_INPLACE=1; FEATURE_SED_PRESERVE_NEWLINE=1
81+ FEATURE_GREP_CONTEXT=1; FEATURE_GREP_MAX_COUNT=1
82+ FEATURE_TAR_CREATE=1; FEATURE_TAR_EXCLUDE=1
83+ FEATURE_STAT_FILESYSTEM=1; FEATURE_STAT_FORMAT=1
84+ FEATURE_SORT_BIG=1; FEATURE_SORT_STABLE=1
85+ FEATURE_OD_ENDIAN=1;
86+ FEATURE_SH_HISTEDIT=1; FEATURE_SH_LOCAL=1
87+ FEATURE_SH_LET=1; FEATURE_SH_ULIMIT=1
88+ FEATURE_SH_SETVAR=1; FEATURE_SH_WORDEXP=1
89+ FEATURE_CAL_EXT=1
90+ FEATURE_LS_COLOR=1
91+ FEATURE_MODPROBE_SHOW_DEPENDS=1; FEATURE_MODPROBE_BLACKLIST=1
92+ FEATURE_MODPROBE_SYSLOG=1; FEATURE_MODPROBE_DIR_OVERRIDE=1
93+ FEATURE_DEPMOD_ALIAS=1; FEATURE_DEPMOD_SYMBOLS=1
94+ ;;
95+*)
96+ printf 'build.cfg: unknown PRESET_FEATURES value: %s\n' "$PRESET_FEATURES" >&2
97+ return 1 2>/dev/null || exit 1
98+ ;;
99+esac
100+
101+BUILD_POSIX=0; BUILD_LINUX=0; BUILD_NET=0; BUILD_XSI=0
102+BUILD_PSEUDO=0; BUILD_DEV=0
103+for _p in $PRESET_UTILS; do
104+ case "$_p" in
105+ posix) BUILD_POSIX=1 ;;
106+ linux) BUILD_LINUX=1 ;;
107+ net) BUILD_NET=1 ;;
108+ xsi) BUILD_XSI=1 ;;
109+ pseudo) BUILD_PSEUDO=1 ;;
110+ dev) BUILD_DEV=1 ;;
111+ *)
112+ printf 'build.cfg: unknown util preset: %s\n' "$_p" >&2
113+ return 1 2>/dev/null || exit 1
114+ ;;
115+ esac
116+done
117+unset _p
118+
119+# --- per-item overrides (after preset expansion) ---
120+# BUILD_POSIX_SH=0 # disable a tool when its group is active
121+# BUILD_DEV_AS=1 # enable a tool whose group isnt in PRESET_UTILS
122+FEATURE_SH_HISTEDIT=1
123+FEATURE_USE_LIBRESSL=0
124+FEATURE_USE_BEARSSL=1
125+# FEATURE_MY_NEW_THING=1 # new FEATURE_* are picked up by Makefile.sh automatically
+10,
-0
1@@ -1,4 +1,10 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+b3sum: compute blake3 checksums
5+usage: b3sum [-bct] [-l length] [file ...]
6+
7+compute and check blake3 message digests
8+*/
9 #include "util.h"
10 #include "arg.h"
11
12@@ -3972,17 +3978,21 @@ main(int argc, char *argv[])
13 int ret = 0;
14
15 ARGBEGIN {
16+ // ?man -b: read in binary mode
17 case 'b':
18 mode = "rb";
19 break;
20+ // ?man -c: check blake3 sums from file
21 case 'c':
22 func = check;
23 break;
24+ // ?man -l length: output digest length in bytes
25 case 'l':
26 outlen = strtoul(EARGF(usage()), &end, 10);
27 if (*end)
28 usage();
29 break;
30+ // ?man -t: read in text mode
31 case 't':
32 mode = "r";
33 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+blkdiscard: discard sectors on a device
5+usage: blkdiscard device
6+
7+discard sectors on a block device
8+*/
9+
10 #include <sys/ioctl.h>
11 #include <sys/mount.h>
12 #include <sys/stat.h>
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+chvt: change foreground virtual terminal
5+usage: chvt num
6+
7+change the active virtual terminal
8+*/
9+
10 #include <sys/ioctl.h>
11 #include <sys/types.h>
12
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+ctrlaltdel: set ctrl-alt-del function
5+usage: ctrlaltdel -h | -s
6+
7+set the behavior of the ctrl-alt-del key combination
8+*/
9+
10 #include <sys/syscall.h>
11
12 #include <stdio.h>
13@@ -19,9 +26,11 @@ main(int argc, char *argv[])
14 int hflag = 0, sflag = 0, cmd;
15
16 ARGBEGIN {
17+ // ?man -h: suppress headers or print help
18 case 'h':
19 hflag = 1;
20 break;
21+ // ?man -s: silent mode or print summary
22 case 's':
23 sflag = 1;
24 break;
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+eject: eject removable media
5+usage: eject [-t] [device ...]
6+
7+eject optical discs or other removable storage media
8+*/
9+
10 #include <sys/ioctl.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13@@ -50,6 +57,7 @@ int
14 main(int argc, char *argv[])
15 {
16 ARGBEGIN {
17+ // ?man -t: sort or specify timestamp
18 case 't':
19 tflag = 1;
20 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+freeramdisk: free ramdisk memory
5+usage: freeramdisk
6+
7+free memory associated with a ramdisk device
8+*/
9+
10 #include <sys/ioctl.h>
11 #include <sys/mount.h>
12 #include <sys/types.h>
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+fsfreeze: suspend access to a filesystem
5+usage: fsfreeze (-f | -u) mountpoint
6+
7+freeze or unfreeze a filesystem to allow safe backups
8+*/
9+
10 #include <sys/ioctl.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13@@ -28,9 +35,11 @@ main(int argc, char *argv[])
14 int fd;
15
16 ARGBEGIN {
17+ // ?man -f: force the operation
18 case 'f':
19 fflag = 1;
20 break;
21+ // ?man -u: unbuffered output
22 case 'u':
23 uflag = 1;
24 break;
+11,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+hwclock: query or set the hardware clock
5+usage: hwclock [-rsw] [-u] [dev]
6+
7+view or adjust the hardware real time clock
8+*/
9+
10 #include <sys/ioctl.h>
11 #include <sys/stat.h>
12 #include <sys/time.h>
13@@ -121,15 +128,19 @@ main(int argc, char *argv[])
14 int wflag = 0;
15
16 ARGBEGIN {
17+ // ?man -r: operate recursively
18 case 'r':
19 rflag = 1;
20 break;
21+ // ?man -s: silent mode or print summary
22 case 's':
23 sflag = 1;
24 break;
25+ // ?man -w: wait for completion
26 case 'w':
27 wflag = 1;
28 break;
29+ // ?man -u: unbuffered output
30 case 'u':
31 break;
32 default:
+6,
-1
1@@ -1,4 +1,9 @@
2-/* See LICENSE file for copyright and license details. */
3+/* ?man
4+insmod: insert a module into the Linux kernel
5+usage: insmod filename [args...]
6+
7+insmod inserts a kernel module from filename into the running kernel
8+*/
9 #include <sys/stat.h>
10 #include <sys/syscall.h>
11
+6,
-1
1@@ -1,4 +1,9 @@
2-/* See LICENSE file for copyright and license details. */
3+/* ?man
4+lsmod: show the status of modules in the Linux kernel
5+usage: lsmod
6+
7+lsmod formats and displays /proc/modules, showing currently loaded modules
8+*/
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+lsusb: list usb devices
5+usage: lsusb
6+
7+display information about usb buses and connected devices
8+*/
9+
10 #include <limits.h>
11 #include <stdio.h>
12 #include <stdlib.h>
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+mkswap: set up a swap area
5+usage: mkswap device
6+
7+initialize a linux swap area on a device or file
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <fcntl.h>
+14,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+mount: mount a filesystem
5+usage: mount [-BMRan] [-t fstype] [-o options] [source] [target]
6+
7+mount a filesystem to the directory tree
8+*/
9+
10 #include <sys/mount.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13@@ -200,25 +207,32 @@ main(int argc, char *argv[])
14 FILE *fp;
15
16 ARGBEGIN {
17+ // ?man -B: specify option flag
18 case 'B':
19 argflags |= MS_BIND;
20 break;
21+ // ?man -M: specify option flag
22 case 'M':
23 argflags |= MS_MOVE;
24 break;
25+ // ?man -R: operate recursively on directories
26 case 'R':
27 argflags |= MS_REC;
28 break;
29+ // ?man -a: print or show all entries
30 case 'a':
31 aflag = 1;
32 break;
33+ // ?man -o: specify output file
34 case 'o':
35 estrlcat(fsopts, EARGF(usage()), sizeof(fsopts));
36 parseopts(fsopts, &flags, data, sizeof(data));
37 break;
38+ // ?man -t: sort or specify timestamp
39 case 't':
40 types = EARGF(usage());
41 break;
42+ // ?man -n: print line numbers or counts
43 case 'n':
44 break;
45 default:
+10,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+mountpoint: check if a directory is a mountpoint
5+usage: mountpoint [-dqx] target
6+
7+determine if a directory is a mountpoint
8+*/
9+
10 #include <sys/stat.h>
11 #include <sys/sysmacros.h>
12 #include <sys/types.h>
13@@ -27,12 +34,15 @@ main(int argc, char *argv[])
14 struct stat st1, st2;
15
16 ARGBEGIN {
17+ // ?man -d: specify directory
18 case 'd':
19 dflag = 1;
20 break;
21+ // ?man -q: quiet mode; suppress output
22 case 'q':
23 qflag = 1;
24 break;
25+ // ?man -x: hex format or match whole lines
26 case 'x':
27 xflag = 1;
28 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+pivot_root: change the root filesystem
5+usage: pivot_root new-root put-old
6+
7+change the root filesystem of the current process
8+*/
9+
10 #include <sys/syscall.h>
11
12 #include <stdio.h>
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+readahead: read files into page cache
5+usage: readahead file...
6+
7+preload file contents into the page cache
8+*/
9+
10 #include <fcntl.h>
11 #include <limits.h>
12 #include <stdio.h>
+9,
-1
1@@ -1,4 +1,12 @@
2-/* See LICENSE file for copyright and license details. */
3+/* ?man
4+rmmod: remove a module from the Linux kernel
5+usage: rmmod [-fw] module...
6+
7+rmmod removes a kernel module from the running kernel
8+
9+// ?man -f: force removal of a module even if it is busy or in use
10+// ?man -w: wait for the module to become unused before removing
11+*/
12 #include <sys/syscall.h>
13
14 #include <fcntl.h>
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+swaplabel: print or change swap label
5+usage: swaplabel [-L label] device
6+
7+display or modify the label and uuid of a swap device
8+*/
9+
10 #include <sys/types.h>
11
12 #include <fcntl.h>
13@@ -33,6 +40,7 @@ main(int argc, char *argv[])
14 int i;
15
16 ARGBEGIN {
17+ // ?man -L: specify option flag
18 case 'L':
19 setlabel = 1;
20 label = EARGF(usage());
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+swapoff: disable swap devices
5+usage: swapoff -a | device
6+
7+disable paging and swapping on specified devices
8+*/
9+
10 #include <sys/swap.h>
11
12 #include <mntent.h>
13@@ -24,6 +31,7 @@ main(int argc, char *argv[])
14 FILE *fp;
15
16 ARGBEGIN {
17+ // ?man -a: print or show all entries
18 case 'a':
19 all = 1;
20 break;
+10,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+swapon: enable swap devices
5+usage: swapon [-dp] -a | device
6+
7+enable paging and swapping on specified devices
8+*/
9+
10 #include <sys/swap.h>
11
12 #include <mntent.h>
13@@ -25,12 +32,15 @@ main(int argc, char *argv[])
14 FILE *fp;
15
16 ARGBEGIN {
17+ // ?man -a: print or show all entries
18 case 'a':
19 all = 1;
20 break;
21+ // ?man -d: specify directory
22 case 'd':
23 flags |= SWAP_FLAG_DISCARD;
24 break;
25+ // ?man -p: preserve file attributes
26 case 'p':
27 flags |= SWAP_FLAG_PREFER;
28 break;
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+switch_root: switch to another root filesystem
5+usage: switch_root [-c console] [newroot] [init] (PID 1)
6+
7+switch to another filesystem as the root of the mount tree
8+*/
9+
10 #include <sys/mount.h>
11 #include <sys/stat.h>
12 #include <sys/vfs.h>
13@@ -73,6 +80,7 @@ main(int argc, char *argv[])
14 struct statfs stfs;
15
16 ARGBEGIN {
17+ // ?man -c: print count or perform stdout action
18 case 'c':
19 console = EARGF(usage());
20 break;
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+sysctl: configure kernel parameters
5+usage: sysctl [-p file] variable[=value]...
6+
7+view and modify kernel parameters at runtime
8+*/
9+
10 #include <fcntl.h>
11 #include <limits.h>
12 #include <stdio.h>
13@@ -169,6 +176,7 @@ main(int argc, char *argv[])
14 int r = 0;
15
16 ARGBEGIN {
17+ // ?man -p: preserve file attributes
18 case 'p':
19 file = EARGF(usage());
20 break;
R cmd/net/tunctl.c =>
cmd/linux/tunctl.c
+11,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+tunctl: configure tun/tap interfaces
5+usage: tunctl [-dtT] [-u owner] [device]
6+
7+create or destroy tun/tap network interfaces
8+*/
9+
10 #include <sys/ioctl.h>
11 #include <sys/socket.h>
12 #include <sys/stat.h>
13@@ -38,17 +45,21 @@ main(int argc, char *argv[])
14
15 ARGBEGIN
16 {
17+ // ?man -d: specify directory
18 case 'd':
19 dflag = 1;
20 tflag = 0;
21 break;
22+ // ?man -t: sort or specify timestamp
23 case 't':
24 tflag = 1;
25 dflag = 0;
26 break;
27+ // ?man -T: specify option flag
28 case 'T':
29 Tflag = 1;
30 break;
31+ // ?man -u: unbuffered output
32 case 'u':
33 owner = EARGF(usage());
34 break;
+12,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+umount: unmount filesystems
5+usage: umount [-lfn] [-O options] target...
6+
7+unmount a filesystem from the directory tree
8+*/
9+
10 #include <sys/mount.h>
11
12 #include <mntent.h>
13@@ -135,18 +142,23 @@ main(int argc, char *argv[])
14 #endif
15
16 ARGBEGIN {
17+ // ?man -a: print or show all entries
18 case 'a':
19 aflag = 1;
20 break;
21+ // ?man -f: force the operation
22 case 'f':
23 flags |= MNT_FORCE;
24 break;
25+ // ?man -l: list in long format
26 case 'l':
27 flags |= MNT_DETACH;
28 break;
29+ // ?man -n: print line numbers or counts
30 case 'n':
31 break;
32 #ifdef STD_NON_POSIX
33+ // ?man -O: specify option flag
34 case 'O':
35 oflag = EARGF(usage());
36 break;
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+unshare: run program in new namespaces
5+usage: unshare [-muinpU] cmd [args...]
6+
7+run a program with some namespaces unshared from the parent
8+*/
9+
10 #include <sched.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -19,21 +26,27 @@ main(int argc, char *argv[])
14 int flags = 0;
15
16 ARGBEGIN {
17+ // ?man -m: specify mode or limit
18 case 'm':
19 flags |= CLONE_NEWNS;
20 break;
21+ // ?man -u: unbuffered output
22 case 'u':
23 flags |= CLONE_NEWUTS;
24 break;
25+ // ?man -i: interactive mode or prompt for confirmation
26 case 'i':
27 flags |= CLONE_NEWIPC;
28 break;
29+ // ?man -n: print line numbers or counts
30 case 'n':
31 flags |= CLONE_NEWNET;
32 break;
33+ // ?man -p: preserve file attributes
34 case 'p':
35 flags |= CLONE_NEWPID;
36 break;
37+ // ?man -U: specify option flag
38 case 'U':
39 flags |= CLONE_NEWUSER;
40 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+vtallow: allow non-root vt access
5+usage: vtallow n | y
6+
7+allow non-root users to access virtual terminal devices
8+*/
9+
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <sys/ioctl.h>
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+host: dns lookup utility
5+usage: host [-t type] name [server]
6+
7+look up hostnames and IP addresses using dns
8+*/
9+
10 #include "util.h"
11 #include "arg.h"
12
13@@ -269,6 +276,7 @@ main(int argc, char *argv[])
14 int i, r;
15
16 ARGBEGIN {
17+ // ?man -t: sort or specify timestamp
18 case 't':
19 tflag = EARGF(usage());
20 break;
+10,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+httpd: simple http daemon
5+usage: httpd [-e string] [-d string] [-v] [dir]
6+
7+serve static files over http
8+*/
9+
10 #include "util.h"
11 #include "arg.h"
12
13@@ -257,12 +264,15 @@ main(int argc, char *argv[])
14 char *enc;
15
16 ARGBEGIN {
17+ // ?man -e: specify expression or pattern
18 case 'e':
19 eflag = EARGF(usage());
20 break;
21+ // ?man -d: specify directory
22 case 'd':
23 dflag = EARGF(usage());
24 break;
25+ // ?man -v: verbose mode; show progress
26 case 'v':
27 break;
28 default:
+95,
-187
1@@ -1,13 +1,15 @@
2 /* See LICENSE file for copyright and license details. */
3-#include "util.h"
4-#include "arg.h"
5+/* ?man
6+ifconfig: configure network interfaces
7+usage: ifconfig [-a] [interface [action ...]]
8+
9+configure network interface parameters and view stats
10+*/
11
12 #include <arpa/inet.h>
13 #include <ctype.h>
14 #include <errno.h>
15 #include <net/if.h>
16-#include <net/if_arp.h>
17-#include <netinet/in.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21@@ -16,6 +18,12 @@
22 #include <sys/types.h>
23 #include <unistd.h>
24
25+#include "util.h"
26+
27+int net_get_interfaces(struct NetInterface **, int *);
28+int net_get_stats(const char *, struct NetStats *);
29+int net_set_txqueuelen(const char *, int);
30+
31 static void
32 usage(void)
33 {
34@@ -23,200 +31,82 @@ usage(void)
35 }
36
37 static void
38-print_ipv6(const char *name)
39+display_interface(const struct NetInterface *iface)
40 {
41- FILE *fp = fopen("/proc/net/if_inet6", "r");
42- char line[256];
43- char addr_hex[40];
44- char ifname[IFNAMSIZ];
45- unsigned int ifindex, plen, scope, flags;
46- int i, j;
47- char ipv6_str[40];
48- const char *scope_str;
49-
50- if (!fp)
51- return;
52-
53- while (fgets(line, sizeof(line), fp)) {
54- if (sscanf(line, "%32s %x %x %x %x %15s", addr_hex, &ifindex, &plen, &scope, &flags, ifname) == 6) {
55- if (strcmp(name, ifname) == 0) {
56- j = 0;
57- for (i = 0; i < 32; i++) {
58- ipv6_str[j++] = addr_hex[i];
59- if (i > 0 && i < 31 && (i % 4 == 3))
60- ipv6_str[j++] = ':';
61- }
62- ipv6_str[j] = '\0';
63-
64- scope_str = "Unknown";
65- switch (scope) {
66- case 0x00: scope_str = "Global"; break;
67- case 0x10: scope_str = "Host"; break;
68- case 0x20: scope_str = "Link"; break;
69- case 0x40: scope_str = "Site"; break;
70- case 0x80: scope_str = "Compat"; break;
71- }
72- printf(" inet6 addr: %s/%d Scope:%s\n", ipv6_str, plen, scope_str);
73- }
74- }
75- }
76- fclose(fp);
77-}
78-
79-static void
80-display_interface(int sock, const char *name)
81-{
82- struct ifreq ifr;
83- struct sockaddr_in *sin;
84- unsigned char *hwaddr;
85- FILE *fp;
86- char line[256];
87- char *p, *ifname;
88- short flags;
89- int mtu = 0;
90- int metric = 0;
91- unsigned long long rx_bytes, rx_packets, rx_errs, rx_drop;
92- unsigned long long tx_bytes, tx_packets, tx_errs, tx_drop;
93-
94- memset(&ifr, 0, sizeof(ifr));
95- strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
96-
97- if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
98- weprintf("ioctl SIOCGIFFLAGS %s:\n", name);
99- return;
100- }
101- flags = ifr.ifr_flags;
102-
103- printf("%-9s ", name);
104-
105- if (ioctl(sock, SIOCGIFHWADDR, &ifr) >= 0) {
106- hwaddr = (unsigned char *)ifr.ifr_hwaddr.sa_data;
107- printf("Link encap:");
108- switch (ifr.ifr_hwaddr.sa_family) {
109- case ARPHRD_ETHER:
110- printf("Ethernet HWaddr %02x:%02x:%02x:%02x:%02x:%02x\n",
111- hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]);
112- break;
113- case ARPHRD_LOOPBACK:
114- printf("Local Loopback\n");
115- break;
116- default:
117- printf("UNSPEC\n");
118- break;
119- }
120+ struct NetStats stats;
121+ char ipv6_str[INET6_ADDRSTRLEN];
122+
123+ printf("%-9s Link encap:", iface->name);
124+ if (iface->has_mac) {
125+ printf("Ethernet HWaddr %02x:%02x:%02x:%02x:%02x:%02x\n",
126+ iface->mac[0], iface->mac[1], iface->mac[2],
127+ iface->mac[3], iface->mac[4], iface->mac[5]);
128+ } else if (iface->flags & IFF_LOOPBACK) {
129+ printf("Local Loopback\n");
130 } else {
131- printf("Link encap:UNSPEC\n");
132+ printf("UNSPEC\n");
133 }
134
135- memset(&ifr, 0, sizeof(ifr));
136- strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
137- if (ioctl(sock, SIOCGIFADDR, &ifr) >= 0) {
138- sin = (struct sockaddr_in *)&ifr.ifr_addr;
139- printf(" inet addr:%s", inet_ntoa(sin->sin_addr));
140-
141- memset(&ifr, 0, sizeof(ifr));
142- strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
143- if (ioctl(sock, SIOCGIFBRDADDR, &ifr) >= 0) {
144- sin = (struct sockaddr_in *)&ifr.ifr_broadaddr;
145- printf(" Bcast:%s", inet_ntoa(sin->sin_addr));
146- }
147+ if (iface->has_ipv4) {
148+ printf(" inet addr:%s", inet_ntoa(iface->ipv4_addr.sin_addr));
149+ printf(" Bcast:%s", inet_ntoa(iface->ipv4_brd.sin_addr));
150+ printf(" Mask:%s\n", inet_ntoa(iface->ipv4_mask.sin_addr));
151+ }
152
153- memset(&ifr, 0, sizeof(ifr));
154- strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
155- if (ioctl(sock, SIOCGIFNETMASK, &ifr) >= 0) {
156- sin = (struct sockaddr_in *)&ifr.ifr_netmask;
157- printf(" Mask:%s", inet_ntoa(sin->sin_addr));
158+ if (iface->has_ipv6) {
159+ if (inet_ntop(AF_INET6, &iface->ipv6_addr.sin6_addr, ipv6_str, sizeof(ipv6_str))) {
160+ printf(" inet6 addr: %s\n", ipv6_str);
161 }
162- printf("\n");
163 }
164
165- print_ipv6(name);
166-
167 printf(" ");
168- if (flags & IFF_UP) printf("UP ");
169- if (flags & IFF_BROADCAST) printf("BROADCAST ");
170- if (flags & IFF_DEBUG) printf("DEBUG ");
171- if (flags & IFF_LOOPBACK) printf("LOOPBACK ");
172- if (flags & IFF_POINTOPOINT) printf("POINTOPOINT ");
173- if (flags & IFF_RUNNING) printf("RUNNING ");
174- if (flags & IFF_NOARP) printf("NOARP ");
175- if (flags & IFF_PROMISC) printf("PROMISC ");
176- if (flags & IFF_ALLMULTI) printf("ALLMULTI ");
177- if (flags & IFF_MULTICAST) printf("MULTICAST ");
178-
179- memset(&ifr, 0, sizeof(ifr));
180- strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
181- if (ioctl(sock, SIOCGIFMTU, &ifr) >= 0)
182- mtu = ifr.ifr_mtu;
183- if (ioctl(sock, SIOCGIFMETRIC, &ifr) >= 0)
184- metric = ifr.ifr_metric;
185- printf(" MTU:%d Metric:%d\n", mtu, metric);
186-
187- fp = fopen("/proc/net/dev", "r");
188- if (fp) {
189- while (fgets(line, sizeof(line), fp)) {
190- p = strchr(line, ':');
191- if (p) {
192- *p = '\0';
193- ifname = line;
194- while (isspace(*ifname))
195- ifname++;
196- if (strcmp(ifname, name) == 0) {
197- if (sscanf(p + 1, "%llu %llu %llu %llu %*u %*u %*u %*u %llu %llu %llu %llu",
198- &rx_bytes, &rx_packets, &rx_errs, &rx_drop,
199- &tx_bytes, &tx_packets, &tx_errs, &tx_drop) == 8) {
200- printf(" RX packets:%llu errors:%llu dropped:%llu overruns:0 frame:0\n",
201- rx_packets, rx_errs, rx_drop);
202- printf(" TX packets:%llu errors:%llu dropped:%llu overruns:0 carrier:0\n",
203- tx_packets, tx_errs, tx_drop);
204- printf(" RX bytes:%llu TX bytes:%llu\n", rx_bytes, tx_bytes);
205- }
206- break;
207- }
208- }
209- }
210- fclose(fp);
211+ if (iface->flags & IFF_UP) printf("UP ");
212+ if (iface->flags & IFF_BROADCAST) printf("BROADCAST ");
213+ if (iface->flags & IFF_DEBUG) printf("DEBUG ");
214+ if (iface->flags & IFF_LOOPBACK) printf("LOOPBACK ");
215+ if (iface->flags & IFF_POINTOPOINT) printf("POINTOPOINT ");
216+ if (iface->flags & IFF_RUNNING) printf("RUNNING ");
217+ if (iface->flags & IFF_NOARP) printf("NOARP ");
218+ if (iface->flags & IFF_PROMISC) printf("PROMISC ");
219+ if (iface->flags & IFF_ALLMULTI) printf("ALLMULTI ");
220+ if (iface->flags & IFF_MULTICAST) printf("MULTICAST ");
221+
222+ printf(" MTU:%d Metric:%d\n", iface->mtu, iface->metric);
223+
224+ if (net_get_stats(iface->name, &stats) >= 0) {
225+ printf(" RX packets:%llu errors:%llu dropped:%llu overruns:0 frame:0\n",
226+ stats.rx_packets, stats.rx_errs, stats.rx_drop);
227+ printf(" TX packets:%llu errors:%llu dropped:%llu overruns:0 carrier:0\n",
228+ stats.tx_packets, stats.tx_errs, stats.tx_drop);
229+ printf(" RX bytes:%llu TX bytes:%llu\n",
230+ stats.rx_bytes, stats.tx_bytes);
231 }
232 printf("\n");
233 }
234
235 static void
236-list_interfaces(int sock, int all)
237+list_interfaces(int all)
238 {
239- FILE *fp = fopen("/proc/net/dev", "r");
240- struct ifreq ifr;
241- char line[256];
242- char *p, *name;
243- int line_num = 0;
244-
245- if (!fp)
246- eprintf("fopen /proc/net/dev:\n");
247-
248- while (fgets(line, sizeof(line), fp)) {
249- line_num++;
250- if (line_num <= 2)
251- continue;
252- p = strchr(line, ':');
253- if (p) {
254- *p = '\0';
255- name = line;
256- while (isspace(*name))
257- name++;
258- memset(&ifr, 0, sizeof(ifr));
259- strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
260- if (ioctl(sock, SIOCGIFFLAGS, &ifr) >= 0) {
261- if (all || (ifr.ifr_flags & IFF_UP)) {
262- display_interface(sock, name);
263- }
264- }
265+ struct NetInterface *ifaces = NULL;
266+ int count = 0;
267+ int i;
268+
269+ if (net_get_interfaces(&ifaces, &count) < 0)
270+ eprintf("net_get_interfaces:");
271+
272+ for (i = 0; i < count; i++) {
273+ if (all || (ifaces[i].flags & IFF_UP)) {
274+ display_interface(&ifaces[i]);
275 }
276 }
277- fclose(fp);
278+ free(ifaces);
279 }
280
281 int
282 main(int argc, char *argv[])
283 {
284+ struct NetInterface *ifaces = NULL;
285+ const struct NetInterface *iface = NULL;
286 struct ifreq ifr;
287 struct sockaddr_in *sin;
288 char *name;
289@@ -226,9 +116,11 @@ main(int argc, char *argv[])
290 int sock = -1;
291 int i = 1;
292 int prefix;
293+ int count = 0;
294 unsigned int mask;
295
296 ARGBEGIN {
297+ // ?man -a: print or show all entries
298 case 'a':
299 aflag = 1;
300 break;
301@@ -236,26 +128,37 @@ main(int argc, char *argv[])
302 usage();
303 } ARGEND
304
305- sock = socket(AF_INET, SOCK_DGRAM, 0);
306- if (sock < 0)
307- eprintf("socket:\n");
308-
309 if (argc == 0) {
310- list_interfaces(sock, aflag);
311- close(sock);
312+ list_interfaces(aflag);
313 return 0;
314 }
315
316 name = argv[0];
317
318+ if (net_get_interfaces(&ifaces, &count) < 0)
319+ eprintf("net_get_interfaces:");
320+
321+ for (prefix = 0; prefix < count; prefix++) {
322+ if (strcmp(ifaces[prefix].name, name) == 0) {
323+ iface = &ifaces[prefix];
324+ break;
325+ }
326+ }
327+
328 if (argc == 1) {
329- display_interface(sock, name);
330- close(sock);
331+ if (!iface)
332+ eprintf("interface %s not found\n", name);
333+ display_interface(iface);
334+ free(ifaces);
335 return 0;
336 }
337
338+ sock = socket(AF_INET, SOCK_DGRAM, 0);
339+ if (sock < 0)
340+ eprintf("socket:");
341+
342 memset(&ifr, 0, sizeof(ifr));
343- strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
344+ strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
345
346 while (i < argc) {
347 arg = argv[i];
348@@ -301,8 +204,8 @@ main(int argc, char *argv[])
349 i += 2;
350 } else if (strcmp(arg, "txqueuelen") == 0) {
351 if (i + 1 >= argc) eprintf("txqueuelen needs a value\n");
352- ifr.ifr_qlen = atoi(argv[i + 1]);
353- if (ioctl(sock, SIOCSIFTXQLEN, &ifr) < 0) eprintf("ioctl SIOCSIFTXQLEN:");
354+ if (net_set_txqueuelen(name, atoi(argv[i + 1])) < 0)
355+ eprintf("net_set_txqueuelen:");
356 i += 2;
357 } else if (strcmp(arg, "dstaddr") == 0 || strcmp(arg, "pointopoint") == 0) {
358 if (i + 1 >= argc) eprintf("%s needs an address\n", arg);
359@@ -387,6 +290,11 @@ main(int argc, char *argv[])
360 }
361 }
362
363+ free(ifaces);
364 close(sock);
365+
366+ if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
367+ return 1;
368+
369 return 0;
370 }
+269,
-0
1@@ -0,0 +1,269 @@
2+/* See LICENSE file for copyright and license details. */
3+/* ?man
4+ip: show or configure routing and devices
5+usage: ip [addr | link | route] [args...]
6+
7+configure and view network devices, routing, and tunnels
8+*/
9+
10+#include <arpa/inet.h>
11+#include <ctype.h>
12+#include <errno.h>
13+#include <net/if.h>
14+#include <stdio.h>
15+#include <stdlib.h>
16+#include <string.h>
17+#include <sys/socket.h>
18+#include <sys/types.h>
19+#include <unistd.h>
20+
21+#include "util.h"
22+
23+int net_get_interfaces(struct NetInterface **, int *);
24+int net_get_stats(const char *, struct NetStats *);
25+int net_set_flags(const char *, unsigned int, int);
26+int net_set_mtu(const char *, int);
27+int net_set_mac(const char *, const unsigned char *);
28+int net_add_addr(const char *, const char *, int);
29+int net_del_addr(const char *, const char *, int);
30+int net_show_routes(void);
31+
32+static void
33+usage(void)
34+{
35+ eprintf("usage: %s [addr | link | route] [args...]\n", argv0);
36+}
37+
38+static void
39+parse_mac(const char *str, unsigned char mac[6])
40+{
41+ unsigned int val;
42+ int i;
43+ const char *p = str;
44+
45+ for (i = 0; i < 6; i++) {
46+ if (i > 0) {
47+ if (*p != ':')
48+ eprintf("invalid mac address: %s\n", str);
49+ p++;
50+ }
51+ if (sscanf(p, "%2x", &val) != 1)
52+ eprintf("invalid mac address: %s\n", str);
53+ mac[i] = val;
54+ p += 2;
55+ }
56+}
57+
58+static void
59+print_link_iface(const struct NetInterface *iface)
60+{
61+ printf("%d: %s: mtu %d ", iface->metric, iface->name, iface->mtu);
62+ if (iface->flags & IFF_UP) printf("UP ");
63+ if (iface->flags & IFF_LOOPBACK) printf("LOOPBACK ");
64+ if (iface->flags & IFF_POINTOPOINT) printf("POINTOPOINT ");
65+ if (iface->flags & IFF_RUNNING) printf("RUNNING ");
66+ printf("\n");
67+ if (iface->has_mac) {
68+ printf(" link/ether %02x:%02x:%02x:%02x:%02x:%02x\n",
69+ iface->mac[0], iface->mac[1], iface->mac[2],
70+ iface->mac[3], iface->mac[4], iface->mac[5]);
71+ }
72+}
73+
74+static void
75+print_addr_iface(const struct NetInterface *iface)
76+{
77+ char ipv6_str[INET6_ADDRSTRLEN];
78+
79+ print_link_iface(iface);
80+ if (iface->has_ipv4) {
81+ printf(" inet %s netmask %s\n",
82+ inet_ntoa(iface->ipv4_addr.sin_addr),
83+ inet_ntoa(iface->ipv4_mask.sin_addr));
84+ }
85+ if (iface->has_ipv6) {
86+ if (inet_ntop(AF_INET6, &iface->ipv6_addr.sin6_addr, ipv6_str, sizeof(ipv6_str))) {
87+ printf(" inet6 %s\n", ipv6_str);
88+ }
89+ }
90+}
91+
92+static int
93+do_addr(int argc, char *argv[])
94+{
95+ struct NetInterface *ifaces = NULL;
96+ char *cmd, *ip, *dev, *slash;
97+ int count = 0;
98+ int prefix = -1;
99+ int i, r;
100+
101+ if (argc == 0)
102+ cmd = "show";
103+ else
104+ cmd = argv[0];
105+
106+ if (strcmp(cmd, "show") == 0 || strcmp(cmd, "list") == 0) {
107+ dev = NULL;
108+ if (argc > 1) {
109+ if (strcmp(argv[1], "dev") == 0 && argc > 2)
110+ dev = argv[2];
111+ else
112+ dev = argv[1];
113+ }
114+ if (net_get_interfaces(&ifaces, &count) < 0)
115+ eprintf("net_get_interfaces:");
116+ for (i = 0; i < count; i++) {
117+ if (!dev || strcmp(ifaces[i].name, dev) == 0)
118+ print_addr_iface(&ifaces[i]);
119+ }
120+ free(ifaces);
121+ return 0;
122+ }
123+
124+ if (strcmp(cmd, "add") == 0 || strcmp(cmd, "del") == 0) {
125+ if (argc < 4)
126+ eprintf("usage: ip addr %s <ip>/<prefix> dev <interface>\n", cmd);
127+ ip = argv[1];
128+ dev = NULL;
129+ for (i = 2; i < argc; i++) {
130+ if (strcmp(argv[i], "dev") == 0 && i + 1 < argc) {
131+ dev = argv[i + 1];
132+ break;
133+ }
134+ }
135+ if (!dev)
136+ eprintf("missing dev parameter\n");
137+
138+ slash = strchr(ip, '/');
139+ if (slash) {
140+ *slash = '\0';
141+ prefix = atoi(slash + 1);
142+ }
143+
144+ if (strcmp(cmd, "add") == 0)
145+ r = net_add_addr(dev, ip, prefix);
146+ else
147+ r = net_del_addr(dev, ip, prefix);
148+
149+ if (r < 0)
150+ eprintf("net_%s_addr:", cmd);
151+ return 0;
152+ }
153+
154+ usage();
155+ return 1;
156+}
157+
158+static int
159+do_link(int argc, char *argv[])
160+{
161+ struct NetInterface *ifaces = NULL;
162+ char *cmd, *dev, *mac_str;
163+ int count = 0;
164+ int i, mtu;
165+ unsigned char mac[6];
166+
167+ if (argc == 0)
168+ cmd = "show";
169+ else
170+ cmd = argv[0];
171+
172+ if (strcmp(cmd, "show") == 0 || strcmp(cmd, "list") == 0) {
173+ dev = NULL;
174+ if (argc > 1) {
175+ if (strcmp(argv[1], "dev") == 0 && argc > 2)
176+ dev = argv[2];
177+ else
178+ dev = argv[1];
179+ }
180+ if (net_get_interfaces(&ifaces, &count) < 0)
181+ eprintf("net_get_interfaces:");
182+ for (i = 0; i < count; i++) {
183+ if (!dev || strcmp(ifaces[i].name, dev) == 0)
184+ print_link_iface(&ifaces[i]);
185+ }
186+ free(ifaces);
187+ return 0;
188+ }
189+
190+ if (strcmp(cmd, "set") == 0) {
191+ if (argc < 3)
192+ eprintf("usage: ip link set <interface> [up | down | mtu <mtu> | address <mac>]\n");
193+ dev = argv[1];
194+ for (i = 2; i < argc; i++) {
195+ if (strcmp(argv[i], "up") == 0) {
196+ if (net_set_flags(dev, IFF_UP, 1) < 0)
197+ eprintf("net_set_flags up:");
198+ } else if (strcmp(argv[i], "down") == 0) {
199+ if (net_set_flags(dev, IFF_UP, 0) < 0)
200+ eprintf("net_set_flags down:");
201+ } else if (strcmp(argv[i], "mtu") == 0 && i + 1 < argc) {
202+ mtu = atoi(argv[i + 1]);
203+ if (net_set_mtu(dev, mtu) < 0)
204+ eprintf("net_set_mtu:");
205+ i++;
206+ } else if (strcmp(argv[i], "address") == 0 && i + 1 < argc) {
207+ mac_str = argv[i + 1];
208+ parse_mac(mac_str, mac);
209+ if (net_set_mac(dev, mac) < 0)
210+ eprintf("net_set_mac:");
211+ i++;
212+ }
213+ }
214+ return 0;
215+ }
216+
217+ usage();
218+ return 1;
219+}
220+
221+static int
222+do_route(int argc, char *argv[])
223+{
224+ char *cmd;
225+
226+ if (argc == 0)
227+ cmd = "show";
228+ else
229+ cmd = argv[0];
230+
231+ if (strcmp(cmd, "show") == 0 || strcmp(cmd, "list") == 0) {
232+ if (net_show_routes() < 0)
233+ eprintf("net_show_routes:");
234+ return 0;
235+ }
236+
237+ usage();
238+ return 1;
239+}
240+
241+int
242+main(int argc, char *argv[])
243+{
244+ char *obj;
245+
246+ ARGBEGIN {
247+ default:
248+ usage();
249+ } ARGEND
250+
251+ if (argc == 0)
252+ usage();
253+
254+ obj = argv[0];
255+
256+ if (strcmp(obj, "addr") == 0 || strcmp(obj, "address") == 0) {
257+ return do_addr(argc - 1, argv + 1);
258+ } else if (strcmp(obj, "link") == 0) {
259+ return do_link(argc - 1, argv + 1);
260+ } else if (strcmp(obj, "route") == 0) {
261+ return do_route(argc - 1, argv + 1);
262+ }
263+
264+ usage();
265+
266+ if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
267+ return 1;
268+
269+ return 0;
270+}
+10,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+netcat: read and write data across network connections
5+usage: netcat [-lu] [-p localport] [host] [port]
6+
7+arbitrary data transmission over tcp or udp
8+*/
9+
10 #include <sys/socket.h>
11 #include <sys/types.h>
12
13@@ -65,12 +72,15 @@ main(int argc, char *argv[])
14
15 ARGBEGIN
16 {
17+ // ?man -l: list in long format
18 case 'l':
19 lflag = 1;
20 break;
21+ // ?man -p: preserve file attributes
22 case 'p':
23 local_port = EARGF(usage());
24 break;
25+ // ?man -u: unbuffered output
26 case 'u':
27 uflag = 1;
28 break;
+13,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+ping: send icmp echo requests
5+usage: ping [-c count] [-i interval] [-s size] [-t ttl] [-w deadline] [-q] host
6+
7+send icmp echo requests to verify network connectivity
8+*/
9+
10 #include "util.h"
11 #include "arg.h"
12
13@@ -136,21 +143,27 @@ main(int argc, char *argv[])
14 socklen_t fromlen;
15
16 ARGBEGIN {
17+ // ?man -c: print count or perform stdout action
18 case 'c':
19 cflag = EARGF(usage());
20 break;
21+ // ?man -i: interactive mode or prompt for confirmation
22 case 'i':
23 iflag = EARGF(usage());
24 break;
25+ // ?man -s: silent mode or print summary
26 case 's':
27 sflag = EARGF(usage());
28 break;
29+ // ?man -t: sort or specify timestamp
30 case 't':
31 tflag = EARGF(usage());
32 break;
33+ // ?man -w: wait for completion
34 case 'w':
35 wflag = EARGF(usage());
36 break;
37+ // ?man -q: quiet mode; suppress output
38 case 'q':
39 qflag = 1;
40 break;
+11,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+tftp: tftp client
5+usage: tftp -h host [-p port] [-x | -c] file
6+
7+transfer files to and from a remote tftp server
8+*/
9+
10 #include <sys/time.h>
11 #include <sys/types.h>
12 #include <sys/socket.h>
13@@ -256,15 +263,19 @@ main(int argc, char *argv[])
14 int ret;
15
16 ARGBEGIN {
17+ // ?man -h: suppress headers or print help
18 case 'h':
19 host = EARGF(usage());
20 break;
21+ // ?man -p: preserve file attributes
22 case 'p':
23 port = EARGF(usage());
24 break;
25+ // ?man -x: hex format or match whole lines
26 case 'x':
27 fn = getfile;
28 break;
29+ // ?man -c: print count or perform stdout action
30 case 'c':
31 fn = putfile;
32 break;
+266,
-69
1@@ -1,6 +1,14 @@
2-/* See LICENSE file for copyright and license details. */
3+/* see license file for copyright and license details */
4+/* ?man
5+wget: retrieve files from the web
6+usage: wget [-cqS] [-O file] [-P dir] [-T timeout] [-U user_agent] [-post-data data] [-post-file file] [-header header] [-no-check-certificate] [-spider] url
7+
8+download files over http or https
9+*/
10+
11 #include "util.h"
12 #include "arg.h"
13+#include "tls.h"
14
15 #include <arpa/inet.h>
16 #include <ctype.h>
17@@ -12,20 +20,44 @@
18 #include <stdlib.h>
19 #include <string.h>
20 #include <sys/socket.h>
21+#include <sys/stat.h>
22 #include <sys/types.h>
23 #include <unistd.h>
24
25 struct Stream {
26- int fd;
27+ struct TlsSocket *ts;
28 char buf[8192];
29 size_t len;
30 size_t idx;
31 };
32
33+static int qflag = 0;
34+static int Sflag = 0;
35+static int cflag = 0;
36+static int spider = 0;
37+static int no_check_certificate = 0;
38+static int timeout_sec = 900;
39+static char *Pflag = NULL;
40+static char *Oflag = NULL;
41+static char *user_agent = "wget/aruu";
42+static char *post_data = NULL;
43+static char *post_file = NULL;
44+static char **custom_headers = NULL;
45+static size_t custom_headers_num = 0;
46+
47 static void
48 usage(void)
49 {
50- eprintf("usage: %s [-O file] [-p post_data] [-r limit] url\n", argv0);
51+ eprintf("usage: %s [-cqS] [-O file] [-P dir] [-T timeout] [-U user_agent] "
52+ "[-post-data data] [-post-file file] [-header header] "
53+ "[-no-check-certificate] [-spider] url\n", argv0);
54+}
55+
56+static void
57+add_header(const char *hdr)
58+{
59+ custom_headers = ereallocarray(custom_headers, custom_headers_num + 1, sizeof(*custom_headers));
60+ custom_headers[custom_headers_num++] = estrdup(hdr);
61 }
62
63 static int
64@@ -40,7 +72,8 @@ dial(const char *host, const char *port)
65
66 r = getaddrinfo(host, port, &hints, &res);
67 if (r != 0) {
68- weprintf("getaddrinfo %s:%s: %s\n", host, port, gai_strerror(r));
69+ if (!qflag)
70+ weprintf("getaddrinfo %s:%s: %s\n", host, port, gai_strerror(r));
71 return -1;
72 }
73
74@@ -48,6 +81,13 @@ dial(const char *host, const char *port)
75 fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
76 if (fd < 0)
77 continue;
78+ if (timeout_sec > 0) {
79+ struct timeval tv;
80+ tv.tv_sec = timeout_sec;
81+ tv.tv_usec = 0;
82+ setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
83+ setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
84+ }
85 if (connect(fd, rp->ai_addr, rp->ai_addrlen) == 0)
86 break;
87 close(fd);
88@@ -59,14 +99,16 @@ dial(const char *host, const char *port)
89 }
90
91 static void
92-parse_url(char *url, char **host, char **port, char **path)
93+parse_url(char *url, char **host, char **port, char **path, int *is_tls)
94 {
95 char *p, *ss;
96
97+ *is_tls = 0;
98 if (strncasecmp(url, "http://", 7) == 0) {
99 url += 7;
100 } else if (strncasecmp(url, "https://", 8) == 0) {
101 url += 8;
102+ *is_tls = 1;
103 } else {
104 eprintf("unsupported protocol or invalid url: %s\n", url);
105 }
106@@ -90,7 +132,7 @@ parse_url(char *url, char **host, char **port, char **path)
107 if (*ss == ':')
108 *port = ss + 1;
109 else
110- *port = "80";
111+ *port = *is_tls ? "443" : "80";
112 } else {
113 eprintf("invalid ipv6 literal: %s\n", *host);
114 }
115@@ -100,7 +142,7 @@ parse_url(char *url, char **host, char **port, char **path)
116 *p = '\0';
117 *port = p + 1;
118 } else {
119- *port = "80";
120+ *port = *is_tls ? "443" : "80";
121 }
122 }
123 }
124@@ -136,7 +178,7 @@ stream_getc(struct Stream *s)
125 return (unsigned char)s->buf[s->idx++];
126 }
127 s->idx = 0;
128- r = read(s->fd, s->buf, sizeof(s->buf));
129+ r = tls_read(s->ts, s->buf, sizeof(s->buf));
130 if (r <= 0) {
131 s->len = 0;
132 return EOF;
133@@ -161,7 +203,7 @@ stream_read(struct Stream *s, void *ptr, size_t size)
134 total += n;
135 } else {
136 s->idx = 0;
137- r = read(s->fd, s->buf, sizeof(s->buf));
138+ r = tls_read(s->ts, s->buf, sizeof(s->buf));
139 if (r <= 0) {
140 s->len = 0;
141 break;
142@@ -242,13 +284,24 @@ read_non_chunked(struct Stream *s, int out_fd, long long content_len)
143 }
144 }
145
146+static void
147+req_printf(struct TlsSocket *ts, const char *fmt, ...)
148+{
149+ va_list ap;
150+ char buf[1024];
151+ int len;
152+
153+ va_start(ap, fmt);
154+ len = vsnprintf(buf, sizeof(buf), fmt, ap);
155+ va_end(ap);
156+ if (len > 0)
157+ tls_write(ts, buf, len);
158+}
159+
160 int
161 main(int argc, char *argv[])
162 {
163 struct Stream s;
164- char *Oflag = NULL;
165- char *pflag = NULL;
166- char *rflag = NULL;
167 char *url, *host, *port, *path, *loc;
168 char *curr_host, *curr_port, *curr_path;
169 char *new_url;
170@@ -258,7 +311,7 @@ main(int argc, char *argv[])
171 char *out_name;
172 int redirects = 0;
173 int max_redirects = 20;
174- int sock = -1;
175+ int sock_fd = -1;
176 int out_fd = 1;
177 int chunked;
178 int status;
179@@ -267,16 +320,83 @@ main(int argc, char *argv[])
180 ssize_t n;
181 size_t dir_len;
182 char *last_slash;
183+ int is_tls = 0;
184+ struct TlsSocket *tls_sock = NULL;
185+ off_t resume_offset = 0;
186+ int out_mode = O_WRONLY | O_CREAT | O_TRUNC;
187+ long long post_len = 0;
188+ int post_fd = -1;
189+ size_t i;
190
191 ARGBEGIN {
192+ // ?man -O: specify output file path
193 case 'O':
194 Oflag = EARGF(usage());
195 break;
196- case 'p':
197- pflag = EARGF(usage());
198+ // ?man -P: specify output directory prefix
199+ case 'P':
200+ Pflag = EARGF(usage());
201+ break;
202+ // ?man -T: set network read and connect timeout
203+ case 'T':
204+ timeout_sec = estrtonum(EARGF(usage()), 0, 100000);
205+ break;
206+ // ?man -U: set User-Agent header
207+ case 'U':
208+ user_agent = EARGF(usage());
209 break;
210- case 'r':
211- rflag = EARGF(usage());
212+ // ?man -c: continue retrieval of aborted transfer
213+ case 'c':
214+ cflag = 1;
215+ break;
216+ // ?man -q: quiet mode to suppress stderr output
217+ case 'q':
218+ qflag = 1;
219+ break;
220+ // ?man -S: print server response headers to stderr
221+ case 'S':
222+ Sflag = 1;
223+ break;
224+ case '-':
225+ if (strcmp(argv[0], "-no-check-certificate") == 0) {
226+ no_check_certificate = 1;
227+ brk_ = 1;
228+ } else if (strncmp(argv[0], "-header=", 8) == 0) {
229+ add_header(argv[0] + 8);
230+ brk_ = 1;
231+ } else if (strcmp(argv[0], "-header") == 0) {
232+ brk_ = 1;
233+ if (!argv[1])
234+ usage();
235+ add_header(argv[1]);
236+ argv++;
237+ argc--;
238+ } else if (strncmp(argv[0], "-post-data=", 11) == 0) {
239+ post_data = argv[0] + 11;
240+ brk_ = 1;
241+ } else if (strcmp(argv[0], "-post-data") == 0) {
242+ brk_ = 1;
243+ if (!argv[1])
244+ usage();
245+ post_data = argv[1];
246+ argv++;
247+ argc--;
248+ } else if (strncmp(argv[0], "-post-file=", 11) == 0) {
249+ post_file = argv[0] + 11;
250+ brk_ = 1;
251+ } else if (strcmp(argv[0], "-post-file") == 0) {
252+ brk_ = 1;
253+ if (!argv[1])
254+ usage();
255+ post_file = argv[1];
256+ argv++;
257+ argc--;
258+ } else if (strcmp(argv[0], "-spider") == 0) {
259+ spider = 1;
260+ brk_ = 1;
261+ } else {
262+ usage();
263+ }
264 break;
265 default:
266 usage();
267@@ -285,47 +405,109 @@ main(int argc, char *argv[])
268 if (argc < 1)
269 usage();
270
271- if (rflag)
272- max_redirects = estrtonum(rflag, 0, 1000);
273-
274 url = estrdup(argv[0]);
275
276- while (sock < 0) {
277+ /* determine output filename early to check for resume */
278+ out_name = NULL;
279+ if (Oflag) {
280+ out_name = Oflag;
281+ } else {
282+ last_slash = strrchr(url, '/');
283+ if (last_slash && *(last_slash + 1))
284+ out_name = last_slash + 1;
285+ else
286+ out_name = "index.html";
287+
288+ if (Pflag) {
289+ char *tmp = emalloc(strlen(Pflag) + 1 + strlen(out_name) + 1);
290+ sprintf(tmp, "%s/%s", Pflag, out_name);
291+ out_name = tmp;
292+ }
293+ }
294+
295+ if (cflag && out_name && strcmp(out_name, "-") != 0) {
296+ struct stat st;
297+ if (stat(out_name, &st) == 0 && S_ISREG(st.st_mode)) {
298+ resume_offset = st.st_size;
299+ }
300+ }
301+
302+ if (post_data) {
303+ post_len = strlen(post_data);
304+ } else if (post_file) {
305+ struct stat st;
306+ post_fd = open(post_file, O_RDONLY);
307+ if (post_fd < 0)
308+ eprintf("open %s:\n", post_file);
309+ if (fstat(post_fd, &st) < 0)
310+ eprintf("stat %s:\n", post_file);
311+ post_len = st.st_size;
312+ }
313+
314+ while (!tls_sock) {
315 if (redirects > max_redirects)
316 eprintf("too many redirects\n");
317
318- /* parse url and backup original parts for relative redirect resolves */
319 curr_host = curr_port = curr_path = NULL;
320- parse_url(url, &curr_host, &curr_port, &curr_path);
321+ parse_url(url, &curr_host, &curr_port, &curr_path, &is_tls);
322
323 host = estrdup(curr_host);
324 port = estrdup(curr_port);
325 path = estrdup(curr_path);
326
327- sock = dial(host, port);
328- if (sock < 0)
329+ sock_fd = dial(host, port);
330+ if (sock_fd < 0)
331 eprintf("failed to connect to %s:%s\n", host, port);
332
333- if (pflag) {
334- dprintf(sock, "POST /%s HTTP/1.1\r\n"
335- "Host: %s\r\n"
336- "User-Agent: wget/aruu\r\n"
337-#ifdef STD_NON_POSIX
338- "Accept: */*\r\n"
339-#endif
340- "Content-Length: %zu\r\n"
341- "Connection: close\r\n\r\n",
342- path, host, strlen(pflag));
343- writeall(sock, pflag, strlen(pflag));
344- } else {
345- dprintf(sock, "GET /%s HTTP/1.1\r\n"
346- "Host: %s\r\n"
347- "User-Agent: wget/aruu\r\n"
348-#ifdef STD_NON_POSIX
349- "Accept: */*\r\n"
350-#endif
351- "Connection: close\r\n\r\n",
352- path, host);
353+ tls_sock = tls_connect(sock_fd, host, !no_check_certificate, is_tls);
354+ if (!tls_sock) {
355+ close(sock_fd);
356+ eprintf("failed to establish TLS connection with %s\n", host);
357+ }
358+
359+ /* send http request */
360+ const char *method = spider ? "HEAD" : ((post_data || post_file) ? "POST" : "GET");
361+ req_printf(tls_sock, "%s /%s HTTP/1.1\r\n", method, path);
362+ req_printf(tls_sock, "Host: %s\r\n", host);
363+ req_printf(tls_sock, "User-Agent: %s\r\n", user_agent);
364+ req_printf(tls_sock, "Connection: close\r\n");
365+
366+ if (resume_offset > 0) {
367+ req_printf(tls_sock, "Range: bytes=%lld-\r\n", (long long)resume_offset);
368+ }
369+
370+ if (post_data || post_file) {
371+ int has_ct = 0;
372+ for (i = 0; i < custom_headers_num; i++) {
373+ if (strncasecmp(custom_headers[i], "Content-Type:", 13) == 0) {
374+ has_ct = 1;
375+ break;
376+ }
377+ }
378+ if (!has_ct) {
379+ req_printf(tls_sock, "Content-Type: application/x-www-form-urlencoded\r\n");
380+ }
381+ req_printf(tls_sock, "Content-Length: %lld\r\n", post_len);
382+ }
383+
384+ for (i = 0; i < custom_headers_num; i++) {
385+ req_printf(tls_sock, "%s\r\n", custom_headers[i]);
386+ }
387+
388+ req_printf(tls_sock, "\r\n");
389+
390+ if (post_data) {
391+ tls_write(tls_sock, post_data, strlen(post_data));
392+ } else if (post_file) {
393+ char io_buf[8192];
394+ ssize_t r;
395+ while ((r = read(post_fd, io_buf, sizeof(io_buf))) > 0) {
396+ if (tls_write(tls_sock, io_buf, r) < 0) {
397+ eprintf("failed to write post data:\n");
398+ }
399+ }
400+ close(post_fd);
401+ post_fd = -1;
402 }
403
404 /* read headers */
405@@ -333,7 +515,7 @@ main(int argc, char *argv[])
406 header_end = NULL;
407 memset(s.buf, 0, sizeof(s.buf));
408 while (total_read < sizeof(s.buf) - 1) {
409- n = read(sock, s.buf + total_read, sizeof(s.buf) - 1 - total_read);
410+ n = tls_read(tls_sock, s.buf + total_read, sizeof(s.buf) - 1 - total_read);
411 if (n <= 0) {
412 if (n < 0)
413 eprintf("read socket:\n");
414@@ -351,11 +533,14 @@ main(int argc, char *argv[])
415 eprintf("http header too large or not found\n");
416
417 *header_end = '\0';
418- s.fd = sock;
419+ s.ts = tls_sock;
420 s.len = total_read;
421 s.idx = (header_end + 4) - s.buf;
422
423- /* parse HTTP status code */
424+ if (Sflag) {
425+ fprintf(stderr, "%s\n\n", s.buf);
426+ }
427+
428 if (strncasecmp(s.buf, "HTTP/1.1 ", 9) != 0 &&
429 strncasecmp(s.buf, "HTTP/1.0 ", 9) != 0) {
430 eprintf("invalid http response: %s\n", s.buf);
431@@ -372,14 +557,14 @@ main(int argc, char *argv[])
432 new_url = estrdup(loc);
433 } else if (loc[0] == '/') {
434 new_url = emalloc(8 + strlen(host) + strlen(port) + strlen(loc) + 2);
435- sprintf(new_url, "http://%s:%s%s", host, port, loc);
436+ sprintf(new_url, "%s://%s:%s%s", is_tls ? "https" : "http", host, port, loc);
437 } else {
438 last_slash = strrchr(path, '/');
439 dir_len = 0;
440 if (last_slash)
441 dir_len = last_slash - path + 1;
442 new_url = emalloc(8 + strlen(host) + strlen(port) + 1 + dir_len + strlen(loc) + 2);
443- sprintf(new_url, "http://%s:%s/", host, port);
444+ sprintf(new_url, "%s://%s:%s/", is_tls ? "https" : "http", host, port);
445 if (dir_len > 0)
446 strncat(new_url, path, dir_len);
447 strcat(new_url, loc);
448@@ -388,10 +573,23 @@ main(int argc, char *argv[])
449 free(loc);
450 free(url);
451 url = new_url;
452- close(sock);
453- sock = -1;
454+ tls_close(tls_sock, 1);
455+ tls_sock = NULL;
456 redirects++;
457- } else if (status != 200) {
458+ } else if (status == 206) {
459+ out_mode = O_WRONLY | O_CREAT | O_APPEND;
460+ } else if (status == 200) {
461+ out_mode = O_WRONLY | O_CREAT | O_TRUNC;
462+ } else if (status == 416) {
463+ if (!qflag)
464+ weprintf("file already fully retrieved or range invalid\n");
465+ tls_close(tls_sock, 1);
466+ free(url);
467+ free(host);
468+ free(port);
469+ free(path);
470+ return 0;
471+ } else {
472 eprintf("server returned status: %d\n", status);
473 }
474
475@@ -400,7 +598,12 @@ main(int argc, char *argv[])
476 free(path);
477 }
478
479- /* parse headers for content length and chunked encoding */
480+ if (spider) {
481+ tls_close(tls_sock, 1);
482+ free(url);
483+ return 0;
484+ }
485+
486 cl_str = find_header(s.buf, "Content-Length:");
487 content_len = -1;
488 if (cl_str) {
489@@ -416,34 +619,28 @@ main(int argc, char *argv[])
490 free(te_str);
491 }
492
493- /* open output file */
494- out_name = NULL;
495- if (Oflag) {
496- out_name = Oflag;
497- } else {
498- last_slash = strrchr(url, '/');
499- if (last_slash && *(last_slash + 1))
500- out_name = last_slash + 1;
501- else
502- out_name = "index.html";
503- }
504-
505 if (strcmp(out_name, "-") != 0) {
506- out_fd = open(out_name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
507+ out_fd = open(out_name, out_mode, 0644);
508 if (out_fd < 0)
509 eprintf("open %s:\n", out_name);
510 }
511
512- /* read and write body */
513 if (chunked)
514 read_chunked(&s, out_fd);
515 else
516 read_non_chunked(&s, out_fd, content_len);
517
518- close(sock);
519+ tls_close(tls_sock, 1);
520 if (out_fd != 1)
521 close(out_fd);
522+ if (Oflag != out_name && Pflag)
523+ free(out_name);
524 free(url);
525
526+ for (i = 0; i < custom_headers_num; i++) {
527+ free(custom_headers[i]);
528+ }
529+ free(custom_headers);
530+
531 return 0;
532 }
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+basename: strip directory and suffix from filenames
5+usage: basename path [suffix]
6+
7+print filename with leading directories and optional suffix removed
8+*/
9+
10 #include <libgen.h>
11 #include <stdio.h>
12 #include <string.h>
+15,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+cal: display a calendar
5+usage: cal [-1 | -3 | -y | -n num]
6+
7+print a formatted calendar of the specified month or year
8+*/
9+
10 #include <limits.h>
11 #include <stdint.h>
12 #include <stdio.h>
13@@ -175,9 +182,11 @@ main(int argc, char *argv[])
14
15 ARGBEGIN {
16 #ifdef FEATURE_CAL_EXT
17+ // ?man -1: specify option flag
18 case '1':
19 nmons = 1;
20 break;
21+ // ?man -3: specify option flag
22 case '3':
23 nmons = 3;
24 if (--month == 0) {
25@@ -185,22 +194,28 @@ main(int argc, char *argv[])
26 year--;
27 }
28 break;
29+ // ?man -c: print count or perform stdout action
30 case 'c':
31 ncols = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
32 break;
33+ // ?man -f: force the operation
34 case 'f':
35 fday = estrtonum(EARGF(usage()), 0, 6);
36 break;
37+ // ?man -m: specify mode or limit
38 case 'm': /* Monday */
39 fday = 1;
40 break;
41+ // ?man -n: print line numbers or counts
42 case 'n':
43 nmons = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
44 break;
45+ // ?man -s: silent mode or print summary
46 case 's': /* Sunday */
47 fday = 0;
48 break;
49 #endif
50+ // ?man -y: specify option flag
51 case 'y':
52 month = 1;
53 nmons = 12;
+21,
-0
1@@ -1,4 +1,13 @@
2 /* See LICENSE file for copyright and license details. */
3+
4+/* ?man
5+cat: concatenate files and print to standard output
6+usage: cat [-u] [file ...]
7+
8+cat reads each file in sequence and writes it to standard output
9+if no file is given, or a file is -, standard input is read
10+*/
11+
12 #include <fcntl.h>
13 #include <string.h>
14 #include <unistd.h>
15@@ -18,6 +27,7 @@ main(int argc, char *argv[])
16
17 ARGBEGIN {
18 case 'u':
19+ // ?man -u: ignored; accepted for posix compatibility; output is always unbuffered
20 break;
21 default:
22 usage();
23@@ -48,5 +58,16 @@ main(int argc, char *argv[])
24 }
25 }
26
27+ /* ?man
28+ ## Exit status
29+
30+ cat exits 0 on success, and >0 if an error occurs reading any file
31+ or writing to standard output
32+
33+ ## See also
34+
35+ cp(1), dd(1)
36+ */
37+
38 return ret;
39 }
+12,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+chgrp: change group ownership
5+usage: chgrp [-h] [-R [-H | -L | -P]] group file ...
6+
7+change the group ownership of files and directories
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <errno.h>
13@@ -43,14 +50,19 @@ main(int argc, char *argv[])
14 struct recursor r = { .fn = chgrp, .maxdepth = 1, .follow = 'P' };
15
16 ARGBEGIN {
17+ // ?man -h: affect symbolic links instead of referenced files
18 case 'h':
19 hflag = 1;
20 break;
21+ // ?man -R: change group ownership recursively
22 case 'R':
23 r.maxdepth = 0;
24 break;
25+ // ?man -H: specify option flag
26 case 'H':
27+ // ?man -L: specify option flag
28 case 'L':
29+ // ?man -P: specify option flag
30 case 'P':
31 r.follow = ARGC();
32 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+chmod: change file modes
5+usage: chmod [-R] mode file ...
6+
7+change the file mode bits of files and directories
8+*/
9+
10 #include <fcntl.h>
11 #include <sys/stat.h>
12
+13,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+chown: change ownership
5+usage: chown [-h] [-R [-H | -L | -P]] owner[:[group]] file ...
6+
7+change the user and group ownership of files and directories
8+*/
9+
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <grp.h>
13@@ -51,15 +58,21 @@ main(int argc, char *argv[])
14 char *owner, *group;
15
16 ARGBEGIN {
17+ // ?man -h: affect symbolic links instead of referenced files
18 case 'h':
19 hflag = 1;
20 break;
21+ // ?man -r: operate recursively
22 case 'r':
23+ // ?man -R: change files and directories recursively
24 case 'R':
25 r.maxdepth = 0;
26 break;
27+ // ?man -H: specify option flag
28 case 'H':
29+ // ?man -L: specify option flag
30 case 'L':
31+ // ?man -P: specify option flag
32 case 'P':
33 r.follow = ARGC();
34 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+cksum: checksum and count bytes
5+usage: cksum [file ...]
6+
7+print crc checksum and byte counts for files
8+*/
9+
10 #include <fcntl.h>
11 #include <inttypes.h>
12 #include <stdio.h>
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+cmp: compare two files
5+usage: cmp [-l | -s] file1 file2
6+
7+compare two files byte by byte
8+*/
9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -19,9 +26,11 @@ main(int argc, char *argv[])
14 int ret = 0, lflag = 0, sflag = 0, same = 1, b[2];
15
16 ARGBEGIN {
17+ // ?man -l: list in long format
18 case 'l':
19 lflag = 1;
20 break;
21+ // ?man -s: silent mode or print summary
22 case 's':
23 sflag = 1;
24 break;
+10,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+comm: compare two sorted files
5+usage: comm [-123] file1 file2
6+
7+compare two sorted files line by line
8+*/
9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -39,8 +46,11 @@ main(int argc, char *argv[])
14 int ret = 0, i, diff = 0, seenline = 0;
15
16 ARGBEGIN {
17+ // ?man -1: specify option flag
18 case '1':
19+ // ?man -2: specify option flag
20 case '2':
21+ // ?man -3: specify option flag
22 case '3':
23 show &= 0x07 ^ (1 << (ARGC() - '1'));
24 break;
+17,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+cp: copy files and directories
5+usage: cp [-afipv] [-R [-H | -L | -P]] source ... dest
6+
7+copy files and directories to a destination
8+*/
9+
10 #include <sys/stat.h>
11
12 #include "fs.h"
13@@ -16,28 +23,38 @@ main(int argc, char *argv[])
14 struct stat st;
15
16 ARGBEGIN {
17+ // ?man -i: prompt before overwriting existing files
18 case 'i':
19 cp_iflag = 1;
20 break;
21+ // ?man -a: archive mode; equivalent to -dpR
22 case 'a':
23 cp_follow = 'P';
24 cp_aflag = cp_pflag = cp_rflag = 1;
25 break;
26+ // ?man -f: force copy by removing existing destination files
27 case 'f':
28 cp_fflag = 1;
29 break;
30+ // ?man -p: preserve file attributes
31 case 'p':
32 cp_pflag = 1;
33 break;
34+ // ?man -r: copy directories recursively
35 case 'r':
36+ // ?man -R: copy directories recursively
37 case 'R':
38 cp_rflag = 1;
39 break;
40+ // ?man -v: verbose mode; show progress
41 case 'v':
42 cp_vflag = 1;
43 break;
44+ // ?man -H: specify option flag
45 case 'H':
46+ // ?man -L: specify option flag
47 case 'L':
48+ // ?man -P: specify option flag
49 case 'P':
50 cp_follow = ARGC();
51 break;
+13,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+cut: cut out fields from lines
5+usage: cut -b list [-n] [file ...]
6+
7+print selected parts of lines from files
8+*/
9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -166,21 +173,27 @@ main(int argc, char *argv[])
14 int ret = 0;
15
16 ARGBEGIN {
17+ // ?man -b: specify block size or base directory
18 case 'b':
19+ // ?man -c: print count or perform stdout action
20 case 'c':
21+ // ?man -f: force the operation
22 case 'f':
23 mode = ARGC();
24 parselist(EARGF(usage()));
25 break;
26+ // ?man -d: specify directory
27 case 'd':
28 delim = EARGF(usage());
29 if (!*delim)
30 eprintf("empty delimiter\n");
31 delimlen = unescape(delim);
32 break;
33+ // ?man -n: print line numbers or counts
34 case 'n':
35 nflag = 1;
36 break;
37+ // ?man -s: silent mode or print summary
38 case 's':
39 sflag = 1;
40 break;
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+date: print or set system date and time
5+usage: date [-u] [-d time] [+format | mmddHHMM[[CC]yy]]
6+
7+display or configure the system date and time
8+*/
9+
10 #include <ctype.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -73,9 +80,11 @@ main(int argc, char *argv[])
14 eprintf("time:");
15
16 ARGBEGIN {
17+ // ?man -d: specify directory
18 case 'd':
19 t = estrtonum(EARGF(usage()), 0, LLONG_MAX);
20 break;
21+ // ?man -u: unbuffered output
22 case 'u':
23 if (setenv("TZ", "UTC0", 1) < 0)
24 eprintf("setenv:");
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+dd: convert and copy a file
5+usage: dd [operand...]
6+
7+copy a file while performing conversions
8+*/
9+
10 #include <ctype.h>
11 #include <fcntl.h>
12 #include <inttypes.h>
+12,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+df: report disk space usage
5+usage: df [-a]
6+
7+display free and used disk space on filesystems
8+*/
9+
10 #include <sys/statvfs.h>
11
12 #include <mntent.h>
13@@ -97,19 +104,24 @@ main(int argc, char *argv[])
14 int ret = 0;
15
16 ARGBEGIN {
17+ // ?man -a: print or show all entries
18 case 'a':
19 aflag = 1;
20 break;
21+ // ?man -h: suppress headers or print help
22 case 'h':
23 hflag = 1;
24 kflag = 0;
25 break;
26+ // ?man -k: specify option flag
27 case 'k':
28 kflag = 1;
29 hflag = 0;
30 blksize = 1024;
31 break;
32+ // ?man -s: silent mode or print summary
33 case 's':
34+ // ?man -i: interactive mode or prompt for confirmation
35 case 'i':
36 eprintf("not implemented\n");
37 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+dirname: strip non-directory suffix from filename
5+usage: dirname path
6+
7+print the directory portion of a pathname
8+*/
9+
10 #include <libgen.h>
11 #include <stdio.h>
12
+16,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+du: estimate file space usage
5+usage: du [-a | -s] [-d depth] [-h] [-k] [-H | -L | -P] [-x] [file ...]
6+
7+display disk space used by files and directories
8+*/
9+
10 #include <sys/stat.h>
11 #include <sys/types.h>
12
13@@ -117,27 +124,36 @@ main(int argc, char *argv[])
14 char *bsize;
15
16 ARGBEGIN {
17+ // ?man -a: print or show all entries
18 case 'a':
19 aflag = 1;
20 break;
21+ // ?man -d: specify directory
22 case 'd':
23 dflag = 1;
24 maxdepth = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
25 break;
26+ // ?man -h: suppress headers or print help
27 case 'h':
28 hflag = 1;
29 break;
30+ // ?man -k: specify option flag
31 case 'k':
32 kflag = 1;
33 break;
34+ // ?man -s: silent mode or print summary
35 case 's':
36 sflag = 1;
37 break;
38+ // ?man -x: hex format or match whole lines
39 case 'x':
40 r.flags |= SAMEDEV;
41 break;
42+ // ?man -H: specify option flag
43 case 'H':
44+ // ?man -L: specify option flag
45 case 'L':
46+ // ?man -P: specify option flag
47 case 'P':
48 r.follow = ARGC();
49 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+echo: write arguments to stdout
5+usage: echo
6+
7+print the specified arguments to standard output
8+*/
9+
10 #include <stdio.h>
11 #include <string.h>
12 #include "util.h"
+11,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+ed: line editor
5+usage: ed [-s] [-p] [file]
6+
7+simple text line editor
8+*/
9+
10 #include <sys/stat.h>
11 #include <fcntl.h>
12 #include <regex.h>
13@@ -1332,6 +1339,7 @@ repeat:
14 goto print;
15 case 'l':
16 case 'n':
17+ // ?man -p: preserve file attributes
18 case 'p':
19 back(cmd);
20 chkprint(1);
21@@ -1390,6 +1398,7 @@ repeat:
22 error("nothing to undo");
23 undo();
24 break;
25+ // ?man -s: silent mode or print summary
26 case 's':
27 deflines(curln, curln);
28 c = input();
29@@ -1700,10 +1709,12 @@ int
30 main(int argc, char *argv[])
31 {
32 ARGBEGIN {
33+ // ?man -p: preserve file attributes
34 case 'p':
35 prompt = EARGF(usage());
36 optprompt = 1;
37 break;
38+ // ?man -s: silent mode or print summary
39 case 's':
40 optdiag = 0;
41 break;
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+env: run command in modified environment
5+usage: env [-i] [-u var] ... [var=value] ... [cmd [arg ...]]
6+
7+set environment variables and run a command
8+*/
9+
10 #include <errno.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -21,9 +28,11 @@ main(int argc, char *argv[])
14 int savederrno;
15
16 ARGBEGIN {
17+ // ?man -i: interactive mode or prompt for confirmation
18 case 'i':
19 *environ = NULL;
20 break;
21+ // ?man -u: unbuffered output
22 case 'u':
23 if (unsetenv(EARGF(usage())) < 0)
24 eprintf("unsetenv:");
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+expand: convert tabs to spaces
5+usage: expand [-i] [-t tablist] [file ...]
6+
7+convert tab characters to space characters
8+*/
9+
10 #include <stdint.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -93,9 +100,11 @@ main(int argc, char *argv[])
14 char *tl = "8";
15
16 ARGBEGIN {
17+ // ?man -i: interactive mode or prompt for confirmation
18 case 'i':
19 iflag = 1;
20 break;
21+ // ?man -t: sort or specify timestamp
22 case 't':
23 tl = EARGF(usage());
24 if (!*tl)
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+expr: evaluate expression
5+usage: expr
6+
7+evaluate a command line expression and print the result
8+*/
9+
10 #include <limits.h>
11 #include <stdio.h>
12 #include <stdlib.h>
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+false: return unsuccessful exit status
5+usage: false
6+
7+exit with status 1 representing failure
8+*/
9+
10 int
11 main(void)
12 {
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+find: search for files
5+usage: find [-H | -L] path ... [expression ...]
6+
7+search for files in a directory hierarchy
8+*/
9+
10 #include "config.h"
11 #include "util.h"
12
13@@ -1429,10 +1436,12 @@ main(int argc, char **argv)
14 gflags.mindepth = -1;
15
16 ARGBEGIN {
17+ // ?man -H: specify option flag
18 case 'H':
19 gflags.h = 1;
20 gflags.l = 0;
21 break;
22+ // ?man -L: specify option flag
23 case 'L':
24 gflags.l = 1;
25 gflags.h = 0;
+10,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+fold: wrap lines to fit width
5+usage: fold [-bs] [-w num | -num] [FILE ...]
6+
7+wrap input lines to fit a specified width
8+*/
9+
10 #include <ctype.h>
11 #include <stdint.h>
12 #include <stdio.h>
13@@ -89,12 +96,15 @@ main(int argc, char *argv[])
14 int ret = 0;
15
16 ARGBEGIN {
17+ // ?man -b: specify block size or base directory
18 case 'b':
19 bflag = 1;
20 break;
21+ // ?man -s: silent mode or print summary
22 case 's':
23 sflag = 1;
24 break;
25+ // ?man -w: wait for completion
26 case 'w':
27 width = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
28 break;
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+getconf: query configuration variables
5+usage: getconf [-v spec] var [path]
6+
7+query system configuration variables
8+*/
9+
10 #include <errno.h>
11 #include <unistd.h>
12 #include <limits.h>
13@@ -29,6 +36,7 @@ main(int argc, char *argv[])
14 char *str;
15
16 ARGBEGIN {
17+ // ?man -v: verbose mode; show progress
18 case 'v':
19 /* ignore */
20 EARGF(usage());
+0,
-605
1@@ -1,605 +0,0 @@
2-static const struct var confstr_l[] = {
3-#ifdef _CS_PATH
4- {"PATH", _CS_PATH},
5-#endif
6-#ifdef _CS_POSIX_V7_ILP32_OFF32_CFLAGS
7- {"POSIX_V7_ILP32_OFF32_CFLAGS", _CS_POSIX_V7_ILP32_OFF32_CFLAGS},
8-#endif
9-#ifdef _CS_POSIX_V7_ILP32_OFF32_LDFLAGS
10- {"POSIX_V7_ILP32_OFF32_LDFLAGS", _CS_POSIX_V7_ILP32_OFF32_LDFLAGS},
11-#endif
12-#ifdef _CS_POSIX_V7_ILP32_OFF32_LIBS
13- {"POSIX_V7_ILP32_OFF32_LIBS", _CS_POSIX_V7_ILP32_OFF32_LIBS},
14-#endif
15-#ifdef _CS_POSIX_V7_ILP32_OFFBIG_CFLAGS
16- {"POSIX_V7_ILP32_OFFBIG_CFLAGS", _CS_POSIX_V7_ILP32_OFFBIG_CFLAGS},
17-#endif
18-#ifdef _CS_POSIX_V7_ILP32_OFFBIG_LDFLAGS
19- {"POSIX_V7_ILP32_OFFBIG_LDFLAGS", _CS_POSIX_V7_ILP32_OFFBIG_LDFLAGS},
20-#endif
21-#ifdef _CS_POSIX_V7_ILP32_OFFBIG_LIBS
22- {"POSIX_V7_ILP32_OFFBIG_LIBS", _CS_POSIX_V7_ILP32_OFFBIG_LIBS},
23-#endif
24-#ifdef _CS_POSIX_V7_LP64_OFF64_CFLAGS
25- {"POSIX_V7_LP64_OFF64_CFLAGS", _CS_POSIX_V7_LP64_OFF64_CFLAGS},
26-#endif
27-#ifdef _CS_POSIX_V7_LP64_OFF64_LDFLAGS
28- {"POSIX_V7_LP64_OFF64_LDFLAGS", _CS_POSIX_V7_LP64_OFF64_LDFLAGS},
29-#endif
30-#ifdef _CS_POSIX_V7_LP64_OFF64_LIBS
31- {"POSIX_V7_LP64_OFF64_LIBS", _CS_POSIX_V7_LP64_OFF64_LIBS},
32-#endif
33-#ifdef _CS_POSIX_V7_LPBIG_OFFBIG_CFLAGS
34- {"POSIX_V7_LPBIG_OFFBIG_CFLAGS", _CS_POSIX_V7_LPBIG_OFFBIG_CFLAGS},
35-#endif
36-#ifdef _CS_POSIX_V7_LPBIG_OFFBIG_LDFLAGS
37- {"POSIX_V7_LPBIG_OFFBIG_LDFLAGS", _CS_POSIX_V7_LPBIG_OFFBIG_LDFLAGS},
38-#endif
39-#ifdef _CS_POSIX_V7_LPBIG_OFFBIG_LIBS
40- {"POSIX_V7_LPBIG_OFFBIG_LIBS", _CS_POSIX_V7_LPBIG_OFFBIG_LIBS},
41-#endif
42-#ifdef _CS_POSIX_V7_THREADS_CFLAGS
43- {"POSIX_V7_THREADS_CFLAGS", _CS_POSIX_V7_THREADS_CFLAGS},
44-#endif
45-#ifdef _CS_POSIX_V7_THREADS_LDFLAGS
46- {"POSIX_V7_THREADS_LDFLAGS", _CS_POSIX_V7_THREADS_LDFLAGS},
47-#endif
48-#ifdef _CS_POSIX_V7_WIDTH_RESTRICTED_ENVS
49- {"POSIX_V7_WIDTH_RESTRICTED_ENVS", _CS_POSIX_V7_WIDTH_RESTRICTED_ENVS},
50-#endif
51-#ifdef _CS_V7_ENV
52- {"V7_ENV", _CS_V7_ENV},
53-#endif
54-};
55-static const struct var limits_l[] = {
56-#ifdef _POSIX_CLOCKRES_MIN
57- {"_POSIX_CLOCKRES_MIN", _POSIX_CLOCKRES_MIN},
58-#endif
59-#ifdef _POSIX_AIO_LISTIO_MAX
60- {"_POSIX_AIO_LISTIO_MAX", _POSIX_AIO_LISTIO_MAX},
61-#endif
62-#ifdef _POSIX_AIO_MAX
63- {"_POSIX_AIO_MAX", _POSIX_AIO_MAX},
64-#endif
65-#ifdef _POSIX_ARG_MAX
66- {"_POSIX_ARG_MAX", _POSIX_ARG_MAX},
67-#endif
68-#ifdef _POSIX_CHILD_MAX
69- {"_POSIX_CHILD_MAX", _POSIX_CHILD_MAX},
70-#endif
71-#ifdef _POSIX_DELAYTIMER_MAX
72- {"_POSIX_DELAYTIMER_MAX", _POSIX_DELAYTIMER_MAX},
73-#endif
74-#ifdef _POSIX_HOST_NAME_MAX
75- {"_POSIX_HOST_NAME_MAX", _POSIX_HOST_NAME_MAX},
76-#endif
77-#ifdef _POSIX_LINK_MAX
78- {"_POSIX_LINK_MAX", _POSIX_LINK_MAX},
79-#endif
80-#ifdef _POSIX_LOGIN_NAME_MAX
81- {"_POSIX_LOGIN_NAME_MAX", _POSIX_LOGIN_NAME_MAX},
82-#endif
83-#ifdef _POSIX_MAX_CANON
84- {"_POSIX_MAX_CANON", _POSIX_MAX_CANON},
85-#endif
86-#ifdef _POSIX_MAX_INPUT
87- {"_POSIX_MAX_INPUT", _POSIX_MAX_INPUT},
88-#endif
89-#ifdef _POSIX_MQ_OPEN_MAX
90- {"_POSIX_MQ_OPEN_MAX", _POSIX_MQ_OPEN_MAX},
91-#endif
92-#ifdef _POSIX_MQ_PRIO_MAX
93- {"_POSIX_MQ_PRIO_MAX", _POSIX_MQ_PRIO_MAX},
94-#endif
95-#ifdef _POSIX_NAME_MAX
96- {"_POSIX_NAME_MAX", _POSIX_NAME_MAX},
97-#endif
98-#ifdef _POSIX_NGROUPS_MAX
99- {"_POSIX_NGROUPS_MAX", _POSIX_NGROUPS_MAX},
100-#endif
101-#ifdef _POSIX_OPEN_MAX
102- {"_POSIX_OPEN_MAX", _POSIX_OPEN_MAX},
103-#endif
104-#ifdef _POSIX_PATH_MAX
105- {"_POSIX_PATH_MAX", _POSIX_PATH_MAX},
106-#endif
107-#ifdef _POSIX_PIPE_BUF
108- {"_POSIX_PIPE_BUF", _POSIX_PIPE_BUF},
109-#endif
110-#ifdef _POSIX_RE_DUP_MAX
111- {"_POSIX_RE_DUP_MAX", _POSIX_RE_DUP_MAX},
112-#endif
113-#ifdef _POSIX_RTSIG_MAX
114- {"_POSIX_RTSIG_MAX", _POSIX_RTSIG_MAX},
115-#endif
116-#ifdef _POSIX_SEM_NSEMS_MAX
117- {"_POSIX_SEM_NSEMS_MAX", _POSIX_SEM_NSEMS_MAX},
118-#endif
119-#ifdef _POSIX_SEM_VALUE_MAX
120- {"_POSIX_SEM_VALUE_MAX", _POSIX_SEM_VALUE_MAX},
121-#endif
122-#ifdef _POSIX_SIGQUEUE_MAX
123- {"_POSIX_SIGQUEUE_MAX", _POSIX_SIGQUEUE_MAX},
124-#endif
125-#ifdef _POSIX_SSIZE_MAX
126- {"_POSIX_SSIZE_MAX", _POSIX_SSIZE_MAX},
127-#endif
128-#ifdef _POSIX_SS_REPL_MAX
129- {"_POSIX_SS_REPL_MAX", _POSIX_SS_REPL_MAX},
130-#endif
131-#ifdef _POSIX_STREAM_MAX
132- {"_POSIX_STREAM_MAX", _POSIX_STREAM_MAX},
133-#endif
134-#ifdef _POSIX_SYMLINK_MAX
135- {"_POSIX_SYMLINK_MAX", _POSIX_SYMLINK_MAX},
136-#endif
137-#ifdef _POSIX_SYMLOOP_MAX
138- {"_POSIX_SYMLOOP_MAX", _POSIX_SYMLOOP_MAX},
139-#endif
140-#ifdef _POSIX_THREAD_DESTRUCTOR_ITERATIONS
141- {"_POSIX_THREAD_DESTRUCTOR_ITERATIONS", _POSIX_THREAD_DESTRUCTOR_ITERATIONS},
142-#endif
143-#ifdef _POSIX_THREAD_KEYS_MAX
144- {"_POSIX_THREAD_KEYS_MAX", _POSIX_THREAD_KEYS_MAX},
145-#endif
146-#ifdef _POSIX_THREAD_THREADS_MAX
147- {"_POSIX_THREAD_THREADS_MAX", _POSIX_THREAD_THREADS_MAX},
148-#endif
149-#ifdef _POSIX_TIMER_MAX
150- {"_POSIX_TIMER_MAX", _POSIX_TIMER_MAX},
151-#endif
152-#ifdef _POSIX_TTY_NAME_MAX
153- {"_POSIX_TTY_NAME_MAX", _POSIX_TTY_NAME_MAX},
154-#endif
155-#ifdef _POSIX_TZNAME_MAX
156- {"_POSIX_TZNAME_MAX", _POSIX_TZNAME_MAX},
157-#endif
158-#ifdef _POSIX2_BC_BASE_MAX
159- {"_POSIX2_BC_BASE_MAX", _POSIX2_BC_BASE_MAX},
160-#endif
161-#ifdef _POSIX2_BC_DIM_MAX
162- {"_POSIX2_BC_DIM_MAX", _POSIX2_BC_DIM_MAX},
163-#endif
164-#ifdef _POSIX2_BC_SCALE_MAX
165- {"_POSIX2_BC_SCALE_MAX", _POSIX2_BC_SCALE_MAX},
166-#endif
167-#ifdef _POSIX2_BC_STRING_MAX
168- {"_POSIX2_BC_STRING_MAX", _POSIX2_BC_STRING_MAX},
169-#endif
170-#ifdef _POSIX2_CHARCLASS_NAME_MAX
171- {"_POSIX2_CHARCLASS_NAME_MAX", _POSIX2_CHARCLASS_NAME_MAX},
172-#endif
173-#ifdef _POSIX2_COLL_WEIGHTS_MAX
174- {"_POSIX2_COLL_WEIGHTS_MAX", _POSIX2_COLL_WEIGHTS_MAX},
175-#endif
176-#ifdef _POSIX2_EXPR_NEST_MAX
177- {"_POSIX2_EXPR_NEST_MAX", _POSIX2_EXPR_NEST_MAX},
178-#endif
179-#ifdef _POSIX2_LINE_MAX
180- {"_POSIX2_LINE_MAX", _POSIX2_LINE_MAX},
181-#endif
182-#ifdef _POSIX2_RE_DUP_MAX
183- {"_POSIX2_RE_DUP_MAX", _POSIX2_RE_DUP_MAX},
184-#endif
185-};
186-static const struct var sysconf_l[] = {
187-#ifdef _SC_AIO_LISTIO_MAX
188- {"AIO_LISTIO_MAX", _SC_AIO_LISTIO_MAX},
189-#endif
190-#ifdef _SC_AIO_MAX
191- {"AIO_MAX", _SC_AIO_MAX},
192-#endif
193-#ifdef _SC_AIO_PRIO_DELTA_MAX
194- {"AIO_PRIO_DELTA_MAX", _SC_AIO_PRIO_DELTA_MAX},
195-#endif
196-#ifdef _SC_ARG_MAX
197- {"ARG_MAX", _SC_ARG_MAX},
198-#endif
199-#ifdef _SC_ATEXIT_MAX
200- {"ATEXIT_MAX", _SC_ATEXIT_MAX},
201-#endif
202-#ifdef _SC_BC_BASE_MAX
203- {"BC_BASE_MAX", _SC_BC_BASE_MAX},
204-#endif
205-#ifdef _SC_BC_DIM_MAX
206- {"BC_DIM_MAX", _SC_BC_DIM_MAX},
207-#endif
208-#ifdef _SC_BC_SCALE_MAX
209- {"BC_SCALE_MAX", _SC_BC_SCALE_MAX},
210-#endif
211-#ifdef _SC_BC_STRING_MAX
212- {"BC_STRING_MAX", _SC_BC_STRING_MAX},
213-#endif
214-#ifdef _SC_CHILD_MAX
215- {"CHILD_MAX", _SC_CHILD_MAX},
216-#endif
217-#ifdef _SC_COLL_WEIGHTS_MAX
218- {"COLL_WEIGHTS_MAX", _SC_COLL_WEIGHTS_MAX},
219-#endif
220-#ifdef _SC_DELAYTIMER_MAX
221- {"DELAYTIMER_MAX", _SC_DELAYTIMER_MAX},
222-#endif
223-#ifdef _SC_EXPR_NEST_MAX
224- {"EXPR_NEST_MAX", _SC_EXPR_NEST_MAX},
225-#endif
226-#ifdef _SC_HOST_NAME_MAX
227- {"HOST_NAME_MAX", _SC_HOST_NAME_MAX},
228-#endif
229-#ifdef _SC_IOV_MAX
230- {"IOV_MAX", _SC_IOV_MAX},
231-#endif
232-#ifdef _SC_LINE_MAX
233- {"LINE_MAX", _SC_LINE_MAX},
234-#endif
235-#ifdef _SC_LOGIN_NAME_MAX
236- {"LOGIN_NAME_MAX", _SC_LOGIN_NAME_MAX},
237-#endif
238-#ifdef _SC_NGROUPS_MAX
239- {"NGROUPS_MAX", _SC_NGROUPS_MAX},
240-#endif
241-#ifdef _SC_MQ_OPEN_MAX
242- {"MQ_OPEN_MAX", _SC_MQ_OPEN_MAX},
243-#endif
244-#ifdef _SC_MQ_PRIO_MAX
245- {"MQ_PRIO_MAX", _SC_MQ_PRIO_MAX},
246-#endif
247-#ifdef _SC_OPEN_MAX
248- {"OPEN_MAX", _SC_OPEN_MAX},
249-#endif
250-#ifdef _SC_ADVISORY_INFO
251- {"_POSIX_ADVISORY_INFO", _SC_ADVISORY_INFO},
252-#endif
253-#ifdef _SC_BARRIERS
254- {"_POSIX_BARRIERS", _SC_BARRIERS},
255-#endif
256-#ifdef _SC_ASYNCHRONOUS_IO
257- {"_POSIX_ASYNCHRONOUS_IO", _SC_ASYNCHRONOUS_IO},
258-#endif
259-#ifdef _SC_CLOCK_SELECTION
260- {"_POSIX_CLOCK_SELECTION", _SC_CLOCK_SELECTION},
261-#endif
262-#ifdef _SC_CPUTIME
263- {"_POSIX_CPUTIME", _SC_CPUTIME},
264-#endif
265-#ifdef _SC_FSYNC
266- {"_POSIX_FSYNC", _SC_FSYNC},
267-#endif
268-#ifdef _SC_IPV6
269- {"_POSIX_IPV6", _SC_IPV6},
270-#endif
271-#ifdef _SC_JOB_CONTROL
272- {"_POSIX_JOB_CONTROL", _SC_JOB_CONTROL},
273-#endif
274-#ifdef _SC_MAPPED_FILES
275- {"_POSIX_MAPPED_FILES", _SC_MAPPED_FILES},
276-#endif
277-#ifdef _SC_MEMLOCK
278- {"_POSIX_MEMLOCK", _SC_MEMLOCK},
279-#endif
280-#ifdef _SC_MEMLOCK_RANGE
281- {"_POSIX_MEMLOCK_RANGE", _SC_MEMLOCK_RANGE},
282-#endif
283-#ifdef _SC_MEMORY_PROTECTION
284- {"_POSIX_MEMORY_PROTECTION", _SC_MEMORY_PROTECTION},
285-#endif
286-#ifdef _SC_MESSAGE_PASSING
287- {"_POSIX_MESSAGE_PASSING", _SC_MESSAGE_PASSING},
288-#endif
289-#ifdef _SC_MONOTONIC_CLOCK
290- {"_POSIX_MONOTONIC_CLOCK", _SC_MONOTONIC_CLOCK},
291-#endif
292-#ifdef _SC_PRIORITIZED_IO
293- {"_POSIX_PRIORITIZED_IO", _SC_PRIORITIZED_IO},
294-#endif
295-#ifdef _SC_PRIORITY_SCHEDULING
296- {"_POSIX_PRIORITY_SCHEDULING", _SC_PRIORITY_SCHEDULING},
297-#endif
298-#ifdef _SC_RAW_SOCKETS
299- {"_POSIX_RAW_SOCKETS", _SC_RAW_SOCKETS},
300-#endif
301-#ifdef _SC_READER_WRITER_LOCKS
302- {"_POSIX_READER_WRITER_LOCKS", _SC_READER_WRITER_LOCKS},
303-#endif
304-#ifdef _SC_REALTIME_SIGNALS
305- {"_POSIX_REALTIME_SIGNALS", _SC_REALTIME_SIGNALS},
306-#endif
307-#ifdef _SC_REGEXP
308- {"_POSIX_REGEXP", _SC_REGEXP},
309-#endif
310-#ifdef _SC_SAVED_IDS
311- {"_POSIX_SAVED_IDS", _SC_SAVED_IDS},
312-#endif
313-#ifdef _SC_SEMAPHORES
314- {"_POSIX_SEMAPHORES", _SC_SEMAPHORES},
315-#endif
316-#ifdef _SC_SHARED_MEMORY_OBJECTS
317- {"_POSIX_SHARED_MEMORY_OBJECTS", _SC_SHARED_MEMORY_OBJECTS},
318-#endif
319-#ifdef _SC_SHELL
320- {"_POSIX_SHELL", _SC_SHELL},
321-#endif
322-#ifdef _SC_SPAWN
323- {"_POSIX_SPAWN", _SC_SPAWN},
324-#endif
325-#ifdef _SC_SPIN_LOCKS
326- {"_POSIX_SPIN_LOCKS", _SC_SPIN_LOCKS},
327-#endif
328-#ifdef _SC_SPORADIC_SERVER
329- {"_POSIX_SPORADIC_SERVER", _SC_SPORADIC_SERVER},
330-#endif
331-#ifdef _SC_SS_REPL_MAX
332- {"_POSIX_SS_REPL_MAX", _SC_SS_REPL_MAX},
333-#endif
334-#ifdef _SC_SYNCHRONIZED_IO
335- {"_POSIX_SYNCHRONIZED_IO", _SC_SYNCHRONIZED_IO},
336-#endif
337-#ifdef _SC_THREAD_ATTR_STACKADDR
338- {"_POSIX_THREAD_ATTR_STACKADDR", _SC_THREAD_ATTR_STACKADDR},
339-#endif
340-#ifdef _SC_THREAD_ATTR_STACKSIZE
341- {"_POSIX_THREAD_ATTR_STACKSIZE", _SC_THREAD_ATTR_STACKSIZE},
342-#endif
343-#ifdef _SC_THREAD_CPUTIME
344- {"_POSIX_THREAD_CPUTIME", _SC_THREAD_CPUTIME},
345-#endif
346-#ifdef _SC_THREAD_PRIO_INHERIT
347- {"_POSIX_THREAD_PRIO_INHERIT", _SC_THREAD_PRIO_INHERIT},
348-#endif
349-#ifdef _SC_THREAD_PRIO_PROTECT
350- {"_POSIX_THREAD_PRIO_PROTECT", _SC_THREAD_PRIO_PROTECT},
351-#endif
352-#ifdef _SC_THREAD_PRIORITY_SCHEDULING
353- {"_POSIX_THREAD_PRIORITY_SCHEDULING", _SC_THREAD_PRIORITY_SCHEDULING},
354-#endif
355-#ifdef _SC_THREAD_PROCESS_SHARED
356- {"_POSIX_THREAD_PROCESS_SHARED", _SC_THREAD_PROCESS_SHARED},
357-#endif
358-#ifdef _SC_THREAD_ROBUST_PRIO_INHERIT
359- {"_POSIX_THREAD_ROBUST_PRIO_INHERIT", _SC_THREAD_ROBUST_PRIO_INHERIT},
360-#endif
361-#ifdef _SC_THREAD_ROBUST_PRIO_PROTECT
362- {"_POSIX_THREAD_ROBUST_PRIO_PROTECT", _SC_THREAD_ROBUST_PRIO_PROTECT},
363-#endif
364-#ifdef _SC_THREAD_SAFE_FUNCTIONS
365- {"_POSIX_THREAD_SAFE_FUNCTIONS", _SC_THREAD_SAFE_FUNCTIONS},
366-#endif
367-#ifdef _SC_THREAD_SPORADIC_SERVER
368- {"_POSIX_THREAD_SPORADIC_SERVER", _SC_THREAD_SPORADIC_SERVER},
369-#endif
370-#ifdef _SC_THREADS
371- {"_POSIX_THREADS", _SC_THREADS},
372-#endif
373-#ifdef _SC_TIMEOUTS
374- {"_POSIX_TIMEOUTS", _SC_TIMEOUTS},
375-#endif
376-#ifdef _SC_TIMERS
377- {"_POSIX_TIMERS", _SC_TIMERS},
378-#endif
379-#ifdef _SC_TRACE
380- {"_POSIX_TRACE", _SC_TRACE},
381-#endif
382-#ifdef _SC_TRACE_EVENT_FILTER
383- {"_POSIX_TRACE_EVENT_FILTER", _SC_TRACE_EVENT_FILTER},
384-#endif
385-#ifdef _SC_TRACE_EVENT_NAME_MAX
386- {"_POSIX_TRACE_EVENT_NAME_MAX", _SC_TRACE_EVENT_NAME_MAX},
387-#endif
388-#ifdef _SC_TRACE_INHERIT
389- {"_POSIX_TRACE_INHERIT", _SC_TRACE_INHERIT},
390-#endif
391-#ifdef _SC_TRACE_LOG
392- {"_POSIX_TRACE_LOG", _SC_TRACE_LOG},
393-#endif
394-#ifdef _SC_TRACE_NAME_MAX
395- {"_POSIX_TRACE_NAME_MAX", _SC_TRACE_NAME_MAX},
396-#endif
397-#ifdef _SC_TRACE_SYS_MAX
398- {"_POSIX_TRACE_SYS_MAX", _SC_TRACE_SYS_MAX},
399-#endif
400-#ifdef _SC_TRACE_USER_EVENT_MAX
401- {"_POSIX_TRACE_USER_EVENT_MAX", _SC_TRACE_USER_EVENT_MAX},
402-#endif
403-#ifdef _SC_TYPED_MEMORY_OBJECTS
404- {"_POSIX_TYPED_MEMORY_OBJECTS", _SC_TYPED_MEMORY_OBJECTS},
405-#endif
406-#ifdef _SC_VERSION
407- {"_POSIX_VERSION", _SC_VERSION},
408-#endif
409-#ifdef _SC_V7_ILP32_OFF32
410- {"_POSIX_V7_ILP32_OFF32", _SC_V7_ILP32_OFF32},
411-#endif
412-#ifdef _SC_V7_ILP32_OFFBIG
413- {"_POSIX_V7_ILP32_OFFBIG", _SC_V7_ILP32_OFFBIG},
414-#endif
415-#ifdef _SC_V7_LP64_OFF64
416- {"_POSIX_V7_LP64_OFF64", _SC_V7_LP64_OFF64},
417-#endif
418-#ifdef _SC_V7_LPBIG_OFFBIG
419- {"_POSIX_V7_LPBIG_OFFBIG", _SC_V7_LPBIG_OFFBIG},
420-#endif
421-#ifdef _SC_2_C_BIND
422- {"_POSIX2_C_BIND", _SC_2_C_BIND},
423-#endif
424-#ifdef _SC_2_C_DEV
425- {"_POSIX2_C_DEV", _SC_2_C_DEV},
426-#endif
427-#ifdef _SC_2_CHAR_TERM
428- {"_POSIX2_CHAR_TERM", _SC_2_CHAR_TERM},
429-#endif
430-#ifdef _SC_2_FORT_DEV
431- {"_POSIX2_FORT_DEV", _SC_2_FORT_DEV},
432-#endif
433-#ifdef _SC_2_FORT_RUN
434- {"_POSIX2_FORT_RUN", _SC_2_FORT_RUN},
435-#endif
436-#ifdef _SC_2_LOCALEDEF
437- {"_POSIX2_LOCALEDEF", _SC_2_LOCALEDEF},
438-#endif
439-#ifdef _SC_2_PBS
440- {"_POSIX2_PBS", _SC_2_PBS},
441-#endif
442-#ifdef _SC_2_PBS_ACCOUNTING
443- {"_POSIX2_PBS_ACCOUNTING", _SC_2_PBS_ACCOUNTING},
444-#endif
445-#ifdef _SC_2_PBS_CHECKPOINT
446- {"_POSIX2_PBS_CHECKPOINT", _SC_2_PBS_CHECKPOINT},
447-#endif
448-#ifdef _SC_2_PBS_LOCATE
449- {"_POSIX2_PBS_LOCATE", _SC_2_PBS_LOCATE},
450-#endif
451-#ifdef _SC_2_PBS_MESSAGE
452- {"_POSIX2_PBS_MESSAGE", _SC_2_PBS_MESSAGE},
453-#endif
454-#ifdef _SC_2_PBS_TRACK
455- {"_POSIX2_PBS_TRACK", _SC_2_PBS_TRACK},
456-#endif
457-#ifdef _SC_2_SW_DEV
458- {"_POSIX2_SW_DEV", _SC_2_SW_DEV},
459-#endif
460-#ifdef _SC_2_UPE
461- {"_POSIX2_UPE", _SC_2_UPE},
462-#endif
463-#ifdef _SC_2_VERSION
464- {"_POSIX2_VERSION", _SC_2_VERSION},
465-#endif
466-#ifdef _SC_PAGE_SIZE
467- {"PAGE_SIZE", _SC_PAGE_SIZE},
468-#endif
469-#ifdef _SC_PAGESIZE
470- {"PAGESIZE", _SC_PAGESIZE},
471-#endif
472-#ifdef _SC_THREAD_DESTRUCTOR_ITERATIONS
473- {"PTHREAD_DESTRUCTOR_ITERATIONS", _SC_THREAD_DESTRUCTOR_ITERATIONS},
474-#endif
475-#ifdef _SC_THREAD_KEYS_MAX
476- {"PTHREAD_KEYS_MAX", _SC_THREAD_KEYS_MAX},
477-#endif
478-#ifdef _SC_THREAD_STACK_MIN
479- {"PTHREAD_STACK_MIN", _SC_THREAD_STACK_MIN},
480-#endif
481-#ifdef _SC_THREAD_THREADS_MAX
482- {"PTHREAD_THREADS_MAX", _SC_THREAD_THREADS_MAX},
483-#endif
484-#ifdef _SC_RE_DUP_MAX
485- {"RE_DUP_MAX", _SC_RE_DUP_MAX},
486-#endif
487-#ifdef _SC_RTSIG_MAX
488- {"RTSIG_MAX", _SC_RTSIG_MAX},
489-#endif
490-#ifdef _SC_SEM_NSEMS_MAX
491- {"SEM_NSEMS_MAX", _SC_SEM_NSEMS_MAX},
492-#endif
493-#ifdef _SC_SEM_VALUE_MAX
494- {"SEM_VALUE_MAX", _SC_SEM_VALUE_MAX},
495-#endif
496-#ifdef _SC_SIGQUEUE_MAX
497- {"SIGQUEUE_MAX", _SC_SIGQUEUE_MAX},
498-#endif
499-#ifdef _SC_STREAM_MAX
500- {"STREAM_MAX", _SC_STREAM_MAX},
501-#endif
502-#ifdef _SC_SYMLOOP_MAX
503- {"SYMLOOP_MAX", _SC_SYMLOOP_MAX},
504-#endif
505-#ifdef _SC_TIMER_MAX
506- {"TIMER_MAX", _SC_TIMER_MAX},
507-#endif
508-#ifdef _SC_TTY_NAME_MAX
509- {"TTY_NAME_MAX", _SC_TTY_NAME_MAX},
510-#endif
511-#ifdef _SC_TZNAME_MAX
512- {"TZNAME_MAX", _SC_TZNAME_MAX},
513-#endif
514-#ifdef _SC_XOPEN_CRYPT
515- {"_XOPEN_CRYPT", _SC_XOPEN_CRYPT},
516-#endif
517-#ifdef _SC_XOPEN_ENH_I18N
518- {"_XOPEN_ENH_I18N", _SC_XOPEN_ENH_I18N},
519-#endif
520-#ifdef _SC_XOPEN_REALTIME
521- {"_XOPEN_REALTIME", _SC_XOPEN_REALTIME},
522-#endif
523-#ifdef _SC_XOPEN_REALTIME_THREADS
524- {"_XOPEN_REALTIME_THREADS", _SC_XOPEN_REALTIME_THREADS},
525-#endif
526-#ifdef _SC_XOPEN_SHM
527- {"_XOPEN_SHM", _SC_XOPEN_SHM},
528-#endif
529-#ifdef _SC_XOPEN_STREAMS
530- {"_XOPEN_STREAMS", _SC_XOPEN_STREAMS},
531-#endif
532-#ifdef _SC_XOPEN_UNIX
533- {"_XOPEN_UNIX", _SC_XOPEN_UNIX},
534-#endif
535-#ifdef _SC_XOPEN_UUCP
536- {"_XOPEN_UUCP", _SC_XOPEN_UUCP},
537-#endif
538-#ifdef _SC_XOPEN_VERSION
539- {"_XOPEN_VERSION", _SC_XOPEN_VERSION},
540-#endif
541-};
542-static const struct var pathconf_l[] = {
543-#ifdef _PC_FILESIZEBITS
544- {"FILESIZEBITS", _PC_FILESIZEBITS},
545-#endif
546-#ifdef _PC_LINK_MAX
547- {"LINK_MAX", _PC_LINK_MAX},
548-#endif
549-#ifdef _PC_MAX_CANON
550- {"MAX_CANON", _PC_MAX_CANON},
551-#endif
552-#ifdef _PC_MAX_INPUT
553- {"MAX_INPUT", _PC_MAX_INPUT},
554-#endif
555-#ifdef _PC_NAME_MAX
556- {"NAME_MAX", _PC_NAME_MAX},
557-#endif
558-#ifdef _PC_PATH_MAX
559- {"PATH_MAX", _PC_PATH_MAX},
560-#endif
561-#ifdef _PC_PIPE_BUF
562- {"PIPE_BUF", _PC_PIPE_BUF},
563-#endif
564-#ifdef _PC_2_SYMLINKS
565- {"POSIX2_SYMLINKS", _PC_2_SYMLINKS},
566-#endif
567-#ifdef _PC_ALLOC_SIZE_MIN
568- {"POSIX_ALLOC_SIZE_MIN", _PC_ALLOC_SIZE_MIN},
569-#endif
570-#ifdef _PC_REC_INCR_XFER_SIZE
571- {"POSIX_REC_INCR_XFER_SIZE", _PC_REC_INCR_XFER_SIZE},
572-#endif
573-#ifdef _PC_REC_MAX_XFER_SIZE
574- {"POSIX_REC_MAX_XFER_SIZE", _PC_REC_MAX_XFER_SIZE},
575-#endif
576-#ifdef _PC_REC_MIN_XFER_SIZE
577- {"POSIX_REC_MIN_XFER_SIZE", _PC_REC_MIN_XFER_SIZE},
578-#endif
579-#ifdef _PC_REC_XFER_ALIGN
580- {"POSIX_REC_XFER_ALIGN", _PC_REC_XFER_ALIGN},
581-#endif
582-#ifdef _PC_SYMLINK_MAX
583- {"SYMLINK_MAX", _PC_SYMLINK_MAX},
584-#endif
585-#ifdef _PC_CHOWN_RESTRICTED
586- {"_POSIX_CHOWN_RESTRICTED", _PC_CHOWN_RESTRICTED},
587-#endif
588-#ifdef _PC_NO_TRUNC
589- {"_POSIX_NO_TRUNC", _PC_NO_TRUNC},
590-#endif
591-#ifdef _PC_VDISABLE
592- {"_POSIX_VDISABLE", _PC_VDISABLE},
593-#endif
594-#ifdef _PC_ASYNC_IO
595- {"_POSIX_ASYNC_IO", _PC_ASYNC_IO},
596-#endif
597-#ifdef _PC_PRIO_IO
598- {"_POSIX_PRIO_IO", _PC_PRIO_IO},
599-#endif
600-#ifdef _PC_SYNC_IO
601- {"_POSIX_SYNC_IO", _PC_SYNC_IO},
602-#endif
603-#ifdef _PC_TIMESTAMP_RESOLUTION
604- {"_POSIX_TIMESTAMP_RESOLUTION", _PC_TIMESTAMP_RESOLUTION},
605-#endif
606-};
+47,
-0
1@@ -1,3 +1,11 @@
2+/* ?man
3+grep: search files for a pattern
4+usage: grep [-EFHchilnqsvwx] [-e pattern] [-f file] [pattern] [file ...]
5+
6+grep searches the named input files for lines matching the given pattern
7+if no files are named, or a file is -, standard input is searched
8+by default, matching lines are written to standard output
9+*/
10 #include "config.h"
11 #include "queue.h"
12 #include "util.h"
13@@ -255,12 +263,15 @@ main(int argc, char *argv[])
14 ARGBEGIN {
15 #if FEATURE_GREP_CONTEXT
16 case 'A':
17+ // ?man -A num: print num lines of trailing context after each match
18 Aflag = estrtonum(EARGF(usage()), 0, LONG_MAX);
19 break;
20 case 'B':
21+ // ?man -B num: print num lines of leading context before each match
22 Bflag = estrtonum(EARGF(usage()), 0, LONG_MAX);
23 break;
24 case 'C':
25+ // ?man -C num: print num lines of context before and after each match; equivalent to -A num -B num
26 Aflag = Bflag = estrtonum(EARGF(usage()), 0, LONG_MAX);
27 break;
28 ARGNUM:
29@@ -269,24 +280,29 @@ main(int argc, char *argv[])
30 #endif
31 #if FEATURE_GREP_MAX_COUNT
32 case 'm':
33+ // ?man -m num: stop reading a file after num matching lines
34 mval = estrtonum(EARGF(usage()), 0, LONG_MAX);
35 break;
36 #endif
37 case 'E':
38+ // ?man -E: interpret pattern as an extended regular expression
39 Eflag = 1;
40 Fflag = 0;
41 flags |= REG_EXTENDED;
42 break;
43 case 'F':
44+ // ?man -F: interpret pattern as a list of fixed strings separated by newlines
45 Fflag = 1;
46 Eflag = 0;
47 flags &= ~REG_EXTENDED;
48 break;
49 case 'H':
50+ // ?man -H: always print the file name with matching lines
51 Hflag = 1;
52 hflag = 0;
53 break;
54 case 'e':
55+ // ?man -e pattern: specify a pattern to match; may be given multiple times
56 arg = EARGF(usage());
57 if (!(fp = fmemopen(arg, strlen(arg) + 1, "r")))
58 eprintf("fmemopen:");
59@@ -295,6 +311,7 @@ main(int argc, char *argv[])
60 eflag = 1;
61 break;
62 case 'f':
63+ // ?man -f file: read patterns from file, one per line
64 arg = EARGF(usage());
65 fp = fopen(arg, "r");
66 if (!fp)
67@@ -304,29 +321,42 @@ main(int argc, char *argv[])
68 fflag = 1;
69 break;
70 case 'h':
71+ // ?man -h: never print file names with matching lines
72 hflag = 1;
73 Hflag = 0;
74 break;
75 case 'c':
76+ // ?man -c: print only a count of matching lines per file
77+ /* FALLTHROUGH */
78 case 'l':
79+ // ?man -l: print only the names of files with at least one matching line
80+ /* FALLTHROUGH */
81 case 'n':
82+ // ?man -n: prefix each matching line with its line number within its file
83+ /* FALLTHROUGH */
84 case 'q':
85+ // ?man -q: quiet mode; exit immediately with status 0 on first match and write nothing
86 mode = ARGC();
87 break;
88 case 'i':
89+ // ?man -i: perform case-insensitive matching
90 flags |= REG_ICASE;
91 iflag = 1;
92 break;
93 case 's':
94+ // ?man -s: suppress error messages about nonexistent or unreadable files
95 sflag = 1;
96 break;
97 case 'v':
98+ // ?man -v: invert the sense of matching to select non-matching lines
99 vflag = 1;
100 break;
101 case 'w':
102+ // ?man -w: match only whole words
103 wflag = 1;
104 break;
105 case 'x':
106+ // ?man -x: match only whole lines
107 xflag = 1;
108 break;
109 default:
110@@ -375,5 +405,22 @@ main(int argc, char *argv[])
111 if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
112 match = Error;
113
114+ /* ?man
115+ ## Exit status
116+
117+ 0
118+ : one or more lines matched in at least one file
119+
120+ 1
121+ : no lines matched in any file
122+
123+ 2
124+ : an error occurred
125+
126+ ## See also
127+
128+ sed(1), awk(1)
129+ */
130+
131 return match;
132 }
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+head: output first part of files
5+usage: head [-num | -n num] [file ...]
6+
7+print the first lines or bytes of files
8+*/
9+
10 #include <stdint.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -36,6 +43,7 @@ main(int argc, char *argv[])
14 int ret = 0, newline = 0, many = 0;
15
16 ARGBEGIN {
17+ // ?man -n: print line numbers or counts
18 case 'n':
19 n = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
20 break;
+11,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+id: print user and group ids
5+usage: id [-n] [-g | -u | -G] [user | uid]
6+
7+display real and effective user and group identities
8+*/
9+
10 #include <sys/types.h>
11
12 #include <ctype.h>
13@@ -137,15 +144,19 @@ int
14 main(int argc, char *argv[])
15 {
16 ARGBEGIN {
17+ // ?man -g: specify option flag
18 case 'g':
19 gflag = 1;
20 break;
21+ // ?man -u: unbuffered output
22 case 'u':
23 uflag = 1;
24 break;
25+ // ?man -G: specify option flag
26 case 'G':
27 Gflag = 1;
28 break;
29+ // ?man -n: print line numbers or counts
30 case 'n':
31 nflag = 1;
32 break;
+14,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+join: join lines on common field
5+usage: join [-1 field] [-2 field] [-o list] [-e string]
6+
7+join lines of two sorted files on a common field
8+*/
9+
10 #include <ctype.h>
11 #include <stdint.h>
12 #include <stdio.h>
13@@ -458,12 +465,15 @@ main(int argc, char *argv[])
14 char *fno;
15
16 ARGBEGIN {
17+ // ?man -1: specify option flag
18 case '1':
19 jf[0] = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
20 break;
21+ // ?man -2: specify option flag
22 case '2':
23 jf[1] = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
24 break;
25+ // ?man -a: print or show all entries
26 case 'a':
27 fno = EARGF(usage());
28 if (strcmp(fno, "1") == 0)
29@@ -473,17 +483,21 @@ main(int argc, char *argv[])
30 else
31 usage();
32 break;
33+ // ?man -e: specify expression or pattern
34 case 'e':
35 replace = EARGF(usage());
36 break;
37+ // ?man -o: specify output file
38 case 'o':
39 oflag = 1;
40 initolist(&output);
41 makeolist(&output, EARGF(usage()));
42 break;
43+ // ?man -t: sort or specify timestamp
44 case 't':
45 sep = EARGF(usage());
46 break;
47+ // ?man -v: verbose mode; show progress
48 case 'v':
49 pairs = 0;
50 fno = EARGF(usage());
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+kill: send signal to process
5+usage: kill [-s signame | -num | -signame] pid ...
6+
7+send a specified signal to processes by pid
8+*/
9+
10 #include "sig.h"
11 #include "util.h"
12
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+link: create a link to a file
5+usage: link target name
6+
7+create a hard link to an existing file
8+*/
9+
10 #include <unistd.h>
11
12 #include "util.h"
+11,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+ln: make links between files
5+usage: ln [-f] [-L | -P | -s] target [name]
6+
7+create hard or symbolic links between files
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <errno.h>
13@@ -25,15 +32,19 @@ main(int argc, char *argv[])
14 struct stat st, tst;
15
16 ARGBEGIN {
17+ // ?man -f: force creation of links by removing existing destination files
18 case 'f':
19 fflag = 1;
20 break;
21+ // ?man -L: specify option flag
22 case 'L':
23 flags |= AT_SYMLINK_FOLLOW;
24 break;
25+ // ?man -P: specify option flag
26 case 'P':
27 flags &= ~AT_SYMLINK_FOLLOW;
28 break;
29+ // ?man -s: make symbolic links instead of hard links
30 case 's':
31 sflag = 1;
32 break;
+11,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+logger: log messages
5+usage: logger [-is] [-p priority] [-t tag] [message ...]
6+
7+add messages to the system log
8+*/
9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -51,15 +58,19 @@ main(int argc, char *argv[])
14 char *buf = NULL, *tag = NULL;
15
16 ARGBEGIN {
17+ // ?man -i: interactive mode or prompt for confirmation
18 case 'i':
19 logflags |= LOG_PID;
20 break;
21+ // ?man -p: preserve file attributes
22 case 'p':
23 priority = decodepri(EARGF(usage()));
24 break;
25+ // ?man -s: silent mode or print summary
26 case 's':
27 logflags |= LOG_PERROR;
28 break;
29+ // ?man -t: sort or specify timestamp
30 case 't':
31 tag = EARGF(usage());
32 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+logname: print login name
5+usage: logname
6+
7+display the user login name
8+*/
9+
10 #include <stdio.h>
11 #include <unistd.h>
12
+83,
-7
1@@ -1,4 +1,11 @@
2-/* See LICENSE file for copyright and license details. */
3+/* see license file for copyright and license details */
4+/* ?man
5+ls: list directory contents
6+usage: ls [-1ACacdFfGghiLlnopqRrtUu] [--color[=always|never|auto]] [file ...]
7+
8+list information about files and directories
9+*/
10+
11 #include <sys/stat.h>
12 #include <sys/types.h>
13 #ifndef major
14@@ -58,6 +65,9 @@ static int first = 1;
15 static char sort = 0;
16 static int showdirs;
17
18+static int gflag = 0;
19+static int oflag = 0;
20+
21 static int Cflag = 0;
22 static int one_flag = 0;
23 static int termwidth = 80;
24@@ -152,17 +162,17 @@ printname(const char *name)
25 }
26 }
27
28-#if FEATURE_LS_COLOR
29 static int
30 should_color(void)
31 {
32+#if FEATURE_LS_COLOR
33 if (color_mode == COLOR_ALWAYS)
34 return 1;
35 if (color_mode == COLOR_AUTO)
36 return isatty(STDOUT_FILENO);
37+#endif
38 return 0;
39 }
40-#endif
41
42 static void
43 printname_colored(const char *name, mode_t mode)
44@@ -287,7 +297,7 @@ printcols(const struct entry *ents, size_t n)
45 printf("%lu ", (unsigned long)ents[idx].ino);
46 printname_colored(ents[idx].name, ents[idx].mode);
47 fputs(indicator(ents[idx].mode), stdout);
48-
49+
50 if (c < ncols - 1 && (c + 1) * nrows + r < (int)n) {
51 int pad = colwidths[c] - w + 2;
52 while (pad-- > 0)
53@@ -368,7 +378,11 @@ output(const struct entry *ent)
54 strftime(buf, sizeof(buf), fmt, tm);
55 else
56 snprintf(buf, sizeof(buf), "%lld", (long long)(ent->t.tv_sec));
57- printf("%s %4ld %-8.8s %-8.8s ", mode, (long)ent->nlink, pwname, grname);
58+ printf("%s %4ld ", mode, (long)ent->nlink);
59+ if (!gflag)
60+ printf("%-8.8s ", pwname);
61+ if (!oflag)
62+ printf("%-8.8s ", grname);
63
64 if (S_ISBLK(ent->mode) || S_ISCHR(ent->mode))
65 printf("%4u, %4u ", major(ent->rdev), minor(ent->rdev));
66@@ -397,9 +411,11 @@ entcmp(const void *va, const void *vb)
67 const struct entry *a = va, *b = vb;
68
69 switch (sort) {
70+ // ?man -S: sort by file size
71 case 'S':
72 cmp = b->size - a->size;
73 break;
74+ // ?man -t: sort by modification time
75 case 't':
76 if (!(cmp = b->t.tv_sec - a->t.tv_sec))
77 cmp = b->t.tv_nsec - a->t.tv_nsec;
78@@ -439,7 +455,7 @@ lsdir(const char *path, const struct entry *dir)
79
80 ents = ereallocarray(ents, ++n, sizeof(*ents));
81 mkent(&ents[n - 1], estrdup(d->d_name), Fflag || iflag ||
82- lflag || pflag || Rflag || sort, Lflag);
83+ lflag || pflag || Rflag || sort || should_color(), Lflag);
84 }
85
86 closedir(dp);
87@@ -537,7 +553,7 @@ ls(const char *path, const struct entry *ent, int listdir)
88 static void
89 usage(void)
90 {
91- eprintf("usage: %s [-1ACacdFfHhiLlnpqRrtUu] [file ...]\n", argv0);
92+ eprintf("usage: %s [-1ACacdFfGghiLlnopqRrtUu] [--color[=always|never|auto]] [file ...]\n", argv0);
93 }
94
95 int
96@@ -545,96 +561,156 @@ main(int argc, char *argv[])
97 {
98 struct entry ent, *dents, *fents;
99 size_t i, ds, fs;
100+#if FEATURE_LS_COLOR
101+ char *val;
102+#endif
103
104 if (isatty(STDOUT_FILENO))
105 Cflag = 1;
106 else
107 one_flag = 1;
108
109+#if FEATURE_LS_COLOR
110+ if ((val = getenv("CLICOLOR_FORCE"))) {
111+ if (*val && strcmp(val, "0") != 0)
112+ color_mode = COLOR_ALWAYS;
113+ else
114+ color_mode = COLOR_NEVER;
115+ } else if ((val = getenv("CLICOLOR"))) {
116+ if (*val && strcmp(val, "0") != 0)
117+ color_mode = COLOR_AUTO;
118+ else
119+ color_mode = COLOR_NEVER;
120+ }
121+#endif
122+
123 tree = ereallocarray(NULL, PATH_MAX, sizeof(*tree));
124
125 ARGBEGIN {
126+ // ?man -1: list one file per line
127 case '1':
128 one_flag = 1;
129 Cflag = 0;
130 lflag = 0;
131 break;
132+ // ?man -A: list all entries except dot and dot dot
133 case 'A':
134 Aflag = 1;
135 break;
136+ // ?man -a: list all entries including those starting with a dot
137 case 'a':
138 aflag = 1;
139 break;
140+ // ?man -c: sort by ctime or use ctime for long listing
141 case 'c':
142 cflag = 1;
143 uflag = 0;
144 break;
145+ // ?man -C: list entries in columns sorted vertically
146 case 'C':
147 Cflag = 1;
148 one_flag = 0;
149 lflag = 0;
150 break;
151+ // ?man -d: list directory entries instead of their contents
152 case 'd':
153 dflag = 1;
154 break;
155+ // ?man -f: do not sort and enable a and U
156 case 'f':
157 aflag = 1;
158 fflag = 1;
159 Uflag = 1;
160 break;
161+ // ?man -F: append type indicators
162 case 'F':
163 Fflag = 1;
164 break;
165+#if FEATURE_LS_COLOR
166+ // ?man -G: enable colored output
167+ case 'G':
168+ color_mode = COLOR_AUTO;
169+ break;
170+#endif
171+ // ?man -g: list in long format without owner name
172+ case 'g':
173+ gflag = 1;
174+ lflag = 1;
175+ Cflag = 0;
176+ one_flag = 0;
177+ break;
178+ // ?man -H: follow symlinks on the command line
179 case 'H':
180 Hflag = 1;
181 break;
182+ // ?man -h: print human readable sizes
183 case 'h':
184 hflag = 1;
185 break;
186+ // ?man -i: print inode number of each file
187 case 'i':
188 iflag = 1;
189 break;
190+ // ?man -L: follow all symlinks
191 case 'L':
192 Lflag = 1;
193 break;
194+ // ?man -l: use a long listing format
195 case 'l':
196 lflag = 1;
197 Cflag = 0;
198 one_flag = 0;
199 break;
200+ // ?man -n: list numeric uids and gids
201 case 'n':
202 lflag = 1;
203 nflag = 1;
204 Cflag = 0;
205 one_flag = 0;
206 break;
207+ // ?man -o: list in long format without group name
208+ case 'o':
209+ oflag = 1;
210+ lflag = 1;
211+ Cflag = 0;
212+ one_flag = 0;
213+ break;
214+ // ?man -p: append slash indicator to directories
215 case 'p':
216 pflag = 1;
217 break;
218+ // ?man -q: print non printable characters as question marks
219 case 'q':
220 qflag = 1;
221 break;
222+ // ?man -R: list subdirectories recursively
223 case 'R':
224 Rflag = 1;
225 break;
226+ // ?man -r: reverse sort order
227 case 'r':
228 rflag = 1;
229 break;
230+ // ?man -S: sort by file size
231 case 'S':
232 sort = 'S';
233 break;
234+ // ?man -t: sort by modification time
235 case 't':
236 sort = 't';
237 break;
238+ // ?man -U: do not sort
239 case 'U':
240 Uflag = 1;
241 break;
242+ // ?man -u: sort by atime or use atime for long listing
243 case 'u':
244 uflag = 1;
245 cflag = 0;
246 break;
247 case '-':
248 #if FEATURE_LS_COLOR
249+ // ?man --color [when]: control coloring
250 if (strncmp(argv[0], "-color", 6) == 0) {
251 char *val = NULL;
252 if (argv[0][6] == '=') {
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+mesg: control write access
5+usage: mesg [n|y]
6+
7+allow or disallow other users to write to the terminal
8+*/
9+
10 #include <sys/stat.h>
11 #include <sys/types.h>
12
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+mkdir: create directories
5+usage: mkdir [-p] [-m mode] name ...
6+
7+create directories at specified paths
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <errno.h>
13@@ -22,9 +29,11 @@ main(int argc, char *argv[])
14 mode = 0777 & ~mask;
15
16 ARGBEGIN {
17+ // ?man -p: create parent directories as needed
18 case 'p':
19 pflag = 1;
20 break;
21+ // ?man -m: set file mode bits for created directories
22 case 'm':
23 mode = parsemode(EARGF(usage()), 0777, mask);
24 break;
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+mkfifo: make fifos
5+usage: mkfifo [-m mode] name ...
6+
7+create named pipes at specified paths
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <stdlib.h>
13@@ -18,6 +25,7 @@ main(int argc, char *argv[])
14 int ret = 0;
15
16 ARGBEGIN {
17+ // ?man -m: specify mode or limit
18 case 'm':
19 mode = parsemode(EARGF(usage()), mode, umask(0));
20 break;
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+mv: move or rename files
5+usage: mv [-f] source ... dest
6+
7+move or rename files and directories
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <errno.h>
13@@ -47,6 +54,7 @@ main(int argc, char *argv[])
14 struct stat st;
15
16 ARGBEGIN {
17+ // ?man -f: do not prompt before overwriting
18 case 'f':
19 break;
20 default:
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+nice: run command with modified priority
5+usage: nice [-n inc] cmd [arg ...]
6+
7+run a command with modified scheduling priority
8+*/
9+
10 #include <sys/resource.h>
11
12 #include <errno.h>
13@@ -27,6 +34,7 @@ main(int argc, char *argv[])
14 int val = 10, r, savederrno;
15
16 ARGBEGIN {
17+ // ?man -n: print line numbers or counts
18 case 'n':
19 val = estrtonum(EARGF(usage()), PRIO_MIN, PRIO_MAX);
20 break;
+20,
-1
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+nl: number lines
5+usage: nl [-p] [-b type] [-d delim] [-f type]
6+
7+number the lines of files
8+*/
9+
10 #include <limits.h>
11 #include <stdint.h>
12 #include <stdio.h>
13@@ -71,7 +78,8 @@ nl(const char *fname, FILE *fp)
14 if (line.data[0] != '\n')
15 donumber = 1;
16 break;
17- case 'p':
18+ // ?man -p: preserve file attributes
19+ case 'p':
20 if (!regexec(preg + section, line.data, 0, NULL, 0))
21 donumber = 1;
22 break;
23@@ -124,6 +132,7 @@ main(int argc, char *argv[])
24 char *d, *formattype, *formatblit;
25
26 ARGBEGIN {
27+ // ?man -d: specify directory
28 case 'd':
29 switch (utflen((d = EARGF(usage())))) {
30 case 0:
31@@ -142,21 +151,27 @@ main(int argc, char *argv[])
32 break;
33 }
34 break;
35+ // ?man -f: force the operation
36 case 'f':
37 type[0] = getlinetype(EARGF(usage()), preg);
38 break;
39+ // ?man -b: specify block size or base directory
40 case 'b':
41 type[1] = getlinetype(EARGF(usage()), preg + 1);
42 break;
43+ // ?man -h: suppress headers or print help
44 case 'h':
45 type[2] = getlinetype(EARGF(usage()), preg + 2);
46 break;
47+ // ?man -i: interactive mode or prompt for confirmation
48 case 'i':
49 incr = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
50 break;
51+ // ?man -l: list in long format
52 case 'l':
53 blines = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
54 break;
55+ // ?man -n: print line numbers or counts
56 case 'n':
57 formattype = EARGF(usage());
58 estrlcpy(format, "%", sizeof(format));
59@@ -174,16 +189,20 @@ main(int argc, char *argv[])
60 estrlcat(format, formatblit, sizeof(format));
61 estrlcat(format, "*ld", sizeof(format));
62 break;
63+ // ?man -p: preserve file attributes
64 case 'p':
65 pflag = 1;
66 break;
67+ // ?man -s: silent mode or print summary
68 case 's':
69 sep = EARGF(usage());
70 seplen = unescape(sep);
71 break;
72+ // ?man -v: verbose mode; show progress
73 case 'v':
74 startnum = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
75 break;
76+ // ?man -w: wait for completion
77 case 'w':
78 width = estrtonum(EARGF(usage()), 1, INT_MAX);
79 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+nohup: run command immune to hangups
5+usage: nohup cmd [arg ...]
6+
7+run a command that persists after logging out
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <errno.h>
+41,
-10
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+od: dump files in formats
5+usage: od [-bdosvx] [-A addressformat]
6+
7+display file contents in octal, hex, or other formats
8+*/
9+
10 #include "queue.h"
11 #include "util.h"
12
13@@ -59,6 +66,7 @@ printchunk(const unsigned char *s, unsigned char format, size_t len)
14 };
15
16 switch (format) {
17+ // ?man -a: print or show all entries
18 case 'a':
19 c = *s & ~128; /* clear high bit as required by standard */
20 if (c < LEN(namedict) || c == 127) {
21@@ -67,6 +75,7 @@ printchunk(const unsigned char *s, unsigned char format, size_t len)
22 printf(" %3c", c);
23 }
24 break;
25+ // ?man -c: print count or perform stdout action
26 case 'c':
27 if (strchr("\a\b\t\n\v\f\r\0", *s)) {
28 printf(" %3s", escdict[*s]);
29@@ -220,69 +229,89 @@ main(int argc, char *argv[])
30 big_endian = (*(uint16_t *)"\0\xff" == 0xff);
31
32 ARGBEGIN {
33+ // ?man -A: specify option flag
34 case 'A':
35 s = EARGF(usage());
36 if (strlen(s) != 1 || !strchr("doxn", s[0]))
37 usage();
38 addr_format = s[0];
39 break;
40+ // ?man -b: specify block size or base directory
41 case 'b':
42 addtype('o', 1);
43 break;
44+ // ?man -d: specify directory
45 case 'd':
46 addtype('u', 2);
47 break;
48 #if FEATURE_OD_ENDIAN
49+ // ?man -E: specify option flag
50 case 'E':
51+ // ?man -e: specify expression or pattern
52 case 'e':
53 big_endian = (ARGC() == 'E');
54 break;
55 #endif
56+ // ?man -j: specify option flag
57 case 'j':
58 if ((skip = parseoffset(EARGF(usage()))) < 0)
59 usage();
60 break;
61+ // ?man -N: specify option flag
62 case 'N':
63 if ((max = parseoffset(EARGF(usage()))) < 0)
64 usage();
65 break;
66+ // ?man -o: specify output file
67 case 'o':
68 addtype('o', 2);
69 break;
70+ // ?man -s: silent mode or print summary
71 case 's':
72 addtype('d', 2);
73 break;
74+ // ?man -t: sort or specify timestamp
75 case 't':
76 s = EARGF(usage());
77 for (; *s; s++) {
78 switch (*s) {
79- case 'a':
80- case 'c':
81+ // ?man -a: print or show all entries
82+ case 'a':
83+ // ?man -c: print count or perform stdout action
84+ case 'c':
85 addtype(*s, 1);
86 break;
87- case 'd':
88- case 'o':
89- case 'u':
90- case 'x':
91+ // ?man -d: specify directory
92+ case 'd':
93+ // ?man -o: specify output file
94+ case 'o':
95+ // ?man -u: unbuffered output
96+ case 'u':
97+ // ?man -x: hex format or match whole lines
98+ case 'x':
99 fmt_char = *s;
100 if (isdigit((unsigned char)*(s + 1))) {
101 len = strtol(s + 1, &end, 10);
102 s = end - 1;
103 } else {
104 switch (*(s + 1)) {
105- case 'C':
106+ // ?man -C: specify option flag
107+ case 'C':
108 len = sizeof(char);
109 s++;
110 break;
111- case 'S':
112+ // ?man -S: specify option flag
113+ case 'S':
114 len = sizeof(short);
115 s++;
116 break;
117- case 'I':
118+ // ?man -I: specify option flag
119+ case 'I':
120 len = sizeof(int);
121 s++;
122 break;
123- case 'L':
124+ // ?man -L: specify option flag
125+ case 'L':
126 len = sizeof(long);
127 s++;
128 break;
129@@ -297,9 +326,11 @@ main(int argc, char *argv[])
130 }
131 }
132 break;
133+ // ?man -v: verbose mode; show progress
134 case 'v':
135 /* always set, use uniq(1) to handle duplicate lines */
136 break;
137+ // ?man -x: hex format or match whole lines
138 case 'x':
139 addtype('x', 2);
140 break;
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+paste: merge lines of files
5+usage: paste [-s] [-d list] file ...
6+
7+merge corresponding lines of files side by side
8+*/
9+
10 #include <stdlib.h>
11 #include <string.h>
12
13@@ -94,9 +101,11 @@ main(int argc, char *argv[])
14 char *delim = "\t";
15
16 ARGBEGIN {
17+ // ?man -s: silent mode or print summary
18 case 's':
19 seq = 1;
20 break;
21+ // ?man -d: specify directory
22 case 'd':
23 delim = EARGF(usage());
24 delim_bytelen = unescape(delim);
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+pathchk: check pathnames
5+usage: pathchk [-pP] filename...
6+
7+verify that pathnames are valid and portable
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <errno.h>
13@@ -84,9 +91,11 @@ main(int argc, char *argv[])
14 int ret = 0;
15
16 ARGBEGIN {
17+ // ?man -p: preserve file attributes
18 case 'p':
19 most = 1;
20 break;
21+ // ?man -P: specify option flag
22 case 'P':
23 extra = 1;
24 break;
+49,
-9
1@@ -1,3 +1,10 @@
2+/* ?man
3+pax: portable archive interchange
4+usage: pax
5+
6+read, write, and list member files of archive files
7+*/
8+
9 /* taken from: https://github.com/michaelforney/pax */
10 #ifndef _GNU_SOURCE
11 #define _GNU_SOURCE /* needed for major/minor (non-posix) */
12@@ -844,10 +851,12 @@ readpax(struct bufio *f, struct header *h)
13 case 'g':
14 readexthdr(f, &globexthdr, h->size);
15 break;
16- case 'x':
17+ // ?man -x: hex format or match whole lines
18+ case 'x':
19 readexthdr(f, &exthdr, h->size);
20 break;
21- case 'L':
22+ // ?man -L: specify option flag
23+ case 'L':
24 if ((exthdr.delete | opt.delete) & PATH)
25 break;
26 readgnuhdr(f, &exthdr.pathbuf, h->size);
27@@ -2080,8 +2089,10 @@ parsereplstr(char *str)
28 for (;;) {
29 switch (*++str) {
30 case 'g': r->global = 1; break;
31- case 'p': r->print = 1; break;
32- case 's': r->symlink = 0; break;
33+ // ?man -p: preserve file attributes
34+ case 'p': r->print = 1; break;
35+ // ?man -s: silent mode or print summary
36+ case 's': r->symlink = 0; break;
37 case 'S': r->symlink = 1; break;
38 case 0: goto done;
39 }
40@@ -2341,84 +2352,113 @@ main(int argc, char *argv[])
41 size_t l;
42
43 ARGBEGIN {
44+ // ?man -a: print or show all entries
45 case 'a':
46 aflag = 1;
47 break;
48+ // ?man -b: specify block size or base directory
49 case 'b':
50 EARGF(usage());
51 break;
52+ // ?man -c: print count or perform stdout action
53 case 'c':
54 cflag = 1;
55 break;
56+ // ?man -d: specify directory
57 case 'd':
58 dflag = 1;
59 break;
60+ // ?man -f: force the operation
61 case 'f':
62 name = EARGF(usage());
63 break;
64+ // ?man -H: specify option flag
65 case 'H':
66 follow = 'H';
67 break;
68+ // ?man -i: interactive mode or prompt for confirmation
69 case 'i':
70 iflag = 1;
71 break;
72+ // ?man -j: specify option flag
73 case 'j':
74 algo = "bzip2";
75 break;
76+ // ?man -J: specify option flag
77 case 'J':
78 algo = "xz";
79 break;
80+ // ?man -k: specify option flag
81 case 'k':
82 kflag = 1;
83 break;
84+ // ?man -l: list in long format
85 case 'l':
86 lflag = 1;
87 break;
88+ // ?man -L: specify option flag
89 case 'L':
90 follow = 'L';
91 break;
92+ // ?man -n: print line numbers or counts
93 case 'n':
94 nflag = 1;
95 break;
96+ // ?man -o: specify output file
97 case 'o':
98 parseopts(EARGF(usage()));
99 break;
100+ // ?man -p: preserve file attributes
101 case 'p':
102 for (arg = EARGF(usage()); *arg; ++arg) {
103 switch (*arg) {
104- case 'a': preserve &= ~ATIME; break;
105- case 'e': preserve = ~0; break;
106- case 'm': preserve &= ~MTIME; break;
107- case 'o': preserve |= UID | GID; break;
108- case 'p': preserve |= MODE; break;
109+ // ?man -a: print or show all entries
110+ case 'a': preserve &= ~ATIME; break;
111+ // ?man -e: specify expression or pattern
112+ case 'e': preserve = ~0; break;
113+ // ?man -m: specify mode or limit
114+ case 'm': preserve &= ~MTIME; break;
115+ // ?man -o: specify output file
116+ case 'o': preserve |= UID | GID; break;
117+ // ?man -p: preserve file attributes
118+ case 'p': preserve |= MODE; break;
119 default: fatal("unknown -p option");
120 }
121 }
122 break;
123+ // ?man -r: operate recursively
124 case 'r':
125 mode |= READ;
126 break;
127+ // ?man -s: silent mode or print summary
128 case 's':
129 parsereplstr(EARGF(usage()));
130 break;
131+ // ?man -t: sort or specify timestamp
132 case 't':
133 tflag = 1;
134 break;
135+ // ?man -u: unbuffered output
136 case 'u':
137 uflag = 1;
138 break;
139+ // ?man -v: verbose mode; show progress
140 case 'v':
141 vflag = 1;
142 break;
143+ // ?man -w: wait for completion
144 case 'w':
145 mode |= WRITE;
146 break;
147+ // ?man -x: hex format or match whole lines
148 case 'x':
149 format = EARGF(usage());
150 break;
151+ // ?man -X: specify option flag
152 case 'X':
153 Xflag = 1;
154 break;
155+ // ?man -z: specify option flag
156 case 'z':
157 algo = "gzip";
158 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+printf: format and print data
5+usage: printf format [arg ...]
6+
7+format and print arguments to standard output
8+*/
9+
10 #include <ctype.h>
11 #include <errno.h>
12 #include <limits.h>
+12,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+ps: report process status
5+usage: ps [-aAdef]
6+
7+display information about active system processes
8+*/
9+
10 #include <sys/ioctl.h>
11 #include <sys/sysinfo.h>
12
13@@ -152,18 +159,23 @@ int
14 main(int argc, char *argv[])
15 {
16 ARGBEGIN {
17+ // ?man -a: print or show all entries
18 case 'a':
19 flags |= PS_aflag;
20 break;
21+ // ?man -A: specify option flag
22 case 'A':
23 flags |= PS_Aflag;
24 break;
25+ // ?man -d: specify directory
26 case 'd':
27 flags |= PS_dflag;
28 break;
29+ // ?man -e: specify expression or pattern
30 case 'e':
31 flags |= PS_Aflag;
32 break;
33+ // ?man -f: force the operation
34 case 'f':
35 flags |= PS_fflag;
36 break;
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+pwd: print working directory
5+usage: pwd [-LP]
6+
7+display the pathname of the current working directory
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <stdio.h>
13@@ -34,7 +41,9 @@ main(int argc, char *argv[])
14 char mode = 'L';
15
16 ARGBEGIN {
17+ // ?man -L: specify option flag
18 case 'L':
19+ // ?man -P: specify option flag
20 case 'P':
21 mode = ARGC();
22 break;
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+readlink: print value of symlink
5+usage: readlink [-fn] path
6+
7+display the target of a symbolic link
8+*/
9+
10 #include <limits.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -30,10 +37,12 @@ main(int argc, char *argv[])
14 ARGBEGIN
15 {
16 #ifdef STD_NON_POSIX
17+ // ?man -f: force the operation
18 case 'f':
19 fflag = 1;
20 break;
21 #endif
22+ // ?man -n: print line numbers or counts
23 case 'n':
24 nflag = 1;
25 break;
+11,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+renice: alter priority of processes
5+usage: renice -n num [-g | -p | -u] id ...
6+
7+change the scheduling priority of running processes
8+*/
9+
10 #include <sys/resource.h>
11
12 #include <errno.h>
13@@ -50,15 +57,19 @@ main(int argc, char *argv[])
14 int who;
15
16 ARGBEGIN {
17+ // ?man -n: print line numbers or counts
18 case 'n':
19 adj = EARGF(usage());
20 break;
21+ // ?man -g: specify option flag
22 case 'g':
23 which = PRIO_PGRP;
24 break;
25+ // ?man -p: preserve file attributes
26 case 'p':
27 which = PRIO_PROCESS;
28 break;
29+ // ?man -u: unbuffered output
30 case 'u':
31 which = PRIO_USER;
32 break;
+11,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+rm: remove files
5+usage: rm [-f] [-iRr] file ...
6+
7+remove files and directory hierarchies
8+*/
9+
10 #include <fcntl.h>
11 #include <string.h>
12
13@@ -51,13 +58,17 @@ main(int argc, char *argv[])
14 struct recursor r = { .fn = rm, .maxdepth = 1, .follow = 'P' };
15
16 ARGBEGIN {
17+ // ?man -f: ignore nonexistent files and never prompt
18 case 'f':
19 r.flags |= SILENT | IGNORE;
20 break;
21+ // ?man -i: prompt before every removal
22 case 'i':
23 r.flags |= CONFIRM;
24 break;
25+ // ?man -R: remove directories and their contents recursively
26 case 'R':
27+ // ?man -r: remove directories and their contents recursively
28 case 'r':
29 r.maxdepth = 0;
30 break;
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+rmdir: remove empty directories
5+usage: rmdir [-p] dir ...
6+
7+remove empty directories from the filesystem
8+*/
9+
10 #include <libgen.h>
11 #include <string.h>
12 #include <unistd.h>
13@@ -18,6 +25,7 @@ main(int argc, char *argv[])
14 char *d;
15
16 ARGBEGIN {
17+ // ?man -p: remove parent directories if they are also empty
18 case 'p':
19 pflag = 1;
20 break;
+13,
-0
1@@ -1,3 +1,10 @@
2+/* ?man
3+sed: stream editor
4+usage: sed [-nrE] script [file ...]
5+
6+stream editor for filtering and transforming text
7+*/
8+
9 /* FIXME: summary
10 * decide whether we enforce valid UTF-8, right now it's enforced in certain
11 * parts of the script, but not the input...
12@@ -1754,24 +1761,30 @@ main(int argc, char *argv[])
13 int script = 0;
14
15 ARGBEGIN {
16+ // ?man -n: print line numbers or counts
17 case 'n':
18 gflags.n = 1;
19 break;
20+ // ?man -r: operate recursively
21 case 'r':
22+ // ?man -E: specify option flag
23 case 'E':
24 gflags.E = 1;
25 break;
26+ // ?man -e: specify expression or pattern
27 case 'e':
28 arg = EARGF(usage());
29 compile(arg, 0);
30 script = 1;
31 break;
32+ // ?man -f: force the operation
33 case 'f':
34 arg = EARGF(usage());
35 compile(arg, 1);
36 script = 1;
37 break;
38 #if FEATURE_SED_INPLACE
39+ // ?man -i: interactive mode or prompt for confirmation
40 case 'i':
41 iflag = 1;
42 if (argv[0][1] != '\0') {
+1,
-1
1@@ -63,7 +63,7 @@
2 #include "show.h"
3 #include "mystring.h"
4 #ifndef NO_HISTORY
5-#include "myhistedit.h"
6+#include "lineedit.h"
7 #endif
8
9
+0,
-802
1@@ -1,802 +0,0 @@
2-/*-
3- * SPDX-License-Identifier: BSD-3-Clause
4- *
5- * Copyright (c) 1993
6- * The Regents of the University of California. All rights reserved.
7- *
8- * This code is derived from software contributed to Berkeley by
9- * Kenneth Almquist.
10- *
11- * Redistribution and use in source and binary forms, with or without
12- * modification, are permitted provided that the following conditions
13- * are met:
14- * 1. Redistributions of source code must retain the above copyright
15- * notice, this list of conditions and the following disclaimer.
16- * 2. Redistributions in binary form must reproduce the above copyright
17- * notice, this list of conditions and the following disclaimer in the
18- * documentation and/or other materials provided with the distribution.
19- * 3. Neither the name of the University nor the names of its contributors
20- * may be used to endorse or promote products derived from this software
21- * without specific prior written permission.
22- *
23- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26- * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33- * SUCH DAMAGE.
34- */
35-
36-#if !FEATURE_SH_HISTEDIT
37-#ifndef NO_HISTORY
38-#define NO_HISTORY
39-#endif
40-#endif
41-
42-#include "shell.h"
43-#include "alias.h"
44-#include "builtins.h"
45-#include "error.h"
46-#include "eval.h"
47-#include "exec.h"
48-#include "main.h"
49-#include "memalloc.h"
50-#include "mystring.h"
51-#include "options.h"
52-#include "output.h"
53-#include "parser.h"
54-#include "var.h"
55-
56-#ifndef NO_HISTORY
57-#include "myhistedit.h"
58-
59-#include <sys/param.h>
60-#include <sys/stat.h>
61-
62-#include <dirent.h>
63-#include <errno.h>
64-#include <fcntl.h>
65-#include <limits.h>
66-#include <paths.h>
67-#include <stdio.h>
68-#include <stdlib.h>
69-#include <unistd.h>
70-
71-#define MAXHISTLOOPS 4 /* max recursions through fc */
72-#define DEFEDITOR "ed" /* default editor *should* be $EDITOR */
73-
74-History *hist; /* history cookie */
75-EditLine *el; /* editline cookie */
76-int displayhist;
77-static int savehist;
78-static FILE *el_in, *el_out;
79-static int in_command_completion;
80-
81-static char *fc_replace(const char *, char *, char *);
82-static int not_fcnumber(const char *);
83-static int str_to_event(const char *, int);
84-static int comparator(const void *, const void *, void *);
85-static char **sh_matches(const char *, int, int);
86-static const char *append_char_function(const char *);
87-static unsigned char sh_complete(EditLine *, int);
88-
89-static const char *
90-get_histfile(void)
91-{
92- const char *histfile;
93-
94- /* don't try to save if the history size is 0 */
95- if (hist == NULL || !strcmp(histsizeval(), "0"))
96- return (NULL);
97- histfile = expandstr("${HISTFILE-${HOME-}/.sh_history}");
98-
99- if (histfile[0] == '\0')
100- return (NULL);
101- return (histfile);
102-}
103-
104-void
105-histsave(void)
106-{
107- HistEvent he;
108- char *histtmpname = NULL;
109- const char *histfile;
110- int fd;
111- FILE *f;
112-
113- if (!savehist || (histfile = get_histfile()) == NULL)
114- return;
115- INTOFF;
116- asprintf(&histtmpname, "%s.XXXXXXXXXX", histfile);
117- if (histtmpname == NULL) {
118- INTON;
119- return;
120- }
121- fd = mkstemp(histtmpname);
122- if (fd == -1 || (f = fdopen(fd, "w")) == NULL) {
123- free(histtmpname);
124- INTON;
125- return;
126- }
127- if (history(hist, &he, H_SAVE_FP, f) < 1 ||
128- rename(histtmpname, histfile) == -1)
129- unlink(histtmpname);
130- fclose(f);
131- free(histtmpname);
132- INTON;
133-
134-}
135-
136-void
137-histload(void)
138-{
139- const char *histfile;
140- HistEvent he;
141-
142- if ((histfile = get_histfile()) == NULL)
143- return;
144- errno = 0;
145- if (history(hist, &he, H_LOAD, histfile) != -1 || errno == ENOENT)
146- savehist = 1;
147-}
148-
149-/*
150- * Set history and editing status. Called whenever the status may
151- * have changed (figures out what to do).
152- */
153-void
154-histedit(void)
155-{
156-
157-#define editing (Eflag || Vflag)
158-
159- if (iflag) {
160- if (!hist) {
161- /*
162- * turn history on
163- */
164- INTOFF;
165- hist = history_init();
166- INTON;
167-
168- if (hist != NULL)
169- sethistsize(histsizeval());
170- else
171- out2fmt_flush("sh: can't initialize history\n");
172- }
173- if (editing && !el && isatty(0)) { /* && isatty(2) ??? */
174- /*
175- * turn editing on
176- */
177- char *term;
178-
179- INTOFF;
180- if (el_in == NULL)
181- el_in = fdopen(0, "r");
182- if (el_out == NULL)
183- el_out = fdopen(2, "w");
184- if (el_in == NULL || el_out == NULL)
185- goto bad;
186- term = lookupvar("TERM");
187- if (term)
188- setenv("TERM", term, 1);
189- else
190- unsetenv("TERM");
191- el = el_init(arg0, el_in, el_out, el_out);
192- if (el != NULL) {
193- if (hist)
194- el_set(el, EL_HIST, history, hist);
195- el_set(el, EL_PROMPT_ESC, getprompt, '\001');
196- el_set(el, EL_ADDFN, "sh-complete",
197- "Filename completion",
198- sh_complete);
199- } else {
200-bad:
201- out2fmt_flush("sh: can't initialize editing\n");
202- }
203- INTON;
204- } else if (!editing && el) {
205- INTOFF;
206- el_end(el);
207- el = NULL;
208- INTON;
209- }
210- if (el) {
211- if (Vflag)
212- el_set(el, EL_EDITOR, "vi");
213- else if (Eflag) {
214- el_set(el, EL_EDITOR, "emacs");
215- }
216- el_set(el, EL_BIND, "^I", "sh-complete", NULL);
217- el_source(el, NULL);
218- }
219- } else {
220- INTOFF;
221- if (el) { /* no editing if not interactive */
222- el_end(el);
223- el = NULL;
224- }
225- if (hist) {
226- history_end(hist);
227- hist = NULL;
228- }
229- INTON;
230- }
231-}
232-
233-
234-void
235-sethistsize(const char *hs)
236-{
237- int histsize;
238- HistEvent he;
239-
240- if (hist != NULL) {
241- if (hs == NULL || !is_number(hs))
242- histsize = 128;
243- else
244- histsize = atoi(hs);
245- history(hist, &he, H_SETSIZE, histsize);
246- history(hist, &he, H_SETUNIQUE, 1);
247- }
248-}
249-
250-void
251-setterm(const char *term)
252-{
253- if (rootshell && el != NULL && term != NULL)
254- el_set(el, EL_TERMINAL, term);
255-}
256-
257-int
258-histcmd(int argc, char **argv __unused)
259-{
260- const char *editor = NULL;
261- HistEvent he;
262- int lflg = 0, nflg = 0, rflg = 0, sflg = 0;
263- int i, retval;
264- const char *firststr, *laststr;
265- int first, last, direction;
266- char *pat = NULL, *repl = NULL;
267- static int active = 0;
268- struct jmploc jmploc;
269- struct jmploc *savehandler;
270- char editfilestr[PATH_MAX];
271- char *volatile editfile;
272- FILE *efp = NULL;
273- int oldhistnum;
274-
275- if (hist == NULL)
276- error("history not active");
277-
278- if (argc == 1)
279- error("missing history argument");
280-
281- while (not_fcnumber(*argptr))
282- do {
283- switch (nextopt("e:lnrs")) {
284- case 'e':
285- editor = shoptarg;
286- break;
287- case 'l':
288- lflg = 1;
289- break;
290- case 'n':
291- nflg = 1;
292- break;
293- case 'r':
294- rflg = 1;
295- break;
296- case 's':
297- sflg = 1;
298- break;
299- case '\0':
300- goto operands;
301- }
302- } while (nextopt_optptr != NULL);
303-operands:
304- savehandler = handler;
305- /*
306- * If executing...
307- */
308- if (lflg == 0 || editor || sflg) {
309- lflg = 0; /* ignore */
310- editfile = NULL;
311- /*
312- * Catch interrupts to reset active counter and
313- * cleanup temp files.
314- */
315- if (setjmp(jmploc.loc)) {
316- active = 0;
317- if (editfile)
318- unlink(editfile);
319- handler = savehandler;
320- longjmp(handler->loc, 1);
321- }
322- handler = &jmploc;
323- if (++active > MAXHISTLOOPS) {
324- active = 0;
325- displayhist = 0;
326- error("called recursively too many times");
327- }
328- /*
329- * Set editor.
330- */
331- if (sflg == 0) {
332- if (editor == NULL &&
333- (editor = bltinlookup("FCEDIT", 1)) == NULL &&
334- (editor = bltinlookup("EDITOR", 1)) == NULL)
335- editor = DEFEDITOR;
336- if (editor[0] == '-' && editor[1] == '\0') {
337- sflg = 1; /* no edit */
338- editor = NULL;
339- }
340- }
341- }
342-
343- /*
344- * If executing, parse [old=new] now
345- */
346- if (lflg == 0 && *argptr != NULL &&
347- ((repl = strchr(*argptr, '=')) != NULL)) {
348- pat = *argptr;
349- *repl++ = '\0';
350- argptr++;
351- }
352- /*
353- * determine [first] and [last]
354- */
355- if (*argptr == NULL) {
356- firststr = lflg ? "-16" : "-1";
357- laststr = "-1";
358- } else if (argptr[1] == NULL) {
359- firststr = argptr[0];
360- laststr = lflg ? "-1" : argptr[0];
361- } else if (argptr[2] == NULL) {
362- firststr = argptr[0];
363- laststr = argptr[1];
364- } else
365- error("too many arguments");
366- /*
367- * Turn into event numbers.
368- */
369- first = str_to_event(firststr, 0);
370- last = str_to_event(laststr, 1);
371-
372- if (rflg) {
373- i = last;
374- last = first;
375- first = i;
376- }
377- /*
378- * XXX - this should not depend on the event numbers
379- * always increasing. Add sequence numbers or offset
380- * to the history element in next (diskbased) release.
381- */
382- direction = first < last ? H_PREV : H_NEXT;
383-
384- /*
385- * If editing, grab a temp file.
386- */
387- if (editor) {
388- int fd;
389- INTOFF; /* easier */
390- sprintf(editfilestr, "%s/_shXXXXXX", _PATH_TMP);
391- if ((fd = mkstemp(editfilestr)) < 0)
392- error("can't create temporary file %s", editfile);
393- editfile = editfilestr;
394- if ((efp = fdopen(fd, "w")) == NULL) {
395- close(fd);
396- error("Out of space");
397- }
398- }
399-
400- /*
401- * Loop through selected history events. If listing or executing,
402- * do it now. Otherwise, put into temp file and call the editor
403- * after.
404- *
405- * The history interface needs rethinking, as the following
406- * convolutions will demonstrate.
407- */
408- history(hist, &he, H_FIRST);
409- retval = history(hist, &he, H_NEXT_EVENT, first);
410- for (;retval != -1; retval = history(hist, &he, direction)) {
411- if (lflg) {
412- if (!nflg)
413- out1fmt("%5d ", he.num);
414- out1str(he.str);
415- } else {
416- const char *s = pat ?
417- fc_replace(he.str, pat, repl) : he.str;
418-
419- if (sflg) {
420- if (displayhist) {
421- out2str(s);
422- flushout(out2);
423- }
424- evalstring(s, 0);
425- if (displayhist && hist) {
426- /*
427- * XXX what about recursive and
428- * relative histnums.
429- */
430- oldhistnum = he.num;
431- history(hist, &he, H_ENTER, s);
432- /*
433- * XXX H_ENTER moves the internal
434- * cursor, set it back to the current
435- * entry.
436- */
437- history(hist, &he,
438- H_NEXT_EVENT, oldhistnum);
439- }
440- } else
441- fputs(s, efp);
442- }
443- /*
444- * At end? (if we were to lose last, we'd sure be
445- * messed up).
446- */
447- if (he.num == last)
448- break;
449- }
450- if (editor) {
451- char *editcmd;
452-
453- fclose(efp);
454- INTON;
455- editcmd = stalloc(strlen(editor) + strlen(editfile) + 2);
456- sprintf(editcmd, "%s %s", editor, editfile);
457- evalstring(editcmd, 0); /* XXX - should use no JC command */
458- readcmdfile(editfile, 0 /* verify */); /* XXX - should read back - quick tst */
459- unlink(editfile);
460- }
461-
462- if (lflg == 0 && active > 0)
463- --active;
464- if (displayhist)
465- displayhist = 0;
466- handler = savehandler;
467- return 0;
468-}
469-
470-static char *
471-fc_replace(const char *s, char *p, char *r)
472-{
473- char *dest;
474- int plen = strlen(p);
475-
476- STARTSTACKSTR(dest);
477- while (*s) {
478- if (*s == *p && strncmp(s, p, plen) == 0) {
479- STPUTS(r, dest);
480- s += plen;
481- *p = '\0'; /* so no more matches */
482- } else
483- STPUTC(*s++, dest);
484- }
485- STPUTC('\0', dest);
486- dest = grabstackstr(dest);
487-
488- return (dest);
489-}
490-
491-static int
492-not_fcnumber(const char *s)
493-{
494- if (s == NULL)
495- return (0);
496- if (*s == '-')
497- s++;
498- return (!is_number(s));
499-}
500-
501-static int
502-str_to_event(const char *str, int last)
503-{
504- HistEvent he;
505- const char *s = str;
506- int relative = 0;
507- int i, retval;
508-
509- retval = history(hist, &he, H_FIRST);
510- switch (*s) {
511- case '-':
512- relative = 1;
513- /*FALLTHROUGH*/
514- case '+':
515- s++;
516- }
517- if (is_number(s)) {
518- i = atoi(s);
519- if (relative) {
520- while (retval != -1 && i--) {
521- retval = history(hist, &he, H_NEXT);
522- }
523- if (retval == -1)
524- retval = history(hist, &he, H_LAST);
525- } else {
526- retval = history(hist, &he, H_NEXT_EVENT, i);
527- if (retval == -1) {
528- /*
529- * the notion of first and last is
530- * backwards to that of the history package
531- */
532- retval = history(hist, &he, last ? H_FIRST : H_LAST);
533- }
534- }
535- if (retval == -1)
536- error("history number %s not found (internal error)",
537- str);
538- } else {
539- /*
540- * pattern
541- */
542- retval = history(hist, &he, H_PREV_STR, str);
543- if (retval == -1)
544- error("history pattern not found: %s", str);
545- }
546- return (he.num);
547-}
548-
549-int
550-bindcmd(int argc, char **argv)
551-{
552- int ret;
553- FILE *old;
554- FILE *out;
555-
556- if (el == NULL)
557- error("line editing is disabled");
558-
559- INTOFF;
560-
561- out = out1fp();
562- if (out == NULL)
563- error("Out of space");
564-
565- el_get(el, EL_GETFP, 1, &old);
566- el_set(el, EL_SETFP, 1, out);
567-
568- ret = el_parse(el, argc, __DECONST(const char **, argv));
569-
570- el_set(el, EL_SETFP, 1, old);
571-
572- fclose(out);
573-
574- if (argc > 1 && argv[1][0] == '-' &&
575- memchr("ve", argv[1][1], 2) != NULL) {
576- Vflag = argv[1][1] == 'v';
577- Eflag = !Vflag;
578- histedit();
579- }
580-
581- INTON;
582-
583- return ret;
584-}
585-
586-/*
587- * Comparator function for qsort(). The use of curpos here is to skip
588- * characters that we already know to compare equal (common prefix).
589- */
590-static int
591-comparator(const void *a, const void *b, void *thunk)
592-{
593- size_t curpos = (intptr_t)thunk;
594-
595- return (strcmp(*(char *const *)a + curpos,
596- *(char *const *)b + curpos));
597-}
598-
599-static char
600-**add_match(char **matches, size_t i, size_t *size, char *match_copy)
601-{
602- if (match_copy == NULL)
603- return (NULL);
604- matches[i] = match_copy;
605- if (i >= *size - 1) {
606- *size *= 2;
607- matches = reallocarray(matches, *size, sizeof(matches[0]));
608- }
609-
610- return (matches);
611-}
612-
613-/*
614- * This function is passed to libedit's fn_complete2(). The library will use
615- * it instead of its standard function that finds matching files in current
616- * directory. If we're at the start of the line, we want to look for
617- * available commands from all paths in $PATH.
618- */
619-static char
620-**sh_matches(const char *text, int start, int end)
621-{
622- char *free_path = NULL, *path;
623- const char *dirname;
624- char **matches = NULL, **rmatches;
625- size_t i = 0, size = 16, uniq;
626- size_t curpos = end - start, lcstring = -1;
627- struct cmdentry e;
628-
629- in_command_completion = 0;
630- if (start > 0 || memchr("/.~", text[0], 3) != NULL)
631- return (NULL);
632- in_command_completion = 1;
633- if ((free_path = path = strdup(pathval())) == NULL)
634- goto out;
635- if ((matches = malloc(size * sizeof(matches[0]))) == NULL)
636- goto out;
637- while ((dirname = strsep(&path, ":")) != NULL) {
638- struct dirent *entry;
639- DIR *dir;
640- int dfd;
641-
642- dir = opendir(dirname[0] == '\0' ? "." : dirname);
643- if (dir == NULL)
644- continue;
645- if ((dfd = dirfd(dir)) == -1) {
646- closedir(dir);
647- continue;
648- }
649- while ((entry = readdir(dir)) != NULL) {
650- struct stat statb;
651-
652- if (strncmp(entry->d_name, text, curpos) != 0)
653- continue;
654- if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) {
655- if (fstatat(dfd, entry->d_name, &statb, 0) == -1)
656- continue;
657- if (!S_ISREG(statb.st_mode))
658- continue;
659- } else if (entry->d_type != DT_REG)
660- continue;
661- rmatches = add_match(matches, ++i, &size,
662- strdup(entry->d_name));
663- if (rmatches == NULL) {
664- closedir(dir);
665- goto out;
666- }
667- matches = rmatches;
668- }
669- closedir(dir);
670- }
671- for (const unsigned char *bp = builtincmd; *bp != 0; bp += 2 + bp[0]) {
672- if (curpos > bp[0] || memcmp(bp + 2, text, curpos) != 0)
673- continue;
674- rmatches = add_match(matches, ++i, &size, strndup(bp + 2, bp[0]));
675- if (rmatches == NULL)
676- goto out;
677- matches = rmatches;
678- }
679- for (const struct alias *ap = NULL; (ap = iteralias(ap)) != NULL;) {
680- if (strncmp(ap->name, text, curpos) != 0)
681- continue;
682- rmatches = add_match(matches, ++i, &size, strdup(ap->name));
683- if (rmatches == NULL)
684- goto out;
685- matches = rmatches;
686- }
687- for (const void *a = NULL; (a = itercmd(a, &e)) != NULL;) {
688- if (e.cmdtype != CMDFUNCTION)
689- continue;
690- if (strncmp(e.cmdname, text, curpos) != 0)
691- continue;
692- rmatches = add_match(matches, ++i, &size, strdup(e.cmdname));
693- if (rmatches == NULL)
694- goto out;
695- matches = rmatches;
696- }
697-out:
698- free(free_path);
699- if (i == 0) {
700- free(matches);
701- return (NULL);
702- }
703- uniq = 1;
704- if (i > 1) {
705- qsort_s(matches + 1, i, sizeof(matches[0]), comparator,
706- (void *)(intptr_t)curpos);
707- for (size_t k = 2; k <= i; k++) {
708- const char *l = matches[uniq] + curpos;
709- const char *r = matches[k] + curpos;
710- size_t common = 0;
711-
712- while (*l != '\0' && *r != '\0' && *l == *r)
713- (void)l++, r++, common++;
714- if (common < lcstring)
715- lcstring = common;
716- if (*l == *r)
717- free(matches[k]);
718- else
719- matches[++uniq] = matches[k];
720- }
721- }
722- matches[uniq + 1] = NULL;
723- /*
724- * matches[0] is special: it's not a real matching file name but
725- * a common prefix for all matching names. It can't be null, unlike
726- * any other element of the array. When strings matches[0] and
727- * matches[1] compare equal and matches[2] is null that means to
728- * libedit that there is only a single match. It will then replace
729- * user input with possibly escaped string in matches[0] which is the
730- * reason to copy the full name of the only match.
731- */
732- if (uniq == 1)
733- matches[0] = strdup(matches[1]);
734- else if (lcstring != (size_t)-1)
735- matches[0] = strndup(matches[1], curpos + lcstring);
736- else
737- matches[0] = strdup(text);
738- if (matches[0] == NULL) {
739- for (size_t k = 1; k <= uniq; k++)
740- free(matches[k]);
741- free(matches);
742- return (NULL);
743- }
744- return (matches);
745-}
746-
747-/*
748- * If we don't specify this function as app_func in the call to fn_complete2,
749- * libedit will use the default one, which adds a " " to plain files and
750- * a "/" to directories regardless of whether it's a command name or a plain
751- * path (relative or absolute). We never want to add "/" to commands.
752- *
753- * For example, after I did "mkdir rmdir", "rmdi" would be autocompleted to
754- * "rmdir/" instead of "rmdir ".
755- */
756-static const char *
757-append_char_function(const char *name)
758-{
759- struct stat stbuf;
760- char *expname = name[0] == '~' ? fn_tilde_expand(name) : NULL;
761- const char *rs;
762-
763- if (!in_command_completion &&
764- stat(expname ? expname : name, &stbuf) == 0 &&
765- S_ISDIR(stbuf.st_mode))
766- rs = "/";
767- else
768- rs = " ";
769- free(expname);
770- return (rs);
771-}
772-
773-/*
774- * This is passed to el_set(el, EL_ADDFN, ...) so that it's possible to
775- * bind a key (tab by default) to execute the function.
776- */
777-unsigned char
778-sh_complete(EditLine *sel, int ch __unused)
779-{
780- return (unsigned char)fn_complete2(sel, NULL, sh_matches,
781- L" \t\n\"\\'`@$><=;|&{(", NULL, append_char_function,
782- (size_t)100, NULL, &((int) {0}), NULL, NULL, FN_QUOTE_MATCH);
783-}
784-
785-#else
786-
787-int
788-histcmd(int argc __unused, char **argv __unused)
789-{
790-
791- error("not compiled with history support");
792- /*NOTREACHED*/
793- return (0);
794-}
795-
796-int
797-bindcmd(int argc __unused, char **argv __unused)
798-{
799-
800- error("not compiled with line editing support");
801- return (0);
802-}
803-#endif
+38,
-29
1@@ -54,7 +54,7 @@
2 #include "alias.h"
3 #include "parser.h"
4 #ifndef NO_HISTORY
5-#include "myhistedit.h"
6+#include "lineedit.h"
7 #endif
8 #include "trap.h"
9
10@@ -132,30 +132,34 @@ preadfd(void)
11
12 retry:
13 #ifndef NO_HISTORY
14- if (parsefile->fd == 0 && el) {
15- const char *line;
16-
17- el_resize(el);
18- line = el_gets(el, &nr);
19- if (nr > 0 && parsefile->bufsize < (size_t)nr + 1) {
20- size_t bufsize;
21-
22- INTOFF;
23- if (parsefile->buf != basebuf) {
24- ckfree(parsefile->buf);
25- parsefile->buf = NULL;
26- parsefile->bufsize = 0;
27+ if (parsefile->fd == 0 && sh_history_enabled) {
28+ char *line;
29+
30+ line = redline(getprompt(NULL));
31+ if (line != NULL) {
32+ nr = strlen(line) + 1;
33+ if (parsefile->bufsize < (size_t)nr + 1) {
34+ size_t bufsize;
35+
36+ INTOFF;
37+ if (parsefile->buf != basebuf) {
38+ ckfree(parsefile->buf);
39+ parsefile->buf = NULL;
40+ parsefile->bufsize = 0;
41+ }
42+ bufsize = (size_t)nr + BUFSIZ + 1;
43+ bufsize -= bufsize % BUFSIZ;
44+ parsefile->buf = ckmalloc(bufsize);
45+ parsefile->bufsize = bufsize;
46+ INTON;
47 }
48- bufsize = (size_t)nr + BUFSIZ + 1;
49- bufsize -= bufsize % BUFSIZ;
50- parsefile->buf = ckmalloc(bufsize);
51- parsefile->bufsize = bufsize;
52- INTON;
53+ memcpy(parsefile->buf, line, nr - 1);
54+ parsefile->buf[nr - 1] = '\n';
55+ parsefile->buf[nr] = '\0';
56+ free(line);
57+ } else {
58+ nr = 0;
59 }
60- if (nr > 0 && line != NULL)
61- memcpy(parsefile->buf, line, nr);
62- else
63- nr = nr ? -1 : 0;
64 } else
65 #endif
66 nr = read(parsefile->fd, parsefile->buf, parsefile->bufsize - 1);
67@@ -249,13 +253,18 @@ preadbuffer(void)
68 *q = '\0';
69
70 #ifndef NO_HISTORY
71- if (parsefile->fd == 0 && hist &&
72+ if (parsefile->fd == 0 && sh_history_enabled &&
73 parsenextc[strspn(parsenextc, " \t\n")] != '\0') {
74- HistEvent he;
75- INTOFF;
76- history(hist, &he, whichprompt == 1 ? H_ENTER : H_ADD,
77- parsenextc);
78- INTON;
79+ char *histline = strdup(parsenextc);
80+ if (histline) {
81+ char *nl = strchr(histline, '\n');
82+ if (nl)
83+ *nl = '\0';
84+ INTOFF;
85+ redlineHistoryAdd(histline);
86+ INTON;
87+ free(histline);
88+ }
89 }
90 #endif
91
+844,
-0
1@@ -0,0 +1,844 @@
2+/*-
3+ * SPDX-License-Identifier: BSD-3-Clause
4+ *
5+ * Copyright (c) 1993
6+ * The Regents of the University of California. All rights reserved.
7+ *
8+ * This code is derived from software contributed to Berkeley by
9+ * Kenneth Almquist.
10+ *
11+ * Redistribution and use in source and binary forms, with or without
12+ * modification, are permitted provided that the following conditions
13+ * are met:
14+ * 1. Redistributions of source code must retain the above copyright
15+ * notice, this list of conditions and the following disclaimer.
16+ * 2. Redistributions in binary form must reproduce the above copyright
17+ * notice, this list of conditions and the following disclaimer in the
18+ * documentation and/or other materials provided with the distribution.
19+ * 3. Neither the name of the University nor the names of its contributors
20+ * may be used to promote products derived from this software
21+ * without specific prior written permission.
22+ *
23+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33+ * SUCH DAMAGE.
34+ */
35+
36+#if !FEATURE_SH_HISTEDIT
37+#ifndef NO_HISTORY
38+#define NO_HISTORY
39+#endif
40+#endif
41+
42+#include "util.h"
43+#include "shell.h"
44+#include "alias.h"
45+#include "builtins.h"
46+#include "error.h"
47+#include "eval.h"
48+#include "exec.h"
49+#include "main.h"
50+#include "memalloc.h"
51+#include "mystring.h"
52+#include "options.h"
53+#include "output.h"
54+#include "parser.h"
55+#include "var.h"
56+
57+#ifndef NO_HISTORY
58+#include "lineedit.h"
59+
60+#include <sys/param.h>
61+#include <sys/stat.h>
62+
63+#include <dirent.h>
64+#include <errno.h>
65+#include <fcntl.h>
66+#include <limits.h>
67+#include <paths.h>
68+#include <pwd.h>
69+#include <stdio.h>
70+#include <stdlib.h>
71+#include <string.h>
72+#include <unistd.h>
73+
74+#define MAXHISTLOOPS 4
75+#define DEFEDITOR "ed"
76+#define VTABSIZE 39
77+
78+extern struct var *vartab[VTABSIZE];
79+
80+int sh_history_enabled = 0;
81+int displayhist = 0;
82+static int savehist = 0;
83+
84+static char *fc_replace(const char *, char *, char *);
85+static int not_fcnumber(const char *);
86+static int str_to_event(const char *, int);
87+
88+static char *
89+escape_filename(const char *filename)
90+{
91+ size_t len;
92+ size_t i;
93+ size_t j;
94+ char *escaped;
95+ const char *special;
96+
97+ len = 0;
98+ special = " \t\n\"'\\$&|;<>()*?[]!{}";
99+ for (i = 0; filename[i] != '\0'; i++) {
100+ if (strchr(special, filename[i]) != NULL)
101+ len += 2;
102+ else
103+ len += 1;
104+ }
105+
106+ escaped = malloc(len + 1);
107+ j = 0;
108+ for (i = 0; filename[i] != '\0'; i++) {
109+ if (strchr(special, filename[i]) != NULL) {
110+ escaped[j++] = '\\';
111+ escaped[j++] = filename[i];
112+ } else {
113+ escaped[j++] = filename[i];
114+ }
115+ }
116+ escaped[j] = '\0';
117+ return escaped;
118+}
119+
120+static char *
121+unescape_filename(const char *filename)
122+{
123+ size_t len;
124+ char *unescaped;
125+ size_t i;
126+ size_t j;
127+
128+ len = strlen(filename);
129+ unescaped = malloc(len + 1);
130+ i = 0;
131+ j = 0;
132+ while (i < len) {
133+ if (filename[i] == '\\' && i + 1 < len) {
134+ unescaped[j++] = filename[i+1];
135+ i += 2;
136+ } else {
137+ unescaped[j++] = filename[i];
138+ i++;
139+ }
140+ }
141+ unescaped[j] = '\0';
142+ return unescaped;
143+}
144+
145+static void
146+complete_tildes(const char *word, struct redlineCompletions *lc)
147+{
148+ struct passwd *pw;
149+ char completed[512];
150+ const char *user_prefix;
151+ size_t prefix_len;
152+
153+ user_prefix = word + 1;
154+ prefix_len = strlen(user_prefix);
155+
156+ setpwent();
157+ while ((pw = getpwent()) != NULL) {
158+ if (strncmp(pw->pw_name, user_prefix, prefix_len) == 0) {
159+ snprintf(completed, sizeof(completed), "~%s/", pw->pw_name);
160+ redlineAddCompletion(lc, completed);
161+ }
162+ }
163+ endpwent();
164+}
165+
166+static void
167+complete_variables(const char *word, struct redlineCompletions *lc)
168+{
169+ struct var **vpp;
170+ struct var *vp;
171+ char name[256];
172+ char completed[512];
173+ const char *var_prefix;
174+ size_t prefix_len;
175+ char *eq;
176+ size_t name_len;
177+
178+ var_prefix = word + 1;
179+ prefix_len = strlen(var_prefix);
180+
181+ for (vpp = vartab; vpp < vartab + VTABSIZE; vpp++) {
182+ for (vp = *vpp; vp; vp = vp->next) {
183+ if (!(vp->flags & VUNSET)) {
184+ eq = strchr(vp->text, '=');
185+ if (eq) {
186+ name_len = eq - vp->text;
187+ if (name_len < sizeof(name)) {
188+ memcpy(name, vp->text, name_len);
189+ name[name_len] = '\0';
190+ if (strncmp(name, var_prefix, prefix_len) == 0) {
191+ snprintf(completed, sizeof(completed), "$%s ", name);
192+ redlineAddCompletion(lc, completed);
193+ }
194+ }
195+ }
196+ }
197+ }
198+ }
199+}
200+
201+static const char *
202+get_histfile(void)
203+{
204+ const char *histfile;
205+
206+ if (!strcmp(histsizeval(), "0"))
207+ return (NULL);
208+ histfile = expandstr("${HISTFILE-${HOME-}/.sh_history}");
209+
210+ if (histfile[0] == '\0')
211+ return (NULL);
212+ return (histfile);
213+}
214+
215+void
216+histsave(void)
217+{
218+ const char *histfile;
219+
220+ if (!savehist || (histfile = get_histfile()) == NULL)
221+ return;
222+ INTOFF;
223+ redlineHistorySave(histfile);
224+ INTON;
225+}
226+
227+void
228+histload(void)
229+{
230+ const char *histfile;
231+
232+ if ((histfile = get_histfile()) == NULL)
233+ return;
234+ errno = 0;
235+ if (redlineHistoryLoad(histfile) != -1 || errno == ENOENT)
236+ savehist = 1;
237+}
238+
239+static void
240+find_completions_recurse(const char *fs_dir, const char *user_prefix,
241+ char **comps, int comp_idx, int comp_count, int is_cmd, struct redlineCompletions *lc)
242+{
243+ DIR *dir;
244+ struct dirent *de;
245+ struct stat st;
246+ char next_fs[4096];
247+ char next_user[4096];
248+ size_t len;
249+
250+ if (comp_idx == comp_count) {
251+ /* reached the end of components, check if the path exists */
252+ if (stat(fs_dir, &st) == 0) {
253+ if (is_cmd && !S_ISDIR(st.st_mode) && access(fs_dir, X_OK) != 0) {
254+ return;
255+ }
256+ snprintf(next_user, sizeof(next_user), "%s", user_prefix);
257+ if (S_ISDIR(st.st_mode)) {
258+ /* if it is a directory and doesnt end with slash, add slash */
259+ len = strlen(next_user);
260+ if (len > 0 && next_user[len - 1] != '/') {
261+ strlcat(next_user, "/", sizeof(next_user));
262+ }
263+ } else {
264+ /* file, add space */
265+ strlcat(next_user, " ", sizeof(next_user));
266+ }
267+ redlineAddCompletion(lc, next_user);
268+ }
269+ return;
270+ }
271+
272+ if (strcmp(comps[comp_idx], ".") == 0 || strcmp(comps[comp_idx], "..") == 0) {
273+ /* construct filesystem path */
274+ if (strcmp(fs_dir, "/") == 0) {
275+ snprintf(next_fs, sizeof(next_fs), "/%s", comps[comp_idx]);
276+ } else if (strcmp(fs_dir, ".") == 0) {
277+ snprintf(next_fs, sizeof(next_fs), "./%s", comps[comp_idx]);
278+ } else {
279+ snprintf(next_fs, sizeof(next_fs), "%s/%s", fs_dir, comps[comp_idx]);
280+ }
281+
282+ /* construct user visible path */
283+ if (strcmp(user_prefix, "/") == 0) {
284+ snprintf(next_user, sizeof(next_user), "/%s", comps[comp_idx]);
285+ } else if (strcmp(user_prefix, "~/") == 0) {
286+ snprintf(next_user, sizeof(next_user), "~/%s", comps[comp_idx]);
287+ } else if (user_prefix[0] == '\0') {
288+ snprintf(next_user, sizeof(next_user), "%s", comps[comp_idx]);
289+ } else {
290+ len = strlen(user_prefix);
291+ if (user_prefix[len - 1] == '/') {
292+ snprintf(next_user, sizeof(next_user), "%s%s", user_prefix, comps[comp_idx]);
293+ } else {
294+ snprintf(next_user, sizeof(next_user), "%s/%s", user_prefix, comps[comp_idx]);
295+ }
296+ }
297+
298+ find_completions_recurse(next_fs, next_user, comps, comp_idx + 1, comp_count, is_cmd, lc);
299+ return;
300+ }
301+
302+ dir = opendir(fs_dir);
303+ if (!dir)
304+ return;
305+
306+ while ((de = readdir(dir)) != NULL) {
307+ if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
308+ continue;
309+
310+ if (strncmp(de->d_name, comps[comp_idx], strlen(comps[comp_idx])) == 0) {
311+ char *escaped_name = escape_filename(de->d_name);
312+ /* construct filesystem path */
313+ if (strcmp(fs_dir, "/") == 0) {
314+ snprintf(next_fs, sizeof(next_fs), "/%s", de->d_name);
315+ } else if (strcmp(fs_dir, ".") == 0) {
316+ snprintf(next_fs, sizeof(next_fs), "./%s", de->d_name);
317+ } else {
318+ snprintf(next_fs, sizeof(next_fs), "%s/%s", fs_dir, de->d_name);
319+ }
320+
321+ /* middle components must be directories */
322+ if (comp_idx < comp_count - 1) {
323+ if (stat(next_fs, &st) != 0 || !S_ISDIR(st.st_mode)) {
324+ free(escaped_name);
325+ continue;
326+ }
327+ }
328+
329+ /* construct user-visible path */
330+ if (strcmp(user_prefix, "/") == 0) {
331+ snprintf(next_user, sizeof(next_user), "/%s", escaped_name);
332+ } else if (strcmp(user_prefix, "~/") == 0) {
333+ snprintf(next_user, sizeof(next_user), "~/%s", escaped_name);
334+ } else if (user_prefix[0] == '\0') {
335+ snprintf(next_user, sizeof(next_user), "%s", escaped_name);
336+ } else {
337+ len = strlen(user_prefix);
338+ if (user_prefix[len - 1] == '/') {
339+ snprintf(next_user, sizeof(next_user), "%s%s", user_prefix, escaped_name);
340+ } else {
341+ snprintf(next_user, sizeof(next_user), "%s/%s", user_prefix, escaped_name);
342+ }
343+ }
344+
345+ find_completions_recurse(next_fs, next_user, comps, comp_idx + 1, comp_count, is_cmd, lc);
346+ free(escaped_name);
347+ }
348+ }
349+ closedir(dir);
350+}
351+
352+/* complete matching files in the filesystem */
353+static void
354+complete_files(const char *word, int is_cmd, struct redlineCompletions *lc)
355+{
356+ char *path_to_split;
357+ const char *fs_dir;
358+ const char *user_prefix;
359+ const char *home;
360+ char *path_copy;
361+ char *p;
362+ char *comps[128];
363+ int comp_count;
364+ char *unescaped_word;
365+
366+ path_to_split = NULL;
367+ fs_dir = ".";
368+ user_prefix = "";
369+ home = getenv("HOME");
370+ comp_count = 0;
371+ unescaped_word = unescape_filename(word);
372+
373+ if (unescaped_word[0] == '~') {
374+ if (unescaped_word[1] == '/' || unescaped_word[1] == '\0') {
375+ fs_dir = home ? home : "/";
376+ user_prefix = "~/";
377+ path_to_split = (unescaped_word[1] == '\0') ? "" : (char *)(unescaped_word + 2);
378+ } else {
379+ /* ~username is not supported for abbreviation, fallback to home */
380+ fs_dir = home ? home : "/";
381+ user_prefix = "~/";
382+ path_to_split = (char *)(unescaped_word + 1);
383+ }
384+ } else if (unescaped_word[0] == '/') {
385+ fs_dir = "/";
386+ user_prefix = "/";
387+ path_to_split = (char *)(unescaped_word + 1);
388+ } else {
389+ fs_dir = ".";
390+ user_prefix = "";
391+ path_to_split = (char *)unescaped_word;
392+ }
393+
394+ path_copy = estrdup(path_to_split);
395+ p = path_copy;
396+ if (*p != '\0') {
397+ comps[comp_count++] = p;
398+ while (*p != '\0') {
399+ if (*p == '/') {
400+ *p = '\0';
401+ p++;
402+ while (*p == '/')
403+ p++;
404+ if (*p == '\0') {
405+ comps[comp_count++] = p;
406+ break;
407+ }
408+ comps[comp_count++] = p;
409+ } else {
410+ p++;
411+ }
412+ }
413+ } else {
414+ /* empty path_to_split (e.g. exactly ~ or exactly / or empty word) */
415+ comps[comp_count++] = p;
416+ }
417+
418+ find_completions_recurse(fs_dir, user_prefix, comps, 0, comp_count, is_cmd, lc);
419+ free(path_copy);
420+ free(unescaped_word);
421+}
422+
423+/* complete matching executable commands and builtins */
424+static void
425+complete_commands(const char *word, struct redlineCompletions *lc)
426+{
427+ char *free_path = NULL, *path;
428+ const char *dirname;
429+ struct cmdentry e;
430+ const struct alias *ap = NULL;
431+ const unsigned char *bp = builtincmd;
432+ const void *a = NULL;
433+ DIR *dir;
434+ struct dirent *entry;
435+ int dfd;
436+ struct stat statb;
437+ char completed[512];
438+
439+ while ((ap = iteralias(ap)) != NULL) {
440+ if (strncmp(ap->name, word, strlen(word)) == 0) {
441+ snprintf(completed, sizeof(completed), "%s ", ap->name);
442+ redlineAddCompletion(lc, completed);
443+ }
444+ }
445+
446+ while (bp && *bp != 0) {
447+ if (strncmp((const char *)(bp + 2), word, strlen(word)) == 0) {
448+ snprintf(completed, sizeof(completed), "%.*s ", (int)bp[0], bp + 2);
449+ redlineAddCompletion(lc, completed);
450+ }
451+ bp += 2 + bp[0];
452+ }
453+
454+ while ((a = itercmd(a, &e)) != NULL) {
455+ if (e.cmdtype == CMDFUNCTION && strncmp(e.cmdname, word, strlen(word)) == 0) {
456+ snprintf(completed, sizeof(completed), "%s ", e.cmdname);
457+ redlineAddCompletion(lc, completed);
458+ }
459+ }
460+
461+ path = pathval();
462+ if (path) {
463+ free_path = path = estrdup(path);
464+ while ((dirname = strsep(&path, ":")) != NULL) {
465+ dir = opendir(dirname[0] == '\0' ? "." : dirname);
466+ if (dir == NULL)
467+ continue;
468+ dfd = dirfd(dir);
469+ if (dfd == -1) {
470+ closedir(dir);
471+ continue;
472+ }
473+ while ((entry = readdir(dir)) != NULL) {
474+ if (strncmp(entry->d_name, word, strlen(word)) != 0)
475+ continue;
476+ if (entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK) {
477+ if (fstatat(dfd, entry->d_name, &statb, 0) == -1)
478+ continue;
479+ if (!S_ISREG(statb.st_mode))
480+ continue;
481+ } else if (entry->d_type != DT_REG) {
482+ continue;
483+ }
484+ snprintf(completed, sizeof(completed), "%s ", entry->d_name);
485+ redlineAddCompletion(lc, completed);
486+ }
487+ closedir(dir);
488+ }
489+ free(free_path);
490+ }
491+}
492+
493+/* main completion callback called by redline library */
494+static void
495+sh_complete_callback(const char *buf, struct redlineCompletions *lc)
496+{
497+ const char *word;
498+ int start;
499+ int is_cmd;
500+ int p;
501+ struct redlineCompletions temp_lc;
502+ char line_prefix[4096];
503+ char full_completion[4096];
504+ size_t i;
505+
506+ start = strlen(buf);
507+ while (start > 0) {
508+ char c = buf[start - 1];
509+ if (c == ' ' || c == '\t' || c == '\n' || c == '"' || c == '\'' || c == '`' ||
510+ c == '@' || c == '$' || c == '>' || c == '<' || c == '=' || c == ';' ||
511+ c == '|' || c == '&' || c == '{' || c == '(') {
512+ if (start > 1 && buf[start - 2] == '\\') {
513+ start -= 2;
514+ continue;
515+ }
516+ break;
517+ } else if (c == '\\') {
518+ break;
519+ }
520+ start--;
521+ }
522+ word = buf + start;
523+
524+ is_cmd = 0;
525+ if (start == 0) {
526+ is_cmd = 1;
527+ } else {
528+ p = start;
529+ while (p > 0 && (buf[p - 1] == ' ' || buf[p - 1] == '\t'))
530+ p--;
531+ if (p == 0 || strchr(";&|({`\n", buf[p - 1]) != NULL)
532+ is_cmd = 1;
533+ }
534+
535+ if (start >= (int)sizeof(line_prefix))
536+ return;
537+ snprintf(line_prefix, sizeof(line_prefix), "%.*s", start, buf);
538+
539+ temp_lc.len = 0;
540+ temp_lc.cvec = NULL;
541+
542+ if (word[0] == '$') {
543+ complete_variables(word, &temp_lc);
544+ } else if (word[0] == '~' && strchr(word, '/') == NULL) {
545+ complete_tildes(word, &temp_lc);
546+ } else if (is_cmd && strchr(word, '/') == NULL && word[0] != '~' && word[0] != '.') {
547+ complete_commands(word, &temp_lc);
548+ } else {
549+ complete_files(word, is_cmd, &temp_lc);
550+ }
551+
552+ for (i = 0; i < temp_lc.len; i++) {
553+ snprintf(full_completion, sizeof(full_completion), "%s%s", line_prefix, temp_lc.cvec[i]);
554+ redlineAddCompletion(lc, full_completion);
555+ }
556+
557+ for (i = 0; i < temp_lc.len; i++) {
558+ free(temp_lc.cvec[i]);
559+ }
560+ free(temp_lc.cvec);
561+}
562+
563+void
564+histedit(void)
565+{
566+ sh_history_enabled = (iflag && (Eflag || Vflag));
567+ if (sh_history_enabled) {
568+ redlineSetCompletionCallback(sh_complete_callback);
569+ redlineSetMultiLine(1);
570+ }
571+}
572+
573+void
574+sethistsize(const char *hs)
575+{
576+ int histsize;
577+
578+ if (hs == NULL || !is_number(hs))
579+ histsize = 128;
580+ else
581+ histsize = atoi(hs);
582+ redlineHistorySetMaxLen(histsize);
583+}
584+
585+void
586+setterm(const char *term __unused)
587+{
588+}
589+
590+int
591+histcmd(int argc, char **argv __unused)
592+{
593+ const char *editor = NULL;
594+ int lflg = 0, nflg = 0, rflg = 0, sflg = 0;
595+ int i;
596+ const char *firststr, *laststr;
597+ int first, last;
598+ char *pat = NULL, *repl = NULL;
599+ static int active = 0;
600+ struct jmploc jmploc;
601+ struct jmploc *savehandler;
602+ char editfilestr[PATH_MAX];
603+ char *volatile editfile;
604+ FILE *efp = NULL;
605+ int dir;
606+
607+ if (redlineHistoryLen() == 0)
608+ error("history not active");
609+
610+ if (argc == 1)
611+ error("missing history argument");
612+
613+ while (not_fcnumber(*argptr))
614+ do {
615+ switch (nextopt("e:lnrs")) {
616+ case 'e':
617+ editor = shoptarg;
618+ break;
619+ case 'l':
620+ lflg = 1;
621+ break;
622+ case 'n':
623+ nflg = 1;
624+ break;
625+ case 'r':
626+ rflg = 1;
627+ break;
628+ case 's':
629+ sflg = 1;
630+ break;
631+ case '\0':
632+ goto operands;
633+ }
634+ } while (nextopt_optptr != NULL);
635+operands:
636+ savehandler = handler;
637+ if (lflg == 0 || editor || sflg) {
638+ lflg = 0;
639+ editfile = NULL;
640+ if (setjmp(jmploc.loc)) {
641+ active = 0;
642+ if (editfile)
643+ unlink(editfile);
644+ handler = savehandler;
645+ longjmp(handler->loc, 1);
646+ }
647+ handler = &jmploc;
648+ if (++active > MAXHISTLOOPS) {
649+ active = 0;
650+ displayhist = 0;
651+ error("called recursively too many times");
652+ }
653+ if (sflg == 0) {
654+ if (editor == NULL &&
655+ (editor = bltinlookup("FCEDIT", 1)) == NULL &&
656+ (editor = bltinlookup("EDITOR", 1)) == NULL)
657+ editor = DEFEDITOR;
658+ if (editor[0] == '-' && editor[1] == '\0') {
659+ sflg = 1;
660+ editor = NULL;
661+ }
662+ }
663+ }
664+
665+ if (lflg == 0 && *argptr != NULL &&
666+ ((repl = strchr(*argptr, '=')) != NULL)) {
667+ pat = *argptr;
668+ *repl++ = '\0';
669+ argptr++;
670+ }
671+
672+ if (*argptr == NULL) {
673+ firststr = lflg ? "-16" : "-1";
674+ laststr = "-1";
675+ } else if (argptr[1] == NULL) {
676+ firststr = argptr[0];
677+ laststr = lflg ? "-1" : argptr[0];
678+ } else if (argptr[2] == NULL) {
679+ firststr = argptr[0];
680+ laststr = argptr[1];
681+ } else {
682+ error("too many arguments");
683+ }
684+
685+ first = str_to_event(firststr, 0);
686+ last = str_to_event(laststr, 1);
687+
688+ if (rflg) {
689+ i = last;
690+ last = first;
691+ first = i;
692+ }
693+
694+ if (editor) {
695+ int fd;
696+ INTOFF;
697+ sprintf(editfilestr, "%s/_shXXXXXX", _PATH_TMP);
698+ if ((fd = mkstemp(editfilestr)) < 0)
699+ error("can't create temporary file %s", editfile);
700+ editfile = editfilestr;
701+ if ((efp = fdopen(fd, "w")) == NULL) {
702+ close(fd);
703+ error("Out of space");
704+ }
705+ }
706+
707+ dir = (first <= last) ? 1 : -1;
708+ for (i = first; ; i += dir) {
709+ if (i < 1 || i > redlineHistoryLen())
710+ continue;
711+ const char *hstr = redlineHistoryGet(i - 1);
712+ if (lflg) {
713+ if (!nflg)
714+ out1fmt("%5d ", i);
715+ out1fmt("%s\n", hstr);
716+ } else {
717+ const char *s = pat ? fc_replace(hstr, pat, repl) : hstr;
718+ if (sflg) {
719+ if (displayhist) {
720+ out2fmt_flush("%s\n", s);
721+ }
722+ evalstring(s, 0);
723+ if (displayhist) {
724+ redlineHistoryAdd(s);
725+ }
726+ } else {
727+ fprintf(efp, "%s\n", s);
728+ }
729+ }
730+ if (i == last)
731+ break;
732+ }
733+
734+ if (editor) {
735+ char *editcmd;
736+
737+ fclose(efp);
738+ INTON;
739+ editcmd = stalloc(strlen(editor) + strlen(editfile) + 2);
740+ sprintf(editcmd, "%s %s", editor, editfile);
741+ evalstring(editcmd, 0);
742+ readcmdfile(editfile, 0);
743+ unlink(editfile);
744+ }
745+
746+ if (lflg == 0 && active > 0)
747+ --active;
748+ if (displayhist)
749+ displayhist = 0;
750+ handler = savehandler;
751+ return 0;
752+}
753+
754+static char *
755+fc_replace(const char *s, char *p, char *r)
756+{
757+ char *dest;
758+ int plen = strlen(p);
759+
760+ STARTSTACKSTR(dest);
761+ while (*s) {
762+ if (*s == *p && strncmp(s, p, plen) == 0) {
763+ STPUTS(r, dest);
764+ s += plen;
765+ *p = '\0';
766+ } else
767+ STPUTC(*s++, dest);
768+ }
769+ STPUTC('\0', dest);
770+ dest = grabstackstr(dest);
771+
772+ return (dest);
773+}
774+
775+static int
776+not_fcnumber(const char *s)
777+{
778+ if (s == NULL)
779+ return (0);
780+ if (*s == '-')
781+ s++;
782+ return (!is_number(s));
783+}
784+
785+static int
786+str_to_event(const char *str, int last_fallback)
787+{
788+ int relative = 0;
789+ int i;
790+ const char *s = str;
791+
792+ if (s == NULL) {
793+ return last_fallback ? redlineHistoryLen() : (redlineHistoryLen() > 16 ? redlineHistoryLen() - 15 : 1);
794+ }
795+
796+ switch (*s) {
797+ case '-':
798+ relative = 1;
799+ s++;
800+ break;
801+ case '+':
802+ s++;
803+ break;
804+ }
805+
806+ if (is_number(s)) {
807+ i = atoi(s);
808+ if (relative) {
809+ return redlineHistoryLen() - i;
810+ }
811+ return i;
812+ }
813+
814+ for (i = redlineHistoryLen() - 1; i >= 0; i--) {
815+ if (strncmp(redlineHistoryGet(i), str, strlen(str)) == 0) {
816+ return i + 1;
817+ }
818+ }
819+ error("history pattern not found: %s", str);
820+ return 0;
821+}
822+
823+int
824+bindcmd(int argc __unused, char **argv __unused)
825+{
826+ error("not compiled with line editing support");
827+ return (0);
828+}
829+
830+#else
831+
832+int
833+histcmd(int argc __unused, char **argv __unused)
834+{
835+ error("not compiled with history support");
836+ return (0);
837+}
838+
839+int
840+bindcmd(int argc __unused, char **argv __unused)
841+{
842+ error("not compiled with line editing support");
843+ return (0);
844+}
845+#endif
+28,
-0
1@@ -0,0 +1,28 @@
2+/* see license file for copyright and license details */
3+#ifndef LINEEDIT_H_
4+#define LINEEDIT_H_
5+
6+#if FEATURE_SH_HISTEDIT
7+
8+#include "redline.h"
9+
10+extern int sh_history_enabled;
11+extern int displayhist;
12+
13+void histedit(void);
14+void sethistsize(const char *s);
15+void setterm(const char *t);
16+void histload(void);
17+void histsave(void);
18+
19+#else
20+
21+#define histedit() ((void)0)
22+#define sethistsize(s) ((void)0)
23+#define setterm(t) ((void)0)
24+#define histload() ((void)0)
25+#define histsave() ((void)0)
26+
27+#endif
28+
29+#endif /* LINEEDIT_H_ */
+1,
-1
1@@ -65,7 +65,7 @@
2 #include "redir.h"
3 #include "builtins.h"
4 #ifndef NO_HISTORY
5-#include "myhistedit.h"
6+#include "lineedit.h"
7 #endif
8
9 int rootpid;
+49,
-2
1@@ -51,6 +51,7 @@
2 #include <unistd.h>
3
4 #include "shell.h"
5+#include "redline.h"
6
7 #ifndef timespeccmp
8 #define timespeccmp(tvp, uvp, cmp) \
9@@ -183,17 +184,39 @@ readcmd(int argc __unused, char **argv __unused)
10 ssize_t nread;
11 int sig;
12 struct fdctx fdctx;
13+#if FEATURE_SH_HISTEDIT
14+ int eflag;
15+ char *rl_line;
16+ char *rl_line_ptr;
17+ size_t rl_idx;
18+#endif
19
20 rflag = 0;
21 prompt = NULL;
22 timeout = -1;
23- while ((i = nextopt("erp:t:")) != '\0') {
24+#if FEATURE_SH_HISTEDIT
25+ eflag = 0;
26+ rl_line = NULL;
27+ rl_line_ptr = NULL;
28+ rl_idx = 0;
29+#endif
30+
31+ while ((i = nextopt(
32+#if FEATURE_SH_HISTEDIT
33+ "erp:t:"
34+#else
35+ "rp:t:"
36+#endif
37+ )) != '\0') {
38 switch(i) {
39 case 'p':
40 prompt = shoptarg;
41 break;
42+#if FEATURE_SH_HISTEDIT
43 case 'e':
44+ eflag = 1;
45 break;
46+#endif
47 case 'r':
48 rflag = 1;
49 break;
50@@ -224,6 +247,12 @@ readcmd(int argc __unused, char **argv __unused)
51 break;
52 }
53 }
54+#if FEATURE_SH_HISTEDIT
55+ if (eflag && isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) {
56+ rl_line = redline(prompt ? prompt : "");
57+ rl_line_ptr = rl_line;
58+ } else
59+#endif
60 if (prompt && isatty(0)) {
61 out2str(prompt);
62 flushall();
63@@ -279,7 +308,22 @@ readcmd(int argc __unused, char **argv __unused)
64 fdctx_init(STDIN_FILENO, &fdctx);
65 for (;;) {
66 c = 0;
67- nread = fdgetc(&fdctx, &c);
68+#if FEATURE_SH_HISTEDIT
69+ if (rl_line_ptr) {
70+ c = rl_line_ptr[rl_idx];
71+ if (c == '\0') {
72+ c = '\n';
73+ nread = 1;
74+ rl_line_ptr = NULL;
75+ } else {
76+ rl_idx++;
77+ nread = 1;
78+ }
79+ } else
80+#endif
81+ {
82+ nread = fdgetc(&fdctx, &c);
83+ }
84 if (nread == -1) {
85 if (errno == EINTR) {
86 sig = pendingsig;
87@@ -380,6 +424,9 @@ readcmd(int argc __unused, char **argv __unused)
88 /* Set any remaining args to "" */
89 while (*++ap != NULL)
90 setvar(*ap, "", 0);
91+#if FEATURE_SH_HISTEDIT
92+ free(rl_line);
93+#endif
94 return status;
95 }
96
+3,
-0
1@@ -127,6 +127,9 @@ main(int argc __unused, char **argv __unused)
2 /* Generate the #define statements in the header file */
3 fputs("/* Syntax classes */\n", hfile);
4 for (i = 0 ; synclass[i].name ; i++) {
5+ if (strcmp(synclass[i].name, "CEOF") == 0) {
6+ fputs("#ifdef CEOF\n#undef CEOF\n#endif\n", hfile);
7+ }
8 sprintf(buf, "#define %s %d", synclass[i].name, i);
9 fputs(buf, hfile);
10 for (pos = strlen(buf) ; pos < 32 ; pos = (pos + 8) & ~07)
+0,
-37
1@@ -1,37 +0,0 @@
2-#ifndef MYHISTEDIT_H_
3-#define MYHISTEDIT_H_
4-
5-#if FEATURE_SH_HISTEDIT
6-
7-#include <histedit.h>
8-#include <filecomplete.h>
9-
10-extern History *hist;
11-extern EditLine *el;
12-
13-void histedit(void);
14-void sethistsize(const char *);
15-void setterm(const char *);
16-void histload(void);
17-void histsave(void);
18-
19-#else
20-
21-/* stubbed histedit definitions */
22-typedef void History;
23-typedef void EditLine;
24-
25-extern History *hist;
26-extern EditLine *el;
27-
28-#define histedit() ((void)0)
29-#define sethistsize(s) ((void)0)
30-#define setterm(t) ((void)0)
31-#define histload() ((void)0)
32-#define histsave() ((void)0)
33-
34-#endif
35-
36-extern int displayhist;
37-
38-#endif /* !MYHISTEDIT_H_ */
+1,
-1
1@@ -52,7 +52,7 @@
2 #include "mystring.h"
3 #include "builtins.h"
4 #ifndef NO_HISTORY
5-#include "myhistedit.h"
6+#include "lineedit.h"
7 #endif
8
9 char *arg0; /* value of $0 */
+5,
-3
1@@ -63,7 +63,7 @@ size_t xstrlcpy(char *, const char *, size_t);
2 #include "main.h"
3 #include "jobs.h"
4 #ifndef NO_HISTORY
5-#include "myhistedit.h"
6+#include "lineedit.h"
7 #endif
8
9 /*
10@@ -1944,7 +1944,7 @@ setprompt(int which)
11 return;
12
13 #ifndef NO_HISTORY
14- if (!el)
15+ if (!sh_history_enabled)
16 #endif
17 {
18 out2str(getprompt(NULL));
19@@ -2045,7 +2045,9 @@ getprompt(void *unused __unused)
20 fmt = "";
21 break;
22 case 1:
23- fmt = ps1val();
24+ fmt = expandstr(ps1val());
25+ if (fmt == NULL)
26+ fmt = ps1val();
27 break;
28 case 2:
29 fmt = ps2val();
+1,
-1
1@@ -47,7 +47,7 @@
2 #include "mystring.h"
3 #include "builtins.h"
4 #ifndef NO_HISTORY
5-#include "myhistedit.h"
6+#include "lineedit.h"
7 #endif
8
9 #include <signal.h>
+2,
-2
1@@ -48,7 +48,7 @@
2 #include "parser.h"
3 #include "builtins.h"
4 #ifndef NO_HISTORY
5-#include "myhistedit.h"
6+#include "lineedit.h"
7 #endif
8
9 #include <langinfo.h>
10@@ -123,7 +123,7 @@ static const struct varinit varinit[] = {
11 NULL }
12 };
13
14-static struct var *vartab[VTABSIZE];
15+struct var *vartab[VTABSIZE];
16
17 static const char *const locale_names[7] = {
18 "LC_COLLATE", "LC_CTYPE", "LC_MONETARY",
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+sleep: delay for a duration
5+usage: sleep num
6+
7+pause execution for a specified amount of time
8+*/
9+
10 #include <unistd.h>
11
12 #include "util.h"
+34,
-6
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+sort: sort lines
5+usage: sort [-Cbcdfimnru
6+
7+sort or merge lines of text files
8+*/
9+
10 #include "queue.h"
11 #include "text.h"
12 #include "utf.h"
13@@ -303,22 +310,28 @@ parse_flags(char **s, int *flags, int bflag)
14 {
15 while (isalpha((int)**s)) {
16 switch (*((*s)++)) {
17- case 'b':
18+ // ?man -b: specify block size or base directory
19+ case 'b':
20 *flags |= bflag;
21 break;
22- case 'd':
23+ // ?man -d: specify directory
24+ case 'd':
25 *flags |= MOD_D;
26 break;
27- case 'f':
28+ // ?man -f: force the operation
29+ case 'f':
30 *flags |= MOD_F;
31 break;
32- case 'i':
33+ // ?man -i: interactive mode or prompt for confirmation
34+ case 'i':
35 *flags |= MOD_I;
36 break;
37- case 'n':
38+ // ?man -n: print line numbers or counts
39+ case 'n':
40 *flags |= MOD_N;
41 break;
42- case 'r':
43+ // ?man -r: operate recursively
44+ case 'r':
45 *flags |= MOD_R;
46 break;
47 default:
48@@ -397,27 +410,35 @@ main(int argc, char *argv[])
49 char *outfile = NULL;
50
51 ARGBEGIN {
52+ // ?man -C: specify option flag
53 case 'C':
54 Cflag = 1;
55 break;
56+ // ?man -b: specify block size or base directory
57 case 'b':
58 global_flags |= MOD_STARTB | MOD_ENDB;
59 break;
60+ // ?man -c: print count or perform stdout action
61 case 'c':
62 cflag = 1;
63 break;
64+ // ?man -d: specify directory
65 case 'd':
66 global_flags |= MOD_D;
67 break;
68+ // ?man -f: force the operation
69 case 'f':
70 global_flags |= MOD_F;
71 break;
72+ // ?man -i: interactive mode or prompt for confirmation
73 case 'i':
74 global_flags |= MOD_I;
75 break;
76+ // ?man -k: specify option flag
77 case 'k':
78 addkeydef(EARGF(usage()), global_flags);
79 break;
80+ // ?man -m: specify mode or limit
81 case 'm':
82 /* more or less for free, but for performance-reasons,
83 * we should keep this flag in mind and maybe some later
84@@ -425,30 +446,37 @@ main(int argc, char *argv[])
85 * while merging large sorted files.
86 */
87 break;
88+ // ?man -n: print line numbers or counts
89 case 'n':
90 global_flags |= MOD_N;
91 break;
92+ // ?man -o: specify output file
93 case 'o':
94 outfile = EARGF(usage());
95 break;
96+ // ?man -r: operate recursively
97 case 'r':
98 global_flags |= MOD_R;
99 break;
100 #if FEATURE_SORT_STABLE
101+ // ?man -s: silent mode or print summary
102 case 's':
103 sflag = 1;
104 break;
105 #endif
106+ // ?man -t: sort or specify timestamp
107 case 't':
108 fieldsep = EARGF(usage());
109 if (!*fieldsep)
110 eprintf("empty delimiter\n");
111 fieldseplen = unescape(fieldsep);
112 break;
113+ // ?man -u: unbuffered output
114 case 'u':
115 uflag = 1;
116 break;
117 #if FEATURE_SORT_BIG
118+ // ?man -z: specify option flag
119 case 'z':
120 zflag = 1;
121 break;
+11,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+split: split file into pieces
5+usage: split [-a num] [-b num[k|m|g] | -l num] [-d]
6+
7+split a file into fixed-size pieces
8+*/
9+
10 #include <ctype.h>
11 #include <stdint.h>
12 #include <stdio.h>
13@@ -53,9 +60,11 @@ main(int argc, char *argv[])
14 char name[NAME_MAX + 1], *prefix = "x", *file = NULL;
15
16 ARGBEGIN {
17+ // ?man -a: print or show all entries
18 case 'a':
19 slen = estrtonum(EARGF(usage()), 0, INT_MAX);
20 break;
21+ // ?man -b: specify block size or base directory
22 case 'b':
23 always = 1;
24 if ((size = parseoffset(EARGF(usage()))) < 0)
25@@ -63,10 +72,12 @@ main(int argc, char *argv[])
26 if (!size)
27 eprintf("size needs to be positive\n");
28 break;
29+ // ?man -d: specify directory
30 case 'd':
31 base = 10;
32 start = '0';
33 break;
34+ // ?man -l: list in long format
35 case 'l':
36 always = 0;
37 size = estrtonum(EARGF(usage()), 1, MIN(LLONG_MAX, SSIZE_MAX));
+23,
-6
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+tail: output last part of files
5+usage: tail [-f] [-c num | -m num | -n num | -num] [file ...]
6+
7+print the last lines or bytes of files
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <fcntl.h>
13@@ -27,13 +34,15 @@ dropinit(int fd, const char *fname, size_t count)
14 count--; /* numbering starts at 1 */
15 while (count && (n = read(fd, buf, sizeof(buf))) > 0) {
16 switch (mode) {
17- case 'n': /* lines */
18+ // ?man -n: print line numbers or counts
19+ case 'n': /* lines */
20 for (p = buf; count && n > 0; p++, n--) {
21 if (*p == '\n')
22 count--;
23 }
24 break;
25- case 'c': /* bytes */
26+ // ?man -c: print count or perform stdout action
27+ case 'c': /* bytes */
28 if (count > (size_t)n) {
29 count -= n;
30 } else {
31@@ -42,7 +51,8 @@ dropinit(int fd, const char *fname, size_t count)
32 count = 0;
33 }
34 break;
35- case 'm': /* runes */
36+ // ?man -m: specify mode or limit
37+ case 'm': /* runes */
38 for (p = buf; count && n > 0; p += nr, n -= nr, count--) {
39 nr = charntorune(&r, p, n);
40 if (!nr) {
41@@ -103,7 +113,8 @@ taketail(int fd, const char *fname, size_t count)
42 break;
43 len += n;
44 switch (mode) {
45- case 'n': /* lines */
46+ // ?man -n: print line numbers or counts
47+ case 'n': /* lines */
48 /* ignore the last character; if it is a newline, it
49 * ends the last line */
50 for (p = buf + len - 2, left = count; p >= buf; p--) {
51@@ -116,10 +127,12 @@ taketail(int fd, const char *fname, size_t count)
52 }
53 }
54 break;
55- case 'c': /* bytes */
56+ // ?man -c: print count or perform stdout action
57+ case 'c': /* bytes */
58 p = count < len ? buf + len - count : buf;
59 break;
60- case 'm': /* runes */
61+ // ?man -m: specify mode or limit
62+ case 'm': /* runes */
63 for (p = buf + len - 1, left = count; p >= buf; p--) {
64 /* skip utf-8 continuation bytes */
65 if (UTF8_POINT(*p))
66@@ -157,11 +170,15 @@ main(int argc, char *argv[])
67 int (*tail)(int, const char *, size_t) = taketail;
68
69 ARGBEGIN {
70+ // ?man -f: force the operation
71 case 'f':
72 fflag = 1;
73 break;
74+ // ?man -c: print count or perform stdout action
75 case 'c':
76+ // ?man -m: specify mode or limit
77 case 'm':
78+ // ?man -n: print line numbers or counts
79 case 'n':
80 mode = ARGC();
81 numstr = EARGF(usage());
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+tee: duplicate input
5+usage: tee [-ai] [file ...]
6+
7+read from standard input and write to standard output and files
8+*/
9+
10 #include <fcntl.h>
11 #include <signal.h>
12 #include <unistd.h>
13@@ -21,9 +28,11 @@ main(int argc, char *argv[])
14 char buf[BUFSIZ];
15
16 ARGBEGIN {
17+ // ?man -a: print or show all entries
18 case 'a':
19 aflag = O_APPEND;
20 break;
21+ // ?man -i: interactive mode or prompt for confirmation
22 case 'i':
23 iflag = 1;
24 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+test: evaluate condition
5+usage: test
6+
7+check file types and compare values, returning 0 or 1
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <ctype.h>
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+time: time command execution
5+usage: time [-p] cmd [arg ...]
6+
7+run a command and report its execution duration
8+*/
9+
10 #include <sys/times.h>
11 #include <sys/wait.h>
12
13@@ -24,6 +31,7 @@ main(int argc, char *argv[])
14 int status, savederrno, ret = 0;
15
16 ARGBEGIN {
17+ // ?man -p: preserve file attributes
18 case 'p':
19 break;
20 default:
+14,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+touch: change file timestamps
5+usage: touch [-acm] [-d time | -r ref_file | -t time | -T time]
6+
7+update the access and modification times of files
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <errno.h>
13@@ -112,20 +119,26 @@ main(int argc, char *argv[])
14 char *ref = NULL;
15
16 ARGBEGIN {
17+ // ?man -a: print or show all entries
18 case 'a':
19 aflag = 1;
20 break;
21+ // ?man -c: print count or perform stdout action
22 case 'c':
23 cflag = 1;
24 break;
25+ // ?man -d: specify directory
26 case 'd':
27+ // ?man -t: sort or specify timestamp
28 case 't':
29 times[0].tv_sec = parsetime(EARGF(usage()));
30 times[0].tv_nsec = 0;
31 break;
32+ // ?man -m: specify mode or limit
33 case 'm':
34 mflag = 1;
35 break;
36+ // ?man -r: operate recursively
37 case 'r':
38 ref = EARGF(usage());
39 if (stat(ref, &st) < 0)
40@@ -133,6 +146,7 @@ main(int argc, char *argv[])
41 times[0] = st.st_atim;
42 times[1] = st.st_mtim;
43 break;
44+ // ?man -T: specify option flag
45 case 'T':
46 times[0].tv_sec = estrtonum(EARGF(usage()), 0, LLONG_MAX);
47 times[0].tv_nsec = 0;
+11,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+tr: translate characters
5+usage: tr [-cCds] set1 [set2]
6+
7+translate, squeeze, or delete characters from standard input
8+*/
9+
10 #include <stdlib.h>
11
12 #include "utf.h"
13@@ -195,13 +202,17 @@ main(int argc, char *argv[])
14 int ret = 0;
15
16 ARGBEGIN {
17+ // ?man -c: print count or perform stdout action
18 case 'c':
19+ // ?man -C: specify option flag
20 case 'C':
21 cflag = 1;
22 break;
23+ // ?man -d: specify directory
24 case 'd':
25 dflag = 1;
26 break;
27+ // ?man -s: silent mode or print summary
28 case 's':
29 sflag = 1;
30 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+true: return successful exit status
5+usage: true
6+
7+exit with status 0 representing success
8+*/
9+
10 int
11 main(void)
12 {
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+tsort: topological sort
5+usage: tsort [file]
6+
7+perform a topological sort on input pairs
8+*/
9+
10 #include <stdio.h>
11 #include <string.h>
12 #include <stdlib.h>
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+tty: print terminal filename
5+usage: tty
6+
7+display the filename of the terminal connected to stdin
8+*/
9+
10 #include <stdio.h>
11 #include <unistd.h>
12
+13,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+uname: print system info
5+usage: uname [-amnrsv]
6+
7+display system hostname, kernel name, release, and architecture
8+*/
9+
10 #include <sys/utsname.h>
11
12 #include <stdio.h>
13@@ -18,21 +25,27 @@ main(int argc, char *argv[])
14 int mflag = 0, nflag = 0, rflag = 0, sflag = 0, vflag = 0;
15
16 ARGBEGIN {
17+ // ?man -a: print or show all entries
18 case 'a':
19 mflag = nflag = rflag = sflag = vflag = 1;
20 break;
21+ // ?man -m: specify mode or limit
22 case 'm':
23 mflag = 1;
24 break;
25+ // ?man -n: print line numbers or counts
26 case 'n':
27 nflag = 1;
28 break;
29+ // ?man -r: operate recursively
30 case 'r':
31 rflag = 1;
32 break;
33+ // ?man -s: silent mode or print summary
34 case 's':
35 sflag = 1;
36 break;
37+ // ?man -v: verbose mode; show progress
38 case 'v':
39 vflag = 1;
40 break;
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+unexpand: convert spaces to tabs
5+usage: unexpand [-a] [-t tablist] [file ...]
6+
7+convert space characters to tab characters
8+*/
9+
10 #include <stdint.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -136,11 +143,13 @@ main(int argc, char *argv[])
14 char *tl = "8";
15
16 ARGBEGIN {
17+ // ?man -t: sort or specify timestamp
18 case 't':
19 tl = EARGF(usage());
20 if (!*tl)
21 eprintf("tablist cannot be empty\n");
22 /* fallthrough */
23+ // ?man -a: print or show all entries
24 case 'a':
25 aflag = 1;
26 break;
+12,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+uniq: report duplicate lines
5+usage: uniq [-c] [-d | -u] [-f fields] [-s chars]
6+
7+filter out repeated lines from sorted files
8+*/
9+
10 #include <ctype.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -105,18 +112,23 @@ main(int argc, char *argv[])
14 char *fname[2] = { "<stdin>", "<stdout>" };
15
16 ARGBEGIN {
17+ // ?man -c: print count or perform stdout action
18 case 'c':
19 countfmt = "%7ld ";
20 break;
21+ // ?man -d: specify directory
22 case 'd':
23 dflag = 1;
24 break;
25+ // ?man -u: unbuffered output
26 case 'u':
27 uflag = 1;
28 break;
29+ // ?man -f: force the operation
30 case 'f':
31 fskip = estrtonum(EARGF(usage()), 0, INT_MAX);
32 break;
33+ // ?man -s: silent mode or print summary
34 case 's':
35 sskip = estrtonum(EARGF(usage()), 0, INT_MAX);
36 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+unlink: remove a file
5+usage: unlink file
6+
7+call the unlink system call to remove a file
8+*/
9+
10 #include <unistd.h>
11
12 #include "util.h"
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+uudecode: decode uuencoded file
5+usage: uudecode [-m] [-o output] [file]
6+
7+decode a file created by uuencode
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <errno.h>
13@@ -234,9 +241,11 @@ main(int argc, char *argv[])
14 void (*d) (FILE *, FILE *) = NULL;
15
16 ARGBEGIN {
17+ // ?man -m: specify mode or limit
18 case 'm':
19 mflag = 1; /* accepted but unused (autodetect file type) */
20 break;
21+ // ?man -o: specify output file
22 case 'o':
23 oflag = 1;
24 ofname = EARGF(usage());
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+uuencode: encode binary file
5+usage: uuencode [-m] [file] name
6+
7+encode a binary file into ascii text
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <stdio.h>
13@@ -104,6 +111,7 @@ main(int argc, char *argv[])
14 int ret = 0;
15
16 ARGBEGIN {
17+ // ?man -m: specify mode or limit
18 case 'm':
19 uuencode_f = uuencodeb64;
20 break;
+11,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+wc: count lines, words, and bytes
5+usage: wc [-c | -m] [-lw] [file ...]
6+
7+display the number of lines, words, and bytes in files
8+*/
9+
10 #include <string.h>
11
12 #include "utf.h"
13@@ -74,15 +81,19 @@ main(int argc, char *argv[])
14 int ret = 0;
15
16 ARGBEGIN {
17+ // ?man -c: print count or perform stdout action
18 case 'c':
19 cmode = 'c';
20 break;
21+ // ?man -m: specify mode or limit
22 case 'm':
23 cmode = 'm';
24 break;
25+ // ?man -l: list in long format
26 case 'l':
27 lflag = 1;
28 break;
29+ // ?man -w: wait for completion
30 case 'w':
31 wflag = 1;
32 break;
+21,
-5
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+who: show logged in users
5+usage: who [-ml]
6+
7+display a list of users currently logged into the system
8+*/
9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -21,11 +28,14 @@ main(int argc, char *argv[])
14 struct utmp usr;
15 FILE *ufp;
16 char timebuf[sizeof "yyyy-mm-dd hh:mm"];
17+ char line_buf[sizeof(usr.ut_line) + 1];
18+ char name_buf[sizeof(usr.ut_name) + 1];
19 char *tty, *ttmp;
20 int mflag = 0, lflag = 0;
21 time_t t;
22
23 ARGBEGIN {
24+ // ?man -m: specify mode or limit
25 case 'm':
26 mflag = 1;
27 tty = ttyname(0);
28@@ -34,6 +44,7 @@ main(int argc, char *argv[])
29 if ((ttmp = strrchr(tty, '/')))
30 tty = ttmp+1;
31 break;
32+ // ?man -l: list in long format
33 case 'l':
34 lflag = 1;
35 break;
36@@ -48,16 +59,21 @@ main(int argc, char *argv[])
37 eprintf("fopen: %s:", UTMP_PATH);
38
39 while (fread(&usr, sizeof(usr), 1, ufp) == 1) {
40- if (!*usr.ut_name || !*usr.ut_line ||
41- usr.ut_line[0] == '~')
42+ memcpy(line_buf, usr.ut_line, sizeof(usr.ut_line));
43+ line_buf[sizeof(usr.ut_line)] = '\0';
44+ memcpy(name_buf, usr.ut_name, sizeof(usr.ut_name));
45+ name_buf[sizeof(usr.ut_name)] = '\0';
46+
47+ if (!*name_buf || !*line_buf ||
48+ line_buf[0] == '~')
49 continue;
50- if (mflag != 0 && strcmp(usr.ut_line, tty) != 0)
51+ if (mflag != 0 && strcmp(line_buf, tty) != 0)
52 continue;
53- if (!!strcmp(usr.ut_name, "LOGIN") == lflag)
54+ if (!!strcmp(name_buf, "LOGIN") == lflag)
55 continue;
56 t = usr.ut_time;
57 strftime(timebuf, sizeof timebuf, "%Y-%m-%d %H:%M", localtime(&t));
58- printf("%-8s %-12s %-16s\n", usr.ut_name, usr.ut_line, timebuf);
59+ printf("%-8s %-12s %-16s\n", name_buf, line_buf, timebuf);
60 }
61 fclose(ufp);
62 return 0;
+18,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+xargs: build and run command lines
5+usage: xargs [-0prtx] [-E eofstr] [-I replstr] [-L maxlines] [-n
6+
7+execute commands built from standard input arguments
8+*/
9+
10 #include <sys/wait.h>
11
12 #include <errno.h>
13@@ -278,33 +285,42 @@ main(int argc, char *argv[])
14
15 ARGBEGIN
16 {
17+ // ?man -0: specify option flag
18 case '0':
19 nulflag = 1;
20 break;
21+ // ?man -n: print line numbers or counts
22 case 'n':
23 nflag = 1;
24 maxargs =
25 estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
26 break;
27+ // ?man -p: preserve file attributes
28 case 'p':
29 pflag = 1;
30 break;
31+ // ?man -r: operate recursively
32 case 'r':
33 rflag = 1;
34 break;
35+ // ?man -s: silent mode or print summary
36 case 's':
37 argmaxsz =
38 estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
39 break;
40+ // ?man -t: sort or specify timestamp
41 case 't':
42 tflag = 1;
43 break;
44+ // ?man -x: hex format or match whole lines
45 case 'x':
46 xflag = 1;
47 break;
48+ // ?man -E: specify option flag
49 case 'E':
50 eofstr = EARGF(usage());
51 break;
52+ // ?man -I: specify option flag
53 case 'I':
54 Iflag = 1;
55 xflag = 1;
56@@ -312,11 +328,13 @@ main(int argc, char *argv[])
57 maxargs = 1;
58 replstr = EARGF(usage());
59 break;
60+ // ?man -L: specify option flag
61 case 'L':
62 Lflag = 1;
63 maxlines =
64 estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
65 break;
66+ // ?man -P: specify option flag
67 case 'P':
68 maxprocs =
69 estrtonum(EARGF(usage()), 1, MIN((unsigned long long)SIZE_MAX, (unsigned long long)LLONG_MAX));
+10,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+base64: encode or decode base64
5+usage: base64 [-d] [-i] [-w cols] [file]
6+
7+encode or decode data in base64 format
8+*/
9+
10 #include "util.h"
11 #include "arg.h"
12
13@@ -142,12 +149,15 @@ main(int argc, char *argv[])
14 fp = stdin;
15
16 ARGBEGIN {
17+ // ?man -d: specify directory
18 case 'd':
19 dflag = 1;
20 break;
21+ // ?man -i: interactive mode or prompt for confirmation
22 case 'i':
23 iflag = 1;
24 break;
25+ // ?man -w: wait for completion
26 case 'w':
27 wrap = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SSIZE_MAX));
28 break;
+0,
-0
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+chroot: run command in new root
5+usage: chroot dir [cmd [arg ...]]
6+
7+run a command or shell with a substitute root directory
8+*/
9+
10 #include <errno.h>
11 #include <stdlib.h>
12 #include <unistd.h>
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+clear: clear terminal screen
5+usage: clear
6+
7+clear the terminal screen and scrollback buffer
8+*/
9+
10 #include <stdio.h>
11
12 #include "util.h"
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+cols: format columns
5+usage: cols [-c num] [file ...]
6+
7+format standard input into vertical columns
8+*/
9+
10 #include <sys/ioctl.h>
11
12 #include <limits.h>
13@@ -28,6 +35,7 @@ main(int argc, char *argv[])
14 char *p;
15
16 ARGBEGIN {
17+ // ?man -c: print count or perform stdout action
18 case 'c':
19 cflag = 1;
20 chars = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+cron: cron daemon
5+usage: cron [-f file] [-n]
6+
7+daemon to run scheduled background commands
8+*/
9+
10 #include <sys/types.h>
11 #include <sys/wait.h>
12
13@@ -498,9 +505,11 @@ main(int argc, char *argv[])
14 struct sigaction sa;
15
16 ARGBEGIN {
17+ // ?man -n: print line numbers or counts
18 case 'n':
19 nflag = 1;
20 break;
21+ // ?man -f: force the operation
22 case 'f':
23 config = EARGF(usage());
24 break;
R cmd/linux/dmesg.c =>
cmd/pseudo/dmesg.c
+32,
-19
1@@ -1,5 +1,10 @@
2 /* See LICENSE file for copyright and license details. */
3-#include <sys/klog.h>
4+/* ?man
5+dmesg: print kernel ring buffer
6+usage: dmesg [-Ccr] [-n level]
7+
8+display or control the kernel ring buffer messages
9+*/
10
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -8,12 +13,9 @@
14
15 #include "util.h"
16
17-enum {
18- SYSLOG_ACTION_READ_ALL = 3,
19- SYSLOG_ACTION_CLEAR = 5,
20- SYSLOG_ACTION_CONSOLE_LEVEL = 8,
21- SYSLOG_ACTION_SIZE_BUFFER = 10
22-};
23+ssize_t get_dmesg(char *, size_t);
24+int clear_dmesg(void);
25+int set_console_level(int);
26
27 static void
28 dmesg_show(const void *buf, size_t n)
29@@ -37,45 +39,56 @@ usage(void)
30 int
31 main(int argc, char *argv[])
32 {
33- int n;
34+ ssize_t n;
35 char *buf;
36 int cflag = 0;
37 long level;
38
39 ARGBEGIN {
40+ // ?man -C: specify option flag
41 case 'C':
42- if (klogctl(SYSLOG_ACTION_CLEAR, NULL, 0) < 0)
43- eprintf("klogctl:");
44+ if (clear_dmesg() < 0)
45+ eprintf("clear_dmesg:");
46 return 0;
47+ // ?man -c: print count or perform stdout action
48 case 'c':
49 cflag = 1;
50 break;
51+ // ?man -r: operate recursively
52 case 'r':
53 break;
54+ // ?man -n: print line numbers or counts
55 case 'n':
56 level = estrtol(EARGF(usage()), 10);
57- if (klogctl(SYSLOG_ACTION_CONSOLE_LEVEL, NULL, level) < 0)
58- eprintf("klogctl:");
59+ if (set_console_level(level) < 0)
60+ eprintf("set_console_level:");
61 return 0;
62 default:
63 usage();
64 } ARGEND;
65
66- n = klogctl(SYSLOG_ACTION_SIZE_BUFFER, NULL, 0);
67- if (n < 0)
68- eprintf("klogctl:");
69+ if (argc)
70+ usage();
71+
72+ n = get_dmesg(NULL, 0);
73+ if (n <= 0)
74+ n = 16384;
75
76 buf = emalloc(n);
77
78- n = klogctl(SYSLOG_ACTION_READ_ALL, buf, n);
79+ n = get_dmesg(buf, n);
80 if (n < 0)
81- eprintf("klogctl:");
82+ eprintf("get_dmesg:");
83
84 dmesg_show(buf, n);
85
86- if (cflag && klogctl(SYSLOG_ACTION_CLEAR, NULL, 0) < 0)
87- eprintf("klogctl:");
88+ if (cflag && clear_dmesg() < 0)
89+ eprintf("clear_dmesg:");
90
91 free(buf);
92+
93+ if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
94+ return 1;
95+
96 return 0;
97 }
R cmd/linux/fallocate.c =>
cmd/pseudo/fallocate.c
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+fallocate: preallocate file space
5+usage: fallocate [-o num] -l num file ...
6+
7+preallocate or deallocate space to a file
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <fcntl.h>
13@@ -23,9 +30,11 @@ main(int argc, char *argv[])
14 off_t size = 0, offset = 0;
15
16 ARGBEGIN {
17+ // ?man -l: list in long format
18 case 'l':
19 size = estrtonum(EARGF(usage()), 1, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
20 break;
21+ // ?man -o: specify output file
22 case 'o':
23 offset = estrtonum(EARGF(usage()), 0, MIN((unsigned long long)LLONG_MAX, (unsigned long long)SIZE_MAX));
24 break;
+12,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+flock: manage locks
5+usage: flock [-nosux] file cmd [arg ...]
6+
7+acquire or release locks from shell scripts
8+*/
9+
10 #include <sys/file.h>
11 #include <sys/wait.h>
12
13@@ -22,18 +29,23 @@ main(int argc, char *argv[])
14 pid_t pid;
15
16 ARGBEGIN {
17+ // ?man -n: print line numbers or counts
18 case 'n':
19 nonblk = LOCK_NB;
20 break;
21+ // ?man -o: specify output file
22 case 'o':
23 oflag = 1;
24 break;
25+ // ?man -s: silent mode or print summary
26 case 's':
27 flags = LOCK_SH;
28 break;
29+ // ?man -u: unbuffered output
30 case 'u':
31 flags = LOCK_UN;
32 break;
33+ // ?man -x: hex format or match whole lines
34 case 'x':
35 flags = LOCK_EX;
36 break;
R cmd/linux/free.c =>
cmd/pseudo/free.c
+33,
-16
1@@ -1,11 +1,18 @@
2 /* See LICENSE file for copyright and license details. */
3-#include <sys/sysinfo.h>
4+/* ?man
5+free: display memory usage
6+usage: free [-bkmg]
7+
8+display the amount of free and used memory in the system
9+*/
10
11 #include <stdio.h>
12 #include <stdlib.h>
13
14 #include "util.h"
15
16+int get_meminfo(struct MemInfo *);
17+
18 static unsigned int mem_unit = 1;
19 static unsigned int unit_shift;
20
21@@ -24,22 +31,22 @@ usage(void)
22 int
23 main(int argc, char *argv[])
24 {
25- struct sysinfo info;
26-
27- if (sysinfo(&info) < 0)
28- eprintf("sysinfo:");
29- mem_unit = info.mem_unit ? info.mem_unit : 1;
30+ struct MemInfo mi;
31
32 ARGBEGIN {
33+ // ?man -b: specify block size or base directory
34 case 'b':
35 unit_shift = 0;
36 break;
37+ // ?man -k: specify option flag
38 case 'k':
39 unit_shift = 10;
40 break;
41+ // ?man -m: specify mode or limit
42 case 'm':
43 unit_shift = 20;
44 break;
45+ // ?man -g: specify option flag
46 case 'g':
47 unit_shift = 30;
48 break;
49@@ -47,6 +54,12 @@ main(int argc, char *argv[])
50 usage();
51 } ARGEND;
52
53+ if (argc)
54+ usage();
55+
56+ if (get_meminfo(&mi) < 0)
57+ eprintf("get_meminfo:");
58+
59 printf(" %13s%13s%13s%13s%13s\n",
60 "total",
61 "used",
62@@ -54,19 +67,23 @@ main(int argc, char *argv[])
63 "shared", "buffers");
64 printf("Mem: ");
65 printf("%13llu%13llu%13llu%13llu%13llu\n",
66- scale(info.totalram),
67- scale(info.totalram - info.freeram),
68- scale(info.freeram),
69- scale(info.sharedram),
70- scale(info.bufferram));
71+ scale(mi.total),
72+ scale(mi.total - mi.free),
73+ scale(mi.free),
74+ scale(mi.shared),
75+ scale(mi.buffers));
76 printf("-/+ buffers/cache:");
77 printf("%13llu%13llu\n",
78- scale(info.totalram - info.freeram - info.bufferram),
79- scale(info.freeram + info.bufferram));
80+ scale(mi.total - mi.free - mi.buffers),
81+ scale(mi.free + mi.buffers));
82 printf("Swap:");
83 printf("%13llu%13llu%13llu\n",
84- scale(info.totalswap),
85- scale(info.totalswap - info.freeswap),
86- scale(info.freeswap));
87+ scale(mi.totalswap),
88+ scale(mi.totalswap - mi.freeswap),
89+ scale(mi.freeswap));
90+
91+ if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
92+ return 1;
93+
94 return 0;
95 }
+11,
-1
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+getty: set terminal mode
5+usage: getty [tty] [term] [cmd] [args...]
6+
7+set terminal line discipline, speed, and mode
8+*/
9+
10 #include <sys/ioctl.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
13@@ -29,6 +36,7 @@ main(int argc, char *argv[])
14 {
15 char term[128], logname[LOGIN_NAME_MAX], c;
16 char hostname[HOST_NAME_MAX + 1];
17+ char ut_line_buf[UT_LINESIZE + 1];
18 struct utmp usr;
19 struct sigaction sa;
20 FILE *fp;
21@@ -97,7 +105,9 @@ main(int argc, char *argv[])
22 break;
23 if (usr.ut_line[0] == '\0')
24 continue;
25- if (strcmp(usr.ut_line, tty) != 0)
26+ memcpy(ut_line_buf, usr.ut_line, UT_LINESIZE);
27+ ut_line_buf[UT_LINESIZE] = '\0';
28+ if (strcmp(ut_line_buf, tty) != 0)
29 continue;
30 memset(&usr, 0, sizeof(usr));
31 fseek(fp, pos, SEEK_SET);
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+halt: halt or reboot
5+usage: halt [-pr]
6+
7+halt, poweroff, or reboot the machine
8+*/
9+
10 #include <sys/syscall.h>
11
12 #include <stdio.h>
13@@ -21,9 +28,11 @@ main(int argc, char *argv[])
14 int cmd = LINUX_REBOOT_CMD_HALT;
15
16 ARGBEGIN {
17+ // ?man -p: preserve file attributes
18 case 'p':
19 pflag = 1;
20 break;
21+ // ?man -r: operate recursively
22 case 'r':
23 rflag = 1;
24 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+hostname: show or set hostname
5+usage: hostname [name]
6+
7+display or configure the system hostname
8+*/
9+
10 #include <stdio.h>
11 #include <string.h>
12 #include <unistd.h>
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+killall5: send signal to all processes
5+usage: killall5 [-o pid1
6+
7+send a signal to all processes except kernel threads
8+*/
9+
10 #include <dirent.h>
11 #include <limits.h>
12 #include <signal.h>
13@@ -49,6 +56,7 @@ main(int argc, char *argv[])
14 size_t i;
15
16 ARGBEGIN {
17+ // ?man -s: silent mode or print summary
18 case 's':
19 v = EARGF(usage());
20 sig = strtol(v, &end, 0);
21@@ -63,6 +71,7 @@ main(int argc, char *argv[])
22 if (i == LEN(sigs))
23 eprintf("%s: unknown signal\n", v);
24 break;
25+ // ?man -o: specify output file
26 case 'o':
27 oflag = 1;
28 arg = EARGF(usage());
+11,
-1
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+last: show last logged in users
5+usage: last [user]
6+
7+display a list of recent user logins
8+*/
9+
10 #include <errno.h>
11 #include <libgen.h>
12 #include <paths.h>
13@@ -25,6 +32,7 @@ main(int argc, char **argv)
14 FILE *fp;
15 struct utmp ut;
16 char *user, *file, *prog;
17+ char ut_name_buf[UT_NAMESIZE + 1];
18 time_t t;
19
20 ARGBEGIN {
21@@ -49,8 +57,10 @@ main(int argc, char **argv)
22 eprintf("fopen %s:", file);
23
24 while (fread(&ut, sizeof(ut), 1, fp) == 1) {
25+ memcpy(ut_name_buf, ut.ut_name, UT_NAMESIZE);
26+ ut_name_buf[UT_NAMESIZE] = '\0';
27 if (ut.ut_type != USER_PROCESS ||
28- (user && strcmp(user, ut.ut_name))) {
29+ (user && strcmp(user, ut_name_buf))) {
30 continue;
31 }
32
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+lastlog: report recent logins
5+usage: lastlog
6+
7+display the most recent login times of users
8+*/
9+
10 #include <errno.h>
11 #include <paths.h>
12 #include <pwd.h>
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+login: begin terminal session
5+usage: login [-p] username
6+
7+authenticate and start a session on the system
8+*/
9+
10 #include <sys/ioctl.h>
11 #include <sys/types.h>
12
13@@ -78,6 +85,7 @@ main(int argc, char *argv[])
14 int pflag = 0;
15
16 ARGBEGIN {
17+ // ?man -p: preserve file attributes
18 case 'p':
19 pflag = 1;
20 break;
+10,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+md5sum: compute md5 checksums
5+usage: md5sum [-c] [file ...]
6+
7+compute and check md5 message digests
8+*/
9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <stdint.h>
13@@ -28,10 +35,13 @@ main(int argc, char *argv[])
14 uint8_t md[MD5_DIGEST_LENGTH];
15
16 ARGBEGIN {
17+ // ?man -b: specify block size or base directory
18 case 'b':
19+ // ?man -t: sort or specify timestamp
20 case 't':
21 /* ignore */
22 break;
23+ // ?man -c: print count or perform stdout action
24 case 'c':
25 cryptfunc = cryptcheck;
26 break;
+12,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+mktemp: create temporary file
5+usage: mktemp [-dqtu] [-p directory] [template]
6+
7+create a temporary file or directory safely
8+*/
9+
10 #include <libgen.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -22,19 +29,24 @@ main(int argc, char *argv[])
14 size_t len;
15
16 ARGBEGIN {
17+ // ?man -d: specify directory
18 case 'd':
19 dflag = 1;
20 break;
21+ // ?man -p: preserve file attributes
22 case 'p':
23 pflag = 1;
24 pdir = EARGF(usage());
25 break;
26+ // ?man -q: quiet mode; suppress output
27 case 'q':
28 qflag = 1;
29 break;
30+ // ?man -t: sort or specify timestamp
31 case 't':
32 tflag = 1;
33 break;
34+ // ?man -u: unbuffered output
35 case 'u':
36 uflag = 1;
37 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+nologin: politely refuse login
5+usage: nologin
6+
7+politely refuse a login attempt by exiting with status 1
8+*/
9+
10 #include <fcntl.h>
11 #include <stdio.h>
12 #include <unistd.h>
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+pagesize: print system page size
5+usage: pagesize
6+
7+display the size of a page in memory
8+*/
9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <unistd.h>
R cmd/linux/pidof.c =>
cmd/pseudo/pidof.c
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+pidof: find process ids
5+usage: pidof [-o pid1
6+
7+find the process identity numbers of running programs
8+*/
9+
10 #include <sys/types.h>
11
12 #include <dirent.h>
13@@ -39,9 +46,11 @@ main(int argc, char *argv[])
14 struct pidentry *pe;
15
16 ARGBEGIN {
17+ // ?man -s: silent mode or print summary
18 case 's':
19 sflag = 1;
20 break;
21+ // ?man -o: specify output file
22 case 'o':
23 oflag = 1;
24 arg = EARGF(usage());
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+printenv: print environment variables
5+usage: printenv [var ...]
6+
7+display all or part of the current environment
8+*/
9+
10 #include <stdio.h>
11 #include <stdlib.h>
12
R cmd/linux/pwdx.c =>
cmd/pseudo/pwdx.c
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+pwdx: print working directory of process
5+usage: pwdx pid...
6+
7+display the current working directory of a process by pid
8+*/
9+
10 #include <errno.h>
11 #include <limits.h>
12 #include <stdio.h>
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+respawn: restart command on exit
5+usage: respawn [-l fifo] [-d N] cmd [args...]
6+
7+run a command and restart it automatically when it exits
8+*/
9+
10 #include <sys/stat.h>
11 #include <sys/time.h>
12 #include <sys/types.h>
13@@ -42,9 +49,11 @@ main(int argc, char *argv[])
14 int polln;
15
16 ARGBEGIN {
17+ // ?man -d: specify directory
18 case 'd':
19 delay = estrtol(EARGF(usage()), 0);
20 break;
21+ // ?man -l: list in long format
22 case 'l':
23 fifo = EARGF(usage());
24 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+rev: reverse lines
5+usage: rev [file ...]
6+
7+reverse the order of characters in each line of input
8+*/
9+
10 #include <stdio.h>
11 #include <string.h>
12 #include <unistd.h>
+13,
-2
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+seq: print sequence of numbers
5+usage: seq [-f fmt] [-s sep] [-w]
6+
7+print a sequence of numbers from start to end
8+*/
9+
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <string.h>
13@@ -9,7 +16,7 @@ static int
14 digitsleft(const char *d)
15 {
16 int shift;
17- char *exp;
18+ const char *exp;
19
20 if (*d == '+')
21 d++;
22@@ -23,7 +30,7 @@ static int
23 digitsright(const char *d)
24 {
25 int shift, after;
26- char *exp;
27+ const char *exp;
28
29 exp = strpbrk(d, "eE");
30 shift = exp ? estrtonum(&exp[1], INT_MIN, INT_MAX) : 0;
31@@ -58,6 +65,7 @@ format:
32 fmt++;
33
34 switch (*fmt) {
35+ // ?man -f: force the operation
36 case 'f': case 'F':
37 case 'g': case 'G':
38 case 'e': case 'E':
39@@ -85,14 +93,17 @@ main(int argc, char *argv[])
40 const char *starts = "1", *steps = "1", *ends = "1", *sep = "\n";
41
42 ARGBEGIN {
43+ // ?man -f: force the operation
44 case 'f':
45 if (!validfmt(tmp=EARGF(usage())))
46 eprintf("%s: invalid format\n", tmp);
47 fmt = tmp;
48 break;
49+ // ?man -s: silent mode or print summary
50 case 's':
51 sep = EARGF(usage());
52 break;
53+ // ?man -w: wait for completion
54 case 'w':
55 wflag = 1;
56 break;
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+setsid: run in new session
5+usage: setsid [-f] cmd [arg ...]
6+
7+run a program in a new session
8+*/
9+
10 #include <errno.h>
11 #include <unistd.h>
12
13@@ -18,6 +25,7 @@ main(int argc, char *argv[])
14 int savederrno;
15
16 ARGBEGIN {
17+ // ?man -f: force the operation
18 case 'f':
19 fflag = 1;
20 break;
+10,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+sha1sum: compute sha1 checksums
5+usage: sha1sum [-c] [file ...]
6+
7+compute and check sha1 message digests
8+*/
9+
10 #include <stdint.h>
11 #include <stdio.h>
12
13@@ -27,10 +34,13 @@ main(int argc, char *argv[])
14 uint8_t md[SHA1_DIGEST_LENGTH];
15
16 ARGBEGIN {
17+ // ?man -b: specify block size or base directory
18 case 'b':
19+ // ?man -t: sort or specify timestamp
20 case 't':
21 /* ignore */
22 break;
23+ // ?man -c: print count or perform stdout action
24 case 'c':
25 cryptfunc = cryptcheck;
26 break;
+10,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+sha224sum: compute sha224 checksums
5+usage: sha224sum [-c] [file ...]
6+
7+compute and check sha224 message digests
8+*/
9+
10 #include <stdint.h>
11 #include <stdio.h>
12
13@@ -27,10 +34,13 @@ main(int argc, char *argv[])
14 uint8_t md[SHA224_DIGEST_LENGTH];
15
16 ARGBEGIN {
17+ // ?man -b: specify block size or base directory
18 case 'b':
19+ // ?man -t: sort or specify timestamp
20 case 't':
21 /* ignore */
22 break;
23+ // ?man -c: print count or perform stdout action
24 case 'c':
25 cryptfunc = cryptcheck;
26 break;
+10,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+sha256sum: compute sha256 checksums
5+usage: sha256sum [-c] [file ...]
6+
7+compute and check sha256 message digests
8+*/
9+
10 #include <stdint.h>
11 #include <stdio.h>
12
13@@ -27,10 +34,13 @@ main(int argc, char *argv[])
14 uint8_t md[SHA256_DIGEST_LENGTH];
15
16 ARGBEGIN {
17+ // ?man -b: specify block size or base directory
18 case 'b':
19+ // ?man -t: sort or specify timestamp
20 case 't':
21 /* ignore */
22 break;
23+ // ?man -c: print count or perform stdout action
24 case 'c':
25 cryptfunc = cryptcheck;
26 break;
+10,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+sha384sum: compute sha384 checksums
5+usage: sha384sum [-c] [file ...]
6+
7+compute and check sha384 message digests
8+*/
9+
10 #include <stdint.h>
11 #include <stdio.h>
12
13@@ -27,10 +34,13 @@ main(int argc, char *argv[])
14 uint8_t md[SHA384_DIGEST_LENGTH];
15
16 ARGBEGIN {
17+ // ?man -b: specify block size or base directory
18 case 'b':
19+ // ?man -t: sort or specify timestamp
20 case 't':
21 /* ignore */
22 break;
23+ // ?man -c: print count or perform stdout action
24 case 'c':
25 cryptfunc = cryptcheck;
26 break;
+10,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+sha512-224sum: compute sha512/224 checksums
5+usage: sha512-224sum [-c] [file ...]
6+
7+compute and check sha512/224 message digests
8+*/
9+
10 #include <stdint.h>
11 #include <stdio.h>
12
13@@ -27,10 +34,13 @@ main(int argc, char *argv[])
14 uint8_t md[SHA512_224_DIGEST_LENGTH];
15
16 ARGBEGIN {
17+ // ?man -b: specify block size or base directory
18 case 'b':
19+ // ?man -t: sort or specify timestamp
20 case 't':
21 /* ignore */
22 break;
23+ // ?man -c: print count or perform stdout action
24 case 'c':
25 cryptfunc = cryptcheck;
26 break;
+10,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+sha512-256sum: compute sha512/256 checksums
5+usage: sha512-256sum [-c] [file ...]
6+
7+compute and check sha512/256 message digests
8+*/
9+
10 #include <stdint.h>
11 #include <stdio.h>
12
13@@ -27,10 +34,13 @@ main(int argc, char *argv[])
14 uint8_t md[SHA512_256_DIGEST_LENGTH];
15
16 ARGBEGIN {
17+ // ?man -b: specify block size or base directory
18 case 'b':
19+ // ?man -t: sort or specify timestamp
20 case 't':
21 /* ignore */
22 break;
23+ // ?man -c: print count or perform stdout action
24 case 'c':
25 cryptfunc = cryptcheck;
26 break;
+10,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+sha512sum: compute sha512 checksums
5+usage: sha512sum [-c] [file ...]
6+
7+compute and check sha512 message digests
8+*/
9+
10 #include <stdint.h>
11 #include <stdio.h>
12
13@@ -27,10 +34,13 @@ main(int argc, char *argv[])
14 uint8_t md[SHA512_DIGEST_LENGTH];
15
16 ARGBEGIN {
17+ // ?man -b: specify block size or base directory
18 case 'b':
19+ // ?man -t: sort or specify timestamp
20 case 't':
21 /* ignore */
22 break;
23+ // ?man -c: print count or perform stdout action
24 case 'c':
25 cryptfunc = cryptcheck;
26 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+sponge: soak up input
5+usage: sponge file
6+
7+soak up standard input and write to a file
8+*/
9+
10 #include <fcntl.h>
11 #include <stdlib.h>
12 #include <unistd.h>
+27,
-8
1@@ -1,3 +1,10 @@
2+/* ?man
3+stat: display file status
4+usage: stat [-L] [-t]
5+
6+display file or filesystem status information
7+*/
8+
9 #include "config.h"
10 #include "util.h"
11
12@@ -56,10 +63,12 @@ print_custom(const char *file, struct stat *st, const char *fmt)
13 p++;
14 switch (*p) {
15 case 'n': putchar('\n'); break;
16- case 't': putchar('\t'); break;
17+ // ?man -t: sort or specify timestamp
18+ case 't': putchar('\t'); break;
19 case 'r': putchar('\r'); break;
20 case 'b': putchar('\b'); break;
21- case 'f': putchar('\f'); break;
22+ // ?man -f: force the operation
23+ case 'f': putchar('\f'); break;
24 case '\\': putchar('\\'); break;
25 case '\0': return;
26 default: putchar(*p); break;
27@@ -169,7 +178,8 @@ print_custom(const char *file, struct stat *st, const char *fmt)
28 fmt_spec[fmt_len] = '\0';
29 printf(fmt_spec, (long long)st->st_size);
30 break;
31- case 't':
32+ // ?man -t: sort or specify timestamp
33+ case 't':
34 fmt_spec[fmt_len++] = 'x';
35 fmt_spec[fmt_len] = '\0';
36 printf(fmt_spec, major(st->st_rdev));
37@@ -273,10 +283,12 @@ print_fs_custom(const char *file, struct statfs *st, const char *fmt)
38 p++;
39 switch (*p) {
40 case 'n': putchar('\n'); break;
41- case 't': putchar('\t'); break;
42+ // ?man -t: sort or specify timestamp
43+ case 't': putchar('\t'); break;
44 case 'r': putchar('\r'); break;
45 case 'b': putchar('\b'); break;
46- case 'f': putchar('\f'); break;
47+ // ?man -f: force the operation
48+ case 'f': putchar('\f'); break;
49 case '\\': putchar('\\'); break;
50 case '\0': return;
51 default: putchar(*p); break;
52@@ -322,7 +334,8 @@ print_fs_custom(const char *file, struct statfs *st, const char *fmt)
53 fmt_spec[fmt_len] = '\0';
54 printf(fmt_spec, (long long)st->f_blocks);
55 break;
56- case 'c':
57+ // ?man -c: print count or perform stdout action
58+ case 'c':
59 fmt_spec[fmt_len++] = 'l';
60 fmt_spec[fmt_len++] = 'l';
61 fmt_spec[fmt_len++] = 'd';
62@@ -336,7 +349,8 @@ print_fs_custom(const char *file, struct statfs *st, const char *fmt)
63 fmt_spec[fmt_len] = '\0';
64 printf(fmt_spec, (long long)st->f_ffree);
65 break;
66- case 'f':
67+ // ?man -f: force the operation
68+ case 'f':
69 fmt_spec[fmt_len++] = 'l';
70 fmt_spec[fmt_len++] = 'l';
71 fmt_spec[fmt_len++] = 'd';
72@@ -373,7 +387,8 @@ print_fs_custom(const char *file, struct statfs *st, const char *fmt)
73 fmt_spec[fmt_len] = '\0';
74 printf(fmt_spec, (long)st->f_frsize ? (long)st->f_frsize : (long)st->f_bsize);
75 break;
76- case 't':
77+ // ?man -t: sort or specify timestamp
78+ case 't':
79 fmt_spec[fmt_len++] = 'l';
80 fmt_spec[fmt_len++] = 'x';
81 fmt_spec[fmt_len] = '\0';
82@@ -463,19 +478,23 @@ main(int argc, char *argv[])
83 void (*showstat)(const char *, struct stat *) = show_stat;
84
85 ARGBEGIN {
86+ // ?man -L: specify option flag
87 case 'L':
88 fn = stat;
89 fnname = "stat";
90 break;
91+ // ?man -t: sort or specify timestamp
92 case 't':
93 showstat = show_stat_terse;
94 break;
95 #if FEATURE_STAT_FILESYSTEM
96+ // ?man -f: force the operation
97 case 'f':
98 fflag = 1;
99 break;
100 #endif
101 #if FEATURE_STAT_FORMAT
102+ // ?man -c: print count or perform stdout action
103 case 'c':
104 format = EARGF(usage());
105 break;
+25,
-1
1@@ -1,3 +1,10 @@
2+/* ?man
3+tar: tape archiver
4+usage: tar [x | t | -x | -t] [-C dir] [-J | -Z | -a | -j | -z] [-m] [-p]
5+
6+manipulate tape archive files
7+*/
8+
9 #include "config.h"
10 #include "fs.h"
11 #include "utf.h"
12@@ -421,7 +428,7 @@ unarchive(char *fname, ssize_t l, char b[BLKSIZ])
13 if (fd != -1) {
14 for (; l > 0; l -= BLKSIZ)
15 if (eread(tarfd, b, BLKSIZ) > 0)
16- ewrite(fd, b, MIN(l, BLKSIZ));
17+ ewrite(fd, b, MIN(l, (ssize_t)BLKSIZ));
18 close(fd);
19 }
20
21@@ -670,38 +677,52 @@ main(int argc, char *argv[])
22 *(argv[1]+1) ? *argv[1] = '-' : (*++argv = argv0, --argc);
23
24 ARGBEGIN {
25+ // ?man -x: extract files from an archive
26 case 'x':
27 #if FEATURE_TAR_CREATE
28+ // ?man -c: create a new archive
29 case 'c':
30 #endif
31+ // ?man -t: list the contents of an archive
32 case 't':
33 mode = ARGC();
34 break;
35+ // ?man -C: specify option flag
36 case 'C':
37 dir = EARGF(usage());
38 break;
39+ // ?man -f: specify archive file
40 case 'f':
41 file = EARGF(usage());
42 break;
43+ // ?man -m: specify mode or limit
44 case 'm':
45 mflag = 1;
46 break;
47+ // ?man -J: specify option flag
48 case 'J':
49+ // ?man -Z: specify option flag
50 case 'Z':
51+ // ?man -a: print or show all entries
52 case 'a':
53+ // ?man -j: specify option flag
54 case 'j':
55+ // ?man -z: specify option flag
56 case 'z':
57 filtermode = ARGC();
58 filtertool = filtertools[filtermode];
59 break;
60+ // ?man -h: suppress headers or print help
61 case 'h':
62 #if FEATURE_TAR_CREATE
63 r.follow = 'L';
64 #endif
65 break;
66+ // ?man -v: verbosely list files processed
67 case 'v':
68 vflag = 1;
69 break;
70+ // ?man -p: preserve file attributes
71 case 'p':
72 break; /* do nothing as already default behaviour */
73 #if FEATURE_TAR_EXCLUDE
74@@ -724,6 +745,7 @@ main(int argc, char *argv[])
75
76 switch (mode) {
77 #if FEATURE_TAR_CREATE
78+ // ?man -c: create a new archive
79 case 'c':
80 if (!argc)
81 usage();
82@@ -747,7 +769,9 @@ main(int argc, char *argv[])
83 recurse(AT_FDCWD, *argv, NULL, &r);
84 break;
85 #endif
86+ // ?man -t: list the contents of an archive
87 case 't':
88+ // ?man -x: extract files from an archive
89 case 'x':
90 tarfd = 0;
91 if (file && *file != '-') {
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+truncate: set file size
5+usage: truncate [-c] -s size file...
6+
7+shrink or extend a file to a specified size
8+*/
9+
10 #include <sys/stat.h>
11
12 #include <fcntl.h>
13@@ -22,10 +29,12 @@ main(int argc, char *argv[])
14 long size = 0;
15
16 ARGBEGIN {
17+ // ?man -s: silent mode or print summary
18 case 's':
19 sflag = 1;
20 size = estrtol(EARGF(usage()), 10);
21 break;
22+ // ?man -c: print count or perform stdout action
23 case 'c':
24 cflag = 1;
25 break;
R cmd/linux/uptime.c =>
cmd/pseudo/uptime.c
+29,
-12
1@@ -1,5 +1,10 @@
2 /* See LICENSE file for copyright and license details. */
3-#include <sys/sysinfo.h>
4+/* ?man
5+uptime: show system uptime
6+usage: uptime
7+
8+display how long the system has been running and load averages
9+*/
10
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -10,6 +15,9 @@
14 #include "config.h"
15 #include "util.h"
16
17+int get_uptime(long *);
18+int get_loads(double *);
19+
20 static void
21 usage(void)
22 {
23@@ -21,7 +29,8 @@ main(int argc, char *argv[])
24 {
25 struct utmpx utx;
26 FILE *ufp;
27- struct sysinfo info;
28+ long uptime;
29+ double loads[3];
30 time_t tmptime;
31 struct tm *now;
32 unsigned int days, hours, minutes;
33@@ -33,16 +42,23 @@ main(int argc, char *argv[])
34 usage();
35 } ARGEND;
36
37- if (sysinfo(&info) < 0)
38- eprintf("sysinfo:");
39+ if (argc)
40+ usage();
41+
42+ if (get_uptime(&uptime) < 0)
43+ eprintf("get_uptime:");
44+ if (get_loads(loads) < 0)
45+ eprintf("get_loads:");
46+
47 time(&tmptime);
48 now = localtime(&tmptime);
49 printf(" %02d:%02d:%02d up ", now->tm_hour, now->tm_min, now->tm_sec);
50- info.uptime /= 60;
51- minutes = info.uptime % 60;
52- info.uptime /= 60;
53- hours = info.uptime % 24;
54- days = info.uptime / 24;
55+
56+ uptime /= 60;
57+ minutes = uptime % 60;
58+ uptime /= 60;
59+ hours = uptime % 24;
60+ days = uptime / 24;
61 if (days)
62 printf("%d day%s, ", days, days != 1 ? "s" : "");
63 if (hours)
64@@ -65,9 +81,10 @@ main(int argc, char *argv[])
65 }
66
67 printf(" load average: %.02f, %.02f, %.02f\n",
68- info.loads[0] / 65536.0f,
69- info.loads[1] / 65536.0f,
70- info.loads[2] / 65536.0f);
71+ loads[0], loads[1], loads[2]);
72+
73+ if (fshut(stdin, "<stdin>") | fshut(stdout, "<stdout>"))
74+ return 1;
75
76 return 0;
77 }
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+watch: execute periodically
5+usage: watch [-t] [-n interval] command
6+
7+run a program periodically, showing output fullscreen
8+*/
9+
10 #include <errno.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13@@ -22,8 +29,10 @@ main(int argc, char *argv[])
14 int i;
15
16 ARGBEGIN {
17+ // ?man -t: sort or specify timestamp
18 case 't':
19 break;
20+ // ?man -n: print line numbers or counts
21 case 'n':
22 period = strtof(EARGF(usage()), &end);
23 if (*end != '\0' || errno != 0)
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+which: locate a command
5+usage: which [-a] name ...
6+
7+find the path of executable files in PATH
8+*/
9+
10 #include <sys/stat.h>
11 #include <sys/types.h>
12
13@@ -75,6 +82,7 @@ main(int argc, char *argv[])
14 int found = 0, foundall = 1;
15
16 ARGBEGIN {
17+ // ?man -a: print or show all entries
18 case 'a':
19 aflag = 1;
20 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+whoami: print effective user name
5+usage: whoami
6+
7+display the effective user name of the current process
8+*/
9+
10 #include <errno.h>
11 #include <stdio.h>
12 #include <unistd.h>
+15,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+xinstall: copy files and set attributes
5+usage: xinstall [-g group] [-o owner] [-m mode] (-d dir ... | [-D] (-t dest source ... | source ... dest))
6+
7+copy files and set their permissions and ownership
8+*/
9+
10 #include <grp.h>
11 #include <pwd.h>
12 #include <errno.h>
13@@ -96,27 +103,35 @@ main(int argc, char *argv[])
14 char *p;
15
16 ARGBEGIN {
17+ // ?man -c: print count or perform stdout action
18 case 'c':
19 /* no-op for compatibility */
20 break;
21+ // ?man -d: specify directory
22 case 'd':
23 dflag = 1;
24 break;
25+ // ?man -D: specify option flag
26 case 'D':
27 Dflag = 1;
28 break;
29+ // ?man -s: silent mode or print summary
30 case 's':
31 /* no-op for compatibility */
32 break;
33+ // ?man -g: specify option flag
34 case 'g':
35 gflag = EARGF(usage());
36 break;
37+ // ?man -o: specify output file
38 case 'o':
39 oflag = EARGF(usage());
40 break;
41+ // ?man -m: specify mode or limit
42 case 'm':
43 mflag = EARGF(usage());
44 break;
45+ // ?man -t: sort or specify timestamp
46 case 't':
47 tflag = EARGF(usage());
48 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+yes: output string repeatedly
5+usage: yes [string]
6+
7+output a string repeatedly until terminated
8+*/
9+
10 #include <stdio.h>
11
12 #include "util.h"
+8,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+mknod: create special files
5+usage: mknod [-m mode] name b|c|u major minor
6+
7+create block or character special files
8+*/
9+
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 #ifndef makedev
13@@ -28,6 +35,7 @@ main(int argc, char *argv[])
14 dev_t dev;
15
16 ARGBEGIN {
17+ // ?man -m: specify mode or limit
18 case 'm':
19 mode = parsemode(EARGF(usage()), mode, umask(0));
20 break;
+7,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+passwd: change user password
5+usage: passwd [username]
6+
7+update a user authentication password
8+*/
9+
10 #include <sys/ioctl.h>
11 #include <sys/stat.h>
12 #include <sys/types.h>
+9,
-0
1@@ -1,4 +1,11 @@
2 /* See LICENSE file for copyright and license details. */
3+/* ?man
4+su: run command with substitute user id
5+usage: su [-lp] [username]
6+
7+run a shell or command with another user id
8+*/
9+
10 #include <sys/types.h>
11
12 #include <errno.h>
13@@ -33,9 +40,11 @@ main(int argc, char *argv[])
14 uid_t uid;
15
16 ARGBEGIN {
17+ // ?man -l: list in long format
18 case 'l':
19 lflag = 1;
20 break;
21+ // ?man -p: preserve file attributes
22 case 'p':
23 pflag = 1;
24 break;
+44,
-14
1@@ -13,7 +13,7 @@ BUILD_LINUX = 1
2 BUILD_NET = 1
3 BUILD_XSI = 1
4 BUILD_PSEUDO = 1
5-BUILD_MAKE = 1
6+BUILD_DEV = 0
7
8 # posix tools
9 BUILD_POSIX_BASENAME = $(BUILD_POSIX)
10@@ -98,42 +98,39 @@ BUILD_POSIX_PAX = $(BUILD_POSIX)
11 BUILD_LINUX_BLKDISCARD = $(BUILD_LINUX)
12 BUILD_LINUX_CHVT = $(BUILD_LINUX)
13 BUILD_LINUX_CTRLALTDEL = $(BUILD_LINUX)
14-BUILD_LINUX_DMESG = $(BUILD_LINUX)
15 BUILD_LINUX_EJECT = $(BUILD_LINUX)
16-BUILD_LINUX_FALLOCATE = $(BUILD_LINUX)
17-BUILD_LINUX_FREE = $(BUILD_LINUX)
18 BUILD_LINUX_FREERAMDISK = $(BUILD_LINUX)
19 BUILD_LINUX_FSFREEZE = $(BUILD_LINUX)
20 BUILD_LINUX_HWCLOCK = $(BUILD_LINUX)
21 BUILD_LINUX_INSMOD = $(BUILD_LINUX)
22 BUILD_LINUX_LSMOD = $(BUILD_LINUX)
23+BUILD_LINUX_MODPROBE = $(BUILD_LINUX)
24+BUILD_LINUX_DEPMOD = $(BUILD_LINUX)
25 BUILD_LINUX_MKSWAP = $(BUILD_LINUX)
26 BUILD_LINUX_MOUNT = $(BUILD_LINUX)
27 BUILD_LINUX_MOUNTPOINT = $(BUILD_LINUX)
28-BUILD_LINUX_PIDOF = $(BUILD_LINUX)
29 BUILD_LINUX_PIVOT_ROOT = $(BUILD_LINUX)
30-BUILD_LINUX_PWDX = $(BUILD_LINUX)
31 BUILD_LINUX_READAHEAD = $(BUILD_LINUX)
32 BUILD_LINUX_RMMOD = $(BUILD_LINUX)
33 BUILD_LINUX_SWAPLABEL = $(BUILD_LINUX)
34 BUILD_LINUX_SWAPOFF = $(BUILD_LINUX)
35 BUILD_LINUX_SWAPON = $(BUILD_LINUX)
36 BUILD_LINUX_SWITCH_ROOT = $(BUILD_LINUX)
37+BUILD_LINUX_TUNCTL = $(BUILD_LINUX)
38 BUILD_LINUX_UMOUNT = $(BUILD_LINUX)
39 BUILD_LINUX_UNSHARE = $(BUILD_LINUX)
40-BUILD_LINUX_UPTIME = $(BUILD_LINUX)
41 BUILD_LINUX_VTALLOW = $(BUILD_LINUX)
42
43 # net tools
44 BUILD_NET_NETCAT = $(BUILD_NET)
45 BUILD_NET_TFTP = $(BUILD_NET)
46-BUILD_NET_TUNCTL = $(BUILD_NET)
47 BUILD_NET_WGET = $(BUILD_NET)
48 BUILD_NET_PING = $(BUILD_NET)
49 BUILD_NET_SDHCP = $(BUILD_NET)
50 BUILD_NET_IFCONFIG = $(BUILD_NET)
51 BUILD_NET_HOST = $(BUILD_NET)
52 BUILD_NET_HTTPD = $(BUILD_NET)
53+BUILD_NET_IP = $(BUILD_NET)
54
55 # xsi tools
56 BUILD_XSI_MKNOD = $(BUILD_XSI)
57@@ -180,9 +177,18 @@ BUILD_PSEUDO_XINSTALL = $(BUILD_PSEUDO)
58 BUILD_PSEUDO_YES = $(BUILD_PSEUDO)
59 BUILD_PSEUDO_BASE64 = $(BUILD_PSEUDO)
60 BUILD_PSEUDO_B3SUM = $(BUILD_PSEUDO)
61+BUILD_PSEUDO_DMESG = $(BUILD_PSEUDO)
62+BUILD_PSEUDO_FALLOCATE = $(BUILD_PSEUDO)
63+BUILD_PSEUDO_FREE = $(BUILD_PSEUDO)
64+BUILD_PSEUDO_PIDOF = $(BUILD_PSEUDO)
65+BUILD_PSEUDO_PWDX = $(BUILD_PSEUDO)
66+BUILD_PSEUDO_UPTIME = $(BUILD_PSEUDO)
67+BUILD_DEV_AR = $(BUILD_DEV)
68+BUILD_DEV_LD = $(BUILD_DEV)
69+BUILD_DEV_CC = $(BUILD_DEV)
70
71 # make tool
72-BUILD_MAKE_MAKE = $(BUILD_MAKE)
73+BUILD_POSIX_MAKE = $(BUILD_POSIX)
74
75 # feature gates for specific utilities
76 FEATURE_FIND_DELETE = 1
77@@ -209,7 +215,7 @@ FEATURE_STAT_FORMAT = 1
78 FEATURE_SORT_BIG = 1
79 FEATURE_SORT_STABLE = 1
80 FEATURE_OD_ENDIAN = 1
81-FEATURE_SH_HISTEDIT = 0
82+FEATURE_SH_HISTEDIT = 1
83 FEATURE_SH_LOCAL = 1
84 FEATURE_SH_LET = 1
85 FEATURE_SH_ULIMIT = 1
86@@ -218,6 +224,15 @@ FEATURE_SH_WORDEXP = 1
87 FEATURE_CAL_EXT = 1
88 FEATURE_SED_PRESERVE_NEWLINE = 1
89 FEATURE_LS_COLOR = 1
90+FEATURE_MODPROBE_SHOW_DEPENDS = 1
91+FEATURE_MODPROBE_BLACKLIST = 1
92+FEATURE_MODPROBE_SYSLOG = 1
93+FEATURE_MODPROBE_DIR_OVERRIDE = 1
94+FEATURE_DEPMOD_ALIAS = 1
95+FEATURE_DEPMOD_SYMBOLS = 1
96+FEATURE_USE_LIBRESSL = 0
97+FEATURE_USE_BEARSSL = 1
98+
99
100 CPPFLAGS =\
101 -Ishared\
102@@ -261,10 +276,25 @@ CPPFLAGS =\
103 -DFEATURE_SH_WORDEXP=$(FEATURE_SH_WORDEXP)\
104 -DFEATURE_CAL_EXT=$(FEATURE_CAL_EXT)\
105 -DFEATURE_SED_PRESERVE_NEWLINE=$(FEATURE_SED_PRESERVE_NEWLINE)\
106- -DFEATURE_LS_COLOR=$(FEATURE_LS_COLOR)
107+ -DFEATURE_LS_COLOR=$(FEATURE_LS_COLOR)\
108+ -DFEATURE_MODPROBE_SHOW_DEPENDS=$(FEATURE_MODPROBE_SHOW_DEPENDS)\
109+ -DFEATURE_MODPROBE_BLACKLIST=$(FEATURE_MODPROBE_BLACKLIST)\
110+ -DFEATURE_MODPROBE_SYSLOG=$(FEATURE_MODPROBE_SYSLOG)\
111+ -DFEATURE_MODPROBE_DIR_OVERRIDE=$(FEATURE_MODPROBE_DIR_OVERRIDE)\
112+ -DFEATURE_DEPMOD_ALIAS=$(FEATURE_DEPMOD_ALIAS)\
113+ -DFEATURE_DEPMOD_SYMBOLS=$(FEATURE_DEPMOD_SYMBOLS)\
114+ -DFEATURE_USE_LIBRESSL=$(FEATURE_USE_LIBRESSL)\
115+ -DFEATURE_USE_BEARSSL=$(FEATURE_USE_BEARSSL) $(CPPFLAGS_TLS)
116
117 CFLAGS = -std=c99 -Wall -Wextra -pedantic
118-LDFLAGS =
119+LDFLAGS = $(LDFLAGS_TLS)
120 LDLIBS_HISTEDIT_0 =
121-LDLIBS_HISTEDIT_1 = -ledit
122-LDLIBS = -lcrypt -lresolv $(LDLIBS_HISTEDIT_$(FEATURE_SH_HISTEDIT))
123+LDLIBS_HISTEDIT_1 =
124+LDLIBS_TLS_LIBRESSL_1 = -ltls
125+LDLIBS_TLS_BEARSSL_1 = externalRepos/BearSSL/build/libbearssl.a
126+LDLIBS_TLS = $(LDLIBS_TLS_LIBRESSL_$(FEATURE_USE_LIBRESSL)) $(LDLIBS_TLS_BEARSSL_$(FEATURE_USE_BEARSSL))
127+CPPFLAGS_TLS_BEARSSL_1 = -IexternalRepos/BearSSL/inc
128+LDFLAGS_TLS_BEARSSL_1 = -LexternalRepos/BearSSL/build
129+CPPFLAGS_TLS = $(CPPFLAGS_TLS_BEARSSL_$(FEATURE_USE_BEARSSL))
130+LDFLAGS_TLS = $(LDFLAGS_TLS_BEARSSL_$(FEATURE_USE_BEARSSL))
131+LDLIBS = -lcrypt -lresolv $(LDLIBS_HISTEDIT_$(FEATURE_SH_HISTEDIT)) $(LDLIBS_TLS)
+1,
-1
1@@ -189,7 +189,7 @@ compile_sh() {
2 die "mktokens failed"
3
4 for s in alias arith_yacc arith_yylex cd echo error eval exec \
5- expand histedit input jobs kill mail main memalloc \
6+ expand lineedit input jobs kill mail main memalloc \
7 miscbltin mystring options output parser printf redir \
8 show test trap var builtins nodes syntax; do
9 out="${BDIR}/sh_${s}.o"
+1135,
-0
1@@ -0,0 +1,1135 @@
2+package djot
3+
4+import (
5+ "bytes"
6+ "strconv"
7+ "strings"
8+)
9+
10+type Assembler struct {
11+ doc *Document
12+ stack []int32
13+
14+ pendingAttr []Attribute
15+ sectionStack []int16
16+
17+ references map[string]Reference
18+ lastClosedNode int32
19+
20+ footnoteLabels map[string]int
21+ usedFootnotes []string
22+ footnoteContent map[string]int32
23+
24+ usedIDs map[string]bool
25+ headingIDs map[int32]string
26+
27+ currentTableAlignments []uint32
28+}
29+
30+func (a *Assembler) Assemble(events []Event) {
31+ a.doc.Nodes = make([]Node, 0, len(events)/2+8)
32+ a.doc.Attributes = make([]Attribute, 0, 16)
33+ a.doc.References = make(map[string]Reference)
34+ a.doc.Extra = make([]byte, 0, 64)
35+ a.stack = make([]int32, 0, 16)
36+ a.pendingAttr = make([]Attribute, 0, 8)
37+ a.sectionStack = make([]int16, 0, 8)
38+ a.references = make(map[string]Reference)
39+ a.lastClosedNode = -1
40+ a.footnoteLabels = make(map[string]int)
41+ a.usedFootnotes = make([]string, 0, 8)
42+ a.footnoteContent = make(map[string]int32)
43+ a.usedIDs = make(map[string]bool)
44+ a.headingIDs = make(map[int32]string)
45+
46+ a.collectReferences(events)
47+ a.collectHeadingReferences(events)
48+
49+ rootIdx := a.addNode(NodeDoc, 0, 0)
50+ a.stack = append(a.stack, rootIdx)
51+
52+ type listTightState struct {
53+ nodeIdx int32
54+ blanklines bool
55+ }
56+ var listTightStack []listTightState
57+
58+ i := 0
59+ for i < len(events) {
60+ ev := events[i]
61+
62+ if len(listTightStack) > 0 {
63+ top := &listTightStack[len(listTightStack)-1]
64+ switch ev.Type {
65+ case EvBlankLine:
66+ top.blanklines = true
67+ case EvOpenList, EvCloseList:
68+ top.blanklines = false
69+ case EvOpenListItem, EvCloseListItem:
70+ default:
71+ if top.blanklines {
72+ a.doc.Nodes[top.nodeIdx].Data &= ^DataListTight
73+ }
74+ top.blanklines = false
75+ }
76+ }
77+
78+ switch ev.Type {
79+ case EvOpenAttributes:
80+ i = a.parseAttributes(events, i)
81+ case EvOpenInlineAttributes:
82+ i = a.parseAttributes(events, i)
83+ if a.lastClosedNode != -1 && len(a.pendingAttr) > 0 {
84+ if a.doc.Nodes[a.lastClosedNode].Type == NodeStr {
85+ nodeIdx := a.lastClosedNode
86+ content := a.doc.Source[a.doc.Nodes[nodeIdx].Start : a.doc.Nodes[nodeIdx].End+1]
87+ if len(content) == 0 || isSpace(content[len(content)-1]) {
88+ a.pendingAttr = a.pendingAttr[:0]
89+ } else {
90+ lastWordStart := -1
91+ for j := len(content) - 1; j >= 0; j-- {
92+ if isSpace(content[j]) {
93+ lastWordStart = j + 1
94+ break
95+ }
96+ }
97+ if lastWordStart == -1 {
98+ lastWordStart = 0
99+ }
100+
101+ if lastWordStart > 0 {
102+ origStart := a.doc.Nodes[nodeIdx].Start
103+ origEnd := a.doc.Nodes[nodeIdx].End
104+ newEnd := origStart + int32(lastWordStart) - 1
105+
106+ spanIdx := a.addNode(NodeSpan, origStart+int32(lastWordStart), origEnd)
107+ wordIdx := a.addNode(NodeStr, origStart+int32(lastWordStart), origEnd)
108+
109+ a.doc.Nodes[nodeIdx].End = newEnd
110+ a.doc.Nodes[spanIdx].Child = wordIdx
111+
112+ parentIdx := a.stack[len(a.stack)-1]
113+ a.linkToParent(parentIdx, spanIdx)
114+
115+ a.lastClosedNode = spanIdx
116+ } else if lastWordStart == 0 {
117+ origStart := a.doc.Nodes[nodeIdx].Start
118+ origEnd := a.doc.Nodes[nodeIdx].End
119+ childIdx := a.addNode(NodeStr, origStart, origEnd)
120+ a.doc.Nodes[nodeIdx].Type = NodeSpan
121+ a.doc.Nodes[nodeIdx].Child = childIdx
122+ }
123+ }
124+ }
125+ a.applyPendingAttr(a.lastClosedNode)
126+ } else {
127+ a.pendingAttr = a.pendingAttr[:0]
128+ }
129+ case EvOpenPara:
130+ idx := a.openNode(NodePara, ev.Start)
131+ a.applyPendingAttr(idx)
132+ case EvClosePara:
133+ a.lastClosedNode = a.closeNode(ev.End)
134+ case EvOpenHeading:
135+ level := int16(ev.End - ev.Start + 1)
136+ a.handleHeadingOpen(level, ev.Start)
137+ case EvCloseHeading:
138+ idx := a.closeNode(ev.End)
139+ a.generateID(idx, a.doc.Nodes[idx].Start)
140+ a.lastClosedNode = idx
141+ case EvOpenBlockQuote:
142+ idx := a.openNode(NodeBlockQuote, ev.Start)
143+ a.applyPendingAttr(idx)
144+ case EvCloseBlockQuote:
145+ a.lastClosedNode = a.closeNode(ev.End)
146+ case EvOpenList:
147+ marker := byte(ev.End)
148+ ntype := NodeBulletList
149+ if isOrderedMarker(marker) {
150+ ntype = NodeOrderedList
151+ } else if marker == ':' {
152+ ntype = NodeDefinitionList
153+ }
154+ idx := a.openNode(ntype, ev.Start)
155+ a.applyPendingAttr(idx)
156+ a.doc.Nodes[idx].Data |= DataListTight
157+ a.setStyleFromMarker(idx, marker)
158+ listTightStack = append(listTightStack, listTightState{nodeIdx: idx})
159+
160+ if ntype == NodeOrderedList && i+1 < len(events) && events[i+1].Type == EvNone {
161+ i++
162+ startNum := int(events[i].End)
163+ if startNum != 1 {
164+ a.doc.Nodes[idx].Data |= uint32(startNum) << 16
165+ }
166+ }
167+ case EvCloseList:
168+ a.lastClosedNode = a.closeNode(ev.Start)
169+ if len(listTightStack) > 0 {
170+ listTightStack = listTightStack[:len(listTightStack)-1]
171+ }
172+ case EvOpenListItem:
173+ pIdx := a.stack[len(a.stack)-1]
174+ ntype := NodeListItem
175+ if a.doc.Nodes[pIdx].Type == NodeDefinitionList {
176+ ntype = NodeDefinitionListItem
177+ }
178+ idx := a.openNode(ntype, ev.Start)
179+ a.applyPendingAttr(idx)
180+ if ev.End > 0 && ntype == NodeListItem {
181+ a.doc.Nodes[idx].Type = NodeTaskListItem
182+ if ev.End == 2 {
183+ a.doc.Nodes[idx].Data |= DataTaskChecked
184+ }
185+ pIdx := a.stack[len(a.stack)-2]
186+ if a.doc.Nodes[pIdx].Type == NodeBulletList {
187+ a.doc.Nodes[pIdx].Type = NodeTaskList
188+ }
189+ }
190+ case EvCloseListItem:
191+ idx := a.stack[len(a.stack)-1]
192+ if a.doc.Nodes[idx].Type == NodeDefinitionListItem {
193+ a.handleDefinitionListItem(idx)
194+ }
195+ a.lastClosedNode = a.closeNode(ev.End)
196+ case EvOpenCodeBlock, EvOpenRawBlock:
197+ ntype := NodeCodeBlock
198+ closeType := EvCloseCodeBlock
199+ if ev.Type == EvOpenRawBlock {
200+ ntype = NodeRawBlock
201+ closeType = EvCloseRawBlock
202+ }
203+ idx := a.openNode(ntype, ev.Start)
204+ a.applyPendingAttr(idx)
205+
206+ j := i + 1
207+ contentStart := int32(-1)
208+ foundStartMarker := false
209+ for j < len(events) {
210+ if !foundStartMarker && events[j].Type == EvNone {
211+ foundStartMarker = true
212+ } else if events[j].Type == EvStr {
213+ if contentStart == -1 {
214+ contentStart = events[j].Start
215+ }
216+ } else if events[j].Type == closeType {
217+ break
218+ }
219+ j++
220+ }
221+
222+ if contentStart != -1 {
223+ var buf bytes.Buffer
224+ for k := i + 1; k < j; k++ {
225+ if events[k].Type == EvStr {
226+ buf.Write(a.doc.Source[events[k].Start : events[k].End+1])
227+ }
228+ }
229+ extraStart := int32(len(a.doc.Extra))
230+ a.doc.Extra = append(a.doc.Extra, buf.Bytes()...)
231+ a.doc.Nodes[idx].Start = ^extraStart
232+ a.doc.Nodes[idx].End = int32(len(a.doc.Extra)) - 1
233+ } else {
234+ a.doc.Nodes[idx].Start = -1
235+ a.doc.Nodes[idx].End = -1
236+ }
237+
238+ if ntype == NodeRawBlock && idx != -1 {
239+ node := &a.doc.Nodes[idx]
240+ if node.Attr != -1 {
241+ for k := uint16(0); k < node.AttrCount; k++ {
242+ attr := &a.doc.Attributes[node.Attr+int32(k)]
243+ if attr.KeyStart == -2 {
244+ val := string(a.getAttrVal(*attr))
245+ if strings.HasPrefix(val, "=") {
246+ attr.KeyStart = -3
247+ }
248+ }
249+ }
250+ }
251+ }
252+ a.closeNode(ev.End)
253+ i = j
254+ case EvOpenDiv:
255+ idx := a.openNode(NodeDiv, ev.Start)
256+ a.applyPendingAttr(idx)
257+ case EvCloseDiv:
258+ a.lastClosedNode = a.closeNode(ev.End)
259+ case EvOpenTable:
260+ idx := a.openNode(NodeTable, ev.Start)
261+ a.applyPendingAttr(idx)
262+ case EvCloseTable:
263+ a.lastClosedNode = a.closeNode(ev.End)
264+ case EvOpenCaption:
265+ idx := int32(-1)
266+ if a.lastClosedNode != -1 && a.doc.Nodes[a.lastClosedNode].Type == NodeTable {
267+ tableIdx := a.lastClosedNode
268+ captionIdx := a.addNode(NodeCaption, ev.Start, -1)
269+ a.linkToParent(tableIdx, captionIdx)
270+ a.stack = append(a.stack, captionIdx)
271+ a.lastClosedNode = -1
272+ idx = captionIdx
273+ } else {
274+ idx = a.openNode(NodeCaption, ev.Start)
275+ }
276+ a.applyPendingAttr(idx)
277+ case EvCloseCaption:
278+ a.lastClosedNode = a.closeNode(ev.End)
279+ case EvOpenRow:
280+ a.openNode(NodeRow, ev.Start)
281+ case EvCloseRow:
282+ a.lastClosedNode = a.closeNode(ev.End)
283+ case EvOpenCell:
284+ idx := a.openNode(NodeCell, ev.Start)
285+ a.applyPendingAttr(idx)
286+
287+ col := 0
288+ curr := a.doc.Nodes[a.stack[len(a.stack)-2]].Child
289+ for curr != -1 && curr != idx {
290+ curr = a.doc.Nodes[curr].Next
291+ col++
292+ }
293+ if col < len(a.currentTableAlignments) {
294+ a.doc.Nodes[idx].Data &= ^(DataAlignLeft | DataAlignCenter | DataAlignRight)
295+ a.doc.Nodes[idx].Data |= a.currentTableAlignments[col]
296+ }
297+ case EvCloseCell:
298+ a.lastClosedNode = a.closeNode(ev.End)
299+ case EvOpenFootnote:
300+ j := i + 1
301+ label := ""
302+ if j < len(events) && events[j].Type == EvStr {
303+ content := a.doc.Source[events[j].Start : events[j].End+1]
304+ if len(content) > 0 && content[0] == '[' && content[1] == '^' {
305+ closeIdx := bytes.IndexByte(content, ']')
306+ if closeIdx != -1 {
307+ label = normalizeLabel(string(content[2:closeIdx]))
308+ }
309+ }
310+ }
311+ idx := a.openNode(NodeFootnote, ev.Start)
312+ if label != "" {
313+ a.footnoteContent[label] = idx
314+ }
315+ a.applyPendingAttr(idx)
316+ case EvCloseFootnote:
317+ a.lastClosedNode = a.closeNode(ev.End)
318+ case EvOpenReferenceDefinition:
319+ idx := a.openNode(NodeReference, ev.Start)
320+ a.applyPendingAttr(idx)
321+ case EvCloseReferenceDefinition:
322+ a.closeNode(ev.End)
323+ case EvOpenLinkText:
324+ idx := a.openNode(NodeLink, ev.Start)
325+ a.applyPendingAttr(idx)
326+ case EvCloseLinkText:
327+ a.lastClosedNode = a.closeNode(ev.End)
328+ case EvOpenImageText:
329+ idx := a.openNode(NodeImage, ev.Start)
330+ a.applyPendingAttr(idx)
331+ case EvCloseImageText:
332+ a.lastClosedNode = a.closeNode(ev.End)
333+ case EvOpenSpan:
334+ idx := a.openNode(NodeSpan, ev.Start)
335+ a.applyPendingAttr(idx)
336+ case EvCloseSpan:
337+ a.lastClosedNode = a.closeNode(ev.End)
338+ case EvOpenDoubleQuoted:
339+ a.openNode(NodeDoubleQuoted, ev.Start)
340+ case EvCloseDoubleQuoted:
341+ a.lastClosedNode = a.closeNode(ev.End)
342+ case EvOpenSingleQuoted:
343+ a.openNode(NodeSingleQuoted, ev.Start)
344+ case EvCloseSingleQuoted:
345+ a.lastClosedNode = a.closeNode(ev.End)
346+ case EvOpenMark:
347+ idx := a.openNode(NodeMark, ev.Start)
348+ a.applyPendingAttr(idx)
349+ case EvCloseMark:
350+ a.lastClosedNode = a.closeNode(ev.End)
351+ case EvOpenInsert:
352+ idx := a.openNode(NodeInsert, ev.Start)
353+ a.applyPendingAttr(idx)
354+ case EvCloseInsert:
355+ a.lastClosedNode = a.closeNode(ev.End)
356+ case EvOpenDelete:
357+ idx := a.openNode(NodeDelete, ev.Start)
358+ a.applyPendingAttr(idx)
359+ case EvCloseDelete:
360+ a.lastClosedNode = a.closeNode(ev.End)
361+ case EvOpenDestination:
362+ a.addLeafNode(NodeUrl, ev.Start, ev.End)
363+ case EvUrl, EvEmail:
364+ idx := a.openNode(NodeLink, ev.Start)
365+ a.applyPendingAttr(idx)
366+
367+ destType := NodeUrl
368+ if ev.Type == EvEmail {
369+ destType = NodeEmail
370+ }
371+ destIdx := a.addNode(destType, ev.Start, ev.End)
372+ a.linkToParent(idx, destIdx)
373+
374+ strIdx := a.addNode(NodeStr, ev.Start, ev.End)
375+ a.linkToParent(idx, strIdx)
376+
377+ a.lastClosedNode = a.closeNode(ev.End)
378+ case EvOpenReference:
379+ label := string(a.doc.Source[ev.Start : ev.End+1])
380+ if label == "" {
381+ var buf strings.Builder
382+ a.collectPlainText(a.stack[len(a.stack)-1], &buf)
383+ label = buf.String()
384+ }
385+ label = normalizeLabel(label)
386+ if ref, ok := a.references[label]; ok {
387+ a.addLeafNode(NodeUrl, ref.DestStart, ref.DestEnd)
388+ if len(ref.Attributes) > 0 {
389+ parentIdx := a.stack[len(a.stack)-1]
390+ a.pendingAttr = append(a.pendingAttr, ref.Attributes...)
391+ a.applyPendingAttr(parentIdx)
392+ }
393+ }
394+ case EvFootnoteReference:
395+ label := string(a.doc.Source[ev.Start+2 : ev.End])
396+ num, ok := a.footnoteLabels[label]
397+ if !ok {
398+ num = len(a.usedFootnotes) + 1
399+ a.footnoteLabels[label] = num
400+ a.usedFootnotes = append(a.usedFootnotes, label)
401+ }
402+ idx := a.addNode(NodeFootnoteReference, ev.Start, ev.End)
403+ a.doc.Nodes[idx].Data = uint32(num)
404+ parentIdx := a.stack[len(a.stack)-1]
405+ a.linkToParent(parentIdx, idx)
406+ case EvSmartPunctuation:
407+ a.addLeafNode(NodeSmartPunctuation, ev.Start, ev.End)
408+ case EvInlineMath:
409+ a.addLeafNode(NodeInlineMath, ev.Start, ev.End)
410+ case EvDisplayMath:
411+ a.addLeafNode(NodeDisplayMath, ev.Start, ev.End)
412+ case EvRawInline:
413+ idx := a.addNode(NodeRawInline, ev.Start, ev.End)
414+ a.linkToParent(a.stack[len(a.stack)-1], idx)
415+ a.lastClosedNode = idx
416+ i++
417+ for i < len(events) && events[i].Type != EvRawInline {
418+ i++
419+ }
420+ case EvOpenVerbatim:
421+ a.openNode(NodeVerbatim, ev.Start)
422+ case EvCloseVerbatim:
423+ a.lastClosedNode = a.closeNode(ev.End)
424+ case EvSoftBreak:
425+ a.addLeafNode(NodeSoftBreak, ev.Start, ev.End)
426+ case EvHardBreak:
427+ a.addLeafNode(NodeHardBreak, ev.Start, ev.End)
428+ case EvNonBreakingSpace:
429+ a.addLeafNode(NodeNonBreakingSpace, ev.Start, ev.End)
430+ case EvThematicBreak:
431+ idx := a.addNode(NodeThematicBreak, ev.Start, ev.End)
432+ a.linkToParent(a.stack[len(a.stack)-1], idx)
433+ a.applyPendingAttr(idx)
434+ case EvBlankLine:
435+ case EvStr:
436+ skipThisStr := false
437+ if i > 0 && events[i-1].Type == EvOpenFootnote {
438+ content := a.doc.Source[ev.Start : ev.End+1]
439+ if len(content) > 0 && content[0] == '[' && len(content) > 1 && content[1] == '^' {
440+ if bytes.Contains(content, []byte("]:")) {
441+ skipThisStr = true
442+ }
443+ }
444+ }
445+ if !skipThisStr {
446+ tipIdx := a.stack[len(a.stack)-1]
447+ if a.doc.Nodes[tipIdx].Type == NodeTable {
448+ a.handleTableSeparator(ev.Start, ev.End)
449+ } else {
450+ a.addLeafNode(NodeStr, ev.Start, ev.End)
451+ }
452+ }
453+ case EvSymb:
454+ a.addLeafNode(NodeSymb, ev.Start, ev.End)
455+ case EvOpenEmph:
456+ idx := a.openNode(NodeEmph, ev.Start)
457+ a.applyPendingAttr(idx)
458+ case EvCloseEmph:
459+ a.lastClosedNode = a.closeNode(ev.End)
460+ case EvOpenStrong:
461+ idx := a.openNode(NodeStrong, ev.Start)
462+ a.applyPendingAttr(idx)
463+ case EvCloseStrong:
464+ a.lastClosedNode = a.closeNode(ev.End)
465+ case EvEscape:
466+ a.addLeafNode(NodeStr, ev.Start+1, ev.End)
467+ case EvOpenSuperscript:
468+ a.openNode(NodeSuperscript, ev.Start)
469+ case EvCloseSuperscript:
470+ a.lastClosedNode = a.closeNode(ev.End)
471+ case EvOpenSubscript:
472+ a.openNode(NodeSubscript, ev.Start)
473+ case EvCloseSubscript:
474+ a.lastClosedNode = a.closeNode(ev.End)
475+ }
476+ i++
477+ }
478+
479+ for _, label := range a.usedFootnotes {
480+ if contentIdx, ok := a.footnoteContent[label]; ok {
481+ a.linkToParent(rootIdx, contentIdx)
482+ }
483+ }
484+
485+ a.doc.UsedFootnotes = a.usedFootnotes
486+ a.doc.FootnoteContent = a.footnoteContent
487+ a.doc.References = a.references
488+
489+ if len(a.doc.Source) > 0 {
490+ a.doc.Nodes[rootIdx].End = int32(len(a.doc.Source) - 1)
491+ }
492+}
493+
494+func normalizeLabel(label string) string {
495+ return strings.Join(strings.Fields(label), " ")
496+}
497+
498+func unescapeBytes(b []byte) []byte {
499+ var res []byte
500+ escaped := false
501+ for _, ch := range b {
502+ if escaped {
503+ res = append(res, ch)
504+ escaped = false
505+ } else if ch == '\\' {
506+ escaped = true
507+ } else {
508+ res = append(res, ch)
509+ }
510+ }
511+ return res
512+}
513+
514+func (a *Assembler) collectReferences(events []Event) {
515+ var pendingAttr []Attribute
516+ for i := 0; i < len(events); i++ {
517+ ev := events[i]
518+ switch ev.Type {
519+ case EvOpenAttributes, EvOpenInlineAttributes:
520+ i = a.parseAttributesTo(events, i, &pendingAttr)
521+ continue
522+ case EvOpenPara, EvOpenBlockQuote, EvOpenList, EvOpenListItem, EvOpenCodeBlock, EvOpenDiv, EvOpenTable, EvOpenCaption, EvOpenRow, EvOpenCell, EvOpenHeading:
523+ pendingAttr = pendingAttr[:0]
524+ case EvOpenReferenceDefinition:
525+ var content string
526+ j := i + 1
527+ for j < len(events) && events[j].Type != EvCloseReferenceDefinition {
528+ if events[j].Type == EvStr || events[j].Type == EvText {
529+ content = string(a.doc.Source[events[j].Start : events[j].End+1])
530+ }
531+ j++
532+ }
533+
534+ if content == "" {
535+ lineStart := events[i].Start
536+ lineEnd := lineStart
537+ for lineEnd < int32(len(a.doc.Source)) && a.doc.Source[lineEnd] != '\n' {
538+ lineEnd++
539+ }
540+ if lineEnd > lineStart {
541+ content = string(a.doc.Source[lineStart:lineEnd])
542+ }
543+ }
544+
545+ if len(content) > 0 && content[0] == '[' {
546+ closeIdx := strings.IndexByte(content, ']')
547+ if closeIdx != -1 && closeIdx+1 < len(content) && content[closeIdx+1] == ':' {
548+ label := normalizeLabel(content[1:closeIdx])
549+ value := content[closeIdx+2:]
550+
551+ value = strings.ReplaceAll(value, "\r\n", "\n")
552+ value = strings.ReplaceAll(value, "\r", "\n")
553+ parts := strings.Split(value, "\n")
554+ var cleanedParts []string
555+ for _, p := range parts {
556+ cleaned := strings.TrimSpace(p)
557+ if len(cleaned) > 0 {
558+ cleanedParts = append(cleanedParts, cleaned)
559+ }
560+ }
561+ cleanValue := strings.Join(cleanedParts, "")
562+ valStart := int32(len(a.doc.Extra))
563+ a.doc.Extra = append(a.doc.Extra, []byte(cleanValue)...)
564+ valEnd := int32(len(a.doc.Extra)) - 1
565+
566+ refAttrs := make([]Attribute, len(pendingAttr))
567+ copy(refAttrs, pendingAttr)
568+
569+ a.references[label] = Reference{
570+ DestStart: ^valStart,
571+ DestEnd: valEnd,
572+ Attributes: refAttrs,
573+ }
574+ pendingAttr = pendingAttr[:0]
575+ }
576+ }
577+ i = j
578+ case EvOpenFootnote:
579+ j := i + 1
580+ if j < len(events) && events[j].Type == EvStr {
581+ content := a.doc.Source[events[j].Start : events[j].End+1]
582+ if len(content) > 0 && content[0] == '[' && content[1] == '^' {
583+ closeIdx := bytes.IndexByte(content, ']')
584+ if closeIdx != -1 && closeIdx+1 < len(content) && content[closeIdx+1] == ':' {
585+ label := normalizeLabel(string(content[2:closeIdx]))
586+ a.footnoteContent[label] = -2
587+ }
588+ }
589+ }
590+ pendingAttr = pendingAttr[:0]
591+ }
592+ }
593+}
594+
595+func (a *Assembler) parseAttributesTo(events []Event, startIdx int, target *[]Attribute) int {
596+ i := startIdx + 1
597+ for i < len(events) && events[i].Type != EvCloseAttributes {
598+ ev := events[i]
599+ switch ev.Type {
600+ case EvAttrIdMarker:
601+ *target = append(*target, Attribute{
602+ KeyStart: -1,
603+ ValStart: ev.Start,
604+ ValEnd: ev.End,
605+ })
606+ case EvAttrClassMarker:
607+ *target = append(*target, Attribute{
608+ KeyStart: -2,
609+ ValStart: ev.Start,
610+ ValEnd: ev.End,
611+ })
612+ case EvAttrKey:
613+ keyEv := ev
614+ if keyEv.Start < int32(len(a.doc.Source)) && a.doc.Source[keyEv.Start] == '=' {
615+ *target = append(*target, Attribute{
616+ KeyStart: -3,
617+ ValStart: keyEv.Start + 1,
618+ ValEnd: keyEv.End,
619+ })
620+ } else {
621+ for i+1 < len(events) && events[i+1].Type != EvCloseAttributes {
622+ i++
623+ if events[i].Type == EvAttrValue {
624+ *target = append(*target, Attribute{
625+ KeyStart: keyEv.Start,
626+ KeyEnd: keyEv.End,
627+ ValStart: events[i].Start,
628+ ValEnd: events[i].End,
629+ })
630+ break
631+ }
632+ }
633+ }
634+ }
635+ i++
636+ }
637+ return i
638+}
639+
640+func (a *Assembler) collectHeadingReferences(events []Event) {
641+ tempAs := &Assembler{doc: a.doc, references: a.references, usedIDs: make(map[string]bool)}
642+ i := 0
643+ for i < len(events) {
644+ ev := events[i]
645+ switch ev.Type {
646+ case EvOpenAttributes:
647+ i = tempAs.parseAttributes(events, i)
648+ continue
649+ case EvOpenPara, EvOpenBlockQuote, EvOpenList, EvOpenListItem, EvOpenCodeBlock, EvOpenDiv, EvOpenTable, EvOpenCaption, EvOpenRow, EvOpenCell:
650+ if len(tempAs.pendingAttr) > 0 {
651+ for _, attr := range tempAs.pendingAttr {
652+ if attr.KeyStart == -1 {
653+ var id string
654+ if attr.ValStart < 0 {
655+ id = string(a.doc.Extra[^attr.ValStart : attr.ValEnd+1])
656+ } else {
657+ id = string(a.doc.Source[attr.ValStart : attr.ValEnd+1])
658+ }
659+ tempAs.usedIDs[id] = true
660+ break
661+ }
662+ }
663+ tempAs.pendingAttr = tempAs.pendingAttr[:0]
664+ }
665+ case EvOpenHeading:
666+ headingStart := ev.Start
667+ j := i + 1
668+ var buf strings.Builder
669+ for j < len(events) && events[j].Type != EvCloseHeading {
670+ if events[j].Type == EvStr {
671+ buf.Write(a.doc.Source[events[j].Start : events[j].End+1])
672+ } else if events[j].Type == EvSoftBreak {
673+ buf.WriteByte('\n')
674+ }
675+ j++
676+ }
677+ text := buf.String()
678+ label := normalizeLabel(text)
679+
680+ var manualID string
681+ if len(tempAs.pendingAttr) > 0 {
682+ for _, attr := range tempAs.pendingAttr {
683+ if attr.KeyStart == -1 {
684+ if attr.ValStart < 0 {
685+ manualID = string(a.doc.Extra[^attr.ValStart : attr.ValEnd+1])
686+ } else {
687+ manualID = string(a.doc.Source[attr.ValStart : attr.ValEnd+1])
688+ }
689+ tempAs.usedIDs[manualID] = true
690+ break
691+ }
692+ }
693+ tempAs.pendingAttr = tempAs.pendingAttr[:0]
694+ }
695+
696+ ident := manualID
697+ if ident == "" {
698+ base := a.slugify(text)
699+ ident = base
700+ if ident == "" {
701+ ident = "s"
702+ }
703+ if tempAs.usedIDs[ident] || base == "" {
704+ k := 1
705+ if base == "" {
706+ ident = "s-1"
707+ } else {
708+ ident = base + "-1"
709+ }
710+ for tempAs.usedIDs[ident] {
711+ k++
712+ ident = base + "-" + strconv.Itoa(k)
713+ if base == "" {
714+ ident = "s-" + strconv.Itoa(k)
715+ }
716+ }
717+ }
718+ tempAs.usedIDs[ident] = true
719+ }
720+
721+ a.headingIDs[headingStart] = ident
722+
723+ if _, ok := a.references[label]; !ok {
724+ start := int32(len(a.doc.Extra))
725+ a.doc.Extra = append(a.doc.Extra, '#')
726+ a.doc.Extra = append(a.doc.Extra, []byte(ident)...)
727+ a.references[label] = Reference{
728+ DestStart: ^start,
729+ DestEnd: int32(len(a.doc.Extra)) - 1,
730+ }
731+ }
732+ i = j
733+ }
734+ i++
735+ }
736+}
737+
738+func (a *Assembler) generateID(idx int32, start int32) {
739+ node := &a.doc.Nodes[idx]
740+ if node.Data&DataNoAutoID != 0 {
741+ return
742+ }
743+
744+ parentIdx := a.stack[len(a.stack)-1]
745+ targetIdx := idx
746+ if a.doc.Nodes[parentIdx].Type == NodeSection {
747+ targetIdx = parentIdx
748+ }
749+
750+ if a.doc.Nodes[targetIdx].Attr != -1 {
751+ for j := uint16(0); j < a.doc.Nodes[targetIdx].AttrCount; j++ {
752+ attr := a.doc.Attributes[a.doc.Nodes[targetIdx].Attr+int32(j)]
753+ if attr.KeyStart == -1 {
754+ return
755+ }
756+ }
757+ }
758+
759+ ident, ok := a.headingIDs[start]
760+ if !ok {
761+ return
762+ }
763+
764+ valStart := int32(len(a.doc.Extra))
765+ a.doc.Extra = append(a.doc.Extra, []byte(ident)...)
766+ valEnd := int32(len(a.doc.Extra)) - 1
767+
768+ attr := Attribute{
769+ KeyStart: -1,
770+ ValStart: ^valStart,
771+ ValEnd: valEnd,
772+ }
773+
774+ a.pendingAttr = append(a.pendingAttr, attr)
775+ a.applyPendingAttr(targetIdx)
776+}
777+
778+func (a *Assembler) collectPlainText(idx int32, buf *strings.Builder) {
779+ curr := a.doc.Nodes[idx].Child
780+ for curr != -1 {
781+ node := a.doc.Nodes[curr]
782+ if node.Type == NodeStr {
783+ buf.Write(a.doc.Source[node.Start : node.End+1])
784+ } else if node.Type == NodeSoftBreak {
785+ buf.WriteByte('\n')
786+ } else {
787+ a.collectPlainText(curr, buf)
788+ }
789+ curr = a.doc.Nodes[curr].Next
790+ }
791+}
792+
793+func (a *Assembler) slugify(text string) string {
794+ var res []rune
795+ lastWasSpec := false
796+ for _, r := range text {
797+ isSpec := strings.ContainsRune("][~!@#$%^&*(){}`,.<>\\|=+/? \t\r\n", r)
798+ if isSpec {
799+ if !lastWasSpec {
800+ res = append(res, ' ')
801+ }
802+ lastWasSpec = true
803+ } else {
804+ res = append(res, r)
805+ lastWasSpec = false
806+ }
807+ }
808+ s := strings.TrimSpace(string(res))
809+ return strings.ReplaceAll(s, " ", "-")
810+}
811+
812+func (a *Assembler) handleTableSeparator(start, end int32) {
813+ parentIdx := a.stack[len(a.stack)-1]
814+ curr := a.doc.Nodes[parentIdx].Child
815+ var lastRow int32 = -1
816+ for curr != -1 {
817+ if a.doc.Nodes[curr].Type == NodeRow {
818+ lastRow = curr
819+ }
820+ curr = a.doc.Nodes[curr].Next
821+ }
822+
823+ line := a.doc.Source[start : end+1]
824+ parts := bytes.Split(line, []byte("|"))
825+
826+ newAlignments := make([]uint32, 0)
827+ for _, part := range parts {
828+ part = bytes.TrimSpace(part)
829+ if len(part) == 0 {
830+ continue
831+ }
832+
833+ align := uint32(0)
834+ left := part[0] == ':'
835+ right := part[len(part)-1] == ':'
836+
837+ if left && right {
838+ align = DataAlignCenter
839+ } else if left {
840+ align = DataAlignLeft
841+ } else if right {
842+ align = DataAlignRight
843+ }
844+ newAlignments = append(newAlignments, align)
845+ }
846+ a.currentTableAlignments = newAlignments
847+
848+ if lastRow != -1 {
849+ cell := a.doc.Nodes[lastRow].Child
850+ col := 0
851+ for cell != -1 {
852+ a.doc.Nodes[cell].Data |= DataCellHeader
853+ if col < len(a.currentTableAlignments) {
854+ a.doc.Nodes[cell].Data &= ^(DataAlignLeft | DataAlignCenter | DataAlignRight)
855+ a.doc.Nodes[cell].Data |= a.currentTableAlignments[col]
856+ }
857+ cell = a.doc.Nodes[cell].Next
858+ col++
859+ }
860+ }
861+}
862+
863+func (a *Assembler) handleHeadingOpen(level int16, start int32) {
864+ for len(a.sectionStack) > 0 && a.sectionStack[len(a.sectionStack)-1] >= level {
865+ a.stack = a.stack[:len(a.stack)-1]
866+ a.sectionStack = a.sectionStack[:len(a.sectionStack)-1]
867+ }
868+
869+ parentIdx := a.stack[len(a.stack)-1]
870+ parentType := a.doc.Nodes[parentIdx].Type
871+
872+ if parentType == NodeDoc || parentType == NodeSection {
873+ sIdx := a.openNode(NodeSection, start)
874+ a.sectionStack = append(a.sectionStack, level)
875+ a.applyPendingAttr(sIdx)
876+ }
877+
878+ hIdx := a.openNode(NodeHeading, start)
879+ a.doc.Nodes[hIdx].Level = level
880+ if len(a.pendingAttr) > 0 {
881+ a.doc.Nodes[hIdx].Data |= DataNoAutoID
882+ a.applyPendingAttr(hIdx)
883+ }
884+}
885+
886+func (a *Assembler) parseAttributes(events []Event, startIdx int) int {
887+ i := startIdx + 1
888+ for i < len(events) && events[i].Type != EvCloseAttributes {
889+ ev := events[i]
890+ switch ev.Type {
891+ case EvAttrIdMarker:
892+ a.pendingAttr = append(a.pendingAttr, Attribute{
893+ KeyStart: -1,
894+ ValStart: ev.Start,
895+ ValEnd: ev.End,
896+ })
897+ case EvAttrClassMarker:
898+ a.pendingAttr = append(a.pendingAttr, Attribute{
899+ KeyStart: -2,
900+ ValStart: ev.Start,
901+ ValEnd: ev.End,
902+ })
903+ case EvAttrKey:
904+ keyEv := ev
905+ if keyEv.Start < int32(len(a.doc.Source)) && a.doc.Source[keyEv.Start] == '=' {
906+ a.pendingAttr = append(a.pendingAttr, Attribute{
907+ KeyStart: -3,
908+ ValStart: keyEv.Start + 1,
909+ ValEnd: keyEv.End,
910+ })
911+ } else {
912+ for i+1 < len(events) && events[i+1].Type != EvCloseAttributes {
913+ i++
914+ if events[i].Type == EvAttrValue {
915+ rawVal := a.doc.Source[events[i].Start : events[i].End+1]
916+ decodedVal := unescapeBytes(rawVal)
917+ valStart := int32(len(a.doc.Extra))
918+ a.doc.Extra = append(a.doc.Extra, decodedVal...)
919+ valEnd := int32(len(a.doc.Extra)) - 1
920+ a.pendingAttr = append(a.pendingAttr, Attribute{
921+ KeyStart: keyEv.Start,
922+ KeyEnd: keyEv.End,
923+ ValStart: ^valStart,
924+ ValEnd: valEnd,
925+ })
926+ break
927+ }
928+ }
929+ }
930+ }
931+ i++
932+ }
933+ return i
934+}
935+
936+func (a *Assembler) getAttrVal(attr Attribute) []byte {
937+ if attr.ValStart < 0 {
938+ return a.doc.Extra[^attr.ValStart : attr.ValEnd+1]
939+ }
940+ return a.doc.Source[attr.ValStart : attr.ValEnd+1]
941+}
942+
943+func (a *Assembler) applyPendingAttr(idx int32) {
944+ if len(a.pendingAttr) == 0 {
945+ return
946+ }
947+
948+ node := &a.doc.Nodes[idx]
949+ var attrs []Attribute
950+ if node.Attr != -1 {
951+ for i := uint16(0); i < node.AttrCount; i++ {
952+ attrs = append(attrs, a.doc.Attributes[node.Attr+int32(i)])
953+ }
954+ }
955+
956+ for _, newAttr := range a.pendingAttr {
957+ if newAttr.KeyStart == -1 {
958+ id := string(a.getAttrVal(newAttr))
959+ a.usedIDs[id] = true
960+ }
961+
962+ foundIdx := -1
963+ for i := range attrs {
964+ if newAttr.KeyStart == -1 && attrs[i].KeyStart == -1 {
965+ foundIdx = i
966+ break
967+ } else if newAttr.KeyStart == -2 && attrs[i].KeyStart == -2 {
968+ foundIdx = i
969+ break
970+ } else if newAttr.KeyStart >= 0 && attrs[i].KeyStart >= 0 {
971+ oldKey := a.doc.Source[attrs[i].KeyStart : attrs[i].KeyEnd+1]
972+ newKey := a.doc.Source[newAttr.KeyStart : newAttr.KeyEnd+1]
973+ if bytes.Equal(oldKey, newKey) {
974+ foundIdx = i
975+ break
976+ }
977+ }
978+ }
979+
980+ if newAttr.KeyStart == -2 && foundIdx != -1 {
981+ oldVal := a.getAttrVal(attrs[foundIdx])
982+ newVal := a.getAttrVal(newAttr)
983+
984+ start := int32(len(a.doc.Extra))
985+ a.doc.Extra = append(a.doc.Extra, oldVal...)
986+ a.doc.Extra = append(a.doc.Extra, ' ')
987+ a.doc.Extra = append(a.doc.Extra, newVal...)
988+ end := int32(len(a.doc.Extra)) - 1
989+
990+ attrs[foundIdx].ValStart = ^start
991+ attrs[foundIdx].ValEnd = end
992+ } else if foundIdx != -1 {
993+ attrs[foundIdx] = newAttr
994+ } else {
995+ attrs = append(attrs, newAttr)
996+ }
997+ }
998+
999+ node.Attr = int32(len(a.doc.Attributes))
1000+ node.AttrCount = uint16(len(attrs))
1001+ a.doc.Attributes = append(a.doc.Attributes, attrs...)
1002+ a.pendingAttr = a.pendingAttr[:0]
1003+}
1004+
1005+func (a *Assembler) handleDefinitionListItem(idx int32) {
1006+ firstIdx := a.doc.Nodes[idx].Child
1007+ if firstIdx != -1 && a.doc.Nodes[firstIdx].Type == NodePara {
1008+ termIdx := firstIdx
1009+ a.doc.Nodes[termIdx].Type = NodeTerm
1010+
1011+ defIdx := a.addNode(NodeDefinition, a.doc.Nodes[termIdx].End, a.doc.Nodes[idx].End)
1012+ a.doc.Nodes[defIdx].Child = a.doc.Nodes[termIdx].Next
1013+ a.doc.Nodes[termIdx].Next = defIdx
1014+ } else {
1015+ termIdx := a.addNode(NodeTerm, a.doc.Nodes[idx].Start, a.doc.Nodes[idx].Start)
1016+ defIdx := a.addNode(NodeDefinition, a.doc.Nodes[idx].Start, a.doc.Nodes[idx].End)
1017+ a.doc.Nodes[defIdx].Child = firstIdx
1018+ a.doc.Nodes[idx].Child = termIdx
1019+ a.doc.Nodes[termIdx].Next = defIdx
1020+ }
1021+}
1022+
1023+func (a *Assembler) isInline(t NodeType) bool {
1024+ switch t {
1025+ case NodeStr, NodeEmph, NodeStrong, NodeLink, NodeImage, NodeSpan, NodeVerbatim, NodeSubscript, NodeSuperscript, NodeInsert, NodeDelete, NodeMark, NodeDoubleQuoted, NodeSingleQuoted, NodeSmartPunctuation, NodeSoftBreak, NodeHardBreak, NodeInlineMath, NodeDisplayMath, NodeSymb, NodeUrl, NodeEmail, NodeFootnoteReference:
1026+ return true
1027+ }
1028+ return false
1029+}
1030+
1031+func (a *Assembler) addNode(ntype NodeType, start, end int32) int32 {
1032+ idx := int32(len(a.doc.Nodes))
1033+ a.doc.Nodes = append(a.doc.Nodes, Node{
1034+ Type: ntype,
1035+ Start: start,
1036+ End: end,
1037+ Child: -1,
1038+ Next: -1,
1039+ Attr: -1,
1040+ })
1041+ return idx
1042+}
1043+
1044+func (a *Assembler) linkToParent(parentIdx, nodeIdx int32) {
1045+ if parentIdx == -1 || nodeIdx == -1 || parentIdx == nodeIdx {
1046+ return
1047+ }
1048+ parent := &a.doc.Nodes[parentIdx]
1049+ if parent.Child == -1 {
1050+ parent.Child = nodeIdx
1051+ } else {
1052+ curr := parent.Child
1053+ if curr == nodeIdx {
1054+ return
1055+ }
1056+ for a.doc.Nodes[curr].Next != -1 {
1057+ if a.doc.Nodes[curr].Next == nodeIdx {
1058+ return
1059+ }
1060+ curr = a.doc.Nodes[curr].Next
1061+ }
1062+ a.doc.Nodes[curr].Next = nodeIdx
1063+ }
1064+}
1065+
1066+func (a *Assembler) openNode(ntype NodeType, start int32) int32 {
1067+ var parentIdx int32 = -1
1068+ if len(a.stack) > 0 {
1069+ parentIdx = a.stack[len(a.stack)-1]
1070+ }
1071+ nodeIdx := a.addNode(ntype, start, -1)
1072+ a.linkToParent(parentIdx, nodeIdx)
1073+ a.stack = append(a.stack, nodeIdx)
1074+ a.lastClosedNode = -1
1075+
1076+ if ntype == NodeTable {
1077+ a.currentTableAlignments = nil
1078+ }
1079+ return nodeIdx
1080+}
1081+
1082+func (a *Assembler) closeNode(end int32) int32 {
1083+ if len(a.stack) <= 1 {
1084+ return -1
1085+ }
1086+ nodeIdx := a.stack[len(a.stack)-1]
1087+ node := &a.doc.Nodes[nodeIdx]
1088+ if node.Type != NodeCodeBlock && node.Type != NodeRawBlock && node.Type != NodeDiv {
1089+ node.End = end
1090+ }
1091+ a.stack = a.stack[:len(a.stack)-1]
1092+ if a.isInline(a.doc.Nodes[nodeIdx].Type) {
1093+ a.lastClosedNode = nodeIdx
1094+ }
1095+ return nodeIdx
1096+}
1097+
1098+func (a *Assembler) addLeafNode(ntype NodeType, start, end int32) {
1099+ parentIdx := a.stack[len(a.stack)-1]
1100+
1101+ if ntype == NodeStr && a.lastClosedNode != -1 {
1102+ prevNode := &a.doc.Nodes[a.lastClosedNode]
1103+ if prevNode.Type == NodeStr && prevNode.End+1 == start {
1104+ prevNode.End = end
1105+ return
1106+ }
1107+ }
1108+
1109+ nodeIdx := a.addNode(ntype, start, end)
1110+ a.linkToParent(parentIdx, nodeIdx)
1111+ a.lastClosedNode = nodeIdx
1112+}
1113+
1114+func isOrderedMarker(marker byte) bool {
1115+ switch marker {
1116+ case '1', 'a', 'A', 'i', 'I':
1117+ return true
1118+ }
1119+ return false
1120+}
1121+
1122+func (a *Assembler) setStyleFromMarker(idx int32, marker byte) {
1123+ node := &a.doc.Nodes[idx]
1124+ switch marker {
1125+ case '1':
1126+ node.Data |= DataListDecimal
1127+ case 'a':
1128+ node.Data |= DataListLowerAlpha
1129+ case 'A':
1130+ node.Data |= DataListUpperAlpha
1131+ case 'i':
1132+ node.Data |= DataListLowerRoman
1133+ case 'I':
1134+ node.Data |= DataListUpperRoman
1135+ }
1136+}
+250,
-0
1@@ -0,0 +1,250 @@
2+package djot
3+
4+type NodeType uint16
5+
6+const (
7+ NodeDoc NodeType = iota
8+ NodeSection
9+ NodePara
10+ NodeHeading
11+ NodeThematicBreak
12+ NodeDiv
13+ NodeCodeBlock
14+ NodeRawBlock
15+ NodeBlockQuote
16+ NodeOrderedList
17+ NodeBulletList
18+ NodeTaskList
19+ NodeDefinitionList
20+ NodeTable
21+ NodeCaption
22+ NodeRow
23+ NodeCell
24+ NodeListItem
25+ NodeTaskListItem
26+ NodeDefinitionListItem
27+ NodeTerm
28+ NodeDefinition
29+ NodeFootnote
30+ NodeReference
31+
32+ NodeStr
33+ NodeSoftBreak
34+ NodeHardBreak
35+ NodeNonBreakingSpace
36+ NodeSymb
37+ NodeVerbatim
38+ NodeRawInline
39+ NodeInlineMath
40+ NodeDisplayMath
41+ NodeUrl
42+ NodeEmail
43+ NodeFootnoteReference
44+ NodeSmartPunctuation
45+ NodeEmph
46+ NodeStrong
47+ NodeLink
48+ NodeImage
49+ NodeSpan
50+ NodeMark
51+ NodeSuperscript
52+ NodeSubscript
53+ NodeInsert
54+ NodeDelete
55+ NodeDoubleQuoted
56+ NodeSingleQuoted
57+)
58+
59+type Node struct {
60+ Type NodeType
61+ Start, End int32
62+ Child int32
63+ Next int32
64+ Attr int32
65+ AttrCount uint16
66+ Level int16
67+ Data uint32
68+}
69+
70+type Attribute struct {
71+ KeyStart, KeyEnd int32
72+ ValStart, ValEnd int32
73+}
74+
75+const (
76+ AttrKeyID = -1
77+ AttrKeyClass = -2
78+ AttrKeyFormat = -3
79+)
80+
81+type EventType uint16
82+
83+const (
84+ EvNone EventType = iota
85+ EvStr
86+ EvSoftBreak
87+ EvHardBreak
88+ EvNonBreakingSpace
89+ EvEscape
90+ EvSymb
91+ EvFootnoteReference
92+ EvSmartPunctuation
93+ EvOpenVerbatim
94+ EvCloseVerbatim
95+ EvRawInline
96+ EvInlineMath
97+ EvDisplayMath
98+ EvUrl
99+ EvEmail
100+
101+ EvOpenAttributes
102+ EvOpenInlineAttributes
103+ EvCloseAttributes
104+ EvAttrIdMarker
105+ EvAttrClassMarker
106+ EvAttrKey
107+ EvAttrValue
108+ EvAttrEqualMarker
109+ EvAttrQuoteMarker
110+ EvAttrSpace
111+ EvComment
112+
113+ EvText
114+
115+ EvOpenPara
116+ EvClosePara
117+ EvOpenHeading
118+ EvCloseHeading
119+ EvOpenBlockQuote
120+ EvCloseBlockQuote
121+ EvOpenList
122+ EvCloseList
123+ EvOpenListItem
124+ EvCloseListItem
125+ EvOpenDiv
126+ EvCloseDiv
127+ EvOpenCodeBlock
128+ EvCloseCodeBlock
129+ EvOpenRawBlock
130+ EvCloseRawBlock
131+ EvOpenTable
132+ EvCloseTable
133+ EvOpenRow
134+ EvCloseRow
135+ EvOpenCell
136+ EvCloseCell
137+ EvOpenCaption
138+ EvCloseCaption
139+ EvOpenFootnote
140+ EvCloseFootnote
141+ EvOpenReferenceDefinition
142+ EvCloseReferenceDefinition
143+
144+ EvOpenEmph
145+ EvCloseEmph
146+ EvOpenStrong
147+ EvCloseStrong
148+ EvOpenLinkText
149+ EvCloseLinkText
150+ EvOpenImageText
151+ EvCloseImageText
152+ EvOpenDestination
153+ EvCloseDestination
154+ EvOpenReference
155+ EvCloseReference
156+ EvOpenSpan
157+ EvCloseSpan
158+ EvOpenMark
159+ EvCloseMark
160+ EvOpenInsert
161+ EvCloseInsert
162+ EvOpenDelete
163+ EvCloseDelete
164+ EvOpenSuperscript
165+ EvCloseSuperscript
166+ EvOpenSubscript
167+ EvCloseSubscript
168+ EvOpenDoubleQuoted
169+ EvCloseDoubleQuoted
170+ EvOpenSingleQuoted
171+ EvCloseSingleQuoted
172+
173+ EvBlankLine
174+ EvThematicBreak
175+)
176+
177+type Event struct {
178+ Start, End int32
179+ Type EventType
180+}
181+
182+type Reference struct {
183+ LabelStart, LabelEnd int32
184+ DestStart, DestEnd int32
185+ Attributes []Attribute
186+}
187+
188+type Document struct {
189+ Source []byte
190+ Extra []byte
191+ Nodes []Node
192+ Attributes []Attribute
193+ References map[string]Reference
194+ UsedFootnotes []string
195+ FootnoteContent map[string]int32
196+ Events []Event
197+}
198+
199+type ContainerType uint8
200+
201+const (
202+ ContainerNone ContainerType = iota
203+ ContainerDoc
204+ ContainerBlockQuote
205+ ContainerListItem
206+ ContainerList
207+ ContainerPara
208+ ContainerHeading
209+ ContainerTable
210+ ContainerCaption
211+ ContainerFootnote
212+ ContainerDiv
213+ ContainerCodeBlock
214+ ContainerRawBlock
215+ ContainerReferenceDef
216+ ContainerThematicBreak
217+)
218+
219+type ContainerData struct {
220+ Level int16
221+ Marker byte
222+ MarkerEnd byte
223+ Ordered bool
224+ Tight bool
225+ BlankLines bool
226+ Indent int32
227+ MarkerWidth int32
228+ FenceChar byte
229+ FenceLen int32
230+ IsTask bool
231+ Checked bool
232+ NextNumber int
233+ MarkerAmbiguous bool
234+ MarkerFirstChar byte
235+ OpenListEventIdx int32
236+}
237+
238+const (
239+ DataAlignLeft uint32 = 1 << iota
240+ DataAlignCenter
241+ DataAlignRight
242+ DataListTight
243+ DataListDecimal
244+ DataListLowerAlpha
245+ DataListUpperAlpha
246+ DataListLowerRoman
247+ DataListUpperRoman
248+ DataTaskChecked
249+ DataCellHeader
250+ DataNoAutoID
251+)
+154,
-0
1@@ -0,0 +1,154 @@
2+package djot
3+
4+func isKeyStartChar(b byte) bool {
5+ return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || b == '-' || b == ':'
6+}
7+
8+func isKeyChar(b byte) bool {
9+ return isKeyStartChar(b) || (b >= '0' && b <= '9')
10+}
11+
12+func isIDChar(b byte) bool {
13+ return isKeyChar(b)
14+}
15+
16+func isSpace(b byte) bool {
17+ return b == ' ' || b == '\t' || b == '\r' || b == '\n'
18+}
19+
20+func ParseAttributes(source []byte, start, end int32) ([]Event, int32, bool) {
21+ if start >= end || source[start] != '{' {
22+ return nil, start, false
23+ }
24+
25+ pos := start + 1
26+ var events []Event
27+
28+ for pos < end {
29+ b := source[pos]
30+ if isSpace(b) {
31+ pos++
32+ continue
33+ }
34+
35+ if b == '}' {
36+ return events, pos, true
37+ }
38+
39+ if b == '#' {
40+ pos++
41+ idStart := pos
42+ for pos < end && isIDChar(source[pos]) {
43+ pos++
44+ }
45+ if pos == idStart {
46+ return nil, start, false
47+ }
48+ events = append(events, Event{Start: idStart, End: pos - 1, Type: EvAttrIdMarker})
49+ continue
50+ }
51+
52+ if b == '.' {
53+ pos++
54+ classStart := pos
55+ for pos < end && isKeyChar(source[pos]) {
56+ pos++
57+ }
58+ if pos == classStart {
59+ return nil, start, false
60+ }
61+ events = append(events, Event{Start: classStart, End: pos - 1, Type: EvAttrClassMarker})
62+ continue
63+ }
64+
65+ if b == '%' {
66+ pos++
67+ commentStart := pos
68+ for pos < end && source[pos] != '%' && source[pos] != '}' {
69+ pos++
70+ }
71+ if pos < end && source[pos] == '%' {
72+ events = append(events, Event{Start: commentStart, End: pos - 1, Type: EvComment})
73+ pos++
74+ }
75+ continue
76+ }
77+
78+ if b == '=' {
79+ eqPos := pos
80+ pos++
81+ keyStart := pos
82+ for pos < end && source[pos] != '}' && !isSpace(source[pos]) {
83+ pos++
84+ }
85+ keyEnd := pos - 1
86+ if keyEnd >= keyStart {
87+ events = append(events, Event{Type: EvAttrKey, Start: eqPos, End: keyEnd})
88+ }
89+ continue
90+ }
91+
92+ if isKeyStartChar(b) {
93+ keyStart := pos
94+ for pos < end && isKeyChar(source[pos]) {
95+ pos++
96+ }
97+ keyEnd := pos - 1
98+
99+ for pos < end && isSpace(source[pos]) {
100+ pos++
101+ }
102+
103+ if pos < end && source[pos] == '=' {
104+ events = append(events, Event{Start: keyStart, End: keyEnd, Type: EvAttrKey})
105+ events = append(events, Event{Start: pos, End: pos, Type: EvAttrEqualMarker})
106+ pos++
107+
108+ for pos < end && isSpace(source[pos]) {
109+ pos++
110+ }
111+
112+ if pos < end && source[pos] == '"' {
113+ events = append(events, Event{Start: pos, End: pos, Type: EvAttrQuoteMarker})
114+ pos++
115+ valStart := pos
116+ valEnd := valStart
117+ for pos < end {
118+ if source[pos] == '\\' && pos+1 < end {
119+ pos += 2
120+ } else if source[pos] == '"' {
121+ valEnd = pos
122+ break
123+ } else {
124+ pos++
125+ }
126+ }
127+
128+ if pos < end && source[pos] == '"' {
129+ events = append(events, Event{Start: valStart, End: valEnd - 1, Type: EvAttrValue})
130+ events = append(events, Event{Start: pos, End: pos, Type: EvAttrQuoteMarker})
131+ pos++
132+ } else {
133+ return nil, start, false
134+ }
135+ } else {
136+ valStart := pos
137+ for pos < end && isKeyChar(source[pos]) {
138+ pos++
139+ }
140+ if pos == valStart {
141+ return nil, start, false
142+ }
143+ events = append(events, Event{Start: valStart, End: pos - 1, Type: EvAttrValue})
144+ }
145+ } else {
146+ return nil, start, false
147+ }
148+ continue
149+ }
150+
151+ return nil, start, false
152+ }
153+
154+ return nil, start, false
155+}
+1224,
-0
1@@ -0,0 +1,1224 @@
2+package djot
3+
4+import (
5+ "bytes"
6+)
7+
8+type ActiveContainer struct {
9+ Type ContainerType
10+ Start int32
11+ End int32
12+ Data ContainerData
13+}
14+type BlockParser struct {
15+ source []byte
16+ pos int32
17+ events []Event
18+ stack []ActiveContainer
19+
20+ inPara bool
21+ paraLines [][2]int32
22+
23+ pendingAttr []Event
24+ hasAttr bool
25+
26+ inAttrBlock bool
27+ attrLines [][2]int32
28+ lineIndent int32
29+ afterBlankLine bool
30+}
31+
32+func NewBlockParser(source []byte) *BlockParser {
33+ p := &BlockParser{
34+ source: source,
35+ events: make([]Event, 0, len(source)/8+32),
36+ stack: make([]ActiveContainer, 0, 16),
37+ paraLines: make([][2]int32, 0, 8),
38+ attrLines: make([][2]int32, 0, 2),
39+ afterBlankLine: true,
40+ }
41+ p.stack = append(p.stack, ActiveContainer{Type: ContainerDoc})
42+ return p
43+}
44+
45+func (p *BlockParser) addEvent(start, end int32, etype EventType) {
46+ p.events = append(p.events, Event{Start: start, End: end, Type: etype})
47+}
48+
49+func (p *BlockParser) Parse() []Event {
50+ for p.pos < int32(len(p.source)) {
51+ lineStart := p.pos
52+ eol := bytes.IndexByte(p.source[p.pos:], '\n')
53+ var nextPos int32
54+ if eol == -1 {
55+ nextPos = int32(len(p.source))
56+ } else {
57+ nextPos = p.pos + int32(eol) + 1
58+ }
59+
60+ p.handleLine(lineStart, nextPos)
61+ p.pos = nextPos
62+ }
63+
64+ p.closePara()
65+ p.flushFailedAttr()
66+ p.closePara()
67+ p.discardDanglingAttr()
68+ for i := len(p.stack) - 1; i > 0; i-- {
69+ p.closeContainer(i)
70+ }
71+ return p.events
72+}
73+
74+func (p *BlockParser) handleLine(start, end int32) {
75+ contentEnd := end
76+ if contentEnd > start && p.source[contentEnd-1] == '\n' {
77+ contentEnd--
78+ if contentEnd > start && p.source[contentEnd-1] == '\r' {
79+ contentEnd--
80+ }
81+ }
82+
83+ p.lineIndent = 0
84+ for start+p.lineIndent < contentEnd && p.source[start+p.lineIndent] == ' ' {
85+ p.lineIndent++
86+ }
87+
88+ matched := 1
89+ currentPos := start
90+ allMatched := true
91+ tipIdx := len(p.stack) - 1
92+
93+ for i := 1; i < len(p.stack); i++ {
94+ c := &p.stack[i]
95+ if i == tipIdx && (c.Type == ContainerCodeBlock || c.Type == ContainerRawBlock) {
96+ if p.isClosingFence(c, currentPos, contentEnd) {
97+ p.closePara()
98+ p.closeContainer(i)
99+ return
100+ }
101+ p.closePara()
102+ p.handleLeafContent(c, currentPos, end)
103+ return
104+ }
105+
106+ tipIsCode := p.stack[tipIdx].Type == ContainerCodeBlock ||
107+ p.stack[tipIdx].Type == ContainerRawBlock
108+ if !tipIsCode && c.Type == ContainerDiv {
109+ if p.isClosingFence(c, currentPos, contentEnd) {
110+ p.closePara()
111+ for j := len(p.stack) - 1; j >= i; j-- {
112+ p.closeContainer(j)
113+ }
114+ return
115+ }
116+ }
117+
118+ if c.Type == ContainerCodeBlock || c.Type == ContainerRawBlock {
119+ break
120+ }
121+
122+ newPos := p.checkContinue(c, currentPos, contentEnd)
123+ if newPos != -1 {
124+ matched++
125+ currentPos = newPos
126+ if isLeaf(c.Type) {
127+ break
128+ }
129+ } else {
130+ allMatched = false
131+ break
132+ }
133+ }
134+ isBlank := isBlankLine(p.source, currentPos, contentEnd)
135+
136+ if isBlank {
137+ p.closePara()
138+ p.discardDanglingAttr()
139+ for i := len(p.stack) - 1; i >= matched; i-- {
140+ p.closeContainer(i)
141+ }
142+ if p.inAttrBlock {
143+ p.flushFailedAttr()
144+ }
145+ p.addEvent(start, end, EvBlankLine)
146+ p.markListBlankLines(matched)
147+ p.afterBlankLine = true
148+ return
149+ }
150+
151+ defer func() { p.afterBlankLine = false }()
152+
153+ if p.inAttrBlock {
154+ p.attrLines = append(p.attrLines, [2]int32{currentPos, contentEnd})
155+ p.tryParseBufferedAttr()
156+ return
157+ }
158+
159+ if allMatched && !p.inAttrBlock && currentPos < contentEnd && p.source[currentPos] == '{' && (p.afterBlankLine || p.hasAttr) {
160+ p.inAttrBlock = true
161+ p.attrLines = append(p.attrLines, [2]int32{currentPos, contentEnd})
162+ p.tryParseBufferedAttr()
163+ return
164+ }
165+
166+ newStarts := false
167+ if !allMatched || p.inPara || !isLeaf(p.stack[len(p.stack)-1].Type) {
168+ openPos := currentPos
169+ if openPos < start+p.lineIndent {
170+ openPos = start + p.lineIndent
171+ }
172+ for {
173+ for i := 0; i < 3 && openPos < contentEnd && p.source[openPos] == ' '; i++ {
174+ openPos++
175+ }
176+
177+ ctype, nextOpenPos, data, ok := p.findOpen(start, openPos, contentEnd)
178+ if !ok {
179+ break
180+ }
181+
182+ if p.inPara && !p.canInterrupt(ctype, openPos, contentEnd) {
183+ break
184+ }
185+
186+ newStarts = true
187+ canHaveImmediateAttr := ctype == ContainerDiv || ctype == ContainerCodeBlock || ctype == ContainerRawBlock || ctype == ContainerHeading
188+ attrPos := nextOpenPos
189+ for attrPos < contentEnd && (p.source[attrPos] == ' ' || p.source[attrPos] == '\t') {
190+ attrPos++
191+ }
192+ if attrPos < contentEnd && canHaveImmediateAttr {
193+ if p.source[attrPos] == '{' {
194+ attrEvents, nextPos, ok := ParseAttributes(p.source, attrPos, contentEnd)
195+ if ok && isBlankLine(p.source, nextPos+1, contentEnd) {
196+ p.hasAttr = true
197+ p.pendingAttr = append(p.pendingAttr, attrEvents...)
198+ p.closePara()
199+ for i := len(p.stack) - 1; i >= matched; i-- {
200+ p.closeContainer(i)
201+ }
202+ p.openContainer(ctype, openPos, data)
203+ return
204+ }
205+ } else if ctype == ContainerDiv || ctype == ContainerCodeBlock || ctype == ContainerRawBlock {
206+ classStart := attrPos
207+ if ctype == ContainerDiv && p.source[classStart] == '.' {
208+ classStart++
209+ }
210+ classEnd := classStart
211+ for classEnd < contentEnd && !isSpace(p.source[classEnd]) {
212+ classEnd++
213+ }
214+ if classEnd > classStart {
215+ p.hasAttr = true
216+ if ctype == ContainerRawBlock {
217+ eqPos := attrPos - 1
218+ p.pendingAttr = append(p.pendingAttr, Event{Start: eqPos, End: classEnd - 1, Type: EvAttrKey})
219+ } else if ctype == ContainerCodeBlock && p.source[classStart] == '=' {
220+ ctype = ContainerRawBlock
221+ p.pendingAttr = append(p.pendingAttr, Event{Start: classStart, End: classEnd - 1, Type: EvAttrKey})
222+ } else {
223+ p.pendingAttr = append(p.pendingAttr, Event{Start: classStart, End: classEnd - 1, Type: EvAttrClassMarker})
224+ }
225+ nextAttrPos := classEnd
226+ for nextAttrPos < contentEnd && isSpace(p.source[nextAttrPos]) {
227+ nextAttrPos++
228+ }
229+ if nextAttrPos < contentEnd && p.source[nextAttrPos] == '{' {
230+ attrEvents, _, ok := ParseAttributes(p.source, nextAttrPos, contentEnd)
231+ if ok {
232+ p.pendingAttr = append(p.pendingAttr, attrEvents...)
233+ }
234+ }
235+ p.closePara()
236+ for i := len(p.stack) - 1; i >= matched; i-- {
237+ p.closeContainer(i)
238+ }
239+ p.openContainer(ctype, openPos, data)
240+ return
241+ }
242+ }
243+ }
244+
245+ p.closePara()
246+ for i := len(p.stack) - 1; i >= matched; i-- {
247+ c := &p.stack[i]
248+ if c.Data.BlankLines && c.Type == ContainerListItem {
249+ c.Data.Tight = false
250+ if i > 0 && p.stack[i-1].Type == ContainerList {
251+ p.stack[i-1].Data.Tight = false
252+ }
253+ }
254+ p.closeContainer(i)
255+ }
256+ p.openContainer(ctype, openPos, data)
257+
258+ currentPos = nextOpenPos
259+ openPos = nextOpenPos
260+ matched = len(p.stack)
261+
262+ if currentPos < contentEnd && p.source[currentPos] == '{' && !p.inAttrBlock {
263+ p.inAttrBlock = true
264+ p.attrLines = append(p.attrLines, [2]int32{currentPos, contentEnd})
265+ p.tryParseBufferedAttr()
266+ return
267+ }
268+
269+ if isLeaf(ctype) {
270+ if ctype == ContainerReferenceDef {
271+ currentPos = openPos
272+ }
273+ break
274+ }
275+ }
276+ }
277+
278+ isLazy := !isBlank && !newStarts && matched < len(p.stack) && p.inPara
279+
280+ if !isLazy {
281+ if matched < len(p.stack) {
282+ p.closePara()
283+ for i := len(p.stack) - 1; i >= matched; i-- {
284+ p.closeContainer(i)
285+ }
286+ }
287+ }
288+
289+ tipIdx = len(p.stack) - 1
290+ tip := &p.stack[tipIdx]
291+
292+ if tip.Type == ContainerTable {
293+ p.handleTableRow(currentPos, contentEnd)
294+ return
295+ }
296+
297+ if isBlankLine(p.source, currentPos, contentEnd) {
298+ return
299+ }
300+
301+ if hasInlineContent(tip.Type) || isBlock(tip.Type) || p.inPara {
302+ for currentPos < contentEnd && (p.source[currentPos] == ' ' || p.source[currentPos] == '\t') {
303+ currentPos++
304+ }
305+ p.accumulateInlineLine(currentPos, contentEnd)
306+ } else {
307+ p.handleLeafContent(tip, currentPos, end)
308+ }
309+}
310+
311+func isBlock(t ContainerType) bool {
312+ switch t {
313+ case ContainerDoc, ContainerBlockQuote, ContainerListItem, ContainerList, ContainerDiv, ContainerFootnote:
314+ return true
315+ }
316+ return false
317+}
318+
319+func hasInlineContent(t ContainerType) bool {
320+ switch t {
321+ case ContainerPara, ContainerHeading, ContainerCaption:
322+ return true
323+ }
324+ return false
325+}
326+
327+func (p *BlockParser) accumulateInlineLine(start, end int32) {
328+ tipIdx := len(p.stack) - 1
329+ tip := &p.stack[tipIdx]
330+ if (tip.Type == ContainerPara || isBlock(tip.Type)) && !p.inPara {
331+ p.inPara = true
332+ if p.hasAttr {
333+ p.addEvent(start, start, EvOpenAttributes)
334+ p.events = append(p.events, p.pendingAttr...)
335+ p.addEvent(start, start, EvCloseAttributes)
336+ p.hasAttr = false
337+ p.pendingAttr = p.pendingAttr[:0]
338+ }
339+ p.addEvent(start, start, EvOpenPara)
340+ }
341+ p.paraLines = append(p.paraLines, [2]int32{start, end})
342+}
343+
344+func (p *BlockParser) tryParseBufferedAttr() {
345+ if len(p.attrLines) == 0 {
346+ return
347+ }
348+
349+ start := p.attrLines[0][0]
350+ end := p.attrLines[len(p.attrLines)-1][1]
351+
352+ attrEvents, nextPos, ok := ParseAttributes(p.source, start, end)
353+
354+ if ok {
355+ if isBlankLine(p.source, nextPos+1, end) {
356+ p.closePara()
357+ p.hasAttr = true
358+ p.pendingAttr = append(p.pendingAttr, attrEvents...)
359+ p.inAttrBlock = false
360+ p.attrLines = p.attrLines[:0]
361+ return
362+ } else {
363+ p.flushFailedAttr()
364+ return
365+ }
366+ } else {
367+ hasClosing := false
368+ for i := start; i < end; i++ {
369+ if p.source[i] == '}' {
370+ hasClosing = true
371+ break
372+ }
373+ }
374+ if hasClosing {
375+ p.flushFailedAttr()
376+ }
377+ }
378+}
379+func (p *BlockParser) flushFailedAttr() {
380+ p.inAttrBlock = false
381+ for _, line := range p.attrLines {
382+ start := line[0]
383+ for start < line[1] && (p.source[start] == ' ' || p.source[start] == '\t') {
384+ start++
385+ }
386+ p.accumulateInlineLine(start, line[1])
387+ }
388+ p.attrLines = p.attrLines[:0]
389+}
390+
391+func (p *BlockParser) closePara() {
392+ if !p.inPara {
393+ if len(p.paraLines) > 0 {
394+ for i, line := range p.paraLines {
395+ start := line[0]
396+ end := line[1]
397+ if i == len(p.paraLines)-1 {
398+ for end > start && (p.source[end-1] == ' ' || p.source[end-1] == '\t') {
399+ end--
400+ }
401+ }
402+ isHardBreak := false
403+ if i < len(p.paraLines)-1 {
404+ tempEnd := end
405+ for tempEnd > start && (p.source[tempEnd-1] == ' ' || p.source[tempEnd-1] == '\t') {
406+ tempEnd--
407+ }
408+ if tempEnd > start && p.source[tempEnd-1] == '\\' {
409+ backslashCount := 0
410+ for j := tempEnd - 1; j >= start && p.source[j] == '\\'; j-- {
411+ backslashCount++
412+ }
413+ if backslashCount%2 != 0 {
414+ isHardBreak = true
415+ end = tempEnd - 1
416+ for end > start && (p.source[end-1] == ' ' || p.source[end-1] == '\t') {
417+ end--
418+ }
419+ }
420+ }
421+ }
422+ if start < end {
423+ p.addEvent(start, end-1, EvText)
424+ }
425+ if i < len(p.paraLines)-1 {
426+ if isHardBreak {
427+ p.addEvent(line[1], line[1], EvHardBreak)
428+ } else {
429+ p.addEvent(line[1], line[1], EvSoftBreak)
430+ }
431+ }
432+ }
433+ p.paraLines = p.paraLines[:0]
434+ }
435+ return
436+ }
437+
438+ for i, line := range p.paraLines {
439+ start := line[0]
440+ end := line[1]
441+ if i == len(p.paraLines)-1 {
442+ for end > start && (p.source[end-1] == ' ' || p.source[end-1] == '\t') {
443+ end--
444+ }
445+ }
446+ isHardBreak := false
447+ if i < len(p.paraLines)-1 {
448+ tempEnd := end
449+ for tempEnd > start && (p.source[tempEnd-1] == ' ' || p.source[tempEnd-1] == '\t') {
450+ tempEnd--
451+ }
452+ if tempEnd > start && p.source[tempEnd-1] == '\\' {
453+ backslashCount := 0
454+ for j := tempEnd - 1; j >= start && p.source[j] == '\\'; j-- {
455+ backslashCount++
456+ }
457+ if backslashCount%2 != 0 {
458+ isHardBreak = true
459+ end = tempEnd - 1
460+ for end > start && (p.source[end-1] == ' ' || p.source[end-1] == '\t') {
461+ end--
462+ }
463+ }
464+ }
465+ }
466+ if start < end {
467+ p.addEvent(start, end-1, EvText)
468+ }
469+ if i < len(p.paraLines)-1 {
470+ if isHardBreak {
471+ p.addEvent(line[1], line[1], EvHardBreak)
472+ } else {
473+ p.addEvent(line[1], line[1], EvSoftBreak)
474+ }
475+ }
476+ }
477+
478+ if len(p.paraLines) > 0 {
479+ lastLine := p.paraLines[len(p.paraLines)-1]
480+ p.addEvent(lastLine[1], lastLine[1], EvClosePara)
481+ }
482+ p.inPara = false
483+ p.paraLines = p.paraLines[:0]
484+}
485+
486+func (p *BlockParser) discardDanglingAttr() {
487+ p.hasAttr = false
488+ p.pendingAttr = p.pendingAttr[:0]
489+}
490+
491+func isLeaf(t ContainerType) bool {
492+ switch t {
493+ case ContainerPara, ContainerHeading, ContainerCodeBlock, ContainerRawBlock, ContainerThematicBreak, ContainerReferenceDef, ContainerCaption, ContainerTable:
494+ return true
495+ }
496+ return false
497+}
498+
499+func isBlankLine(source []byte, start, end int32) bool {
500+ for i := start; i < end; i++ {
501+ if source[i] != ' ' && source[i] != '\t' {
502+ return false
503+ }
504+ }
505+ return true
506+}
507+
508+func (p *BlockParser) markListBlankLines(matched int) {
509+}
510+
511+func (p *BlockParser) checkContinue(c *ActiveContainer, pos, end int32) int32 {
512+ switch c.Type {
513+ case ContainerBlockQuote:
514+ if pos < end && p.source[pos] == '>' {
515+ pos++
516+ if pos == end {
517+ return pos
518+ }
519+ if p.source[pos] == ' ' {
520+ return pos + 1
521+ }
522+ return -1
523+ }
524+ case ContainerHeading:
525+ if isBlankLine(p.source, pos, end) {
526+ return -1
527+ }
528+ hLevel := int32(0)
529+ for pos+hLevel < end && p.source[pos+hLevel] == '#' {
530+ hLevel++
531+ }
532+ if hLevel > 0 && hLevel <= 6 && (pos+hLevel == end || p.source[pos+hLevel] == ' ' || p.source[pos+hLevel] == '\t') {
533+ if hLevel == int32(c.Data.Level) {
534+ newPos := pos + hLevel
535+ if newPos < end && (p.source[newPos] == ' ' || p.source[newPos] == '\t') {
536+ newPos++
537+ }
538+ return newPos
539+ }
540+ return -1
541+ }
542+ return pos
543+ case ContainerCodeBlock, ContainerRawBlock:
544+ if p.isClosingFence(c, pos, end) {
545+ return -1
546+ }
547+ return pos
548+ case ContainerDiv:
549+ return pos
550+ case ContainerList:
551+ return pos
552+ case ContainerListItem:
553+ if isBlankLine(p.source, pos, end) {
554+ return pos
555+ }
556+ if p.lineIndent > c.Data.Indent {
557+ return pos
558+ }
559+ if p.inPara && !p.afterBlankLine {
560+ markerPos := pos + p.lineIndent
561+ _, _, ok := parseListMarker(p.source, markerPos, end)
562+ if !ok {
563+ return pos
564+ }
565+ }
566+ return -1
567+ case ContainerTable:
568+ if pos < end && p.source[pos] == '|' {
569+ return pos
570+ }
571+ case ContainerFootnote:
572+ if isBlankLine(p.source, pos, end) {
573+ return pos
574+ }
575+ if p.lineIndent > c.Data.Indent {
576+ return pos
577+ }
578+ return -1
579+ case ContainerReferenceDef:
580+ if isBlankLine(p.source, pos, end) {
581+ return pos
582+ }
583+ if p.lineIndent > c.Data.Indent {
584+ return pos
585+ }
586+ return -1
587+ case ContainerCaption:
588+ if isBlankLine(p.source, pos, end) {
589+ return -1
590+ }
591+ return pos
592+ }
593+ return -1
594+}
595+
596+func (p *BlockParser) isClosingFence(c *ActiveContainer, pos, end int32) bool {
597+ i := 0
598+ for i < 3 && pos < end && p.source[pos] == ' ' {
599+ pos++
600+ i++
601+ }
602+
603+ char := c.Data.FenceChar
604+ count := int32(0)
605+ for pos < end && p.source[pos] == char {
606+ count++
607+ pos++
608+ }
609+
610+ if count < c.Data.FenceLen {
611+ return false
612+ }
613+
614+ for pos < end && (p.source[pos] == ' ' || p.source[pos] == '\t') {
615+ pos++
616+ }
617+
618+ return pos == end || p.source[pos] == '\r' || p.source[pos] == '\n'
619+}
620+
621+func (p *BlockParser) findOpen(start, pos, end int32) (ContainerType, int32, ContainerData, bool) {
622+ if pos >= end {
623+ return ContainerNone, pos, ContainerData{}, false
624+ }
625+ b := p.source[pos]
626+
627+ if isThematicBreak(p.source, pos, end) {
628+ return ContainerThematicBreak, end, ContainerData{}, true
629+ }
630+
631+ if b == '`' || b == '~' || b == ':' {
632+ count := int32(0)
633+ for i := pos; i < end && p.source[i] == b; i++ {
634+ count++
635+ }
636+ if count >= 3 {
637+ if b == '`' || b == '~' {
638+ rest := p.source[pos+count : end]
639+ hasInvalid := false
640+ for j := 0; j < len(rest); j++ {
641+ if rest[j] == '{' {
642+ break
643+ }
644+ if rest[j] == b {
645+ c := int32(0)
646+ for j < len(rest) && rest[j] == b {
647+ c++
648+ j++
649+ }
650+ if c >= count {
651+ hasInvalid = true
652+ break
653+ }
654+ j--
655+ continue
656+ }
657+ if rest[j] != ' ' && rest[j] != ' ' {
658+ k := j
659+ for k < len(rest) && rest[k] != ' ' && rest[k] != ' ' && rest[k] != '{' {
660+ if rest[k] == b {
661+ c := int32(0)
662+ for k < len(rest) && rest[k] == b {
663+ c++
664+ k++
665+ }
666+ if c >= count {
667+ hasInvalid = true
668+ break
669+ }
670+ continue
671+ }
672+ k++
673+ }
674+ if hasInvalid {
675+ break
676+ }
677+ for k < len(rest) && (rest[k] == ' ' || rest[k] == ' ') {
678+ k++
679+ }
680+ if k < len(rest) && rest[k] != '{' {
681+ hasInvalid = true
682+ break
683+ }
684+ j = k - 1
685+ }
686+ }
687+ if hasInvalid {
688+ return ContainerNone, pos, ContainerData{}, false
689+ }
690+ }
691+ if b == ':' {
692+ return ContainerDiv, pos + count, ContainerData{FenceChar: b, FenceLen: count, Indent: pos - start}, true
693+ }
694+ classPos := pos + count
695+ for classPos < end && p.source[classPos] == ' ' {
696+ classPos++
697+ }
698+ if classPos < end && p.source[classPos] == '=' {
699+ return ContainerRawBlock, classPos + 1, ContainerData{FenceChar: b, FenceLen: count, Indent: pos - start}, true
700+ }
701+ return ContainerCodeBlock, pos + count, ContainerData{FenceChar: b, FenceLen: count, Indent: pos - start}, true
702+ }
703+ }
704+
705+ if b == '#' {
706+ level := int32(0)
707+ for i := pos; i < end && p.source[i] == '#'; i++ {
708+ level++
709+ }
710+ if level > 0 && level <= 6 && (pos+level == end || p.source[pos+level] == ' ') {
711+ newPos := pos + level
712+ if newPos < end && p.source[newPos] == ' ' {
713+ newPos++
714+ }
715+ return ContainerHeading, newPos, ContainerData{Level: int16(level)}, true
716+ }
717+ }
718+
719+ if b == '>' {
720+ newPos := pos + 1
721+ if newPos == end {
722+ return ContainerBlockQuote, newPos, ContainerData{}, true
723+ }
724+ if p.source[newPos] == ' ' {
725+ return ContainerBlockQuote, newPos + 1, ContainerData{}, true
726+ }
727+ return ContainerNone, pos, ContainerData{}, false
728+ }
729+
730+ if b == '|' {
731+ pipes := 0
732+ i := pos
733+ for i < end {
734+ if p.source[i] == '|' {
735+ pipes++
736+ i++
737+ } else if p.source[i] == '`' {
738+ count := int32(0)
739+ for i < end && p.source[i] == '`' {
740+ count++
741+ i++
742+ }
743+ for i < end {
744+ if p.source[i] == '`' {
745+ c := int32(0)
746+ for i < end && p.source[i] == '`' {
747+ c++
748+ i++
749+ }
750+ if c == count {
751+ break
752+ }
753+ } else {
754+ i++
755+ }
756+ }
757+ } else {
758+ i++
759+ }
760+ }
761+ if pipes >= 2 {
762+ return ContainerTable, pos, ContainerData{}, true
763+ }
764+ }
765+
766+ if b == '^' {
767+ if pos+1 < end && p.source[pos+1] == ' ' {
768+ return ContainerCaption, pos + 2, ContainerData{}, true
769+ }
770+ }
771+
772+ if b == '[' {
773+ if pos+1 < end && p.source[pos+1] == '^' {
774+ for i := pos + 2; i < end; i++ {
775+ if p.source[i] == ']' && i+1 < end && p.source[i+1] == ':' {
776+ newPos := i + 2
777+ for newPos < end && (p.source[newPos] == ' ' || p.source[newPos] == '\t') {
778+ newPos++
779+ }
780+ return ContainerFootnote, newPos, ContainerData{Indent: p.lineIndent, MarkerWidth: newPos - pos}, true
781+ }
782+ }
783+ } else {
784+ for i := pos + 1; i < end; i++ {
785+ if p.source[i] == ']' && i+1 < end && p.source[i+1] == ':' {
786+ newPos := i + 2
787+ for newPos < end && (p.source[newPos] == ' ' || p.source[newPos] == '\t') {
788+ newPos++
789+ }
790+ return ContainerReferenceDef, newPos, ContainerData{Indent: p.lineIndent}, true
791+ }
792+ }
793+ }
794+ }
795+
796+ newPos, data, ok := parseListMarker(p.source, pos, end)
797+ if ok {
798+ data.Indent = pos - start
799+ if !p.canListMarkerInterrupt(p.lineIndent, data.Marker, p.afterBlankLine) {
800+ return ContainerNone, pos, ContainerData{}, false
801+ }
802+ return ContainerListItem, newPos, data, true
803+ }
804+
805+ return ContainerNone, pos, ContainerData{}, false
806+}
807+
808+func (p *BlockParser) openContainer(ctype ContainerType, start int32, data ContainerData) {
809+ if p.hasAttr {
810+ p.addEvent(start, start, EvOpenAttributes)
811+ p.events = append(p.events, p.pendingAttr...)
812+ p.addEvent(start, start, EvCloseAttributes)
813+ p.hasAttr = false
814+ p.pendingAttr = p.pendingAttr[:0]
815+ }
816+
817+ switch ctype {
818+ case ContainerHeading:
819+ p.addEvent(start, start+int32(data.Level)-1, EvOpenHeading)
820+ case ContainerBlockQuote:
821+ p.addEvent(start, start, EvOpenBlockQuote)
822+ case ContainerListItem:
823+ tipIdx := len(p.stack) - 1
824+ tip := &p.stack[tipIdx]
825+ if tip.Type == ContainerListItem {
826+ pIdx := len(p.stack) - 2
827+ parent := &p.stack[pIdx]
828+ if parent.Type == ContainerList {
829+ itemIndent := tip.Data.Indent
830+ if data.Indent > itemIndent {
831+ p.openList(start, data)
832+ } else if data.Indent == parent.Data.Indent {
833+ p.closeContainer(tipIdx)
834+ if parent.Data.Marker == data.Marker && parent.Data.MarkerEnd == data.MarkerEnd && parent.Data.Ordered == data.Ordered {
835+ if data.Ordered {
836+ parent.Data.NextNumber++
837+ }
838+ } else {
839+ p.closeContainer(pIdx)
840+ p.openList(start, data)
841+ }
842+ } else {
843+ p.closeContainer(tipIdx)
844+ p.closeContainer(pIdx)
845+ p.openList(start, data)
846+ }
847+ } else {
848+ p.openList(start, data)
849+ }
850+ } else if tip.Type == ContainerList {
851+ if tip.Data.Marker == data.Marker && tip.Data.MarkerEnd == data.MarkerEnd && tip.Data.Ordered == data.Ordered {
852+ if !data.MarkerAmbiguous {
853+ tip.Data.MarkerAmbiguous = false
854+ }
855+ } else if data.Indent == tip.Data.Indent {
856+ if p.tryNarrowList(tip, data) {
857+ } else {
858+ p.closeContainer(tipIdx)
859+ p.openList(start, data)
860+ }
861+ } else {
862+ p.openList(start, data)
863+ }
864+ } else {
865+ p.openList(start, data)
866+ }
867+
868+ taskStatus := int32(0)
869+ if data.IsTask {
870+ if data.Checked {
871+ taskStatus = 2
872+ } else {
873+ taskStatus = 1
874+ }
875+ }
876+ p.addEvent(start, taskStatus, EvOpenListItem)
877+ case ContainerCodeBlock:
878+ p.addEvent(start, start+data.FenceLen-1, EvOpenCodeBlock)
879+ p.addEvent(start, start, EvNone)
880+ case ContainerRawBlock:
881+ p.addEvent(start, start+data.FenceLen-1, EvOpenRawBlock)
882+ p.addEvent(start, start, EvNone)
883+ case ContainerDiv:
884+ p.addEvent(start, start+data.FenceLen-1, EvOpenDiv)
885+ p.addEvent(start, start, EvNone)
886+ case ContainerThematicBreak:
887+ p.addEvent(start, start, EvThematicBreak)
888+ case ContainerTable:
889+ p.addEvent(start, start, EvOpenTable)
890+ case ContainerCaption:
891+ p.addEvent(start, start, EvOpenCaption)
892+ case ContainerFootnote:
893+ p.addEvent(start, start, EvOpenFootnote)
894+ p.addEvent(start, start+data.MarkerWidth-1, EvStr)
895+ case ContainerReferenceDef:
896+ p.addEvent(start, start, EvOpenReferenceDefinition)
897+ }
898+ p.stack = append(p.stack, ActiveContainer{Type: ctype, Start: start, Data: data})
899+}
900+
901+func (p *BlockParser) closeContainer(idx int) {
902+ c := p.stack[idx]
903+ switch c.Type {
904+ case ContainerHeading:
905+ p.addEvent(c.Start, c.Start, EvCloseHeading)
906+ case ContainerBlockQuote:
907+ p.addEvent(c.Start, c.Start, EvCloseBlockQuote)
908+ case ContainerListItem:
909+ p.addEvent(c.Start, c.Start, EvCloseListItem)
910+ case ContainerList:
911+ tight := int32(0)
912+ if c.Data.Tight {
913+ tight = 1
914+ }
915+ p.addEvent(c.Start, tight, EvCloseList)
916+ case ContainerCodeBlock:
917+ p.addEvent(c.Start, c.Start, EvCloseCodeBlock)
918+ case ContainerRawBlock:
919+ p.addEvent(c.Start, c.Start, EvCloseRawBlock)
920+ case ContainerDiv:
921+ p.addEvent(c.Start, c.Start, EvCloseDiv)
922+ case ContainerTable:
923+ p.addEvent(c.Start, c.Start, EvCloseTable)
924+ case ContainerCaption:
925+ p.addEvent(c.Start, c.Start, EvCloseCaption)
926+ case ContainerFootnote:
927+ p.addEvent(c.Start, c.Start, EvCloseFootnote)
928+ case ContainerReferenceDef:
929+ p.addEvent(c.Start, c.Start, EvCloseReferenceDefinition)
930+ }
931+ p.stack = p.stack[:idx]
932+}
933+
934+func (p *BlockParser) handleLeafContent(tip *ActiveContainer, start, end int32) {
935+ contentEnd := end
936+ if contentEnd > start && p.source[contentEnd-1] == '\n' {
937+ contentEnd--
938+ if contentEnd > start && p.source[contentEnd-1] == '\r' {
939+ contentEnd--
940+ }
941+ }
942+
943+ if p.hasAttr {
944+ p.addEvent(start, start, EvOpenAttributes)
945+ p.events = append(p.events, p.pendingAttr...)
946+ p.addEvent(start, start, EvCloseAttributes)
947+ p.hasAttr = false
948+ p.pendingAttr = p.pendingAttr[:0]
949+ }
950+ switch tip.Type {
951+ case ContainerCodeBlock, ContainerRawBlock, ContainerDiv:
952+ if p.isClosingFence(tip, start, end) {
953+ p.closeContainer(len(p.stack) - 1)
954+ return
955+ }
956+
957+ indentToStrip := tip.Data.Indent
958+ current := start
959+ for indentToStrip > 0 && current < end && p.source[current] == ' ' {
960+ current++
961+ indentToStrip--
962+ }
963+ if end < int32(len(p.source)) && p.source[end] == '\n' {
964+ end++
965+ } else if end < int32(len(p.source)) && p.source[end] == '\r' {
966+ end++
967+ if end < int32(len(p.source)) && p.source[end] == '\n' {
968+ end++
969+ }
970+ }
971+ p.addEvent(current, end-1, EvStr)
972+ case ContainerTable:
973+ p.handleTableRow(start, end)
974+ case ContainerHeading, ContainerCaption:
975+ p.accumulateInlineLine(start, end)
976+ case ContainerReferenceDef:
977+ actualEnd := contentEnd - 1
978+ if actualEnd >= int32(len(p.source)) {
979+ actualEnd = int32(len(p.source)) - 1
980+ }
981+ if tip.Start <= actualEnd {
982+ p.addEvent(tip.Start, actualEnd, EvStr)
983+ }
984+ case ContainerFootnote:
985+ actualEnd := contentEnd - 1
986+ if actualEnd >= int32(len(p.source)) {
987+ actualEnd = int32(len(p.source)) - 1
988+ }
989+ if actualEnd >= start {
990+ p.addEvent(start, actualEnd, EvStr)
991+ }
992+ }
993+}
994+
995+func (p *BlockParser) handleTableRow(start, end int32) {
996+ isSep := false
997+ hasDash := false
998+ for i := start; i < end; i++ {
999+ b := p.source[i]
1000+ if b == '-' || b == ':' {
1001+ hasDash = true
1002+ } else if b != '|' && b != ' ' && b != '\t' {
1003+ hasDash = false
1004+ break
1005+ }
1006+ }
1007+ isSep = hasDash
1008+
1009+ if isSep {
1010+ p.addEvent(start, end, EvStr)
1011+ return
1012+ }
1013+
1014+ p.addEvent(start, start, EvOpenRow)
1015+ pos := start
1016+ for pos < end {
1017+ if p.source[pos] == '|' {
1018+ pos++
1019+ if pos >= end {
1020+ break
1021+ }
1022+ cellStart := pos
1023+ for pos < end {
1024+ if p.source[pos] == '\\' && pos+1 < end {
1025+ pos += 2
1026+ } else if p.source[pos] == '`' {
1027+ count := int32(0)
1028+ for pos < end && p.source[pos] == '`' {
1029+ count++
1030+ pos++
1031+ }
1032+ for pos < end {
1033+ if p.source[pos] == '`' {
1034+ c := int32(0)
1035+ for pos < end && p.source[pos] == '`' {
1036+ c++
1037+ pos++
1038+ }
1039+ if c == count {
1040+ break
1041+ }
1042+ } else {
1043+ pos++
1044+ }
1045+ }
1046+ } else if p.source[pos] == '|' {
1047+ break
1048+ } else {
1049+ pos++
1050+ }
1051+ }
1052+ cellEnd := pos - 1
1053+ for cellStart <= cellEnd && isSpace(p.source[cellStart]) {
1054+ cellStart++
1055+ }
1056+ for cellEnd >= cellStart && isSpace(p.source[cellEnd]) {
1057+ cellEnd--
1058+ }
1059+ p.addEvent(cellStart, cellStart, EvOpenCell)
1060+ if cellStart <= cellEnd {
1061+ p.addEvent(cellStart, cellEnd, EvText)
1062+ }
1063+ p.addEvent(pos, pos, EvCloseCell)
1064+ } else {
1065+ pos++
1066+ }
1067+ }
1068+ p.addEvent(end, end, EvCloseRow)
1069+}
1070+
1071+func (p *BlockParser) canListMarkerInterrupt(markerIndent int32, markerChar byte, afterBlankLine bool) bool {
1072+ isDefinition := markerChar == ':'
1073+
1074+ for i := len(p.stack) - 1; i >= 0; i-- {
1075+ c := p.stack[i]
1076+ if c.Type == ContainerList || c.Type == ContainerListItem {
1077+ parentMarker := c.Data.Marker
1078+ parentIsDefinition := parentMarker == ':'
1079+ parentIndent := c.Data.Indent
1080+
1081+ if isDefinition == parentIsDefinition {
1082+ if !afterBlankLine && markerIndent > parentIndent && markerIndent < parentIndent+4 {
1083+ return false
1084+ }
1085+ }
1086+ return true
1087+ }
1088+ }
1089+ return true
1090+}
1091+
1092+func (p *BlockParser) canInterrupt(ctype ContainerType, pos, end int32) bool {
1093+ switch ctype {
1094+ case ContainerFootnote:
1095+ return true
1096+ case ContainerListItem:
1097+ _, data, ok := parseListMarker(p.source, pos, end)
1098+ if !ok {
1099+ return false
1100+ }
1101+ if data.Ordered && data.NextNumber != 1 {
1102+ alreadyInOrderedList := false
1103+ canNarrowExistingList := false
1104+ canContinueAsRoman := false
1105+ hasDifferentMarkerList := false
1106+ for i := len(p.stack) - 1; i >= 0; i-- {
1107+ c := p.stack[i]
1108+ if c.Type == ContainerList && c.Data.Ordered &&
1109+ c.Data.Marker == data.Marker && c.Data.MarkerEnd == data.MarkerEnd &&
1110+ c.Data.Indent == p.lineIndent {
1111+ alreadyInOrderedList = true
1112+ break
1113+ }
1114+ if c.Type == ContainerList && c.Data.Ordered &&
1115+ c.Data.Indent == p.lineIndent {
1116+ if c.Data.Marker != data.Marker || c.Data.MarkerEnd != data.MarkerEnd {
1117+ hasDifferentMarkerList = true
1118+ }
1119+ if c.Data.Marker == 'i' && data.Marker == 'a' && c.Data.MarkerAmbiguous && !isRomanLowerStart(data.MarkerFirstChar) {
1120+ canNarrowExistingList = true
1121+ break
1122+ }
1123+ if c.Data.Marker == 'I' && data.Marker == 'A' && c.Data.MarkerAmbiguous && !isRomanUpperStart(data.MarkerFirstChar) {
1124+ canNarrowExistingList = true
1125+ break
1126+ }
1127+ if c.Data.Marker == 'i' && data.Marker == 'a' && data.MarkerAmbiguous && isRomanLowerStart(data.MarkerFirstChar) {
1128+ if parseRoman([]byte{data.MarkerFirstChar}) == c.Data.NextNumber {
1129+ canContinueAsRoman = true
1130+ break
1131+ }
1132+ }
1133+ if c.Data.Marker == 'I' && data.Marker == 'A' && data.MarkerAmbiguous && isRomanUpperStart(data.MarkerFirstChar) {
1134+ if parseRoman([]byte{data.MarkerFirstChar}) == c.Data.NextNumber {
1135+ canContinueAsRoman = true
1136+ break
1137+ }
1138+ }
1139+ }
1140+ }
1141+ if !alreadyInOrderedList && !canNarrowExistingList && !canContinueAsRoman && !hasDifferentMarkerList {
1142+ return false
1143+ }
1144+ }
1145+ return p.canListMarkerInterrupt(p.lineIndent, data.Marker, p.afterBlankLine)
1146+ }
1147+ return false
1148+}
1149+func (p *BlockParser) tryNarrowList(list *ActiveContainer, newItem ContainerData) bool {
1150+ lower := list.Data.Marker == 'i'
1151+ upper := list.Data.Marker == 'I'
1152+ if !lower && !upper {
1153+ return false
1154+ }
1155+
1156+ if lower {
1157+ if list.Data.MarkerAmbiguous && newItem.Marker == 'a' && !isRomanLowerStart(newItem.MarkerFirstChar) {
1158+ startNum := int(list.Data.MarkerFirstChar - 'a' + 1)
1159+ p.events[list.Data.OpenListEventIdx].End = int32('a')
1160+ p.events[list.Data.OpenListEventIdx+1].End = int32(startNum)
1161+ list.Data.Marker = 'a'
1162+ list.Data.MarkerAmbiguous = false
1163+ return true
1164+ }
1165+ if !list.Data.MarkerAmbiguous && newItem.Marker == 'a' && !isRomanLowerStart(newItem.MarkerFirstChar) {
1166+ return false
1167+ }
1168+ if !list.Data.MarkerAmbiguous && newItem.Marker == 'a' && isRomanLowerStart(newItem.MarkerFirstChar) {
1169+ if parseRoman([]byte{newItem.MarkerFirstChar}) == list.Data.NextNumber {
1170+ list.Data.NextNumber++
1171+ return true
1172+ }
1173+ }
1174+ if !list.Data.MarkerAmbiguous && newItem.Marker == 'i' && newItem.MarkerAmbiguous {
1175+ if newItem.NextNumber == list.Data.NextNumber {
1176+ list.Data.NextNumber++
1177+ return true
1178+ }
1179+ }
1180+ if !list.Data.MarkerAmbiguous && newItem.Marker == 'a' && newItem.MarkerAmbiguous && isRomanLowerStart(newItem.MarkerFirstChar) {
1181+ if parseRoman([]byte{newItem.MarkerFirstChar}) == list.Data.NextNumber {
1182+ list.Data.NextNumber++
1183+ return true
1184+ }
1185+ }
1186+ }
1187+
1188+ if upper {
1189+ if list.Data.MarkerAmbiguous && newItem.Marker == 'A' && !isRomanUpperStart(newItem.MarkerFirstChar) {
1190+ startNum := int(list.Data.MarkerFirstChar - 'A' + 1)
1191+ p.events[list.Data.OpenListEventIdx].End = int32('A')
1192+ p.events[list.Data.OpenListEventIdx+1].End = int32(startNum)
1193+ list.Data.Marker = 'A'
1194+ list.Data.MarkerAmbiguous = false
1195+ return true
1196+ }
1197+ if !list.Data.MarkerAmbiguous && newItem.Marker == 'A' && isRomanUpperStart(newItem.MarkerFirstChar) {
1198+ if parseRoman([]byte{newItem.MarkerFirstChar}) == list.Data.NextNumber {
1199+ list.Data.NextNumber++
1200+ return true
1201+ }
1202+ }
1203+ if !list.Data.MarkerAmbiguous && newItem.Marker == 'I' && newItem.MarkerAmbiguous {
1204+ if newItem.NextNumber == list.Data.NextNumber {
1205+ list.Data.NextNumber++
1206+ return true
1207+ }
1208+ }
1209+ if !list.Data.MarkerAmbiguous && newItem.Marker == 'A' && newItem.MarkerAmbiguous && isRomanUpperStart(newItem.MarkerFirstChar) {
1210+ if parseRoman([]byte{newItem.MarkerFirstChar}) == list.Data.NextNumber {
1211+ list.Data.NextNumber++
1212+ return true
1213+ }
1214+ }
1215+ }
1216+ return false
1217+}
1218+
1219+func (p *BlockParser) openList(start int32, data ContainerData) {
1220+ data.OpenListEventIdx = int32(len(p.events))
1221+ p.addEvent(start, int32(data.Marker), EvOpenList)
1222+ p.addEvent(start, int32(data.NextNumber), EvNone)
1223+ p.stack = append(p.stack, ActiveContainer{Type: ContainerList, Start: start, Data: data})
1224+ p.stack[len(p.stack)-1].Data.NextNumber++
1225+}
+918,
-0
1@@ -0,0 +1,918 @@
2+package djot
3+
4+type Opener struct {
5+ MatchIndex int32
6+ Start int32
7+ Type EventType
8+ Char byte
9+ Explicit bool
10+}
11+
12+type InlineParser struct {
13+ source []byte
14+ events []Event
15+ stack []Opener
16+}
17+
18+func NewInlineParser(source []byte) *InlineParser {
19+ return &InlineParser{
20+ source: source,
21+ events: make([]Event, 0, 256),
22+ stack: make([]Opener, 0, 16),
23+ }
24+}
25+
26+func (p *InlineParser) Reset() {
27+ p.stack = p.stack[:0]
28+}
29+
30+func (p *InlineParser) Parse(blockEvents []Event) []Event {
31+ var textEvents []Event
32+ for i := 0; i < len(blockEvents); i++ {
33+ ev := blockEvents[i]
34+ if ev.Type == EvText || ev.Type == EvSoftBreak || ev.Type == EvHardBreak {
35+ textEvents = append(textEvents, ev)
36+ } else {
37+ if len(textEvents) > 0 {
38+ p.parseInlineBlock(textEvents)
39+ textEvents = textEvents[:0]
40+ }
41+ if ev.Type == EvClosePara || ev.Type == EvCloseHeading || ev.Type == EvCloseCaption || ev.Type == EvCloseListItem || ev.Type == EvCloseCell {
42+ p.Trim()
43+ }
44+ p.events = append(p.events, ev)
45+ p.Reset()
46+ }
47+ }
48+ if len(textEvents) > 0 {
49+ p.parseInlineBlock(textEvents)
50+ }
51+
52+ return p.events
53+}
54+
55+func (p *InlineParser) Trim() {
56+ if len(p.events) == 0 {
57+ return
58+ }
59+ i := len(p.events) - 1
60+ if p.events[i].Type == EvSoftBreak || p.events[i].Type == EvHardBreak {
61+ p.events = p.events[:i]
62+ i--
63+ if i >= 0 && p.events[i].Type == EvStr {
64+ end := p.events[i].End
65+ start := p.events[i].Start
66+ for end >= start && (p.source[end] == ' ' || p.source[end] == '\t') {
67+ end--
68+ }
69+ if end < start {
70+ p.events = p.events[:i]
71+ } else {
72+ p.events[i].End = end
73+ }
74+ }
75+ }
76+}
77+
78+func (p *InlineParser) findNextSpecialAhead(pos int32, end int32) int32 {
79+ for i := pos; i <= end; i++ {
80+ if isSpecial(p.source[i]) || p.source[i] == '{' {
81+ return i
82+ }
83+ }
84+ return end + 1
85+}
86+
87+func (p *InlineParser) parseInlineBlock(textEvents []Event) {
88+ if len(textEvents) == 0 {
89+ return
90+ }
91+
92+ blockEnd := textEvents[len(textEvents)-1].End
93+
94+ evIdx := 0
95+ for evIdx < len(textEvents) {
96+ ev := textEvents[evIdx]
97+ if ev.Type == EvSoftBreak {
98+ p.addEvent(ev.Start, ev.End, EvSoftBreak)
99+ evIdx++
100+ continue
101+ }
102+ if ev.Type == EvHardBreak {
103+ p.addEvent(ev.Start, ev.End, EvHardBreak)
104+ evIdx++
105+ continue
106+ }
107+
108+ start := ev.Start
109+ end := ev.End
110+ pos := start
111+
112+ for pos <= end {
113+ if pos >= int32(len(p.source)) {
114+ break
115+ }
116+
117+ b := p.source[pos]
118+
119+ if !isSpecial(b) && b != '{' {
120+ next := p.findNextSpecialAhead(pos+1, end)
121+ p.addEvent(pos, next-1, EvStr)
122+ pos = next
123+ continue
124+ }
125+
126+ if b == '{' {
127+ if pos+1 <= end {
128+ next := p.source[pos+1]
129+ if next == '_' || next == '*' || next == '~' || next == '^' || next == '-' || next == '+' || next == '=' || next == '\'' || next == '"' {
130+ p.addEvent(pos, pos, EvOpenInlineAttributes)
131+ newPos := p.handleEmphasis(next, pos+1, end)
132+ pos = newPos
133+ continue
134+ }
135+ }
136+
137+ attrEvents, nextPos, ok := ParseAttributes(p.source, pos, blockEnd+1)
138+ if ok {
139+ p.handleAttributeAttachment(pos, nextPos, attrEvents)
140+ pos = nextPos + 1
141+ for evIdx < len(textEvents) && pos > textEvents[evIdx].End {
142+ evIdx++
143+ }
144+ if evIdx < len(textEvents) && pos >= textEvents[evIdx].Start {
145+ end = textEvents[evIdx].End
146+ start = pos
147+ }
148+ continue
149+ } else {
150+ p.addEvent(pos, pos, EvStr)
151+ pos++
152+ continue
153+ }
154+ }
155+
156+ switch b {
157+ case '\\':
158+ if pos+1 <= end {
159+ next := p.source[pos+1]
160+ if next == ' ' {
161+ p.addEvent(pos, pos+1, EvNonBreakingSpace)
162+ pos += 2
163+ } else if isPunctuation(next) {
164+ p.addEvent(pos, pos+1, EvEscape)
165+ pos += 2
166+ } else {
167+ p.addEvent(pos, pos, EvStr)
168+ pos++
169+ }
170+ } else {
171+ p.addEvent(pos, pos, EvStr)
172+ pos++
173+ }
174+ case '*':
175+ pos = p.handleEmphasis(b, pos, end)
176+ case '_':
177+ pos = p.handleEmphasis(b, pos, end)
178+ case '^':
179+ pos = p.handleEmphasis(b, pos, end)
180+ case '~':
181+ pos = p.handleEmphasis(b, pos, end)
182+ case '"':
183+ p.handleDoubleQuote(pos, end)
184+ pos++
185+ case '\'':
186+ pos = p.handleSingleQuote(pos, end)
187+ case '[':
188+ if pos+1 <= blockEnd && p.source[pos+1] == '^' {
189+ closeIdx := p.findClosingBracketAhead(pos, textEvents, evIdx)
190+ if closeIdx != -1 {
191+ p.addEvent(pos, closeIdx, EvFootnoteReference)
192+ pos = closeIdx + 1
193+ for evIdx < len(textEvents) && pos > textEvents[evIdx].End {
194+ evIdx++
195+ }
196+ if evIdx < len(textEvents) && pos >= textEvents[evIdx].Start {
197+ end = textEvents[evIdx].End
198+ start = pos
199+ }
200+ continue
201+ }
202+ }
203+ p.addOpener(b, pos, EvOpenLinkText, false)
204+ pos++
205+ case '!':
206+ if pos+1 <= end && p.source[pos+1] == '[' {
207+ p.addOpener('[', pos+1, EvOpenImageText, false)
208+ pos += 2
209+ } else {
210+ p.addEvent(pos, pos, EvStr)
211+ pos++
212+ }
213+ case ']':
214+ pos = p.handleCloseBracketAhead(pos, textEvents, evIdx, blockEnd)
215+ for evIdx < len(textEvents) && pos > textEvents[evIdx].End {
216+ evIdx++
217+ }
218+ if evIdx < len(textEvents) && pos >= textEvents[evIdx].Start {
219+ end = textEvents[evIdx].End
220+ start = pos
221+ }
222+ case '(':
223+ if len(p.events) > 0 && p.events[len(p.events)-1].Type == EvCloseLinkText {
224+ destEnd := p.findClosingParenAhead(pos+1, textEvents, evIdx)
225+ if destEnd != -1 {
226+ p.addEvent(pos+1, destEnd-1, EvOpenDestination)
227+ p.addEvent(pos+1, destEnd-1, EvCloseDestination)
228+ pos = destEnd + 1
229+ for evIdx < len(textEvents) && pos > textEvents[evIdx].End {
230+ evIdx++
231+ }
232+ if evIdx < len(textEvents) && pos >= textEvents[evIdx].Start {
233+ end = textEvents[evIdx].End
234+ start = pos
235+ }
236+ continue
237+ }
238+ }
239+ p.addEvent(pos, pos, EvStr)
240+ pos++
241+ case '`':
242+ pos = p.handleVerbatimAhead(pos, textEvents, evIdx)
243+ for evIdx < len(textEvents) && pos > textEvents[evIdx].End {
244+ evIdx++
245+ }
246+ if evIdx < len(textEvents) && pos >= textEvents[evIdx].Start {
247+ end = textEvents[evIdx].End
248+ start = pos
249+ }
250+
251+ if pos < blockEnd && p.source[pos] == '{' {
252+ attrEvents, nextPos, ok := ParseAttributes(p.source, pos, blockEnd+1)
253+ if ok {
254+ isRaw := false
255+ if len(attrEvents) == 1 {
256+ ae := attrEvents[0]
257+ if ae.Type == EvAttrKey && p.source[ae.Start] == '=' {
258+ isRaw = true
259+ }
260+ }
261+
262+ if isRaw {
263+ p.events[len(p.events)-2].Type = EvRawInline
264+ p.events[len(p.events)-1].Type = EvRawInline
265+ p.handleAttributeAttachment(pos, nextPos, attrEvents)
266+ pos = nextPos + 1
267+ }
268+
269+ for evIdx < len(textEvents) && pos > textEvents[evIdx].End {
270+ evIdx++
271+ }
272+ if evIdx < len(textEvents) && pos >= textEvents[evIdx].Start {
273+ end = textEvents[evIdx].End
274+ start = pos
275+ }
276+ }
277+ }
278+ case '.':
279+ if pos+2 <= end && p.source[pos+1] == '.' && p.source[pos+2] == '.' {
280+ p.addEvent(pos, pos+2, EvSmartPunctuation)
281+ pos += 3
282+ } else {
283+ p.addEvent(pos, pos, EvStr)
284+ pos++
285+ }
286+ case '-':
287+ if (pos > 0 && p.source[pos-1] == '{') || (pos+1 <= end && p.source[pos+1] == '}') {
288+ pos = p.handleEmphasis(b, pos, end)
289+ continue
290+ }
291+
292+ count := int32(0)
293+ for pos+count <= end && p.source[pos+count] == '-' {
294+ count++
295+ }
296+
297+ if pos+count <= end && p.source[pos+count] == '}' {
298+ if count > 1 {
299+ count--
300+ } else {
301+ pos = p.handleEmphasis(b, pos, end)
302+ continue
303+ }
304+ }
305+
306+ if count == 1 {
307+ p.addEvent(pos, pos, EvStr)
308+ pos++
309+ } else {
310+ emd := int32(0)
311+ end2 := int32(0)
312+ if count%3 == 0 {
313+ emd = count / 3
314+ end2 = 0
315+ } else if count%2 == 0 {
316+ emd = 0
317+ end2 = count / 2
318+ } else if count%3 == 2 {
319+ emd = count / 3
320+ end2 = 1
321+ } else {
322+ emd = (count - 4) / 3
323+ end2 = 2
324+ }
325+ p2 := pos
326+ for i := int32(0); i < emd; i++ {
327+ p.addEvent(p2, p2+2, EvSmartPunctuation)
328+ p2 += 3
329+ }
330+ for i := int32(0); i < end2; i++ {
331+ p.addEvent(p2, p2+1, EvSmartPunctuation)
332+ p2 += 2
333+ }
334+ pos = p2
335+ }
336+ case '+':
337+ if (pos > 0 && p.source[pos-1] == '{') || (pos+1 <= end && p.source[pos+1] == '}') {
338+ pos = p.handleEmphasis(b, pos, end)
339+ continue
340+ } else {
341+ p.addEvent(pos, pos, EvStr)
342+ pos++
343+ }
344+ case '=':
345+ if (pos > 0 && p.source[pos-1] == '{') || (pos+1 <= end && p.source[pos+1] == '}') {
346+ pos = p.handleEmphasis(b, pos, end)
347+ continue
348+ } else {
349+ p.addEvent(pos, pos, EvStr)
350+ pos++
351+ }
352+ case '<':
353+ pos = p.handleAutolink(pos, end)
354+ case ':':
355+ pos = p.handleEmoji(pos, end)
356+ case '$':
357+ count := int32(0)
358+ for pos+count <= end && p.source[pos+count] == '$' {
359+ count++
360+ }
361+ if pos+count <= end && p.source[pos+count] == '`' {
362+ pos = p.handleMathVerbatim(pos, pos+count, end, textEvents, evIdx)
363+ for evIdx < len(textEvents) && pos > textEvents[evIdx].End {
364+ evIdx++
365+ }
366+ if evIdx < len(textEvents) && pos >= textEvents[evIdx].Start {
367+ end = textEvents[evIdx].End
368+ start = pos
369+ }
370+ } else {
371+ pos = p.handleMath(pos, end)
372+ }
373+ default:
374+ p.addEvent(pos, pos, EvStr)
375+ pos++
376+ }
377+ start = pos
378+ }
379+ evIdx++
380+ }
381+
382+ for _, opener := range p.stack {
383+ if opener.MatchIndex != -1 && (opener.Char == '\'' || opener.Char == '"') {
384+ p.events[opener.MatchIndex].Type = EvSmartPunctuation
385+ }
386+ }
387+}
388+
389+func (p *InlineParser) findClosingParenAhead(start int32, textEvents []Event, startEvIdx int) int32 {
390+ count := 0
391+ for j := startEvIdx; j < len(textEvents); j++ {
392+ ev := textEvents[j]
393+ if ev.Type == EvSoftBreak {
394+ continue
395+ }
396+ s := ev.Start
397+ if j == startEvIdx {
398+ s = start
399+ }
400+ for i := s; i <= ev.End; i++ {
401+ if p.source[i] == '(' {
402+ count++
403+ } else if p.source[i] == ')' {
404+ count--
405+ if count == 0 {
406+ return i
407+ }
408+ }
409+ }
410+ }
411+ return -1
412+}
413+
414+func (p *InlineParser) findClosingBracketAhead(start int32, textEvents []Event, startEvIdx int) int32 {
415+ count := 0
416+ for j := startEvIdx; j < len(textEvents); j++ {
417+ ev := textEvents[j]
418+ if ev.Type == EvSoftBreak {
419+ continue
420+ }
421+ s := ev.Start
422+ if j == startEvIdx {
423+ s = start
424+ }
425+ for i := s; i <= ev.End; i++ {
426+ if p.source[i] == '[' {
427+ count++
428+ } else if p.source[i] == ']' {
429+ count--
430+ if count == 0 {
431+ return i
432+ }
433+ }
434+ }
435+ }
436+ return -1
437+}
438+
439+func (p *InlineParser) handleCloseBracketAhead(pos int32, textEvents []Event, evIdx int, blockEnd int32) int32 {
440+ for i := len(p.stack) - 1; i >= 0; i-- {
441+ opener := p.stack[i]
442+ if opener.MatchIndex != -1 && opener.Char == '[' {
443+ next := pos + 1
444+ if next <= blockEnd && p.source[next] == '(' {
445+ closeIdx := p.findClosingParenAhead(next, textEvents, evIdx)
446+ if closeIdx != -1 {
447+ p.events[opener.MatchIndex].Type = opener.Type
448+ p.addEvent(next+1, closeIdx-1, EvOpenDestination)
449+ p.addEvent(next+1, closeIdx-1, EvCloseDestination)
450+ p.addEvent(pos, pos, closeTypeFor(opener.Type))
451+ p.stack = p.stack[:i]
452+ return closeIdx + 1
453+ }
454+ }
455+
456+ if next <= blockEnd && p.source[next] == '[' {
457+ closeIdx := p.findClosingBracketAhead(next, textEvents, evIdx)
458+ if closeIdx != -1 {
459+ p.events[opener.MatchIndex].Type = opener.Type
460+ p.addEvent(next+1, closeIdx-1, EvOpenReference)
461+ p.addEvent(next+1, closeIdx-1, EvCloseReference)
462+ p.addEvent(pos, pos, closeTypeFor(opener.Type))
463+ p.stack = p.stack[:i]
464+ return closeIdx + 1
465+ }
466+ }
467+
468+ if next <= blockEnd && p.source[next] == '{' {
469+ attrEvents, attrEndPos, ok := ParseAttributes(p.source, next, blockEnd+1)
470+ if ok {
471+ p.events[opener.MatchIndex].Type = EvOpenSpan
472+ p.addEvent(pos, pos, EvCloseSpan)
473+ p.addEvent(next, next, EvOpenInlineAttributes)
474+ p.events = append(p.events, attrEvents...)
475+ p.addEvent(attrEndPos, attrEndPos, EvCloseAttributes)
476+ p.stack = p.stack[:i]
477+ return attrEndPos + 1
478+ }
479+ }
480+
481+ p.events[opener.MatchIndex].Type = EvStr
482+ p.addEvent(pos, pos, EvStr)
483+ p.stack = p.stack[:i]
484+ return pos + 1
485+ }
486+ }
487+ p.addEvent(pos, pos, EvStr)
488+ return pos + 1
489+}
490+
491+func (p *InlineParser) handleMathVerbatim(dollarPos, backtickPos, end int32, textEvents []Event, startEvIdx int) int32 {
492+ count := int32(0)
493+ pos := backtickPos
494+ ev := textEvents[startEvIdx]
495+ for pos <= ev.End && p.source[pos] == '`' {
496+ count++
497+ pos++
498+ }
499+
500+ contentStart := pos
501+
502+ for j := startEvIdx; j < len(textEvents); j++ {
503+ tev := textEvents[j]
504+ if tev.Type == EvSoftBreak {
505+ continue
506+ }
507+
508+ s := tev.Start
509+ if j == startEvIdx {
510+ s = pos
511+ }
512+
513+ for i := s; i <= tev.End; i++ {
514+ if p.source[i] == '`' {
515+ c := int32(0)
516+ for i+c <= tev.End && p.source[i+c] == '`' {
517+ c++
518+ }
519+ if c == count {
520+ contentEnd := i - 1
521+ dollarCount := backtickPos - dollarPos
522+ etype := EvInlineMath
523+ if dollarCount == 2 {
524+ etype = EvDisplayMath
525+ }
526+ p.addEvent(contentStart, contentEnd, etype)
527+ return i + count
528+ }
529+ i += c - 1
530+ }
531+ }
532+ }
533+
534+ lastEv := textEvents[len(textEvents)-1]
535+ dollarCount := backtickPos - dollarPos
536+ etype := EvInlineMath
537+ if dollarCount == 2 {
538+ etype = EvDisplayMath
539+ }
540+ p.addEvent(contentStart, lastEv.End, etype)
541+ return lastEv.End + 1
542+}
543+
544+func (p *InlineParser) handleVerbatimAhead(pos int32, textEvents []Event, startEvIdx int) int32 {
545+ count := int32(0)
546+
547+ ev := textEvents[startEvIdx]
548+ for pos <= ev.End && p.source[pos] == '`' {
549+ count++
550+ pos++
551+ }
552+
553+ contentStart := pos
554+
555+ for j := startEvIdx; j < len(textEvents); j++ {
556+ tev := textEvents[j]
557+ if tev.Type == EvSoftBreak {
558+ continue
559+ }
560+
561+ s := tev.Start
562+ if j == startEvIdx {
563+ s = pos
564+ }
565+
566+ for i := s; i <= tev.End; i++ {
567+ if p.source[i] == '`' {
568+ c := int32(0)
569+ for i+c <= tev.End && p.source[i+c] == '`' {
570+ c++
571+ }
572+ if c == count {
573+ contentEnd := i - 1
574+
575+ if count == 1 && contentEnd-contentStart+1 >= 2 && p.source[contentStart] == ' ' && p.source[contentEnd] == ' ' {
576+ allSpaces := true
577+ for k := contentStart; k <= contentEnd; k++ {
578+ if p.source[k] != ' ' {
579+ allSpaces = false
580+ break
581+ }
582+ }
583+ if !allSpaces {
584+ contentStart++
585+ contentEnd--
586+ }
587+ }
588+
589+ p.addEvent(contentStart, contentEnd, EvOpenVerbatim)
590+ p.addEvent(contentStart, contentEnd, EvCloseVerbatim)
591+ return i + count
592+ }
593+ i += c - 1
594+ }
595+ }
596+ }
597+
598+ lastEv := textEvents[len(textEvents)-1]
599+ p.addEvent(contentStart, lastEv.End, EvOpenVerbatim)
600+ p.addEvent(contentStart, lastEv.End, EvCloseVerbatim)
601+ return lastEv.End + 1
602+}
603+
604+func (p *InlineParser) handleAttributeAttachment(braceStart, braceEnd int32, attrEvents []Event) {
605+ p.addEvent(braceStart, braceStart, EvOpenInlineAttributes)
606+ p.events = append(p.events, attrEvents...)
607+ p.addEvent(braceEnd, braceEnd, EvCloseAttributes)
608+}
609+
610+func isSpecial(b byte) bool {
611+ switch b {
612+ case '\\', '`', '_', '*', '~', '^', '$', '[', ']', '!', '<', '.', '-', ':', '"', '\'', '+', '=':
613+ return true
614+ }
615+ return false
616+}
617+
618+func closeTypeFor(openType EventType) EventType {
619+ if openType == EvOpenLinkText {
620+ return EvCloseLinkText
621+ }
622+ return EvCloseImageText
623+}
624+
625+func (p *InlineParser) addEvent(start, end int32, etype EventType) int32 {
626+ p.events = append(p.events, Event{Start: start, End: end, Type: etype})
627+ return int32(len(p.events) - 1)
628+}
629+
630+func (p *InlineParser) addOpener(char byte, pos int32, etype EventType, explicit bool) {
631+ idx := p.addEvent(pos, pos, etype)
632+ p.stack = append(p.stack, Opener{
633+ MatchIndex: idx,
634+ Start: pos,
635+ Type: etype,
636+ Char: char,
637+ Explicit: explicit,
638+ })
639+}
640+
641+func (p *InlineParser) handleEmphasis(b byte, pos int32, end int32) int32 {
642+ lastMatch := Event{Type: EvNone}
643+ if len(p.events) > 0 {
644+ lastMatch = p.events[len(p.events)-1]
645+ }
646+
647+ isExplicitOpen := lastMatch.Type == EvOpenInlineAttributes && lastMatch.Start == pos-1
648+ isExplicitClose := pos+1 <= end && p.source[pos+1] == '}'
649+
650+ canOpen := pos+1 <= end && !isSpace(p.source[pos+1])
651+ canClose := pos-1 >= 0 && !isSpace(p.source[pos-1])
652+
653+ if isExplicitOpen {
654+ canOpen = true
655+ canClose = false
656+ }
657+ if isExplicitClose {
658+ canClose = true
659+ canOpen = false
660+ }
661+
662+ if canClose {
663+ var openerIdx = -1
664+ for i := len(p.stack) - 1; i >= 0; i-- {
665+ if p.stack[i].MatchIndex != -1 && p.stack[i].Char == b && p.stack[i].Explicit == isExplicitClose && p.stack[i].Type != EvOpenLinkText && p.stack[i].Type != EvOpenImageText {
666+ openerIdx = i
667+ break
668+ }
669+ }
670+
671+ if openerIdx != -1 {
672+ opener := p.stack[openerIdx]
673+ if opener.Start != pos-1 {
674+ p.handleCloseEmphasisAt(openerIdx, pos, isExplicitClose)
675+ if isExplicitClose {
676+ return pos + 2
677+ }
678+ return pos + 1
679+ }
680+ }
681+ }
682+
683+ if canOpen {
684+ startPos := pos
685+ if isExplicitOpen {
686+ startPos = pos - 1
687+ p.events = p.events[:len(p.events)-1]
688+ p.addOpener(b, startPos, EvStr, isExplicitOpen)
689+ p.addEvent(pos, pos, EvStr)
690+ } else {
691+ p.addOpener(b, startPos, EvStr, isExplicitOpen)
692+ }
693+ return pos + 1
694+ } else {
695+ p.addEvent(pos, pos, EvStr)
696+ if isExplicitClose {
697+ p.addEvent(pos+1, pos+1, EvStr)
698+ return pos + 2
699+ }
700+ return pos + 1
701+ }
702+}
703+
704+func (p *InlineParser) handleCloseEmphasisAt(openerIdx int, pos int32, explicit bool) {
705+ opener := p.stack[openerIdx]
706+ var b = opener.Char
707+ var openType, closeType EventType
708+ switch b {
709+ case '_':
710+ openType, closeType = EvOpenEmph, EvCloseEmph
711+ case '*':
712+ openType, closeType = EvOpenStrong, EvCloseStrong
713+ case '~':
714+ openType, closeType = EvOpenSubscript, EvCloseSubscript
715+ case '^':
716+ openType, closeType = EvOpenSuperscript, EvCloseSuperscript
717+ case '-':
718+ openType, closeType = EvOpenDelete, EvCloseDelete
719+ case '+':
720+ openType, closeType = EvOpenInsert, EvCloseInsert
721+ case '=':
722+ openType, closeType = EvOpenMark, EvCloseMark
723+ case '\'':
724+ openType, closeType = EvOpenSingleQuoted, EvCloseSingleQuoted
725+ case '"':
726+ openType, closeType = EvOpenDoubleQuoted, EvCloseDoubleQuoted
727+ }
728+ p.events[opener.MatchIndex].Type = openType
729+ p.events[opener.MatchIndex].Start = opener.Start
730+
731+ if opener.Explicit {
732+ if opener.MatchIndex+1 < int32(len(p.events)) && p.events[opener.MatchIndex+1].Start == opener.Start+1 && p.events[opener.MatchIndex+1].Type == EvStr {
733+ p.events[opener.MatchIndex+1].Type = EvNone
734+ }
735+ }
736+
737+ endPos := pos
738+ if explicit {
739+ endPos = pos + 1
740+ }
741+ p.addEvent(pos, endPos, closeType)
742+
743+ p.clearOpeners(opener.Start, pos)
744+ p.stack = p.stack[:openerIdx]
745+}
746+
747+func (p *InlineParser) clearOpeners(start, end int32) {
748+ for i := range p.stack {
749+ if p.stack[i].Start > start && p.stack[i].Start < end {
750+ if p.stack[i].MatchIndex != -1 {
751+ p.events[p.stack[i].MatchIndex].Type = EvStr
752+ p.stack[i].MatchIndex = -1
753+ }
754+ }
755+ }
756+}
757+
758+func (p *InlineParser) handleMath(pos, end int32) int32 {
759+ start := pos
760+ pos++
761+ count := int32(1)
762+ if pos <= end && p.source[pos] == '$' {
763+ count = 2
764+ pos++
765+ }
766+ for pos <= end {
767+ if p.source[pos] == '$' {
768+ c := int32(0)
769+ for pos <= end && p.source[pos] == '$' {
770+ c++
771+ pos++
772+ }
773+ if c == count {
774+ etype := EvInlineMath
775+ if count == 2 {
776+ etype = EvDisplayMath
777+ }
778+ p.addEvent(start+count, pos-count-1, etype)
779+ return pos
780+ }
781+ } else {
782+ pos++
783+ }
784+ }
785+ p.addEvent(start, start+count-1, EvStr)
786+ return start + count
787+}
788+
789+func (p *InlineParser) handleAutolink(pos, end int32) int32 {
790+ start := pos
791+ hasAt := false
792+ hasSlash := false
793+ for pos <= end && p.source[pos] != '>' && p.source[pos] != ' ' {
794+ if p.source[pos] == '@' {
795+ hasAt = true
796+ } else if p.source[pos] == '/' {
797+ hasSlash = true
798+ }
799+ pos++
800+ }
801+ if pos <= end && p.source[pos] == '>' {
802+ etype := EvUrl
803+ if hasAt && !hasSlash {
804+ etype = EvEmail
805+ }
806+ p.addEvent(start+1, pos-1, etype)
807+ return pos + 1
808+ }
809+ p.addEvent(start, start, EvStr)
810+ return start + 1
811+}
812+
813+func (p *InlineParser) handleEmoji(pos, end int32) int32 {
814+ start := pos
815+ pos++
816+ for pos <= end && isAlphaLower(p.source[pos]) {
817+ pos++
818+ }
819+ if pos <= end && p.source[pos] == ':' {
820+ p.addEvent(start+1, pos-1, EvSymb)
821+ return pos + 1
822+ }
823+ p.addEvent(start, start, EvStr)
824+ return start + 1
825+}
826+
827+func (p *InlineParser) handleDoubleQuote(pos, end int32) {
828+ for i := len(p.stack) - 1; i >= 0; i-- {
829+ opener := p.stack[i]
830+ if opener.MatchIndex != -1 && opener.Char == '"' {
831+ p.events[opener.MatchIndex].Type = EvOpenDoubleQuoted
832+ p.addEvent(pos, pos, EvCloseDoubleQuoted)
833+ p.stack = p.stack[:i]
834+ return
835+ }
836+ }
837+ p.addOpener('"', pos, EvStr, false)
838+}
839+
840+func (p *InlineParser) handleSingleQuote(pos, end int32) int32 {
841+ isExplicitClose := pos+1 <= end && p.source[pos+1] == '}'
842+
843+ if isExplicitClose {
844+ for i := len(p.stack) - 1; i >= 0; i-- {
845+ if p.stack[i].MatchIndex != -1 && p.stack[i].Char == '\'' && p.stack[i].Explicit {
846+ opener := p.stack[i]
847+ p.events[opener.MatchIndex].Type = EvOpenSingleQuoted
848+ p.addEvent(pos, pos, EvCloseSingleQuoted)
849+ if opener.MatchIndex+1 < int32(len(p.events)) && p.events[opener.MatchIndex+1].Start == opener.Start+1 && p.events[opener.MatchIndex+1].Type == EvStr {
850+ p.events[opener.MatchIndex+1].Type = EvNone
851+ }
852+ p.stack = p.stack[:i]
853+ return pos + 2
854+ }
855+ }
856+ }
857+
858+ prevIsCloser := pos > 0 && (isAlnum(p.source[pos-1]) || isClosingPunct(p.source[pos-1]))
859+ nextDigit := pos+1 <= end && isDigit(p.source[pos+1])
860+
861+ canClose := pos > 0 && !isSpace(p.source[pos-1])
862+ canOpen := pos+1 <= end && !isSpace(p.source[pos+1]) &&
863+ (pos == 0 || isOpeningContext(p.source[pos-1]))
864+
865+ if prevIsCloser || nextDigit {
866+ for i := len(p.stack) - 1; i >= 0; i-- {
867+ if p.stack[i].MatchIndex != -1 && p.stack[i].Char == '\'' {
868+ p.events[p.stack[i].MatchIndex].Type = EvOpenSingleQuoted
869+ p.addEvent(pos, pos, EvCloseSingleQuoted)
870+ p.stack = p.stack[:i]
871+ return pos + 1
872+ }
873+ }
874+ p.addEvent(pos, pos, EvSmartPunctuation)
875+ return pos + 1
876+ }
877+
878+ if canClose && !canOpen {
879+ for i := len(p.stack) - 1; i >= 0; i-- {
880+ opener := p.stack[i]
881+ if opener.MatchIndex != -1 && opener.Char == '\'' {
882+ p.events[opener.MatchIndex].Type = EvOpenSingleQuoted
883+ p.addEvent(pos, pos, EvCloseSingleQuoted)
884+ p.stack = p.stack[:i]
885+ return pos + 1
886+ }
887+ }
888+ }
889+
890+ if canOpen {
891+ p.addOpener('\'', pos, EvStr, false)
892+ } else {
893+ p.addEvent(pos, pos, EvSmartPunctuation)
894+ }
895+ return pos + 1
896+}
897+
898+func isAlnum(b byte) bool {
899+ return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9')
900+}
901+
902+func isClosingPunct(b byte) bool {
903+ return b == ')' || b == ']' || b == '}' ||
904+ b == '.' || b == ',' || b == ';' || b == ':' ||
905+ b == '!' || b == '?'
906+}
907+
908+func isOpeningContext(b byte) bool {
909+ return b == ' ' || b == '\t' || b == '\n' || b == '\r' ||
910+ b == '"' || b == '\'' || b == '-' || b == '(' || b == '['
911+}
912+
913+func isPunctuation(b byte) bool {
914+ switch b {
915+ case '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_', '`', '{', '|', '}', '~':
916+ return true
917+ }
918+ return false
919+}
+306,
-0
1@@ -0,0 +1,306 @@
2+package djot
3+
4+func isThematicBreak(source []byte, start, end int32) bool {
5+ count := 0
6+ for i := start; i < end; i++ {
7+ b := source[i]
8+ if b == '-' || b == '*' {
9+ count++
10+ } else if b == ' ' || b == '\t' {
11+ continue
12+ } else {
13+ return false
14+ }
15+ }
16+ return count >= 3
17+}
18+
19+func parseListMarker(source []byte, pos, end int32) (int32, ContainerData, bool) {
20+ if pos >= end {
21+ return pos, ContainerData{}, false
22+ }
23+
24+ startPos := pos
25+ b := source[pos]
26+
27+ if b == '-' || b == '+' || b == '*' {
28+ return checkListItemEnd(source, startPos, pos, end, b, false, 0, 0)
29+ }
30+
31+ if b == ':' {
32+ return checkListItemEnd(source, startPos, pos, end, ':', false, 0, 0)
33+ }
34+
35+ if isDigit(b) {
36+ p := pos
37+ for p < end && isDigit(source[p]) {
38+ p++
39+ }
40+ if p < end && (source[p] == '.' || source[p] == ')') {
41+ num := parseDecimal(source[pos:p])
42+ return checkListItemEnd(source, startPos, p, end, '1', true, num, source[p])
43+ }
44+ }
45+
46+ if isRomanLowerStart(b) {
47+ p := pos
48+ for p < end && isRomanLower(source[p]) {
49+ p++
50+ }
51+ if p < end && (source[p] == '.' || source[p] == ')') {
52+ num := parseRoman(source[pos:p])
53+ newPos, data, ok := checkListItemEnd(source, startPos, p, end, 'i', true, num, source[p])
54+ if ok && p == pos+1 {
55+ data.MarkerAmbiguous = true
56+ }
57+ if ok {
58+ data.MarkerFirstChar = b
59+ }
60+ return newPos, data, ok
61+ }
62+ }
63+
64+ if isAlphaLower(b) {
65+ if pos+1 < end && (source[pos+1] == '.' || source[pos+1] == ')') {
66+ num := int(b - 'a' + 1)
67+ newPos, data, ok := checkListItemEnd(source, startPos, pos+1, end, 'a', true, num, source[pos+1])
68+ if ok {
69+ data.MarkerFirstChar = b
70+ }
71+ return newPos, data, ok
72+ }
73+ }
74+
75+ if isRomanUpperStart(b) {
76+ p := pos
77+ for p < end && isRomanUpper(source[p]) {
78+ p++
79+ }
80+ if p < end && (source[p] == '.' || source[p] == ')') {
81+ num := parseRoman(source[pos:p])
82+ newPos, data, ok := checkListItemEnd(source, startPos, p, end, 'I', true, num, source[p])
83+ if ok && p == pos+1 {
84+ data.MarkerAmbiguous = true
85+ }
86+ if ok {
87+ data.MarkerFirstChar = b
88+ }
89+ return newPos, data, ok
90+ }
91+ }
92+
93+ if isAlphaUpper(b) {
94+ if pos+1 < end && (source[pos+1] == '.' || source[pos+1] == ')') {
95+ num := int(b - 'A' + 1)
96+ newPos, data, ok := checkListItemEnd(source, startPos, pos+1, end, 'A', true, num, source[pos+1])
97+ if ok {
98+ data.MarkerFirstChar = b
99+ }
100+ return newPos, data, ok
101+ }
102+ }
103+
104+ if b == '(' {
105+ p := pos + 1
106+ if p < end {
107+ b2 := source[p]
108+
109+ if isDigit(b2) {
110+ start := p
111+ for p < end && isDigit(source[p]) {
112+ p++
113+ }
114+ if p < end && source[p] == ')' {
115+ num := parseDecimal(source[start:p])
116+ newPos, data, ok := checkListItemEnd(source, startPos, p, end, '1', true, num, 'X')
117+ if ok {
118+ data.MarkerFirstChar = b2
119+ }
120+ return newPos, data, ok
121+ }
122+ }
123+
124+ if isAlphaLower(b2) && p+1 < end && source[p+1] == ')' {
125+ num := int(b2 - 'a' + 1)
126+ newPos, data, ok := checkListItemEnd(source, startPos, p+1, end, 'a', true, num, 'X')
127+ if ok {
128+ data.MarkerFirstChar = b2
129+ if isRomanLowerStart(b2) {
130+ data.MarkerAmbiguous = true
131+ }
132+ }
133+ return newPos, data, ok
134+ }
135+ if isAlphaUpper(b2) && p+1 < end && source[p+1] == ')' {
136+ num := int(b2 - 'A' + 1)
137+ newPos, data, ok := checkListItemEnd(source, startPos, p+1, end, 'A', true, num, 'X')
138+ if ok {
139+ data.MarkerFirstChar = b2
140+ if isRomanUpperStart(b2) {
141+ data.MarkerAmbiguous = true
142+ }
143+ }
144+ return newPos, data, ok
145+ }
146+
147+ if isRomanLowerStart(b2) {
148+ start := p
149+ for p < end && isRomanLower(source[p]) {
150+ p++
151+ }
152+ if p < end && source[p] == ')' {
153+ if p > start+1 {
154+ num := parseRoman(source[start:p])
155+ newPos, data, ok := checkListItemEnd(source, startPos, p, end, 'i', true, num, 'X')
156+ if ok {
157+ data.MarkerFirstChar = b2
158+ }
159+ return newPos, data, ok
160+ }
161+ }
162+ }
163+
164+ if isRomanUpperStart(b2) {
165+ start := p
166+ for p < end && isRomanUpper(source[p]) {
167+ p++
168+ }
169+ if p < end && source[p] == ')' {
170+ if p > start+1 {
171+ num := parseRoman(source[start:p])
172+ newPos, data, ok := checkListItemEnd(source, startPos, p, end, 'I', true, num, 'X')
173+ if ok {
174+ data.MarkerFirstChar = b2
175+ }
176+ return newPos, data, ok
177+ }
178+ }
179+ }
180+ }
181+ }
182+
183+ return startPos, ContainerData{}, false
184+}
185+
186+func checkListItemEnd(source []byte, start, pos, end int32, style byte, ordered bool, num int, endChar byte) (int32, ContainerData, bool) {
187+ if pos+1 == end || source[pos+1] == ' ' || source[pos+1] == '\t' {
188+ newPos := pos + 1
189+ for newPos < end && (source[newPos] == ' ' || source[newPos] == '\t') {
190+ newPos++
191+ }
192+ indent := newPos - start
193+
194+ checked := false
195+ isTask := false
196+ if !ordered && (style == '-' || style == '+' || style == '*') && newPos+3 <= end && source[newPos] == '[' {
197+ if source[newPos+1] == ' ' && source[newPos+2] == ']' {
198+ isTask = true
199+ newPos += 3
200+ } else if (source[newPos+1] == 'x' || source[newPos+1] == 'X') && source[newPos+2] == ']' {
201+ isTask = true
202+ checked = true
203+ newPos += 3
204+ }
205+
206+ if isTask {
207+ if newPos == end || source[newPos] == ' ' || source[newPos] == '\t' {
208+ for newPos < end && (source[newPos] == ' ' || source[newPos] == '\t') {
209+ newPos++
210+ }
211+ indent = newPos - start
212+ } else {
213+ isTask = false
214+ checked = false
215+ newPos = pos + 1
216+ for newPos < end && (source[newPos] == ' ' || source[newPos] == '\t') {
217+ newPos++
218+ }
219+ indent = newPos - start
220+ }
221+ }
222+ }
223+
224+ if !ordered && indent == 1 && newPos < end {
225+ return start, ContainerData{}, false
226+ }
227+
228+ return newPos, ContainerData{Marker: style, MarkerEnd: endChar, Ordered: ordered, IsTask: isTask, Checked: checked, Tight: true, NextNumber: num}, true
229+ }
230+ return start, ContainerData{}, false
231+}
232+
233+func parseDecimal(b []byte) int {
234+ res := 0
235+ for _, c := range b {
236+ res = res*10 + int(c-'0')
237+ }
238+ return res
239+}
240+
241+func parseRoman(b []byte) int {
242+ val := func(c byte) int {
243+ switch c {
244+ case 'i', 'I':
245+ return 1
246+ case 'v', 'V':
247+ return 5
248+ case 'x', 'X':
249+ return 10
250+ case 'l', 'L':
251+ return 50
252+ case 'c', 'C':
253+ return 100
254+ case 'd', 'D':
255+ return 500
256+ case 'm', 'M':
257+ return 1000
258+ }
259+ return 0
260+ }
261+ res := 0
262+ for i := 0; i < len(b); i++ {
263+ v := val(b[i])
264+ if i+1 < len(b) && val(b[i+1]) > v {
265+ res -= v
266+ } else {
267+ res += v
268+ }
269+ }
270+ return res
271+}
272+
273+func isDigit(b byte) bool {
274+ return b >= '0' && b <= '9'
275+}
276+
277+func isAlphaLower(b byte) bool {
278+ return b >= 'a' && b <= 'z'
279+}
280+
281+func isAlphaUpper(b byte) bool {
282+ return b >= 'A' && b <= 'Z'
283+}
284+
285+func isRomanLowerStart(b byte) bool {
286+ switch b {
287+ case 'i', 'v', 'x', 'l', 'c', 'd', 'm':
288+ return true
289+ }
290+ return false
291+}
292+
293+func isRomanLower(b byte) bool {
294+ return isRomanLowerStart(b)
295+}
296+
297+func isRomanUpperStart(b byte) bool {
298+ switch b {
299+ case 'I', 'V', 'X', 'L', 'C', 'D', 'M':
300+ return true
301+ }
302+ return false
303+}
304+
305+func isRomanUpper(b byte) bool {
306+ return isRomanUpperStart(b)
307+}
+17,
-0
1@@ -0,0 +1,17 @@
2+package djot
3+
4+func Parse(source []byte) *Document {
5+ doc := &Document{Source: source}
6+ bp := NewBlockParser(source)
7+
8+ blockEvents := bp.Parse()
9+
10+ ip := NewInlineParser(source)
11+ inlineEvents := ip.Parse(blockEvents)
12+
13+ as := Assembler{doc: doc}
14+ as.Assemble(inlineEvents)
15+ doc.Events = inlineEvents
16+
17+ return doc
18+}
+3,
-0
1@@ -0,0 +1,3 @@
2+module aruu/mkman
3+
4+go 1.21
+600,
-0
1@@ -0,0 +1,600 @@
2+package main
3+
4+import (
5+ "bufio"
6+ "flag"
7+ "fmt"
8+ "os"
9+ "path/filepath"
10+ "strings"
11+ "time"
12+
13+ "aruu/mkman/djot"
14+)
15+
16+// Parse config.mk variables and values
17+//
18+type Config map[string]string
19+
20+func parseConfigMk(path string) (Config, error) {
21+ f, err := os.Open(path)
22+ if err != nil {
23+ return nil, err
24+ }
25+ defer f.Close()
26+
27+ raw := make(Config)
28+ sc := bufio.NewScanner(f)
29+ for sc.Scan() {
30+ line := strings.TrimSpace(sc.Text())
31+ if line == "" || strings.HasPrefix(line, "#") {
32+ continue
33+ }
34+ eq := strings.IndexByte(line, '=')
35+ if eq < 0 {
36+ continue
37+ }
38+ k := strings.TrimSpace(line[:eq])
39+ v := strings.TrimSpace(line[eq+1:])
40+ raw[k] = v
41+ }
42+ if err := sc.Err(); err != nil {
43+ return nil, err
44+ }
45+
46+ cfg := make(Config, len(raw))
47+ for k, v := range raw {
48+ cfg[k] = expandVars(v, raw)
49+ }
50+ return cfg, nil
51+}
52+
53+func expandVars(s string, env Config) string {
54+ for {
55+ start := strings.Index(s, "$(")
56+ if start < 0 {
57+ break
58+ }
59+ end := strings.Index(s[start:], ")")
60+ if end < 0 {
61+ break
62+ }
63+ end += start
64+ varname := s[start+2 : end]
65+ replacement := ""
66+ if v, ok := env[varname]; ok {
67+ replacement = v
68+ }
69+ s = s[:start] + replacement + s[end+1:]
70+ }
71+ return s
72+}
73+
74+func (cfg Config) isEnabled(key string) bool {
75+ return cfg[key] == "1"
76+}
77+
78+// Preprocessor condition stack for nested feature blocks
79+//
80+type ifFrame struct {
81+ active bool
82+ seen bool
83+}
84+
85+type ifStack struct {
86+ frames []ifFrame
87+}
88+
89+func (s *ifStack) push(active, seen bool) {
90+ s.frames = append(s.frames, ifFrame{active: active, seen: seen})
91+}
92+
93+func (s *ifStack) pop() {
94+ if len(s.frames) > 0 {
95+ s.frames = s.frames[:len(s.frames)-1]
96+ }
97+}
98+
99+func (s *ifStack) parentActive() bool {
100+ for i := 0; i < len(s.frames)-1; i++ {
101+ if !s.frames[i].active {
102+ return false
103+ }
104+ }
105+ return true
106+}
107+
108+func (s *ifStack) globallyActive() bool {
109+ for _, f := range s.frames {
110+ if !f.active {
111+ return false
112+ }
113+ }
114+ return true
115+}
116+
117+func (s *ifStack) top() *ifFrame {
118+ if len(s.frames) == 0 {
119+ return nil
120+ }
121+ return &s.frames[len(s.frames)-1]
122+}
123+
124+// Parse and evaluate preprocessor feature conditions
125+//
126+func evalCondition(expr string, cfg Config) bool {
127+ expr = strings.TrimSpace(expr)
128+ if strings.HasPrefix(expr, "defined(") && strings.HasSuffix(expr, ")") {
129+ key := expr[8 : len(expr)-1]
130+ _, ok := cfg[key]
131+ return ok
132+ }
133+ if strings.HasPrefix(expr, "!defined(") && strings.HasSuffix(expr, ")") {
134+ key := expr[9 : len(expr)-1]
135+ _, ok := cfg[key]
136+ return !ok
137+ }
138+ if idx := strings.Index(expr, "=="); idx >= 0 {
139+ lhs := strings.TrimSpace(expr[:idx])
140+ rhs := strings.TrimSpace(expr[idx+2:])
141+ return cfg[lhs] == rhs
142+ }
143+ if idx := strings.Index(expr, "!="); idx >= 0 {
144+ lhs := strings.TrimSpace(expr[:idx])
145+ rhs := strings.TrimSpace(expr[idx+2:])
146+ return cfg[lhs] != rhs
147+ }
148+ if strings.HasPrefix(expr, "!") {
149+ key := strings.TrimSpace(expr[1:])
150+ return cfg[key] != "1"
151+ }
152+ return cfg[expr] == "1"
153+}
154+
155+// Scanning and parsing djot comments in c source files
156+//
157+func formatToken(tok string) string {
158+ if strings.HasPrefix(tok, "-") {
159+ return "`" + tok + "`"
160+ }
161+ if len(tok) >= 2 {
162+ first := tok[0]
163+ last := tok[len(tok)-1]
164+ if (first == '[' && last == ']') || (first == '<' && last == '>') || (first == '(' && last == ')') {
165+ inner := tok[1 : len(tok)-1]
166+ if strings.HasPrefix(inner, "-") {
167+ return string(first) + "`" + inner + "`" + string(last)
168+ }
169+ return string(first) + "_" + inner + "_" + string(last)
170+ }
171+ }
172+ return "_" + tok + "_"
173+}
174+
175+func parseNameSummary(line string) (string, string, bool) {
176+ line = strings.TrimSpace(line)
177+ if idx := strings.Index(line, ":"); idx >= 0 {
178+ return strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+1:]), true
179+ }
180+ if idx := strings.Index(line, " \\- "); idx >= 0 {
181+ return strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+4:]), true
182+ }
183+ if idx := strings.Index(line, " - "); idx >= 0 {
184+ return strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+3:]), true
185+ }
186+ return "", "", false
187+}
188+
189+func isOptionPattern(s string) (string, string, bool) {
190+ s = strings.TrimSpace(s)
191+ if !strings.HasPrefix(s, "-") {
192+ return "", "", false
193+ }
194+ idx := strings.Index(s, ":")
195+ if idx < 0 {
196+ return "", "", false
197+ }
198+ opt := strings.TrimSpace(s[:idx])
199+ desc := strings.TrimSpace(s[idx+1:])
200+ fields := strings.Fields(opt)
201+ if len(fields) == 0 || !strings.HasPrefix(fields[0], "-") {
202+ return "", "", false
203+ }
204+ return opt, desc, true
205+}
206+
207+func scanC(path string, cfg Config) (string, error) {
208+ contentBytes, err := os.ReadFile(path)
209+ if err != nil {
210+ return "", err
211+ }
212+ contentStr := string(contentBytes)
213+
214+ if !strings.Contains(contentStr, "?man") {
215+ // Original parser mode using explicit man markers
216+ var djotBuf strings.Builder
217+ stack := &ifStack{}
218+ sc := bufio.NewScanner(strings.NewReader(contentStr))
219+ sc.Buffer(make([]byte, 1<<20), 1<<20)
220+ inBlockComment := false
221+
222+ for sc.Scan() {
223+ line := sc.Text()
224+ trimmed := strings.TrimSpace(line)
225+
226+ if inBlockComment {
227+ if trimmed == "*/" || trimmed == "* /" {
228+ inBlockComment = false
229+ djotBuf.WriteByte('\n')
230+ } else {
231+ stripped := trimmed
232+ if strings.HasPrefix(stripped, "* ") {
233+ stripped = stripped[2:]
234+ } else if stripped == "*" {
235+ stripped = ""
236+ }
237+ djotBuf.WriteString(stripped)
238+ djotBuf.WriteByte('\n')
239+ }
240+ continue
241+ }
242+
243+ if strings.HasPrefix(trimmed, "#") {
244+ directive := trimmed[1:]
245+ if ci := strings.Index(directive, "//"); ci >= 0 {
246+ directive = directive[:ci]
247+ }
248+ directive = strings.TrimSpace(directive)
249+
250+ switch {
251+ case strings.HasPrefix(directive, "ifdef "):
252+ key := strings.TrimSpace(directive[6:])
253+ _, defined := cfg[key]
254+ pa := stack.parentActive()
255+ stack.push(pa && defined, defined)
256+ case strings.HasPrefix(directive, "ifndef "):
257+ key := strings.TrimSpace(directive[7:])
258+ _, defined := cfg[key]
259+ pa := stack.parentActive()
260+ stack.push(pa && !defined, !defined)
261+ case strings.HasPrefix(directive, "if "):
262+ expr := strings.TrimSpace(directive[3:])
263+ result := evalCondition(expr, cfg)
264+ pa := stack.parentActive()
265+ stack.push(pa && result, result)
266+ case directive == "else":
267+ if top := stack.top(); top != nil {
268+ pa := stack.parentActive()
269+ newActive := pa && !top.seen
270+ top.active = newActive
271+ top.seen = true
272+ }
273+ case strings.HasPrefix(directive, "elif "):
274+ if top := stack.top(); top != nil {
275+ expr := strings.TrimSpace(directive[5:])
276+ result := evalCondition(expr, cfg)
277+ pa := stack.parentActive()
278+ newActive := pa && !top.seen && result
279+ top.active = newActive
280+ if result {
281+ top.seen = true
282+ }
283+ }
284+ case directive == "endif":
285+ stack.pop()
286+ }
287+ continue
288+ }
289+
290+ if !stack.globallyActive() {
291+ continue
292+ }
293+
294+ if strings.Contains(trimmed, "/* !man") {
295+ startIdx := strings.Index(trimmed, "/* !man")
296+ rest := strings.TrimSpace(trimmed[startIdx+7:])
297+ if strings.HasSuffix(rest, "*/") {
298+ content := strings.TrimSpace(rest[:len(rest)-2])
299+ if content != "" {
300+ djotBuf.WriteString(content)
301+ djotBuf.WriteByte('\n')
302+ }
303+ } else {
304+ if rest != "" {
305+ djotBuf.WriteString(rest)
306+ djotBuf.WriteByte('\n')
307+ }
308+ inBlockComment = true
309+ }
310+ continue
311+ }
312+
313+ if idx := strings.Index(trimmed, "// !man"); idx >= 0 {
314+ content := strings.TrimSpace(trimmed[idx+7:])
315+ djotBuf.WriteString(content)
316+ djotBuf.WriteByte('\n')
317+ continue
318+ }
319+ }
320+
321+ if err := sc.Err(); err != nil {
322+ return "", err
323+ }
324+ return djotBuf.String(), nil
325+ }
326+
327+ // Heuristic parser mode using question mark man markers
328+ type optionDoc struct {
329+ opt string
330+ desc string
331+ }
332+ type sectionDoc struct {
333+ title string
334+ lines []string
335+ }
336+
337+ var name, summary, synopsis string
338+ var descriptionBody []string
339+ var options []optionDoc
340+ var otherSections []sectionDoc
341+ var currentSection *sectionDoc
342+ var lastOption *optionDoc
343+
344+ stack := &ifStack{}
345+ sc := bufio.NewScanner(strings.NewReader(contentStr))
346+ sc.Buffer(make([]byte, 1<<20), 1<<20)
347+ inBlockComment := false
348+ firstLineOfFirstBlock := true
349+
350+ for sc.Scan() {
351+ line := sc.Text()
352+ trimmed := strings.TrimSpace(line)
353+
354+ if inBlockComment {
355+ if trimmed == "*/" || trimmed == "* /" {
356+ inBlockComment = false
357+ } else {
358+ stripped := trimmed
359+ if strings.HasPrefix(stripped, "* ") {
360+ stripped = stripped[2:]
361+ } else if stripped == "*" {
362+ stripped = ""
363+ }
364+ if strings.HasPrefix(stripped, "// ?man ") {
365+ stripped = stripped[8:]
366+ } else if strings.HasPrefix(stripped, "// ?man") {
367+ stripped = stripped[7:]
368+ }
369+ if firstLineOfFirstBlock {
370+ firstLineOfFirstBlock = false
371+ if n, s, ok := parseNameSummary(stripped); ok {
372+ name = n
373+ summary = s
374+ continue
375+ }
376+ }
377+ if synopsis == "" && strings.HasPrefix(strings.ToLower(stripped), "usage:") {
378+ synopsis = strings.TrimSpace(stripped[6:])
379+ continue
380+ }
381+ if strings.HasPrefix(stripped, "## ") {
382+ title := strings.TrimSpace(stripped[3:])
383+ otherSections = append(otherSections, sectionDoc{title: title})
384+ currentSection = &otherSections[len(otherSections)-1]
385+ lastOption = nil
386+ } else {
387+ if opt, desc, ok := isOptionPattern(stripped); ok {
388+ options = append(options, optionDoc{opt: opt, desc: desc})
389+ lastOption = &options[len(options)-1]
390+ currentSection = nil
391+ } else {
392+ if currentSection != nil {
393+ currentSection.lines = append(currentSection.lines, stripped)
394+ } else {
395+ descriptionBody = append(descriptionBody, stripped)
396+ }
397+ }
398+ }
399+ }
400+ continue
401+ }
402+
403+ if strings.HasPrefix(trimmed, "#") {
404+ directive := trimmed[1:]
405+ if ci := strings.Index(directive, "//"); ci >= 0 {
406+ directive = directive[:ci]
407+ }
408+ directive = strings.TrimSpace(directive)
409+
410+ switch {
411+ case strings.HasPrefix(directive, "ifdef "):
412+ key := strings.TrimSpace(directive[6:])
413+ _, defined := cfg[key]
414+ pa := stack.parentActive()
415+ stack.push(pa && defined, defined)
416+ case strings.HasPrefix(directive, "ifndef "):
417+ key := strings.TrimSpace(directive[7:])
418+ _, defined := cfg[key]
419+ pa := stack.parentActive()
420+ stack.push(pa && !defined, !defined)
421+ case strings.HasPrefix(directive, "if "):
422+ expr := strings.TrimSpace(directive[3:])
423+ result := evalCondition(expr, cfg)
424+ pa := stack.parentActive()
425+ stack.push(pa && result, result)
426+ case directive == "else":
427+ if top := stack.top(); top != nil {
428+ pa := stack.parentActive()
429+ newActive := pa && !top.seen
430+ top.active = newActive
431+ top.seen = true
432+ }
433+ case strings.HasPrefix(directive, "elif "):
434+ if top := stack.top(); top != nil {
435+ expr := strings.TrimSpace(directive[5:])
436+ result := evalCondition(expr, cfg)
437+ pa := stack.parentActive()
438+ newActive := pa && !top.seen && result
439+ top.active = newActive
440+ if result {
441+ top.seen = true
442+ }
443+ }
444+ case directive == "endif":
445+ stack.pop()
446+ }
447+ continue
448+ }
449+
450+ if !stack.globallyActive() {
451+ continue
452+ }
453+
454+ if strings.Contains(trimmed, "/* ?man") {
455+ startIdx := strings.Index(trimmed, "/* ?man")
456+ rest := strings.TrimSpace(trimmed[startIdx+7:])
457+ if strings.HasSuffix(rest, "*/") {
458+ content := strings.TrimSpace(rest[:len(rest)-2])
459+ if n, s, ok := parseNameSummary(content); ok {
460+ name = n
461+ summary = s
462+ }
463+ } else {
464+ if rest != "" {
465+ if n, s, ok := parseNameSummary(rest); ok {
466+ name = n
467+ summary = s
468+ } else {
469+ descriptionBody = append(descriptionBody, rest)
470+ }
471+ }
472+ inBlockComment = true
473+ }
474+ continue
475+ }
476+
477+ if idx := strings.Index(trimmed, "// ?man"); idx >= 0 {
478+ content := strings.TrimSpace(trimmed[idx+7:])
479+ if opt, desc, ok := isOptionPattern(content); ok {
480+ options = append(options, optionDoc{opt: opt, desc: desc})
481+ lastOption = &options[len(options)-1]
482+ currentSection = nil
483+ } else {
484+ if lastOption != nil {
485+ lastOption.desc += " " + content
486+ } else if currentSection != nil {
487+ currentSection.lines = append(currentSection.lines, content)
488+ } else {
489+ descriptionBody = append(descriptionBody, content)
490+ }
491+ }
492+ continue
493+ }
494+ }
495+
496+ if err := sc.Err(); err != nil {
497+ return "", err
498+ }
499+
500+ var djotBuf strings.Builder
501+ djotBuf.WriteString("# " + name + "\n\n")
502+ djotBuf.WriteString("## NAME\n\n")
503+ djotBuf.WriteString(name + " \\- " + summary + "\n\n")
504+
505+ if synopsis != "" {
506+ djotBuf.WriteString("## SYNOPSIS\n\n")
507+ djotBuf.WriteString("```\n" + synopsis + "\n```\n\n")
508+ }
509+
510+ if len(descriptionBody) > 0 {
511+ djotBuf.WriteString("## DESCRIPTION\n\n")
512+ for _, l := range descriptionBody {
513+ djotBuf.WriteString(l + "\n")
514+ }
515+ djotBuf.WriteString("\n")
516+ }
517+
518+ if len(options) > 0 {
519+ djotBuf.WriteString("## OPTIONS\n\n")
520+ for _, opt := range options {
521+ words := strings.Fields(opt.opt)
522+ var formatted []string
523+ for _, w := range words {
524+ formatted = append(formatted, formatToken(w))
525+ }
526+ djotBuf.WriteString(strings.Join(formatted, " ") + "\n")
527+ djotBuf.WriteString(": " + opt.desc + "\n\n")
528+ }
529+ }
530+
531+ for _, sec := range otherSections {
532+ djotBuf.WriteString("## " + sec.title + "\n\n")
533+ for _, l := range sec.lines {
534+ djotBuf.WriteString(l + "\n")
535+ }
536+ djotBuf.WriteString("\n")
537+ }
538+
539+ return djotBuf.String(), nil
540+}
541+
542+// Section inference from file path
543+//
544+func inferSection(path string) int {
545+ clean := filepath.ToSlash(path)
546+ switch {
547+ case strings.Contains(clean, "/linux/"),
548+ strings.Contains(clean, "/net/"),
549+ strings.Contains(clean, "/xsi/"):
550+ return 8
551+ default:
552+ return 1
553+ }
554+}
555+
556+// Main entry point and flags definition
557+//
558+func main() {
559+ configPath := flag.String("config", "config.mk", "path to config.mk")
560+ sectionFlag := flag.Int("section", 0, "man section override (0 = infer from path)")
561+ dateFlag := flag.String("date", "", "date string for TH line (default: current month/year)")
562+ flag.Parse()
563+
564+ if flag.NArg() < 1 {
565+ fmt.Fprintf(os.Stderr, "usage: mkman [-config config.mk] [-section N] file.c\n")
566+ os.Exit(1)
567+ }
568+ cfile := flag.Arg(0)
569+
570+ cfg, err := parseConfigMk(*configPath)
571+ if err != nil {
572+ fmt.Fprintf(os.Stderr, "mkman: config: %v\n", err)
573+ os.Exit(1)
574+ }
575+
576+ section := *sectionFlag
577+ if section == 0 {
578+ section = inferSection(cfile)
579+ }
580+
581+ date := *dateFlag
582+ if date == "" {
583+ date = time.Now().Format("January 2006")
584+ }
585+
586+ djotText, err := scanC(cfile, cfg)
587+ if err != nil {
588+ fmt.Fprintf(os.Stderr, "mkman: scan: %v\n", err)
589+ os.Exit(1)
590+ }
591+
592+ if strings.TrimSpace(djotText) == "" {
593+ fmt.Fprintf(os.Stderr, "mkman: %s: no !man comments found\n", cfile)
594+ os.Exit(0)
595+ }
596+
597+ doc := djot.Parse([]byte(djotText))
598+
599+ r := NewTroffRenderer(doc, os.Stdout, section, date)
600+ r.Render()
601+}
+248,
-0
1@@ -0,0 +1,248 @@
2+package main
3+
4+import (
5+ "fmt"
6+ "io"
7+ "strings"
8+
9+ "aruu/mkman/djot"
10+)
11+
12+type TroffRenderer struct {
13+ doc *djot.Document
14+ w io.Writer
15+ section int
16+ date string
17+ thDone bool
18+ listDepth int
19+}
20+
21+func NewTroffRenderer(doc *djot.Document, w io.Writer, section int, date string) *TroffRenderer {
22+ return &TroffRenderer{doc: doc, w: w, section: section, date: date}
23+}
24+
25+func (r *TroffRenderer) getBytes(start, end int32) []byte {
26+ if start == -1 && end == -1 {
27+ return nil
28+ }
29+ if start < 0 {
30+ idx := ^start
31+ if idx < 0 || idx >= int32(len(r.doc.Extra)) {
32+ return nil
33+ }
34+ return r.doc.Extra[idx : end+1]
35+ }
36+ if start >= int32(len(r.doc.Source)) || end >= int32(len(r.doc.Source)) || start > end {
37+ return nil
38+ }
39+ return r.doc.Source[start : end+1]
40+}
41+
42+func troffEscape(s string) string {
43+ s = strings.ReplaceAll(s, "\\", "\\\\")
44+ s = strings.ReplaceAll(s, "-", "\\-")
45+ // Leading dot is escaped to prevent troff from treating it as a macro
46+ if len(s) > 0 && s[0] == '.' {
47+ s = "\\&" + s
48+ }
49+ return s
50+}
51+
52+func (r *TroffRenderer) collectInlineText(idx int32) string {
53+ var b strings.Builder
54+ curr := r.doc.Nodes[idx].Child
55+ for curr != -1 {
56+ node := r.doc.Nodes[curr]
57+ switch node.Type {
58+ case djot.NodeStr:
59+ b.Write(r.getBytes(node.Start, node.End))
60+ case djot.NodeSoftBreak:
61+ b.WriteRune(' ')
62+ case djot.NodeVerbatim:
63+ b.Write(r.getBytes(node.Start, node.End))
64+ default:
65+ b.WriteString(r.collectInlineText(curr))
66+ }
67+ curr = node.Next
68+ }
69+ return b.String()
70+}
71+
72+func (r *TroffRenderer) renderInlines(idx int32) {
73+ curr := r.doc.Nodes[idx].Child
74+ for curr != -1 {
75+ r.renderInlineNode(curr)
76+ curr = r.doc.Nodes[curr].Next
77+ }
78+}
79+
80+func (r *TroffRenderer) renderInlineNode(idx int32) {
81+ node := r.doc.Nodes[idx]
82+ switch node.Type {
83+ case djot.NodeStr:
84+ raw := string(r.getBytes(node.Start, node.End))
85+ fmt.Fprint(r.w, troffEscape(raw))
86+ case djot.NodeSoftBreak:
87+ fmt.Fprint(r.w, " ")
88+ case djot.NodeHardBreak:
89+ fmt.Fprint(r.w, "\n.br\n")
90+ case djot.NodeNonBreakingSpace:
91+ fmt.Fprint(r.w, "\\ ")
92+ case djot.NodeEmph:
93+ fmt.Fprint(r.w, `\fI`)
94+ r.renderInlines(idx)
95+ fmt.Fprint(r.w, `\fP`)
96+ case djot.NodeStrong:
97+ fmt.Fprint(r.w, `\fB`)
98+ r.renderInlines(idx)
99+ fmt.Fprint(r.w, `\fP`)
100+ case djot.NodeVerbatim:
101+ fmt.Fprint(r.w, `\fB`)
102+ raw := string(r.getBytes(node.Start, node.End))
103+ fmt.Fprint(r.w, troffEscape(raw))
104+ fmt.Fprint(r.w, `\fP`)
105+ case djot.NodeSmartPunctuation:
106+ data := node.Data
107+ switch data {
108+ case 1:
109+ fmt.Fprint(r.w, `\(em`)
110+ case 2:
111+ fmt.Fprint(r.w, `\(en`)
112+ default:
113+ fmt.Fprint(r.w, `\-\-`)
114+ }
115+ case djot.NodeLink:
116+ // Render link text only because raw urls are typically omitted in man pages
117+ r.renderInlines(idx)
118+ case djot.NodeDoubleQuoted:
119+ fmt.Fprint(r.w, `\(lq`)
120+ r.renderInlines(idx)
121+ fmt.Fprint(r.w, `\(rq`)
122+ case djot.NodeSingleQuoted:
123+ fmt.Fprint(r.w, `\(oq`)
124+ r.renderInlines(idx)
125+ fmt.Fprint(r.w, `\(cq`)
126+ default:
127+ r.renderInlines(idx)
128+ }
129+}
130+
131+func (r *TroffRenderer) renderChildren(idx int32) {
132+ curr := r.doc.Nodes[idx].Child
133+ for curr != -1 {
134+ r.renderNode(curr)
135+ curr = r.doc.Nodes[curr].Next
136+ }
137+}
138+
139+func (r *TroffRenderer) renderNode(idx int32) {
140+ node := r.doc.Nodes[idx]
141+ switch node.Type {
142+ case djot.NodeDoc:
143+ r.renderChildren(idx)
144+
145+ case djot.NodeSection:
146+ r.renderChildren(idx)
147+
148+ case djot.NodeHeading:
149+ level := int(node.Level)
150+ if level == 0 {
151+ level = int(node.Data & 0xFFFF)
152+ }
153+ text := r.collectInlineText(idx)
154+ switch level {
155+ case 1:
156+ if !r.thDone {
157+ fmt.Fprintf(r.w, ".TH %s %d \"%s\"\n",
158+ strings.ToUpper(text), r.section, r.date)
159+ r.thDone = true
160+ } else {
161+ fmt.Fprintf(r.w, ".SH %s\n", strings.ToUpper(text))
162+ }
163+ case 2:
164+ fmt.Fprintf(r.w, ".SH %s\n", strings.ToUpper(text))
165+ case 3:
166+ fmt.Fprintf(r.w, ".SS %s\n", text)
167+ default:
168+ fmt.Fprintf(r.w, ".PP\n\\fB%s\\fP\n", troffEscape(text))
169+ }
170+
171+ case djot.NodePara:
172+ if r.listDepth > 0 {
173+ r.renderInlines(idx)
174+ fmt.Fprint(r.w, "\n")
175+ } else {
176+ fmt.Fprint(r.w, ".PP\n")
177+ r.renderInlines(idx)
178+ fmt.Fprint(r.w, "\n")
179+ }
180+
181+ case djot.NodeCodeBlock:
182+ fmt.Fprint(r.w, ".nf\n")
183+ if node.Child != -1 {
184+ r.renderChildren(idx)
185+ } else {
186+ raw := r.getBytes(node.Start, node.End)
187+ r.w.Write(raw) //nolint
188+ }
189+ fmt.Fprint(r.w, ".fi\n")
190+
191+ case djot.NodeStr:
192+ raw := string(r.getBytes(node.Start, node.End))
193+ fmt.Fprint(r.w, raw)
194+
195+ case djot.NodeBlockQuote:
196+ fmt.Fprint(r.w, ".RS\n")
197+ r.renderChildren(idx)
198+ fmt.Fprint(r.w, ".RE\n")
199+
200+ case djot.NodeBulletList, djot.NodeOrderedList, djot.NodeTaskList:
201+ r.listDepth++
202+ r.renderChildren(idx)
203+ r.listDepth--
204+
205+ case djot.NodeListItem, djot.NodeTaskListItem:
206+ if r.doc.Nodes[idx].Type == djot.NodeTaskListItem {
207+ checked := (node.Data & djot.DataTaskChecked) != 0
208+ if checked {
209+ fmt.Fprint(r.w, ".IP \"[x]\" 4\n")
210+ } else {
211+ fmt.Fprint(r.w, ".IP \"[ ]\" 4\n")
212+ }
213+ } else {
214+ fmt.Fprint(r.w, ".IP \\(bu 4\n")
215+ }
216+ r.renderChildren(idx)
217+
218+ case djot.NodeDefinitionList:
219+ r.renderChildren(idx)
220+
221+ case djot.NodeDefinitionListItem:
222+ r.renderChildren(idx)
223+
224+ case djot.NodeTerm:
225+ fmt.Fprint(r.w, ".TP\n")
226+ r.renderInlines(idx)
227+ fmt.Fprint(r.w, "\n")
228+
229+ case djot.NodeDefinition:
230+ r.listDepth++
231+ r.renderChildren(idx)
232+ r.listDepth--
233+
234+ case djot.NodeThematicBreak:
235+ // Omit thematic breaks because troff lacks a clean representation
236+
237+ case djot.NodeDiv:
238+ r.renderChildren(idx)
239+
240+ default:
241+ }
242+}
243+
244+func (r *TroffRenderer) Render() {
245+ if len(r.doc.Nodes) == 0 {
246+ return
247+ }
248+ r.renderNode(0)
249+}
1@@ -0,0 +1,1412 @@
2+/* see license file for copyright and license details */
3+
4+#include "redline.h"
5+
6+#include <ctype.h>
7+#include <dirent.h>
8+#include <errno.h>
9+#include <fcntl.h>
10+#include <signal.h>
11+#include <stdint.h>
12+#include <stdio.h>
13+#include <stdlib.h>
14+#include <string.h>
15+#include <sys/ioctl.h>
16+#include <sys/stat.h>
17+#include <sys/types.h>
18+#include <termios.h>
19+#include <unistd.h>
20+
21+#define REDLINE_DEFAULT_HISTORY_MAX_LEN 100
22+#define REDLINE_MAX_LINE (1024*1024)
23+#define REDLINE_INITIAL_BUFLEN 4096
24+
25+#define ENTER 13
26+#define CTRL_A 1
27+#define CTRL_B 2
28+#define CTRL_C 3
29+#define CTRL_D 4
30+#define CTRL_E 5
31+#define CTRL_F 6
32+#define CTRL_H 8
33+#define CTRL_K 11
34+#define CTRL_L 12
35+#define CTRL_N 14
36+#define CTRL_P 16
37+#define CTRL_T 20
38+#define CTRL_U 21
39+#define CTRL_W 23
40+#define CTRL_Y 25
41+#define BACKSPACE 127
42+#define ESC 27
43+
44+struct redlineState {
45+ int in_completion;
46+ int ifd;
47+ int ofd;
48+ char *buf;
49+ size_t buflen;
50+ const char *prompt;
51+ size_t plen;
52+ size_t pos;
53+ size_t oldpos;
54+ size_t len;
55+ size_t cols;
56+ size_t oldrows;
57+ int oldrpos;
58+ int history_index;
59+};
60+
61+
62+struct abuf {
63+ char *b;
64+ int len;
65+};
66+
67+static struct termios orig_termios;
68+static int rawmode = 0;
69+static int mlmode = 0;
70+static int history_max_len = REDLINE_DEFAULT_HISTORY_MAX_LEN;
71+static int history_len = 0;
72+static char **history = NULL;
73+static char *kill_buffer = NULL;
74+static volatile sig_atomic_t winch_received = 0;
75+static struct sigaction orig_sigwinch;
76+
77+static void (*completionCallback)(const char *, struct redlineCompletions *) = NULL;
78+
79+/* return the number of bytes that compose the utf-8 character starting at c */
80+static int
81+utf8ByteLen(char c)
82+{
83+ unsigned char uc = (unsigned char)c;
84+ if ((uc & 0x80) == 0) return 1;
85+ if ((uc & 0xE0) == 0xC0) return 2;
86+ if ((uc & 0xF0) == 0xE0) return 3;
87+ if ((uc & 0xF8) == 0xF0) return 4;
88+ return 1;
89+}
90+
91+/* decode character starting at s */
92+static uint32_t
93+utf8DecodeChar(const char *s, size_t *len)
94+{
95+ uint32_t cp = 0;
96+ int l = utf8ByteLen(*s);
97+ *len = l;
98+ if (l == 1) {
99+ cp = ((unsigned char)*s);
100+ } else if (l == 2) {
101+ cp = ((unsigned char)*s & 0x1F) << 6;
102+ cp |= ((unsigned char)*(s+1) & 0x3F);
103+ } else if (l == 3) {
104+ cp = ((unsigned char)*s & 0x0F) << 12;
105+ cp |= ((unsigned char)*(s+1) & 0x3F) << 6;
106+ cp |= ((unsigned char)*(s+2) & 0x3F);
107+ } else if (l == 4) {
108+ cp = ((unsigned char)*s & 0x07) << 18;
109+ cp |= ((unsigned char)*(s+1) & 0x3F) << 12;
110+ cp |= ((unsigned char)*(s+2) & 0x3F) << 6;
111+ cp |= ((unsigned char)*(s+3) & 0x3F);
112+ }
113+ return cp;
114+}
115+
116+static int
117+isZWJ(uint32_t cp)
118+{
119+ return cp == 0x200D;
120+}
121+
122+static int
123+isCombiningMark(uint32_t cp)
124+{
125+ return (cp >= 0x0300 && cp <= 0x036F) || (cp >= 0x1DC0 && cp <= 0x1DFF) ||
126+ (cp >= 0x20D0 && cp <= 0x20FF) || (cp >= 0xFE20 && cp <= 0xFE2F);
127+}
128+
129+static int
130+isVariationSelector(uint32_t cp)
131+{
132+ return (cp >= 0xFE00 && cp <= 0xFE0F) || (cp >= 0xE0100 && cp <= 0xE01EF);
133+}
134+
135+static int
136+isSkinToneModifier(uint32_t cp)
137+{
138+ return cp >= 0x1F3FB && cp <= 0x1F3FF;
139+}
140+
141+
142+static int
143+isGraphemeExtend(uint32_t cp)
144+{
145+ return isCombiningMark(cp) || isVariationSelector(cp) || isSkinToneModifier(cp);
146+}
147+
148+/* decode character going backward from pos */
149+static uint32_t
150+utf8DecodePrev(const char *buf, size_t pos, size_t *cplen)
151+{
152+ size_t i = 1;
153+ while (pos >= i && i <= 4) {
154+ unsigned char uc = (unsigned char)buf[pos-i];
155+ if ((uc & 0x80) == 0) {
156+ if (i == 1) {
157+ *cplen = 1;
158+ return uc;
159+ }
160+ break;
161+ }
162+ if ((uc & 0xC0) == 0xC0) {
163+ int l = utf8ByteLen(buf[pos-i]);
164+ if ((size_t)l == i) {
165+ *cplen = i;
166+ return utf8DecodeChar(buf + pos - i, cplen);
167+ }
168+ break;
169+ }
170+ i++;
171+ }
172+ *cplen = 1;
173+ return (unsigned char)buf[pos-1];
174+}
175+
176+/* calculate width of utf-8 char pos */
177+static size_t
178+utf8PrevCharLen(const char *buf, size_t pos)
179+{
180+ size_t len = 0;
181+ size_t next_len = 0;
182+ uint32_t cp;
183+ if (pos == 0) return 0;
184+ cp = utf8DecodePrev(buf, pos, &len);
185+ pos -= len;
186+ while (pos > 0 && isGraphemeExtend(cp)) {
187+ cp = utf8DecodePrev(buf, pos, &next_len);
188+ len += next_len;
189+ pos -= next_len;
190+ }
191+ if (pos > 0 && isZWJ(cp)) {
192+ size_t j = utf8PrevCharLen(buf, pos);
193+ if (j > 0) len += j;
194+ }
195+ return len;
196+}
197+
198+/* calculate width of next utf-8 char */
199+static size_t
200+utf8NextCharLen(const char *buf, size_t pos, size_t len)
201+{
202+ size_t clen = 0;
203+ size_t offset = 0;
204+ uint32_t cp;
205+ if (pos >= len) return 0;
206+ cp = utf8DecodeChar(buf + pos, &clen);
207+ offset = clen;
208+ while (pos + offset < len) {
209+ size_t next_len = 0;
210+ uint32_t next_cp = utf8DecodeChar(buf + pos + offset, &next_len);
211+ if (isGraphemeExtend(next_cp)) {
212+ offset += next_len;
213+ } else if (isZWJ(cp)) {
214+ offset += next_len;
215+ cp = next_cp;
216+ } else {
217+ break;
218+ }
219+ }
220+ return offset;
221+}
222+
223+/* get columns needed to display char */
224+static int
225+utf8CharWidth(uint32_t cp)
226+{
227+ if (cp == 0) return 0;
228+ if (cp < 0x20 || (cp >= 0x7f && cp < 0xa0)) return 0;
229+ if ((cp >= 0x1100 && cp <= 0x115f) ||
230+ (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) ||
231+ (cp >= 0xac00 && cp <= 0xd7a3) ||
232+ (cp >= 0xf900 && cp <= 0xfaff) ||
233+ (cp >= 0xfe10 && cp <= 0xfe19) ||
234+ (cp >= 0xfe30 && cp <= 0xfe6f) ||
235+ (cp >= 0xff00 && cp <= 0xff60) ||
236+ (cp >= 0xffe0 && cp <= 0xffe6) ||
237+ (cp >= 0x20000 && cp <= 0x2fffd) ||
238+ (cp >= 0x30000 && cp <= 0x3fffd)) {
239+ return 2;
240+ }
241+ return 1;
242+}
243+
244+/* get ansi escape sequence length */
245+static size_t
246+ansiEscapeLen(const char *s, size_t len)
247+{
248+ size_t i = 0;
249+ if (len < 2 || s[0] != '\x1b' || s[1] != '[') return 0;
250+ i = 2;
251+ while (i < len) {
252+ char c = s[i];
253+ if ((c >= '0' && c <= '9') || c == ';' || c == '?' || c == '"') {
254+ i++;
255+ } else if (c >= 'A' && c <= 'Z') {
256+ return i + 1;
257+ } else if (c >= 'a' && c <= 'z') {
258+ return i + 1;
259+ } else {
260+ break;
261+ }
262+ }
263+ return 0;
264+}
265+
266+/* calculate width of string */
267+static size_t
268+utf8StrWidth(const char *s, size_t len)
269+{
270+ size_t width = 0;
271+ size_t i = 0;
272+ while (i < len) {
273+ size_t elen = ansiEscapeLen(s + i, len - i);
274+ if (elen > 0) {
275+ i += elen;
276+ continue;
277+ }
278+ size_t clen = 0;
279+ uint32_t cp = utf8DecodeChar(s + i, &clen);
280+ width += utf8CharWidth(cp);
281+ i += clen;
282+ }
283+ return width;
284+}
285+
286+/* get single character width */
287+static int
288+utf8SingleCharWidth(const char *s, size_t len)
289+{
290+ size_t clen = 0;
291+ uint32_t cp = utf8DecodeChar(s, &clen);
292+ (void)len;
293+ return utf8CharWidth(cp);
294+}
295+
296+static int
297+isUnsupportedTerm(void)
298+{
299+ char *term = getenv("TERM");
300+ int i;
301+ static char *unsupported[] = {"dumb", "cons25", "emacs", NULL};
302+ if (term == NULL) return 0;
303+ for (i = 0; unsupported[i]; i++) {
304+ if (strcasecmp(term, unsupported[i]) == 0)
305+ return 1;
306+ }
307+ return 0;
308+}
309+
310+static void
311+sigwinchHandler(int sig)
312+{
313+ (void)sig;
314+ winch_received = 1;
315+}
316+
317+static int
318+enableRawMode(int fd)
319+{
320+ struct termios raw;
321+ struct sigaction sa;
322+
323+ if (!isatty(STDIN_FILENO))
324+ return -1;
325+ if (tcgetattr(fd, &orig_termios) == -1)
326+ return -1;
327+
328+ raw = orig_termios;
329+ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
330+ raw.c_oflag &= ~(OPOST);
331+ raw.c_cflag |= (CS8);
332+ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
333+ raw.c_cc[VMIN] = 1;
334+ raw.c_cc[VTIME] = 0;
335+
336+ if (tcsetattr(fd, TCSAFLUSH, &raw) < 0)
337+ return -1;
338+
339+ rawmode = 1;
340+
341+ /* register sigwinch handler */
342+ sa.sa_handler = sigwinchHandler;
343+ sigemptyset(&sa.sa_mask);
344+ sa.sa_flags = 0;
345+ sigaction(SIGWINCH, &sa, &orig_sigwinch);
346+
347+ return 0;
348+}
349+
350+static void
351+disableRawMode(int fd)
352+{
353+ if (rawmode) {
354+ tcsetattr(fd, TCSAFLUSH, &orig_termios);
355+ sigaction(SIGWINCH, &orig_sigwinch, NULL);
356+ rawmode = 0;
357+ }
358+}
359+
360+static int
361+getCursorPosition(int ifd, int ofd)
362+{
363+ char buf[32];
364+ int cols, rows;
365+ unsigned int i = 0;
366+
367+ if (write(ofd, "\x1b[6n", 4) != 4) return -1;
368+
369+ while (i < sizeof(buf)-1) {
370+ if (read(ifd,buf+i,1) != 1) break;
371+ if (buf[i] == 'R') break;
372+ i++;
373+ }
374+ buf[i] = '\0';
375+
376+ if (buf[0] != 27 || buf[1] != '[') return -1;
377+ if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
378+ return cols;
379+}
380+
381+static int
382+getColumns(int ifd, int ofd)
383+{
384+ struct winsize ws;
385+ char *cols_env;
386+ int tty_fd;
387+ int cols = 0;
388+
389+ if (ioctl(ofd, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
390+ return ws.ws_col;
391+ if (ioctl(ifd, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
392+ return ws.ws_col;
393+ if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
394+ return ws.ws_col;
395+ if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
396+ return ws.ws_col;
397+ if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20)
398+ return ws.ws_col;
399+
400+ tty_fd = open("/dev/tty", O_RDWR | O_NOCTTY);
401+ if (tty_fd >= 0) {
402+ if (ioctl(tty_fd, TIOCGWINSZ, &ws) == 0 && ws.ws_col >= 20) {
403+ cols = ws.ws_col;
404+ }
405+ close(tty_fd);
406+ if (cols >= 20) return cols;
407+ }
408+
409+ cols_env = getenv("COLUMNS");
410+ if (cols_env) {
411+ cols = atoi(cols_env);
412+ if (cols >= 20) return cols;
413+ }
414+
415+ /* fallback to cursor position query */
416+ int start;
417+
418+ if (!isatty(ifd) || !isatty(ofd)) return 80;
419+
420+ start = getCursorPosition(ifd,ofd);
421+ if (start == -1) return 80;
422+
423+ if (write(ofd,"\x1b[999C",6) != 6) return 80;
424+ cols = getCursorPosition(ifd,ofd);
425+ if (cols == -1) return 80;
426+
427+ if (cols > start) {
428+ char seq[32];
429+ snprintf(seq,sizeof(seq),"\x1b[%dD",cols-start);
430+ if (write(ofd,seq,strlen(seq)) == -1) {}
431+ }
432+ if (cols < 20) return 80;
433+ return cols;
434+}
435+
436+static void
437+redlineBeep(void)
438+{
439+ fprintf(stderr, "\x7");
440+ fflush(stderr);
441+}
442+
443+static void
444+freeCompletions(struct redlineCompletions *lc)
445+{
446+ size_t i;
447+ if (lc->cvec) {
448+ for (i = 0; i < lc->len; i++) {
449+ free(lc->cvec[i]);
450+ }
451+ free(lc->cvec);
452+ }
453+}
454+
455+static size_t
456+longestCommonPrefix(struct redlineCompletions *lc)
457+{
458+ size_t i, j;
459+ if (lc->len == 0) return 0;
460+ for (i = 0; ; i++) {
461+ char c = lc->cvec[0][i];
462+ if (c == '\0') return i;
463+ for (j = 1; j < lc->len; j++) {
464+ if (lc->cvec[j][i] != c) {
465+ return i;
466+ }
467+ }
468+ }
469+}
470+
471+static void
472+printCompletions(struct redlineState *ls, struct redlineCompletions *lc)
473+{
474+ size_t max_len = 0;
475+ size_t i, j, k;
476+ size_t col_width, num_cols, num_rows;
477+ size_t sp, len, idx;
478+ const char *comp;
479+ const char *name;
480+
481+ for (i = 0; i < lc->len; i++) {
482+ comp = lc->cvec[i];
483+ sp = strlen(comp);
484+ if (sp > 0 && comp[sp-1] == ' ') {
485+ sp--;
486+ }
487+ while (sp > 0 && comp[sp-1] != ' ') {
488+ sp--;
489+ }
490+ len = strlen(comp + sp);
491+ if (len > 0 && (comp + sp)[len-1] == ' ') {
492+ len--;
493+ }
494+ if (len > max_len) {
495+ max_len = len;
496+ }
497+ }
498+
499+ col_width = max_len + 2;
500+ num_cols = ls->cols / col_width;
501+ if (num_cols == 0) num_cols = 1;
502+ num_rows = (lc->len + num_cols - 1) / num_cols;
503+
504+ if (write(ls->ofd, "\r\n", 2) == -1) {}
505+ for (i = 0; i < num_rows; i++) {
506+ for (j = 0; j < num_cols; j++) {
507+ idx = j * num_rows + i;
508+ if (idx < lc->len) {
509+ comp = lc->cvec[idx];
510+ sp = strlen(comp);
511+ if (sp > 0 && comp[sp-1] == ' ') {
512+ sp--;
513+ }
514+ while (sp > 0 && comp[sp-1] != ' ') {
515+ sp--;
516+ }
517+ name = comp + sp;
518+ len = strlen(name);
519+ if (len > 0 && name[len-1] == ' ') {
520+ len--;
521+ }
522+ if (write(ls->ofd, name, len) == -1) {}
523+ if (j < num_cols - 1) {
524+ for (k = len; k < col_width; k++) {
525+ if (write(ls->ofd, " ", 1) == -1) {}
526+ }
527+ }
528+ }
529+ }
530+ if (write(ls->ofd, "\r\n", 2) == -1) {}
531+ }
532+}
533+
534+static void
535+abInit(struct abuf *ab)
536+{
537+ ab->b = NULL;
538+ ab->len = 0;
539+}
540+
541+static void
542+abAppend(struct abuf *ab, const char *s, int len)
543+{
544+ char *new = realloc(ab->b,ab->len+len);
545+ if (new == NULL) return;
546+ memcpy(new+ab->len,s,len);
547+ ab->b = new;
548+ ab->len += len;
549+}
550+
551+static void
552+abFree(struct abuf *ab)
553+{
554+ free(ab->b);
555+}
556+
557+static void
558+refreshSingleLine(struct redlineState *l, int flags)
559+{
560+ char seq[64];
561+ size_t pwidth = utf8StrWidth(l->prompt, l->plen);
562+ int fd = l->ofd;
563+ char *buf = l->buf;
564+ size_t len = l->len;
565+ size_t pos = l->pos;
566+ size_t poscol;
567+ size_t lencol;
568+ struct abuf ab;
569+
570+ poscol = utf8StrWidth(buf, pos);
571+ lencol = utf8StrWidth(buf, len);
572+
573+ while (pwidth + poscol >= l->cols) {
574+ size_t clen = utf8NextCharLen(buf, 0, len);
575+ int cwidth = utf8SingleCharWidth(buf, clen);
576+ buf += clen;
577+ len -= clen;
578+ pos -= clen;
579+ poscol -= cwidth;
580+ lencol -= cwidth;
581+ }
582+
583+ while (pwidth + lencol > l->cols) {
584+ size_t clen = utf8PrevCharLen(buf, len);
585+ int cwidth = utf8SingleCharWidth(buf + len - clen, clen);
586+ len -= clen;
587+ lencol -= cwidth;
588+ }
589+
590+ abInit(&ab);
591+ snprintf(seq,sizeof(seq),"\r");
592+ abAppend(&ab,seq,strlen(seq));
593+
594+ if (flags & 1) {
595+ abAppend(&ab,l->prompt,l->plen);
596+ abAppend(&ab,buf,len);
597+ }
598+
599+ snprintf(seq,sizeof(seq),"\x1b[0K");
600+ abAppend(&ab,seq,strlen(seq));
601+
602+ if (flags & 1) {
603+ snprintf(seq,sizeof(seq),"\r\x1b[%dC", (int)(poscol+pwidth));
604+ abAppend(&ab,seq,strlen(seq));
605+ }
606+
607+ if (write(fd,ab.b,ab.len) == -1) {}
608+ abFree(&ab);
609+}
610+
611+static void
612+refreshMultiLine(struct redlineState *l, int flags)
613+{
614+ char seq[64];
615+ size_t pwidth = utf8StrWidth(l->prompt, l->plen);
616+ size_t bufwidth;
617+ size_t poswidth;
618+ int rows;
619+ int rpos2;
620+ int col;
621+ int old_rows = l->oldrows;
622+ int rpos = l->oldrpos;
623+ int fd = l->ofd, j;
624+ struct abuf ab;
625+
626+ (void)flags;
627+
628+ bufwidth = utf8StrWidth(l->buf, l->len);
629+ poswidth = utf8StrWidth(l->buf, l->pos);
630+ rows = (pwidth+bufwidth+l->cols-1)/l->cols;
631+ l->oldrows = rows;
632+
633+ abInit(&ab);
634+
635+ /* move cursor up to the first row, column 0 of the input area */
636+ if (rpos > 1) {
637+ snprintf(seq, 64, "\r\x1b[%dA", rpos - 1);
638+ abAppend(&ab, seq, strlen(seq));
639+ } else {
640+ abAppend(&ab, "\r", 1);
641+ }
642+
643+ /* clear all old rows */
644+ for (j = 0; j < old_rows; j++) {
645+ abAppend(&ab, "\x1b[0K", 4);
646+ if (j < old_rows - 1) {
647+ abAppend(&ab, "\n\r", 2);
648+ }
649+ }
650+
651+ /* move cursor back to the first row, column 0 */
652+ if (old_rows > 1) {
653+ snprintf(seq, 64, "\r\x1b[%dA", old_rows - 1);
654+ abAppend(&ab, seq, strlen(seq));
655+ } else {
656+ abAppend(&ab, "\r", 1);
657+ }
658+
659+ /* print prompt and new buffer */
660+ abAppend(&ab, l->prompt, l->plen);
661+ abAppend(&ab, l->buf, l->len);
662+
663+ /* if cursor is at the end of the line and wraps, print a newline */
664+ if (l->pos && l->pos == l->len && (poswidth+pwidth) % l->cols == 0) {
665+ abAppend(&ab, "\n\r", 2);
666+ rows++;
667+ if (rows > (int)l->oldrows) l->oldrows = rows;
668+ }
669+
670+ /* calculate cursor row and column */
671+ rpos2 = (pwidth+poswidth+l->cols)/l->cols;
672+ col = (pwidth+poswidth) % l->cols;
673+
674+ /* move cursor to the correct row and column */
675+ if (rows - rpos2 > 0) {
676+ snprintf(seq, 64, "\x1b[%dA", rows - rpos2);
677+ abAppend(&ab, seq, strlen(seq));
678+ }
679+ if (col) {
680+ snprintf(seq, 64, "\r\x1b[%dC", col);
681+ abAppend(&ab, seq, strlen(seq));
682+ } else {
683+ abAppend(&ab, "\r", 1);
684+ }
685+
686+ l->oldpos = l->pos;
687+ l->oldrpos = rpos2;
688+
689+ if (write(fd, ab.b, ab.len) == -1) {}
690+ abFree(&ab);
691+}
692+
693+static void
694+refreshLineWithFlags(struct redlineState *l, int flags)
695+{
696+ if (mlmode)
697+ refreshMultiLine(l,flags);
698+ else
699+ refreshSingleLine(l,flags);
700+}
701+
702+static void
703+refreshLine(struct redlineState *l)
704+{
705+ refreshLineWithFlags(l, 1);
706+}
707+
708+static int
709+completeLine(struct redlineState *ls, int keypressed)
710+{
711+ struct redlineCompletions lc = { 0, NULL };
712+ size_t lcp_len;
713+ int c = keypressed;
714+ int proceed = 1;
715+ char query[128];
716+ char answer = 0;
717+
718+ if (c != 9) {
719+ ls->in_completion = 0;
720+ return c;
721+ }
722+
723+ completionCallback(ls->buf, &lc);
724+ if (lc.len == 0) {
725+ redlineBeep();
726+ ls->in_completion = 0;
727+ c = 0;
728+ } else if (lc.len == 1) {
729+ size_t nwritten = snprintf(ls->buf, ls->buflen, "%s", lc.cvec[0]);
730+ ls->len = ls->pos = nwritten;
731+ refreshLine(ls);
732+ ls->in_completion = 0;
733+ c = 0;
734+ } else {
735+ lcp_len = longestCommonPrefix(&lc);
736+ if (lcp_len > ls->len) {
737+ size_t nwritten = snprintf(ls->buf, ls->buflen, "%.*s", (int)lcp_len, lc.cvec[0]);
738+ ls->len = ls->pos = nwritten;
739+ refreshLine(ls);
740+ ls->in_completion = 1;
741+ c = 0;
742+ } else {
743+ /* prefix cannot be expanded further */
744+ if (ls->in_completion == 0) {
745+ /* first tab: beep and wait for the second tab */
746+ redlineBeep();
747+ ls->in_completion = 1;
748+ c = 0;
749+ } else {
750+ /* second tab: display possibilities */
751+ if (lc.len > 100) {
752+ snprintf(query, sizeof(query), "\r\nDisplay all %d possibilities? (y or n) ", (int)lc.len);
753+ if (write(ls->ofd, query, strlen(query)) == -1) {}
754+ while (1) {
755+ if (read(ls->ifd, &answer, 1) != 1) {
756+ proceed = 0;
757+ break;
758+ }
759+ if (answer == 'y' || answer == 'Y' || answer == ' ' || answer == '\t') {
760+ proceed = 1;
761+ break;
762+ }
763+ if (answer == 'n' || answer == 'N' || answer == 27 || answer == 3 || answer == 4) {
764+ proceed = 0;
765+ break;
766+ }
767+ redlineBeep();
768+ }
769+ }
770+ if (proceed) {
771+ printCompletions(ls, &lc);
772+ } else {
773+ if (write(ls->ofd, "\r\n", 2) == -1) {}
774+ }
775+ ls->oldrows = 0;
776+ refreshLine(ls);
777+ ls->in_completion = 0;
778+ c = 0;
779+ }
780+ }
781+ }
782+
783+ freeCompletions(&lc);
784+ return c;
785+}
786+
787+static int
788+redlineEditInsert(struct redlineState *l, const char *c, int clen)
789+{
790+ if (l->len + clen >= l->buflen) {
791+ return 0;
792+ }
793+ if (l->len == l->pos) {
794+ memcpy(l->buf + l->pos, c, clen);
795+ l->pos += clen;
796+ l->len += clen;
797+ l->buf[l->len] = '\0';
798+ refreshLine(l);
799+ } else {
800+ memmove(l->buf + l->pos + clen, l->buf + l->pos, l->len - l->pos);
801+ memcpy(l->buf + l->pos, c, clen);
802+ l->pos += clen;
803+ l->len += clen;
804+ l->buf[l->len] = '\0';
805+ refreshLine(l);
806+ }
807+ return 1;
808+}
809+
810+static void
811+redlineEditBackspace(struct redlineState *l)
812+{
813+ if (l->pos > 0 && l->len > 0) {
814+ size_t clen = utf8PrevCharLen(l->buf, l->pos);
815+ memmove(l->buf+l->pos-clen, l->buf+l->pos, l->len-l->pos);
816+ l->pos -= clen;
817+ l->len -= clen;
818+ l->buf[l->len] = '\0';
819+ refreshLine(l);
820+ }
821+}
822+
823+static void
824+redlineEditDelete(struct redlineState *l)
825+{
826+ if (l->len > 0 && l->pos < l->len) {
827+ size_t clen = utf8NextCharLen(l->buf, l->pos, l->len);
828+ memmove(l->buf+l->pos, l->buf+l->pos+clen, l->len-l->pos-clen);
829+ l->len -= clen;
830+ l->buf[l->len] = '\0';
831+ refreshLine(l);
832+ }
833+}
834+
835+static void
836+redlineEditMoveLeft(struct redlineState *l)
837+{
838+ if (l->pos > 0) {
839+ l->pos -= utf8PrevCharLen(l->buf, l->pos);
840+ refreshLine(l);
841+ }
842+}
843+
844+static void
845+redlineEditMoveRight(struct redlineState *l)
846+{
847+ if (l->pos != l->len) {
848+ l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
849+ refreshLine(l);
850+ }
851+}
852+
853+static void
854+redlineEditMoveHome(struct redlineState *l)
855+{
856+ if (l->pos != 0) {
857+ l->pos = 0;
858+ refreshLine(l);
859+ }
860+}
861+
862+static void
863+redlineEditMoveEnd(struct redlineState *l)
864+{
865+ if (l->pos != l->len) {
866+ l->pos = l->len;
867+ refreshLine(l);
868+ }
869+}
870+
871+static void
872+redlineEditMoveWordLeft(struct redlineState *l)
873+{
874+ if (l->pos > 0) {
875+ while (l->pos > 0 && l->buf[l->pos-1] == ' ')
876+ l->pos -= utf8PrevCharLen(l->buf, l->pos);
877+ while (l->pos > 0 && l->buf[l->pos-1] != ' ')
878+ l->pos -= utf8PrevCharLen(l->buf, l->pos);
879+ refreshLine(l);
880+ }
881+}
882+
883+static void
884+redlineEditMoveWordRight(struct redlineState *l)
885+{
886+ if (l->pos < l->len) {
887+ while (l->pos < l->len && l->buf[l->pos] == ' ')
888+ l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
889+ while (l->pos < l->len && l->buf[l->pos] != ' ')
890+ l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
891+ refreshLine(l);
892+ }
893+}
894+
895+static void
896+killBufferSave(const char *text, size_t len)
897+{
898+ free(kill_buffer);
899+ kill_buffer = malloc(len + 1);
900+ if (kill_buffer) {
901+ memcpy(kill_buffer, text, len);
902+ kill_buffer[len] = '\0';
903+ }
904+}
905+
906+static void
907+redlineEditDeleteWordRight(struct redlineState *l)
908+{
909+ size_t old_pos = l->pos;
910+ size_t diff;
911+ if (l->pos < l->len) {
912+ while (l->pos < l->len && l->buf[l->pos] == ' ')
913+ l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
914+ while (l->pos < l->len && l->buf[l->pos] != ' ')
915+ l->pos += utf8NextCharLen(l->buf, l->pos, l->len);
916+ diff = l->pos - old_pos;
917+ l->pos = old_pos;
918+ killBufferSave(l->buf + l->pos, diff);
919+ memmove(l->buf + l->pos, l->buf + l->pos + diff, l->len - l->pos - diff + 1);
920+ l->len -= diff;
921+ refreshLine(l);
922+ }
923+}
924+
925+static void
926+redlineEditDeletePrevWord(struct redlineState *l)
927+{
928+ size_t old_pos = l->pos;
929+ size_t diff;
930+ if (l->pos > 0) {
931+ while (l->pos > 0 && l->buf[l->pos-1] == ' ')
932+ l->pos -= utf8PrevCharLen(l->buf, l->pos);
933+ while (l->pos > 0 && l->buf[l->pos-1] != ' ')
934+ l->pos -= utf8PrevCharLen(l->buf, l->pos);
935+ diff = old_pos - l->pos;
936+ killBufferSave(l->buf + l->pos, diff);
937+ memmove(l->buf + l->pos, l->buf + old_pos, l->len - old_pos + 1);
938+ l->len -= diff;
939+ refreshLine(l);
940+ }
941+}
942+
943+void
944+redlineHistoryAdd(const char *line)
945+{
946+ char *linecopy;
947+ if (history_max_len == 0) return;
948+ if (history == NULL) {
949+ history = malloc(sizeof(char*) * history_max_len);
950+ if (history == NULL) return;
951+ memset(history,0,sizeof(char*)*history_max_len);
952+ }
953+ if (history_len && strcmp(history[history_len-1], line) == 0) return;
954+ linecopy = strdup(line);
955+ if (!linecopy) return;
956+ if (history_len == history_max_len) {
957+ free(history[0]);
958+ memmove(history,history+1,sizeof(char*)*(history_max_len-1));
959+ history_len--;
960+ }
961+ history[history_len] = linecopy;
962+ history_len++;
963+}
964+
965+void
966+redlineHistorySetMaxLen(int len)
967+{
968+ char **new;
969+ if (len < 1) return;
970+ if (history) {
971+ int tocopy = history_len;
972+ new = malloc(sizeof(char*)*len);
973+ if (new == NULL) return;
974+ if (len < tocopy) {
975+ int j;
976+ for (j = 0; j < tocopy-len; j++) free(history[j]);
977+ tocopy = len;
978+ }
979+ memset(new,0,sizeof(char*)*len);
980+ memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy);
981+ free(history);
982+ history = new;
983+ }
984+ history_max_len = len;
985+ if (history_len > history_max_len)
986+ history_len = history_max_len;
987+}
988+
989+int
990+redlineHistorySave(const char *filename)
991+{
992+ mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
993+ FILE *fp;
994+ int j;
995+
996+ fp = fopen(filename,"w");
997+ umask(old_umask);
998+ if (fp == NULL) return -1;
999+ chmod(filename,S_IRUSR|S_IWUSR);
1000+ for (j = 0; j < history_len; j++) {
1001+ fprintf(fp,"%s\n",history[j]);
1002+ }
1003+ fclose(fp);
1004+ return 0;
1005+}
1006+
1007+int
1008+redlineHistoryLoad(const char *filename)
1009+{
1010+ FILE *fp = fopen(filename,"r");
1011+ char buf[REDLINE_INITIAL_BUFLEN];
1012+ if (fp == NULL) return -1;
1013+ while (fgets(buf,sizeof(buf),fp) != NULL) {
1014+ char *p = strchr(buf,'\r');
1015+ if (!p) p = strchr(buf,'\n');
1016+ if (p) *p = '\0';
1017+ redlineHistoryAdd(buf);
1018+ }
1019+ fclose(fp);
1020+ return 0;
1021+}
1022+
1023+char *
1024+redlineHistoryGet(int idx)
1025+{
1026+ if (idx >= 0 && idx < history_len)
1027+ return history[idx];
1028+ return NULL;
1029+}
1030+
1031+int
1032+redlineHistoryLen(void)
1033+{
1034+ return history_len;
1035+}
1036+
1037+static void
1038+redlineEditHistoryNext(struct redlineState *l, int dir)
1039+{
1040+ if (history_len > 1) {
1041+ const char *src;
1042+ size_t len;
1043+ free(history[history_len - 1 - l->history_index]);
1044+ history[history_len - 1 - l->history_index] = strdup(l->buf);
1045+ l->history_index += (dir == 1) ? 1 : -1;
1046+ if (l->history_index < 0) {
1047+ l->history_index = 0;
1048+ return;
1049+ } else if (l->history_index >= history_len) {
1050+ l->history_index = history_len - 1;
1051+ return;
1052+ }
1053+ src = history[history_len - 1 - l->history_index];
1054+ len = strlen(src);
1055+ if (len >= l->buflen) len = l->buflen - 1;
1056+ memcpy(l->buf, src, len);
1057+ l->buf[len] = '\0';
1058+ l->len = l->pos = len;
1059+ refreshLine(l);
1060+ }
1061+}
1062+
1063+static char *
1064+redlineReadLine(FILE *fp)
1065+{
1066+ char *line = NULL;
1067+ size_t len = 0, cap = 0;
1068+ while (1) {
1069+ if (len+1 >= cap) {
1070+ size_t newcap = cap ? cap*2 : 16;
1071+ char *new = realloc(line, newcap);
1072+ if (!new) {
1073+ free(line);
1074+ return NULL;
1075+ }
1076+ line = new;
1077+ cap = newcap;
1078+ }
1079+ int c = fgetc(fp);
1080+ if (c == EOF || c == '\n') {
1081+ if (c == EOF && len == 0) {
1082+ free(line);
1083+ return NULL;
1084+ }
1085+ line[len] = '\0';
1086+ return line;
1087+ }
1088+ line[len++] = c;
1089+ }
1090+}
1091+
1092+static char *
1093+redlineNoTTY(void)
1094+{
1095+ return redlineReadLine(stdin);
1096+}
1097+
1098+void
1099+redlineClearScreen(void)
1100+{
1101+ if (write(STDOUT_FILENO, "\x1b[H\x1b[2J", 7) == -1) {}
1102+}
1103+
1104+static char *
1105+redlineEditFeed(struct redlineState *l)
1106+{
1107+ char c;
1108+ int nread;
1109+ char seq[3];
1110+ char param[8];
1111+ size_t plen;
1112+ char final;
1113+ char p;
1114+ int is_word_jump;
1115+ char tmp[32];
1116+ size_t prevlen;
1117+ size_t currlen;
1118+ size_t prevstart;
1119+ char utf8[4];
1120+ int utf8len;
1121+ int i;
1122+
1123+ if (!isatty(l->ifd) && !getenv("REDLINE_ASSUME_TTY")) return redlineNoTTY();
1124+
1125+ while (1) {
1126+ nread = read(l->ifd, &c, 1);
1127+ if (nread < 0) {
1128+ if (errno == EINTR) {
1129+ if (winch_received) {
1130+ winch_received = 0;
1131+ l->cols = getColumns(l->ifd, l->ofd);
1132+ refreshLine(l);
1133+ }
1134+ continue;
1135+ }
1136+ return (errno == EAGAIN || errno == EWOULDBLOCK) ? "more" : NULL;
1137+ } else if (nread == 0) {
1138+ return NULL;
1139+ }
1140+ break;
1141+ }
1142+
1143+ if ((l->in_completion || c == 9) && completionCallback != NULL) {
1144+ int retval = completeLine(l, c);
1145+ if (retval == 0) return "more";
1146+ c = retval;
1147+ }
1148+
1149+ switch (c) {
1150+ case ENTER:
1151+ if (mlmode) redlineEditMoveEnd(l);
1152+ return strdup(l->buf);
1153+ case CTRL_C:
1154+ errno = EAGAIN;
1155+ return NULL;
1156+ case BACKSPACE:
1157+ case 8:
1158+ redlineEditBackspace(l);
1159+ break;
1160+ case CTRL_D:
1161+ if (l->len > 0) {
1162+ redlineEditDelete(l);
1163+ } else {
1164+ errno = ENOENT;
1165+ return NULL;
1166+ }
1167+ break;
1168+ case CTRL_T:
1169+ if (l->pos > 0 && l->pos < l->len) {
1170+ prevlen = utf8PrevCharLen(l->buf, l->pos);
1171+ currlen = utf8NextCharLen(l->buf, l->pos, l->len);
1172+ prevstart = l->pos - prevlen;
1173+ if (prevlen > sizeof(tmp) || currlen > sizeof(tmp)) break;
1174+ memcpy(tmp, l->buf + l->pos, currlen);
1175+ memmove(l->buf + prevstart + currlen, l->buf + prevstart, prevlen);
1176+ memcpy(l->buf + prevstart, tmp, currlen);
1177+ if (l->pos + currlen <= l->len) l->pos += currlen;
1178+ refreshLine(l);
1179+ }
1180+ break;
1181+ case CTRL_B:
1182+ redlineEditMoveLeft(l);
1183+ break;
1184+ case CTRL_F:
1185+ redlineEditMoveRight(l);
1186+ break;
1187+ case CTRL_P:
1188+ redlineEditHistoryNext(l, 1);
1189+ break;
1190+ case CTRL_N:
1191+ redlineEditHistoryNext(l, 0);
1192+ break;
1193+ case ESC:
1194+ if (read(l->ifd, seq, 1) == -1) break;
1195+ if (seq[0] == '[' || seq[0] == 'O') {
1196+ if (read(l->ifd, seq+1, 1) == -1) break;
1197+ if (seq[0] == '[') {
1198+ if (seq[1] >= '0' && seq[1] <= '9') {
1199+ plen = 1;
1200+ final = 0;
1201+ param[0] = seq[1];
1202+ while (plen < sizeof(param)) {
1203+ if (read(l->ifd, &p, 1) != 1) break;
1204+ if ((p >= '0' && p <= '9') || p == ';') {
1205+ param[plen++] = p;
1206+ } else {
1207+ final = p;
1208+ break;
1209+ }
1210+ }
1211+ if (final == '~') {
1212+ if (plen == 1 && param[0] == '3') {
1213+ redlineEditDelete(l);
1214+ }
1215+ } else if (final == 'D' || final == 'C') {
1216+ is_word_jump = 0;
1217+ if (plen == 3 && param[0] == '1' && param[1] == ';' && (param[2] == '5' || param[2] == '3')) {
1218+ is_word_jump = 1;
1219+ } else if (plen == 1 && (param[0] == '5' || param[0] == '3')) {
1220+ is_word_jump = 1;
1221+ }
1222+ if (is_word_jump) {
1223+ if (final == 'D') {
1224+ redlineEditMoveWordLeft(l);
1225+ } else {
1226+ redlineEditMoveWordRight(l);
1227+ }
1228+ }
1229+ }
1230+ } else {
1231+ switch (seq[1]) {
1232+ case 'A':
1233+ redlineEditHistoryNext(l, 1);
1234+ break;
1235+ case 'B':
1236+ redlineEditHistoryNext(l, 0);
1237+ break;
1238+ case 'C':
1239+ redlineEditMoveRight(l);
1240+ break;
1241+ case 'D':
1242+ redlineEditMoveLeft(l);
1243+ break;
1244+ case 'H':
1245+ redlineEditMoveHome(l);
1246+ break;
1247+ case 'F':
1248+ redlineEditMoveEnd(l);
1249+ break;
1250+ }
1251+ }
1252+ } else if (seq[0] == 'O') {
1253+ switch (seq[1]) {
1254+ case 'H':
1255+ redlineEditMoveHome(l);
1256+ break;
1257+ case 'F':
1258+ redlineEditMoveEnd(l);
1259+ break;
1260+ }
1261+ }
1262+ } else {
1263+ if (seq[0] == 'b' || seq[0] == 'B') {
1264+ redlineEditMoveWordLeft(l);
1265+ } else if (seq[0] == 'f' || seq[0] == 'F') {
1266+ redlineEditMoveWordRight(l);
1267+ } else if (seq[0] == 'd' || seq[0] == 'D') {
1268+ redlineEditDeleteWordRight(l);
1269+ } else if (seq[0] == 127 || seq[0] == 8) {
1270+ redlineEditDeletePrevWord(l);
1271+ }
1272+ }
1273+ break;
1274+ default:
1275+ utf8len = utf8ByteLen(c);
1276+ utf8[0] = c;
1277+ if (utf8len > 1) {
1278+ for (i = 1; i < utf8len; i++) {
1279+ if (read(l->ifd, utf8+i, 1) != 1) break;
1280+ }
1281+ }
1282+ if (redlineEditInsert(l, utf8, utf8len) == 0) return NULL;
1283+ break;
1284+ case CTRL_U:
1285+ killBufferSave(l->buf, l->pos);
1286+ memmove(l->buf, l->buf + l->pos, l->len - l->pos + 1);
1287+ l->len -= l->pos;
1288+ l->pos = 0;
1289+ refreshLine(l);
1290+ break;
1291+ case CTRL_K:
1292+ killBufferSave(l->buf + l->pos, l->len - l->pos);
1293+ l->buf[l->pos] = '\0';
1294+ l->len = l->pos;
1295+ refreshLine(l);
1296+ break;
1297+ case CTRL_A:
1298+ redlineEditMoveHome(l);
1299+ break;
1300+ case CTRL_E:
1301+ redlineEditMoveEnd(l);
1302+ break;
1303+ case CTRL_L:
1304+ redlineClearScreen();
1305+ refreshLine(l);
1306+ break;
1307+ case CTRL_W:
1308+ redlineEditDeletePrevWord(l);
1309+ break;
1310+ case CTRL_Y:
1311+ if (kill_buffer) {
1312+ redlineEditInsert(l, kill_buffer, strlen(kill_buffer));
1313+ }
1314+ break;
1315+ }
1316+ return "more";
1317+}
1318+
1319+static int
1320+redlineEditStart(struct redlineState *l, int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt)
1321+{
1322+ l->in_completion = 0;
1323+ l->ifd = stdin_fd;
1324+ l->ofd = stdout_fd;
1325+ l->buf = buf;
1326+ l->buflen = buflen;
1327+ l->prompt = prompt;
1328+ l->plen = strlen(prompt);
1329+ l->pos = 0;
1330+ l->oldpos = 0;
1331+ l->len = 0;
1332+ l->cols = getColumns(stdin_fd, stdout_fd);
1333+ l->oldrows = 0;
1334+ l->oldrpos = 0;
1335+ l->history_index = 0;
1336+ l->buf[0] = '\0';
1337+
1338+ if (enableRawMode(l->ifd) == -1) return -1;
1339+ refreshLine(l);
1340+ return 0;
1341+}
1342+
1343+static void
1344+redlineEditStop(struct redlineState *l)
1345+{
1346+ if (!isatty(l->ifd) && !getenv("REDLINE_ASSUME_TTY")) return;
1347+ disableRawMode(l->ifd);
1348+ printf("\n");
1349+}
1350+
1351+char *
1352+redline(const char *prompt)
1353+{
1354+ struct redlineState l;
1355+ char *buf;
1356+ char *res;
1357+
1358+ if (!isatty(STDIN_FILENO) || isUnsupportedTerm()) {
1359+ if (write(STDOUT_FILENO, prompt, strlen(prompt)) == -1) {}
1360+ return redlineNoTTY();
1361+ }
1362+
1363+ buf = malloc(REDLINE_INITIAL_BUFLEN);
1364+ if (buf == NULL) return NULL;
1365+ if (redlineEditStart(&l, STDIN_FILENO, STDOUT_FILENO, buf, REDLINE_INITIAL_BUFLEN, prompt) == -1) {
1366+ free(buf);
1367+ return NULL;
1368+ }
1369+ redlineHistoryAdd("");
1370+ while (1) {
1371+ res = redlineEditFeed(&l);
1372+ if (res == NULL || strcmp(res, "more") != 0) {
1373+ break;
1374+ }
1375+ }
1376+ redlineEditStop(&l);
1377+ if (history_len > 0) {
1378+ history_len--;
1379+ free(history[history_len]);
1380+ }
1381+ free(l.buf);
1382+ return res;
1383+}
1384+
1385+void
1386+redlineSetCompletionCallback(void (*cb)(const char *, struct redlineCompletions *))
1387+{
1388+ completionCallback = cb;
1389+}
1390+
1391+void
1392+redlineAddCompletion(struct redlineCompletions *lc, const char *str)
1393+{
1394+ size_t len = strlen(str);
1395+ char *copy, **cvec;
1396+
1397+ copy = malloc(len+1);
1398+ if (copy == NULL) return;
1399+ memcpy(copy,str,len+1);
1400+ cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
1401+ if (cvec == NULL) {
1402+ free(copy);
1403+ return;
1404+ }
1405+ lc->cvec = cvec;
1406+ lc->cvec[lc->len++] = copy;
1407+}
1408+
1409+void
1410+redlineSetMultiLine(int ml)
1411+{
1412+ mlmode = ml;
1413+}
1@@ -0,0 +1,403 @@
2+#include "../util.h"
3+
4+#include <arpa/inet.h>
5+#include <ctype.h>
6+#include <errno.h>
7+#include <ifaddrs.h>
8+#include <net/if.h>
9+#include <stdio.h>
10+#include <stdlib.h>
11+#include <string.h>
12+#include <sys/ioctl.h>
13+#include <sys/socket.h>
14+#include <unistd.h>
15+
16+#ifdef __linux__
17+#include <netpacket/packet.h>
18+#else
19+#include <net/if_dl.h>
20+#include <sys/sysctl.h>
21+#endif
22+
23+static void
24+get_mtu_metric(const char *name, int *mtu, int *metric)
25+{
26+ struct ifreq ifr;
27+ int sock;
28+
29+ *mtu = 0;
30+ *metric = 0;
31+ sock = socket(AF_INET, SOCK_DGRAM, 0);
32+ if (sock < 0)
33+ return;
34+ memset(&ifr, 0, sizeof(ifr));
35+ strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
36+ if (ioctl(sock, SIOCGIFMTU, &ifr) >= 0)
37+ *mtu = ifr.ifr_mtu;
38+ if (ioctl(sock, SIOCGIFMETRIC, &ifr) >= 0)
39+ *metric = ifr.ifr_metric;
40+ close(sock);
41+}
42+
43+int
44+net_get_interfaces(struct NetInterface **ifaces, int *count)
45+{
46+ struct ifaddrs *ifaddr, *ifa;
47+ struct NetInterface *list = NULL;
48+ struct NetInterface *iface;
49+ int found_count = 0;
50+ int i;
51+
52+ if (getifaddrs(&ifaddr) < 0)
53+ return -1;
54+
55+ for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
56+ iface = NULL;
57+ for (i = 0; i < found_count; i++) {
58+ if (strcmp(list[i].name, ifa->ifa_name) == 0) {
59+ iface = &list[i];
60+ break;
61+ }
62+ }
63+ if (!iface) {
64+ list = reallocarray(list, found_count + 1, sizeof(*list));
65+ if (!list) {
66+ freeifaddrs(ifaddr);
67+ return -1;
68+ }
69+ iface = &list[found_count];
70+ memset(iface, 0, sizeof(*iface));
71+ strlcpy(iface->name, ifa->ifa_name, sizeof(iface->name));
72+ iface->flags = ifa->ifa_flags;
73+ get_mtu_metric(iface->name, &iface->mtu, &iface->metric);
74+ found_count++;
75+ }
76+
77+ if (!ifa->ifa_addr)
78+ continue;
79+
80+ if (ifa->ifa_addr->sa_family == AF_INET) {
81+ memcpy(&iface->ipv4_addr, ifa->ifa_addr, sizeof(struct sockaddr_in));
82+ iface->has_ipv4 = 1;
83+ if (ifa->ifa_netmask)
84+ memcpy(&iface->ipv4_mask, ifa->ifa_netmask, sizeof(struct sockaddr_in));
85+ if (ifa->ifa_dstaddr)
86+ memcpy(&iface->ipv4_brd, ifa->ifa_dstaddr, sizeof(struct sockaddr_in));
87+ } else if (ifa->ifa_addr->sa_family == AF_INET6) {
88+ memcpy(&iface->ipv6_addr, ifa->ifa_addr, sizeof(struct sockaddr_in6));
89+ iface->has_ipv6 = 1;
90+ /* scope id could be set here from sin6_scope_id */
91+ }
92+#ifdef __linux__
93+ else if (ifa->ifa_addr->sa_family == AF_PACKET) {
94+ struct sockaddr_ll *sll = (struct sockaddr_ll *)ifa->ifa_addr;
95+ if (sll->sll_halen == 6) {
96+ memcpy(iface->mac, sll->sll_addr, 6);
97+ iface->has_mac = 1;
98+ }
99+ }
100+#else
101+ else if (ifa->ifa_addr->sa_family == AF_LINK) {
102+ struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifa->ifa_addr;
103+ if (sdl->sdl_alen == 6) {
104+ memcpy(iface->mac, LLADDR(sdl), 6);
105+ iface->has_mac = 1;
106+ }
107+ }
108+#endif
109+ }
110+
111+ freeifaddrs(ifaddr);
112+ *ifaces = list;
113+ *count = found_count;
114+ return 0;
115+}
116+
117+#ifdef __linux__
118+int
119+net_get_stats(const char *ifname, struct NetStats *stats)
120+{
121+ FILE *fp;
122+ char line[256];
123+ char *p, *name;
124+ int found = 0;
125+
126+ memset(stats, 0, sizeof(*stats));
127+ fp = fopen("/proc/net/dev", "r");
128+ if (!fp)
129+ return -1;
130+
131+ while (fgets(line, sizeof(line), fp)) {
132+ p = strchr(line, ':');
133+ if (p) {
134+ *p = '\0';
135+ name = line;
136+ while (isspace(*name))
137+ name++;
138+ if (strcmp(name, ifname) == 0) {
139+ if (sscanf(p + 1, "%llu %llu %llu %llu %*u %*u %*u %*u %llu %llu %llu %llu",
140+ &stats->rx_bytes, &stats->rx_packets, &stats->rx_errs, &stats->rx_drop,
141+ &stats->tx_bytes, &stats->tx_packets, &stats->tx_errs, &stats->tx_drop) == 8) {
142+ found = 1;
143+ }
144+ break;
145+ }
146+ }
147+ }
148+ fclose(fp);
149+ return found ? 0 : -1;
150+}
151+#else
152+int
153+net_get_stats(const char *ifname, struct NetStats *stats)
154+{
155+ struct ifaddrs *ifaddr, *ifa;
156+ int found = 0;
157+
158+ memset(stats, 0, sizeof(*stats));
159+ if (getifaddrs(&ifaddr) < 0)
160+ return -1;
161+
162+ for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
163+ if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_LINK &&
164+ strcmp(ifa->ifa_name, ifname) == 0) {
165+ struct if_data *ifd = (struct if_data *)ifa->ifa_data;
166+ if (ifd) {
167+ stats->rx_bytes = ifd->ifi_ibytes;
168+ stats->rx_packets = ifd->ifi_ipackets;
169+ stats->rx_errs = ifd->ifi_ierrors;
170+ stats->rx_drop = ifd->ifi_iqdrops;
171+ stats->tx_bytes = ifd->ifi_obytes;
172+ stats->tx_packets = ifd->ifi_opackets;
173+ stats->tx_errs = ifd->ifi_oerrors;
174+ stats->tx_drop = 0;
175+ found = 1;
176+ break;
177+ }
178+ }
179+ }
180+ freeifaddrs(ifaddr);
181+ return found ? 0 : -1;
182+}
183+#endif
184+
185+#ifdef __linux__
186+int
187+net_set_txqueuelen(const char *name, int qlen)
188+{
189+ struct ifreq ifr;
190+ int sock;
191+
192+ sock = socket(AF_INET, SOCK_DGRAM, 0);
193+ if (sock < 0)
194+ return -1;
195+ memset(&ifr, 0, sizeof(ifr));
196+ strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
197+ ifr.ifr_qlen = qlen;
198+ if (ioctl(sock, SIOCSIFTXQLEN, &ifr) < 0) {
199+ close(sock);
200+ return -1;
201+ }
202+ close(sock);
203+ return 0;
204+}
205+#else
206+int
207+net_set_txqueuelen(const char *name, int qlen)
208+{
209+ (void)name;
210+ (void)qlen;
211+ return -1;
212+}
213+#endif
214+
215+int
216+net_set_flags(const char *name, unsigned int flags, int set)
217+{
218+ struct ifreq ifr;
219+ int sock;
220+
221+ sock = socket(AF_INET, SOCK_DGRAM, 0);
222+ if (sock < 0)
223+ return -1;
224+ memset(&ifr, 0, sizeof(ifr));
225+ strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
226+ if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
227+ close(sock);
228+ return -1;
229+ }
230+ if (set)
231+ ifr.ifr_flags |= flags;
232+ else
233+ ifr.ifr_flags &= ~flags;
234+ if (ioctl(sock, SIOCSIFFLAGS, &ifr) < 0) {
235+ close(sock);
236+ return -1;
237+ }
238+ close(sock);
239+ return 0;
240+}
241+
242+int
243+net_set_mtu(const char *name, int mtu)
244+{
245+ struct ifreq ifr;
246+ int sock;
247+
248+ sock = socket(AF_INET, SOCK_DGRAM, 0);
249+ if (sock < 0)
250+ return -1;
251+ memset(&ifr, 0, sizeof(ifr));
252+ strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
253+ ifr.ifr_mtu = mtu;
254+ if (ioctl(sock, SIOCSIFMTU, &ifr) < 0) {
255+ close(sock);
256+ return -1;
257+ }
258+ close(sock);
259+ return 0;
260+}
261+
262+int
263+net_set_mac(const char *name, const unsigned char mac[6])
264+{
265+#ifdef __linux__
266+ struct ifreq ifr;
267+ int sock;
268+
269+ sock = socket(AF_INET, SOCK_DGRAM, 0);
270+ if (sock < 0)
271+ return -1;
272+ memset(&ifr, 0, sizeof(ifr));
273+ strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
274+ ifr.ifr_hwaddr.sa_family = 1;
275+ memcpy(ifr.ifr_hwaddr.sa_data, mac, 6);
276+ if (ioctl(sock, SIOCSIFHWADDR, &ifr) < 0) {
277+ close(sock);
278+ return -1;
279+ }
280+ close(sock);
281+ return 0;
282+#else
283+ (void)name;
284+ (void)mac;
285+ return -1;
286+#endif
287+}
288+
289+int
290+net_add_addr(const char *name, const char *addr, int prefix)
291+{
292+ struct ifreq ifr;
293+ struct sockaddr_in *sin;
294+ int sock;
295+ unsigned int mask;
296+
297+ sock = socket(AF_INET, SOCK_DGRAM, 0);
298+ if (sock < 0)
299+ return -1;
300+
301+ memset(&ifr, 0, sizeof(ifr));
302+ strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
303+
304+ sin = (struct sockaddr_in *)&ifr.ifr_addr;
305+ sin->sin_family = AF_INET;
306+ if (inet_pton(AF_INET, addr, &sin->sin_addr) <= 0) {
307+ close(sock);
308+ return -1;
309+ }
310+
311+ if (ioctl(sock, SIOCSIFADDR, &ifr) < 0) {
312+ close(sock);
313+ return -1;
314+ }
315+
316+ if (prefix >= 0) {
317+ mask = 0;
318+ if (prefix > 0)
319+ mask = htonl(~0U << (32 - prefix));
320+ sin = (struct sockaddr_in *)&ifr.ifr_netmask;
321+ sin->sin_family = AF_INET;
322+ sin->sin_addr.s_addr = mask;
323+ if (ioctl(sock, SIOCSIFNETMASK, &ifr) < 0) {
324+ close(sock);
325+ return -1;
326+ }
327+ }
328+
329+ memset(&ifr, 0, sizeof(ifr));
330+ strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
331+ if (ioctl(sock, SIOCGIFFLAGS, &ifr) >= 0) {
332+ ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
333+ ioctl(sock, SIOCSIFFLAGS, &ifr);
334+ }
335+
336+ close(sock);
337+ return 0;
338+}
339+
340+int
341+net_del_addr(const char *name, const char *addr, int prefix)
342+{
343+ struct ifreq ifr;
344+ struct sockaddr_in *sin;
345+ int sock;
346+
347+ (void)prefix;
348+ sock = socket(AF_INET, SOCK_DGRAM, 0);
349+ if (sock < 0)
350+ return -1;
351+
352+ memset(&ifr, 0, sizeof(ifr));
353+ strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
354+
355+ sin = (struct sockaddr_in *)&ifr.ifr_addr;
356+ sin->sin_family = AF_INET;
357+ if (inet_pton(AF_INET, addr, &sin->sin_addr) <= 0) {
358+ close(sock);
359+ return -1;
360+ }
361+
362+ if (ioctl(sock, SIOCDIFADDR, &ifr) < 0) {
363+ close(sock);
364+ return -1;
365+ }
366+ close(sock);
367+ return 0;
368+}
369+
370+int
371+net_show_routes(void)
372+{
373+#ifdef __linux__
374+ FILE *fp;
375+ char line[256];
376+ char iface[16], dest[16], gateway[16], mask[16];
377+ unsigned int d, g, m, flags, metric;
378+
379+ fp = fopen("/proc/net/route", "r");
380+ if (!fp)
381+ return -1;
382+
383+ printf("%-10s %-15s %-15s %-15s %-6s %-6s\n", "Iface", "Destination", "Gateway", "Genmask", "Flags", "Metric");
384+ if (fgets(line, sizeof(line), fp)) {
385+ while (fgets(line, sizeof(line), fp)) {
386+ if (sscanf(line, "%15s %x %x %x %*u %*u %u %x", iface, &d, &g, &m, &metric, &flags) == 6) {
387+ struct in_addr dest_addr, gw_addr, mask_addr;
388+ dest_addr.s_addr = d;
389+ gw_addr.s_addr = g;
390+ mask_addr.s_addr = m;
391+ strlcpy(dest, inet_ntoa(dest_addr), sizeof(dest));
392+ strlcpy(gateway, inet_ntoa(gw_addr), sizeof(gateway));
393+ strlcpy(mask, inet_ntoa(mask_addr), sizeof(mask));
394+ printf("%-10s %-15s %-15s %-15s 0x%04X %-6u\n", iface, dest, gateway, mask, flags, metric);
395+ }
396+ }
397+ }
398+ fclose(fp);
399+ return 0;
400+#else
401+ printf("route listing not implemented on this OS\n");
402+ return 0;
403+#endif
404+}
1@@ -0,0 +1,147 @@
2+#include "../util.h"
3+
4+#include <stdio.h>
5+#include <stdlib.h>
6+#include <string.h>
7+#include <time.h>
8+#include <unistd.h>
9+
10+#ifdef __linux__
11+#include <sys/sysinfo.h>
12+#else
13+#include <sys/sysctl.h>
14+#include <sys/time.h>
15+#endif
16+
17+int
18+get_uptime(long *uptime_secs)
19+{
20+#ifdef __linux__
21+ struct sysinfo info;
22+
23+ if (sysinfo(&info) < 0)
24+ return -1;
25+ *uptime_secs = info.uptime;
26+ return 0;
27+#else
28+ struct timeval boottime;
29+ size_t len = sizeof(boottime);
30+ int mib[2] = { CTL_KERN, KERN_BOOTTIME };
31+ time_t now;
32+
33+ if (sysctl(mib, 2, &boottime, &len, NULL, 0) < 0)
34+ return -1;
35+ time(&now);
36+ *uptime_secs = (long)(now - boottime.tv_sec);
37+ return 0;
38+#endif
39+}
40+
41+int
42+get_loads(double loads[3])
43+{
44+ if (getloadavg(loads, 3) != 3)
45+ return -1;
46+ return 0;
47+}
48+
49+int
50+get_meminfo(struct MemInfo *mi)
51+{
52+ memset(mi, 0, sizeof(*mi));
53+#ifdef __linux__
54+ struct sysinfo info;
55+ unsigned long long unit;
56+
57+ if (sysinfo(&info) < 0)
58+ return -1;
59+ unit = info.mem_unit ? info.mem_unit : 1;
60+ mi->total = info.totalram * unit;
61+ mi->free = info.freeram * unit;
62+ mi->shared = info.sharedram * unit;
63+ mi->buffers = info.bufferram * unit;
64+ mi->cached = 0;
65+ mi->totalswap = info.totalswap * unit;
66+ mi->freeswap = info.freeswap * unit;
67+ return 0;
68+#else
69+ int mib[2];
70+ size_t len;
71+ unsigned long physmem = 0;
72+ unsigned int page_size = 4096;
73+ unsigned int free_pages = 0;
74+
75+ mib[0] = CTL_HW;
76+ mib[1] = HW_PHYSMEM;
77+ len = sizeof(physmem);
78+ if (sysctl(mib, 2, &physmem, &len, NULL, 0) < 0)
79+ return -1;
80+ mi->total = physmem;
81+
82+ mib[0] = CTL_HW;
83+ mib[1] = HW_PAGESIZE;
84+ len = sizeof(page_size);
85+ sysctl(mib, 2, &page_size, &len, NULL, 0);
86+
87+ len = sizeof(free_pages);
88+ if (sysctlbyname("vm.stats.vm.v_free_count", &free_pages, &len, NULL, 0) >= 0) {
89+ mi->free = (unsigned long long)free_pages * page_size;
90+ } else {
91+ mi->free = mi->total / 4;
92+ }
93+ return 0;
94+#endif
95+}
96+
97+#ifdef __linux__
98+#include <sys/klog.h>
99+ssize_t
100+get_dmesg(char *buf, size_t len)
101+{
102+ if (!buf && len == 0)
103+ return klogctl(10, NULL, 0);
104+ return klogctl(3, buf, len);
105+}
106+
107+int
108+clear_dmesg(void)
109+{
110+ if (klogctl(5, NULL, 0) < 0)
111+ return -1;
112+ return 0;
113+}
114+
115+int
116+set_console_level(int level)
117+{
118+ if (klogctl(8, NULL, level) < 0)
119+ return -1;
120+ return 0;
121+}
122+#else
123+ssize_t
124+get_dmesg(char *buf, size_t len)
125+{
126+ int mib[2];
127+ size_t needed = len;
128+
129+ mib[0] = CTL_KERN;
130+ mib[1] = KERN_MSGBUF;
131+ if (sysctl(mib, 2, buf, &needed, NULL, 0) < 0)
132+ return -1;
133+ return (ssize_t)needed;
134+}
135+
136+int
137+clear_dmesg(void)
138+{
139+ return -1;
140+}
141+
142+int
143+set_console_level(int level)
144+{
145+ (void)level;
146+ return -1;
147+}
148+#endif
1@@ -0,0 +1,437 @@
2+/* see license file for copyright and license details */
3+#include "../tls.h"
4+#include "../util.h"
5+
6+#include <errno.h>
7+#include <signal.h>
8+#include <stdio.h>
9+#include <stdlib.h>
10+#include <string.h>
11+#include <unistd.h>
12+
13+#if FEATURE_USE_LIBRESSL
14+#include <tls.h>
15+
16+struct TlsSocket {
17+ int fd;
18+ int is_tls;
19+ struct tls *ctx;
20+};
21+
22+#elif FEATURE_USE_BEARSSL
23+#include <bearssl.h>
24+
25+struct my_x509_context {
26+ const br_x509_class *vtable;
27+ br_x509_minimal_context minimal;
28+};
29+
30+struct TlsSocket {
31+ int fd;
32+ int is_tls;
33+ br_ssl_client_context sc;
34+ struct my_x509_context my_x509;
35+ unsigned char iobuf[BR_SSL_BUFSIZE_BIDI];
36+ br_sslio_context ioc;
37+};
38+
39+struct dn_accum {
40+ unsigned char *data;
41+ size_t len;
42+ size_t cap;
43+};
44+
45+static void
46+append_dn_callback(void *dn_ctx, const void *src, size_t len)
47+{
48+ struct dn_accum *accum = dn_ctx;
49+ if (accum->len + len > accum->cap) {
50+ accum->cap = accum->len + len + 256;
51+ accum->data = erealloc(accum->data, accum->cap);
52+ }
53+ memcpy(accum->data + accum->len, src, len);
54+ accum->len += len;
55+}
56+
57+static void
58+my_start_chain(const br_x509_class **ctx, const char *server_name)
59+{
60+ struct my_x509_context *t = (struct my_x509_context *)ctx;
61+ br_x509_minimal_vtable.start_chain((const br_x509_class **)&t->minimal, server_name);
62+}
63+
64+static void
65+my_start_cert(const br_x509_class **ctx, uint32_t length)
66+{
67+ struct my_x509_context *t = (struct my_x509_context *)ctx;
68+ br_x509_minimal_vtable.start_cert((const br_x509_class **)&t->minimal, length);
69+}
70+
71+static void
72+my_append(const br_x509_class **ctx, const unsigned char *buf, size_t len)
73+{
74+ struct my_x509_context *t = (struct my_x509_context *)ctx;
75+ br_x509_minimal_vtable.append((const br_x509_class **)&t->minimal, buf, len);
76+}
77+
78+static void
79+my_end_cert(const br_x509_class **ctx)
80+{
81+ struct my_x509_context *t = (struct my_x509_context *)ctx;
82+ br_x509_minimal_vtable.end_cert((const br_x509_class **)&t->minimal);
83+}
84+
85+static unsigned
86+my_end_chain(const br_x509_class **ctx)
87+{
88+ struct my_x509_context *t = (struct my_x509_context *)ctx;
89+ (void)br_x509_minimal_vtable.end_chain((const br_x509_class **)&t->minimal);
90+ /* always succeed and accept certificate when check_cert is 0 */
91+ return 0;
92+}
93+
94+static const br_x509_pkey *
95+my_get_pkey(const br_x509_class *const *ctx, unsigned *usages)
96+{
97+ const struct my_x509_context *t = (const struct my_x509_context *)ctx;
98+ return br_x509_minimal_vtable.get_pkey((const br_x509_class *const *)&t->minimal, usages);
99+}
100+
101+static const br_x509_class my_x509_vtable = {
102+ sizeof(struct my_x509_context),
103+ my_start_chain,
104+ my_start_cert,
105+ my_append,
106+ my_end_cert,
107+ my_end_chain,
108+ my_get_pkey
109+};
110+
111+static int
112+sock_read(void *ctx, unsigned char *buf, size_t len)
113+{
114+ int fd = *(int *)ctx;
115+ for (;;) {
116+ ssize_t rlen = read(fd, buf, len);
117+ if (rlen <= 0) {
118+ if (rlen < 0 && errno == EINTR)
119+ continue;
120+ return -1;
121+ }
122+ return (int)rlen;
123+ }
124+}
125+
126+static int
127+sock_write(void *ctx, const unsigned char *buf, size_t len)
128+{
129+ int fd = *(int *)ctx;
130+ for (;;) {
131+ ssize_t wlen = write(fd, buf, len);
132+ if (wlen <= 0) {
133+ if (wlen < 0 && errno == EINTR)
134+ continue;
135+ return -1;
136+ }
137+ return (int)wlen;
138+ }
139+}
140+
141+static int
142+b64_decode_char(char c)
143+{
144+ if (c >= 'A' && c <= 'Z') return c - 'A';
145+ if (c >= 'a' && c <= 'z') return c - 'a' + 26;
146+ if (c >= '0' && c <= '9') return c - '0' + 52;
147+ if (c == '+') return 62;
148+ if (c == '/') return 63;
149+ return -1;
150+}
151+
152+static size_t
153+b64_decode(const char *src, size_t src_len, unsigned char *dst)
154+{
155+ size_t i, j = 0;
156+ int val = 0, valb = -8;
157+ for (i = 0; i < src_len; i++) {
158+ int c = b64_decode_char(src[i]);
159+ if (c >= 0) {
160+ val = (val << 6) | c;
161+ valb += 6;
162+ if (valb >= 0) {
163+ dst[j++] = (val >> valb) & 0xFF;
164+ valb -= 8;
165+ }
166+ }
167+ }
168+ return j;
169+}
170+
171+static int
172+decode_cert_der(const unsigned char *der, size_t der_len, br_x509_trust_anchor *ta)
173+{
174+ br_x509_decoder_context dc;
175+ struct dn_accum accum = { NULL, 0, 0 };
176+ const br_x509_pkey *pk;
177+
178+ br_x509_decoder_init(&dc, append_dn_callback, &accum);
179+ br_x509_decoder_push(&dc, der, der_len);
180+ if (br_x509_decoder_last_error(&dc) != 0) {
181+ free(accum.data);
182+ return 0;
183+ }
184+ pk = br_x509_decoder_get_pkey(&dc);
185+ if (!pk) {
186+ free(accum.data);
187+ return 0;
188+ }
189+
190+ ta->dn.data = accum.data;
191+ ta->dn.len = accum.len;
192+ ta->flags = br_x509_decoder_isCA(&dc) ? BR_X509_TA_CA : 0;
193+ ta->pkey.key_type = pk->key_type;
194+
195+ if (pk->key_type == BR_KEYTYPE_RSA) {
196+ ta->pkey.key.rsa.nlen = pk->key.rsa.nlen;
197+ ta->pkey.key.rsa.n = emalloc(pk->key.rsa.nlen);
198+ memcpy(ta->pkey.key.rsa.n, pk->key.rsa.n, pk->key.rsa.nlen);
199+ ta->pkey.key.rsa.elen = pk->key.rsa.elen;
200+ ta->pkey.key.rsa.e = emalloc(pk->key.rsa.elen);
201+ memcpy(ta->pkey.key.rsa.e, pk->key.rsa.e, pk->key.rsa.elen);
202+ } else if (pk->key_type == BR_KEYTYPE_EC) {
203+ ta->pkey.key.ec.curve = pk->key.ec.curve;
204+ ta->pkey.key.ec.qlen = pk->key.ec.qlen;
205+ ta->pkey.key.ec.q = emalloc(pk->key.ec.qlen);
206+ memcpy(ta->pkey.key.ec.q, pk->key.ec.q, pk->key.ec.qlen);
207+ } else {
208+ free(accum.data);
209+ return 0;
210+ }
211+ return 1;
212+}
213+
214+static br_x509_trust_anchor *tas = NULL;
215+static size_t tas_num = 0;
216+
217+static void
218+load_ca_certs(void)
219+{
220+ static const char *ca_paths[] = {
221+ "/etc/ssl/certs/ca-certificates.crt",
222+ "/etc/ssl/cert.pem",
223+ "/etc/pki/tls/certs/ca-bundle.crt",
224+ };
225+ FILE *fp = NULL;
226+ size_t i;
227+ char line[512];
228+ struct {
229+ char *data;
230+ size_t len;
231+ size_t cap;
232+ } pem = { NULL, 0, 0 };
233+ int in_cert = 0;
234+
235+ for (i = 0; i < LEN(ca_paths); i++) {
236+ if ((fp = fopen(ca_paths[i], "r")))
237+ break;
238+ }
239+ if (!fp) {
240+ weprintf("no CA certificates found, TLS verification will fail\n");
241+ return;
242+ }
243+
244+ while (fgets(line, sizeof(line), fp)) {
245+ if (strncmp(line, "-----BEGIN CERTIFICATE-----", 27) == 0) {
246+ in_cert = 1;
247+ pem.len = 0;
248+ } else if (strncmp(line, "-----END CERTIFICATE-----", 25) == 0) {
249+ if (in_cert) {
250+ unsigned char *der = emalloc(pem.len);
251+ size_t der_len = b64_decode(pem.data, pem.len, der);
252+ br_x509_trust_anchor ta;
253+ if (decode_cert_der(der, der_len, &ta)) {
254+ tas = ereallocarray(tas, tas_num + 1, sizeof(*tas));
255+ tas[tas_num++] = ta;
256+ }
257+ free(der);
258+ in_cert = 0;
259+ }
260+ } else if (in_cert) {
261+ size_t llen = strlen(line);
262+ while (llen > 0 && (line[llen-1] == '\r' || line[llen-1] == '\n'))
263+ llen--;
264+ if (pem.len + llen > pem.cap) {
265+ pem.cap = pem.len + llen + 1024;
266+ pem.data = erealloc(pem.data, pem.cap);
267+ }
268+ memcpy(pem.data + pem.len, line, llen);
269+ pem.len += llen;
270+ }
271+ }
272+ free(pem.data);
273+ fclose(fp);
274+}
275+#else
276+struct TlsSocket {
277+ int fd;
278+ int is_tls;
279+};
280+#endif
281+
282+struct TlsSocket *
283+tls_connect(int fd, const char *host, int check_cert, int is_tls)
284+{
285+ struct TlsSocket *s;
286+
287+ s = emalloc(sizeof(*s));
288+ s->fd = fd;
289+ s->is_tls = is_tls;
290+
291+ if (!is_tls)
292+ return s;
293+
294+#if FEATURE_USE_LIBRESSL
295+ {
296+ struct tls_config *cfg;
297+
298+ s->ctx = tls_client();
299+ if (!s->ctx) {
300+ weprintf("tls_client failed\n");
301+ free(s);
302+ return NULL;
303+ }
304+ cfg = tls_config_new();
305+ if (!cfg) {
306+ weprintf("tls_config_new failed\n");
307+ tls_free(s->ctx);
308+ free(s);
309+ return NULL;
310+ }
311+ if (!check_cert) {
312+ tls_config_insecure_noverifycert(cfg);
313+ tls_config_insecure_noverifyname(cfg);
314+ }
315+ if (tls_configure(s->ctx, cfg) < 0) {
316+ weprintf("tls_configure: %s\n", tls_error(s->ctx));
317+ tls_config_free(cfg);
318+ tls_free(s->ctx);
319+ free(s);
320+ return NULL;
321+ }
322+ tls_config_free(cfg);
323+
324+ if (tls_connect_socket(s->ctx, fd, host) < 0) {
325+ weprintf("tls_connect_socket: %s\n", tls_error(s->ctx));
326+ tls_free(s->ctx);
327+ free(s);
328+ return NULL;
329+ }
330+ }
331+#elif FEATURE_USE_BEARSSL
332+ {
333+ signal(SIGPIPE, SIG_IGN);
334+
335+ if (check_cert) {
336+ static int ca_loaded = 0;
337+ if (!ca_loaded) {
338+ load_ca_certs();
339+ ca_loaded = 1;
340+ }
341+ br_ssl_client_init_full(&s->sc, &s->my_x509.minimal, tas, tas_num);
342+ } else {
343+ br_ssl_client_init_full(&s->sc, &s->my_x509.minimal, NULL, 0);
344+ s->my_x509.vtable = &my_x509_vtable;
345+ br_ssl_engine_set_x509(&s->sc.eng, &s->my_x509.vtable);
346+ }
347+
348+ br_ssl_engine_set_buffer(&s->sc.eng, s->iobuf, sizeof(s->iobuf), 1);
349+ br_ssl_client_reset(&s->sc, host, 0);
350+ br_sslio_init(&s->ioc, &s->sc.eng, sock_read, &s->fd, sock_write, &s->fd);
351+
352+ if (br_sslio_flush(&s->ioc) < 0) {
353+ weprintf("TLS handshake failed with %s\n", host);
354+ free(s);
355+ return NULL;
356+ }
357+ }
358+#else
359+ weprintf("TLS not supported, compile with FEATURE_USE_LIBRESSL or FEATURE_USE_BEARSSL\n");
360+ free(s);
361+ return NULL;
362+#endif
363+
364+ return s;
365+}
366+
367+ssize_t
368+tls_read(struct TlsSocket *s, void *buf, size_t len)
369+{
370+ if (!s->is_tls) {
371+ for (;;) {
372+ ssize_t r = read(s->fd, buf, len);
373+ if (r < 0 && errno == EINTR)
374+ continue;
375+ return r;
376+ }
377+ }
378+#if FEATURE_USE_LIBRESSL
379+ for (;;) {
380+ ssize_t r = tls_read(s->ctx, buf, len);
381+ if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT)
382+ continue;
383+ return r;
384+ }
385+#elif FEATURE_USE_BEARSSL
386+ return br_sslio_read(&s->ioc, buf, len);
387+#else
388+ return -1;
389+#endif
390+}
391+
392+ssize_t
393+tls_write(struct TlsSocket *s, const void *buf, size_t len)
394+{
395+ if (!s->is_tls) {
396+ for (;;) {
397+ ssize_t r = write(s->fd, buf, len);
398+ if (r < 0 && errno == EINTR)
399+ continue;
400+ return r;
401+ }
402+ }
403+#if FEATURE_USE_LIBRESSL
404+ for (;;) {
405+ ssize_t r = tls_write(s->ctx, buf, len);
406+ if (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT)
407+ continue;
408+ return r;
409+ }
410+#elif FEATURE_USE_BEARSSL
411+ {
412+ int r = br_sslio_write_all(&s->ioc, buf, len);
413+ if (r < 0)
414+ return -1;
415+ if (br_sslio_flush(&s->ioc) < 0)
416+ return -1;
417+ return len;
418+ }
419+#else
420+ return -1;
421+#endif
422+}
423+
424+void
425+tls_close(struct TlsSocket *s, int close_fd)
426+{
427+ if (s->is_tls) {
428+#if FEATURE_USE_LIBRESSL
429+ tls_close(s->ctx);
430+ tls_free(s->ctx);
431+#elif FEATURE_USE_BEARSSL
432+ br_sslio_close(&s->ioc);
433+#endif
434+ }
435+ if (close_fd)
436+ close(s->fd);
437+ free(s);
438+}
1@@ -0,0 +1,24 @@
2+/* see license file for copyright and license details */
3+#ifndef REDLINE_H_
4+#define REDLINE_H_
5+
6+#include <stddef.h>
7+
8+struct redlineCompletions {
9+ size_t len;
10+ char **cvec;
11+};
12+
13+char *redline(const char *prompt);
14+void redlineSetCompletionCallback(void (*cb)(const char *, struct redlineCompletions *));
15+void redlineAddCompletion(struct redlineCompletions *lc, const char *str);
16+void redlineHistoryAdd(const char *line);
17+void redlineHistorySetMaxLen(int len);
18+int redlineHistorySave(const char *filename);
19+int redlineHistoryLoad(const char *filename);
20+char *redlineHistoryGet(int idx);
21+int redlineHistoryLen(void);
22+void redlineSetMultiLine(int ml);
23+void redlineClearScreen(void);
24+
25+#endif /* REDLINE_H_ */
1@@ -0,0 +1,21 @@
2+/* see license file for copyright and license details */
3+#ifndef TLS_H_
4+#define TLS_H_
5+
6+#include <sys/types.h>
7+
8+struct TlsSocket;
9+
10+/* tls_connect takes an already connected socket fd and upgrades it to tls
11+ if check_cert is 0 certificate validation is bypassed
12+ returns a new tls socket or null on error */
13+struct TlsSocket *tls_connect(int fd, const char *host, int check_cert, int is_tls);
14+
15+/* standard read and write wrapping either libressl bearssl or plain fallback */
16+ssize_t tls_read(struct TlsSocket *s, void *buf, size_t len);
17+ssize_t tls_write(struct TlsSocket *s, const void *buf, size_t len);
18+
19+/* closes the tls connection and optionally closes the socket fd */
20+void tls_close(struct TlsSocket *s, int close_fd);
21+
22+#endif /* tls_h_ */
1@@ -106,3 +106,52 @@ void explicit_bzero(void *, size_t);
2 void recurse_dir(const char *, void (*)(const char *));
3 void devtotty(int, int *, int *);
4 int ttytostr(int, int, char *, size_t);
5+
6+#include <sys/socket.h>
7+#include <netinet/in.h>
8+
9+#ifndef IFNAMSIZ
10+#define IFNAMSIZ 16
11+#endif
12+
13+struct NetStats {
14+ unsigned long long rx_bytes;
15+ unsigned long long rx_packets;
16+ unsigned long long rx_errs;
17+ unsigned long long rx_drop;
18+ unsigned long long tx_bytes;
19+ unsigned long long tx_packets;
20+ unsigned long long tx_errs;
21+ unsigned long long tx_drop;
22+};
23+
24+struct NetInterface {
25+ char name[IFNAMSIZ];
26+ unsigned int flags;
27+ int mtu;
28+ int metric;
29+ unsigned char mac[6];
30+ int has_mac;
31+
32+ /* ipv4 addresses */
33+ struct sockaddr_in ipv4_addr;
34+ int has_ipv4;
35+ struct sockaddr_in ipv4_mask;
36+ struct sockaddr_in ipv4_brd;
37+
38+ /* ipv6 addresses */
39+ struct sockaddr_in6 ipv6_addr;
40+ int has_ipv6;
41+ unsigned int ipv6_scope;
42+ unsigned int ipv6_prefix;
43+};
44+
45+struct MemInfo {
46+ unsigned long long total;
47+ unsigned long long free;
48+ unsigned long long shared;
49+ unsigned long long buffers;
50+ unsigned long long cached;
51+ unsigned long long totalswap;
52+ unsigned long long freeswap;
53+};