commit b5460d3
shrub
·
2026-06-21 15:57:21 +0000 UTC
parent b5460d3
init
48 files changed,
+4810,
-0
+2,
-0
1@@ -0,0 +1,2 @@
2+**/.ninja_log
3+**/.ninja deps
+0,
-0
A
drudge
+84,
-0
1@@ -0,0 +1,84 @@
2+#!/bin/sh
3+# fetch n format drudge report headlines
4+set -eu
5+
6+url=https://www.drudgereport.com/
7+limit=5
8+
9+usage() {
10+ printf 'usage: %s [-n count]\n' "${0##*/}" >&2
11+ exit 2
12+}
13+
14+while getopts 'n:' opt; do
15+ case $opt in
16+ n)
17+ limit=$OPTARG
18+ ;;
19+ *)
20+ usage
21+ ;;
22+ esac
23+done
24+
25+shift $((OPTIND - 1))
26+
27+[ "$#" -eq 0 ] || usage
28+
29+case $limit in
30+ ''|*[!0-9]*)
31+ usage
32+ ;;
33+esac
34+
35+[ "$limit" -gt 0 ] 2>/dev/null || usage
36+
37+curl -L -s "$url" |
38+tr '\n' ' ' |
39+sed 's#</[Aa]>#</A>\
40+#g; s#<[Aa][[:space:]]#\
41+<A #g' |
42+awk -v limit="$limit" '
43+BEGIN {
44+ n = 0
45+}
46+
47+{
48+ segment = $0
49+ sub(/^[[:space:]]*/, "", segment)
50+
51+ if (segment !~ /^<[Aa][[:space:]]/)
52+ next
53+
54+ if (segment !~ /<\/[Aa]>/)
55+ next
56+
57+ href = segment
58+ sub(/^.*[Hh][Rr][Ee][Ff]="/, "", href)
59+ sub(/".*$/, "", href)
60+
61+ text = segment
62+ sub(/^<[Aa][^>]*>/, "", text)
63+ sub(/<\/[Aa]>.*/, "", text)
64+ gsub(/<[^>]*>/, " ", text)
65+ gsub(/ /, " ", text)
66+ gsub(/'/, "'"'"'", text)
67+ gsub(/"/, "\"", text)
68+ gsub(/&/, "\\&", text)
69+ gsub(/[[:space:]][[:space:]]*/, " ", text)
70+ sub(/^ /, "", text)
71+ sub(/ $/, "", text)
72+
73+ if (href ~ /^https?:\/\/(www\.)?drudgereport\.com\/?$/)
74+ next
75+ if (text == "")
76+ next
77+
78+ print text
79+ print href
80+
81+ n++
82+ if (n == limit)
83+ exit
84+}
85+'
A
fine
+66,
-0
1@@ -0,0 +1,66 @@
2+#!/bin/sh
3+# its like hyperfine but not written in rust and also worse but it has everything i need so
4+#
5+# usage:
6+# fine 'command'
7+# fine -t 20 'command'
8+
9+set -eu
10+
11+times=10
12+
13+usage() {
14+ printf 'usage: %s [-t times] command\n' "${0##*/}" >&2
15+ exit 2
16+}
17+
18+while getopts 't:' opt; do
19+ case $opt in
20+ t)
21+ times=$OPTARG
22+ ;;
23+ *)
24+ usage
25+ ;;
26+ esac
27+done
28+shift $((OPTIND - 1))
29+
30+[ "$#" -eq 1 ] || usage
31+
32+case $times in
33+ ''|*[!0-9]*)
34+ usage
35+ ;;
36+esac
37+
38+[ "$times" -gt 0 ] 2>/dev/null || usage
39+
40+cmd=$1
41+
42+now() {
43+ date +%s.%N
44+}
45+
46+i=1
47+while [ "$i" -le "$times" ]; do
48+ printf 'run %d/%d\n' "$i" "$times" >&2
49+ start=$(now)
50+ sh -c "$cmd" >/dev/null 2>&1
51+ end=$(now)
52+ awk -v start="$start" -v end="$end" 'BEGIN { printf "%.6f\n", end - start }'
53+ i=$((i + 1))
54+done | awk '
55+{
56+ sum += $1
57+ n++
58+}
59+{
60+ printf "run %d: %.6fs\n", n, $1
61+}
62+END {
63+ if (n == 0)
64+ exit 1
65+ printf "avg: %.6fs\n", sum / n
66+}
67+'
A
flick
+23,
-0
1@@ -0,0 +1,23 @@
2+#!/bin/sh
3+# flick through lots of files
4+# example:
5+#
6+# find -type f -maxdepth 2 -name '*.c' | flick
7+set -eu
8+
9+pager=${PAGER:-less}
10+pagercmd=${pager%% *}
11+
12+trap 'exit 130' INT TERM
13+
14+while IFS= read -r page; do
15+ [ -n "$page" ] || continue
16+ case $pagercmd in
17+ less|*/less)
18+ LESS="${LESS:+$LESS }-K" "$pager" -- "$page"
19+ ;;
20+ *)
21+ "$pager" -- "$page"
22+ ;;
23+ esac
24+done
A
gen
+80,
-0
1@@ -0,0 +1,80 @@
2+#!/bin/sh
3+# gen is a small generator for ninja build files. before i was hand writing them, but this makes it
4+# pretty easy. it takes a list of files from stdin and a target as an argument, soyou can do something
5+# like:
6+#
7+# find. -name '*.c' | gen binary
8+#
9+# and that will generate ninja to build the binary. you can edit the variables at the top of the file
10+# to add any libraries you link, or edit cflags, etc. its's quite simplistic, so it only really works
11+# for simple projects with a few C files and a single target. the ninja files are simple enough for
12+# users to edit to their liking, so you can just generate them once and ship the generated files.
13+#
14+# it only works for C sources, and dependency tracking is handled by the compiler. cleaning is handled
15+# by ninja, eg 'ninja -t clean'
16+
17+set -eu
18+
19+if [ "$#" -ne 1 ]; then
20+ printf 'usage: %s target\n' "${0##*/}" >&2
21+ exit 2
22+fi
23+
24+target=$1
25+objs=
26+first=1
27+printf "# i know this is a ninja file, but you're allowed to edit it.\n"
28+printf "# you can also pass things on the command line if you like, eg\n"
29+printf "#\n"
30+printf "# CC=clang ninja\n"
31+printf "\n"
32+printf 'cc = $${CC:-cc}\n'
33+printf 'cflags = $${CFLAGS:--O2 -std=c99}\n'
34+printf 'ldflags = $${LDFLAGS:-}\n'
35+printf 'libs = $${LIBS:-}\n'
36+printf 'prefix = $${PREFIX:-/usr/local}\n'
37+printf 'destdir = $${DESTDIR:-}\n'
38+printf 'bindir = $${BINDIR:-$${DESTDIR:-}$${PREFIX:-/usr/local}/bin}\n'
39+printf '\n'
40+printf 'rule cc\n'
41+printf ' command = $cc $cflags -MMD -MF $out.d -c -o $out $in\n'
42+printf ' deps = gcc\n'
43+printf ' depfile = $out.d\n'
44+printf ' description = cc $out\n'
45+printf '\n'
46+printf 'rule link\n'
47+printf ' command = $cc $ldflags -o $out $in $libs\n'
48+printf ' description = link $out\n'
49+printf '\n'
50+printf 'rule install\n'
51+printf ' command = mkdir -p $bindir && cp %s $bindir/\n' "$target"
52+printf ' description = install %s\n' "$target"
53+printf '\n'
54+
55+while IFS= read -r src; do
56+ [ -n "$src" ] || continue
57+ case $src in
58+ *.c) ;;
59+ *) continue ;;
60+ esac
61+ obj=${src%.c}.o
62+ printf 'build %s: cc %s\n' "$obj" "$src"
63+ if [ $first -eq 1 ]; then
64+ objs=$obj
65+ first=0
66+ else
67+ objs="$objs $obj"
68+ fi
69+done
70+
71+if [ $first -eq 1 ]; then
72+ printf '%s: no .c files on stdin\n' "${0##*/}" >&2
73+ exit 1
74+fi
75+
76+printf '\n'
77+printf 'build %s: link %s\n' "$target" "$objs"
78+printf 'build all: phony %s\n' "$target"
79+printf 'build install: install %s\n' "$target"
80+printf '\n'
81+printf 'default all\n'
A
license
+18,
-0
1@@ -0,0 +1,18 @@
2+to the extent possible under law, i(shrub) waive all copyright or related neighbouring rights to these works:
3+
4+as such, you have freedom to use, copy, modify, and/or distribute this software for any purpose with or without fee.
5+
6+in jurisdictions where the above is not possible, you are free to take these works under the 0BSD license, which is as follows:
7+
8+----------------------
9+
10+Permission to use, copy, modify, and distribute this software for
11+any purpose with or without fee is hereby granted.
12+
13+THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
14+WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
15+OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
16+FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
17+DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
18+AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
19+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
A
life
+73,
-0
1@@ -0,0 +1,73 @@
2+#!/bin/sh
3+# conways game of life
4+# usage:
5+# life [generations]
6+
7+# dependencies:
8+# shell
9+# awk
10+# 'rs' command from bs
11+
12+set -eu
13+
14+rows=10
15+cols=10
16+gens=${1:-20}
17+delay=${DELAY:-0.15}
18+state=$(mktemp)
19+
20+cleanup() {
21+ rm -f "$state" "$state.next"
22+}
23+trap cleanup EXIT INT TERM
24+
25+awk -v n="$((rows * cols))" '
26+BEGIN {
27+ srand()
28+ for (i = 1; i <= n; i++)
29+ print rand() < 0.35 ? 1 : 0
30+}
31+' > "$state"
32+
33+gen=0
34+while [ "$gen" -lt "$gens" ]; do
35+ printf '\033[H\033[J'
36+ awk '{ print $1 ? "@" : "." }' "$state" | rs "$rows" "$cols"
37+ printf '\ngen %s\n' "$gen"
38+
39+ awk -v rows="$rows" -v cols="$cols" '
40+ {
41+ cell[NR] = $1
42+ }
43+ END {
44+ for (r = 1; r <= rows; r++) {
45+ for (c = 1; c <= cols; c++) {
46+ idx = (r - 1) * cols + c
47+ n = 0
48+ for (dr = -1; dr <= 1; dr++) {
49+ for (dc = -1; dc <= 1; dc++) {
50+ if (dr == 0 && dc == 0)
51+ continue
52+ rr = r + dr
53+ cc = c + dc
54+ if (rr < 1 || rr > rows || cc < 1 || cc > cols)
55+ continue
56+ n += cell[(rr - 1) * cols + cc] ? 1 : 0
57+ }
58+ }
59+ alive = cell[idx] ? 1 : 0
60+ if (alive)
61+ nxt[idx] = (n == 2 || n == 3) ? 1 : 0
62+ else
63+ nxt[idx] = (n == 3) ? 1 : 0
64+ }
65+ }
66+ for (i = 1; i <= rows * cols; i++)
67+ print nxt[i]
68+ }
69+ ' "$state" > "$state.next"
70+
71+ mv "$state.next" "$state"
72+ gen=$((gen + 1))
73+ sleep "$delay"
74+done
A
lint
+109,
-0
1@@ -0,0 +1,109 @@
2+#!/bin/sh
3+# a small clang wrapper that prints in the same style as the old sun linter. it needs some work,
4+# it should probably (definitely) use compile_commands.json, but it's still quite useful.
5+# usage:
6+#
7+# lint [files/directory]
8+# lint (recursively searches for .c files)
9+#
10+# dependencies:
11+# shell
12+# clang
13+set -eu
14+
15+clang=${CLANG:-clang}
16+tmp=${TMPDIR:-/tmp}/lint.$$.out
17+trap 'rm -f "$tmp"' EXIT HUP INT TERM
18+
19+list() {
20+ if [ "$#" -eq 0 ]; then
21+ find . -type f -name '*.c'
22+ return
23+ fi
24+ for path do
25+ if [ -d "$path" ]; then
26+ find "$path" -type f -name '*.c'
27+ elif [ -f "$path" ]; then
28+ case $path in
29+ *.c)
30+ printf '%s\n' "$path"
31+ ;;
32+ esac
33+ fi
34+ done
35+}
36+
37+if ! list "$@" | grep . > /dev/null; then
38+ printf '%s: no .c files found\n' "${0##*/}" >&2
39+ exit 1
40+fi
41+
42+set +e
43+list "$@" | sort -u |
44+xargs "$clang" -fsyntax-only \
45+ -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion \
46+ -Wshadow -Wformat=2 -Wimplicit-fallthrough -Wcast-qual \
47+ -Wwrite-strings -Wstrict-prototypes -Wmissing-prototypes \
48+ -Wpointer-arith -Wundef -Wunreachable-code -Wnull-dereference \
49+ > /dev/null 2> "$tmp"
50+status=$?
51+set -e
52+
53+list "$@" | sort -u | awk -v diagfile="$tmp" '
54+BEGIN {
55+ nfiles = 0
56+}
57+
58+{
59+ files[++nfiles] = $0
60+}
61+
62+END {
63+ while ((getline line < diagfile) > 0) {
64+ sub(/\r$/, "", line)
65+ if (match(line, /^([^:]+):([0-9]+):([0-9]+): (warning|error|note): (.*)$/, m)) {
66+ file = m[1]
67+ lnum = m[2]
68+ kind = m[4]
69+ msg = m[5]
70+ if (kind == "note")
71+ continue
72+ gsub(/[[:space:]]*\[-W[^]]+\]$/, "", msg)
73+ msgs[file] = msgs[file] sprintf("(%s) %s: %s\n", lnum, kind, msg)
74+ continue
75+ }
76+ if (match(line, /^([^:]+): (warning|error|note): (.*)$/, m)) {
77+ file = m[1]
78+ kind = m[2]
79+ msg = m[3]
80+ if (kind == "note")
81+ continue
82+ gsub(/[[:space:]]*\[-W[^]]+\]$/, "", msg)
83+ msgs[file] = msgs[file] sprintf("%s: %s\n", kind, msg)
84+ }
85+ }
86+ close(diagfile)
87+ for (i = 1; i <= nfiles; ++i) {
88+ file = files[i]
89+ if (file in msgs) {
90+ print file ":"
91+ printf "%s", msgs[file]
92+ }
93+ }
94+ for (file in msgs) {
95+ found = 0
96+ for (i = 1; i <= nfiles; ++i) {
97+ if (files[i] == file) {
98+ found = 1
99+ break
100+ }
101+ }
102+ if (!found) {
103+ print file ":"
104+ printf "%s", msgs[file]
105+ }
106+ }
107+}
108+'
109+
110+exit "$status"
+21,
-0
1@@ -0,0 +1,21 @@
2+extern const char *argv0;
3+
4+#define ARGBEGIN \
5+ for (;;) { \
6+ if (argc > 0) \
7+ ++argv, --argc; \
8+ if (argc == 0 || (*argv)[0] != '-') \
9+ break; \
10+ if ((*argv)[1] == '-' && !(*argv)[2]) { \
11+ ++argv, --argc; \
12+ break; \
13+ } \
14+ for (char *opt_ = &(*argv)[1], done_ = 0; !done_ && *opt_; ++opt_) { \
15+ switch (*opt_)
16+
17+#define ARGEND \
18+ } \
19+ }
20+
21+#define EARGF(x) \
22+ (done_ = 1, *++opt_ ? opt_ : argv[1] ? --argc, *++argv : ((x), abort(), (char *)0))
+43,
-0
1@@ -0,0 +1,43 @@
2+# i know this is a ninja file, but you're allowed to edit it.
3+# you can also pass things on the command line if you like, eg
4+#
5+# CC=clang ninja
6+
7+cc = $${CC:-cc}
8+cflags = $${CFLAGS:--O2 -std=c99}
9+ldflags = $${LDFLAGS:-}
10+libs = $${LIBS:-}
11+prefix = $${PREFIX:-/usr/local}
12+destdir = $${DESTDIR:-}
13+bindir = $${BINDIR:-$${DESTDIR:-}$${PREFIX:-/usr/local}/bin}
14+
15+rule cc
16+ command = $cc $cflags -MMD -MF $out.d -c -o $out $in
17+ deps = gcc
18+ depfile = $out.d
19+ description = cc $out
20+
21+rule link
22+ command = $cc $ldflags -o $out $in $libs
23+ description = link $out
24+
25+rule install
26+ command = mkdir -p $bindir && cp nviz $bindir/
27+ description = install nviz
28+
29+build env.o: cc env.c
30+build graph.o: cc graph.c
31+build htab.o: cc htab.c
32+build log.o: cc log.c
33+build nviz.o: cc nviz.c
34+build os-posix.o: cc os-posix.c
35+build parse.o: cc parse.c
36+build scan.o: cc scan.c
37+build tree.o: cc tree.c
38+build util.o: cc util.c
39+
40+build nviz: link env.o graph.o htab.o log.o nviz.o os-posix.o parse.o scan.o tree.o util.o
41+build all: phony nviz
42+build install: install nviz
43+
44+default all
+0,
-0
+289,
-0
1@@ -0,0 +1,289 @@
2+#include <stdbool.h>
3+#include <stdlib.h>
4+#include <string.h>
5+#include "env.h"
6+#include "graph.h"
7+#include "tree.h"
8+#include "util.h"
9+
10+struct environment {
11+ struct environment *parent;
12+ struct treenode *bindings;
13+ struct treenode *rules;
14+ struct environment *allnext;
15+};
16+
17+struct environment *rootenv;
18+struct rule phonyrule = {.name = "phony"};
19+struct pool consolepool = {.name = "console", .maxjobs = 1};
20+static struct treenode *pools;
21+static struct environment *allenvs;
22+
23+static void addpool(struct pool *);
24+static void delpool(void *);
25+static void delrule(void *);
26+
27+void
28+envinit(void)
29+{
30+ struct environment *env;
31+
32+ /* free old environments and pools in case we rebuilt the manifest */
33+ while (allenvs) {
34+ env = allenvs;
35+ allenvs = env->allnext;
36+ deltree(env->bindings, free, free);
37+ deltree(env->rules, NULL, delrule);
38+ free(env);
39+ }
40+ deltree(pools, NULL, delpool);
41+
42+ rootenv = mkenv(NULL);
43+ envaddrule(rootenv, &phonyrule);
44+ pools = NULL;
45+ addpool(&consolepool);
46+}
47+
48+static void
49+addvar(struct treenode **tree, char *var, void *val)
50+{
51+ char *old;
52+
53+ old = treeinsert(tree, var, val);
54+ if (old)
55+ free(old);
56+}
57+
58+struct environment *
59+mkenv(struct environment *parent)
60+{
61+ struct environment *env;
62+
63+ env = xmalloc(sizeof(*env));
64+ env->parent = parent;
65+ env->bindings = NULL;
66+ env->rules = NULL;
67+ env->allnext = allenvs;
68+ allenvs = env;
69+
70+ return env;
71+}
72+
73+struct string *
74+envvar(struct environment *env, char *var)
75+{
76+ struct treenode *n;
77+
78+ do {
79+ n = treefind(env->bindings, var);
80+ if (n)
81+ return n->value;
82+ env = env->parent;
83+ } while (env);
84+
85+ return NULL;
86+}
87+
88+void
89+envaddvar(struct environment *env, char *var, struct string *val)
90+{
91+ addvar(&env->bindings, var, val);
92+}
93+
94+static struct string *
95+merge(struct evalstring *str, size_t n)
96+{
97+ struct string *result;
98+ struct evalstring *p;
99+ char *s;
100+
101+ result = mkstr(n);
102+ s = result->s;
103+ for (p = str; p; p = p->next) {
104+ if (!p->str)
105+ continue;
106+ memcpy(s, p->str->s, p->str->n);
107+ s += p->str->n;
108+ }
109+ *s = '\0';
110+
111+ return result;
112+}
113+
114+struct string *
115+enveval(struct environment *env, struct evalstring *str)
116+{
117+ size_t n;
118+ struct evalstring *p;
119+ struct string *res;
120+
121+ n = 0;
122+ for (p = str; p; p = p->next) {
123+ if (p->var)
124+ p->str = envvar(env, p->var);
125+ if (p->str)
126+ n += p->str->n;
127+ }
128+ res = merge(str, n);
129+ delevalstr(str);
130+
131+ return res;
132+}
133+
134+void
135+envaddrule(struct environment *env, struct rule *r)
136+{
137+ if (treeinsert(&env->rules, r->name, r))
138+ fatal("rule '%s' redefined", r->name);
139+}
140+
141+struct rule *
142+envrule(struct environment *env, char *name)
143+{
144+ struct treenode *n;
145+
146+ do {
147+ n = treefind(env->rules, name);
148+ if (n)
149+ return n->value;
150+ env = env->parent;
151+ } while (env);
152+
153+ return NULL;
154+}
155+
156+static struct string *
157+pathlist(struct node **nodes, size_t n, char sep, bool escape)
158+{
159+ size_t i, len;
160+ struct string *path, *result;
161+ char *s;
162+
163+ if (n == 0)
164+ return NULL;
165+ if (n == 1)
166+ return nodepath(nodes[0], escape);
167+ for (i = 0, len = 0; i < n; ++i)
168+ len += nodepath(nodes[i], escape)->n;
169+ result = mkstr(len + n - 1);
170+ s = result->s;
171+ for (i = 0; i < n; ++i) {
172+ path = nodepath(nodes[i], escape);
173+ memcpy(s, path->s, path->n);
174+ s += path->n;
175+ *s++ = sep;
176+ }
177+ *--s = '\0';
178+
179+ return result;
180+}
181+
182+struct rule *
183+mkrule(char *name)
184+{
185+ struct rule *r;
186+
187+ r = xmalloc(sizeof(*r));
188+ r->name = name;
189+ r->bindings = NULL;
190+
191+ return r;
192+}
193+
194+static void
195+delrule(void *ptr)
196+{
197+ struct rule *r = ptr;
198+
199+ if (r == &phonyrule)
200+ return;
201+ deltree(r->bindings, free, delevalstr);
202+ free(r->name);
203+ free(r);
204+}
205+
206+void
207+ruleaddvar(struct rule *r, char *var, struct evalstring *val)
208+{
209+ addvar(&r->bindings, var, val);
210+}
211+
212+struct string *
213+edgevar(struct edge *e, char *var, bool escape)
214+{
215+ static void *const cycle = (void *)&cycle;
216+ struct evalstring *str, *p;
217+ struct treenode *n;
218+ size_t len;
219+
220+ if (strcmp(var, "in") == 0)
221+ return pathlist(e->in, e->inimpidx, ' ', escape);
222+ if (strcmp(var, "in_newline") == 0)
223+ return pathlist(e->in, e->inimpidx, '\n', escape);
224+ if (strcmp(var, "out") == 0)
225+ return pathlist(e->out, e->outimpidx, ' ', escape);
226+ n = treefind(e->env->bindings, var);
227+ if (n)
228+ return n->value;
229+ n = treefind(e->rule->bindings, var);
230+ if (!n)
231+ return envvar(e->env->parent, var);
232+ if (n->value == cycle)
233+ fatal("cycle in rule variable involving '%s'", var);
234+ str = n->value;
235+ n->value = cycle;
236+ len = 0;
237+ for (p = str; p; p = p->next) {
238+ if (p->var)
239+ p->str = edgevar(e, p->var, escape);
240+ if (p->str)
241+ len += p->str->n;
242+ }
243+ n->value = str;
244+ return merge(str, len);
245+}
246+
247+static void
248+addpool(struct pool *p)
249+{
250+ if (treeinsert(&pools, p->name, p))
251+ fatal("pool '%s' redefined", p->name);
252+}
253+
254+struct pool *
255+mkpool(char *name)
256+{
257+ struct pool *p;
258+
259+ p = xmalloc(sizeof(*p));
260+ p->name = name;
261+ p->numjobs = 0;
262+ p->maxjobs = 0;
263+ p->work = NULL;
264+ addpool(p);
265+
266+ return p;
267+}
268+
269+static void
270+delpool(void *ptr)
271+{
272+ struct pool *p = ptr;
273+
274+ if (p == &consolepool)
275+ return;
276+ free(p->name);
277+ free(p);
278+}
279+
280+struct pool *
281+poolget(char *name)
282+{
283+ struct treenode *n;
284+
285+ n = treefind(pools, name);
286+ if (!n)
287+ fatal("unknown pool '%s'", name);
288+
289+ return n->value;
290+}
+47,
-0
1@@ -0,0 +1,47 @@
2+struct evalstring;
3+struct string;
4+
5+struct rule {
6+ char *name;
7+ struct treenode *bindings;
8+};
9+
10+struct pool {
11+ char *name;
12+ int numjobs, maxjobs;
13+
14+ /* a queue of ready edges blocked by the pool's capacity */
15+ struct edge *work;
16+};
17+
18+void envinit(void);
19+
20+/* create a new environment with an optional parent */
21+struct environment *mkenv(struct environment *);
22+/* search environment and its parents for a variable, returning the value or NULL if not found */
23+struct string *envvar(struct environment *, char *);
24+/* add to environment a variable and its value, replacing the old value if there is one */
25+void envaddvar(struct environment *, char *, struct string *);
26+/* evaluate an unevaluated string within an environment, returning the result */
27+struct string *enveval(struct environment *, struct evalstring *);
28+/* search an environment and its parents for a rule, returning the rule or NULL if not found */
29+struct rule *envrule(struct environment *, char *);
30+/* add a rule to an environment, or fail if the rule already exists */
31+void envaddrule(struct environment *, struct rule *);
32+
33+/* create a new rule with the given name */
34+struct rule *mkrule(char *);
35+/* add to rule a variable and its value */
36+void ruleaddvar(struct rule *, char *, struct evalstring *);
37+
38+/* create a new pool with the given name */
39+struct pool *mkpool(char *);
40+/* lookup a pool by name, or fail if it does not exist */
41+struct pool *poolget(char *);
42+
43+/* evaluate and return an edge's variable, optionally shell-escaped */
44+struct string *edgevar(struct edge *, char *, _Bool);
45+
46+extern struct environment *rootenv;
47+extern struct rule phonyrule;
48+extern struct pool consolepool;
+215,
-0
1@@ -0,0 +1,215 @@
2+#include <ctype.h>
3+#include <stdbool.h>
4+#include <stdlib.h>
5+#include <string.h>
6+#include "env.h"
7+#include "graph.h"
8+#include "htab.h"
9+#include "os.h"
10+#include "util.h"
11+
12+static struct hashtable *allnodes;
13+struct edge *alledges;
14+
15+static void
16+delnode(void *p)
17+{
18+ struct node *n = p;
19+
20+ if (n->shellpath != n->path)
21+ free(n->shellpath);
22+ free(n->use);
23+ free(n->path);
24+ free(n);
25+}
26+
27+void
28+graphinit(void)
29+{
30+ struct edge *e;
31+
32+ /* delete old nodes and edges in case we rebuilt the manifest */
33+ delhtab(allnodes, delnode);
34+ while (alledges) {
35+ e = alledges;
36+ alledges = e->allnext;
37+ free(e->out);
38+ free(e->in);
39+ free(e);
40+ }
41+ allnodes = mkhtab(1024);
42+}
43+
44+struct node *
45+mknode(struct string *path)
46+{
47+ void **v;
48+ struct node *n;
49+ struct hashtablekey k;
50+
51+ htabkey(&k, path->s, path->n);
52+ v = htabput(allnodes, &k);
53+ if (*v) {
54+ free(path);
55+ return *v;
56+ }
57+ n = xmalloc(sizeof(*n));
58+ n->path = path;
59+ n->shellpath = NULL;
60+ n->gen = NULL;
61+ n->use = NULL;
62+ n->nuse = 0;
63+ n->mtime = MTIME_UNKNOWN;
64+ n->logmtime = MTIME_MISSING;
65+ n->hash = 0;
66+ n->id = -1;
67+ *v = n;
68+
69+ return n;
70+}
71+
72+struct node *
73+nodeget(const char *path, size_t len)
74+{
75+ struct hashtablekey k;
76+
77+ if (!len)
78+ len = strlen(path);
79+ htabkey(&k, path, len);
80+ return htabget(allnodes, &k);
81+}
82+
83+void
84+nodestat(struct node *n)
85+{
86+ n->mtime = osmtime(n->path->s);
87+}
88+
89+struct string *
90+nodepath(struct node *n, bool escape)
91+{
92+ char *s, *d;
93+ int nquote;
94+
95+ if (!escape)
96+ return n->path;
97+ if (n->shellpath)
98+ return n->shellpath;
99+ escape = false;
100+ nquote = 0;
101+ for (s = n->path->s; *s; ++s) {
102+ if (!isalnum(*(unsigned char *)s) && !strchr("_+-./", *s))
103+ escape = true;
104+ if (*s == '\'')
105+ ++nquote;
106+ }
107+ if (escape) {
108+ n->shellpath = mkstr(n->path->n + 2 + 3 * nquote);
109+ d = n->shellpath->s;
110+ *d++ = '\'';
111+ for (s = n->path->s; *s; ++s) {
112+ *d++ = *s;
113+ if (*s == '\'') {
114+ *d++ = '\\';
115+ *d++ = '\'';
116+ *d++ = '\'';
117+ }
118+ }
119+ *d++ = '\'';
120+ } else {
121+ n->shellpath = n->path;
122+ }
123+ return n->shellpath;
124+}
125+
126+void
127+nodeuse(struct node *n, struct edge *e)
128+{
129+ /* allocate in powers of two */
130+ if (!(n->nuse & (n->nuse - 1)))
131+ n->use = xreallocarray(n->use, n->nuse ? n->nuse * 2 : 1, sizeof(e));
132+ n->use[n->nuse++] = e;
133+}
134+
135+struct edge *
136+mkedge(struct environment *parent)
137+{
138+ struct edge *e;
139+
140+ e = xmalloc(sizeof(*e));
141+ e->env = mkenv(parent);
142+ e->pool = NULL;
143+ e->out = NULL;
144+ e->nout = 0;
145+ e->in = NULL;
146+ e->nin = 0;
147+ e->flags = 0;
148+ e->allnext = alledges;
149+ alledges = e;
150+
151+ return e;
152+}
153+
154+void
155+edgehash(struct edge *e)
156+{
157+ static const char sep[] = ";rspfile=";
158+ struct string *cmd, *rsp, *s;
159+
160+ if (e->flags & FLAG_HASH)
161+ return;
162+ e->flags |= FLAG_HASH;
163+ cmd = edgevar(e, "command", true);
164+ if (!cmd)
165+ fatal("rule '%s' has no command", e->rule->name);
166+ rsp = edgevar(e, "rspfile_content", true);
167+ if (rsp && rsp->n > 0) {
168+ s = mkstr(cmd->n + sizeof(sep) - 1 + rsp->n);
169+ memcpy(s->s, cmd->s, cmd->n);
170+ memcpy(s->s + cmd->n, sep, sizeof(sep) - 1);
171+ memcpy(s->s + cmd->n + sizeof(sep) - 1, rsp->s, rsp->n);
172+ s->s[s->n] = '\0';
173+ e->hash = rapidhashv1(s->s, s->n);
174+ free(s);
175+ } else {
176+ e->hash = rapidhashv1(cmd->s, cmd->n);
177+ }
178+}
179+
180+static struct edge *
181+mkphony(struct node *n)
182+{
183+ struct edge *e;
184+
185+ e = mkedge(rootenv);
186+ e->rule = &phonyrule;
187+ e->inimpidx = 0;
188+ e->inorderidx = 0;
189+ e->outimpidx = 1;
190+ e->nout = 1;
191+ e->out = xmalloc(sizeof(n));
192+ e->out[0] = n;
193+
194+ return e;
195+}
196+
197+void
198+edgeadddeps(struct edge *e, struct node **deps, size_t ndeps)
199+{
200+ struct node **order, *n;
201+ size_t norder, i;
202+
203+ for (i = 0; i < ndeps; ++i) {
204+ n = deps[i];
205+ if (!n->gen)
206+ n->gen = mkphony(n);
207+ nodeuse(n, e);
208+ }
209+ e->in = xreallocarray(e->in, e->nin + ndeps, sizeof(e->in[0]));
210+ order = e->in + e->inorderidx;
211+ norder = e->nin - e->inorderidx;
212+ memmove(order + ndeps, order, norder * sizeof(e->in[0]));
213+ memcpy(order, deps, ndeps * sizeof(e->in[0]));
214+ e->inorderidx += ndeps;
215+ e->nin += ndeps;
216+}
+92,
-0
1@@ -0,0 +1,92 @@
2+#include <stdint.h> /* for uint64_t */
3+
4+/* set in the tv_nsec field of a node's mtime */
5+enum {
6+ /* we haven't stat the file yet */
7+ MTIME_UNKNOWN = -1,
8+ /* the file does not exist */
9+ MTIME_MISSING = -2,
10+};
11+
12+struct node {
13+ /* shellpath is the escaped shell path, and is populated as needed by nodepath */
14+ struct string *path, *shellpath;
15+
16+ /* modification time of file (in nanoseconds) and build log entry (in seconds) */
17+ int64_t mtime, logmtime;
18+
19+ /* generating edge and dependent edges */
20+ struct edge *gen, **use;
21+ size_t nuse;
22+
23+ /* command hash used to build this output, read from build log */
24+ uint64_t hash;
25+
26+ /* ID for .ninja_deps. -1 if not present in log. */
27+ int32_t id;
28+
29+ /* does the node need to be rebuilt */
30+ _Bool dirty;
31+};
32+
33+/* build rule, i.e., edge between inputs and outputs */
34+struct edge {
35+ struct rule *rule;
36+ struct pool *pool;
37+ struct environment *env;
38+
39+ /* input and output nodes */
40+ struct node **out, **in;
41+ size_t nout, nin;
42+
43+ /* index of first implicit output */
44+ size_t outimpidx;
45+ /* index of first implicit and order-only input */
46+ size_t inimpidx, inorderidx;
47+
48+ /* command hash */
49+ uint64_t hash;
50+
51+ /* how many inputs need to be rebuilt or pruned before this edge is ready */
52+ size_t nblock;
53+ /* how many inputs need to be pruned before all outputs can be pruned */
54+ size_t nprune;
55+
56+ enum {
57+ FLAG_WORK = 1 << 0, /* scheduled for build */
58+ FLAG_HASH = 1 << 1, /* calculated the command hash */
59+ FLAG_DIRTY_IN = 1 << 3, /* dirty input */
60+ FLAG_DIRTY_OUT = 1 << 4, /* missing or outdated output */
61+ FLAG_DIRTY = FLAG_DIRTY_IN | FLAG_DIRTY_OUT,
62+ FLAG_CYCLE = 1 << 5, /* used for cycle detection */
63+ FLAG_DEPS = 1 << 6, /* dependencies loaded */
64+ } flags;
65+
66+ /* used to coordinate ready work in build() */
67+ struct edge *worknext;
68+ /* used for alledges linked list */
69+ struct edge *allnext;
70+};
71+
72+void graphinit(void);
73+
74+/* create a new node or return existing node */
75+struct node *mknode(struct string *);
76+/* lookup a node by name; returns NULL if it does not exist */
77+struct node *nodeget(const char *, size_t);
78+/* update the mtime field of a node */
79+void nodestat(struct node *);
80+/* get a node's path, possibly escaped for the shell */
81+struct string *nodepath(struct node *, _Bool);
82+/* record the usage of a node by an edge */
83+void nodeuse(struct node *, struct edge *);
84+
85+/* create a new edge with the given parent environment */
86+struct edge *mkedge(struct environment *parent);
87+/* compute the rapidhashv1 of an edge command and store it in the hash field */
88+void edgehash(struct edge *);
89+/* add dependencies from $depfile or .ninja_deps as implicit inputs */
90+void edgeadddeps(struct edge *e, struct node **deps, size_t ndeps);
91+
92+/* a single linked list of all edges, valid up until build() */
93+extern struct edge *alledges;
+245,
-0
1@@ -0,0 +1,245 @@
2+#include <assert.h>
3+#include <limits.h>
4+#include <stdbool.h>
5+#include <stdint.h>
6+#include <stdlib.h>
7+#include <string.h>
8+#include "util.h"
9+#include "htab.h"
10+
11+struct hashtable {
12+ size_t len, cap;
13+ struct hashtablekey *keys;
14+ void **vals;
15+};
16+
17+void
18+htabkey(struct hashtablekey *k, const char *s, size_t n)
19+{
20+ k->str = s;
21+ k->len = n;
22+ k->hash = rapidhashv1(s, n);
23+}
24+
25+struct hashtable *
26+mkhtab(size_t cap)
27+{
28+ struct hashtable *h;
29+ size_t i;
30+
31+ assert(!(cap & (cap - 1)));
32+ h = xmalloc(sizeof(*h));
33+ h->len = 0;
34+ h->cap = cap;
35+ h->keys = xreallocarray(NULL, cap, sizeof(h->keys[0]));
36+ h->vals = xreallocarray(NULL, cap, sizeof(h->vals[0]));
37+ for (i = 0; i < cap; ++i)
38+ h->keys[i].str = NULL;
39+
40+ return h;
41+}
42+
43+void
44+delhtab(struct hashtable *h, void del(void *))
45+{
46+ size_t i;
47+
48+ if (!h)
49+ return;
50+ if (del) {
51+ for (i = 0; i < h->cap; ++i) {
52+ if (h->keys[i].str)
53+ del(h->vals[i]);
54+ }
55+ }
56+ free(h->keys);
57+ free(h->vals);
58+ free(h);
59+}
60+
61+static bool
62+keyequal(struct hashtablekey *k1, struct hashtablekey *k2)
63+{
64+ if (k1->hash != k2->hash || k1->len != k2->len)
65+ return false;
66+ return memcmp(k1->str, k2->str, k1->len) == 0;
67+}
68+
69+static size_t
70+keyindex(struct hashtable *h, struct hashtablekey *k)
71+{
72+ size_t i;
73+
74+ i = k->hash & (h->cap - 1);
75+ while (h->keys[i].str && !keyequal(&h->keys[i], k))
76+ i = (i + 1) & (h->cap - 1);
77+ return i;
78+}
79+
80+void **
81+htabput(struct hashtable *h, struct hashtablekey *k)
82+{
83+ struct hashtablekey *oldkeys;
84+ void **oldvals;
85+ size_t i, j, oldcap;
86+
87+ if (h->cap / 2 < h->len) {
88+ oldkeys = h->keys;
89+ oldvals = h->vals;
90+ oldcap = h->cap;
91+ h->cap *= 2;
92+ h->keys = xreallocarray(NULL, h->cap, sizeof(h->keys[0]));
93+ h->vals = xreallocarray(NULL, h->cap, sizeof(h->vals[0]));
94+ for (i = 0; i < h->cap; ++i)
95+ h->keys[i].str = NULL;
96+ for (i = 0; i < oldcap; ++i) {
97+ if (oldkeys[i].str) {
98+ j = keyindex(h, &oldkeys[i]);
99+ h->keys[j] = oldkeys[i];
100+ h->vals[j] = oldvals[i];
101+ }
102+ }
103+ free(oldkeys);
104+ free(oldvals);
105+ }
106+ i = keyindex(h, k);
107+ if (!h->keys[i].str) {
108+ h->keys[i] = *k;
109+ h->vals[i] = NULL;
110+ ++h->len;
111+ }
112+
113+ return &h->vals[i];
114+}
115+
116+void *
117+htabget(struct hashtable *h, struct hashtablekey *k)
118+{
119+ size_t i;
120+
121+ i = keyindex(h, k);
122+ return h->keys[i].str ? h->vals[i] : NULL;
123+}
124+
125+static inline uint_least32_t
126+getle32(const void *p)
127+{
128+ const unsigned char *b = p;
129+ uint_least32_t v;
130+
131+ v = b[0] & 0xfful;
132+ v |= (b[1] & 0xfful) << 8;
133+ v |= (b[2] & 0xfful) << 16;
134+ v |= (b[3] & 0xfful) << 24;
135+ return v;
136+}
137+
138+static inline uint_least64_t
139+getle64(const void *p)
140+{
141+ const unsigned char *b = p;
142+ uint_least64_t v;
143+
144+ v = b[0] & 0xffull;
145+ v |= (b[1] & 0xffull) << 8;
146+ v |= (b[2] & 0xffull) << 16;
147+ v |= (b[3] & 0xffull) << 24;
148+ v |= (b[4] & 0xffull) << 32;
149+ v |= (b[5] & 0xffull) << 40;
150+ v |= (b[6] & 0xffull) << 48;
151+ v |= (b[7] & 0xffull) << 56;
152+ return v;
153+}
154+
155+#if __STDC_VERSION__ >= 202311L && BITINT_MAXWIDTH >= 128
156+#define uint128 unsigned _BitInt(128)
157+#elif __SIZEOF_INT128__
158+#define uint128 __uint128_t
159+#endif
160+
161+static inline void
162+mum(uint64_t *a, uint64_t *b)
163+{
164+#ifdef uint128
165+ uint128 r;
166+
167+ r = *a;
168+ r *= *b;
169+ *a = r;
170+ *b = r >> 64;
171+#else
172+ uint64_t al, ah, bl, bh, rl, rh;
173+ uint64_t ll, lh, hl, hh, m;
174+
175+ al = (uint32_t)*a;
176+ bl = (uint32_t)*b;
177+ ah = *a >> 32;
178+ bh = *b >> 32;
179+ ll = al * bl;
180+ lh = al * bh;
181+ hl = ah * bl;
182+ hh = ah * bh;
183+
184+ m = (ll >> 32) + lh + (hl & 0xffffffff);
185+ rl = ll & 0xffffffff | m << 32;
186+ rh = hh + (m >> 32) + (hl >> 32);
187+ *a = rl;
188+ *b = rh;
189+#endif
190+}
191+
192+static inline uint64_t
193+mix(uint64_t a, uint64_t b)
194+{
195+ mum(&a, &b);
196+ return a ^ b;
197+}
198+
199+uint64_t
200+rapidhashv1(const void *ptr, size_t len)
201+{
202+ static const uint64_t secret[] = {
203+ 0x2d358dccaa6c78a5ull,
204+ 0x8bb84b93962eacc9ull,
205+ 0x4b33a62ed433d4a3ull,
206+ };
207+ uint64_t seed[3];
208+ const unsigned char *pos, *end;
209+ int i;
210+
211+ pos = ptr;
212+ end = pos + len;
213+ seed[0] = 0xbdd89aa982704029ull;
214+ seed[0] ^= mix(seed[0] ^ secret[0], secret[1]) ^ len;
215+ if (len == 0) {
216+ seed[1] = seed[2] = 0;
217+ } else if (len < 4) {
218+ seed[1] = (uint64_t)pos[0] << 56 | (uint64_t)pos[len > 1] << 32 | end[-1];
219+ seed[2] = 0;
220+ } else if (len <= 16) {
221+ seed[1] = (uint64_t)getle32(pos) << 32 | getle32(end - 4);
222+ if (len >= 8)
223+ pos += 4, end -= 4;
224+ seed[2] = (uint64_t)getle32(pos) << 32 | getle32(end - 4);
225+ } else {
226+ seed[1] = seed[2] = seed[0];
227+ if (len > 48) {
228+ do {
229+ for (i = 0; i < 3; ++i, pos += 16)
230+ seed[i] = mix(getle64(pos) ^ secret[i], getle64(pos + 8) ^ seed[i]);
231+ } while (end - pos >= 48);
232+ seed[0] ^= seed[1] ^ seed[2];
233+ }
234+ if (end - pos > 16) {
235+ seed[0] ^= secret[1];
236+ do seed[0] = mix(getle64(pos) ^ secret[2], getle64(pos + 8) ^ seed[0]);
237+ while (pos += 16, end - pos > 16);
238+ }
239+ seed[1] = getle64(end - 16);
240+ seed[2] = getle64(end - 8);
241+ }
242+ seed[1] ^= secret[1];
243+ seed[2] ^= seed[0];
244+ mum(&seed[1], &seed[2]);
245+ return mix(seed[1] ^ secret[0] ^ len, seed[2] ^ secret[1]);
246+}
+16,
-0
1@@ -0,0 +1,16 @@
2+#include <stdint.h> /* for uint64_t */
3+
4+struct hashtablekey {
5+ uint64_t hash;
6+ const char *str;
7+ size_t len;
8+};
9+
10+void htabkey(struct hashtablekey *, const char *, size_t);
11+
12+struct hashtable *mkhtab(size_t);
13+void delhtab(struct hashtable *, void(void *));
14+void **htabput(struct hashtable *, struct hashtablekey *);
15+void *htabget(struct hashtable *, struct hashtablekey *);
16+
17+uint64_t rapidhashv1(const void *, size_t);
+162,
-0
1@@ -0,0 +1,162 @@
2+#include <errno.h>
3+#include <inttypes.h>
4+#include <stdio.h>
5+#include <stdlib.h>
6+#include <string.h>
7+#include "graph.h"
8+#include "log.h"
9+#include "util.h"
10+
11+static FILE *logfile;
12+static const char *logname = ".ninja_log";
13+static const char *logtmpname = ".ninja_log.tmp";
14+static const char *logfmt = "# ninja log v%d\n";
15+static const int logver = 7;
16+
17+static char *
18+nextfield(char **end)
19+{
20+ char *s = *end;
21+
22+ if (!*s) {
23+ warn("corrupt build log: missing field");
24+ return NULL;
25+ }
26+ *end += strcspn(*end, "\t\n");
27+ if (**end)
28+ *(*end)++ = '\0';
29+
30+ return s;
31+}
32+
33+void
34+loginit(const char *builddir)
35+{
36+ int ver;
37+ char *logpath = (char *)logname, *logtmppath = (char *)logtmpname, *p, *s;
38+ size_t nline, nentry, i;
39+ struct edge *e;
40+ struct node *n;
41+ int64_t mtime;
42+ struct buffer buf = {0};
43+
44+ nline = 0;
45+ nentry = 0;
46+
47+ if (logfile)
48+ fclose(logfile);
49+ if (builddir)
50+ xasprintf(&logpath, "%s/%s", builddir, logname);
51+ logfile = fopen(logpath, "r+");
52+ if (!logfile) {
53+ if (errno != ENOENT)
54+ fatal("open %s:", logpath);
55+ goto rewrite;
56+ }
57+ setvbuf(logfile, NULL, _IOLBF, 0);
58+ if (fscanf(logfile, logfmt, &ver) < 1)
59+ goto rewrite;
60+ if (ver != logver)
61+ goto rewrite;
62+
63+ for (;;) {
64+ if (buf.cap - buf.len < BUFSIZ) {
65+ buf.cap = buf.cap ? buf.cap * 2 : BUFSIZ;
66+ buf.data = xreallocarray(buf.data, buf.cap, 1);
67+ }
68+ buf.data[buf.cap - 2] = '\0';
69+ if (!fgets(buf.data + buf.len, buf.cap - buf.len, logfile))
70+ break;
71+ if (buf.data[buf.cap - 2] && buf.data[buf.cap - 2] != '\n') {
72+ buf.len = buf.cap - 1;
73+ continue;
74+ }
75+ ++nline;
76+ p = buf.data;
77+ buf.len = 0;
78+ if (!nextfield(&p)) /* start time */
79+ continue;
80+ if (!nextfield(&p)) /* end time */
81+ continue;
82+ s = nextfield(&p); /* mtime (used for restat) */
83+ if (!s)
84+ continue;
85+ mtime = strtoll(s, &s, 10);
86+ if (*s) {
87+ warn("corrupt build log: invalid mtime");
88+ continue;
89+ }
90+ s = nextfield(&p); /* output path */
91+ if (!s)
92+ continue;
93+ n = nodeget(s, 0);
94+ if (!n || !n->gen)
95+ continue;
96+ if (n->logmtime == MTIME_MISSING)
97+ ++nentry;
98+ n->logmtime = mtime;
99+ s = nextfield(&p); /* command hash */
100+ if (!s)
101+ continue;
102+ n->hash = strtoull(s, &s, 16);
103+ if (*s) {
104+ warn("corrupt build log: invalid hash for '%s'", n->path->s);
105+ continue;
106+ }
107+ }
108+ free(buf.data);
109+ if (ferror(logfile)) {
110+ warn("build log read:");
111+ goto rewrite;
112+ }
113+ if (nline <= 100 || nline <= 3 * nentry) {
114+ if (builddir)
115+ free(logpath);
116+ return;
117+ }
118+
119+rewrite:
120+ if (logfile)
121+ fclose(logfile);
122+ if (builddir)
123+ xasprintf(&logtmppath, "%s/%s", builddir, logtmpname);
124+ logfile = fopen(logtmppath, "w");
125+ if (!logfile)
126+ fatal("open %s:", logtmppath);
127+ setvbuf(logfile, NULL, _IOLBF, 0);
128+ fprintf(logfile, logfmt, logver);
129+ if (nentry > 0) {
130+ for (e = alledges; e; e = e->allnext) {
131+ for (i = 0; i < e->nout; ++i) {
132+ n = e->out[i];
133+ if (!n->hash)
134+ continue;
135+ logrecord(n);
136+ }
137+ }
138+ }
139+ fflush(logfile);
140+ if (ferror(logfile))
141+ fatal("build log write failed");
142+ if (rename(logtmppath, logpath) < 0)
143+ fatal("build log rename:");
144+ if (builddir) {
145+ free(logpath);
146+ free(logtmppath);
147+ }
148+}
149+
150+void
151+logclose(void)
152+{
153+ fflush(logfile);
154+ if (ferror(logfile))
155+ fatal("build log write failed");
156+ fclose(logfile);
157+}
158+
159+void
160+logrecord(struct node *n)
161+{
162+ fprintf(logfile, "0\t0\t%" PRId64 "\t%s\t%" PRIx64 "\n", n->logmtime, n->path->s, n->hash);
163+}
+5,
-0
1@@ -0,0 +1,5 @@
2+struct node;
3+
4+void loginit(const char *);
5+void logclose(void);
6+void logrecord(struct node *);
+314,
-0
1@@ -0,0 +1,314 @@
2+#include <stdbool.h>
3+#include <stdio.h>
4+#include <stdlib.h>
5+#include <string.h>
6+#include "arg.h"
7+#include "env.h"
8+#include "graph.h"
9+#include "os.h"
10+#include "parse.h"
11+#include "util.h"
12+
13+const char *argv0;
14+
15+static bool graphviz;
16+static bool ascii;
17+static bool firstroot = true;
18+static struct node **seen;
19+static size_t nseen, capseen;
20+static struct node **dotnodes;
21+static size_t ndotnodes, capdotnodes;
22+static struct node **dotseen_nodes;
23+static size_t ndotseen_nodes, capdotseen_nodes;
24+
25+static const char *
26+progname(const char *arg, const char *def)
27+{
28+ const char *slash;
29+
30+ if (!arg)
31+ return def;
32+ slash = strrchr(arg, '/');
33+ return slash ? slash + 1 : arg;
34+}
35+
36+static void
37+usage(void)
38+{
39+ fprintf(stderr, "usage: %s [-a] [-g] [-C dir] [-f buildfile] [target ...]\n", argv0);
40+ exit(2);
41+}
42+
43+static bool
44+onstack(struct node *n, struct node **stack, size_t nstack)
45+{
46+ size_t i;
47+
48+ for (i = 0; i < nstack; ++i) {
49+ if (stack[i] == n)
50+ return true;
51+ }
52+ return false;
53+}
54+
55+static bool
56+seenbefore(struct node *n)
57+{
58+ size_t i;
59+
60+ for (i = 0; i < nseen; ++i) {
61+ if (seen[i] == n)
62+ return true;
63+ }
64+ return false;
65+}
66+
67+static void
68+markseen(struct node *n)
69+{
70+ if (seenbefore(n))
71+ return;
72+ if (nseen == capseen) {
73+ capseen = capseen ? capseen * 2 : 128;
74+ seen = xreallocarray(seen, capseen, sizeof(seen[0]));
75+ }
76+ seen[nseen++] = n;
77+}
78+
79+static void
80+emitstr(const char *s)
81+{
82+ for (; *s; ++s) {
83+ if (*s == '"' || *s == '\\')
84+ putchar('\\');
85+ putchar(*s);
86+ }
87+}
88+
89+static bool
90+isroot(struct node *n)
91+{
92+ return n->nuse == 0;
93+}
94+
95+//dedup so we dont get two arrows
96+static bool
97+dotseen(struct node *n)
98+{
99+ size_t i;
100+
101+ for (i = 0; i < ndotnodes; ++i) {
102+ if (dotnodes[i] == n)
103+ return true;
104+ }
105+ return false;
106+}
107+
108+static bool
109+dotwalkseen(struct node *n)
110+{
111+ size_t i;
112+
113+ for (i = 0; i < ndotseen_nodes; ++i) {
114+ if (dotseen_nodes[i] == n)
115+ return true;
116+ }
117+ return false;
118+}
119+
120+static void
121+markdotwalk(struct node *n)
122+{
123+ if (dotwalkseen(n))
124+ return;
125+ if (ndotseen_nodes == capdotseen_nodes) {
126+ capdotseen_nodes = capdotseen_nodes ? capdotseen_nodes * 2 : 128;
127+ dotseen_nodes = xreallocarray(dotseen_nodes, capdotseen_nodes, sizeof(dotseen_nodes[0]));
128+ }
129+ dotseen_nodes[ndotseen_nodes++] = n;
130+}
131+
132+static size_t
133+dotid(struct node *n)
134+{
135+ size_t i;
136+
137+ for (i = 0; i < ndotnodes; ++i) {
138+ if (dotnodes[i] == n)
139+ return i;
140+ }
141+ if (ndotnodes == capdotnodes) {
142+ capdotnodes = capdotnodes ? capdotnodes * 2 : 128;
143+ dotnodes = xreallocarray(dotnodes, capdotnodes, sizeof(dotnodes[0]));
144+ }
145+ dotnodes[ndotnodes] = n;
146+ return ndotnodes++;
147+}
148+
149+static void
150+emitdotnode(struct node *n)
151+{
152+ size_t id;
153+
154+ if (dotseen(n))
155+ return;
156+ id = dotid(n);
157+ printf(" n%zu [label=\"", id);
158+ emitstr(n->path->s);
159+ printf("\"");
160+ printf(", shape=box");
161+ if (n->gen && n->gen->rule == &phonyrule) {
162+ printf(", style=dashed");
163+ } else if (isroot(n)) {
164+ printf(", style=bold");
165+ }
166+ printf("];\n");
167+}
168+
169+static void
170+printindent(size_t depth, bool *more)
171+{
172+ size_t i;
173+
174+ for (i = 0; i < depth; ++i)
175+ printf("%s", more[i] ? (ascii ? "| " : "│ ") : " ");
176+}
177+
178+static void
179+dumpdot(struct node *n, struct node **stack, size_t nstack)
180+{
181+ size_t i;
182+ size_t nid, iid;
183+
184+ if (dotwalkseen(n))
185+ return;
186+ if (onstack(n, stack, nstack))
187+ return;
188+ markdotwalk(n);
189+ emitdotnode(n);
190+ stack[nstack++] = n;
191+ if (!n->gen)
192+ return;
193+ nid = dotid(n);
194+ for (i = 0; i < n->gen->nin; ++i) {
195+ struct node *in = n->gen->in[i];
196+
197+ emitdotnode(in);
198+ iid = dotid(in);
199+ printf(" n%zu -> n%zu", iid, nid);
200+ if (i >= n->gen->inorderidx)
201+ printf(" [style=dashed]");
202+ printf(";\n");
203+ dumpdot(in, stack, nstack);
204+ }
205+}
206+
207+static void
208+dumptree(struct node *n, size_t depth, bool last, bool *more, struct node **stack, size_t nstack)
209+{
210+ size_t i;
211+
212+ if (depth == 0) {
213+ if (!firstroot)
214+ putchar('\n');
215+ firstroot = false;
216+ printf("%s\n", n->path->s);
217+ } else {
218+ printindent(depth - 1, more);
219+ printf("%s%s", last ? (ascii ? "`-- " : "└── ") : (ascii ? "|-- " : "├── "), n->path->s);
220+ if (seenbefore(n)) {
221+ printf(" [shared]\n");
222+ return;
223+ }
224+ putchar('\n');
225+ }
226+ if (onstack(n, stack, nstack))
227+ return;
228+ markseen(n);
229+ stack[nstack++] = n;
230+ if (!n->gen)
231+ return;
232+ for (i = 0; i < n->gen->nin; ++i) {
233+ more[depth] = i + 1 < n->gen->nin;
234+ dumptree(n->gen->in[i], depth + 1, i + 1 == n->gen->nin, more, stack, nstack);
235+ }
236+}
237+
238+static void
239+showroot(struct node *n)
240+{
241+ struct node *stack[512];
242+ bool more[512];
243+
244+ memset(more, 0, sizeof(more));
245+ if (graphviz)
246+ dumpdot(n, stack, 0);
247+ else
248+ dumptree(n, 0, true, more, stack, 0);
249+}
250+
251+int
252+main(int argc, char *argv[])
253+{
254+ char *manifest;
255+ struct node *n;
256+ struct edge *e;
257+ size_t i;
258+
259+ argv0 = progname(argv[0], "nviz");
260+ manifest = "build.ninja";
261+
262+ ARGBEGIN {
263+ case 'a':
264+ ascii = true;
265+ break;
266+ case 'C':
267+ oschdir(EARGF(usage()));
268+ break;
269+ case 'f':
270+ manifest = EARGF(usage());
271+ break;
272+ case 'g':
273+ graphviz = true;
274+ break;
275+ default:
276+ usage();
277+ } ARGEND
278+
279+ graphinit();
280+ envinit();
281+ parseinit();
282+ parse(manifest, rootenv);
283+
284+ if (graphviz)
285+ printf("digraph build {\n"
286+ " rankdir=\"LR\";\n"
287+ " graph [fontname=\"serif\"];\n"
288+ " node [fontsize=10, fontname=\"serif\", shape=box, height=0.25];\n"
289+ " edge [fontsize=10, fontname=\"serif\"];\n");
290+
291+ if (argc > 0) {
292+ while (*argv) {
293+ n = nodeget(*argv, 0);
294+ if (!n)
295+ fatal("unknown target '%s'", *argv);
296+ showroot(n);
297+ ++argv;
298+ }
299+ } else {
300+ for (e = alledges; e; e = e->allnext) {
301+ for (i = 0; i < e->nout; ++i) {
302+ n = e->out[i];
303+ if (n->nuse == 0)
304+ showroot(n);
305+ }
306+ }
307+ }
308+
309+ if (graphviz)
310+ printf("}\n");
311+ free(dotnodes);
312+ free(dotseen_nodes);
313+ free(seen);
314+ return 0;
315+}
+168,
-0
1@@ -0,0 +1,168 @@
2+#define _POSIX_C_SOURCE 200809L
3+#include <errno.h>
4+#include <stdbool.h>
5+#include <stdlib.h>
6+#include <stdint.h>
7+#include <fcntl.h>
8+#include <sys/stat.h>
9+#include <unistd.h>
10+#ifndef NO_POSIX_SPAWN
11+#include <spawn.h>
12+#endif
13+#include "graph.h"
14+#include "os.h"
15+#include "util.h"
16+
17+void
18+osgetcwd(char *buf, size_t len)
19+{
20+ if (!getcwd(buf, len))
21+ fatal("getcwd:");
22+}
23+
24+void
25+oschdir(const char *dir)
26+{
27+ if (chdir(dir) < 0)
28+ fatal("chdir %s:", dir);
29+}
30+
31+int
32+osmkdirs(struct string *path, bool parent)
33+{
34+ int ret;
35+ struct stat st;
36+ char *s, *end;
37+
38+ ret = 0;
39+ end = path->s + path->n;
40+ for (s = end - parent; s > path->s; --s) {
41+ if (*s != '/' && *s)
42+ continue;
43+ *s = '\0';
44+ if (stat(path->s, &st) == 0)
45+ break;
46+ if (errno != ENOENT) {
47+ warn("stat %s:", path->s);
48+ ret = -1;
49+ break;
50+ }
51+ }
52+ if (s > path->s && s < end)
53+ *s = '/';
54+ while (++s <= end - parent) {
55+ if (*s != '\0')
56+ continue;
57+ if (ret == 0 && mkdir(path->s, 0777) < 0 && errno != EEXIST) {
58+ warn("mkdir %s:", path->s);
59+ ret = -1;
60+ }
61+ if (s < end)
62+ *s = '/';
63+ }
64+
65+ return ret;
66+}
67+
68+int64_t
69+osmtime(const char *name)
70+{
71+ struct stat st;
72+
73+ if (stat(name, &st) < 0) {
74+ if (errno != ENOENT)
75+ fatal("stat %s:", name);
76+ return MTIME_MISSING;
77+ } else {
78+#ifdef __APPLE__
79+ return (int64_t)st.st_mtime * 1000000000 + st.st_mtimensec;
80+/*
81+Illumos hides the members of st_mtim when you define _POSIX_C_SOURCE
82+since it has not been updated to support POSIX.1-2008:
83+https://www.illumos.org/issues/13327
84+*/
85+#elif defined(__sun)
86+ return (int64_t)st.st_mtim.__tv_sec * 1000000000 + st.st_mtim.__tv_nsec;
87+#else
88+ return (int64_t)st.st_mtim.tv_sec * 1000000000 + st.st_mtim.tv_nsec;
89+#endif
90+ }
91+}
92+
93+long
94+osnproc(void)
95+{
96+#ifdef _SC_NPROCESSORS_ONLN
97+ return sysconf(_SC_NPROCESSORS_ONLN);
98+#else
99+ return 1;
100+#endif
101+}
102+
103+pid_t
104+osspawn(char *const argv[], int outfd)
105+{
106+#ifdef NO_POSIX_SPAWN
107+ pid_t pid;
108+ int i, fd[3];
109+
110+ pid = fork();
111+ switch (pid) {
112+ case 0:
113+ if (outfd != -1) {
114+ fd[0] = open("/dev/null", O_RDONLY | O_CLOEXEC);
115+ if (fd[0] == -1)
116+ _exit(1);
117+ fd[1] = outfd;
118+ fd[2] = outfd;
119+ for (i = 0; i <= 2; ++i) {
120+ if (dup2(fd[i], i) == -1)
121+ _exit(1);
122+ }
123+ }
124+ execvp(argv[0], argv);
125+ _exit(1);
126+ /* unreachable */
127+ return -1;
128+ default:
129+ return pid;
130+ case -1:
131+ warn("fork:");
132+ return -1;
133+ }
134+#else
135+ extern char **environ;
136+ pid_t pid;
137+ posix_spawn_file_actions_t actions;
138+
139+ if ((errno = posix_spawn_file_actions_init(&actions))) {
140+ warn("posix_spawn_file_actions_init:");
141+ goto err0;
142+ }
143+ if (outfd != -1) {
144+ if ((errno = posix_spawn_file_actions_addopen(&actions, 0, "/dev/null", O_RDONLY, 0))) {
145+ warn("posix_spawn_file_actions_adddup2:");
146+ goto err1;
147+ }
148+ if ((errno = posix_spawn_file_actions_adddup2(&actions, outfd, 1))) {
149+ warn("posix_spawn_file_actions_adddup2:");
150+ goto err1;
151+ }
152+ if ((errno = posix_spawn_file_actions_adddup2(&actions, outfd, 2))) {
153+ warn("posix_spawn_file_actions_adddup2:");
154+ goto err1;
155+ }
156+ }
157+ if ((errno = posix_spawn(&pid, argv[0], &actions, NULL, argv, environ))) {
158+ warn("posix_spawn %s:", argv[0]);
159+ goto err1;
160+ }
161+ posix_spawn_file_actions_destroy(&actions);
162+ return pid;
163+
164+err1:
165+ posix_spawn_file_actions_destroy(&actions);
166+err0:
167+ return -1;
168+#endif
169+}
+15,
-0
1@@ -0,0 +1,15 @@
2+#include <sys/types.h>
3+
4+struct string;
5+
6+void osgetcwd(char *, size_t);
7+/* changes the working directory to the given path */
8+void oschdir(const char *);
9+/* creates all the parent directories of the given path */
10+int osmkdirs(struct string *, _Bool);
11+/* queries the mtime of a file in nanoseconds since the UNIX epoch */
12+int64_t osmtime(const char *);
13+/* queries the number of online processors */
14+long osnproc(void);
15+/* spawn a child process */
16+pid_t osspawn(char *const argv[], int fd);
+280,
-0
1@@ -0,0 +1,280 @@
2+#include <stdbool.h>
3+#include <stdio.h>
4+#include <string.h>
5+#include <stdlib.h>
6+#include "env.h"
7+#include "graph.h"
8+#include "parse.h"
9+#include "scan.h"
10+#include "util.h"
11+
12+struct parseoptions parseopts;
13+static struct node **deftarg;
14+static size_t ndeftarg;
15+
16+void
17+parseinit(void)
18+{
19+ free(deftarg);
20+ deftarg = NULL;
21+ ndeftarg = 0;
22+}
23+
24+static void
25+parselet(struct scanner *s, struct evalstring **val)
26+{
27+ scanchar(s, '=');
28+ *val = scanstring(s, false);
29+ scannewline(s);
30+}
31+
32+static void
33+parserule(struct scanner *s, struct environment *env)
34+{
35+ struct rule *r;
36+ char *var;
37+ struct evalstring *val;
38+ bool hascommand = false, hasrspfile = false, hasrspcontent = false;
39+
40+ r = mkrule(scanname(s));
41+ scannewline(s);
42+ while (scanindent(s)) {
43+ var = scanname(s);
44+ parselet(s, &val);
45+ ruleaddvar(r, var, val);
46+ if (!val)
47+ continue;
48+ if (strcmp(var, "command") == 0)
49+ hascommand = true;
50+ else if (strcmp(var, "rspfile") == 0)
51+ hasrspfile = true;
52+ else if (strcmp(var, "rspfile_content") == 0)
53+ hasrspcontent = true;
54+ }
55+ if (!hascommand)
56+ fatal("rule '%s' has no command", r->name);
57+ if (hasrspfile != hasrspcontent)
58+ fatal("rule '%s' has rspfile and no rspfile_content or vice versa", r->name);
59+ envaddrule(env, r);
60+}
61+
62+static void
63+parseedge(struct scanner *s, struct environment *env)
64+{
65+ struct edge *e;
66+ struct evalstring *str, **path;
67+ char *name;
68+ struct string *val;
69+ struct node *n;
70+ size_t i;
71+ int p;
72+
73+ e = mkedge(env);
74+
75+ scanpaths(s);
76+ e->outimpidx = npaths;
77+ if (scanpipe(s, 1))
78+ scanpaths(s);
79+ e->nout = npaths;
80+ if (e->nout == 0)
81+ scanerror(s, "expected output path");
82+ scanchar(s, ':');
83+ name = scanname(s);
84+ e->rule = envrule(env, name);
85+ if (!e->rule)
86+ fatal("undefined rule '%s'", name);
87+ free(name);
88+ scanpaths(s);
89+ e->inimpidx = npaths - e->nout;
90+ p = scanpipe(s, 1 | 2);
91+ if (p == 1) {
92+ scanpaths(s);
93+ p = scanpipe(s, 2);
94+ }
95+ e->inorderidx = npaths - e->nout;
96+ if (p == 2)
97+ scanpaths(s);
98+ e->nin = npaths - e->nout;
99+ scannewline(s);
100+ while (scanindent(s)) {
101+ name = scanname(s);
102+ parselet(s, &str);
103+ val = enveval(env, str);
104+ envaddvar(e->env, name, val);
105+ }
106+
107+ e->out = xreallocarray(NULL, e->nout, sizeof(e->out[0]));
108+ for (i = 0, path = paths; i < e->nout; ++path) {
109+ val = enveval(e->env, *path);
110+ canonpath(val);
111+ n = mknode(val);
112+ if (n->gen) {
113+ if (!parseopts.dupbuildwarn)
114+ fatal("multiple rules generate '%s'", n->path->s);
115+ warn("multiple rules generate '%s'", n->path->s);
116+ --e->nout;
117+ if (i < e->outimpidx)
118+ --e->outimpidx;
119+ } else {
120+ n->gen = e;
121+ e->out[i] = n;
122+ ++i;
123+ }
124+ }
125+ e->in = xreallocarray(NULL, e->nin, sizeof(e->in[0]));
126+ for (i = 0; i < e->nin; ++i, ++path) {
127+ val = enveval(e->env, *path);
128+ canonpath(val);
129+ n = mknode(val);
130+ e->in[i] = n;
131+ nodeuse(n, e);
132+ }
133+ npaths = 0;
134+
135+ val = edgevar(e, "pool", true);
136+ if (val)
137+ e->pool = poolget(val->s);
138+}
139+
140+static void
141+parseinclude(struct scanner *s, struct environment *env, bool newscope)
142+{
143+ struct evalstring *str;
144+ struct string *path;
145+
146+ str = scanstring(s, true);
147+ if (!str)
148+ scanerror(s, "expected include path");
149+ scannewline(s);
150+ path = enveval(env, str);
151+
152+ if (newscope)
153+ env = mkenv(env);
154+ parse(path->s, env);
155+ free(path);
156+}
157+
158+static void
159+parsedefault(struct scanner *s, struct environment *env)
160+{
161+ struct string *path;
162+ struct node *n;
163+ size_t i;
164+
165+ scanpaths(s);
166+ deftarg = xreallocarray(deftarg, ndeftarg + npaths, sizeof(*deftarg));
167+ for (i = 0; i < npaths; ++i) {
168+ path = enveval(env, paths[i]);
169+ canonpath(path);
170+ n = nodeget(path->s, path->n);
171+ if (!n)
172+ fatal("unknown target '%s'", path->s);
173+ free(path);
174+ deftarg[ndeftarg++] = n;
175+ }
176+ scannewline(s);
177+ npaths = 0;
178+}
179+
180+static void
181+parsepool(struct scanner *s, struct environment *env)
182+{
183+ struct pool *p;
184+ struct evalstring *val;
185+ struct string *str;
186+ char *var, *end;
187+
188+ p = mkpool(scanname(s));
189+ scannewline(s);
190+ while (scanindent(s)) {
191+ var = scanname(s);
192+ parselet(s, &val);
193+ if (strcmp(var, "depth") == 0) {
194+ str = enveval(env, val);
195+ p->maxjobs = strtol(str->s, &end, 10);
196+ if (*end)
197+ fatal("invalid pool depth '%s'", str->s);
198+ free(str);
199+ } else {
200+ fatal("unexpected pool variable '%s'", var);
201+ }
202+ }
203+ if (!p->maxjobs)
204+ fatal("pool '%s' has no depth", p->name);
205+}
206+
207+static void
208+checkversion(const char *ver)
209+{
210+ int major, minor = 0;
211+
212+ if (sscanf(ver, "%d.%d", &major, &minor) < 1)
213+ fatal("invalid ninja_required_version");
214+ if (major > ninjamajor || (major == ninjamajor && minor > ninjaminor))
215+ fatal("ninja_required_version %s is newer than %d.%d", ver, ninjamajor, ninjaminor);
216+}
217+
218+void
219+parse(const char *name, struct environment *env)
220+{
221+ struct scanner s;
222+ char *var;
223+ struct string *val;
224+ struct evalstring *str;
225+
226+ scaninit(&s, name);
227+ for (;;) {
228+ switch (scankeyword(&s, &var)) {
229+ case RULE:
230+ parserule(&s, env);
231+ break;
232+ case BUILD:
233+ parseedge(&s, env);
234+ break;
235+ case INCLUDE:
236+ parseinclude(&s, env, false);
237+ break;
238+ case SUBNINJA:
239+ parseinclude(&s, env, true);
240+ break;
241+ case DEFAULT:
242+ parsedefault(&s, env);
243+ break;
244+ case POOL:
245+ parsepool(&s, env);
246+ break;
247+ case VARIABLE:
248+ parselet(&s, &str);
249+ val = enveval(env, str);
250+ if (strcmp(var, "ninja_required_version") == 0)
251+ checkversion(val->s);
252+ envaddvar(env, var, val);
253+ break;
254+ case EOF:
255+ scanclose(&s);
256+ return;
257+ }
258+ }
259+}
260+
261+void
262+defaultnodes(void fn(struct node *))
263+{
264+ struct edge *e;
265+ struct node *n;
266+ size_t i;
267+
268+ if (ndeftarg > 0) {
269+ for (i = 0; i < ndeftarg; ++i)
270+ fn(deftarg[i]);
271+ } else {
272+ /* by default build all nodes which are not used by any edges */
273+ for (e = alledges; e; e = e->allnext) {
274+ for (i = 0; i < e->nout; ++i) {
275+ n = e->out[i];
276+ if (n->nuse == 0)
277+ fn(n);
278+ }
279+ }
280+ }
281+}
+20,
-0
1@@ -0,0 +1,20 @@
2+struct environment;
3+struct node;
4+
5+struct parseoptions {
6+ _Bool dupbuildwarn;
7+};
8+
9+void parseinit(void);
10+void parse(const char *, struct environment *);
11+
12+extern struct parseoptions parseopts;
13+
14+/* supported ninja version */
15+enum {
16+ ninjamajor = 1,
17+ ninjaminor = 9,
18+};
19+
20+/* execute a function with all default nodes */
21+void defaultnodes(void(struct node *));
+22,
-0
1@@ -0,0 +1,22 @@
2+nviz
3+----
4+
5+nviz is a tool that reads a build.ninja and dumps a tree graph to stdout. i wrote
6+it because i was sick of making graphviz graphs from ninja output when i was debugging
7+shinobi. i also made it output graphviz with a -g flag, even though samu already has that,
8+just for fun. i think the graphs it emits are ever so slightly prettier.
9+
10+the vast majority of nviz's code is stolen directly from samurai, so it's under the same
11+ISC license that samurai is.
12+
13+build:
14+ ninja
15+ ninja install
16+
17+examples:
18+ nviz
19+ nviz -g | dot -Tpng -o build.png
20+
21+dependencies:
22+ C compiler
23+ ninja-compatible build tool
+352,
-0
1@@ -0,0 +1,352 @@
2+#include <ctype.h>
3+#include <stdarg.h>
4+#include <stdbool.h>
5+#include <stdio.h>
6+#include <stdlib.h>
7+#include <string.h>
8+#include "scan.h"
9+#include "util.h"
10+
11+struct evalstring **paths;
12+size_t npaths;
13+static struct buffer buf;
14+
15+void
16+scaninit(struct scanner *s, const char *path)
17+{
18+ s->path = path;
19+ s->line = 1;
20+ s->col = 1;
21+ s->f = fopen(path, "r");
22+ if (!s->f)
23+ fatal("open %s:", path);
24+ s->chr = getc(s->f);
25+}
26+
27+void
28+scanclose(struct scanner *s)
29+{
30+ fclose(s->f);
31+}
32+
33+void
34+scanerror(struct scanner *s, const char *fmt, ...)
35+{
36+ extern const char *argv0;
37+ va_list ap;
38+
39+ fprintf(stderr, "%s: %s:%d:%d: ", argv0, s->path, s->line, s->col);
40+ va_start(ap, fmt);
41+ vfprintf(stderr, fmt, ap);
42+ va_end(ap);
43+ putc('\n', stderr);
44+ exit(1);
45+}
46+
47+static int
48+next(struct scanner *s)
49+{
50+ if (s->chr == '\n') {
51+ ++s->line;
52+ s->col = 1;
53+ } else {
54+ ++s->col;
55+ }
56+ s->chr = getc(s->f);
57+
58+ return s->chr;
59+}
60+
61+static int
62+issimplevar(int c)
63+{
64+ return isalnum(c) || c == '_' || c == '-';
65+}
66+
67+static int
68+isvar(int c)
69+{
70+ return issimplevar(c) || c == '.';
71+}
72+
73+static bool
74+newline(struct scanner *s)
75+{
76+ switch (s->chr) {
77+ case '\r':
78+ if (next(s) != '\n')
79+ scanerror(s, "expected '\\n' after '\\r'");
80+ /* fallthrough */
81+ case '\n':
82+ next(s);
83+ return true;
84+ }
85+ return false;
86+}
87+
88+static bool
89+singlespace(struct scanner *s)
90+{
91+ switch (s->chr) {
92+ case '$':
93+ next(s);
94+ if (newline(s))
95+ return true;
96+ ungetc(s->chr, s->f);
97+ s->chr = '$';
98+ return false;
99+ case ' ':
100+ next(s);
101+ return true;
102+ }
103+ return false;
104+}
105+
106+static bool
107+space(struct scanner *s)
108+{
109+ if (!singlespace(s))
110+ return false;
111+ while (singlespace(s))
112+ ;
113+ return true;
114+}
115+
116+static bool
117+comment(struct scanner *s)
118+{
119+ if (s->chr != '#')
120+ return false;
121+ do next(s);
122+ while (!newline(s));
123+ return true;
124+}
125+
126+static void
127+name(struct scanner *s)
128+{
129+ buf.len = 0;
130+ while (isvar(s->chr)) {
131+ bufadd(&buf, s->chr);
132+ next(s);
133+ }
134+ if (!buf.len)
135+ scanerror(s, "expected name");
136+ bufadd(&buf, '\0');
137+ space(s);
138+}
139+
140+int
141+scankeyword(struct scanner *s, char **var)
142+{
143+ /* must stay in sorted order */
144+ static const struct {
145+ const char *name;
146+ int value;
147+ } keywords[] = {
148+ {"build", BUILD},
149+ {"default", DEFAULT},
150+ {"include", INCLUDE},
151+ {"pool", POOL},
152+ {"rule", RULE},
153+ {"subninja", SUBNINJA},
154+ };
155+ int low = 0, high = countof(keywords) - 1, mid, cmp;
156+
157+ for (;;) {
158+ switch (s->chr) {
159+ case ' ':
160+ space(s);
161+ if (!comment(s) && !newline(s))
162+ scanerror(s, "unexpected indent");
163+ break;
164+ case '#':
165+ comment(s);
166+ break;
167+ case '\r':
168+ case '\n':
169+ newline(s);
170+ break;
171+ case EOF:
172+ return EOF;
173+ default:
174+ name(s);
175+ while (low <= high) {
176+ mid = (low + high) / 2;
177+ cmp = strcmp(buf.data, keywords[mid].name);
178+ if (cmp == 0)
179+ return keywords[mid].value;
180+ if (cmp < 0)
181+ high = mid - 1;
182+ else
183+ low = mid + 1;
184+ }
185+ *var = xmemdup(buf.data, buf.len);
186+ return VARIABLE;
187+ }
188+ }
189+}
190+
191+char *
192+scanname(struct scanner *s)
193+{
194+ name(s);
195+ return xmemdup(buf.data, buf.len);
196+}
197+
198+static void
199+addstringpart(struct evalstring ***end, bool var)
200+{
201+ struct evalstring *p;
202+
203+ p = xmalloc(sizeof(*p));
204+ p->next = NULL;
205+ **end = p;
206+ if (var) {
207+ bufadd(&buf, '\0');
208+ p->var = xmemdup(buf.data, buf.len);
209+ } else {
210+ p->var = NULL;
211+ p->str = mkstr(buf.len);
212+ memcpy(p->str->s, buf.data, buf.len);
213+ p->str->s[buf.len] = '\0';
214+ }
215+ *end = &p->next;
216+ buf.len = 0;
217+}
218+
219+static void
220+escape(struct scanner *s, struct evalstring ***end)
221+{
222+ switch (s->chr) {
223+ case '$':
224+ case ' ':
225+ case ':':
226+ bufadd(&buf, s->chr);
227+ next(s);
228+ break;
229+ case '{':
230+ if (buf.len > 0)
231+ addstringpart(end, false);
232+ while (isvar(next(s)))
233+ bufadd(&buf, s->chr);
234+ if (s->chr != '}')
235+ scanerror(s, "invalid variable name");
236+ next(s);
237+ addstringpart(end, true);
238+ break;
239+ case '\r':
240+ case '\n':
241+ newline(s);
242+ space(s);
243+ break;
244+ default:
245+ if (buf.len > 0)
246+ addstringpart(end, false);
247+ while (issimplevar(s->chr)) {
248+ bufadd(&buf, s->chr);
249+ next(s);
250+ }
251+ if (!buf.len)
252+ scanerror(s, "invalid $ escape");
253+ addstringpart(end, true);
254+ }
255+}
256+
257+struct evalstring *
258+scanstring(struct scanner *s, bool path)
259+{
260+ struct evalstring *str = NULL, **end = &str;
261+
262+ buf.len = 0;
263+ for (;;) {
264+ switch (s->chr) {
265+ case '$':
266+ next(s);
267+ escape(s, &end);
268+ break;
269+ case ':':
270+ case '|':
271+ case ' ':
272+ if (path)
273+ goto out;
274+ /* fallthrough */
275+ default:
276+ bufadd(&buf, s->chr);
277+ next(s);
278+ break;
279+ case '\r':
280+ case '\n':
281+ case EOF:
282+ goto out;
283+ }
284+ }
285+out:
286+ if (buf.len > 0)
287+ addstringpart(&end, 0);
288+ if (path)
289+ space(s);
290+ return str;
291+}
292+
293+void
294+scanpaths(struct scanner *s)
295+{
296+ static size_t max;
297+ struct evalstring *str;
298+
299+ while ((str = scanstring(s, true))) {
300+ if (npaths == max) {
301+ max = max ? max * 2 : 32;
302+ paths = xreallocarray(paths, max, sizeof(paths[0]));
303+ }
304+ paths[npaths++] = str;
305+ }
306+}
307+
308+void
309+scanchar(struct scanner *s, int c)
310+{
311+ if (s->chr != c)
312+ scanerror(s, "expected '%c'", c);
313+ next(s);
314+ space(s);
315+}
316+
317+int
318+scanpipe(struct scanner *s, int n)
319+{
320+ if (s->chr != '|')
321+ return 0;
322+ next(s);
323+ if (s->chr != '|') {
324+ if (!(n & 1))
325+ scanerror(s, "expected '||'");
326+ space(s);
327+ return 1;
328+ }
329+ if (!(n & 2))
330+ scanerror(s, "unexpected '||'");
331+ next(s);
332+ space(s);
333+ return 2;
334+}
335+
336+bool
337+scanindent(struct scanner *s)
338+{
339+ bool indent;
340+
341+ for (;;) {
342+ indent = space(s);
343+ if (!comment(s))
344+ return indent && !newline(s);
345+ }
346+}
347+
348+void
349+scannewline(struct scanner *s)
350+{
351+ if (!newline(s))
352+ scanerror(s, "expected newline");
353+}
+31,
-0
1@@ -0,0 +1,31 @@
2+enum token {
3+ BUILD,
4+ DEFAULT,
5+ INCLUDE,
6+ POOL,
7+ RULE,
8+ SUBNINJA,
9+ VARIABLE,
10+};
11+
12+struct scanner {
13+ FILE *f;
14+ const char *path;
15+ int chr, line, col;
16+};
17+
18+extern struct evalstring **paths;
19+extern size_t npaths;
20+
21+void scaninit(struct scanner *, const char *);
22+void scanclose(struct scanner *);
23+
24+void scanerror(struct scanner *, const char *, ...);
25+int scankeyword(struct scanner *, char **);
26+char *scanname(struct scanner *);
27+struct evalstring *scanstring(struct scanner *, _Bool);
28+void scanpaths(struct scanner *);
29+void scanchar(struct scanner *, int);
30+int scanpipe(struct scanner *, int);
31+_Bool scanindent(struct scanner *);
32+void scannewline(struct scanner *);
+130,
-0
1@@ -0,0 +1,130 @@
2+/* Based on musl's src/search/tsearch.c, by Szabolcs Nagy.
3+ * See LICENSE file for copyright details. */
4+#include <stdlib.h>
5+#include <string.h>
6+#include "tree.h"
7+#include "util.h"
8+
9+#define MAXH (sizeof(void *) * 8 * 3 / 2)
10+
11+void
12+deltree(struct treenode *n, void delkey(void *), void delval(void *))
13+{
14+ if (!n)
15+ return;
16+ if (delkey)
17+ delkey(n->key);
18+ if (delval)
19+ delval(n->value);
20+ deltree(n->child[0], delkey, delval);
21+ deltree(n->child[1], delkey, delval);
22+ free(n);
23+}
24+
25+static inline int
26+height(struct treenode *n)
27+{
28+ return n ? n->height : 0;
29+}
30+
31+static int
32+rot(struct treenode **p, struct treenode *x, int dir /* deeper side */)
33+{
34+ struct treenode *y = x->child[dir];
35+ struct treenode *z = y->child[!dir];
36+ int hx = x->height;
37+ int hz = height(z);
38+
39+ if (hz > height(y->child[dir])) {
40+ /*
41+ * x
42+ * / \ dir z
43+ * A y / \
44+ * / \ --> x y
45+ * z D /| |\
46+ * / \ A B C D
47+ * B C
48+ */
49+ x->child[dir] = z->child[!dir];
50+ y->child[!dir] = z->child[dir];
51+ z->child[!dir] = x;
52+ z->child[dir] = y;
53+ x->height = hz;
54+ y->height = hz;
55+ z->height = hz + 1;
56+ } else {
57+ /*
58+ * x y
59+ * / \ / \
60+ * A y --> x D
61+ * / \ / \
62+ * z D A z
63+ */
64+ x->child[dir] = z;
65+ y->child[!dir] = x;
66+ x->height = hz + 1;
67+ y->height = hz + 2;
68+ z = y;
69+ }
70+ *p = z;
71+ return z->height - hx;
72+}
73+
74+static int
75+balance(struct treenode **p)
76+{
77+ struct treenode *n = *p;
78+ int h0 = height(n->child[0]);
79+ int h1 = height(n->child[1]);
80+
81+ if (h0 - h1 + 1u < 3u) {
82+ int old = n->height;
83+ n->height = h0 < h1 ? h1 + 1 : h0 + 1;
84+ return n->height - old;
85+ }
86+ return rot(p, n, h0 < h1);
87+}
88+
89+struct treenode *
90+treefind(struct treenode *n, const char *key)
91+{
92+ int c;
93+
94+ while (n) {
95+ c = strcmp(key, n->key);
96+ if (c == 0)
97+ return n;
98+ n = n->child[c > 0];
99+ }
100+ return NULL;
101+}
102+
103+void *
104+treeinsert(struct treenode **rootp, char *key, void *value)
105+{
106+ struct treenode **a[MAXH], *n = *rootp, *r;
107+ void *old;
108+ int i = 0, c;
109+
110+ a[i++] = rootp;
111+ while (n) {
112+ c = strcmp(key, n->key);
113+ if (c == 0) {
114+ old = n->value;
115+ n->value = value;
116+ return old;
117+ }
118+ a[i++] = &n->child[c > 0];
119+ n = n->child[c > 0];
120+ }
121+ r = xmalloc(sizeof(*r));
122+ r->key = key;
123+ r->value = value;
124+ r->child[0] = r->child[1] = NULL;
125+ r->height = 1;
126+ /* insert new node, rebalance ancestors. */
127+ *a[--i] = r;
128+ while (i && balance(a[--i]))
129+ ;
130+ return NULL;
131+}
+14,
-0
1@@ -0,0 +1,14 @@
2+/* binary tree node, such that keys are sorted lexicographically for fast lookup */
3+struct treenode {
4+ char *key;
5+ void *value;
6+ struct treenode *child[2];
7+ int height;
8+};
9+
10+/* free a tree and its children recursively, free keys and values with a function */
11+void deltree(struct treenode *, void(void *), void(void *));
12+/* search a binary tree for a key, return the key's value or NULL */
13+struct treenode *treefind(struct treenode *, const char *);
14+/* insert into a binary tree a key and a value, replace and return the old value if the key already exists */
15+void *treeinsert(struct treenode **, char *, void *);
+225,
-0
1@@ -0,0 +1,225 @@
2+#include <errno.h>
3+#include <stdarg.h>
4+#include <stdlib.h>
5+#include <stdint.h>
6+#include <stdio.h>
7+#include <string.h>
8+#include "util.h"
9+
10+extern const char *argv0;
11+
12+static void
13+vwarn(const char *fmt, va_list ap)
14+{
15+ fprintf(stderr, "%s: ", argv0);
16+ vfprintf(stderr, fmt, ap);
17+ if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
18+ putc(' ', stderr);
19+ perror(NULL);
20+ } else {
21+ putc('\n', stderr);
22+ }
23+}
24+
25+void
26+warn(const char *fmt, ...)
27+{
28+ va_list ap;
29+
30+ va_start(ap, fmt);
31+ vwarn(fmt, ap);
32+ va_end(ap);
33+}
34+
35+void
36+fatal(const char *fmt, ...)
37+{
38+ va_list ap;
39+
40+ va_start(ap, fmt);
41+ vwarn(fmt, ap);
42+ va_end(ap);
43+ exit(1);
44+}
45+
46+void *
47+xmalloc(size_t n)
48+{
49+ void *p;
50+
51+ p = malloc(n);
52+ if (!p)
53+ fatal("malloc:");
54+
55+ return p;
56+}
57+
58+static void *
59+reallocarray_(void *p, size_t n, size_t m)
60+{
61+ if (m && n > SIZE_MAX / m) {
62+ errno = ENOMEM;
63+ return NULL;
64+ }
65+ return realloc(p, n * m);
66+}
67+
68+void *
69+xreallocarray(void *p, size_t n, size_t m)
70+{
71+ p = reallocarray_(p, n, m);
72+ if (!p)
73+ fatal("reallocarray:");
74+
75+ return p;
76+}
77+
78+char *
79+xmemdup(const char *s, size_t n)
80+{
81+ char *p;
82+
83+ p = xmalloc(n);
84+ memcpy(p, s, n);
85+
86+ return p;
87+}
88+
89+int
90+xasprintf(char **s, const char *fmt, ...)
91+{
92+ va_list ap;
93+ int ret;
94+ size_t n;
95+
96+ va_start(ap, fmt);
97+ ret = vsnprintf(NULL, 0, fmt, ap);
98+ va_end(ap);
99+ if (ret < 0)
100+ fatal("vsnprintf:");
101+ n = ret + 1;
102+ *s = xmalloc(n);
103+ va_start(ap, fmt);
104+ ret = vsnprintf(*s, n, fmt, ap);
105+ va_end(ap);
106+ if (ret < 0 || (size_t)ret >= n)
107+ fatal("vsnprintf:");
108+
109+ return ret;
110+}
111+
112+void
113+bufadd(struct buffer *buf, char c)
114+{
115+ if (buf->len >= buf->cap) {
116+ buf->cap = buf->cap ? buf->cap * 2 : 1 << 8;
117+ buf->data = realloc(buf->data, buf->cap);
118+ if (!buf->data)
119+ fatal("realloc:");
120+ }
121+ buf->data[buf->len++] = c;
122+}
123+
124+struct string *
125+mkstr(size_t n)
126+{
127+ struct string *str;
128+
129+ str = xmalloc(sizeof(*str) + n + 1);
130+ str->n = n;
131+
132+ return str;
133+}
134+
135+void
136+delevalstr(void *ptr)
137+{
138+ struct evalstring *str = ptr, *p;
139+
140+ while (str) {
141+ p = str;
142+ str = str->next;
143+ if (p->var)
144+ free(p->var);
145+ else
146+ free(p->str);
147+ free(p);
148+ }
149+}
150+
151+void
152+canonpath(struct string *path)
153+{
154+ char *component[60];
155+ int n;
156+ char *s, *d, *end;
157+
158+ if (path->n == 0)
159+ fatal("empty path");
160+ s = d = path->s;
161+ end = path->s + path->n;
162+ n = 0;
163+ if (*s == '/') {
164+ ++s;
165+ ++d;
166+ }
167+ while (s < end) {
168+ switch (s[0]) {
169+ case '/':
170+ ++s;
171+ continue;
172+ case '.':
173+ switch (s[1]) {
174+ case '\0': case '/':
175+ s += 2;
176+ continue;
177+ case '.':
178+ if (s[2] != '/' && s[2] != '\0')
179+ break;
180+ if (n > 0) {
181+ d = component[--n];
182+ } else {
183+ *d++ = s[0];
184+ *d++ = s[1];
185+ *d++ = s[2];
186+ }
187+ s += 3;
188+ continue;
189+ }
190+ }
191+ if (n == countof(component))
192+ fatal("path has too many components: %s", path->s);
193+ component[n++] = d;
194+ while (*s != '/' && *s != '\0')
195+ *d++ = *s++;
196+ *d++ = *s++;
197+ }
198+ if (d == path->s) {
199+ *d++ = '.';
200+ *d = '\0';
201+ } else {
202+ *--d = '\0';
203+ }
204+ path->n = d - path->s;
205+}
206+
207+int
208+writefile(const char *name, struct string *s)
209+{
210+ FILE *f;
211+ int ret;
212+
213+ f = fopen(name, "w");
214+ if (!f) {
215+ warn("open %s:", name);
216+ return -1;
217+ }
218+ ret = 0;
219+ if (s && (fwrite(s->s, 1, s->n, f) != s->n || fflush(f) != 0)) {
220+ warn("write %s:", name);
221+ ret = -1;
222+ }
223+ fclose(f);
224+
225+ return ret;
226+}
+44,
-0
1@@ -0,0 +1,44 @@
2+struct buffer {
3+ char *data;
4+ size_t len, cap;
5+};
6+
7+struct string {
8+ size_t n;
9+ char s[];
10+};
11+
12+/* an unevaluated string */
13+struct evalstring {
14+ char *var;
15+ struct string *str;
16+ struct evalstring *next;
17+};
18+
19+#ifndef countof
20+#define countof(a) (sizeof(a) / sizeof((a)[0]))
21+#endif
22+
23+void warn(const char *, ...);
24+void fatal(const char *, ...);
25+
26+void *xmalloc(size_t);
27+void *xreallocarray(void *, size_t, size_t);
28+char *xmemdup(const char *, size_t);
29+int xasprintf(char **, const char *, ...);
30+
31+/* append a byte to a buffer */
32+void bufadd(struct buffer *buf, char c);
33+
34+/* allocates a new string with length n. n + 1 bytes are allocated for
35+ * s, but not initialized. */
36+struct string *mkstr(size_t n);
37+
38+/* delete an unevaluated string */
39+void delevalstr(void *);
40+
41+/* canonicalizes the given path by removing duplicate slashes, and
42+ * folding '/.' and 'foo/..' */
43+void canonpath(struct string *);
44+/* write a new file with the given name and contents */
45+int writefile(const char *, struct string *);
+34,
-0
1@@ -0,0 +1,34 @@
2+# i know this is a ninja file, but you're allowed to edit it.
3+# you can also pass things on the command line if you like, eg
4+#
5+# CC=clang ninja
6+
7+cc = $${CC:-cc}
8+cflags = $${CFLAGS:--O2 -std=c99}
9+ldflags = $${LDFLAGS:-}
10+libs = $${LIBS:-}
11+prefix = $${PREFIX:-/usr/local}
12+destdir = $${DESTDIR:-}
13+bindir = $${BINDIR:-$${DESTDIR:-}$${PREFIX:-/usr/local}/bin}
14+
15+rule cc
16+ command = $cc $cflags -MMD -MF $out.d -c -o $out $in
17+ deps = gcc
18+ depfile = $out.d
19+ description = cc $out
20+
21+rule link
22+ command = $cc $ldflags -o $out $in $libs
23+ description = link $out
24+
25+rule install
26+ command = mkdir -p $bindir && cp peek $bindir/
27+ description = install peek
28+
29+build peek.o: cc peek.c
30+
31+build peek: link peek.o
32+build all: phony peek
33+build install: install peek
34+
35+default all
+263,
-0
1@@ -0,0 +1,263 @@
2+#define _POSIX_C_SOURCE 200809L
3+#include <arpa/inet.h>
4+#include <errno.h>
5+#include <netinet/in.h>
6+#include <signal.h>
7+#include <stdbool.h>
8+#include <stdio.h>
9+#include <stdlib.h>
10+#include <string.h>
11+#include <sys/ptrace.h>
12+#include <sys/socket.h>
13+#include <sys/syscall.h>
14+#include <sys/types.h>
15+#include <sys/un.h>
16+#include <sys/user.h>
17+#include <sys/wait.h>
18+#include <unistd.h>
19+
20+struct proc {
21+ pid_t pid;
22+ int entering;
23+};
24+
25+static struct proc *procs;
26+static size_t nprocs;
27+
28+static void
29+die(const char *msg)
30+{
31+ perror(msg);
32+ exit(1);
33+}
34+
35+static struct proc *
36+procget(pid_t pid)
37+{
38+ size_t i;
39+
40+ for (i = 0; i < nprocs; i++) {
41+ if (procs[i].pid == pid)
42+ return &procs[i];
43+ }
44+ procs = realloc(procs, (nprocs + 1) * sizeof(procs[0]));
45+ if (!procs)
46+ die("realloc");
47+ procs[nprocs] = (struct proc){.pid = pid};
48+ return &procs[nprocs++];
49+}
50+
51+static void
52+procdel(pid_t pid)
53+{
54+ size_t i;
55+
56+ for (i = 0; i < nprocs; i++) {
57+ if (procs[i].pid != pid)
58+ continue;
59+ procs[i] = procs[--nprocs];
60+ return;
61+ }
62+}
63+
64+static void
65+setopts(pid_t pid)
66+{
67+ long opts = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEFORK |
68+ PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE |
69+ PTRACE_O_TRACEEXEC;
70+ if (ptrace(PTRACE_SETOPTIONS, pid, 0, opts) < 0)
71+ die("ptrace SETOPTIONS");
72+}
73+
74+static void
75+readmem(pid_t pid, unsigned long addr, void *buf, size_t n)
76+{
77+ size_t i;
78+ unsigned char *p = buf;
79+
80+ for (i = 0; i < n; i += sizeof(long)) {
81+ long v = ptrace(PTRACE_PEEKDATA, pid, addr + i, 0);
82+ if (v == -1 && errno)
83+ memset(p + i, 0, n - i);
84+ else
85+ memcpy(p + i, &v, n - i < sizeof(v) ? n - i : sizeof(v));
86+ }
87+}
88+
89+static char *
90+readstr(pid_t pid, unsigned long addr)
91+{
92+ char *s;
93+ size_t cap, len;
94+
95+ if (!addr)
96+ return strdup("?");
97+ cap = 64;
98+ len = 0;
99+ s = malloc(cap);
100+ if (!s)
101+ die("malloc");
102+ for (;;) {
103+ long v = ptrace(PTRACE_PEEKDATA, pid, addr + len, 0);
104+ size_t i;
105+ char *b = (char *)&v;
106+
107+ if (v == -1 && errno) {
108+ free(s);
109+ return strdup("?");
110+ }
111+ for (i = 0; i < sizeof(v); i++) {
112+ if (len + 1 >= cap) {
113+ cap *= 2;
114+ s = realloc(s, cap);
115+ if (!s)
116+ die("realloc");
117+ }
118+ s[len++] = b[i];
119+ if (!b[i])
120+ return s;
121+ }
122+ }
123+}
124+
125+static void
126+printnet(pid_t pid, unsigned long addr, unsigned long len)
127+{
128+ struct sockaddr_storage ss;
129+ char host[INET6_ADDRSTRLEN + 32];
130+
131+ if (!addr || len < sizeof(sa_family_t))
132+ return;
133+ memset(&ss, 0, sizeof(ss));
134+ readmem(pid, addr, &ss, len < sizeof(ss) ? len : sizeof(ss));
135+ if (ss.ss_family == AF_UNIX) {
136+ struct sockaddr_un *un = (struct sockaddr_un *)&ss;
137+ printf("net %d unix:%s\n", pid, un->sun_path[0] ? un->sun_path : "(anon)");
138+ } else if (ss.ss_family == AF_INET) {
139+ struct sockaddr_in *in = (struct sockaddr_in *)&ss;
140+ if (!inet_ntop(AF_INET, &in->sin_addr, host, sizeof(host)))
141+ return;
142+ printf("net %d %s:%u\n", pid, host, ntohs(in->sin_port));
143+ } else if (ss.ss_family == AF_INET6) {
144+ struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&ss;
145+ if (!inet_ntop(AF_INET6, &in6->sin6_addr, host, sizeof(host)))
146+ return;
147+ printf("net %d [%s]:%u\n", pid, host, ntohs(in6->sin6_port));
148+ }
149+}
150+
151+static void
152+tracecall(pid_t pid, struct user_regs_struct *r)
153+{
154+ char *s;
155+
156+ switch ((long)r->orig_rax) {
157+ case SYS_execve:
158+ s = readstr(pid, r->rdi);
159+ printf("exec %d %s\n", pid, s);
160+ free(s);
161+ break;
162+ case SYS_execveat:
163+ s = readstr(pid, r->rsi);
164+ printf("exec %d %s\n", pid, s);
165+ free(s);
166+ break;
167+ case SYS_open:
168+ s = readstr(pid, r->rdi);
169+ printf("file %d %s\n", pid, s);
170+ free(s);
171+ break;
172+ case SYS_openat:
173+ s = readstr(pid, r->rsi);
174+ printf("file %d %s\n", pid, s);
175+ free(s);
176+ break;
177+ case SYS_openat2:
178+ s = readstr(pid, r->rsi);
179+ printf("file %d %s\n", pid, s);
180+ free(s);
181+ break;
182+ case SYS_connect:
183+ printnet(pid, r->rsi, r->rdx);
184+ break;
185+ }
186+}
187+
188+int
189+main(int argc, char *argv[])
190+{
191+ int status;
192+ pid_t pid;
193+
194+ if (argc < 2) {
195+ fprintf(stderr, "usage: %s cmd [args...]\n", argv[0]);
196+ return 2;
197+ }
198+ pid = fork();
199+ if (pid < 0)
200+ die("fork");
201+ if (pid == 0) {
202+ if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0)
203+ die("ptrace TRACEME");
204+ raise(SIGSTOP);
205+ execvp(argv[1], argv + 1);
206+ die("execvp");
207+ }
208+ if (waitpid(pid, &status, 0) < 0)
209+ die("waitpid");
210+ procget(pid);
211+ setopts(pid);
212+ if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0)
213+ die("ptrace SYSCALL");
214+ while (nprocs > 0) {
215+ unsigned int event;
216+ struct proc *p;
217+
218+ pid = waitpid(-1, &status, __WALL);
219+ if (pid < 0) {
220+ if (errno == EINTR)
221+ continue;
222+ die("waitpid");
223+ }
224+ p = procget(pid);
225+ if (WIFEXITED(status)) {
226+ printf("exit %d %d\n", pid, WEXITSTATUS(status));
227+ procdel(pid);
228+ continue;
229+ }
230+ if (WIFSIGNALED(status)) {
231+ printf("exit %d sig%d\n", pid, WTERMSIG(status));
232+ procdel(pid);
233+ continue;
234+ }
235+ if (!WIFSTOPPED(status))
236+ continue;
237+ if (WSTOPSIG(status) == (SIGTRAP | 0x80)) {
238+ struct user_regs_struct r;
239+
240+ if (!p->entering && ptrace(PTRACE_GETREGS, pid, 0, &r) == 0)
241+ tracecall(pid, &r);
242+ p->entering = !p->entering;
243+ ptrace(PTRACE_SYSCALL, pid, 0, 0);
244+ continue;
245+ }
246+ event = (unsigned int)status >> 16;
247+ if (WSTOPSIG(status) == SIGTRAP && event) {
248+ unsigned long msg = 0;
249+
250+ if ((event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK ||
251+ event == PTRACE_EVENT_CLONE) &&
252+ ptrace(PTRACE_GETEVENTMSG, pid, 0, &msg) == 0) {
253+ printf("fork %d -> %lu\n", pid, msg);
254+ procget((pid_t)msg);
255+ }
256+ ptrace(PTRACE_SYSCALL, pid, 0, 0);
257+ continue;
258+ }
259+ setopts(pid);
260+ ptrace(PTRACE_SYSCALL, pid, 0, WSTOPSIG(status) == SIGSTOP ? 0 : WSTOPSIG(status));
261+ }
262+ free(procs);
263+ return 0;
264+}
+18,
-0
1@@ -0,0 +1,18 @@
2+peek
3+----
4+
5+peek is a small linux-only command tracer. it is simpler and has more human-readable output than strace,
6+although it's not as in-depth. it runs some given command and prints a short stream of file opens, execs,
7+forks, simple network stuff, and exits.
8+
9+build:
10+ ninja
11+ ninja install
12+
13+example:
14+ peek [any command here]
15+
16+dependencies:
17+ - linux system
18+ - C compiler
19+ - ninja compatible build tool
A
pinkie
+35,
-0
1@@ -0,0 +1,35 @@
2+#!/bin/sh
3+# it's like finger but nice n simple.
4+# usage:
5+# pinkie user@host
6+#
7+# dependencies:
8+# shell
9+# netcat
10+#
11+# try pinkie shrub@shrub.industries :)
12+
13+set -eu
14+
15+usage() {
16+ printf 'usage: %s user@host\n' "${0##*/}" >&2
17+ exit 2
18+}
19+
20+[ "$#" -eq 1 ] || usage
21+
22+case $1 in
23+*@*)
24+ user=${1%@*}
25+ host=${1#*@}
26+ ;;
27+ *)
28+ usage
29+ ;;
30+esac
31+
32+[ -n "$user" ] || usage
33+[ -n "$host" ] || usage
34+
35+printf '[%s]\n' "$host"
36+printf '%s\r\n' "$user" | nc "$host" 79
+21,
-0
1@@ -0,0 +1,21 @@
2+extern const char *argv0;
3+
4+#define ARGBEGIN \
5+ for (;;) { \
6+ if (argc > 0) \
7+ ++argv, --argc; \
8+ if (argc == 0 || (*argv)[0] != '-') \
9+ break; \
10+ if ((*argv)[1] == '-' && !(*argv)[2]) { \
11+ ++argv, --argc; \
12+ break; \
13+ } \
14+ for (char *opt_ = &(*argv)[1], done_ = 0; !done_ && *opt_; ++opt_) { \
15+ switch (*opt_)
16+
17+#define ARGEND \
18+ } \
19+ }
20+
21+#define EARGF(x) \
22+ (done_ = 1, *++opt_ ? opt_ : argv[1] ? --argc, *++argv : ((x), abort(), (char *)0))
+37,
-0
1@@ -0,0 +1,37 @@
2+# i know this is a ninja file, but you're allowed to edit it.
3+# you can also pass things on the command line if you like, eg
4+#
5+# CC=clang ninja
6+
7+cc = $${CC:-cc}
8+cflags = $${CFLAGS:--O2 -std=c99}
9+ldflags = $${LDFLAGS:-}
10+libs = $${LIBS:--lexpat}
11+prefix = $${PREFIX:-/usr/local}
12+destdir = $${DESTDIR:-}
13+bindir = $${BINDIR:-$${DESTDIR:-}$${PREFIX:-/usr/local}/bin}
14+
15+rule cc
16+ command = $cc $cflags -MMD -MF $out.d -c -o $out $in
17+ deps = gcc
18+ depfile = $out.d
19+ description = cc $out
20+
21+rule link
22+ command = $cc $ldflags -o $out $in $libs
23+ description = link $out
24+
25+rule install
26+ command = mkdir -p $bindir && cp proto $bindir/
27+ description = install proto
28+
29+build parse.o: cc parse.c
30+build proto.o: cc proto.c
31+build protocol.o: cc protocol.c
32+build util.o: cc util.c
33+
34+build proto: link parse.o proto.o protocol.o util.o
35+build all: phony proto
36+build install: install proto
37+
38+default all
+360,
-0
1@@ -0,0 +1,360 @@
2+#include <expat.h>
3+#include <stdbool.h>
4+#include <stdio.h>
5+#include <stdlib.h>
6+#include <string.h>
7+
8+#include "parse.h"
9+#include "util.h"
10+
11+enum parse_scope {
12+ SCOPE_PROTOCOL,
13+ SCOPE_INTERFACE,
14+ SCOPE_MESSAGE,
15+ SCOPE_ENUM,
16+ SCOPE_DESCRIPTION,
17+};
18+
19+struct parser {
20+ const char *path;
21+ XML_Parser xml;
22+ struct protocol *proto;
23+ struct interface *iface;
24+ struct message *msg;
25+ struct enumdef *en;
26+ enum parse_scope stack[32];
27+ size_t depth;
28+ struct description *desc;
29+ char *text;
30+ size_t textlen;
31+ size_t textcap;
32+};
33+
34+static const char *attr(const char **, const char *);
35+static int parseint(const char *, const char *);
36+static void pushscope(struct parser *, enum parse_scope);
37+static void popscope(struct parser *);
38+static bool inscope(const struct parser *, enum parse_scope);
39+static void cleartext(struct parser *);
40+static void appendtext(struct parser *, const char *, int);
41+static char *normalizedtext(const char *);
42+static void onstart(void *, const char *, const char **);
43+static void onend(void *, const char *);
44+static void ontext(void *, const XML_Char *, int);
45+
46+static const char *
47+attr(const char **attrs, const char *name)
48+{
49+ size_t i;
50+
51+ for (i = 0; attrs && attrs[i]; i += 2) {
52+ if (strcmp(attrs[i], name) == 0)
53+ return attrs[i + 1];
54+ }
55+ return NULL;
56+}
57+
58+static int
59+parseint(const char *what, const char *s)
60+{
61+ char *end;
62+ long v;
63+
64+ if (!s)
65+ die("missing %s attribute", what);
66+ v = strtol(s, &end, 10);
67+ if (*s == '\0' || *end != '\0' || v < 0)
68+ die("invalid %s: %s", what, s);
69+ return (int)v;
70+}
71+
72+static void
73+pushscope(struct parser *p, enum parse_scope scope)
74+{
75+ if (p->depth == countof(p->stack))
76+ die("%s: XML nesting too deep", p->path);
77+ p->stack[p->depth++] = scope;
78+}
79+
80+static void
81+popscope(struct parser *p)
82+{
83+ if (p->depth > 0)
84+ --p->depth;
85+}
86+
87+static bool
88+inscope(const struct parser *p, enum parse_scope scope)
89+{
90+ return p->depth > 0 && p->stack[p->depth - 1] == scope;
91+}
92+
93+static void
94+cleartext(struct parser *p)
95+{
96+ free(p->text);
97+ p->text = NULL;
98+ p->textlen = 0;
99+ p->textcap = 0;
100+}
101+
102+static void
103+appendtext(struct parser *p, const char *s, int n)
104+{
105+ size_t need;
106+
107+ if (n <= 0)
108+ return;
109+ need = p->textlen + (size_t)n + 1;
110+ if (need > p->textcap) {
111+ p->textcap = p->textcap ? p->textcap * 2 : 128;
112+ while (p->textcap < need)
113+ p->textcap *= 2;
114+ p->text = xreallocarray(p->text, p->textcap, sizeof(p->text[0]));
115+ }
116+ memcpy(p->text + p->textlen, s, (size_t)n);
117+ p->textlen += (size_t)n;
118+ p->text[p->textlen] = '\0';
119+}
120+
121+static char *
122+normalizedtext(const char *s)
123+{
124+ const char *start, *end, *lineend;
125+ char *out;
126+ size_t cap, len, n;
127+
128+ if (!s)
129+ return NULL;
130+ start = s;
131+ while (*start == '\n' || *start == '\r' || *start == '\t' || *start == ' ')
132+ ++start;
133+ end = s + strlen(s);
134+ while (end > start && (end[-1] == '\n' || end[-1] == '\r' || end[-1] == '\t' || end[-1] == ' '))
135+ --end;
136+ if (start == end)
137+ return NULL;
138+
139+ cap = (size_t)(end - start) + 1;
140+ out = xmalloc(cap);
141+ len = 0;
142+ while (start < end) {
143+ while (start < end && (*start == '\n' || *start == '\r'))
144+ ++start;
145+ lineend = start;
146+ while (lineend < end && *lineend != '\n' && *lineend != '\r')
147+ ++lineend;
148+ while (start < lineend && (*start == ' ' || *start == '\t'))
149+ ++start;
150+ while (lineend > start && (lineend[-1] == ' ' || lineend[-1] == '\t'))
151+ --lineend;
152+ n = (size_t)(lineend - start);
153+ if (n > 0) {
154+ if (len > 0)
155+ out[len++] = '\n';
156+ memcpy(out + len, start, n);
157+ len += n;
158+ }
159+ start = lineend;
160+ while (start < end && *start != '\n' && *start != '\r')
161+ ++start;
162+ }
163+ if (len == 0) {
164+ free(out);
165+ return NULL;
166+ }
167+ out[len] = '\0';
168+ return out;
169+}
170+
171+static void
172+onstart(void *data, const char *name, const char **attrs)
173+{
174+ struct parser *p = data;
175+ const char *aname, *atype, *iface, *allow_null, *version, *type, *summary;
176+
177+ if (strcmp(name, "protocol") == 0) {
178+ if (p->proto->name)
179+ die("%s:%lu: duplicate protocol element",
180+ p->path, XML_GetCurrentLineNumber(p->xml));
181+ aname = attr(attrs, "name");
182+ if (!aname)
183+ die("%s:%lu: protocol missing name",
184+ p->path, XML_GetCurrentLineNumber(p->xml));
185+ p->proto->name = xstrdup(aname);
186+ pushscope(p, SCOPE_PROTOCOL);
187+ return;
188+ }
189+
190+ if (strcmp(name, "interface") == 0 && inscope(p, SCOPE_PROTOCOL)) {
191+ aname = attr(attrs, "name");
192+ version = attr(attrs, "version");
193+ if (!aname || !version)
194+ die("%s:%lu: interface missing name/version",
195+ p->path, XML_GetCurrentLineNumber(p->xml));
196+ p->iface = proto_add_interface(p->proto, aname, parseint("version", version));
197+ pushscope(p, SCOPE_INTERFACE);
198+ return;
199+ }
200+
201+ if ((strcmp(name, "request") == 0 || strcmp(name, "event") == 0) && inscope(p, SCOPE_INTERFACE)) {
202+ aname = attr(attrs, "name");
203+ type = attr(attrs, "type");
204+ if (!aname)
205+ die("%s:%lu: message missing name",
206+ p->path, XML_GetCurrentLineNumber(p->xml));
207+ p->msg = iface_add_message(p->iface,
208+ strcmp(name, "request") == 0 ? MSG_REQUEST : MSG_EVENT, aname);
209+ p->msg->destructor = type && strcmp(type, "destructor") == 0;
210+ pushscope(p, SCOPE_MESSAGE);
211+ return;
212+ }
213+
214+ if (strcmp(name, "arg") == 0 && inscope(p, SCOPE_MESSAGE)) {
215+ aname = attr(attrs, "name");
216+ atype = attr(attrs, "type");
217+ iface = attr(attrs, "interface");
218+ allow_null = attr(attrs, "allow-null");
219+ if (!aname || !atype)
220+ die("%s:%lu: arg missing name/type",
221+ p->path, XML_GetCurrentLineNumber(p->xml));
222+ message_add_arg(p->msg, aname, atype, iface,
223+ allow_null && strcmp(allow_null, "true") == 0);
224+ return;
225+ }
226+
227+ if (strcmp(name, "enum") == 0 && inscope(p, SCOPE_INTERFACE)) {
228+ aname = attr(attrs, "name");
229+ if (!aname)
230+ die("%s:%lu: enum missing name",
231+ p->path, XML_GetCurrentLineNumber(p->xml));
232+ iface_add_enum(p->iface, aname);
233+ p->en = iface_last_enum(p->iface);
234+ pushscope(p, SCOPE_ENUM);
235+ return;
236+ }
237+
238+ if (strcmp(name, "description") == 0) {
239+ if (inscope(p, SCOPE_MESSAGE))
240+ p->desc = &p->msg->desc;
241+ else if (inscope(p, SCOPE_ENUM))
242+ p->desc = &p->en->desc;
243+ else if (inscope(p, SCOPE_INTERFACE))
244+ p->desc = &p->iface->desc;
245+ else if (inscope(p, SCOPE_PROTOCOL))
246+ p->desc = &p->proto->desc;
247+ else
248+ return;
249+ summary = attr(attrs, "summary");
250+ free(p->desc->summary);
251+ p->desc->summary = summary ? xstrdup(summary) : NULL;
252+ cleartext(p);
253+ pushscope(p, SCOPE_DESCRIPTION);
254+ return;
255+ }
256+}
257+
258+static void
259+onend(void *data, const char *name)
260+{
261+ struct parser *p = data;
262+
263+ if (strcmp(name, "request") == 0 || strcmp(name, "event") == 0) {
264+ if (inscope(p, SCOPE_MESSAGE)) {
265+ p->msg = NULL;
266+ popscope(p);
267+ }
268+ return;
269+ }
270+ if (strcmp(name, "enum") == 0) {
271+ if (inscope(p, SCOPE_ENUM))
272+ {
273+ p->en = NULL;
274+ popscope(p);
275+ }
276+ return;
277+ }
278+ if (strcmp(name, "description") == 0) {
279+ if (inscope(p, SCOPE_DESCRIPTION)) {
280+ free(p->desc->text);
281+ p->desc->text = normalizedtext(p->text);
282+ p->desc = NULL;
283+ cleartext(p);
284+ popscope(p);
285+ }
286+ return;
287+ }
288+ if (strcmp(name, "interface") == 0) {
289+ if (inscope(p, SCOPE_INTERFACE)) {
290+ p->iface = NULL;
291+ popscope(p);
292+ }
293+ return;
294+ }
295+ if (strcmp(name, "protocol") == 0) {
296+ if (inscope(p, SCOPE_PROTOCOL))
297+ popscope(p);
298+ return;
299+ }
300+}
301+
302+static void
303+ontext(void *data, const XML_Char *s, int len)
304+{
305+ struct parser *p = data;
306+
307+ if (inscope(p, SCOPE_DESCRIPTION))
308+ appendtext(p, s, len);
309+}
310+
311+void
312+parsefile(const char *path, struct protocol *proto)
313+{
314+ struct parser p;
315+ FILE *fp;
316+ void *buf;
317+ int done;
318+
319+ memset(&p, 0, sizeof(p));
320+ p.path = path;
321+ p.proto = proto;
322+ p.xml = XML_ParserCreate(NULL);
323+ if (!p.xml)
324+ die("XML_ParserCreate:");
325+ XML_SetUserData(p.xml, &p);
326+ XML_SetElementHandler(p.xml, onstart, onend);
327+ XML_SetCharacterDataHandler(p.xml, ontext);
328+
329+ fp = fopen(path, "rb");
330+ if (!fp) {
331+ XML_ParserFree(p.xml);
332+ die("open %s:", path);
333+ }
334+
335+ done = 0;
336+ while (!done) {
337+ size_t nread;
338+
339+ buf = XML_GetBuffer(p.xml, 4096);
340+ if (!buf)
341+ die("%s: XML_GetBuffer failed", path);
342+ nread = fread(buf, 1, 4096, fp);
343+ done = nread < 4096;
344+ if (ferror(fp))
345+ die("read %s:", path);
346+ if (XML_ParseBuffer(p.xml, (int)nread, done) == XML_STATUS_ERROR) {
347+ die("%s:%lu:%lu: %s", path,
348+ XML_GetCurrentLineNumber(p.xml),
349+ XML_GetCurrentColumnNumber(p.xml),
350+ XML_ErrorString(XML_GetErrorCode(p.xml)));
351+ }
352+ }
353+
354+ fclose(fp);
355+ XML_ParserFree(p.xml);
356+ cleartext(&p);
357+
358+ if (!proto->name)
359+ die("%s: missing protocol element", path);
360+ proto_resolve(proto);
361+}
+8,
-0
1@@ -0,0 +1,8 @@
2+#ifndef PARSE_H
3+#define PARSE_H
4+
5+#include "protocol.h"
6+
7+void parsefile(const char *, struct protocol *);
8+
9+#endif
+307,
-0
1@@ -0,0 +1,307 @@
2+#include <stdbool.h>
3+#include <glob.h>
4+#include <sys/stat.h>
5+#include <stdio.h>
6+#include <stdlib.h>
7+#include <string.h>
8+
9+#include "parse.h"
10+#include "protocol.h"
11+#include "util.h"
12+
13+const char *argv0;
14+
15+struct nameset {
16+ const char **items;
17+ size_t len;
18+ size_t cap;
19+};
20+
21+static void usage(FILE *);
22+static const char *progname(const char *, const char *);
23+static bool fileexists(const char *);
24+static bool haspathsep(const char *);
25+static char *joinpath(const char *, const char *);
26+static char *searchsystem(const char *);
27+static char *resolvepath(const char *);
28+static void print_protocol_summary(const struct protocol *);
29+static void print_interface_summary(const struct protocol *, const struct interface *);
30+static void print_message(const struct message *);
31+static void print_args(const struct message *);
32+static void print_arg(const struct argument *);
33+static void print_description(const struct description *, const char *);
34+
35+static void
36+usage(FILE *fp)
37+{
38+ fprintf(fp, "usage: %s [-h] FILE [INTERFACE]\n", argv0);
39+}
40+
41+static const char *
42+progname(const char *arg, const char *def)
43+{
44+ const char *slash;
45+
46+ if (!arg)
47+ return def;
48+ slash = strrchr(arg, '/');
49+ return slash ? slash + 1 : arg;
50+}
51+
52+static bool
53+fileexists(const char *path)
54+{
55+ struct stat st;
56+
57+ return stat(path, &st) == 0 && S_ISREG(st.st_mode);
58+}
59+
60+static bool
61+haspathsep(const char *path)
62+{
63+ return strchr(path, '/') != NULL;
64+}
65+
66+static char *
67+joinpath(const char *dir, const char *name)
68+{
69+ size_t ndir, nname;
70+ char *path;
71+
72+ ndir = strlen(dir);
73+ nname = strlen(name);
74+ path = xmalloc(ndir + 1 + nname + 1);
75+ memcpy(path, dir, ndir);
76+ path[ndir] = '/';
77+ memcpy(path + ndir + 1, name, nname + 1);
78+ return path;
79+}
80+
81+static char *
82+searchsystem(const char *name)
83+{
84+ static const char *patterns[] = {
85+ "/usr/share/wayland/%s",
86+ "/usr/share/wayland/%s.xml",
87+ "/usr/share/wayland-protocols/*/*/%s",
88+ "/usr/share/wayland-protocols/*/*/%s.xml",
89+ "/use/share/wayland-protocols/*/*/%s",
90+ "/use/share/wayland-protocols/*/*/%s.xml",
91+ };
92+ glob_t g;
93+ char *pattern, *found;
94+ size_t i;
95+
96+ for (i = 0; i < countof(patterns); ++i) {
97+ xasprintf(&pattern, patterns[i], name);
98+ memset(&g, 0, sizeof(g));
99+ if (glob(pattern, 0, NULL, &g) == 0 && g.gl_pathc > 0 && fileexists(g.gl_pathv[0])) {
100+ found = xstrdup(g.gl_pathv[0]);
101+ globfree(&g);
102+ free(pattern);
103+ return found;
104+ }
105+ globfree(&g);
106+ free(pattern);
107+ }
108+ return NULL;
109+}
110+
111+static char *
112+resolvepath(const char *arg)
113+{
114+ char *path, *found;
115+
116+ if (fileexists(arg))
117+ return xstrdup(arg);
118+ if (!haspathsep(arg)) {
119+ path = joinpath(".", arg);
120+ if (fileexists(path))
121+ return path;
122+ free(path);
123+ found = searchsystem(arg);
124+ if (found)
125+ return found;
126+ }
127+ return xstrdup(arg);
128+}
129+
130+static void
131+print_description(const struct description *desc, const char *prefix)
132+{
133+ const char *line, *next;
134+
135+ if (desc->summary && desc->summary[0])
136+ printf("%s%s\n", prefix, desc->summary);
137+ if (!desc->text || !desc->text[0])
138+ return;
139+ line = desc->text;
140+ while (*line) {
141+ next = strchr(line, '\n');
142+ if (!next)
143+ next = line + strlen(line);
144+ printf("%s%.*s\n", prefix, (int)(next - line), line);
145+ line = *next ? next + 1 : next;
146+ }
147+}
148+
149+static void
150+print_protocol_summary(const struct protocol *proto)
151+{
152+ size_t i;
153+
154+ printf("%s\n\n", proto->name);
155+ print_description(&proto->desc, "");
156+ if ((proto->desc.summary && proto->desc.summary[0]) || (proto->desc.text && proto->desc.text[0]))
157+ printf("\n");
158+ printf("interfaces: %zu\n", proto->nifaces);
159+ printf("requests: %zu\n", proto->total_requests);
160+ printf("events: %zu\n", proto->total_events);
161+ printf("enums: %zu\n\n", proto->total_enums);
162+
163+ for (i = 0; i < proto->nifaces; ++i) {
164+ const struct interface *iface = &proto->ifaces[i];
165+
166+ printf("%-18s v%-2d requests=%-3zu events=%-2zu creates=%-2zu",
167+ iface->name, iface->version, iface->nrequests, iface->nevents, iface->ncreates);
168+ if (!iface->created_by_local)
169+ printf(" root");
170+ putchar('\n');
171+ }
172+}
173+
174+static void
175+print_arg(const struct argument *arg)
176+{
177+ if (arg->nullable)
178+ putchar('?');
179+ if (arg->iface && arg->iface[0] && (strcmp(arg->type, "object") == 0 || strcmp(arg->type, "new_id") == 0))
180+ printf("%s", arg->iface);
181+ else
182+ printf("%s", arg->type);
183+}
184+
185+static void
186+print_args(const struct message *msg)
187+{
188+ size_t i;
189+ bool first;
190+
191+ first = true;
192+ for (i = 0; i < msg->nargs; ++i) {
193+ const struct argument *arg = &msg->args[i];
194+
195+ if (arg->new_id && arg->iface && arg->iface[0])
196+ continue;
197+ if (!first)
198+ printf(", ");
199+ print_arg(arg);
200+ first = false;
201+ }
202+}
203+
204+static void
205+print_message(const struct message *msg)
206+{
207+ const struct argument *creator;
208+
209+ printf(" %d %s(", msg->opcode, msg->name);
210+ print_args(msg);
211+ printf(")");
212+ creator = message_creator_arg(msg);
213+ if (creator)
214+ printf(" => %s", creator->iface);
215+ if (msg->destructor)
216+ printf(" [destructor]");
217+ putchar('\n');
218+ print_description(&msg->desc, " ");
219+}
220+
221+static void
222+print_interface_summary(const struct protocol *proto, const struct interface *iface)
223+{
224+ size_t i;
225+
226+ (void)proto;
227+ printf("interface %s@%d\n\n", iface->name, iface->version);
228+ print_description(&iface->desc, "");
229+ if ((iface->desc.summary && iface->desc.summary[0]) || (iface->desc.text && iface->desc.text[0]))
230+ printf("\n");
231+
232+ printf("requests\n");
233+ for (i = 0; i < iface->nrequests; ++i)
234+ print_message(&iface->requests[i]);
235+
236+ printf("\n");
237+ printf("events\n");
238+ for (i = 0; i < iface->nevents; ++i)
239+ print_message(&iface->events[i]);
240+
241+ printf("\n");
242+ printf("enums\n");
243+ for (i = 0; i < iface->nenums; ++i)
244+ {
245+ printf(" %s\n", iface->enums[i].name);
246+ print_description(&iface->enums[i].desc, " ");
247+ }
248+}
249+
250+int
251+main(int argc, char *argv[])
252+{
253+ struct protocol proto;
254+ struct interface *iface;
255+ const char *ifname;
256+ char *path;
257+ int i;
258+
259+ memset(&proto, 0, sizeof(proto));
260+ argv0 = progname(argv[0], "proto");
261+ path = NULL;
262+ ifname = NULL;
263+
264+ for (i = 1; i < argc; ++i) {
265+ if (strcmp(argv[i], "-h") == 0) {
266+ usage(stdout);
267+ return 0;
268+ }
269+ if (argv[i][0] == '-' && argv[i][1] != '\0') {
270+ usage(stderr);
271+ return 2;
272+ }
273+ if (!path) {
274+ path = argv[i];
275+ continue;
276+ }
277+ if (!ifname) {
278+ ifname = argv[i];
279+ continue;
280+ }
281+ usage(stderr);
282+ return 2;
283+ }
284+
285+ if (!path) {
286+ usage(stderr);
287+ return 2;
288+ }
289+
290+ path = resolvepath(path);
291+ parsefile(path, &proto);
292+
293+ iface = NULL;
294+ if (ifname) {
295+ iface = proto_find_interface(&proto, ifname);
296+ if (!iface)
297+ die("unknown interface: %s", ifname);
298+ }
299+
300+ if (iface)
301+ print_interface_summary(&proto, iface);
302+ else
303+ print_protocol_summary(&proto);
304+
305+ free(path);
306+ proto_free(&proto);
307+ return 0;
308+}
+197,
-0
1@@ -0,0 +1,197 @@
2+#include <stdlib.h>
3+#include <string.h>
4+
5+#include "protocol.h"
6+#include "util.h"
7+
8+static void free_message(struct message *);
9+static void free_description(struct description *);
10+
11+struct interface *
12+proto_add_interface(struct protocol *proto, const char *name, int version)
13+{
14+ struct interface *iface;
15+
16+ if (proto->nifaces == proto->ifacecap) {
17+ proto->ifacecap = proto->ifacecap ? proto->ifacecap * 2 : 8;
18+ proto->ifaces = xreallocarray(proto->ifaces, proto->ifacecap, sizeof(proto->ifaces[0]));
19+ }
20+ iface = &proto->ifaces[proto->nifaces++];
21+ memset(iface, 0, sizeof(*iface));
22+ iface->name = xstrdup(name);
23+ iface->version = version;
24+ return iface;
25+}
26+
27+struct interface *
28+proto_find_interface(const struct protocol *proto, const char *name)
29+{
30+ size_t i;
31+
32+ for (i = 0; i < proto->nifaces; ++i) {
33+ if (strcmp(proto->ifaces[i].name, name) == 0)
34+ return &proto->ifaces[i];
35+ }
36+ return NULL;
37+}
38+
39+struct message *
40+iface_add_message(struct interface *iface, enum message_dir dir, const char *name)
41+{
42+ struct message *msg;
43+ struct message **list;
44+ size_t *nlist, *cap;
45+
46+ if (dir == MSG_REQUEST) {
47+ list = &iface->requests;
48+ nlist = &iface->nrequests;
49+ cap = &iface->requestcap;
50+ } else {
51+ list = &iface->events;
52+ nlist = &iface->nevents;
53+ cap = &iface->eventcap;
54+ }
55+ if (*nlist == *cap) {
56+ *cap = *cap ? *cap * 2 : 8;
57+ *list = xreallocarray(*list, *cap, sizeof((*list)[0]));
58+ }
59+ msg = &(*list)[(*nlist)++];
60+ memset(msg, 0, sizeof(*msg));
61+ msg->name = xstrdup(name);
62+ msg->dir = dir;
63+ msg->opcode = (int)(*nlist - 1);
64+ return msg;
65+}
66+
67+void
68+message_add_arg(struct message *msg, const char *name, const char *type, const char *iface, bool nullable)
69+{
70+ struct argument *arg;
71+
72+ if (msg->nargs == msg->argcap) {
73+ msg->argcap = msg->argcap ? msg->argcap * 2 : 8;
74+ msg->args = xreallocarray(msg->args, msg->argcap, sizeof(msg->args[0]));
75+ }
76+ arg = &msg->args[msg->nargs++];
77+ memset(arg, 0, sizeof(*arg));
78+ arg->name = xstrdup(name);
79+ arg->type = xstrdup(type);
80+ arg->iface = iface ? xstrdup(iface) : NULL;
81+ arg->nullable = nullable;
82+ arg->new_id = strcmp(type, "new_id") == 0;
83+}
84+
85+void
86+iface_add_enum(struct interface *iface, const char *name)
87+{
88+ if (iface->nenums == iface->enumcap) {
89+ iface->enumcap = iface->enumcap ? iface->enumcap * 2 : 4;
90+ iface->enums = xreallocarray(iface->enums, iface->enumcap, sizeof(iface->enums[0]));
91+ }
92+ memset(&iface->enums[iface->nenums], 0, sizeof(iface->enums[0]));
93+ iface->enums[iface->nenums++].name = xstrdup(name);
94+}
95+
96+struct enumdef *
97+iface_last_enum(struct interface *iface)
98+{
99+ if (iface->nenums == 0)
100+ return NULL;
101+ return &iface->enums[iface->nenums - 1];
102+}
103+
104+const struct argument *
105+message_creator_arg(const struct message *msg)
106+{
107+ size_t i;
108+
109+ for (i = 0; i < msg->nargs; ++i) {
110+ if (msg->args[i].new_id && msg->args[i].iface && msg->args[i].iface[0])
111+ return &msg->args[i];
112+ }
113+ return NULL;
114+}
115+
116+void
117+proto_resolve(struct protocol *proto)
118+{
119+ size_t i, j;
120+ struct interface *target;
121+ const struct argument *arg;
122+
123+ for (i = 0; i < proto->nifaces; ++i) {
124+ proto->ifaces[i].created_by_local = false;
125+ proto->ifaces[i].ncreates = 0;
126+ }
127+ proto->total_requests = 0;
128+ proto->total_events = 0;
129+ proto->total_enums = 0;
130+
131+ for (i = 0; i < proto->nifaces; ++i) {
132+ struct interface *iface = &proto->ifaces[i];
133+
134+ proto->total_requests += iface->nrequests;
135+ proto->total_events += iface->nevents;
136+ proto->total_enums += iface->nenums;
137+ for (j = 0; j < iface->nrequests; ++j) {
138+ arg = message_creator_arg(&iface->requests[j]);
139+ if (!arg)
140+ continue;
141+ ++iface->ncreates;
142+ target = proto_find_interface(proto, arg->iface);
143+ if (target)
144+ target->created_by_local = true;
145+ }
146+ }
147+}
148+
149+static void
150+free_description(struct description *desc)
151+{
152+ free(desc->summary);
153+ free(desc->text);
154+}
155+
156+static void
157+free_message(struct message *msg)
158+{
159+ size_t i;
160+
161+ free(msg->name);
162+ free_description(&msg->desc);
163+ for (i = 0; i < msg->nargs; ++i) {
164+ free(msg->args[i].name);
165+ free(msg->args[i].type);
166+ free(msg->args[i].iface);
167+ }
168+ free(msg->args);
169+}
170+
171+void
172+proto_free(struct protocol *proto)
173+{
174+ size_t i, j;
175+
176+ free(proto->name);
177+ free_description(&proto->desc);
178+ for (i = 0; i < proto->nifaces; ++i) {
179+ struct interface *iface = &proto->ifaces[i];
180+
181+ free(iface->name);
182+ free_description(&iface->desc);
183+ for (j = 0; j < iface->nrequests; ++j)
184+ free_message(&iface->requests[j]);
185+ for (j = 0; j < iface->nevents; ++j)
186+ free_message(&iface->events[j]);
187+ for (j = 0; j < iface->nenums; ++j)
188+ {
189+ free(iface->enums[j].name);
190+ free_description(&iface->enums[j].desc);
191+ }
192+ free(iface->requests);
193+ free(iface->events);
194+ free(iface->enums);
195+ }
196+ free(proto->ifaces);
197+ memset(proto, 0, sizeof(*proto));
198+}
+80,
-0
1@@ -0,0 +1,80 @@
2+#ifndef PROTOCOL_H
3+#define PROTOCOL_H
4+
5+#include <stdbool.h>
6+#include <stddef.h>
7+
8+enum message_dir {
9+ MSG_REQUEST,
10+ MSG_EVENT,
11+};
12+
13+struct argument {
14+ char *name;
15+ char *type;
16+ char *iface;
17+ bool nullable;
18+ bool new_id;
19+};
20+
21+struct description {
22+ char *summary;
23+ char *text;
24+};
25+
26+struct enumdef {
27+ char *name;
28+ struct description desc;
29+};
30+
31+struct message {
32+ char *name;
33+ enum message_dir dir;
34+ int opcode;
35+ bool destructor;
36+ struct description desc;
37+ struct argument *args;
38+ size_t nargs;
39+ size_t argcap;
40+};
41+
42+struct interface {
43+ char *name;
44+ int version;
45+ struct description desc;
46+ struct message *requests;
47+ size_t nrequests;
48+ size_t requestcap;
49+ struct message *events;
50+ size_t nevents;
51+ size_t eventcap;
52+ struct enumdef *enums;
53+ size_t nenums;
54+ size_t enumcap;
55+ bool created_by_local;
56+ size_t ncreates;
57+};
58+
59+struct protocol {
60+ char *name;
61+ struct description desc;
62+ struct interface *ifaces;
63+ size_t nifaces;
64+ size_t ifacecap;
65+ size_t total_requests;
66+ size_t total_events;
67+ size_t total_enums;
68+};
69+
70+struct interface *proto_add_interface(struct protocol *, const char *, int);
71+struct interface *proto_find_interface(const struct protocol *, const char *);
72+struct message *iface_add_message(struct interface *, enum message_dir, const char *);
73+void message_add_arg(struct message *, const char *, const char *, const char *, bool);
74+void iface_add_enum(struct interface *, const char *);
75+struct enumdef *iface_last_enum(struct interface *);
76+void proto_resolve(struct protocol *);
77+void proto_free(struct protocol *);
78+
79+const struct argument *message_creator_arg(const struct message *);
80+
81+#endif
+18,
-0
1@@ -0,0 +1,18 @@
2+proto
3+-----
4+
5+proto is a small command-line tool for inspecting wayland protocol xml files in a nice human-readable way. i was kinda sick of having a browser open just for browsing wayland.app. it searches your system paths, so you can do stuff like 'proto xdg-shell' or 'proto wayland' and it knows where to look. it can also read local files, but you need to give it the path. you can also provide an interface as an argument, and it will give you the rundown on that.
6+
7+build:
8+ ninja
9+ ninja install
10+
11+examples
12+ proto xdg-shell
13+ proto xdg-shell xdg_wm_base
14+ proto files/my-local-wayland-protocol.xml
15+
16+dependencies:
17+ C compiler
18+ expat
19+ ninja-compatible build tool
+131,
-0
1@@ -0,0 +1,131 @@
2+#include <errno.h>
3+#include <stdarg.h>
4+#include <stdbool.h>
5+#include <stdint.h>
6+#include <stdio.h>
7+#include <stdlib.h>
8+#include <string.h>
9+
10+#include "util.h"
11+
12+extern const char *argv0;
13+
14+static void
15+vwarn(const char *fmt, va_list ap)
16+{
17+ fprintf(stderr, "%s: ", argv0);
18+ vfprintf(stderr, fmt, ap);
19+ if (fmt[0] && fmt[strlen(fmt) - 1] == ':') {
20+ putc(' ', stderr);
21+ perror(NULL);
22+ } else {
23+ putc('\n', stderr);
24+ }
25+}
26+
27+void
28+warn(const char *fmt, ...)
29+{
30+ va_list ap;
31+
32+ va_start(ap, fmt);
33+ vwarn(fmt, ap);
34+ va_end(ap);
35+}
36+
37+void
38+die(const char *fmt, ...)
39+{
40+ va_list ap;
41+
42+ va_start(ap, fmt);
43+ vwarn(fmt, ap);
44+ va_end(ap);
45+ exit(1);
46+}
47+
48+void *
49+xmalloc(size_t n)
50+{
51+ void *p;
52+
53+ p = malloc(n ? n : 1);
54+ if (!p)
55+ die("malloc:");
56+ return p;
57+}
58+
59+void *
60+xcalloc(size_t n, size_t m)
61+{
62+ void *p;
63+
64+ p = calloc(n, m);
65+ if (!p)
66+ die("calloc:");
67+ return p;
68+}
69+
70+static void *
71+reallocarray_(void *p, size_t n, size_t m)
72+{
73+ if (m && n > SIZE_MAX / m) {
74+ errno = ENOMEM;
75+ return NULL;
76+ }
77+ return realloc(p, n * m);
78+}
79+
80+void *
81+xreallocarray(void *p, size_t n, size_t m)
82+{
83+ p = reallocarray_(p, n, m);
84+ if (!p)
85+ die("reallocarray:");
86+ return p;
87+}
88+
89+char *
90+xstrdup(const char *s)
91+{
92+ size_t n;
93+ char *p;
94+
95+ n = strlen(s) + 1;
96+ p = xmalloc(n);
97+ memcpy(p, s, n);
98+ return p;
99+}
100+
101+char *
102+xmemdup0(const char *s, size_t n)
103+{
104+ char *p;
105+
106+ p = xmalloc(n + 1);
107+ memcpy(p, s, n);
108+ p[n] = '\0';
109+ return p;
110+}
111+
112+int
113+xasprintf(char **s, const char *fmt, ...)
114+{
115+ va_list ap;
116+ int ret;
117+ size_t n;
118+
119+ va_start(ap, fmt);
120+ ret = vsnprintf(NULL, 0, fmt, ap);
121+ va_end(ap);
122+ if (ret < 0)
123+ die("vsnprintf:");
124+ n = (size_t)ret + 1;
125+ *s = xmalloc(n);
126+ va_start(ap, fmt);
127+ ret = vsnprintf(*s, n, fmt, ap);
128+ va_end(ap);
129+ if (ret < 0 || (size_t)ret >= n)
130+ die("vsnprintf:");
131+ return ret;
132+}
+20,
-0
1@@ -0,0 +1,20 @@
2+#ifndef UTIL_H
3+#define UTIL_H
4+
5+#include <stddef.h>
6+
7+#ifndef countof
8+#define countof(a) (sizeof(a) / sizeof((a)[0]))
9+#endif
10+
11+void warn(const char *, ...);
12+void die(const char *, ...);
13+
14+void *xmalloc(size_t);
15+void *xcalloc(size_t, size_t);
16+void *xreallocarray(void *, size_t, size_t);
17+int xasprintf(char **, const char *, ...);
18+char *xstrdup(const char *);
19+char *xmemdup0(const char *, size_t);
20+
21+#endif
A
readme
+33,
-0
1@@ -0,0 +1,33 @@
2+shrubtools
3+----------
4+
5+this is a small collection of utilities i wrote and use quite often. they are useful to me, and i am sharing them in the hope they will be useful to you too. some of them are posix shell, and some are in C. some are really useful, and some are not that useful.
6+
7+the ones in C have their own subdirectories with ninja to build and install. for the scripts, just copy them to your path.
8+
9+tools
10+-----
11+
12+- drudge : fetch and format drudge report headlines.
13+
14+- fine : tiny command benchmarker.
15+
16+- flick : page through a list of files from stdin.
17+
18+- gen : generate a simple and flexible build.ninja for small C programs.
19+
20+- life : conway's game of life in the terminal.
21+
22+- lint : clang wrapper that prints warnings in the style of sun microsystem's lint.
23+
24+- pinkie : finger as a shell script.
25+
26+- tlsver : check what tls versions a host supports.
27+
28+- nviz : inspect build.ninja and print dependency graphs, to the terminal or graphviz.
29+
30+- proto : inspect wayland protocol xml files in a human-readable way.
31+
32+- peek : simple command tracer.
33+
34+
A
tlsver
+43,
-0
1@@ -0,0 +1,43 @@
2+#!/bin/sh
3+# check what tls versions a given hosts support, using brssl cli. doesn't support tls 1.3, but
4+# that's mostly fine, if a site doesn't support all 3, you can assume it's using tls 1.3 most
5+# of the time. this is mostly to check why some site is bugging under bearssl, usually it's
6+# because they force tls 1.3 :(
7+#
8+# dependencies:
9+# shell
10+# brssl cli from bearssl
11+
12+set -eu
13+
14+usage() {
15+ printf 'usage: %s host[:port]\n' "${0##*/}" >&2
16+ exit 2
17+}
18+
19+[ "$#" -eq 1 ] || usage
20+
21+target=$1
22+
23+case $target in
24+*:[0-9]*)
25+ ;;
26+ *)
27+ target=$target:443
28+ ;;
29+esac
30+
31+for ver in tls10 tls11 tls12; do
32+ out=$(
33+ brssl client "$target" -vmin "$ver" -vmax "$ver" </dev/null 2>&1 || true
34+ )
35+
36+ case $out in
37+ *"Handshake completed"*)
38+ printf '%s yes\n' "$ver"
39+ ;;
40+ *)
41+ printf '%s no\n' "$ver"
42+ ;;
43+ esac
44+done