summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--bin/.gitignore36
-rw-r--r--bin/LICENSE661
-rw-r--r--bin/Makefile153
-rw-r--r--bin/README.783
-rw-r--r--bin/beef.c141
-rw-r--r--bin/bibsort.pl66
-rw-r--r--bin/bit.y194
-rw-r--r--bin/bri.c85
-rw-r--r--bin/c.sh87
-rw-r--r--bin/dash/.gitignore42
-rw-r--r--bin/dash/COPYING56
-rw-r--r--bin/dash/ChangeLog806
-rw-r--r--bin/dash/ChangeLog.O1023
-rw-r--r--bin/dash/Makefile.am1
-rwxr-xr-xbin/dash/autogen.sh6
-rw-r--r--bin/dash/configure.ac194
-rw-r--r--bin/dash/src/.gitignore13
-rw-r--r--bin/dash/src/Makefile.am70
-rw-r--r--bin/dash/src/TOUR343
-rw-r--r--bin/dash/src/alias.c227
-rw-r--r--bin/dash/src/alias.h52
-rw-r--r--bin/dash/src/arith_yacc.c304
-rw-r--r--bin/dash/src/arith_yacc.h89
-rw-r--r--bin/dash/src/arith_yylex.c240
-rw-r--r--bin/dash/src/bltin/bltin.h89
-rw-r--r--bin/dash/src/bltin/echo.1109
-rw-r--r--bin/dash/src/bltin/printf.1354
-rw-r--r--bin/dash/src/bltin/printf.c478
-rw-r--r--bin/dash/src/bltin/test.1309
-rw-r--r--bin/dash/src/bltin/test.c700
-rw-r--r--bin/dash/src/bltin/times.c42
-rw-r--r--bin/dash/src/builtins.def.in94
-rw-r--r--bin/dash/src/cd.c332
-rw-r--r--bin/dash/src/cd.h35
-rw-r--r--bin/dash/src/dash.12361
-rw-r--r--bin/dash/src/error.c238
-rw-r--r--bin/dash/src/error.h129
-rw-r--r--bin/dash/src/eval.c1147
-rw-r--r--bin/dash/src/eval.h65
-rw-r--r--bin/dash/src/exec.c908
-rw-r--r--bin/dash/src/exec.h84
-rw-r--r--bin/dash/src/expand.c1753
-rw-r--r--bin/dash/src/expand.h83
-rw-r--r--bin/dash/src/funcs/cmv47
-rw-r--r--bin/dash/src/funcs/dirs71
-rw-r--r--bin/dash/src/funcs/kill47
-rw-r--r--bin/dash/src/funcs/login36
-rw-r--r--bin/dash/src/funcs/newgrp35
-rw-r--r--bin/dash/src/funcs/popd71
-rw-r--r--bin/dash/src/funcs/pushd71
-rw-r--r--bin/dash/src/funcs/suspend39
-rw-r--r--bin/dash/src/histedit.c503
-rw-r--r--bin/dash/src/init.h40
-rw-r--r--bin/dash/src/input.c498
-rw-r--r--bin/dash/src/input.h101
-rw-r--r--bin/dash/src/jobs.c1545
-rw-r--r--bin/dash/src/jobs.h113
-rw-r--r--bin/dash/src/machdep.h47
-rw-r--r--bin/dash/src/mail.c115
-rw-r--r--bin/dash/src/mail.h38
-rw-r--r--bin/dash/src/main.c363
-rw-r--r--bin/dash/src/main.h54
-rw-r--r--bin/dash/src/memalloc.c300
-rw-r--r--bin/dash/src/memalloc.h101
-rw-r--r--bin/dash/src/miscbltin.c506
-rw-r--r--bin/dash/src/miscbltin.h31
-rw-r--r--bin/dash/src/mkbuiltins120
-rw-r--r--bin/dash/src/mkinit.c493
-rw-r--r--bin/dash/src/mknodes.c448
-rw-r--r--bin/dash/src/mksignames.c417
-rw-r--r--bin/dash/src/mksyntax.c315
-rw-r--r--bin/dash/src/mktokens97
-rw-r--r--bin/dash/src/myhistedit.h46
-rw-r--r--bin/dash/src/mystring.c257
-rw-r--r--bin/dash/src/mystring.h63
-rw-r--r--bin/dash/src/nodes.c.pat166
-rw-r--r--bin/dash/src/nodetypes151
-rw-r--r--bin/dash/src/options.c558
-rw-r--r--bin/dash/src/options.h86
-rw-r--r--bin/dash/src/output.c429
-rw-r--r--bin/dash/src/output.h126
-rw-r--r--bin/dash/src/parser.c1677
-rw-r--r--bin/dash/src/parser.h103
-rw-r--r--bin/dash/src/redir.c483
-rw-r--r--bin/dash/src/redir.h52
-rw-r--r--bin/dash/src/shell.h104
-rw-r--r--bin/dash/src/show.c406
-rw-r--r--bin/dash/src/show.h45
-rw-r--r--bin/dash/src/system.c199
-rw-r--r--bin/dash/src/system.h118
-rw-r--r--bin/dash/src/trap.c446
-rw-r--r--bin/dash/src/trap.h57
-rw-r--r--bin/dash/src/var.c676
-rw-r--r--bin/dash/src/var.h173
-rw-r--r--bin/dtch.c271
-rw-r--r--bin/ever.c116
-rw-r--r--bin/fbatt.c123
-rw-r--r--bin/fbclock.c132
-rw-r--r--bin/glitch.c538
-rw-r--r--bin/hi.c766
-rw-r--r--bin/hnel.c120
-rw-r--r--bin/html.sh41
-rw-r--r--bin/man1/beef.191
-rw-r--r--bin/man1/bibsort.137
-rw-r--r--bin/man1/bit.145
-rw-r--r--bin/man1/bri.144
-rw-r--r--bin/man1/c.140
-rw-r--r--bin/man1/dtch.167
-rw-r--r--bin/man1/ever.149
-rw-r--r--bin/man1/fbatt.134
-rw-r--r--bin/man1/fbclock.136
-rw-r--r--bin/man1/glitch.177
-rw-r--r--bin/man1/hi.1173
-rw-r--r--bin/man1/hnel.136
-rw-r--r--bin/man1/modem.131
-rw-r--r--bin/man1/nudge.144
-rw-r--r--bin/man1/order.138
-rw-r--r--bin/man1/pbd.159
-rw-r--r--bin/man1/pngo.156
-rw-r--r--bin/man1/psf2png.153
-rw-r--r--bin/man1/psfed.1166
-rw-r--r--bin/man1/ptee.140
-rw-r--r--bin/man1/relay.148
-rw-r--r--bin/man1/scheme.156
-rw-r--r--bin/man1/shotty.192
-rw-r--r--bin/man1/title.151
-rw-r--r--bin/man1/ttpre.132
-rw-r--r--bin/man1/up.176
-rw-r--r--bin/man1/when.176
-rw-r--r--bin/man1/xx.168
-rw-r--r--bin/man3/png.390
-rw-r--r--bin/modem.c102
-rw-r--r--bin/nudge.c78
-rw-r--r--bin/order.y193
-rw-r--r--bin/pbd.c152
-rw-r--r--bin/png.h108
-rw-r--r--bin/pngo.c812
-rw-r--r--bin/psf2png.c107
-rw-r--r--bin/psfed.c577
-rw-r--r--bin/ptee.c116
-rw-r--r--bin/relay.c218
-rw-r--r--bin/scheme.c256
-rw-r--r--bin/shotty.c648
-rw-r--r--bin/title.c211
-rw-r--r--bin/ttpre.c66
-rw-r--r--bin/up.sh76
-rw-r--r--bin/when.y250
-rw-r--r--bin/xx.c142
-rw-r--r--doc/pdf/.gitignore1
-rw-r--r--doc/pdf/Makefile31
-rw-r--r--doc/rfc/.gitignore3
-rw-r--r--doc/rfc/Makefile21
-rw-r--r--doc/rfc/rfc.vim30
-rw-r--r--doc/rfc/rfctags.pl28
-rw-r--r--doc/zlib/Makefile (renamed from Makefile)0
-rw-r--r--doc/zlib/adler32.3 (renamed from adler32.3)0
-rw-r--r--doc/zlib/adler32_combine.3 (renamed from adler32_combine.3)0
-rw-r--r--doc/zlib/compress.3 (renamed from compress.3)0
-rw-r--r--doc/zlib/compressBound.3 (renamed from compressBound.3)0
-rw-r--r--doc/zlib/crc32.3 (renamed from crc32.3)0
-rw-r--r--doc/zlib/crc32_combine.3 (renamed from crc32_combine.3)0
-rw-r--r--doc/zlib/deflate.3 (renamed from deflate.3)0
-rw-r--r--doc/zlib/deflateBound.3 (renamed from deflateBound.3)0
-rw-r--r--doc/zlib/deflateCopy.3 (renamed from deflateCopy.3)0
-rw-r--r--doc/zlib/deflateEnd.3 (renamed from deflateEnd.3)0
-rw-r--r--doc/zlib/deflateGetDictionary.3 (renamed from deflateGetDictionary.3)0
-rw-r--r--doc/zlib/deflateInit.3 (renamed from deflateInit.3)0
-rw-r--r--doc/zlib/deflateInit2.3 (renamed from deflateInit2.3)0
-rw-r--r--doc/zlib/deflateParams.3 (renamed from deflateParams.3)0
-rw-r--r--doc/zlib/deflatePending.3 (renamed from deflatePending.3)0
-rw-r--r--doc/zlib/deflatePrime.3 (renamed from deflatePrime.3)0
-rw-r--r--doc/zlib/deflateReset.3 (renamed from deflateReset.3)0
-rw-r--r--doc/zlib/deflateSetDictionary.3 (renamed from deflateSetDictionary.3)0
-rw-r--r--doc/zlib/deflateSetHeader.3 (renamed from deflateSetHeader.3)0
-rw-r--r--doc/zlib/deflateTune.3 (renamed from deflateTune.3)0
-rw-r--r--doc/zlib/gzbuffer.3 (renamed from gzbuffer.3)0
-rw-r--r--doc/zlib/gzclose.3 (renamed from gzclose.3)0
-rw-r--r--doc/zlib/gzdirect.3 (renamed from gzdirect.3)0
-rw-r--r--doc/zlib/gzeof.3 (renamed from gzeof.3)0
-rw-r--r--doc/zlib/gzerror.3 (renamed from gzerror.3)0
-rw-r--r--doc/zlib/gzflush.3 (renamed from gzflush.3)0
-rw-r--r--doc/zlib/gzfread.3 (renamed from gzfread.3)0
-rw-r--r--doc/zlib/gzfwrite.3 (renamed from gzfwrite.3)0
-rw-r--r--doc/zlib/gzgetc.3 (renamed from gzgetc.3)0
-rw-r--r--doc/zlib/gzgets.3 (renamed from gzgets.3)0
-rw-r--r--doc/zlib/gzoffset.3 (renamed from gzoffset.3)0
-rw-r--r--doc/zlib/gzopen.3 (renamed from gzopen.3)0
-rw-r--r--doc/zlib/gzprintf.3 (renamed from gzprintf.3)0
-rw-r--r--doc/zlib/gzputc.3 (renamed from gzputc.3)0
-rw-r--r--doc/zlib/gzputs.3 (renamed from gzputs.3)0
-rw-r--r--doc/zlib/gzread.3 (renamed from gzread.3)0
-rw-r--r--doc/zlib/gzseek.3 (renamed from gzseek.3)0
-rw-r--r--doc/zlib/gzsetparams.3 (renamed from gzsetparams.3)0
-rw-r--r--doc/zlib/gzungetc.3 (renamed from gzungetc.3)0
-rw-r--r--doc/zlib/gzwrite.3 (renamed from gzwrite.3)0
-rw-r--r--doc/zlib/inflate.3 (renamed from inflate.3)0
-rw-r--r--doc/zlib/inflateBack.3 (renamed from inflateBack.3)0
-rw-r--r--doc/zlib/inflateBackEnd.3 (renamed from inflateBackEnd.3)0
-rw-r--r--doc/zlib/inflateBackInit.3 (renamed from inflateBackInit.3)0
-rw-r--r--doc/zlib/inflateCopy.3 (renamed from inflateCopy.3)0
-rw-r--r--doc/zlib/inflateEnd.3 (renamed from inflateEnd.3)0
-rw-r--r--doc/zlib/inflateGetDictionary.3 (renamed from inflateGetDictionary.3)0
-rw-r--r--doc/zlib/inflateGetHeader.3 (renamed from inflateGetHeader.3)0
-rw-r--r--doc/zlib/inflateInit.3 (renamed from inflateInit.3)0
-rw-r--r--doc/zlib/inflateInit2.3 (renamed from inflateInit2.3)0
-rw-r--r--doc/zlib/inflateMark.3 (renamed from inflateMark.3)0
-rw-r--r--doc/zlib/inflatePrime.3 (renamed from inflatePrime.3)0
-rw-r--r--doc/zlib/inflateReset.3 (renamed from inflateReset.3)0
-rw-r--r--doc/zlib/inflateSetDictionary.3 (renamed from inflateSetDictionary.3)0
-rw-r--r--doc/zlib/inflateSync.3 (renamed from inflateSync.3)0
-rw-r--r--doc/zlib/uncompress.3 (renamed from uncompress.3)0
-rw-r--r--doc/zlib/zlibCompileFlags.3 (renamed from zlibCompileFlags.3)0
-rw-r--r--doc/zlib/zlibVersion.3 (renamed from zlibVersion.3)0
-rw-r--r--etc/CodeQWERTY.bundle/Contents/Info.plist19
-rw-r--r--etc/CodeQWERTY.bundle/Contents/Resources/CodeQWERTY.keylayout1178
-rw-r--r--etc/Dark.terminal1684
-rw-r--r--etc/Go-Mono-Bold-Italic.ttfbin0 -> 176832 bytes
-rw-r--r--etc/Go-Mono-Bold.ttfbin0 -> 168340 bytes
-rw-r--r--etc/Go-Mono-Italic.ttfbin0 -> 173548 bytes
-rw-r--r--etc/Go-Mono.ttfbin0 -> 164200 bytes
-rw-r--r--etc/README.Go-Mono36
-rw-r--r--etc/agpl.c20
-rw-r--r--etc/code.map20
-rw-r--r--etc/gpl.c20
-rw-r--r--etc/psf/.gitignore2
-rw-r--r--etc/psf/Makefile24
-rw-r--r--etc/psf/default.u259
-rw-r--r--etc/psf/sans6x10.psfbin0 -> 2592 bytes
-rw-r--r--etc/psf/sans6x12.psfbin0 -> 3104 bytes
-rw-r--r--etc/psf/sans6x8.psfbin0 -> 2080 bytes
-rw-r--r--etc/samba_mdns26
-rw-r--r--home/.config/git/config21
-rw-r--r--home/.config/git/ignore2
-rw-r--r--home/.config/htop/htoprc29
-rw-r--r--home/.config/nvim/colors/trivial.vim65
-rw-r--r--home/.config/nvim/ftdetect/mdoc.vim1
-rw-r--r--home/.config/nvim/init.vim37
-rw-r--r--home/.config/nvim/syntax/mdoc.vim12
-rw-r--r--home/.editrc1
-rw-r--r--home/.gdbinit1
-rw-r--r--home/.hushlogin0
-rw-r--r--home/.inputrc1
-rw-r--r--home/.lldbinit1
-rwxr-xr-xhome/.local/bin/aes7
-rwxr-xr-xhome/.local/bin/def47
-rwxr-xr-xhome/.local/bin/git-password7
-rwxr-xr-xhome/.local/bin/nasd9
-rwxr-xr-xhome/.local/bin/notify-send9
-rwxr-xr-xhome/.local/bin/np7
-rwxr-xr-xhome/.local/bin/versions9
-rw-r--r--home/.profile21
-rw-r--r--home/.shrc49
-rw-r--r--home/.ssh/config18
-rw-r--r--install.sh43
-rw-r--r--link.sh23
-rw-r--r--port/caesar/.gitignore2
-rw-r--r--port/caesar/Makefile19
-rw-r--r--port/caesar/caesar.673
-rw-r--r--port/caesar/caesar.c159
-rw-r--r--port/caesar/rot13.sh33
-rw-r--r--port/cgram/.gitignore1
-rw-r--r--port/cgram/Makefile17
-rw-r--r--port/cgram/cgram.665
-rw-r--r--port/cgram/cgram.c344
-rw-r--r--port/cgram/pathnames.h30
-rw-r--r--port/file2c/.gitignore1
-rw-r--r--port/file2c/Makefile15
-rw-r--r--port/file2c/file2c.175
-rw-r--r--port/file2c/file2c.c92
-rw-r--r--port/wcwidth/.gitignore3
-rw-r--r--port/wcwidth/COPYRIGHT190
-rw-r--r--port/wcwidth/Makefile27
-rw-r--r--port/wcwidth/nonspacing.h89
-rw-r--r--port/wcwidth/wcfix.in7
-rw-r--r--port/wcwidth/wcswidth.c8
-rw-r--r--port/wcwidth/wcwidth.c29
-rw-r--r--port/wcwidth/wide.h65
-rw-r--r--prune.sh7
-rw-r--r--txt/.notemap3
-rw-r--r--txt/books.txt114
-rw-r--r--txt/music.txt285
-rw-r--r--txt/plan.745
-rw-r--r--txt/shows.txt32
-rw-r--r--txt/trouble-at-jinx-hotel.txt236
-rw-r--r--txt/tweets.txt29
-rw-r--r--www/causal.agency/.gitignore3
-rw-r--r--www/causal.agency/Makefile31
-rw-r--r--www/causal.agency/catgirl.pty97
-rw-r--r--www/causal.agency/index.html.in125
-rw-r--r--www/causal.agency/index.sed5
-rw-r--r--www/causal.agency/play.pty23
-rw-r--r--www/causal.agency/scheme.pty10
-rw-r--r--www/causal.agency/torus.pty774
-rw-r--r--www/git.causal.agency/.gitignore3
-rw-r--r--www/git.causal.agency/Makefile18
-rw-r--r--www/git.causal.agency/about-filter.sh15
-rw-r--r--www/git.causal.agency/cgit/.gitignore12
-rw-r--r--www/git.causal.agency/cgit/.mailmap10
-rw-r--r--www/git.causal.agency/cgit/AUTHORS13
-rw-r--r--www/git.causal.agency/cgit/COPYING339
-rw-r--r--www/git.causal.agency/cgit/Makefile170
-rw-r--r--www/git.causal.agency/cgit/README99
-rw-r--r--www/git.causal.agency/cgit/cache.c468
-rw-r--r--www/git.causal.agency/cgit/cache.h37
-rw-r--r--www/git.causal.agency/cgit/cgit.c1112
-rw-r--r--www/git.causal.agency/cgit/cgit.css895
-rw-r--r--www/git.causal.agency/cgit/cgit.h398
-rw-r--r--www/git.causal.agency/cgit/cgit.mk141
-rw-r--r--www/git.causal.agency/cgit/cgit.pngbin0 -> 1366 bytes
-rw-r--r--www/git.causal.agency/cgit/cgitrc.5.txt1011
-rw-r--r--www/git.causal.agency/cgit/cmd.c208
-rw-r--r--www/git.causal.agency/cgit/cmd.h16
-rw-r--r--www/git.causal.agency/cgit/configfile.c90
-rw-r--r--www/git.causal.agency/cgit/configfile.h10
-rwxr-xr-xwww/git.causal.agency/cgit/contrib/hooks/post-receive.agefile19
-rw-r--r--www/git.causal.agency/cgit/favicon.icobin0 -> 1078 bytes
-rw-r--r--www/git.causal.agency/cgit/filter.c457
-rwxr-xr-xwww/git.causal.agency/cgit/filters/about-formatting.sh27
-rwxr-xr-xwww/git.causal.agency/cgit/filters/commit-links.sh28
-rw-r--r--www/git.causal.agency/cgit/filters/email-gravatar.lua35
-rwxr-xr-xwww/git.causal.agency/cgit/filters/email-gravatar.py39
-rw-r--r--www/git.causal.agency/cgit/filters/email-libravatar.lua36
-rw-r--r--www/git.causal.agency/cgit/filters/file-authentication.lua359
-rw-r--r--www/git.causal.agency/cgit/filters/gentoo-ldap-authentication.lua360
-rwxr-xr-xwww/git.causal.agency/cgit/filters/html-converters/man2html4
-rwxr-xr-xwww/git.causal.agency/cgit/filters/html-converters/md2html307
-rwxr-xr-xwww/git.causal.agency/cgit/filters/html-converters/rst2html2
-rwxr-xr-xwww/git.causal.agency/cgit/filters/html-converters/txt2html4
-rw-r--r--www/git.causal.agency/cgit/filters/owner-example.lua17
-rw-r--r--www/git.causal.agency/cgit/filters/simple-authentication.lua314
-rwxr-xr-xwww/git.causal.agency/cgit/filters/syntax-highlighting.py55
-rwxr-xr-xwww/git.causal.agency/cgit/filters/syntax-highlighting.sh121
-rwxr-xr-xwww/git.causal.agency/cgit/gen-version.sh20
-rw-r--r--www/git.causal.agency/cgit/html.c344
-rw-r--r--www/git.causal.agency/cgit/html.h37
-rw-r--r--www/git.causal.agency/cgit/parsing.c224
-rw-r--r--www/git.causal.agency/cgit/robots.txt4
-rw-r--r--www/git.causal.agency/cgit/scan-tree.c270
-rw-r--r--www/git.causal.agency/cgit/scan-tree.h2
-rw-r--r--www/git.causal.agency/cgit/shared.c579
-rw-r--r--www/git.causal.agency/cgit/tests/.gitignore2
-rw-r--r--www/git.causal.agency/cgit/tests/Makefile17
-rw-r--r--www/git.causal.agency/cgit/tests/filters/dump.lua17
-rwxr-xr-xwww/git.causal.agency/cgit/tests/filters/dump.sh4
-rwxr-xr-xwww/git.causal.agency/cgit/tests/setup.sh176
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0001-validate-git-versions.sh45
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0010-validate-html.sh40
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0020-validate-cache.sh78
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0101-index.sh17
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0102-summary.sh25
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0103-log.sh24
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0104-tree.sh32
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0105-commit.sh36
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0106-diff.sh19
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0107-snapshot.sh208
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0108-patch.sh62
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0109-gitconfig.sh48
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0110-rawdiff.sh42
-rwxr-xr-xwww/git.causal.agency/cgit/tests/t0111-filter.sh46
-rwxr-xr-xwww/git.causal.agency/cgit/tests/valgrind/bin/cgit12
-rw-r--r--www/git.causal.agency/cgit/ui-atom.c149
-rw-r--r--www/git.causal.agency/cgit/ui-atom.h6
-rw-r--r--www/git.causal.agency/cgit/ui-blame.c308
-rw-r--r--www/git.causal.agency/cgit/ui-blame.h6
-rw-r--r--www/git.causal.agency/cgit/ui-blob.c186
-rw-r--r--www/git.causal.agency/cgit/ui-blob.h8
-rw-r--r--www/git.causal.agency/cgit/ui-clone.c126
-rw-r--r--www/git.causal.agency/cgit/ui-clone.h8
-rw-r--r--www/git.causal.agency/cgit/ui-commit.c148
-rw-r--r--www/git.causal.agency/cgit/ui-commit.h6
-rw-r--r--www/git.causal.agency/cgit/ui-diff.c501
-rw-r--r--www/git.causal.agency/cgit/ui-diff.h15
-rw-r--r--www/git.causal.agency/cgit/ui-log.c550
-rw-r--r--www/git.causal.agency/cgit/ui-log.h9
-rw-r--r--www/git.causal.agency/cgit/ui-patch.c98
-rw-r--r--www/git.causal.agency/cgit/ui-patch.h7
-rw-r--r--www/git.causal.agency/cgit/ui-plain.c208
-rw-r--r--www/git.causal.agency/cgit/ui-plain.h6
-rw-r--r--www/git.causal.agency/cgit/ui-refs.c219
-rw-r--r--www/git.causal.agency/cgit/ui-refs.h8
-rw-r--r--www/git.causal.agency/cgit/ui-repolist.c379
-rw-r--r--www/git.causal.agency/cgit/ui-repolist.h7
-rw-r--r--www/git.causal.agency/cgit/ui-shared.c1225
-rw-r--r--www/git.causal.agency/cgit/ui-shared.h87
-rw-r--r--www/git.causal.agency/cgit/ui-snapshot.c316
-rw-r--r--www/git.causal.agency/cgit/ui-snapshot.h7
-rw-r--r--www/git.causal.agency/cgit/ui-ssdiff.c420
-rw-r--r--www/git.causal.agency/cgit/ui-ssdiff.h25
-rw-r--r--www/git.causal.agency/cgit/ui-stats.c426
-rw-r--r--www/git.causal.agency/cgit/ui-stats.h28
-rw-r--r--www/git.causal.agency/cgit/ui-summary.c148
-rw-r--r--www/git.causal.agency/cgit/ui-summary.h7
-rw-r--r--www/git.causal.agency/cgit/ui-tag.c120
-rw-r--r--www/git.causal.agency/cgit/ui-tag.h6
-rw-r--r--www/git.causal.agency/cgit/ui-tree.c410
-rw-r--r--www/git.causal.agency/cgit/ui-tree.h6
-rw-r--r--www/git.causal.agency/cgitrc27
-rw-r--r--www/git.causal.agency/custom.css87
-rw-r--r--www/git.causal.agency/source-filter.sh3
-rw-r--r--www/temp.causal.agency/.gitignore1
-rw-r--r--www/temp.causal.agency/Makefile16
-rw-r--r--www/temp.causal.agency/up.c156
-rw-r--r--www/text.causal.agency/.gitignore2
-rw-r--r--www/text.causal.agency/001-make.7159
-rw-r--r--www/text.causal.agency/002-writing-mdoc.7138
-rw-r--r--www/text.causal.agency/003-pleasant-c.7120
-rw-r--r--www/text.causal.agency/004-uloc.764
-rw-r--r--www/text.causal.agency/005-testing-c.773
-rw-r--r--www/text.causal.agency/006-some-libs.796
-rw-r--r--www/text.causal.agency/007-cgit-setup.7271
-rw-r--r--www/text.causal.agency/008-how-irc.7193
-rw-r--r--www/text.causal.agency/009-casual-update.7127
-rw-r--r--www/text.causal.agency/010-irc-suite.7409
-rw-r--r--www/text.causal.agency/011-libretls.7220
-rw-r--r--www/text.causal.agency/012-inability.739
-rw-r--r--www/text.causal.agency/013-hot-tips.7156
-rw-r--r--www/text.causal.agency/Makefile31
-rw-r--r--www/text.causal.agency/feed.sh55
419 files changed, 60689 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..3bcf7b3f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/build
+/clone
+/git
+/trash
diff --git a/bin/.gitignore b/bin/.gitignore
new file mode 100644
index 00000000..df322fd4
--- /dev/null
+++ b/bin/.gitignore
@@ -0,0 +1,36 @@
+*.html
+*.o
+beef
+bibsort
+bit
+bri
+c
+config.mk
+dtch
+ever
+fbatt
+fbclock
+glitch
+hi
+hnel
+modem
+nudge
+open
+order
+pbcopy
+pbd
+pbpaste
+pngo
+psf2png
+psfed
+ptee
+relay
+scheme
+scheme.h
+shotty
+tags
+title
+ttpre
+up
+when
+xx
diff --git a/bin/LICENSE b/bin/LICENSE
new file mode 100644
index 00000000..dba13ed2
--- /dev/null
+++ b/bin/LICENSE
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/bin/Makefile b/bin/Makefile
new file mode 100644
index 00000000..5691b0fc
--- /dev/null
+++ b/bin/Makefile
@@ -0,0 +1,153 @@
+PREFIX = ~/.local
+MANDIR = ${PREFIX}/share/man
+
+LIBS_PREFIX = /usr/local
+CFLAGS += -I${LIBS_PREFIX}/include
+LDFLAGS += -L${LIBS_PREFIX}/lib
+
+CFLAGS += -Wall -Wextra -Wpedantic -Wno-gnu-case-range
+LDLIBS.dtch = -lutil
+LDLIBS.fbclock = -lz
+LDLIBS.glitch = -lz
+LDLIBS.hnel = -lutil
+LDLIBS.modem = -lutil
+LDLIBS.pngo = -lz
+LDLIBS.ptee = -lutil
+LDLIBS.relay = -ltls
+LDLIBS.scheme = -lm
+LDLIBS.title = -lcurl
+
+-include config.mk
+
+BINS += beef
+BINS += bibsort
+BINS += bit
+BINS += c
+BINS += dtch
+BINS += glitch
+BINS += hi
+BINS += hnel
+BINS += modem
+BINS += nudge
+BINS += order
+BINS += pbd
+BINS += pngo
+BINS += psf2png
+BINS += ptee
+BINS += scheme
+BINS += shotty
+BINS += title
+BINS += ttpre
+BINS += up
+BINS += when
+BINS += xx
+
+LINKS += open
+LINKS += pbcopy
+LINKS += pbpaste
+
+BINS_BSD += ever
+BINS_LINUX += bri
+BINS_LINUX += fbatt
+BINS_LINUX += fbclock
+BINS_LINUX += psfed
+BINS_TLS += relay
+
+BINS_ALL = ${BINS} ${BINS_BSD} ${BINS_LINUX} ${BINS_TLS}
+MANS_ALL = ${BINS_ALL:%=man1/%.1}
+
+any: meta ${BINS} ${LINKS}
+
+bsd: meta ${BINS_BSD}
+
+linux: meta ${BINS_LINUX}
+
+tls: meta ${BINS_TLS}
+
+meta: .gitignore tags
+
+.SUFFIXES: .pl
+
+.c:
+	${CC} ${CFLAGS} ${LDFLAGS} $< ${LDLIBS.$@} -o $@
+
+.o:
+	${CC} ${LDFLAGS} $< ${LDLIBS.$@} -o $@
+
+.pl:
+	cp -f $< $@
+	chmod a+x $@
+
+hi: hi.c
+	${CC} ${CFLAGS} ${LDFLAGS} hi.c ${LDLIBS.$@} -o $@
+	./hi -c
+
+open pbcopy pbpaste: pbd
+	ln -f pbd $@
+
+fbatt.o fbclock.o: scheme.h
+
+psf2png.o scheme.o: png.h
+
+scheme.h: scheme
+	./scheme -c > scheme.h
+
+tags: *.h *.c
+	ctags -w *.h *.c
+
+IGNORE = *.o *.html ${BINS_ALL} ${LINKS} scheme.h tags
+
+.gitignore: Makefile
+	echo config.mk '${IGNORE}' | tr ' ' '\n' | sort > .gitignore
+
+clean:
+	rm -f ${IGNORE}
+
+setuid: bri
+	chown root bri
+	chmod u+s bri
+
+link:
+	install -d ${PREFIX}/bin ${MANDIR}/man1
+	ln -fs ${BINS_ALL:%=${PWD}/%} ${PREFIX}/bin
+	ln -fs ${MANS_ALL:%=${PWD}/%} ${MANDIR}/man1
+	ln -fs ${LINKS:%=${PWD}/%} ${PREFIX}/bin
+
+unlink:
+	rm -f ${BINS_ALL:%=${PREFIX}/bin/%}
+	rm -f ${MANS_ALL:%=${MANDIR}/%}
+	rm -f ${LINKS:%=${PREFIX}/bin/%}
+
+HTMLS = index.html ${BINS_ALL:=.html} png.html
+WEBROOT = /usr/local/www/causal.agency
+
+html: ${HTMLS}
+	@true
+
+${HTMLS}: html.sh scheme hi ttpre
+
+.SUFFIXES: .html
+
+.c.html:
+	sh html.sh $< man1/${<:.c=.1} > $@
+
+.h.html:
+	sh html.sh $< man3/${<:.h=.3} > $@
+
+.y.html:
+	sh html.sh $< man1/${<:.y=.1} > $@
+
+.sh.html:
+	sh html.sh $< man1/${<:.sh=.1} > $@
+
+.pl.html:
+	sh html.sh $< man1/${<:.pl=.1} > $@
+
+index.html: Makefile README.7
+	sh html.sh Makefile README.7 \
+		| sed -E 's,([a-z0-9-]+)[(][1-9][)],<a href="\1.html">&</a>,' \
+		> index.html
+
+install-html: ${HTMLS}
+	install -d ${WEBROOT}/bin
+	install -C -m 644 ${HTMLS} ${WEBROOT}/bin
diff --git a/bin/README.7 b/bin/README.7
new file mode 100644
index 00000000..40dcdd0b
--- /dev/null
+++ b/bin/README.7
@@ -0,0 +1,83 @@
+.Dd December 15, 2020
+.Dt BIN 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm bin
+.Nd various utilities
+.
+.Sh DESCRIPTION
+Various tools primarily targeting
+Darwin,
+.Fx
+and
+.Nx .
+Some tools target Linux.
+.
+.Pp
+.Bl -tag -width "fbclock(1)" -compact
+.It Xr beef 1
+Befunge-93 interpreter
+.It Xr bibsort 1
+reformat bibliography
+.It Xr bit 1
+calculator
+.It Xr bri 1
+backlight brightness control
+.It Xr c 1
+run C statements
+.It Xr dtch 1
+detached sessions
+.It Xr ever 1
+watch files
+.It Xr fbatt 1
+framebuffer battery indicator
+.It Xr fbclock 1
+framebuffer clock
+.It Xr glitch 1
+PNG glitcher
+.It Xr hi 1
+syntax highlighter
+.It Xr hnel 1
+PTY input remapper
+.It Xr modem 1
+fixed baud rate wrapper
+.It Xr nudge 1
+terminal vibrator
+.It Xr order 1
+operator precedence
+.It Xr pbd 1
+macOS pasteboard daemon
+.It Xr pngo 1
+PNG optimizer
+.It Xr psf2png 1
+PSF2 to PNG renderer
+.It Xr psfed 1
+PSF2 font editor
+.It Xr ptee 1
+tee for PTYs
+.It Xr relay 1
+IRC relay bot
+.It Xr scheme 1
+color scheme
+.It Xr shotty 1
+terminal capture
+.It Xr title 1
+page titles
+.It Xr ttpre 1
+man output to HTML
+.It Xr up 1
+upload file
+.It Xr when 1
+date calculator
+.It Xr xx 1
+hexdump
+.El
+.
+.Pp
+One piece of reused code.
+.Pp
+.Bl -tag -width "png(3)" -compact
+.It Xr png 3
+basic PNG output
+.El
diff --git a/bin/beef.c b/bin/beef.c
new file mode 100644
index 00000000..b2579b73
--- /dev/null
+++ b/bin/beef.c
@@ -0,0 +1,141 @@
+/* Copyright (C) 2019  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <time.h>
+
+enum {
+	Cols = 80,
+	Rows = 25,
+};
+static char page[Rows][Cols];
+
+static char get(int y, int x) {
+	if (y < 0 || y >= Rows) return 0;
+	if (x < 0 || x >= Cols) return 0;
+	return page[y][x];
+}
+static void put(int y, int x, char v) {
+	if (y < 0 || y >= Rows) return;
+	if (x < 0 || x >= Cols) return;
+	page[y][x] = v;
+}
+
+enum { StackLen = 1024 };
+static long stack[StackLen];
+static size_t top = StackLen;
+
+static void push(long val) {
+	if (!top) errx(EX_SOFTWARE, "stack overflow");
+	stack[--top] = val;
+}
+static long pop(void) {
+	if (top == StackLen) return 0;
+	return stack[top++];
+}
+
+static struct {
+	int y, x;
+	int dy, dx;
+} pc = { .dx = 1 };
+
+static void inc(void) {
+	pc.y += pc.dy;
+	pc.x += pc.dx;
+	if (pc.y == -1) pc.y += Rows;
+	if (pc.x == -1) pc.x += Cols;
+	if (pc.y == Rows) pc.y -= Rows;
+	if (pc.x == Cols) pc.x -= Cols;
+}
+
+static bool string;
+
+static bool step(void) {
+	char ch = page[pc.y][pc.x];
+
+	if (ch == '"') {
+		string ^= true;
+	} else if (string) {
+		push(ch);
+		inc();
+		return true;
+	}
+
+	if (ch == '?') ch = "><^v"[rand() % 4];
+
+	long x, y, v;
+	switch (ch) {
+		break; case '+': push(pop() + pop());
+		break; case '-': y = pop(); x = pop(); push(x - y);
+		break; case '*': push(pop() * pop());
+		break; case '/': y = pop(); x = pop(); push(x / y);
+		break; case '%': y = pop(); x = pop(); push(x % y);
+		break; case '!': push(!pop());
+		break; case '`': y = pop(); x = pop(); push(x > y);
+		break; case '>': pc.dy = 0; pc.dx = +1;
+		break; case '<': pc.dy = 0; pc.dx = -1;
+		break; case '^': pc.dy = -1; pc.dx = 0;
+		break; case 'v': pc.dy = +1; pc.dx = 0;
+		break; case '_': pc.dy = 0; pc.dx = (pop() ? -1 : +1);
+		break; case '|': pc.dx = 0; pc.dy = (pop() ? -1 : +1);
+		break; case ':': x = pop(); push(x); push(x);
+		break; case '\\': y = pop(); x = pop(); push(y); push(x);
+		break; case '$': pop();
+		break; case '.': printf("%ld ", pop()); fflush(stdout);
+		break; case ',': printf("%c", (char)pop()); fflush(stdout);
+		break; case '#': inc();
+		break; case 'g': y = pop(); x = pop(); push(get(y, x));
+		break; case 'p': y = pop(); x = pop(); v = pop(); put(y, x, v);
+		break; case '&': x = EOF; scanf("%ld", &x); push(x);
+		break; case '~': push(getchar());
+		break; case '@': return false;
+		break; default:  if (ch >= '0' && ch <= '9') push(ch - '0');
+	}
+
+	inc();
+	return true;
+}
+
+int main(int argc, char *argv[]) {
+	srand(time(NULL));
+	memset(page, ' ', sizeof(page));
+
+	FILE *file = stdin;
+	if (argc > 1) {
+		file = fopen(argv[1], "r");
+		if (!file) err(EX_NOINPUT, "%s", argv[1]);
+	}
+
+	int y = 0;
+	char *line = NULL;
+	size_t cap = 0;
+	while (y < Rows && 0 < getline(&line, &cap, file)) {
+		for (int x = 0; x < Cols; ++x) {
+			if (line[x] == '\n' || !line[x]) break;
+			page[y][x] = line[x];
+		}
+		y++;
+	}
+	free(line);
+
+	while (step());
+	return pop();
+}
diff --git a/bin/bibsort.pl b/bin/bibsort.pl
new file mode 100644
index 00000000..f87f066f
--- /dev/null
+++ b/bin/bibsort.pl
@@ -0,0 +1,66 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+
+while (<>) {
+	print;
+	last if /^[.]Sh STANDARDS$/;
+}
+
+my ($ref, @refs);
+while (<>) {
+	next if /^[.](Bl|It|$)/;
+	last if /^[.]El$/;
+	if (/^[.]Rs$/) {
+		$ref = {};
+	} elsif (/^[.]%(.) (.*)/) {
+		$ref->{$1} = [] unless $ref->{$1};
+		push @{$ref->{$1}}, $2;
+	} elsif (/^[.]Re$/) {
+		push @refs, $ref;
+	} else {
+		print;
+	}
+}
+
+sub byLast {
+	my ($af, $al) = split /\s(\S+)(,.*)?$/, $a;
+	my ($bf, $bl) = split /\s(\S+)(,.*)?$/, $b;
+	$al cmp $bl || $af cmp $bf;
+}
+
+foreach $ref (@refs) {
+	@{$ref->{A}} = sort byLast @{$ref->{A}};
+	@{$ref->{Q}} = sort @{$ref->{Q}} if $ref->{Q};
+	next unless $ref->{N};
+	if ($ref->{N}[0] =~ /RFC/) {
+		$ref->{R} = $ref->{N};
+		delete $ref->{N};
+	}
+}
+
+sub byAuthor {
+	local ($a, $b) = ($a->{A}[0], $b->{A}[0]);
+	byLast();
+}
+
+{
+	local ($,, $\) = (' ', "\n");
+	print '.Bl', '-item';
+	foreach $ref (sort byAuthor @refs) {
+		print '.It';
+		print '.Rs';
+		foreach my $key (qw(A T B I J R N V U P Q C D O)) {
+			next unless $ref->{$key};
+			foreach (@{$ref->{$key}}) {
+				print ".%${key}", $_;
+			}
+		}
+		print '.Re';
+	}
+	print '.El';
+}
+
+while (<>) {
+	print;
+}
diff --git a/bin/bit.y b/bin/bit.y
new file mode 100644
index 00000000..0a9bc7ce
--- /dev/null
+++ b/bin/bit.y
@@ -0,0 +1,194 @@
+/* Copyright (C) 2019  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+%{
+
+#include <ctype.h>
+#include <err.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+
+#define MASK(b) ((1ULL << (b)) - 1)
+
+static void yyerror(const char *str);
+static int yylex(void);
+
+#define YYSTYPE uint64_t
+
+static uint64_t vars[128];
+
+%}
+
+%right '='
+%left '|'
+%left '^'
+%left '&'
+%left Shl Shr Sar
+%left '+' '-'
+%left '*' '/' '%'
+%right '~'
+%left 'K' 'M' 'G' 'T'
+
+%token Int Var
+
+%%
+
+stmt:
+	expr { vars['_'] = $1; }
+	;
+
+expr:
+	Int
+	| Var { $$ = vars[$1]; }
+	| '(' expr ')' { $$ = $2; }
+	| expr 'K' { $$ = $1 << 10; }
+	| expr 'M' { $$ = $1 << 20; }
+	| expr 'G' { $$ = $1 << 30; }
+	| expr 'T' { $$ = $1 << 40; }
+	| '~' expr { $$ = ~$2; }
+	| '-' expr { $$ = -$2; }
+	| expr '*' expr { $$ = $1 * $3; }
+	| expr '/' expr { $$ = $1 / $3; }
+	| expr '%' expr { $$ = $1 % $3; }
+	| expr '+' expr { $$ = $1 + $3; }
+	| expr '-' expr { $$ = $1 - $3; }
+	| expr Shl expr { $$ = $1 << $3; }
+	| expr Shr expr { $$ = $1 >> $3; }
+	| expr Sar expr { $$ = (int64_t)$1 >> $3; }
+	| expr '&' expr { $$ = $1 & $3; }
+	| expr '^' expr { $$ = $1 ^ $3; }
+	| expr '|' expr { $$ = $1 | $3; }
+	| Var '=' expr { $$ = vars[$1] = $3; }
+	;
+
+%%
+
+static void yyerror(const char *str) {
+	warnx("%s", str);
+}
+
+#define T(a, b) ((int)(a) << 8 | (int)(b))
+
+static const char *input;
+
+static int lexInt(uint64_t base) {
+	for (yylval = 0; input[0]; ++input) {
+		uint64_t digit;
+		if (input[0] == '_') {
+			continue;
+		} else if (input[0] >= '0' && input[0] <= '9') {
+			digit = input[0] - '0';
+		} else if (input[0] >= 'A' && input[0] <= 'F') {
+			digit = 0xA + input[0] - 'A';
+		} else if (input[0] >= 'a' && input[0] <= 'f') {
+			digit = 0xA + input[0] - 'a';
+		} else {
+			return Int;
+		}
+		if (digit >= base) return Int;
+		yylval *= base;
+		yylval += digit;
+	}
+	return Int;
+}
+
+static int yylex(void) {
+	while (isspace(input[0])) input++;
+	if (!input[0]) return EOF;
+
+	if (input[0] == '\'' && input[1] && input[2] == '\'') {
+		yylval = input[1];
+		input += 3;
+		return Int;
+	}
+
+	if (input[0] == '0') {
+		if (input[1] == 'b') {
+			input += 2;
+			return lexInt(2);
+		} else if (input[1] == 'x') {
+			input += 2;
+			return lexInt(16);
+		} else {
+			input += 1;
+			return lexInt(8);
+		}
+	} else if (isdigit(input[0])) {
+		return lexInt(10);
+	}
+	
+	if (input[0] == '_' || islower(input[0])) {
+		yylval = *input++;
+		return Var;
+	}
+
+	switch (T(input[0], input[1])) {
+		case T('<', '<'): input += 2; return Shl;
+		case T('>', '>'): input += 2; return Shr;
+		case T('-', '>'): input += 2; return Sar;
+		default: return *input++;
+	}
+}
+
+int main(void) {
+	char *line = NULL;
+	size_t cap = 0;
+	while (0 < getline(&line, &cap, stdin)) {
+		if (line[0] == '\n') continue;
+
+		input = line;
+		int error = yyparse();
+		if (error) continue;
+
+		uint64_t result = vars['_'];
+
+		int bits = result > UINT32_MAX ? 64
+			: result > UINT16_MAX ? 32
+			: result > UINT8_MAX ? 16
+			: 8;
+
+		printf("0x%0*"PRIX64" %"PRId64"", bits >> 2, result, (int64_t)result);
+
+		if (bits == 8) {
+			char bin[9] = {0};
+			for (int i = 0; i < 8; ++i) {
+				bin[i] = '0' + (result >> (7 - i) & 1);
+			}
+			printf(" 0b%s", bin);
+		}
+
+		if (result < 128 && isprint(result)) {
+			printf(" '%c'", (char)result);
+		}
+
+		if (result) {
+			if (!(result & MASK(40))) {
+				printf(" %"PRIu64"T", result >> 40);
+			} else if (!(result & MASK(30))) {
+				printf(" %"PRIu64"G", result >> 30);
+			} else if (!(result & MASK(20))) {
+				printf(" %"PRIu64"M", result >> 20);
+			} else if (!(result & MASK(10))) {
+				printf(" %"PRIu64"K", result >> 10);
+			}
+		}
+
+		printf("\n\n");
+	}
+}
diff --git a/bin/bri.c b/bin/bri.c
new file mode 100644
index 00000000..81df2995
--- /dev/null
+++ b/bin/bri.c
@@ -0,0 +1,85 @@
+/* Copyright (C) 2017  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+typedef unsigned uint;
+
+static const char *Class = "/sys/class/backlight";
+
+int main(int argc, char *argv[]) {
+	int error;
+
+	const char *input = (argc > 1) ? argv[1] : NULL;
+
+	error = chdir(Class);
+	if (error) err(EX_OSFILE, "%s", Class);
+
+	DIR *dir = opendir(".");
+	if (!dir) err(EX_OSFILE, "%s", Class);
+
+	struct dirent *entry;
+	while (NULL != (errno = 0, entry = readdir(dir))) {
+		if (entry->d_name[0] == '.') continue;
+
+		error = chdir(entry->d_name);
+		if (error) err(EX_OSFILE, "%s/%s", Class, entry->d_name);
+		break;
+	}
+	if (!entry) {
+		if (errno) err(EX_IOERR, "%s", Class);
+		errx(EX_CONFIG, "%s: empty", Class);
+	}
+
+	FILE *actual = fopen("actual_brightness", "r");
+	if (!actual) err(EX_OSFILE, "actual_brightness");
+
+	uint value;
+	int match = fscanf(actual, "%u", &value);
+	if (match == EOF) err(EX_IOERR, "actual_brightness");
+	if (match < 1) errx(EX_DATAERR, "actual_brightness");
+
+	if (!input) {
+		printf("%u\n", value);
+		return EX_OK;
+	}
+
+	if (input[0] == '+' || input[0] == '-') {
+		size_t count = strnlen(input, 16);
+		if (input[0] == '+') {
+			value += 16 * count;
+		} else {
+			value -= 16 * count;
+		}
+	} else {
+		value = strtoul(input, NULL, 0);
+	}
+
+	FILE *brightness = fopen("brightness", "w");
+	if (!brightness) err(EX_OSFILE, "brightness");
+
+	int size = fprintf(brightness, "%u", value);
+	if (size < 0) err(EX_IOERR, "brightness");
+
+	return EX_OK;
+}
diff --git a/bin/c.sh b/bin/c.sh
new file mode 100644
index 00000000..f1143fd3
--- /dev/null
+++ b/bin/c.sh
@@ -0,0 +1,87 @@
+#!/bin/sh
+set -eu
+
+temp=$(mktemp -d)
+trap 'rm -r "${temp}"' EXIT
+
+exec 3>>"${temp}/run.c"
+
+cat >&3 <<EOF
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <locale.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#include <fcntl.h>
+#include <strings.h>
+#include <unistd.h>
+EOF
+
+expr=
+while getopts 'e:i:' opt; do
+	case "${opt}" in
+		(e) expr=$OPTARG;;
+		(i) echo "#include <${OPTARG}>" >&3;;
+		(?) exit 1;;
+	esac
+done
+shift $((OPTIND - 1))
+
+cat >&3 <<EOF
+int main(int argc, char *argv[]) {
+	(void)argc;
+	(void)argv;
+	$*;
+EOF
+
+if [ -n "${expr}" ]; then
+	cat >&3 <<EOF
+	printf(
+		_Generic(
+			${expr},
+			char: "%c\n",
+			char *: "%s\n",
+			const char *: "%s\n",
+			wchar_t *: "%ls\n",
+			const wchar_t *: "%ls\n",
+			signed char: "%hhd\n",
+			short: "%hd\n",
+			int: "%d\n",
+			long: "%ld\n",
+			long long: "%lld\n",
+			unsigned char: "%hhu\n",
+			unsigned short: "%hu\n",
+			unsigned int: "%u\n",
+			unsigned long: "%lu\n",
+			unsigned long long: "%llu\n",
+			double: "%g\n",
+			default: "%p\n"
+		),
+		${expr}
+	);
+EOF
+fi
+
+if [ $# -eq 0 -a -z "${expr}" ]; then
+	cat >&3
+fi
+
+echo '}' >&3
+
+cat >"${temp}/Makefile" <<EOF
+CFLAGS += -Wall -Wextra -Wpedantic
+EOF
+
+make -s -C "${temp}" run
+"${temp}/run"
diff --git a/bin/dash/.gitignore b/bin/dash/.gitignore
new file mode 100644
index 00000000..e349901a
--- /dev/null
+++ b/bin/dash/.gitignore
@@ -0,0 +1,42 @@
+# .gitignore for dash
+
+# generated by autogen.sh
+Makefile.in
+/aclocal.m4
+/autom4te.cache/
+/compile
+/config.h.in
+/configure
+/depcomp
+/install-sh
+/missing
+
+# generated by configure
+Makefile
+.deps
+.dirstamp
+/config.cache
+/config.h
+/config.log
+/config.status
+/stamp-h1
+
+# generated by make
+/src/token_vars.h
+
+# Apple debug symbol bundles
+*.dSYM/
+
+# backups and patch artefacts
+*~
+*.bak
+*.orig
+*.rej
+
+# OS generated files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight*
+.Trash*
+*[Tt]humbs.db
diff --git a/bin/dash/COPYING b/bin/dash/COPYING
new file mode 100644
index 00000000..37f8189c
--- /dev/null
+++ b/bin/dash/COPYING
@@ -0,0 +1,56 @@
+Copyright (c) 1989-1994
+	The Regents of the University of California.  All rights reserved.
+Copyright (c) 1997 Christos Zoulas.  All rights reserved.
+Copyright (c) 1997-2005
+	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+
+This code is derived from software contributed to Berkeley by Kenneth Almquist.
+
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. Neither the name of the University nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+mksignames.c:
+
+This file is not directly linked with dash.  However, its output is.
+
+Copyright (C) 1992 Free Software Foundation, Inc.
+
+This file is part of GNU Bash, the Bourne Again SHell.
+
+Bash is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 2, or (at your option) any later
+version.
+
+Bash is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License with
+your Debian GNU/Linux system, in /usr/share/common-licenses/GPL, or with the
+Debian GNU/Linux hello source package as the file COPYING.  If not,
+write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+Boston, MA 02111 USA.
diff --git a/bin/dash/ChangeLog b/bin/dash/ChangeLog
new file mode 100644
index 00000000..406e20c0
--- /dev/null
+++ b/bin/dash/ChangeLog
@@ -0,0 +1,806 @@
+2014-11-17  Stéphane Aulery <saulery@free.fr>
+
+	* Correct typo in manual page.
+	* Document redirection file descriptor limitation.
+
+2014-10-30  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Catch variable length expansions on non-existant specials.
+
+2014-10-28  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Removed unnecessary pungetc on EOF from parser.
+	* Simplify EOF/newline handling in list parser.
+
+2014-10-27  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Add printf support for format string a, A, and F.
+	* Handle embedded NULs correctly in printf.
+	* Replace open-coded flushall in preadbuffer.
+	* Add likely tag in outmem.
+	* Add ifdefs around MEM_OUT handling in outmem.
+	* Remove unnecessary restoration of format string in printf.
+	* Remove getintmax in printf.
+	* Use error instead of warnx for fatal errors in printf.
+	* Optimise handling of backslash octals in printf.
+	* Simplify echo command.
+	* Handle -- in dotcmd.
+
+2014-10-13  Eric Blake <eblake@redhat.com>
+
+	* cd: support drive letters on Cygwin.
+
+2014-10-08  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Split unquoted $@/$* correctly when IFS is set but empty.
+	* Do not split quoted VSLENGTH and VSTRIM.
+	* Optimise nulonly away and just use quoted as before.
+
+2014-10-07  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Use setvareq to set OPTIND initially.
+
+2014-10-06  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Exit without arguments in a trap should use status outside traps.
+	* Do not allow break to break across function calls.
+	* Move common skipcount logic into skiploop.
+	* Allow return in loop conditional to set exit status.
+	* Return without arguments in a trap should use status outside traps.
+
+2014-10-03  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Do not clobber exitstatus in evalcommand.
+
+2014-10-02  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix use-after-free in dotrap/evalstring.
+	* Make sure evalskip is zero before running traps.
+	* Set exitstatus in onint.
+
+2014-09-29  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Kill pgetc_macro.
+	* Handle backslash newlines properly after dollar sign.
+	* Add nlprompt/nlnoprompt helpers.
+
+2014-09-28  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Correctly handle test ! ! = !.
+
+2014-09-26  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Small optimisation of command -pv change.
+	* Set command -p path to /usr/sbin:/usr/bin:/sbin:/bin.
+	* Change "characters" for printf precision to "bytes".
+	* Release 0.5.8.
+
+2014-09-26  Jonathan Nieder <jrnieder@gmail.com>
+
+	* Clarify "trap '' signals" syntax in manual page.
+
+2014-09-26  Adam Buchbinder <adam.buchbinder@gmail.com>
+
+	* Clarify that 0 and EXIT are both acceptable for exit traps.
+
+2014-09-26  Harald van Dijk <harald@gigawatt.nl>
+
+	* command: allow combining -p with -v.
+	* getjob: Fix off-by-one error for multiple of four job numbers
+
+2013-08-23  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Propagate EXP_QPAT in subevalvar.
+	* Initialise OPTIND after importing environment.
+	* Fixed argument parsing crash in test.
+
+2013-03-12  Peter Rosin <peda@lysator.liu.se>
+
+	* Add newline when tracing in poplocalvars.
+
+2013-01-10  Jérémie Courrèges-Anglas <jca+dash@wxcvbn.org>
+
+	* Markup fixes in manual for mandoc 1.12.1.
+
+2012-12-03  Harald van Dijk <harald@gigawatt.nl>
+
+	* Use PRIdMAX instead of %j in printf.
+
+2012-07-20  Kimo Rosenbaum <kimor79@yahoo.com>
+
+	* Fix typo for wait in manual.
+
+2012-06-09  Christoph Mathys <eraserix@gmail.com>
+
+	* Add support for ulimit -r.
+
+2012-03-11  Jim Meyering  <meyering@redhat.com>
+
+	* Avoid overflow for very long variable name.
+	$ perl -le 'print "v"x(2**31+1) ."=1"' | dash
+
+2012-02-25  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Sanitise environment variable names on entry.
+
+2011-08-17  David S. Miller <davem@davemloft.net>
+
+	* Allow building without LINEO support.
+	* Add top-level autogen.sh.
+
+2011-07-26  Harald van Dijk <harald@gigawatt.nl>
+
+	* Avoid imaxdiv when only one of the results is wanted.
+
+2010-07-09  maximilian attems <max@stro.at>
+
+	* Fix klibc DEBUG compilation.
+
+2011-07-09  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Merge SKIPFUNC/SKIPFILE and only clear SKIPFUNC when leaving dotcmd.
+
+2011-07-08  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Release 0.5.7.
+
+2011-07-07  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Optimize dash -c "command" to avoid a fork.
+	* Eliminate unnecessary promotion in echocmd.
+
+2011-05-25  Jim Meyering  <meyering@redhat.com>
+
+	* Avoid using undefined handler.
+
+2011-05-23  Jim Meyering  <meyering@redhat.com>
+
+	* Avoid gcc warning: variable 'oldstackp' set but not used.
+	* Avoid clang warning about dead store to "size".
+
+2011-05-22  Jonathan Nieder <jrnieder@gmail.com>
+
+	* Make outc an inline function.
+
+2011-05-02  Kalle Olavi Niemitalo <kon@iki.fi>
+
+	* Remove spurious space in descriptions of PS1, PS2, PS4.
+
+2011-04-10  Jonathan Nieder <jrnieder@gmail.com>
+ 
+	* Remove unused EV_BACKCMD flag.
+
+2011-03-15  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix clobbering of checkkwd.
+
+2011-03-15  Jonathan Nieder <jrnieder@gmail.com>
+
+	* Free IFS state after here document expansion.
+	* Use va_copy when reusing a va_list.
+
+2011-03-15  Harald van Dijk <harald@gigawatt.nl>
+
+	* Let funcnode refer to a function definition, not its first command.
+	* Improve LINENO support.
+
+2011-03-15  Brian Koropoff <bkoropoff@gmail.com>
+
+	* Port to Solaris.
+
+2011-03-11  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix backslash handling in read(1).
+
+2011-03-10  Jonathan Nieder <jrnieder@gmail.com>
+
+	* Dotcmd should exit with zero when doing nothing.
+
+2011-03-10  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix CTLESC clobbering by read(1).
+
+2011-03-10  Brian Koropoff <bkoropoff@gmail.com>
+
+	* Port to AIX.
+
+2011-03-10  Jilles Tjoelker <jilles@stack.nl>
+
+	* Replace GPL noclobberopen code with the FreeBSD version.
+	* Do not split the result of tilde expansion.
+
+2010-11-28  Maciej Żenczykowski <zenczykowski@gmail.com>
+
+	* Mark status as volatile in exitshell.
+
+2010-11-28  Jonathan Nieder <jrnieder@gmail.com>
+
+	* Use EXEXIT in place of EXEXEC.
+	* Stop documenting EXSHELLPROC.
+
+2010-11-28  Gerrit Pape <pape@smarden.org>
+
+	* Use exit status 127 when the script to run does not exist.
+
+2010-11-28  Philipp Weis <pweis@pweis.com>
+
+	* Document optional open parenthesis for case patterns.
+
+2010-11-28  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fixed trap/return regression due to SKIPEVAL removal.
+	* Allow the originator of EXERROR to set the exit status.
+	* Free IFS state in evalbackcmd.
+
+2010-10-18  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix ifsfirst/ifslastp leak in casematch.
+
+2010-10-07  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix EXEXEC status clobbering.
+
+2010-09-08  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix ifsfirst/ifslastp leak.
+	* Fix trailing field bug in read(1).
+
+2010-09-08  maximilian attems <max@stro.at>
+
+	* Debug compile fix.
+
+2010-09-08  Jilles Tjoelker <jilles@stack.nl>
+
+	* Fix varinit ordering that broke fc.
+
+2010-07-06  Gerrit Pape <pape@smarden.org>
+
+	* Check exit for eval NSUBSHELL.
+
+2010-07-06  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix loss of variables when hash collides.
+	* Removed dead code for eval NPIPE.
+
+2010-06-28  Gerrit Pape <pape@smarden.org>
+
+	* Don't clear eflag in evalbackcmd.
+
+2010-05-29  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Continue after EINTR in read(1) with no pending signals.
+
+2010-05-27  Jilles Tjoelker <jilles@stack.nl>
+
+	* Force fork if any trap is set, not just on EXIT.
+	* Fix corruption of redirections with byte 0x81.
+
+2010-05-27  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix poplocalvar on abnormal exit from function.
+	* Do not poplocalvars prematurely on regular utilities.
+	* Move null redirect checks into caller.
+	* Fix popredir on abnormal exit from built-in.
+	* Fix wait regression where it does not wait for all jobs.
+
+2010-05-26  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Replace cmdenviron with localvars.
+
+2010-05-25  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix poplocalvar leak.
+	* Move unsetvar functionality into setvareq.
+
+2010-05-24  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Add localvars nesting, local now fails outside functions.
+
+2010-05-03  Gerrit Pape <pape@smarden.org>
+
+	* Fix command -- crash.
+
+2010-04-15  H. Peter Anvin <hpa@zytor.com>
+
+	* Fix for job control off warning.
+
+2010-04-02  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Use faccessat if available.
+
+2010-04-02  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Make trap signal name/number errors non-fatal.
+	* Release 0.5.6.
+
+2010-04-02  maximilian attems <max@stro.at>
+
+	* Use TMPDIR in mkbuiltins.
+
+2010-03-10  Jilles Tjoelker <jilles@stack.nl>
+
+	* Fix logical or result value.
+
+2010-03-09  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix binary operator parsing.
+
+2009-11-26  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix off-by-one recordregion in readcmd.
+
+2009-09-28  Jim Meyering  <meyering@redhat.com>
+
+	don't read-uninitialized for \177 in a here-doc
+	A DEL (0177, dec 127) byte in a here-document would cause dash to
+	access uninitialized memory at the end of one of the syntax.c tables,
+	since those tables are sized to accommodate a maximum index of
+	BASESYNTAX + 126.  Make the generated tables one byte larger.
+	printf ':<<\\E\n\200y\nE'|./dash
+	* src/mksyntax.c (filltable): Use 258, not 257 as the size,
+	so that BASESYNTAX(=130) + 127 is a valid index.
+	(print): Likewise.
+	Don't emit explicit array dimension in declaration.
+
+2009-08-31  Eric Blake <ebb9@byu.net>
+
+	* Avoid compiler warnings on isdigit.
+
+2009-08-31  Matthew Burgess <matthew@linuxfromscratch.org>
+
+	* Add another missing LC_COLLATE to mkbuiltins.
+
+2009-08-31  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix NUL termination in readcmd.
+	* Lookup PWD after going through CDPATH.
+
+2009-08-11  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Pass EV_TESTED into evalcmd.
+	* Revert SKIPEVAL into EXEXIT.
+
+2009-08-11  Rocky Bernstein <rocky.bernstein@gmail.com>
+
+	* Add preliminary LINENO support.
+
+2009-08-11  Stefan Potyra <stefan.potyra@informatik.uni-erlangen.de>
+
+	* Honor tab as IFS whitespace when splitting fields in readcmd.
+
+2009-06-30  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Do not truncate file for FROMTO redirection.
+
+2009-06-27  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix quoted pattern patch breakage.
+
+2009-05-23  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix incorrect savefd conversions.
+
+2009-02-22  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix dowait signal race.
+	* Remove EXSIG.
+	* Do not close stderr when /dev/tty fails to open.
+	* Allow newlines after var name in for statements.
+	* Use CHKNL to parse case statements.
+
+2009-02-22  Gerrit Pape <pape@smarden.org>
+
+	* Update manual page to differentiate dash from ash.
+
+2009-01-14  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Add arith_yacc.h to dash_SOURCES.
+	* Release 0.5.5.1.
+
+2009-01-13  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Release 0.5.5.
+
+2009-01-13  Mark Mentovai <mmentovai@gmail.com>
+
+	* Fixed build on OS X.
+
+2008-12-26  Aleksey Cheusov <vle@gmx.net>
+
+	* Fixed build on NetBSD.
+
+2008-10-17  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Removed obsolete for loop syntax in manual.
+
+2008-08-05  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fixed getcwd build error for the non-glibc case.
+
+2008-06-13  Gerrit Pape <pape@smarden.org>
+
+	* Added missing right parenthesis in manual page.
+
+2008-06-13  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fixed 3,4-argument cases for test per POSIX.
+	* Made aexpr/oexpr non-recursive.
+	* Made t_lex reentrant.
+	* Made setinputfd static.
+	* Expand ENV before using it.
+	* Added support for -l.
+
+2008-05-19  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fixed non-leading slash treatment in expmeta.
+
+2008-05-07  Gerrit Pape <pape@smarden.org>
+
+	* Fixed lexical error in arithmetic expansion of & and |.
+
+2008-05-03  Dan McGee <dpmcgee@gmail.com>
+
+	* Fixed klibc/klcc build problems.
+	* Added gitignore.
+
+2008-05-03  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fixed _PATH_BSHELL warning.
+	* Test __GLIBC__ instead of _GNU_SOURCE.
+	* Restored warning when getcwd fails.
+	* Set default exvwarning2 arg0 for errors during early initialisation.
+	* Use uninitialized_var to silence bogus warnings.
+
+2008-05-02  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Restored non-glibc getcwd support.
+
+2008-03-07  Larry Doolittle <ldoolitt@recycle.lbl.gov>
+
+	* Fix cmdtxt crash on if statements.
+
+2008-01-01  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix slash treatment in expmeta.
+
+2007-12-27  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Add FAKEEOFMARK for expandstr.
+	* Do not show prompts in expandstr.
+
+2007-12-23  Gerrit Pape <pape@smarden.org>
+
+	* If imaxdiv() isn't available, use / and % operators.
+
+2007-12-23  Richard M Kreuter <kreuter@progn.net>
+
+	* Add set +o support.
+
+2007-12-23  Steve Langasek <steve.langasek@canonical.com>
+
+	* Fixed bad pointer arithmetic in evalcommand.
+
+2007-11-11  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Removed noexpand/length check on eofmark.
+	* Removed herefd hack.
+	* Expand here-documents in the current shell environment.
+
+2007-10-20  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Added configure --enable-glob and --enable-fnmatch options.
+	* Fix here-doc corruption.
+
+2007-10-17  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Replace shared illnum message by badnum function.
+
+2007-10-17  Oleg Verych <olecom@flower.upol.cz>
+
+	* Disallow completely blank strings in non-arithmetic context.
+
+2007-10-15  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fixed execing of scripts with no hash-bang.
+
+2007-10-11  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Add assignment support in arithmetic expansions.
+	* Size optimisations in arithmetic lexer.
+	* Add likely flags in expari.
+	* Use setvarint to set OPTIND.
+
+2007-10-08  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Report substition errors at expansion time.
+
+2007-10-06  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Add pushstackmark.
+	* Treat OPTIND=0 in the same way as OPTIND=1.
+	* Remove setvarsafe.
+	* Use intmax_t arithmetic in test.
+
+2007-10-05  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Made grabstackblock an inline wrapper for stalloc.
+
+2007-10-04  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix parsing of ${##1}.
+	* Size optimisations in parameter expansion parser.
+
+2007-10-04  Alexey Gladkov <legion@altlinux.org>
+
+	* Add --enable-static option to configure.
+
+2007-09-26  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Recognise here-doc delimiters terminated by EOF.
+
+2007-09-26  Roy Marples <uberlord@gentoo.org>
+
+	* Refresh stack pointers after makestrspace in _rmescapes.
+
+2007-09-25  Gerrit Pape <pape@smarden.org>
+
+	* Clarify description of -nt, -ot options to test builtin.
+	* Clarify syntax of the for command.
+
+2007-09-25  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Do not expand tilde in parameter expansion within quotes.
+	* Move parse-time quote flag detection to run-time.
+
+2007-09-24  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Do not quote back slashes in parameter expansions outside quotes.
+	* Perform tilde expansion in all parameter expansion words.
+	* Remove superfluous arinest test in CENDQUOTE.
+	* Remove superfluous arinest test for dqvarnest.
+	* Remove superfluous dblquote settings when ending arith.
+	* Remove arithmetic expansion collapsing at parse time.
+
+2007-09-22  Oleg Verych <olecom@flower.upol.cz>
+
+	* White space fixes for test(1).
+	* Use direct comparison instead of strcmp in test(1).
+
+2007-09-22  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Move flushall to the point just before _exit.
+
+2007-09-21  Denis Vlasenko <vda.linux@googlemail.com>
+
+	* Restore foreground process group on exit.
+
+2007-07-12  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Release 0.5.4.
+
+2007-05-12  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix redirect restore on closed file descriptors.
+	* Size optimisations in redir.c.
+
+2007-05-06  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Removed unnecessary inclusion of redir.h from parser.c.
+	* Invoke sh_error on error in copyfd.
+	* Use dup2 instead of copyfd in evalbackcmd.
+	* Replace copyfd by savefd and dup2.
+	* Removed redundant CLOEXEC calls.
+
+2007-05-05  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fixed typo in parser.h comment.
+
+2007-04-28  Dan Nicholson <dbn.lists@gmail.com>
+
+	* Set LC_COLLATE for sort in mkbuiltin.
+
+2006-10-22  Gerrit Pape <pape@smarden.org>
+
+	* Fixed command -v segmentation fault.
+
+2006-10-13  Alexey Gladkov <legion@altlinux.org>
+
+	* Check return code for getgroups and fwrite.
+
+2006-10-04  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fixed inverted char class matching.
+
+2006-05-23  Alexey Gladkov <legion@altlinux.org>
+
+	* Added --with-libedit option to configure.
+
+2006-03-29  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Removed useless parsebackquote flag.
+	* Use alloca to get rid of setjmp in parse.c.
+	* Only use signed char for syntax arrays.
+
+2006-01-12  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fixed eval exit status with empty arguments.
+
+2005-11-26  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Release 0.5.3.
+
+2005-11-14  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fix \c spillage across echo commands.
+
+2005-11-13  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Remove unnecessary truncation in _STPUTC.
+	* Always call conv_escape_str in echocmd.
+
+2005-11-04  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Use mktemp if tempfile is not available.
+
+2005-10-30  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fixed support for disabling job control.
+
+2005-10-29  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Updated BSD licence to 3-clause version per NetBSD.
+	* Updated copyright.
+	* Removed CVS IDs and inclusion of sys/cdefs.h.
+	* Removed use of __P from error.h.
+	* Use bsd_signal if it exists and signal does not.
+	* Stop using sysexits.h in commandcmd.
+	* Use stat if stat64 does not exist.
+	* Added default implementation of bsearch.
+	* Added getpwhome as a wrapper for getpwnam.
+	* Fixed gcc 4.0 compilation problems.
+	* Added missing system.h inclusion for mempcpy.
+	* Added default implementation of strsignal.
+	* Added default implementation of killpg.
+	* Disable ulimit if there is no getrlimit.
+	* Disable histcmd completely when SMALL is defined.
+	* Added default definition for SSIZE_MAX.
+	* Removed standalone/csh support from printf.
+	* Added dummy strtod implementation.
+	* Removed standalone/csh support from test.
+	* Added dummy sysconf implementation.
+	* Include system.h for stpcpy in nodes.c.
+	* Added out-of-line ctypes functions for klibc.
+	* Fixed fallback stpcpy implementation.
+
+2005-10-26  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Size optimisations in preadbuffer().
+
+2005-10-01  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Skip NUL characters properly in input.c.
+
+2005-03-28  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Removed some unnecessary inclusions of input.h.
+	* Removed unnecessary inclusion of main.h from eval.c.
+	* Removed unnecessary inclusion of eval.h from parser.c.
+	* Generalise setinputfile for use in read_profile/readcmdfile.
+	* Handle SKIPEVAL in read_profile by exiting.
+	* Let evaltree handle traps from cmdloop.
+	* Reset evalskip after minusc is executed.
+	* Stop executing traps once SKIPEVAL is seen.
+	* Only handle each signal once in dotrap.
+
+2005-03-25  Gerrit Pape <pape@smarden.org>
+
+	* Add trailing equal sign in setvar for variables set to null.
+
+2005-03-25  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Cleaned up src/Makefile.am.
+	* Get rid of duplicate -g -O2 in CFLAGS.
+	* Eliminate first null termination in setvar.
+	* Turn evalskip into a bit field.
+
+2005-03-25  Gilles Chanteperdrix <gilles.chanteperdrix@laposte.net>
+
+	* Fixed support for cross-compilation.
+
+2005-03-03  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Removed qflag.
+	* Removed redundant setstackmark from dotcmd.
+	* Do not clobber exit status in dotcmd.
+
+2005-03-02  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Renamed symbols to avoid conflict with libc.
+	* Optimisations in bltin/test.c.
+
+2005-02-28  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Replaced EXEVAL with SKIPEVAL.
+	* Update funcnest atomically.
+	* Only set skipcount for break and continue.
+	* Removed expcmd built-in.
+	* Normalise input in likely/unlikely macros.
+
+2005-02-28  A Costa <agcosta@gis.net>
+
+	* Corrected that/than typo in manual page.
+
+2005-02-25  A Costa <agcosta@gis.net>
+
+	* Corrected grammar in manual page.
+
+2005-02-25  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Changed boolean rootshell into shlvl counter.
+	* Catch set -e exits within built-in commands.
+	* Size optimisations with setjmp in main().
+	* Only reread exitstatus on EXEXIT in exitshell.
+	* Eliminated global exerrno.
+
+2005-01-31  Gerrit Pape <pape@smarden.org>
+
+	* Release 0.5.2.
+	* Corrected manual entry about ENV and non-interactive shells.
+
+2004-11-24  Gerrit Pape <pape@smarden.org>
+
+	* Spell behaviour consistently in manual page.
+
+2004-11-23  A Costa <agcosta@gis.net>
+
+	* Fixed spelling errors in manual page.
+
+2004-08-21  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Size optimisations around varvalue() in src/expand.c.
+	* Fixed signed char promotion in src/expand.c.
+
+2004-08-18  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fixed $@ expansion when leading argument is null in src/expand.c.
+
+2004-08-07  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Allow negative pid argument to kill(1) in src/jobs.c.
+
+2004-08-04  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fixed cd - when OLDPWD is unset in src/cd.c.
+
+2004-07-09  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Include system.h (Gerrit Pape):
+	  . src/mystring.c
+	  . src/var.c
+	  . src/bltin/printf.c
+
+2004-07-03  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Release 0.5.1.
+	* Use automake and autoconf.
+	* Include stdlib.h for exit(3) in src/mksyntax.c.
+
+2004-06-29  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Added sigclearmask.
+	* Removed hack for _setjmp/_longjmp.
+	* Added default implementations of mempcpy/stpcpy/strchrnul.
+	* Use strtoll/strtoull if strtoimax/strtoumax are unavailable.
+	* Removed sh.1 to dash.1.
+
+2004-05-28  Herbert Xu <herbert@gondor.apana.org.au>
+
+	* Fixed vstype trim operator ordering in cmdputs.
+	* Fixed quote for CTLENDVAR in cmdputs.
+	* Fixed VSLENGTH crash in cmdputs.
+	* Turned vstype back into a 2-d array of chars.
+	* Added POSIX character class support in pmatch.
+
+For older ChangeLogs see ChangeLog.O.
+
+ChangeLog ends here
diff --git a/bin/dash/ChangeLog.O b/bin/dash/ChangeLog.O
new file mode 100644
index 00000000..dfdb2cec
--- /dev/null
+++ b/bin/dash/ChangeLog.O
@@ -0,0 +1,1023 @@
+dash (0.4.26) unstable; urgency=low
+
+  * Disabled fnmatch code again (closes: #240887).
+  * Updated copyright.
+  * Updated German debconf translation (Florian Ernst, closes: #244507).
+  * Fixed obstack corruption in setprompt (closes: #246635).
+
+ -- Herbert Xu <herbert@debian.org>  Fri, 30 Apr 2004 21:48:52 +1000
+
+dash (0.4.25) unstable; urgency=low
+
+  * Fixed use-after-free bug in setvareq (Vladimir N. Oleynik).
+  * Fixed value of expdest after _STPUTC in expandarg (closes: #238265).
+
+ -- Herbert Xu <herbert@debian.org>  Thu, 18 Mar 2004 20:55:57 +1100
+
+dash (0.4.24) unstable; urgency=low
+
+  * Fixed segmentation fault when PWD is undefined.
+
+ -- Herbert Xu <herbert@debian.org>  Tue,  9 Mar 2004 19:58:41 +1100
+
+dash (0.4.23) unstable; urgency=low
+
+  * Verify PWD before using it.
+
+ -- Herbert Xu <herbert@debian.org>  Mon,  8 Mar 2004 20:12:27 +1100
+
+dash (0.4.22) unstable; urgency=low
+
+  * Read PWD from environment (closes: #228659).
+  * Added Danish debconf translation (Claus Hindsgaul, closes: #233756).
+  * Added check_gcc to support gcc 2.95 (closes: #235933).
+  * Perform here-doc expansion on PS1/PS2/PS4 (closes: #230858).
+
+ -- Herbert Xu <herbert@debian.org>  Sun,  7 Mar 2004 21:50:04 +1100
+
+dash (0.4.21) unstable; urgency=low
+
+  * Fixed typo that broke ulimit (GCS, closes: #228369).
+
+ -- Herbert Xu <herbert@debian.org>  Mon, 19 Jan 2004 19:02:32 +1100
+
+dash (0.4.20) unstable; urgency=low
+
+  * Added Dutch debconf translation (Tim Dijkstra, closes: #218904).
+  * Check existence RLIMIT symbols for ulimit.
+  * Removed table lookup in errmsg.
+  * Restored NULL check in cmdtxt.
+  * Restored ash postrm to purge debconf entries (closes: #221913).
+  * Fixed exit status of exit in EXIT trap (closes: #227734).
+  * Updated Brazilian debconf translation (Andre Luis Lopes, closes: #228095).
+  * Restored goodname check in prehash.
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 17 Jan 2004 09:57:14 +1100
+
+dash (0.4.19) unstable; urgency=low
+
+  * Fixed handling of evalskip in dotcmd (closes: #212975).
+  * Updated Russian debconf translation (Ilgiz Kalmetev, closes: #214333).
+  * Added Portugese debconf translation (Bruno Rodrigues, closes: #216214).
+  * Updated Spanish debconf translation (Carlos Valdivia, closes: #216338).
+  * Fixed length expansion of special variables (closes: #216767).
+  * Replaced umask builtin with pdksh version.
+  * Reverted bogus eval change in 0.3.1-20.
+  * Added vmemory/locks support in ulimit.
+  * Call install -D instead of cp for merged template.
+
+ -- Herbert Xu <herbert@debian.org>  Wed, 29 Oct 2003 22:14:22 +1100
+
+dash (0.4.18) unstable; urgency=low
+
+  * Fixed boundary checks in getopts.
+  * Updated Japanese debconf template (Tomohiro KUBOTA, closes: #192382).
+  * Use -falign-* instead -malign-*.
+  * Use strtoll for parsing integers in arith expansion.
+  * Added support for add-shell/remove-shell (closes: #163131).
+  * Fixed JOBS ifdefs in sprint_status (closes: #211009).
+  * Fixed bit-wise or in arith expansion (Mototoshi KONDO, closes: #212825).
+  * Print PS4 on previous stderr.
+  * Converted debconf templates to gettext (Christian Perrier,
+    closes: #200112).
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 27 Sep 2003 14:26:36 +1000
+
+dash (0.4.17) unstable; urgency=low
+
+  * Reset rehash when recylcing cmd entries.
+  * Fixed null arg0 segfault with -c (closes: #191687).
+  * Relocate job pointers in makejob (closes: #191595).
+
+ -- Herbert Xu <herbert@debian.org>  Sat,  3 May 2003 20:57:46 +1000
+
+dash (0.4.16) unstable; urgency=low
+
+  * Fixed printf so that exit status is cleared on entry.
+  * Call nextopt() in printf.
+  * Fixed command substitution corruption by grabbing expdest in expbackq
+    (closes: #187896).
+
+ -- Herbert Xu <herbert@debian.org>  Mon,  7 Apr 2003 21:21:30 +1000
+
+dash (0.4.15) unstable; urgency=low
+
+  * Fixed octal escapes in echo/printf (closes: #187827).
+
+ -- Herbert Xu <herbert@debian.org>  Sun,  6 Apr 2003 20:45:38 +1000
+
+dash (0.4.14) unstable; urgency=low
+
+  * Added missing newline when printing in dowait.
+  * Do not print status in dowait when stopped.
+  * Fixed job status display.
+  * Fixed current job setting.
+  * Fixed kill segfault with no arguments (closes: #187189).
+  * Fixed sorting of set output.
+  * Removed setvar builtin.
+
+ -- Herbert Xu <herbert@debian.org>  Thu,  3 Apr 2003 20:38:33 +1000
+
+dash (0.4.13) unstable; urgency=low
+
+  * Fixed precision type on 64 bit systems in showvars.
+
+ -- Herbert Xu <herbert@debian.org>  Wed, 26 Mar 2003 20:00:03 +1100
+
+dash (0.4.12) unstable; urgency=low
+
+  * Fixed dash_errno build problem with gcc 3.2.
+
+ -- Herbert Xu <herbert@debian.org>  Tue, 25 Mar 2003 22:28:50 +1100
+
+dash (0.4.11) unstable; urgency=low
+
+  * Removed unused pgrp field from job structure.
+  * Free jobs when calling wait with no arguments.
+  * Fixed build problem with bison.
+  * Merged changes from NetBSD 20030123.
+   . printf:
+    - Fixed mklong sefault.
+    - Fixed precision/field width with %b.
+   . Improved option parsing of command(1).
+   . Added rudimentary support for PS4.
+  * Moved builtin flags into builtins.def.
+  * Updated Spanish debconf template (Carlos Valdivia Yagüe, closes: #178359).
+  * Fixed ordering of redirection versus assignment substitution.
+  * Fixed potential setvareq memory leaks.
+  * Use bison instead of byacc.
+  * Fixed wait(1) race condition.
+  * Fixed alignment memory corruption bug in growstackblock().
+  * Fixed potential memory corruption in parsing position parameters.
+  * Fixed getopts done check.
+
+ -- Herbert Xu <herbert@debian.org>  Mon, 24 Mar 2003 20:42:29 +1100
+
+dash (0.4.10) unstable; urgency=low
+
+  * Fixed redirection fd leak when execing.
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 19 Jan 2003 13:25:41 +1100
+
+dash (0.4.9) unstable; urgency=low
+
+  * Reset exitstatus in evalsubshell if backgnd is true.
+  * Fixed glibc glob syntax error in expand.c.
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 11 Jan 2003 16:04:02 +1100
+
+dash (0.4.8) unstable; urgency=low
+
+  * Removed backgnd flag from ncmd due to previous redirection change.
+  * Set lim after the stack stablises in updatepwd (closes: #173884).
+  * Do not clobber the exitstatus after redirection.
+
+ -- Herbert Xu <herbert@debian.org>  Mon, 23 Dec 2002 19:50:06 +1100
+
+dash (0.4.7) unstable; urgency=low
+
+  * Merged clearredir with reset code in redir.c.
+  * Redirect before command search in evalcommand (closes: #168862).
+  * Build binary-all packages in binary-indep (closes: #173191).
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 21 Dec 2002 13:52:37 +1100
+
+dash (0.4.6) unstable; urgency=low
+
+  * Restored code for leaving job control.
+
+ -- Herbert Xu <herbert@debian.org>  Sun,  8 Dec 2002 15:21:58 +1100
+
+dash (0.4.5) unstable; urgency=low
+
+  * Optimised doformat so that vsnprintf is usually called only once.
+  * Reset redirlist in clearredir so that popredir can work (closes: #170247).
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 23 Nov 2002 22:09:59 +1100
+
+dash (0.4.4) unstable; urgency=low
+
+  * Fixed duplicate define warnings in init.c.
+  * Set debhelper compat to 4.
+  * Vanishing mail boxes no longer elicit "you have mail" messages.
+  * Function redirection errors no longer abort the shell.
+  * Fixed potential memory leak in redirect.
+  * Only allocate memory if necessary in redirect.
+  * Reap dead here documents.
+  * Do not strdup default values of static shell variables.
+  * Removed unnecessary setprompt(0) calls.
+  * Read in BUFSIZ chunks rather than BUFSIZ - 1.
+  * Documented undefined escape behaviour for echo(1) (closes: #167893).
+  * Do va_copy when we use a va_list twice (closes: #169503).
+
+ -- Herbert Xu <herbert@debian.org>  Wed, 20 Nov 2002 19:48:31 +1100
+
+dash (0.4.3) unstable; urgency=low
+
+  * Added manual entry for PPID.
+  * Exporting an unset variable no longer causes it to be set.
+  * Fixed fd0 redirection in asynchronous lists.
+  * Only stat if necessary in cdcmd (see #42880).
+  * Removed extra newline in error message in arith lexer.
+  * Set heredoclist to 0 ASAP in parseheredoc.
+  * Removed BSD advertising clause from copyright file.
+  * Check non-ash diversions as well in dash.postinst.
+  * Duplicated diversion checking in ash.postinst (closes: #166441).
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 26 Oct 2002 21:28:33 +1000
+
+dash (0.4.2) unstable; urgency=low
+
+  * Give benefits of dash in templates (closes: #161527).
+  * Fixed signed/unsigned on result of xwrite (closes: #161606).
+  * Removed support for SIG prefixes in kill and trap.
+  * Added -- processing in trap.
+  * Dropped use of unset in postinst (closes: 161868).
+  * Fixed printf(1) %* processing on bad integers and zero.
+  * Use stat64 in test(1).
+  * Allocate group_array with stalloc in test(1).
+  * Disabled alias checking after a pattern in a case statement.
+  * Wait now returns 128 + last trapped signal.
+  * Printf now keeps going after errors.
+  * Empty non-trivial parameter expansions are now removed correctly.
+  * Call reset() before exitshell() is called.  This fixes the bug where
+    returning an error from a function running under set -e caused the exit
+    trap to be taken with evalskip set.
+  * Fixed quoting of empty strings in single_quote().
+  * Show line numbers on all errors.
+  * Function names must be valid identifiers.
+  * Removed unused dependency on groff.
+  * Fixed race condition before entering a function.
+  * Fixed getopts initialisation for functions.
+  * Added memory barriers in INT macros.
+  * Banned empty compound lists in most places.
+  * Keep usage counters on functions (closes: #164234).
+  * Updated copyright file.
+  * Check evalskip in evalstring (closes: #165056).
+  * Merged changes from NetBSD 1.6:
+   . Added intmax support in printf(1).
+   . Implemented set -u.
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 19 Oct 2002 14:23:11 +1000
+
+dash (0.4.1) unstable; urgency=low
+
+  * Removed extra new line in command -v output for aliases.
+  * Removed alais prefix in the output of alias.
+  * Recognise octal and hex numbers in arith expansion (closes: #151449).
+  * Added sh(1) entries for echo, printf and test (closes: #156446).
+  * Renamed to dash --- the Debian Almquist Shell.
+  * Cleaned up rules file (Matej Vela).
+  * Check mtime instead of size in chkmail per POSIX.
+  * Added support for LFS (closes: #157884).
+  * Added SuS options to cd and pwd (closes: #145828).
+
+ -- Herbert Xu <herbert@debian.org>  Fri, 13 Sep 2002 20:35:06 +1000
+
+ash (0.3.8-38) unstable; urgency=low
+
+  * Turned pre-dependency to dependency in udeb since the former is not allowed
+    (closes: #143749).
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 28 Apr 2002 11:59:05 +1000
+
+ash (0.3.8-37) unstable; urgency=low
+
+  * Added Japanese debconf translation (Tomohiro KUBOTA, closes: #137431).
+  * Added missing escapes in manual page (Aaron Schrab, closes: #137966).
+  * Added Russian debconf translation (Ilgiz Kalmetev, closes: #137618).
+  * Fixed trap(1) documentation (closes: #140973).
+  * Do not abort if getcwd fails.
+
+ -- Herbert Xu <herbert@debian.org>  Wed,  3 Apr 2002 20:58:09 +1000
+
+ash (0.3.8-36) unstable; urgency=low
+
+  * Added library dependency for ash-udeb.
+  * Handle null case statements correctly.
+  * Fixed alias expansions in case statements (NetBSD).
+  * Disabled unused jobid command.
+  * Corrected documentation about shifting too much.
+  * Added French debconf translation (Denis Barbier, closes: #134625).
+  * Updated Spanish debconf translation (Carlos Valdivia, closes: #136366).
+
+ -- Herbert Xu <herbert@debian.org>  Sat,  2 Mar 2002 18:31:22 +1100
+
+ash (0.3.8-35) unstable; urgency=low
+
+  * Moved PWD initialisation into var.c (closes: #124032).
+
+ -- Herbert Xu <herbert@debian.org>  Mon, 24 Dec 2001 09:34:55 +1100
+
+ash (0.3.8-34) unstable; urgency=low
+
+  * NSEMI must be NOR + 1.
+  * Set exitstatus to zero before evaluating cases (closes: #124066).
+  * Explicitly set default answer of the ash/sh question to false so that
+    people whose debconf priority is set to low and who keeps banging on their
+    keyboards don't accidently end up with ash as /bin/sh.
+
+ -- Herbert Xu <herbert@debian.org>  Fri, 21 Dec 2001 20:30:49 +1100
+
+ash (0.3.8-33) unstable; urgency=low
+
+  * Added missing inclusion of bltin.h in bltin/times.c.
+
+ -- Herbert Xu <herbert@debian.org>  Thu, 13 Dec 2001 18:46:07 +1100
+
+ash (0.3.8-32) unstable; urgency=low
+
+  * Back slashes in expansions are now escaped (closes: #121516).
+
+ -- Herbert Xu <herbert@debian.org>  Wed, 28 Nov 2001 20:15:01 +1100
+
+ash (0.3.8-31) unstable; urgency=low
+
+  * Made sure all back slashes are escaped.
+
+ -- Herbert Xu <herbert@debian.org>  Mon, 26 Nov 2001 19:10:27 +1100
+
+ash (0.3.8-30) unstable; urgency=low
+
+  * Restored fnmatch(3) code.
+  * Treat escaped slashes correctly while globbing.
+  * Restored missing EV_EXIT check in evalcommand (closes: #120364).
+  * Fixed stack corruption in _rmescapes.
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 25 Nov 2001 17:51:19 +1100
+
+ash (0.3.8-29) unstable; urgency=low
+
+  * Added missing va_end in fmtstr (NetBSD).
+  * Removed shellproc crap.
+  * Updated Swedish debconf translation (Mikael Hedin, closes: #116097).
+  * Updated German debconf translation (Andreas Metzler, closes: #117160).
+  * Break now treats illegal numbers according to SuS.
+  * Errors in special builtins now rise to the top.
+  * Normal redirection errors no longer abort the shell.
+  * Functions now have the same variable assignment properties as special
+    builtins.
+
+ -- Herbert Xu <herbert@debian.org>  Sat,  3 Nov 2001 11:36:36 +1100
+
+ash (0.3.8-28) unstable; urgency=low
+
+  * Local variables are now unset properly in shprocvar() (closes: #114917).
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 13 Oct 2001 14:07:21 +1000
+
+ash (0.3.8-27) unstable; urgency=low
+
+  * Kill no longer aborts if it fails to kill someone.
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 30 Sep 2001 22:20:36 +1000
+
+ash (0.3.8-26) unstable; urgency=low
+
+  * The sh.1.gz diversion now agrees with reality (closes: #113831).
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 29 Sep 2001 08:43:27 +1000
+
+ash (0.3.8-25) unstable; urgency=low
+
+  * Only read ENV if the shell is interactive (closes: #110421).
+
+ -- Herbert Xu <herbert@debian.org>  Wed, 29 Aug 2001 19:18:53 +1000
+
+ash (0.3.8-24) unstable; urgency=low
+
+  * Handle SIGINT when waiting even if there is no trap (closes: #107699).
+  * Protect all makejob/forkshell/waitforjobs sequences from SIGINT.
+  * Work around gcc bug that generates bad ..ng references (closes: #107994).
+
+ -- Herbert Xu <herbert@debian.org>  Wed,  8 Aug 2001 20:28:28 +1000
+
+ash (0.3.8-23) unstable; urgency=low
+
+  * Fixed fence post error in scanleft (closes: #107229).
+  * Removed stunalloc in expname as it interferes with addfname.
+  * Fixed CTLESC skipping in scanright.
+
+ -- Herbert Xu <herbert@debian.org>  Thu,  2 Aug 2001 20:06:00 +1000
+
+ash (0.3.8-22) unstable; urgency=low
+
+  * Fixed trailing back slash bug in echo/printf (closes: #106693).
+  * Some quoted's are meant to be quotes.
+  * Added Brazilian translation (Andre Luis Lopes, closes: #107041).
+
+ -- Herbert Xu <herbert@debian.org>  Mon, 30 Jul 2001 20:21:52 +1000
+
+ash (0.3.8-21) unstable; urgency=low
+
+  * Fixed EV_EXIT/redirection bugs that caused core dumps.
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 28 Jul 2001 17:03:28 +1000
+
+ash (0.3.8-20) unstable; urgency=low
+
+  * Don't save fd2 if job control is turned off.
+  * Don't push redirections when EV_EXIT is set.
+  * Fixed assignment recognition in the presence of back ticks.
+  * Combined checkkwd and checkalias.
+
+ -- Herbert Xu <herbert@debian.org>  Fri, 27 Jul 2001 22:29:41 +1000
+
+ash (0.3.8-19) unstable; urgency=low
+
+  * Recompute strings after growing in subevalvar (closes: #106050).
+
+ -- Herbert Xu <herbert@debian.org>  Mon, 23 Jul 2001 21:16:50 +1000
+
+ash (0.3.8-18) unstable; urgency=low
+
+  * Added more space optimisations for udeb on i386.
+  * Set stack mark in patmatch (closes: #106050).
+  * Fixed theoretical bug in expari.
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 21 Jul 2001 20:08:15 +1000
+
+ash (0.3.8-17) unstable; urgency=low
+
+  * Don't complain about unknown escape codes in echo and printf
+    (closes: #105659).
+  * Updated build-time dependency on groff-base (closes: #105612).
+
+ -- Herbert Xu <herbert@debian.org>  Wed, 18 Jul 2001 19:33:20 +1000
+
+ash (0.3.8-16) unstable; urgency=low
+
+  * Fixed backslash bug in new pattern matching code.
+
+ -- Herbert Xu <herbert@debian.org>  Mon, 16 Jul 2001 21:47:39 +1000
+
+ash (0.3.8-15) unstable; urgency=low
+
+  * Added Swedish translation of templates (Martin Sjögren, closes: #103158).
+  * Restored escape code support in echo.
+  * Removed assignment builtins since it is at best undefined by the SuS and
+    also can't be implemented consistently.
+  * Removed extraneous volatile modifier (closes: #104518).
+  * General overhaul of word expansion (closes: #96588).
+  * Redirection prefixes no longer stop assignments from being recognised.
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 15 Jul 2001 17:27:03 +1000
+
+ash (0.3.8-14) unstable; urgency=low
+
+  * Divert sh.1.gz to sh.distrib.1.gz (closes: #102251).
+  * Added HETIO support for ^D and ^U (Aaron Lehmann, closes: #102215).
+  * Added Spaniash translation of debconf templates (Carlos Valdivia Yagüe,
+    closes: #103040).
+  * Added versioned build-time dependency on groff.
+
+ -- Herbert Xu <herbert@debian.org>  Mon,  2 Jul 2001 19:32:03 +1000
+
+ash (0.3.8-13) unstable; urgency=low
+
+  * Fixed a bug where errors in pipelines which are part of andor lists were
+    not ignored when -e is in effect.
+
+ -- Herbert Xu <herbert@debian.org>  Mon, 25 Jun 2001 19:40:27 +1000
+
+ash (0.3.8-12) unstable; urgency=low
+
+  * Rewrote arith_lex.l in C (Aaron Lehmann, closes: #101741).
+  * && and || in arithmetic expansions now return either 0 or 1.
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 24 Jun 2001 20:14:29 +1000
+
+ash (0.3.8-11) unstable; urgency=low
+
+  * Check for NULL argument in evaltree() (closes: #98865, #98867).
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 27 May 2001 17:53:14 +1000
+
+ash (0.3.8-10) unstable; urgency=low
+
+  * Use /bin/ash in postinst to sidestep bugs in other shells (closes: #98739).
+  * Exit status is now tested on non-negated pipelines (closes: #98736).
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 26 May 2001 23:56:07 +1000
+
+ash (0.3.8-9) unstable; urgency=medium
+
+  * IFS is now fetched using bltinlookup() again in read (closes: #98343).
+  * Divert sh(1) man page as well as /bin/sh (closes: #98525).
+
+ -- Herbert Xu <herbert@debian.org>  Fri, 25 May 2001 20:30:06 +1000
+
+ash (0.3.8-8) unstable; urgency=low
+
+  * Fixed diversion removal in prerm (duh, closes: #98031).
+
+ -- Herbert Xu <herbert@debian.org>  Mon, 21 May 2001 20:52:48 +1000
+
+ash (0.3.8-7) unstable; urgency=low
+
+  * Fixed diversion test in prerm (closes: #98031).
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 20 May 2001 12:30:53 +1000
+
+ash (0.3.8-6) unstable; urgency=low
+
+  * Make sure that fd2 is closed when clearing redirects (closes: #96619).
+  * Fixed memory corruption in stunalloc().
+  * The output of export/readonly/set is now correctly quoted.
+  * Fixed newline eating bug in expbackq().
+  * Set OLDPWD.
+  * Removed ash-medium as neither bf or di uses it.
+  * Wait now waits for all its argument rather than the first one.
+  * Wait will exit with 129 when interrupted by a signal for a which a trap has
+    been set.
+
+ -- Herbert Xu <herbert@debian.org>  Fri, 18 May 2001 21:51:41 +1000
+
+ash (0.3.8-5) unstable; urgency=low
+
+  * Added German translation to template file (Sebastian Feltel,
+    closes: #96203).
+  * Added missing initialisation in setalias() (closes: #95433).
+
+ -- Herbert Xu <herbert@debian.org>  Fri,  4 May 2001 20:54:31 +1000
+
+ash (0.3.8-4) unstable; urgency=low
+
+  * Disabled fnmatch code as fnmatch(3) in glibc is broken.
+  * Fixed echo example in man page (Kalle Olavi Niemitalo, closes: #96014).
+  * Fixed trailing semicolon bug with eval (NetBSD).
+  * Fixed globbing inconsistency with broken symlinks (NetBSD).
+
+ -- Herbert Xu <herbert@debian.org>  Wed,  2 May 2001 22:57:16 +1000
+
+ash (0.3.8-3) unstable; urgency=low
+
+  * Work around broken autoconf scripts (closes: #95430).
+
+ -- Herbert Xu <herbert@debian.org>  Tue,  1 May 2001 18:27:50 +1000
+
+ash (0.3.8-2) unstable; urgency=low
+
+  * Save checkalias before calling xxreadtoken() (closes: #95628).
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 29 Apr 2001 17:36:01 +1000
+
+ash (0.3.8-1) unstable; urgency=low
+
+  * NetBSD-current version as of 20010316.
+  * Removed code that sets IFS.
+  * Fixed memory leak with PWD.
+  * Set PPID.
+  * Fixed inconsistencies in alias expansion.
+  * Restored original output code.
+  * Enabled fnmatch code again.
+  * Added builtin printf.
+  * Offer to divert /bin/sh (closes: #70462).
+
+ -- Herbert Xu <herbert@debian.org>  Wed, 25 Apr 2001 22:32:39 +1000
+
+ash (0.3.7-16) unstable; urgency=low
+
+  * Fixed incorrect default IFS in readcmd (closes: #88950).
+  * Added missing return in hashcmd.
+
+ -- Herbert Xu <herbert@debian.org>  Fri,  9 Mar 2001 20:44:40 +1100
+
+ash (0.3.7-15) unstable; urgency=low
+
+  * Unknown escape codes are now prnted literally by echo (closes: #82869).
+  * Made hetio_read_input() fail if fd is not stdin.
+  * Some uses of VSQUOTE were really meant to be quotes (closes: #88777).
+  * Build different ashes in different subdirectories.
+
+ -- Herbert Xu <herbert@debian.org>  Thu,  8 Mar 2001 21:32:28 +1100
+ 
+ash (0.3.7-14) unstable; urgency=low
+
+  * Removed predependency from udeb (closes: #81995).
+  * Added /bin/sh symlink to udeb (closes: #81967).
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 13 Jan 2001 15:23:21 +1100
+
+ash (0.3.7-13) unstable; urgency=low
+
+  * Renamed the udeb to ash-udeb.
+
+ -- Herbert Xu <herbert@debian.org>  Wed, 20 Dec 2000 19:32:34 +1100
+
+ash (0.3.7-12) unstable; urgency=low
+
+  * Added support for udebs (Randolph Chung, closes: #79237).
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 16 Dec 2000 13:53:28 +1100
+
+ash (0.3.7-11) unstable; urgency=low
+
+  * Preserve the previous exit status upon entering a function
+    (closes: #78374).
+
+ -- Herbert Xu <herbert@debian.org>  Sun,  3 Dec 2000 13:34:27 +1100
+
+ash (0.3.7-10) unstable; urgency=low
+
+  * Merged changes for GNU from Igor Khavkine.
+  * Minimise the number of sigactions.
+
+ -- Herbert Xu <herbert@debian.org>  Fri,  3 Nov 2000 20:31:52 +1100
+
+ash (0.3.7-9) unstable; urgency=low
+
+  * Predepend on the libraries.
+  * Always save fd 2 when it is redirected (closes: #75302).
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 22 Oct 2000 08:40:40 +1100
+
+ash (0.3.7-8) unstable; urgency=high
+
+  * More redirection fixes (closes: #73613).
+
+ -- Herbert Xu <herbert@debian.org>  Thu,  5 Oct 2000 18:22:17 +1100
+
+ash (0.3.7-7) unstable; urgency=high
+
+  * Added missing break in redirection code (closes: #72956).
+
+ -- Herbert Xu <herbert@debian.org>  Tue,  3 Oct 2000 07:58:04 +1100
+
+ash (0.3.7-6) unstable; urgency=low
+
+  * command -[vV] no longer displays an error message on stdout.
+  * Redirecting to /proc/self/fd/* now works (closes: #72852).
+
+ -- Herbert Xu <herbert@debian.org>  Sun,  1 Oct 2000 12:56:39 +1100
+
+ash (0.3.7-5) unstable; urgency=low
+
+  * Implemented set -a.
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 30 Sep 2000 16:00:33 +1100
+
+ash (0.3.7-4) unstable; urgency=low
+
+  * Added build-time dependency on debhelper (closes: #69920).
+  * Extended maximum length of arithmetic expansions to match 32-bit integers.
+
+ -- Herbert Xu <herbert@debian.org>  Wed, 20 Sep 2000 14:28:16 +1100
+
+ash (0.3.7-3) unstable; urgency=low
+
+  * Switch to the old globbing code since glob(3) is hopelessly broken
+    (closes: #69455).
+
+ -- Herbert Xu <herbert@debian.org>  Mon, 21 Aug 2000 20:37:15 +1000
+
+ash (0.3.7-2) unstable; urgency=low
+
+  * Call glob(3) with GLOB_NOMAGIC (ouch).
+
+ -- Herbert Xu <herbert@debian.org>  Sun,  6 Aug 2000 17:47:08 +1000
+
+ash (0.3.7-1) unstable; urgency=low
+
+  * NetBSD-current version as of 20000729.
+  * Use fnmatch(3) and glob(3).
+  * Fixed the use of backslashes in the pattern in parameter substitutions,
+    hopefully for the last time.
+  * Applied HETIO patch and built ash.medium (closes: #50788).  Will do ash.big
+    when readline is fixed so that it doesn't leak anymore.
+
+ -- Herbert Xu <herbert@debian.org>  Fri,  4 Aug 2000 21:36:44 +1000
+
+ash (0.3.6-5) unstable; urgency=low
+
+  * Fixed manpage entry for read with patch from Kevin Ryde (closes: #62500).
+  * Fixed a file descriptor leak for pipelines.
+
+ -- Herbert Xu <herbert@debian.org>  Wed, 19 Apr 2000 18:56:20 +1000
+
+ash (0.3.6-4) unstable; urgency=low
+
+  * Fixed the case of an empty command with redirections.
+
+ -- Herbert Xu <herbert@debian.org>  Fri,  7 Apr 2000 12:07:18 +1000
+
+ash (0.3.6-3) unstable; urgency=low
+
+  * ! is now recognised correctly.
+  * Ash is now more strict on the syntax, e.g., a lone ! is no longer accepted
+    as an alternative to ! true.
+
+ -- Herbert Xu <herbert@debian.org>  Fri,  7 Apr 2000 10:46:06 +1000
+
+ash (0.3.6-2) unstable; urgency=low
+
+  * Fixed a problem with fmtstr() which broke getopts.
+
+ -- Herbert Xu <herbert@debian.org>  Sun,  2 Apr 2000 10:49:26 +1000
+
+ash (0.3.6-1) unstable; urgency=low
+
+  * NetBSD-current version as of 20000326.
+  * Added a Build-Depends on groff (closes: #61041).
+  * Implemented noclobber (closes: #59028).
+  * Rewrote output.c to use stream IO.
+
+ -- Herbert Xu <herbert@debian.org>  Sat,  1 Apr 2000 19:24:31 +1000
+
+ash (0.3.5-10) frozen unstable; urgency=low
+
+  * Don't stat mail boxes in non-interactive mode (closes: #59213).
+  * Added an fflush(stdout) to the times builtin (closes: #59027).
+  * Documented the times builtin.
+  * Added source depends.
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 18 Mar 2000 18:58:44 +1100
+
+ash (0.3.5-9) unstable; urgency=low
+
+  * Double quotes inside paramater substitutions inside double quotes are now
+    ignored as in bash (the originial behaviour was POSIX compliant too but
+    IMHO this one makes a little bit more sense).
+    This one broke mwm (but it was actually mwm's fault).
+  * Corrected backslash/CTLESC treatment for patterns in parameter
+    substitutions.
+
+ -- Herbert Xu <herbert@debian.org>  Sat,  6 Nov 1999 18:13:19 +1100
+
+ash (0.3.5-8) unstable; urgency=low
+
+  * Replaced use of echo -n in manual page with escape codes.
+  * Made FHS compliant (closes: #47978).
+  * Restored echo's option processing ability.
+
+ -- Herbert Xu <herbert@debian.org>  Fri, 22 Oct 1999 10:20:58 +1000
+
+ash (0.3.5-7) unstable; urgency=low
+
+  * echo no longer supports options.
+  * Don't quote patterns inside parameter substitutions enclosed by double
+    quotes (closes: #47842).
+
+ -- Herbert Xu <herbert@debian.org>  Wed, 20 Oct 1999 20:28:14 +1000
+
+ash (0.3.5-6) unstable; urgency=low
+
+  * Use getcwd() instead of /bin/pwd -- Zack Weinberg (closes: #46981).
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 10 Oct 1999 16:31:49 +1000
+
+ash (0.3.5-5) unstable; urgency=low
+
+  * Only test for -e on simple commands (fixes #44559).
+
+ -- Herbert Xu <herbert@debian.org>  Wed,  8 Sep 1999 22:18:27 +1000
+
+ash (0.3.5-4) unstable; urgency=low
+
+  * Don't wait for stopped children if job control is disabled (fixes #42814).
+  * Allow an option '(' in a case statement (fixes #42364).
+
+ -- Herbert Xu <herbert@debian.org>  Thu, 12 Aug 1999 23:30:30 +1000
+
+ash (0.3.5-3) unstable; urgency=low
+
+  * OK, the fix to the esoteric problem in 0.3.5-1 actually breaks VSASSIGN
+    and VSQUESTION, they should work properly now (fixes #41327).
+
+ -- Herbert Xu <herbert@debian.org>  Thu, 15 Jul 1999 22:47:13 +1000
+
+ash (0.3.5-2) unstable; urgency=low
+
+  * PATH search and execution is now correct.
+  * hash no longer shows builtins.
+  * Added kill builtin.
+  * New description from James R. van Zandt reformatted by Josip Rodin.
+
+ -- Herbert Xu <herbert@debian.org>  Mon, 12 Jul 1999 18:51:42 +1000
+
+ash (0.3.5-1) unstable; urgency=low
+
+  * New upstream release.
+  * Adapted to new pmake (fixes #38737).
+  * Fixed behvaiour of backslashes preceding a closing brace for a parameter
+    substituion inside double quotes (even bash messes this one up :).
+  * Fixed command (fixes #34639).
+  * Fixed a pipe bug where stdin may be wrongly closed (fixes #35452).
+  * Revamped getopts (fixes #39694).
+
+ -- Herbert Xu <herbert@debian.org>  Sun,  4 Jul 1999 12:19:01 +1000
+
+ash (0.3.4-7) unstable; urgency=low
+
+  * Fixed a glibc 2.1 compatitibility problem.
+  * Fixed a PWD inconsistency that stuffed up the kernel compilation.
+
+ -- Herbert Xu <herbert@debian.org>  Mon, 17 May 1999 23:14:57 +1000
+
+ash (0.3.4-6) unstable; urgency=low
+
+  * Fixed incorrect -e test due to the last bug fix (fixes #26509).
+
+ -- Herbert Xu <herbert@debian.org>  Tue,  8 Sep 1998 10:02:46 +1000
+
+ash (0.3.4-5) unstable; urgency=low
+
+  * Use test_eaccess from bash instead of access(2) (fixes #26110).
+
+ -- Herbert Xu <herbert@debian.org>  Wed, 26 Aug 1998 21:22:49 +1000
+
+ash (0.3.4-4) unstable; urgency=low
+
+  * Only upload to unstable.
+
+ -- Herbert Xu <herbert@debian.org>  Tue,  5 May 1998 18:01:02 +1000
+
+ash (0.3.4-3) frozen unstable; urgency=low
+
+  * Applied sparc patch (fixes #21562).
+
+ -- Herbert Xu <herbert@debian.org>  Fri,  1 May 1998 19:48:13 +1000
+
+ash (0.3.4-2) frozen unstable; urgency=low
+
+  * Fixed the incorrect trap fixes (fixes #20363).
+
+ -- Herbert Xu <herbert@debian.org>  Thu, 16 Apr 1998 21:07:10 +1000
+
+ash (0.3.4-1) unstable; urgency=low
+
+  * New upstream release.
+  * Reverted word splitting change in 0.3.2-1 since the fix was broken and
+    major work (the quote removal is done too quickly at the moment) is needed
+    to fix it properly.
+  * Fixed more trap noncompliance.
+
+ -- Herbert Xu <herbert@debian.org>  Thu, 19 Mar 1998 22:59:12 +1100
+
+ash (0.3.2-5) unstable; urgency=low
+
+  * Fixed a bug when doing pattern matching in parameter expansions.
+
+ -- Herbert Xu <herbert@debian.org>  Tue, 10 Mar 1998 21:25:40 +1100
+
+ash (0.3.2-4) unstable; urgency=low
+
+  * Allow ] to be quoted in bracket expressions (fixes #17533).
+  * Move dh_fixperms to second last spot (fixes #18267).
+  * Don't do field splitting in evalfor.
+
+ -- Herbert Xu <herbert@debian.org>  Tue, 17 Feb 1998 13:32:09 +1100
+
+ash (0.3.2-3) unstable; urgency=low
+
+  * Fixed stupid core dump.
+
+ -- Herbert Xu <herbert@debian.org>  Wed, 11 Feb 1998 21:33:55 +1100
+
+ash (0.3.2-2) unstable; urgency=low
+
+  * Hack for special builtins (fixes #18055).
+  * Hack for command.
+
+ -- Herbert Xu <herbert@debian.org>  Wed, 11 Feb 1998 21:19:46 +1100
+
+ash (0.3.2-1) unstable; urgency=low
+
+  * NetBSD-current version as of 19980209.
+  * Fixed a word splitting problem after parameter expansion thanks to Alexey
+    Marinichev.
+  * Converted to debhelper (fixes #14612, #15005).
+
+ -- Herbert Xu <herbert@debian.org>  Mon,  9 Feb 1998 16:53:48 +1100
+
+ash (0.3.1-20) unstable; urgency=low
+
+  * Fixed -e problem with eval.
+
+ -- Herbert Xu <herbert@debian.org>  Sun,  7 Dec 1997 20:19:00 +1100
+
+ash (0.3.1-19) unstable; urgency=low
+
+  * Fixed -e problem with command substitution.
+
+ -- Herbert Xu <herbert@debian.org>  Sun,  7 Dec 1997 19:44:49 +1100
+
+ash (0.3.1-18) unstable; urgency=low
+
+  * Do not link with ncurses (#15485).
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 30 Nov 1997 12:00:11 +1100
+
+ash (0.3.1-17) unstable; urgency=low
+
+  * Set PATH like bash (#15238).
+
+ -- Herbert Xu <herbert@debian.org>  Wed, 26 Nov 1997 16:17:27 +1100
+
+ash (0.3.1-16) unstable; urgency=low
+
+  * Fixed incorrect assignment builtin code.
+
+ -- Herbert Xu <herbert@debian.org>  Mon, 24 Nov 1997 16:19:10 +1100
+
+ash (0.3.1-15) unstable; urgency=low
+
+  * hash now returns error codes (needed by the Linux kernel).
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 23 Nov 1997 21:37:08 +1100
+
+ash (0.3.1-14) unstable; urgency=low
+
+  * Disabled word-splitting for assignment builtins.
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 23 Nov 1997 12:45:15 +1100
+
+ash (0.3.1-13) unstable; urgency=low
+
+  * ! is now recognised even after &&/||.
+
+ -- Herbert Xu <herbert@debian.org>  Fri, 21 Nov 1997 22:09:05 +1100
+
+ash (0.3.1-12) unstable; urgency=low
+
+  * More fixes to the handling of SIGINT when forking.
+
+ -- Herbert Xu <herbert@debian.org>  Fri, 14 Nov 1997 15:14:32 +1100
+
+ash (0.3.1-11) unstable; urgency=low
+
+  * Ignore SIGINT when forking non-interactively.
+
+ -- Herbert Xu <herbert@debian.org>  Mon,  3 Nov 1997 12:00:02 +1100
+
+ash (0.3.1-10) unstable; urgency=low
+
+  * echo now handles options correctly.
+  * echo nolonger returns 0 if erorrs occured while writing to stdout.
+  * New code from GNU echo merged.
+  * Error messages from test now work.
+
+ -- Herbert Xu <herbert@debian.org>  Wed,  8 Oct 1997 21:47:13 +1000
+
+ash (0.3.1-9) unstable; urgency=low
+
+  * ! is recognised at pipeline level like bash.
+
+ -- Herbert Xu <herbert@debian.org>  Mon, 15 Sep 1997 23:13:45 +1000
+
+ash (0.3.1-8) unstable; urgency=medium
+
+  * Old patch regarding SIGCHLD in again.
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 31 Aug 1997 11:20:27 +1000
+
+ash (0.3.1-7) unstable; urgency=low
+
+  * /bin/sh -e is behaving even better now (for loops within conditionals).
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 23 Aug 1997 22:08:19 +1000
+
+ash (0.3.1-6) unstable; urgency=low
+
+  * /bin/sh -e is behaving better now.
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 23 Aug 1997 13:16:26 +1000
+
+ash (0.3.1-5) unstable; urgency=low
+
+  * hash -v /dir/command doesn't coredump anymore.
+  * type /dir/command now works correctly.
+
+ -- Herbert Xu <herbert@debian.org>  Fri,  1 Aug 1997 20:48:19 +1000
+
+ash (0.3.1-4) unstable; urgency=low
+
+  * trap now understands symbolic signal names.
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 26 Jul 1997 14:04:46 +1000
+
+ash (0.3.1-3) unstable; urgency=low
+
+  * Added the builtin test command.
+
+ -- Herbert Xu <herbert@debian.org>  Sun, 20 Jul 1997 15:00:14 +1000
+
+ash (0.3.1-2) unstable; urgency=medium
+
+  * Fixed a coredump involving $*.
+
+ -- Herbert Xu <herbert@debian.org>  Sat, 19 Jul 1997 12:03:02 +1000
+
+ash (0.3.1-1) unstable; urgency=medium
+
+  * NetBSD-current version as of 19970715.
+  * Fixed a "use after free" bug (#11294).
+
+ -- Herbert Xu <herbert@debian.org>  Fri, 18 Jul 1997 13:48:09 +1000
+
+ash (0.3-1) unstable; urgency=low
+
+  * Initial Release.
+
+ -- Herbert Xu <herbert@debian.org>  Thu, 19 Jun 1997 19:29:16 +1000
+
diff --git a/bin/dash/Makefile.am b/bin/dash/Makefile.am
new file mode 100644
index 00000000..af437a64
--- /dev/null
+++ b/bin/dash/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = src
diff --git a/bin/dash/autogen.sh b/bin/dash/autogen.sh
new file mode 100755
index 00000000..9879c53e
--- /dev/null
+++ b/bin/dash/autogen.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+aclocal \
+&& autoheader \
+&& automake --add-missing \
+&& autoconf
diff --git a/bin/dash/configure.ac b/bin/dash/configure.ac
new file mode 100644
index 00000000..1edc45b8
--- /dev/null
+++ b/bin/dash/configure.ac
@@ -0,0 +1,194 @@
+AC_INIT([dash],[0.5.11.2])
+AM_INIT_AUTOMAKE([foreign subdir-objects])
+AC_CONFIG_SRCDIR([src/main.c])
+
+AC_CONFIG_HEADERS(config.h)
+
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES(yes)])
+
+dnl Checks for programs.
+AC_PROG_CC
+AC_USE_SYSTEM_EXTENSIONS
+
+AC_MSG_CHECKING([for build system compiler])
+if test "$cross_compiling" = yes; then
+	CC_FOR_BUILD=${CC_FOR_BUILD-cc}
+else
+	CC_FOR_BUILD=${CC}
+fi
+AC_MSG_RESULT(${CC_FOR_BUILD})
+AC_SUBST(CC_FOR_BUILD)
+
+AC_MSG_CHECKING([for __attribute__((__alias__()))])
+dash_cv_have_attribute_alias=no
+AC_LINK_IFELSE([AC_LANG_PROGRAM([void t() {}
+                                 void a() __attribute__((__alias__("t")));],
+                                [a();])],
+               [dash_cv_have_attribute_alias=yes])
+AC_MSG_RESULT($dash_cv_have_attribute_alias)
+if test "x$dash_cv_have_attribute_alias" = xyes; then
+  AC_DEFINE([HAVE_ALIAS_ATTRIBUTE], 1,
+            [Define if __attribute__((__alias__())) is supported])
+fi
+
+AC_ARG_ENABLE(static, AS_HELP_STRING(--enable-static, \
+				     [Build statical linked program]))
+if test "$enable_static" = "yes"; then
+	export LDFLAGS="-static -Wl,--fatal-warnings"
+fi
+
+AC_ARG_ENABLE(fnmatch, AS_HELP_STRING(--enable-fnmatch, \
+				      [Use fnmatch(3) from libc]))
+AC_ARG_ENABLE(glob, AS_HELP_STRING(--enable-glob, [Use glob(3) from libc]))
+
+dnl Checks for libraries.
+
+dnl Checks for header files.
+AC_CHECK_HEADERS(alloca.h paths.h)
+
+dnl Check for declarations
+AC_CHECK_DECL([_PATH_BSHELL],,AC_DEFINE_UNQUOTED([_PATH_BSHELL], "/bin/sh", [Define to system shell path]),[
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+])
+AC_CHECK_DECL([_PATH_DEVNULL],,AC_DEFINE_UNQUOTED([_PATH_DEVNULL], "/dev/null", [Define to devnull device node path]),[
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+])
+AC_CHECK_DECL([_PATH_TTY],,AC_DEFINE_UNQUOTED([_PATH_TTY], "/dev/tty", [Define to tty device node path]),[
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+])
+
+dnl Some systems lack isblank
+AC_CHECK_DECLS([isblank],,,[#include <ctype.h>])
+
+dnl Check for sizes of types
+AC_CHECK_SIZEOF([intmax_t])
+AC_CHECK_SIZEOF([long long int])
+
+dnl Select a fallback format string for intmax_t in case we don't find PRIdMAX
+if test "x$ac_cv_sizeof_intmax_t" = "x$ac_cv_sizeof_long_long_int"; then
+  intmax_fstr="lld"
+else
+  intmax_fstr="jd"
+fi
+
+dnl Check for PRIdMAX and define it to a fallback if not found
+AC_CHECK_DECL([PRIdMAX],,
+	[AC_DEFINE_UNQUOTED([PRIdMAX], "$intmax_fstr",
+				       [Define to printf format string for intmax_t])],
+        [
+#include <inttypes.h>
+])
+
+dnl Checks for library functions.
+AC_CHECK_FUNCS(bsearch faccessat getpwnam getrlimit isalpha killpg \
+	       mempcpy \
+	       sigsetmask stpcpy strchrnul strsignal strtod strtoimax \
+	       strtoumax sysconf)
+
+dnl Check whether it's worth working around FreeBSD PR kern/125009.
+dnl The traditional behavior of access/faccessat is crazy, but
+dnl POSIX.1-2008 explicitly allows those functions to misbehave.
+dnl
+dnl Unaffected kernels:
+dnl
+dnl - all versions of Linux
+dnl - NetBSD sys/kern/vfs_subr.c 1.64, 1997-04-23
+dnl - FreeBSD 9 (r212002), 2010-09-10
+dnl - OpenBSD sys/kern/vfs_subr.c 1.166, 2008-06-09
+dnl
+dnl Also worked around in Debian's libc0.1 2.13-19 when using
+dnl kFreeBSD 8.
+
+AC_ARG_ENABLE(test-workaround, AS_HELP_STRING(--enable-test-workaround, \
+	[Guard against faccessat(2) that tells root all files are executable]),,
+	[enable_test_workaround=auto])
+
+if test "enable_test_workaround" = "auto" &&
+   test "$ac_cv_func_faccessat" = yes; then
+	case `uname -s 2>/dev/null` in
+	GNU/kFreeBSD | \
+	FreeBSD)
+		enable_test_workaround=yes
+	esac
+fi
+if test "$enable_test_workaround" = "yes"; then
+	AC_DEFINE([HAVE_TRADITIONAL_FACCESSAT], [1],
+		[Define if your faccessat tells root all files are executable])
+fi
+
+if test "$enable_fnmatch" = yes; then
+	use_fnmatch=
+	AC_CHECK_FUNCS(fnmatch, use_fnmatch=yes)
+fi
+
+if test "$use_fnmatch" = yes && test "$enable_glob" = yes; then
+	AC_CHECK_FUNCS(glob)
+fi
+
+dnl Check for klibc signal.
+AC_CHECK_FUNC(signal)
+if test "$ac_cv_func_signal" != yes; then
+	AC_CHECK_FUNC(bsd_signal,
+		      [AC_DEFINE(signal, bsd_signal,
+				 [klibc has bsd_signal instead of signal])])
+fi
+
+dnl Check for stat64 (dietlibc/klibc).
+AC_CHECK_FUNC(stat64,, [
+	AC_DEFINE(fstat64, fstat, [64-bit operations are the same as 32-bit])
+	AC_DEFINE(lstat64, lstat, [64-bit operations are the same as 32-bit])
+	AC_DEFINE(stat64, stat, [64-bit operations are the same as 32-bit])
+])
+
+dnl OS X apparently has stat64 but not open64.
+AC_CHECK_FUNC(open64,, [
+	AC_DEFINE(open64, open, [64-bit operations are the same as 32-bit])
+	AC_DEFINE(readdir64, readdir,
+		  [64-bit operations are the same as 32-bit])
+	AC_DEFINE(dirent64, dirent,
+		  [64-bit operations are the same as 32-bit])
+])
+
+dnl Check if struct stat has st_mtim.
+AC_MSG_CHECKING(for stat::st_mtim)
+AC_COMPILE_IFELSE(
+[AC_LANG_PROGRAM([#include <time.h>
+#include <sys/time.h>
+#include <sys/stat.h>],
+[struct stat foo; return sizeof(foo.st_mtim.tv_sec)])],
+have_st_mtim=yes, have_st_mtim=no)
+AC_MSG_RESULT($have_st_mtim)
+if test "$have_st_mtim" = "yes"; then
+	AC_DEFINE([HAVE_ST_MTIM], [1],
+		[Define if your `struct stat' has `st_mtim'])
+fi
+
+AC_ARG_WITH(libedit,
+	AS_HELP_STRING(--without-libedit, [Compile without libedit support]))
+use_libedit=
+if test "$with_libedit" != "no"; then
+	AC_CHECK_LIB(edit, history_init, [
+		AC_CHECK_HEADER([histedit.h], [use_libedit="yes"],
+				AC_MSG_ERROR(
+					[Can't find required header files.]))], [
+		AC_MSG_ERROR([Can't find libedit.])])
+fi
+if test "$use_libedit" != "yes"; then
+	AC_DEFINE([SMALL], 1, [Define if you build with -DSMALL])
+else
+	export LIBS="$LIBS -ledit"
+	AC_CHECK_FUNCS(_el_fn_sh_complete)
+fi
+AC_ARG_ENABLE(lineno, AS_HELP_STRING(--disable-lineno, \
+				     [Disable LINENO support]))
+if test "$enable_lineno" != "no"; then
+	AC_DEFINE([WITH_LINENO], 1, [Define if you build with -DWITH_LINENO])
+fi
+AC_CONFIG_FILES([Makefile src/Makefile])
+AC_OUTPUT
diff --git a/bin/dash/src/.gitignore b/bin/dash/src/.gitignore
new file mode 100644
index 00000000..644eccb8
--- /dev/null
+++ b/bin/dash/src/.gitignore
@@ -0,0 +1,13 @@
+*.o
+builtins.[ch]
+builtins.def
+dash
+init.c
+mkinit
+mknodes
+mksignames
+mksyntax
+nodes.[ch]
+signames.c
+syntax.[ch]
+token.h
diff --git a/bin/dash/src/Makefile.am b/bin/dash/src/Makefile.am
new file mode 100644
index 00000000..fb8229f1
--- /dev/null
+++ b/bin/dash/src/Makefile.am
@@ -0,0 +1,70 @@
+AM_YFLAGS = -d
+
+COMMON_CFLAGS = -Wall
+COMMON_CPPFLAGS = \
+	-DBSD=1 -DSHELL
+
+AM_CFLAGS = $(COMMON_CFLAGS)
+AM_CPPFLAGS = -include $(top_builddir)/config.h $(COMMON_CPPFLAGS)
+AM_CFLAGS_FOR_BUILD = -g -O2 $(COMMON_CFLAGS) 
+AM_CPPFLAGS_FOR_BUILD = $(COMMON_CPPFLAGS)
+
+COMPILE_FOR_BUILD = \
+	$(CC_FOR_BUILD) $(DEFAULT_INCLUDES) $(AM_CPPFLAGS_FOR_BUILD) \
+	$(CPPFLAGS_FOR_BUILD) \
+	$(AM_CFLAGS_FOR_BUILD) $(CFLAGS_FOR_BUILD) 
+
+bin_PROGRAMS = dash
+
+dash_CFILES = \
+	alias.c arith_yacc.c arith_yylex.c cd.c error.c eval.c exec.c expand.c \
+	histedit.c input.c jobs.c mail.c main.c memalloc.c miscbltin.c \
+	mystring.c options.c parser.c redir.c show.c trap.c output.c \
+	bltin/printf.c system.c bltin/test.c bltin/times.c var.c
+dash_SOURCES = \
+	$(dash_CFILES) \
+	alias.h arith_yacc.h bltin/bltin.h cd.h error.h eval.h exec.h \
+	expand.h \
+	init.h input.h jobs.h machdep.h mail.h main.h memalloc.h miscbltin.h \
+	myhistedit.h mystring.h options.h output.h parser.h redir.h shell.h \
+	show.h system.h trap.h var.h
+dash_LDADD = builtins.o init.o nodes.o signames.o syntax.o
+
+HELPERS = mkinit mksyntax mknodes mksignames
+
+BUILT_SOURCES = builtins.h nodes.h syntax.h token.h token_vars.h
+CLEANFILES = \
+	$(BUILT_SOURCES) $(dash_LDADD:%.o=%.c) \
+	$(HELPERS) builtins.def
+
+man_MANS = dash.1
+EXTRA_DIST = \
+	$(man_MANS) \
+	mktokens mkbuiltins builtins.def.in mkinit.c \
+	mknodes.c nodetypes nodes.c.pat mksyntax.c mksignames.c
+
+token.h token_vars.h: mktokens
+	$(AM_V_GEN)$(SHELL) mktokens
+
+builtins.def: builtins.def.in $(top_builddir)/config.h
+	$(AM_V_CC)$(COMPILE) -E -x c -o $@ builtins.def.in
+
+builtins.c builtins.h: mkbuiltins builtins.def
+	$(AM_V_GEN)$(SHELL) mkbuiltins builtins.def
+
+init.c: mkinit $(dash_CFILES)
+	$(AM_V_GEN)./mkinit $(dash_CFILES)
+
+nodes.c nodes.h: mknodes nodetypes nodes.c.pat
+	$(AM_V_GEN)./mknodes nodetypes nodes.c.pat
+
+syntax.c syntax.h: mksyntax
+	$(AM_V_GEN)./mksyntax
+
+signames.c: mksignames
+	$(AM_V_GEN)./mksignames
+
+mksyntax: token.h
+
+$(HELPERS):
+	$(AM_V_CC)$(COMPILE_FOR_BUILD) -o $@ $@.c
diff --git a/bin/dash/src/TOUR b/bin/dash/src/TOUR
new file mode 100644
index 00000000..e30836e1
--- /dev/null
+++ b/bin/dash/src/TOUR
@@ -0,0 +1,343 @@
+#	@(#)TOUR	8.1 (Berkeley) 5/31/93
+
+NOTE -- This is the original TOUR paper distributed with ash and
+does not represent the current state of the shell.  It is provided anyway
+since it provides helpful information for how the shell is structured,
+but be warned that things have changed -- the current shell is
+still under development.
+
+================================================================
+
+                       A Tour through Ash
+
+               Copyright 1989 by Kenneth Almquist.
+
+
+DIRECTORIES:  The subdirectory bltin contains commands which can
+be compiled stand-alone.  The rest of the source is in the main
+ash directory.
+
+SOURCE CODE GENERATORS:  Files whose names begin with "mk" are
+programs that generate source code.  A complete list of these
+programs is:
+
+        program         intput files        generates
+        -------         ------------        ---------
+        mkbuiltins      builtins            builtins.h builtins.c
+        mkinit          *.c                 init.c
+        mknodes         nodetypes           nodes.h nodes.c
+        mksignames          -               signames.h signames.c
+        mksyntax            -               syntax.h syntax.c
+        mktokens            -               token.h
+        bltin/mkexpr    unary_op binary_op  operators.h operators.c
+
+There are undoubtedly too many of these.  Mkinit searches all the
+C source files for entries looking like:
+
+        INIT {
+              x = 1;    /* executed during initialization */
+        }
+
+        RESET {
+              x = 2;    /* executed when the shell does a longjmp
+                           back to the main command loop */
+        }
+
+It pulls this code out into routines which are when particular
+events occur.  The intent is to improve modularity by isolating
+the information about which modules need to be explicitly
+initialized/reset within the modules themselves.
+
+Mkinit recognizes several constructs for placing declarations in
+the init.c file.
+        INCLUDE "file.h"
+includes a file.  The storage class MKINIT makes a declaration
+available in the init.c file, for example:
+        MKINIT int funcnest;    /* depth of function calls */
+MKINIT alone on a line introduces a structure or union declara-
+tion:
+        MKINIT
+        struct redirtab {
+              short renamed[10];
+        };
+Preprocessor #define statements are copied to init.c without any
+special action to request this.
+
+INDENTATION:  The ash source is indented in multiples of six
+spaces.  The only study that I have heard of on the subject con-
+cluded that the optimal amount to indent is in the range of four
+to six spaces.  I use six spaces since it is not too big a jump
+from the widely used eight spaces.  If you really hate six space
+indentation, use the adjind (source included) program to change
+it to something else.
+
+EXCEPTIONS:  Code for dealing with exceptions appears in
+exceptions.c.  The C language doesn't include exception handling,
+so I implement it using setjmp and longjmp.  The global variable
+exception contains the type of exception.  EXERROR is raised by
+calling error.  EXINT is an interrupt.
+
+INTERRUPTS:  In an interactive shell, an interrupt will cause an
+EXINT exception to return to the main command loop.  (Exception:
+EXINT is not raised if the user traps interrupts using the trap
+command.)  The INTOFF and INTON macros (defined in exception.h)
+provide uninterruptable critical sections.  Between the execution
+of INTOFF and the execution of INTON, interrupt signals will be
+held for later delivery.  INTOFF and INTON can be nested.
+
+MEMALLOC.C:  Memalloc.c defines versions of malloc and realloc
+which call error when there is no memory left.  It also defines a
+stack oriented memory allocation scheme.  Allocating off a stack
+is probably more efficient than allocation using malloc, but the
+big advantage is that when an exception occurs all we have to do
+to free up the memory in use at the time of the exception is to
+restore the stack pointer.  The stack is implemented using a
+linked list of blocks.
+
+STPUTC:  If the stack were contiguous, it would be easy to store
+strings on the stack without knowing in advance how long the
+string was going to be:
+        p = stackptr;
+        *p++ = c;       /* repeated as many times as needed */
+        stackptr = p;
+The following three macros (defined in memalloc.h) perform these
+operations, but grow the stack if you run off the end:
+        STARTSTACKSTR(p);
+        STPUTC(c, p);   /* repeated as many times as needed */
+        grabstackstr(p);
+
+We now start a top-down look at the code:
+
+MAIN.C:  The main routine performs some initialization, executes
+the user's profile if necessary, and calls cmdloop.  Cmdloop is
+repeatedly parses and executes commands.
+
+OPTIONS.C:  This file contains the option processing code.  It is
+called from main to parse the shell arguments when the shell is
+invoked, and it also contains the set builtin.  The -i and -j op-
+tions (the latter turns on job control) require changes in signal
+handling.  The routines setjobctl (in jobs.c) and setinteractive
+(in trap.c) are called to handle changes to these options.
+
+PARSING:  The parser code is all in parser.c.  A recursive des-
+cent parser is used.  Syntax tables (generated by mksyntax) are
+used to classify characters during lexical analysis.  There are
+three tables:  one for normal use, one for use when inside single
+quotes, and one for use when inside double quotes.  The tables
+are machine dependent because they are indexed by character vari-
+ables and the range of a char varies from machine to machine.
+
+PARSE OUTPUT:  The output of the parser consists of a tree of
+nodes.  The various types of nodes are defined in the file node-
+types.
+
+Nodes of type NARG are used to represent both words and the con-
+tents of here documents.  An early version of ash kept the con-
+tents of here documents in temporary files, but keeping here do-
+cuments in memory typically results in significantly better per-
+formance.  It would have been nice to make it an option to use
+temporary files for here documents, for the benefit of small
+machines, but the code to keep track of when to delete the tem-
+porary files was complex and I never fixed all the bugs in it.
+(AT&T has been maintaining the Bourne shell for more than ten
+years, and to the best of my knowledge they still haven't gotten
+it to handle temporary files correctly in obscure cases.)
+
+The text field of a NARG structure points to the text of the
+word.  The text consists of ordinary characters and a number of
+special codes defined in parser.h.  The special codes are:
+
+        CTLVAR              Variable substitution
+        CTLENDVAR           End of variable substitution
+        CTLBACKQ            Command substitution
+        CTLESC              Escape next character
+
+A variable substitution contains the following elements:
+
+        CTLVAR type name '=' [ alternative-text CTLENDVAR ]
+
+The type field is a single character specifying the type of sub-
+stitution.  The possible types are:
+
+        VSNORMAL            $var
+        VSMINUS             ${var-text}
+        VSMINUS|VSNUL       ${var:-text}
+        VSPLUS              ${var+text}
+        VSPLUS|VSNUL        ${var:+text}
+        VSQUESTION          ${var?text}
+        VSQUESTION|VSNUL    ${var:?text}
+        VSASSIGN            ${var=text}
+        VSASSIGN|VSNUL      ${var=text}
+
+The name of the variable comes next, terminated by an equals
+sign.  If the type is not VSNORMAL, then the text field in the
+substitution follows, terminated by a CTLENDVAR byte.
+
+Commands in back quotes are parsed and stored in a linked list.
+The locations of these commands in the string are indicated by
+the CTLBACKQ character.
+
+The character CTLESC escapes the next character, so that in case
+any of the CTL characters mentioned above appear in the input,
+they can be passed through transparently.  CTLESC is also used to
+escape '*', '?', '[', and '!' characters which were quoted by the
+user and thus should not be used for file name generation.
+
+CTLESC characters have proved to be particularly tricky to get
+right.  In the case of here documents which are not subject to
+variable and command substitution, the parser doesn't insert any
+CTLESC characters to begin with (so the contents of the text
+field can be written without any processing).  Other here docu-
+ments, and words which are not subject to splitting and file name
+generation, have the CTLESC characters removed during the vari-
+able and command substitution phase.  Words which are subject
+splitting and file name generation have the CTLESC characters re-
+moved as part of the file name phase.
+
+EXECUTION:  Command execution is handled by the following files:
+        eval.c     The top level routines.
+        redir.c    Code to handle redirection of input and output.
+        jobs.c     Code to handle forking, waiting, and job control.
+        exec.c     Code to path searches and the actual exec sys call.
+        expand.c   Code to evaluate arguments.
+        var.c      Maintains the variable symbol table.  Called from expand.c.
+
+EVAL.C:  Evaltree recursively executes a parse tree.  The exit
+status is returned in the global variable exitstatus.  The alter-
+native entry evalbackcmd is called to evaluate commands in back
+quotes.  It saves the result in memory if the command is a buil-
+tin; otherwise it forks off a child to execute the command and
+connects the standard output of the child to a pipe.
+
+JOBS.C:  To create a process, you call makejob to return a job
+structure, and then call forkshell (passing the job structure as
+an argument) to create the process.  Waitforjob waits for a job
+to complete.  These routines take care of process groups if job
+control is defined.
+
+REDIR.C:  Ash allows file descriptors to be redirected and then
+restored without forking off a child process.  This is accom-
+plished by duplicating the original file descriptors.  The redir-
+tab structure records where the file descriptors have be dupli-
+cated to.
+
+EXEC.C:  The routine find_command locates a command, and enters
+the command in the hash table if it is not already there.  The
+third argument specifies whether it is to print an error message
+if the command is not found.  (When a pipeline is set up,
+find_command is called for all the commands in the pipeline be-
+fore any forking is done, so to get the commands into the hash
+table of the parent process.  But to make command hashing as
+transparent as possible, we silently ignore errors at that point
+and only print error messages if the command cannot be found
+later.)
+
+The routine shellexec is the interface to the exec system call.
+
+EXPAND.C:  Arguments are processed in three passes.  The first
+(performed by the routine argstr) performs variable and command
+substitution.  The second (ifsbreakup) performs word splitting
+and the third (expandmeta) performs file name generation.  If the
+"/u" directory is simulated, then when "/u/username" is replaced
+by the user's home directory, the flag "didudir" is set.  This
+tells the cd command that it should print out the directory name,
+just as it would if the "/u" directory were implemented using
+symbolic links.
+
+VAR.C:  Variables are stored in a hash table.  Probably we should
+switch to extensible hashing.  The variable name is stored in the
+same string as the value (using the format "name=value") so that
+no string copying is needed to create the environment of a com-
+mand.  Variables which the shell references internally are preal-
+located so that the shell can reference the values of these vari-
+ables without doing a lookup.
+
+When a program is run, the code in eval.c sticks any environment
+variables which precede the command (as in "PATH=xxx command") in
+the variable table as the simplest way to strip duplicates, and
+then calls "environment" to get the value of the environment.
+There are two consequences of this.  First, if an assignment to
+PATH precedes the command, the value of PATH before the assign-
+ment must be remembered and passed to shellexec.  Second, if the
+program turns out to be a shell procedure, the strings from the
+environment variables which preceded the command must be pulled
+out of the table and replaced with strings obtained from malloc,
+since the former will automatically be freed when the stack (see
+the entry on memalloc.c) is emptied.
+
+BUILTIN COMMANDS:  The procedures for handling these are scat-
+tered throughout the code, depending on which location appears
+most appropriate.  They can be recognized because their names al-
+ways end in "cmd".  The mapping from names to procedures is
+specified in the file builtins, which is processed by the mkbuil-
+tins command.
+
+A builtin command is invoked with argc and argv set up like a
+normal program.  A builtin command is allowed to overwrite its
+arguments.  Builtin routines can call nextopt to do option pars-
+ing.  This is kind of like getopt, but you don't pass argc and
+argv to it.  Builtin routines can also call error.  This routine
+normally terminates the shell (or returns to the main command
+loop if the shell is interactive), but when called from a builtin
+command it causes the builtin command to terminate with an exit
+status of 2.
+
+The directory bltins contains commands which can be compiled in-
+dependently but can also be built into the shell for efficiency
+reasons.  The makefile in this directory compiles these programs
+in the normal fashion (so that they can be run regardless of
+whether the invoker is ash), but also creates a library named
+bltinlib.a which can be linked with ash.  The header file bltin.h
+takes care of most of the differences between the ash and the
+stand-alone environment.  The user should call the main routine
+"main", and #define main to be the name of the routine to use
+when the program is linked into ash.  This #define should appear
+before bltin.h is included; bltin.h will #undef main if the pro-
+gram is to be compiled stand-alone.
+
+CD.C:  This file defines the cd and pwd builtins.  The pwd com-
+mand runs /bin/pwd the first time it is invoked (unless the user
+has already done a cd to an absolute pathname), but then
+remembers the current directory and updates it when the cd com-
+mand is run, so subsequent pwd commands run very fast.  The main
+complication in the cd command is in the docd command, which
+resolves symbolic links into actual names and informs the user
+where the user ended up if he crossed a symbolic link.
+
+SIGNALS:  Trap.c implements the trap command.  The routine set-
+signal figures out what action should be taken when a signal is
+received and invokes the signal system call to set the signal ac-
+tion appropriately.  When a signal that a user has set a trap for
+is caught, the routine "onsig" sets a flag.  The routine dotrap
+is called at appropriate points to actually handle the signal.
+When an interrupt is caught and no trap has been set for that
+signal, the routine "onint" in error.c is called.
+
+OUTPUT:  Ash uses it's own output routines.  There are three out-
+put structures allocated.  "Output" represents the standard out-
+put, "errout" the standard error, and "memout" contains output
+which is to be stored in memory.  This last is used when a buil-
+tin command appears in backquotes, to allow its output to be col-
+lected without doing any I/O through the UNIX operating system.
+The variables out1 and out2 normally point to output and errout,
+respectively, but they are set to point to memout when appropri-
+ate inside backquotes.
+
+INPUT:  The basic input routine is pgetc, which reads from the
+current input file.  There is a stack of input files; the current
+input file is the top file on this stack.  The code allows the
+input to come from a string rather than a file.  (This is for the
+-c option and the "." and eval builtin commands.)  The global
+variable plinno is saved and restored when files are pushed and
+popped from the stack.  The parser routines store the number of
+the current line in this variable.
+
+DEBUGGING:  If DEBUG is defined in shell.h, then the shell will
+write debugging information to the file $HOME/trace.  Most of
+this is done using the TRACE macro, which takes a set of printf
+arguments inside two sets of parenthesis.  Example:
+"TRACE(("n=%d0, n))".  The double parenthesis are necessary be-
+cause the preprocessor can't handle functions with a variable
+number of arguments.  Defining DEBUG also causes the shell to
+generate a core dump if it is sent a quit signal.  The tracing
+code is in show.c.
diff --git a/bin/dash/src/alias.c b/bin/dash/src/alias.c
new file mode 100644
index 00000000..daeacbb8
--- /dev/null
+++ b/bin/dash/src/alias.c
@@ -0,0 +1,227 @@
+/*-
+ * Copyright (c) 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include "shell.h"
+#include "input.h"
+#include "output.h"
+#include "error.h"
+#include "memalloc.h"
+#include "mystring.h"
+#include "alias.h"
+#include "options.h"	/* XXX for argptr (should remove?) */
+
+#define ATABSIZE 39
+
+struct alias *atab[ATABSIZE];
+
+STATIC void setalias(const char *, const char *);
+STATIC struct alias *freealias(struct alias *);
+STATIC struct alias **__lookupalias(const char *);
+
+STATIC
+void
+setalias(const char *name, const char *val)
+{
+	struct alias *ap, **app;
+
+	app = __lookupalias(name);
+	ap = *app;
+	INTOFF;
+	if (ap) {
+		if (!(ap->flag & ALIASINUSE)) {
+			ckfree(ap->val);
+		}
+		ap->val	= savestr(val);
+		ap->flag &= ~ALIASDEAD;
+	} else {
+		/* not found */
+		ap = ckmalloc(sizeof (struct alias));
+		ap->name = savestr(name);
+		ap->val = savestr(val);
+		ap->flag = 0;
+		ap->next = 0;
+		*app = ap;
+	}
+	INTON;
+}
+
+int
+unalias(const char *name)
+{
+	struct alias **app;
+
+	app = __lookupalias(name);
+
+	if (*app) {
+		INTOFF;
+		*app = freealias(*app);
+		INTON;
+		return (0);
+	}
+
+	return (1);
+}
+
+void
+rmaliases(void)
+{
+	struct alias *ap, **app;
+	int i;
+
+	INTOFF;
+	for (i = 0; i < ATABSIZE; i++) {
+		app = &atab[i];
+		for (ap = *app; ap; ap = *app) {
+			*app = freealias(*app);
+			if (ap == *app) {
+				app = &ap->next;
+			}
+		}
+	}
+	INTON;
+}
+
+struct alias *
+lookupalias(const char *name, int check)
+{
+	struct alias *ap = *__lookupalias(name);
+
+	if (check && ap && (ap->flag & ALIASINUSE))
+		return (NULL);
+	return (ap);
+}
+
+/*
+ * TODO - sort output
+ */
+int
+aliascmd(int argc, char **argv)
+{
+	char *n, *v;
+	int ret = 0;
+	struct alias *ap;
+
+	if (argc == 1) {
+		int i;
+
+		for (i = 0; i < ATABSIZE; i++)
+			for (ap = atab[i]; ap; ap = ap->next) {
+				printalias(ap);
+			}
+		return (0);
+	}
+	while ((n = *++argv) != NULL) {
+		if ((v = strchr(n+1, '=')) == NULL) { /* n+1: funny ksh stuff */
+			if ((ap = *__lookupalias(n)) == NULL) {
+				outfmt(out2, "%s: %s not found\n", "alias", n);
+				ret = 1;
+			} else
+				printalias(ap);
+		} else {
+			*v++ = '\0';
+			setalias(n, v);
+		}
+	}
+
+	return (ret);
+}
+
+int
+unaliascmd(int argc, char **argv)
+{
+	int i;
+
+	while ((i = nextopt("a")) != '\0') {
+		if (i == 'a') {
+			rmaliases();
+			return (0);
+		}
+	}
+	for (i = 0; *argptr; argptr++) {
+		if (unalias(*argptr)) {
+			outfmt(out2, "%s: %s not found\n", "unalias", *argptr);
+			i = 1;
+		}
+	}
+
+	return (i);
+}
+
+STATIC struct alias *
+freealias(struct alias *ap) {
+	struct alias *next;
+
+	if (ap->flag & ALIASINUSE) {
+		ap->flag |= ALIASDEAD;
+		return ap;
+	}
+
+	next = ap->next;
+	ckfree(ap->name);
+	ckfree(ap->val);
+	ckfree(ap);
+	return next;
+}
+
+void
+printalias(const struct alias *ap) {
+	out1fmt("%s=%s\n", ap->name, single_quote(ap->val));
+}
+
+STATIC struct alias **
+__lookupalias(const char *name) {
+	unsigned int hashval;
+	struct alias **app;
+	const char *p;
+	unsigned int ch;
+
+	p = name;
+
+	ch = (unsigned char)*p;
+	hashval = ch << 4;
+	while (ch) {
+		hashval += ch;
+		ch = (unsigned char)*++p;
+	}
+	app = &atab[hashval % ATABSIZE];
+
+	for (; *app; app = &(*app)->next) {
+		if (equal(name, (*app)->name)) {
+			break;
+		}
+	}
+
+	return app;
+}
diff --git a/bin/dash/src/alias.h b/bin/dash/src/alias.h
new file mode 100644
index 00000000..fb841d64
--- /dev/null
+++ b/bin/dash/src/alias.h
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)alias.h	8.2 (Berkeley) 5/4/95
+ */
+
+#define ALIASINUSE	1
+#define ALIASDEAD	2
+
+struct alias {
+	struct alias *next;
+	char *name;
+	char *val;
+	int flag;
+};
+
+struct alias *lookupalias(const char *, int);
+int aliascmd(int, char **);
+int unaliascmd(int, char **);
+void rmaliases(void);
+int unalias(const char *);
+void printalias(const struct alias *);
diff --git a/bin/dash/src/arith_yacc.c b/bin/dash/src/arith_yacc.c
new file mode 100644
index 00000000..1a087c32
--- /dev/null
+++ b/bin/dash/src/arith_yacc.c
@@ -0,0 +1,304 @@
+/*-
+ * Copyright (c) 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 2007
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include "arith_yacc.h"
+#include "expand.h"
+#include "shell.h"
+#include "error.h"
+#include "output.h"
+#include "var.h"
+
+#if ARITH_BOR + 11 != ARITH_BORASS || ARITH_ASS + 11 != ARITH_EQ
+#error Arithmetic tokens are out of order.
+#endif
+
+static const char *arith_startbuf;
+
+const char *arith_buf;
+union yystype yylval;
+
+static int last_token;
+
+#define ARITH_PRECEDENCE(op, prec) [op - ARITH_BINOP_MIN] = prec
+
+static const char prec[ARITH_BINOP_MAX - ARITH_BINOP_MIN] = {
+	ARITH_PRECEDENCE(ARITH_MUL, 0),
+	ARITH_PRECEDENCE(ARITH_DIV, 0),
+	ARITH_PRECEDENCE(ARITH_REM, 0),
+	ARITH_PRECEDENCE(ARITH_ADD, 1),
+	ARITH_PRECEDENCE(ARITH_SUB, 1),
+	ARITH_PRECEDENCE(ARITH_LSHIFT, 2),
+	ARITH_PRECEDENCE(ARITH_RSHIFT, 2),
+	ARITH_PRECEDENCE(ARITH_LT, 3),
+	ARITH_PRECEDENCE(ARITH_LE, 3),
+	ARITH_PRECEDENCE(ARITH_GT, 3),
+	ARITH_PRECEDENCE(ARITH_GE, 3),
+	ARITH_PRECEDENCE(ARITH_EQ, 4),
+	ARITH_PRECEDENCE(ARITH_NE, 4),
+	ARITH_PRECEDENCE(ARITH_BAND, 5),
+	ARITH_PRECEDENCE(ARITH_BXOR, 6),
+	ARITH_PRECEDENCE(ARITH_BOR, 7),
+};
+
+#define ARITH_MAX_PREC 8
+
+static void yyerror(const char *s) __attribute__ ((noreturn));
+static void yyerror(const char *s)
+{
+	sh_error("arithmetic expression: %s: \"%s\"", s, arith_startbuf);
+	/* NOTREACHED */
+}
+
+static inline int arith_prec(int op)
+{
+	return prec[op - ARITH_BINOP_MIN];
+}
+
+static inline int higher_prec(int op1, int op2)
+{
+	return arith_prec(op1) < arith_prec(op2);
+}
+
+static intmax_t do_binop(int op, intmax_t a, intmax_t b)
+{
+	switch (op) {
+	default:
+	case ARITH_REM:
+	case ARITH_DIV:
+		if (!b)
+			yyerror("division by zero");
+		return op == ARITH_REM ? a % b : a / b;
+	case ARITH_MUL:
+		return a * b;
+	case ARITH_ADD:
+		return a + b;
+	case ARITH_SUB:
+		return a - b;
+	case ARITH_LSHIFT:
+		return a << b;
+	case ARITH_RSHIFT:
+		return a >> b;
+	case ARITH_LT:
+		return a < b;
+	case ARITH_LE:
+		return a <= b;
+	case ARITH_GT:
+		return a > b;
+	case ARITH_GE:
+		return a >= b;
+	case ARITH_EQ:
+		return a == b;
+	case ARITH_NE:
+		return a != b;
+	case ARITH_BAND:
+		return a & b;
+	case ARITH_BXOR:
+		return a ^ b;
+	case ARITH_BOR:
+		return a | b;
+	}
+}
+
+static intmax_t assignment(int var, int noeval);
+
+static intmax_t primary(int token, union yystype *val, int op, int noeval)
+{
+	intmax_t result;
+
+again:
+	switch (token) {
+	case ARITH_LPAREN:
+		result = assignment(op, noeval);
+		if (last_token != ARITH_RPAREN)
+			yyerror("expecting ')'");
+		last_token = yylex();
+		return result;
+	case ARITH_NUM:
+		last_token = op;
+		return val->val;
+	case ARITH_VAR:
+		last_token = op;
+		return noeval ? val->val : lookupvarint(val->name);
+	case ARITH_ADD:
+		token = op;
+		*val = yylval;
+		op = yylex();
+		goto again;
+	case ARITH_SUB:
+		*val = yylval;
+		return -primary(op, val, yylex(), noeval);
+	case ARITH_NOT:
+		*val = yylval;
+		return !primary(op, val, yylex(), noeval);
+	case ARITH_BNOT:
+		*val = yylval;
+		return ~primary(op, val, yylex(), noeval);
+	default:
+		yyerror("expecting primary");
+	}
+}
+
+static intmax_t binop2(intmax_t a, int op, int prec, int noeval)
+{
+	for (;;) {
+		union yystype val;
+		intmax_t b;
+		int op2;
+		int token;
+
+		token = yylex();
+		val = yylval;
+
+		b = primary(token, &val, yylex(), noeval);
+
+		op2 = last_token;
+		if (op2 >= ARITH_BINOP_MIN && op2 < ARITH_BINOP_MAX &&
+		    higher_prec(op2, op)) {
+			b = binop2(b, op2, arith_prec(op), noeval);
+			op2 = last_token;
+		}
+
+		a = noeval ? b : do_binop(op, a, b);
+
+		if (op2 < ARITH_BINOP_MIN || op2 >= ARITH_BINOP_MAX ||
+		    arith_prec(op2) >= prec)
+			return a;
+
+		op = op2;
+	}
+}
+
+static intmax_t binop(int token, union yystype *val, int op, int noeval)
+{
+	intmax_t a = primary(token, val, op, noeval);
+
+	op = last_token;
+	if (op < ARITH_BINOP_MIN || op >= ARITH_BINOP_MAX)
+		return a;
+
+	return binop2(a, op, ARITH_MAX_PREC, noeval);
+}
+
+static intmax_t and(int token, union yystype *val, int op, int noeval)
+{
+	intmax_t a = binop(token, val, op, noeval);
+	intmax_t b;
+
+	op = last_token;
+	if (op != ARITH_AND)
+		return a;
+
+	token = yylex();
+	*val = yylval;
+
+	b = and(token, val, yylex(), noeval | !a);
+
+	return a && b;
+}
+
+static intmax_t or(int token, union yystype *val, int op, int noeval)
+{
+	intmax_t a = and(token, val, op, noeval);
+	intmax_t b;
+
+	op = last_token;
+	if (op != ARITH_OR)
+		return a;
+
+	token = yylex();
+	*val = yylval;
+
+	b = or(token, val, yylex(), noeval | !!a);
+
+	return a || b;
+}
+
+static intmax_t cond(int token, union yystype *val, int op, int noeval)
+{
+	intmax_t a = or(token, val, op, noeval);
+	intmax_t b;
+	intmax_t c;
+
+	if (last_token != ARITH_QMARK)
+		return a;
+
+	b = assignment(yylex(), noeval | !a);
+
+	if (last_token != ARITH_COLON)
+		yyerror("expecting ':'");
+
+	token = yylex();
+	*val = yylval;
+
+	c = cond(token, val, yylex(), noeval | !!a);
+
+	return a ? b : c;
+}
+
+static intmax_t assignment(int var, int noeval)
+{
+	union yystype val = yylval;
+	int op = yylex();
+	intmax_t result;
+
+	if (var != ARITH_VAR)
+		return cond(var, &val, op, noeval);
+
+	if (op != ARITH_ASS && (op < ARITH_ASS_MIN || op >= ARITH_ASS_MAX))
+		return cond(var, &val, op, noeval);
+
+	result = assignment(yylex(), noeval);
+	if (noeval)
+		return result;
+
+	return setvarint(val.name,
+			 op == ARITH_ASS ? result :
+			 do_binop(op - 11, lookupvarint(val.name), result), 0);
+}
+
+intmax_t arith(const char *s)
+{
+	intmax_t result;
+
+	arith_buf = arith_startbuf = s;
+
+	result = assignment(yylex(), 0);
+
+	if (last_token)
+		yyerror("expecting EOF");
+
+	return result;
+}
diff --git a/bin/dash/src/arith_yacc.h b/bin/dash/src/arith_yacc.h
new file mode 100644
index 00000000..ff34d524
--- /dev/null
+++ b/bin/dash/src/arith_yacc.h
@@ -0,0 +1,89 @@
+/*-
+ * Copyright (c) 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 2007
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#define ARITH_ASS 1
+
+#define ARITH_OR 2
+#define ARITH_AND 3
+#define ARITH_BAD 4
+#define ARITH_NUM 5
+#define ARITH_VAR 6
+#define ARITH_NOT 7
+
+#define ARITH_BINOP_MIN 8
+#define ARITH_LE 8
+#define ARITH_GE 9
+#define ARITH_LT 10
+#define ARITH_GT 11
+#define ARITH_EQ 12
+#define ARITH_REM 13
+#define ARITH_BAND 14
+#define ARITH_LSHIFT 15
+#define ARITH_RSHIFT 16
+#define ARITH_MUL 17
+#define ARITH_ADD 18
+#define ARITH_BOR 19
+#define ARITH_SUB 20
+#define ARITH_BXOR 21
+#define ARITH_DIV 22
+#define ARITH_NE 23
+#define ARITH_BINOP_MAX 24
+
+#define ARITH_ASS_MIN 24
+#define ARITH_REMASS 24
+#define ARITH_BANDASS 25
+#define ARITH_LSHIFTASS 26
+#define ARITH_RSHIFTASS 27
+#define ARITH_MULASS 28
+#define ARITH_ADDASS 29
+#define ARITH_BORASS 30
+#define ARITH_SUBASS 31
+#define ARITH_BXORASS 32
+#define ARITH_DIVASS 33
+#define ARITH_ASS_MAX 34
+
+#define ARITH_LPAREN 34
+#define ARITH_RPAREN 35
+#define ARITH_BNOT 36
+#define ARITH_QMARK 37
+#define ARITH_COLON 38
+
+union yystype {
+	intmax_t val;
+	char *name;
+};
+
+extern union yystype yylval;
+
+int yylex(void);
diff --git a/bin/dash/src/arith_yylex.c b/bin/dash/src/arith_yylex.c
new file mode 100644
index 00000000..ec5b5b25
--- /dev/null
+++ b/bin/dash/src/arith_yylex.c
@@ -0,0 +1,240 @@
+/*-
+ * Copyright (c) 2002
+ *	Herbert Xu.
+ * Copyright (c) 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include "arith_yacc.h"
+#include "expand.h"
+#include "error.h"
+#include "shell.h"
+#include "memalloc.h"
+#include "syntax.h"
+#include "system.h"
+
+#if ARITH_BOR + 11 != ARITH_BORASS || ARITH_ASS + 11 != ARITH_EQ
+#error Arithmetic tokens are out of order.
+#endif
+
+extern const char *arith_buf;
+
+int
+yylex()
+{
+	int value;
+	const char *buf = arith_buf;
+	const char *p;
+
+	for (;;) {
+		value = *buf;
+		switch (value) {
+		case ' ':
+		case '\t':
+		case '\n':
+			buf++;
+			continue;
+		default:
+			return ARITH_BAD;
+		case '0':
+		case '1':
+		case '2':
+		case '3':
+		case '4':
+		case '5':
+		case '6':
+		case '7':
+		case '8':
+		case '9':
+			yylval.val = strtoimax(buf, (char **)&arith_buf, 0);
+			return ARITH_NUM;
+		case 'A':
+		case 'B':
+		case 'C':
+		case 'D':
+		case 'E':
+		case 'F':
+		case 'G':
+		case 'H':
+		case 'I':
+		case 'J':
+		case 'K':
+		case 'L':
+		case 'M':
+		case 'N':
+		case 'O':
+		case 'P':
+		case 'Q':
+		case 'R':
+		case 'S':
+		case 'T':
+		case 'U':
+		case 'V':
+		case 'W':
+		case 'X':
+		case 'Y':
+		case 'Z':
+		case '_':
+		case 'a':
+		case 'b':
+		case 'c':
+		case 'd':
+		case 'e':
+		case 'f':
+		case 'g':
+		case 'h':
+		case 'i':
+		case 'j':
+		case 'k':
+		case 'l':
+		case 'm':
+		case 'n':
+		case 'o':
+		case 'p':
+		case 'q':
+		case 'r':
+		case 's':
+		case 't':
+		case 'u':
+		case 'v':
+		case 'w':
+		case 'x':
+		case 'y':
+		case 'z':
+			p = buf;
+			while (buf++, is_in_name(*buf))
+				;
+			yylval.name = stalloc(buf - p + 1);
+			*(char *)mempcpy(yylval.name, p, buf - p) = 0;
+			value = ARITH_VAR;
+			goto out;
+		case '=':
+			value += ARITH_ASS - '=';
+checkeq:
+			buf++;
+checkeqcur:
+			if (*buf != '=')
+				goto out;
+			value += 11;
+			break;
+		case '>':
+			switch (*++buf) {
+			case '=':
+				value += ARITH_GE - '>';
+				break;
+			case '>':
+				value += ARITH_RSHIFT - '>';
+				goto checkeq;
+			default:
+				value += ARITH_GT - '>';
+				goto out;
+			}
+			break;
+		case '<':
+			switch (*++buf) {
+			case '=':
+				value += ARITH_LE - '<';
+				break;
+			case '<':
+				value += ARITH_LSHIFT - '<';
+				goto checkeq;
+			default:
+				value += ARITH_LT - '<';
+				goto out;
+			}
+			break;
+		case '|':
+			if (*++buf != '|') {
+				value += ARITH_BOR - '|';
+				goto checkeqcur;
+			}
+			value += ARITH_OR - '|';
+			break;
+		case '&':
+			if (*++buf != '&') {
+				value += ARITH_BAND - '&';
+				goto checkeqcur;
+			}
+			value += ARITH_AND - '&';
+			break;
+		case '!':
+			if (*++buf != '=') {
+				value += ARITH_NOT - '!';
+				goto out;
+			}
+			value += ARITH_NE - '!';
+			break;
+		case 0:
+			goto out;
+		case '(':
+			value += ARITH_LPAREN - '(';
+			break;
+		case ')':
+			value += ARITH_RPAREN - ')';
+			break;
+		case '*':
+			value += ARITH_MUL - '*';
+			goto checkeq;
+		case '/':
+			value += ARITH_DIV - '/';
+			goto checkeq;
+		case '%':
+			value += ARITH_REM - '%';
+			goto checkeq;
+		case '+':
+			value += ARITH_ADD - '+';
+			goto checkeq;
+		case '-':
+			value += ARITH_SUB - '-';
+			goto checkeq;
+		case '~':
+			value += ARITH_BNOT - '~';
+			break;
+		case '^':
+			value += ARITH_BXOR - '^';
+			goto checkeq;
+		case '?':
+			value += ARITH_QMARK - '?';
+			break;
+		case ':':
+			value += ARITH_COLON - ':';
+			break;
+		}
+		break;
+	}
+
+	buf++;
+out:
+	arith_buf = buf;
+	return value;
+}
diff --git a/bin/dash/src/bltin/bltin.h b/bin/dash/src/bltin/bltin.h
new file mode 100644
index 00000000..f5ac06f2
--- /dev/null
+++ b/bin/dash/src/bltin/bltin.h
@@ -0,0 +1,89 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)bltin.h	8.1 (Berkeley) 5/31/93
+ */
+
+/*
+ * This file is included by programs which are optionally built into the
+ * shell.  If SHELL is defined, we try to map the standard UNIX library
+ * routines to ash routines using defines.
+ */
+
+#include "../shell.h"
+#include "../mystring.h"
+#include "../options.h"
+#ifdef SHELL
+#include "../memalloc.h"
+#include "../output.h"
+#include "../error.h"
+#ifndef USE_GLIBC_STDIO
+#undef stdout
+#undef stderr
+#undef putc
+#undef putchar
+#undef fileno
+#define stdout out1
+#define stderr out2
+#define printf out1fmt
+#define putc(c, file)	outc(c, file)
+#define putchar(c)	out1c(c)
+#define FILE struct output
+#define fprintf outfmt
+#define fputs outstr
+#define fflush flushout
+#define fileno(f) ((f)->fd)
+#define ferror outerr
+#endif
+#define INITARGS(argv)
+#define	error sh_error
+#define	warn sh_warn
+#define	warnx sh_warnx
+#define exit sh_exit
+#define setprogname(s)
+#define getprogname() commandname
+#define setlocate(l,s) 0
+
+#define getenv(p) bltinlookup((p),0)
+
+#else
+#undef NULL
+#include <stdio.h>
+#undef main
+#define INITARGS(argv)	if ((commandname = argv[0]) == NULL) {fputs("Argc is zero\n", stderr); exit(2);} else
+#endif
+
+int echocmd(int, char **);
+
+
+extern const char *commandname;
diff --git a/bin/dash/src/bltin/echo.1 b/bin/dash/src/bltin/echo.1
new file mode 100644
index 00000000..fbc7fb43
--- /dev/null
+++ b/bin/dash/src/bltin/echo.1
@@ -0,0 +1,109 @@
+.\" Copyright (c) 1991, 1993
+.\"	The Regents of the University of California.  All rights reserved.
+.\" Copyright (c) 1997-2005
+.\"	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Kenneth Almquist.
+.\" Copyright 1989 by Kenneth Almquist
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\"	@(#)echo.1	8.1 (Berkeley) 5/31/93
+.\"
+.Dd May 31, 1993
+.Dt ECHO 1
+.Os
+.Sh NAME
+.Nm echo
+.Nd produce message in a shell script
+.Sh SYNOPSIS
+.Nm
+.Op Fl n | Fl e
+.Ar args ...
+.Sh DESCRIPTION
+.Nm
+prints its arguments on the standard output, separated by spaces.
+Unless the
+.Fl n
+option is present, a newline is output following the arguments.
+The
+.Fl e
+option causes
+.Nm
+to treat the escape sequences specially, as described in the following
+paragraph.
+The
+.Fl e
+option is the default, and is provided solely for compatibility with
+other systems.
+Only one of the options
+.Fl n
+and
+.Fl e
+may be given.
+.Pp
+If any of the following sequences of characters is encountered during
+output, the sequence is not output.  Instead, the specified action is
+performed:
+.Bl -tag -width indent
+.It Li \eb
+A backspace character is output.
+.It Li \ec
+Subsequent output is suppressed.  This is normally used at the end of the
+last argument to suppress the trailing newline that
+.Nm
+would otherwise output.
+.It Li \ef
+Output a form feed.
+.It Li \en
+Output a newline character.
+.It Li \er
+Output a carriage return.
+.It Li \et
+Output a (horizontal) tab character.
+.It Li \ev
+Output a vertical tab.
+.It Li \e0 Ns Ar digits
+Output the character whose value is given by zero to three digits.
+If there are zero digits, a nul character is output.
+.It Li \e\e
+Output a backslash.
+.El
+.Sh HINTS
+Remember that backslash is special to the shell and needs to be escaped.
+To output a message to standard error, say
+.Pp
+.D1  echo message \*[Gt]\*[Am]2
+.Sh BUGS
+The octal character escape mechanism
+.Pq Li \e0 Ns Ar digits
+differs from the
+C language mechanism.
+.Pp
+There is no way to force
+.Nm
+to treat its arguments literally, rather than interpreting them as
+options and escape sequences.
diff --git a/bin/dash/src/bltin/printf.1 b/bin/dash/src/bltin/printf.1
new file mode 100644
index 00000000..38731732
--- /dev/null
+++ b/bin/dash/src/bltin/printf.1
@@ -0,0 +1,354 @@
+.\" Copyright (c) 1989, 1990, 1993
+.\"	The Regents of the University of California.  All rights reserved.
+.\" Copyright (c) 1997-2005
+.\"	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\"	from: @(#)printf.1	8.1 (Berkeley) 6/6/93
+.\"
+.Dd November 5, 1993
+.Dt PRINTF 1
+.Os
+.Sh NAME
+.Nm printf
+.Nd formatted output
+.Sh SYNOPSIS
+.Nm
+.Ar format
+.Op Ar arguments  ...
+.Sh DESCRIPTION
+.Nm
+formats and prints its arguments, after the first, under control
+of the
+.Ar format  .
+The
+.Ar format
+is a character string which contains three types of objects: plain characters,
+which are simply copied to standard output, character escape sequences which
+are converted and copied to the standard output, and format specifications,
+each of which causes printing of the next successive
+.Ar argument  .
+.Pp
+The
+.Ar arguments
+after the first are treated as strings if the corresponding format is
+either
+.Cm b ,
+.Cm B ,
+.Cm c
+or
+.Cm s ;
+otherwise it is evaluated as a C constant, with the following extensions:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+A leading plus or minus sign is allowed.
+.It
+If the leading character is a single or double quote, the value is the
+.Tn ASCII
+code of the next character.
+.El
+.Pp
+The format string is reused as often as necessary to satisfy the
+.Ar arguments  .
+Any extra format specifications are evaluated with zero or the null
+string.
+.Pp
+Character escape sequences are in backslash notation as defined in
+.St -ansiC .
+The characters and their meanings are as follows:
+.Bl -tag -width Ds -offset indent
+.It Cm \ee
+Write an \*[Lt]escape\*[Gt] character.
+.It Cm \ea
+Write a \*[Lt]bell\*[Gt] character.
+.It Cm \eb
+Write a \*[Lt]backspace\*[Gt] character.
+.It Cm \ef
+Write a \*[Lt]form-feed\*[Gt] character.
+.It Cm \en
+Write a \*[Lt]new-line\*[Gt] character.
+.It Cm \er
+Write a \*[Lt]carriage return\*[Gt] character.
+.It Cm \et
+Write a \*[Lt]tab\*[Gt] character.
+.It Cm \ev
+Write a \*[Lt]vertical tab\*[Gt] character.
+.It Cm \e\'
+Write a \*[Lt]single quote\*[Gt] character.
+.It Cm \e"
+Write a \*[Lt]double quote\*[Gt] character.
+.It Cm \e\e
+Write a backslash character.
+.It Cm \e Ns Ar num
+Write an 8\-bit character whose
+.Tn ASCII
+value is the 1\-, 2\-, or 3\-digit octal number
+.Ar num .
+.It Cm \ex Ns Ar xx
+Write an 8\-bit character whose
+.Tn ASCII
+value is the 1\- or 2\-digit hexadecimal number
+.Ar xx .
+.El
+.Pp
+Each format specification is introduced by the percent character
+(``%'').
+The remainder of the format specification includes,
+in the following order:
+.Bl -tag -width Ds
+.It "Zero or more of the following flags:"
+.Bl -tag -width Ds
+.It Cm #
+A `#' character
+specifying that the value should be printed in an ``alternative form''.
+For
+.Cm b ,
+.Cm c ,
+.Cm d ,
+and
+.Cm s
+formats, this option has no effect.
+For the
+.Cm o
+format the precision of the number is increased to force the first
+character of the output string to a zero.
+For the
+.Cm x
+.Pq Cm X
+format, a non-zero result has the string
+.Li 0x
+.Pq Li 0X
+prepended to it.
+For
+.Cm e  ,
+.Cm E ,
+.Cm f  ,
+.Cm g ,
+and
+.Cm G
+formats, the result will always contain a decimal point, even if no
+digits follow the point (normally, a decimal point only appears in the
+results of those formats if a digit follows the decimal point).
+For
+.Cm g
+and
+.Cm G
+formats, trailing zeros are not removed from the result as they
+would otherwise be.
+.\" I turned this off - decided it isn't a valid use of '#'
+.\" For the
+.\" .Cm B
+.\" format, backslash-escape sequences are expanded first;
+.It Cm \&\-
+A minus sign `\-' which specifies
+.Em left adjustment
+of the output in the indicated field;
+.It Cm \&+
+A `+' character specifying that there should always be
+a sign placed before the number when using signed formats.
+.It Sq \&\ \&
+A space specifying that a blank should be left before a positive number
+for a signed format.
+A `+' overrides a space if both are used;
+.It Cm \&0
+A zero `0' character indicating that zero-padding should be used
+rather than blank-padding.
+A `\-' overrides a `0' if both are used;
+.El
+.It "Field Width:"
+An optional digit string specifying a
+.Em field width ;
+if the output string has fewer characters than the field width it will
+be blank-padded on the left (or right, if the left-adjustment indicator
+has been given) to make up the field width (note that a leading zero
+is a flag, but an embedded zero is part of a field width);
+.It Precision :
+An optional period,
+.Sq Cm \&.\& ,
+followed by an optional digit string giving a
+.Em precision
+which specifies the number of digits to appear after the decimal point,
+for
+.Cm e
+and
+.Cm f
+formats, or the maximum number of characters to be printed
+from a string
+.Sm off
+.Pf ( Cm b No ,
+.Sm on
+.Cm B
+and
+.Cm s
+formats); if the digit string is missing, the precision is treated
+as zero;
+.It Format :
+A character which indicates the type of format to use (one of
+.Cm diouxXfwEgGbBcs ) .
+.El
+.Pp
+A field width or precision may be
+.Sq Cm \&*
+instead of a digit string.
+In this case an
+.Ar argument
+supplies the field width or precision.
+.Pp
+The format characters and their meanings are:
+.Bl -tag -width Fl
+.It Cm diouXx
+The
+.Ar argument
+is printed as a signed decimal (d or i), unsigned octal, unsigned decimal,
+or unsigned hexadecimal (X or x), respectively.
+.It Cm f
+The
+.Ar argument
+is printed in the style
+.Sm off
+.Pf [\-]ddd Cm \&. No ddd
+.Sm on
+where the number of d's
+after the decimal point is equal to the precision specification for
+the argument.
+If the precision is missing, 6 digits are given; if the precision
+is explicitly 0, no digits and no decimal point are printed.
+.It Cm eE
+The
+.Ar argument
+is printed in the style
+.Sm off
+.Pf [\-]d Cm \&. No ddd Cm e No \\*(Pmdd
+.Sm on
+where there
+is one digit before the decimal point and the number after is equal to
+the precision specification for the argument; when the precision is
+missing, 6 digits are produced.
+An upper-case E is used for an `E' format.
+.It Cm gG
+The
+.Ar argument
+is printed in style
+.Cm f
+or in style
+.Cm e
+.Pq Cm E
+whichever gives full precision in minimum space.
+.It Cm b
+Characters from the string
+.Ar argument
+are printed with backslash-escape sequences expanded.
+.br
+The following additional backslash-escape sequences are supported:
+.Bl -tag -width Ds
+.It Cm \ec
+Causes
+.Nm
+to ignore any remaining characters in the string operand containing it,
+any remaining string operands, and any additional characters in
+the format operand.
+.It Cm \e0 Ns Ar num
+Write an 8\-bit character whose
+.Tn ASCII
+value is the 1\-, 2\-, or 3\-digit
+octal number
+.Ar num .
+.It Cm \e^ Ns Ar c
+Write the control character 
+.Ar c .
+Generates characters `\e000' through `\e037`, and `\e177' (from `\e^?').
+.It Cm \eM\- Ns Ar c
+Write the character 
+.Ar c
+with the 8th bit set.
+Generates characters `\e241' through `\e376`.
+.It Cm \eM^ Ns Ar c
+Write the control character 
+.Ar c
+with the 8th bit set.
+Generates characters `\e000' through `\e037`, and `\e177' (from `\eM^?').
+.El
+.It Cm B
+Characters from the string
+.Ar argument
+are printed with unprintable characters backslash-escaped using the
+.Sm off
+.Pf ` Cm \e Ar c No ',
+.Pf ` Cm \e^ Ar c No ',
+.Pf ` Cm \eM\- Ar c No '
+or
+.Pf ` Cm \eM^ Ar c No ',
+.Sm on
+formats described above.
+.It Cm c
+The first character of
+.Ar argument
+is printed.
+.It Cm s
+Characters from the string
+.Ar argument
+are printed until the end is reached or until the number of characters
+indicated by the precision specification is reached; if the
+precision is omitted, all characters in the string are printed.
+.It Cm \&%
+Print a `%'; no argument is used.
+.El
+.Pp
+In no case does a non-existent or small field width cause truncation of
+a field; padding takes place only if the specified field width exceeds
+the actual width.
+.Sh EXIT STATUS
+.Nm
+exits 0 on success, 1 on failure.
+.Sh SEE ALSO
+.Xr echo 1 ,
+.Xr printf 3 ,
+.Xr printf 9
+.Xr vis 3
+.Sh STANDARDS
+The
+.Nm
+utility conforms to
+.St -p1003.1-2001 .
+.Pp
+Support for the floating point formats and `*' as a field width and precision
+are optional in POSIX.
+.Pp
+The behaviour of the %B format and the \e', \e", \exxx, \ee and
+\e[M][\-|^]c escape sequences are undefined in POSIX.
+.Sh BUGS
+Since the floating point numbers are translated from
+.Tn ASCII
+to floating-point and
+then back again, floating-point precision may be lost.
+.Pp
+Hexadecimal character constants are restricted to, and should be specified
+as, two character constants.  This is contrary to the ISO C standard but
+does guarantee detection of the end of the constant.
diff --git a/bin/dash/src/bltin/printf.c b/bin/dash/src/bltin/printf.c
new file mode 100644
index 00000000..7785735b
--- /dev/null
+++ b/bin/dash/src/bltin/printf.c
@@ -0,0 +1,478 @@
+/*
+ * Copyright (c) 1989, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+static int	 conv_escape_str(char *, char **);
+static char	*conv_escape(char *, int *);
+static int	 getchr(void);
+static double	 getdouble(void);
+static uintmax_t getuintmax(int);
+static char	*getstr(void);
+static char	*mklong(const char *, const char *);
+static void      check_conversion(const char *, const char *);
+
+static int	rval;
+static char  **gargv;
+
+#define isodigit(c)	((c) >= '0' && (c) <= '7')
+#define octtobin(c)	((c) - '0')
+
+#include "bltin.h"
+#include "system.h"
+
+#define PF(f, func) { \
+	switch ((char *)param - (char *)array) { \
+	default: \
+		(void)printf(f, array[0], array[1], func); \
+		break; \
+	case sizeof(*param): \
+		(void)printf(f, array[0], func); \
+		break; \
+	case 0: \
+		(void)printf(f, func); \
+		break; \
+	} \
+}
+
+#define ASPF(sp, f, func) ({ \
+	int ret; \
+	switch ((char *)param - (char *)array) { \
+	default: \
+		ret = xasprintf(sp, f, array[0], array[1], func); \
+		break; \
+	case sizeof(*param): \
+		ret = xasprintf(sp, f, array[0], func); \
+		break; \
+	case 0: \
+		ret = xasprintf(sp, f, func); \
+		break; \
+	} \
+	ret; \
+})
+
+
+static int print_escape_str(const char *f, int *param, int *array, char *s)
+{
+	struct stackmark smark;
+	char *p, *q;
+	int done;
+	int len;
+	int total;
+
+	setstackmark(&smark);
+	done = conv_escape_str(s, &q);
+	p = stackblock();
+	len = q - p;
+	total = len - 1;
+
+	q[-1] = (!!((f[1] - 's') | done) - 1) & f[2];
+	total += !!q[-1];
+	if (f[1] == 's')
+		goto easy;
+
+	p = makestrspace(len, q);
+	memset(p, 'X', total);
+	p[total] = 0;
+
+	q = stackblock();
+	total = ASPF(&p, f, p);
+
+	len = strchrnul(p, 'X') - p;
+	memcpy(p + len, q, strspn(p + len, "X"));
+
+easy:
+	out1mem(p, total);
+
+	popstackmark(&smark);
+	return done;
+}
+
+
+int printfcmd(int argc, char *argv[])
+{
+	char *fmt;
+	char *format;
+	int ch;
+
+	rval = 0;
+
+	nextopt(nullstr);
+
+	argv = argptr;
+	format = *argv;
+
+	if (!format)
+		error("usage: printf format [arg ...]");
+
+	gargv = ++argv;
+
+#define SKIP1	"#-+ 0"
+#define SKIP2	"*0123456789"
+	do {
+		/*
+		 * Basic algorithm is to scan the format string for conversion
+		 * specifications -- once one is found, find out if the field
+		 * width or precision is a '*'; if it is, gather up value. 
+		 * Note, format strings are reused as necessary to use up the
+		 * provided arguments, arguments of zero/null string are 
+		 * provided to use up the format string.
+		 */
+
+		/* find next format specification */
+		for (fmt = format; (ch = *fmt++) ;) {
+			char *start;
+			char nextch;
+			int array[2];
+			int *param;
+
+			if (ch == '\\') {
+				int c_ch;
+				fmt = conv_escape(fmt, &c_ch);
+				ch = c_ch;
+				goto pc;
+			}
+			if (ch != '%' || (*fmt == '%' && (++fmt || 1))) {
+pc:
+				putchar(ch);
+				continue;
+			}
+
+			/* Ok - we've found a format specification,
+			   Save its address for a later printf(). */
+			start = fmt - 1;
+			param = array;
+
+			/* skip to field width */
+			fmt += strspn(fmt, SKIP1);
+			if (*fmt == '*') {
+				++fmt;
+				*param++ = getuintmax(1);
+			} else {
+				/* skip to possible '.',
+				 * get following precision
+				 */
+				fmt += strspn(fmt, SKIP2);
+			}
+
+			if (*fmt == '.') {
+				++fmt;
+				if (*fmt == '*') {
+					++fmt;
+					*param++ = getuintmax(1);
+				} else
+					fmt += strspn(fmt, SKIP2);
+			}
+
+			ch = *fmt;
+			if (!ch)
+				error("missing format character");
+			/* null terminate format string to we can use it
+			   as an argument to printf. */
+			nextch = fmt[1];
+			fmt[1] = 0;
+			switch (ch) {
+
+			case 'b':
+				*fmt = 's';
+				/* escape if a \c was encountered */
+				if (print_escape_str(start, param, array,
+						     getstr()))
+					goto out;
+				*fmt = 'b';
+				break;
+			case 'c': {
+				int p = getchr();
+				PF(start, p);
+				break;
+			}
+			case 's': {
+				char *p = getstr();
+				PF(start, p);
+				break;
+			}
+			case 'd':
+			case 'i': {
+				uintmax_t p = getuintmax(1);
+				start = mklong(start, fmt);
+				PF(start, p);
+				break;
+			}
+			case 'o':
+			case 'u':
+			case 'x':
+			case 'X': {
+				uintmax_t p = getuintmax(0);
+				start = mklong(start, fmt);
+				PF(start, p);
+				break;
+			}
+			case 'a':
+			case 'A':
+			case 'e':
+			case 'E':
+			case 'f':
+			case 'F':
+			case 'g':
+			case 'G': {
+				double p = getdouble();
+				PF(start, p);
+				break;
+			}
+			default:
+				error("%s: invalid directive", start);
+			}
+			*++fmt = nextch;
+		}
+	} while (gargv != argv && *gargv);
+
+out:
+	return rval;
+}
+
+
+/*
+ * Print SysV echo(1) style escape string 
+ *	Halts processing string if a \c escape is encountered.
+ */
+static int
+conv_escape_str(char *str, char **sp)
+{
+	int c;
+	int ch;
+	char *cp;
+
+	/* convert string into a temporary buffer... */
+	STARTSTACKSTR(cp);
+
+	do {
+		c = ch = *str++;
+		if (ch != '\\')
+			continue;
+
+		c = *str++;
+		if (c == 'c') {
+			/* \c as in SYSV echo - abort all processing.... */
+			c = ch = 0x100;
+			continue;
+		}
+
+		/* 
+		 * %b string octal constants are not like those in C.
+		 * They start with a \0, and are followed by 0, 1, 2, 
+		 * or 3 octal digits. 
+		 */
+		if (c == '0' && isodigit(*str))
+			str++;
+
+		/* Finally test for sequences valid in the format string */
+		str = conv_escape(str - 1, &c);
+	} while (STPUTC(c, cp), (char)ch);
+
+	*sp = cp;
+
+	return ch;
+}
+
+/*
+ * Print "standard" escape characters 
+ */
+static char *
+conv_escape(char *str, int *conv_ch)
+{
+	int value;
+	int ch;
+
+	ch = *str;
+
+	switch (ch) {
+	default:
+		if (!isodigit(*str)) {
+			value = '\\';
+			goto out;
+		}
+
+		ch = 3;
+		value = 0;
+		do {
+			value <<= 3;
+			value += octtobin(*str++);
+		} while (isodigit(*str) && --ch);
+		goto out;
+
+	case '\\':	value = '\\';	break;	/* backslash */
+	case 'a':	value = '\a';	break;	/* alert */
+	case 'b':	value = '\b';	break;	/* backspace */
+	case 'f':	value = '\f';	break;	/* form-feed */
+	case 'n':	value = '\n';	break;	/* newline */
+	case 'r':	value = '\r';	break;	/* carriage-return */
+	case 't':	value = '\t';	break;	/* tab */
+	case 'v':	value = '\v';	break;	/* vertical-tab */
+	}
+
+	str++;
+out:
+	*conv_ch = value;
+	return str;
+}
+
+static char *
+mklong(const char *str, const char *ch)
+{
+	/*
+	 * Replace a string like "%92.3u" with "%92.3"PRIuMAX.
+	 *
+	 * Although C99 does not guarantee it, we assume PRIiMAX,
+	 * PRIoMAX, PRIuMAX, PRIxMAX, and PRIXMAX are all the same
+	 * as PRIdMAX with the final 'd' replaced by the corresponding
+	 * character.
+	 */
+
+	char *copy;
+	size_t len;	
+
+	len = ch - str + sizeof(PRIdMAX);
+	STARTSTACKSTR(copy);
+	copy = makestrspace(len, copy);
+	memcpy(copy, str, len - sizeof(PRIdMAX));
+	memcpy(copy + len - sizeof(PRIdMAX), PRIdMAX, sizeof(PRIdMAX));
+	copy[len - 2] = *ch;
+	return (copy);	
+}
+
+static int
+getchr(void)
+{
+	int val = 0;
+
+	if (*gargv)
+		val = **gargv++;
+	return val;
+}
+
+static char *
+getstr(void)
+{
+	char *val = nullstr;
+
+	if (*gargv)
+		val = *gargv++;
+	return val;
+}
+
+static uintmax_t
+getuintmax(int sign)
+{
+	uintmax_t val = 0;
+	char *cp, *ep;
+
+	cp = *gargv;
+	if (cp == NULL)
+		goto out;
+	gargv++;
+
+	val = (unsigned char) cp[1];
+	if (*cp == '\"' || *cp == '\'')
+		goto out;
+
+	errno = 0;
+	val = sign ? strtoimax(cp, &ep, 0) : strtoumax(cp, &ep, 0);
+	check_conversion(cp, ep);
+out:
+	return val;
+}
+
+static double
+getdouble(void)
+{
+	double val;
+	char *cp, *ep;
+
+	cp = *gargv;
+	if (cp == NULL)
+		return 0;
+	gargv++;
+
+	if (*cp == '\"' || *cp == '\'')
+		return (unsigned char) cp[1];
+
+	errno = 0;
+	val = strtod(cp, &ep);
+	check_conversion(cp, ep);
+	return val;
+}
+
+static void
+check_conversion(const char *s, const char *ep)
+{
+	if (*ep) {
+		if (ep == s)
+			warnx("%s: expected numeric value", s);
+		else
+			warnx("%s: not completely converted", s);
+		rval = 1;
+	} else if (errno == ERANGE) {
+		warnx("%s: %s", s, strerror(ERANGE));
+		rval = 1;
+	}
+}
+
+int
+echocmd(int argc, char **argv)
+{
+	const char *lastfmt = snlfmt;
+	int nonl;
+
+	if (*++argv && equal(*argv, "-n")) {
+		argv++;
+		lastfmt = "%s";
+	}
+
+	do {
+		const char *fmt = "%s ";
+		char *s = *argv;
+
+		if (!s || !*++argv)
+			fmt = lastfmt;
+
+		nonl = print_escape_str(fmt, NULL, NULL, s ?: nullstr);
+	} while (!nonl && *argv);
+	return 0;
+}
diff --git a/bin/dash/src/bltin/test.1 b/bin/dash/src/bltin/test.1
new file mode 100644
index 00000000..42435fb3
--- /dev/null
+++ b/bin/dash/src/bltin/test.1
@@ -0,0 +1,309 @@
+.\" Copyright (c) 1991, 1993
+.\"	The Regents of the University of California.  All rights reserved.
+.\" Copyright (c) 1997-2005
+.\"	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" the Institute of Electrical and Electronics Engineers, Inc.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\"     @(#)test.1	8.1 (Berkeley) 5/31/93
+.\"
+.Dd May 31, 1993
+.Dt TEST 1
+.Os
+.Sh NAME
+.Nm test ,
+.Nm \&[
+.Nd condition evaluation utility
+.Sh SYNOPSIS
+.Nm test
+.Ar expression
+.Nm \&[
+.Ar expression Cm ]
+.Sh DESCRIPTION
+The
+.Nm test
+utility evaluates the expression and, if it evaluates
+to true, returns a zero (true) exit status; otherwise
+it returns 1 (false).
+If there is no expression, test also
+returns 1 (false).
+.Pp
+All operators and flags are separate arguments to the
+.Nm test
+utility.
+.Pp
+The following primaries are used to construct expression:
+.Bl -tag -width Ar
+.It Fl b Ar file
+True if
+.Ar file
+exists and is a block special
+file.
+.It Fl c Ar file
+True if
+.Ar file
+exists and is a character
+special file.
+.It Fl d Ar file
+True if
+.Ar file
+exists and is a directory.
+.It Fl e Ar file
+True if
+.Ar file
+exists (regardless of type).
+.It Fl f Ar file
+True if
+.Ar file
+exists and is a regular file.
+.It Fl g Ar file
+True if
+.Ar file
+exists and its set group ID flag
+is set.
+.It Fl h Ar file
+True if
+.Ar file
+exists and is a symbolic link.
+.It Fl k Ar file
+True if
+.Ar file
+exists and its sticky bit is set.
+.It Fl n Ar string
+True if the length of
+.Ar string
+is nonzero.
+.It Fl p Ar file
+True if
+.Ar file
+is a named pipe
+.Po Tn FIFO Pc .
+.It Fl r Ar file
+True if
+.Ar file
+exists and is readable.
+.It Fl s Ar file
+True if
+.Ar file
+exists and has a size greater
+than zero.
+.It Fl t Ar file_descriptor
+True if the file whose file descriptor number
+is
+.Ar file_descriptor
+is open and is associated with a terminal.
+.It Fl u Ar file
+True if
+.Ar file
+exists and its set user ID flag
+is set.
+.It Fl w Ar file
+True if
+.Ar file
+exists and is writable.
+True
+indicates only that the write flag is on.
+The file is not writable on a read-only file
+system even if this test indicates true.
+.It Fl x Ar file
+True if
+.Ar file
+exists and is executable.
+True
+indicates only that the execute flag is on.
+If
+.Ar file
+is a directory, true indicates that
+.Ar file
+can be searched.
+.It Fl z Ar string
+True if the length of
+.Ar string
+is zero.
+.It Fl L Ar file
+True if
+.Ar file
+exists and is a symbolic link.
+This operator is retained for compatibility with previous versions of
+this program.
+Do not rely on its existence; use
+.Fl h
+instead.
+.It Fl O Ar file
+True if
+.Ar file
+exists and its owner matches the effective user id of this process.
+.It Fl G Ar file
+True if
+.Ar file
+exists and its group matches the effective group id of this process.
+.It Fl S Ar file
+True if
+.Ar file
+exists and is a socket.
+.It Ar file1 Fl nt Ar file2
+True if
+.Ar file1
+exists and is newer than
+.Ar file2 .
+.It Ar file1 Fl ot Ar file2
+True if
+.Ar file1
+exists and is older than
+.Ar file2 .
+.It Ar file1 Fl ef Ar file2
+True if
+.Ar file1
+and
+.Ar file2
+exist and refer to the same file.
+.It Ar string
+True if
+.Ar string
+is not the null
+string.
+.It Ar \&s\&1 Cm \&= Ar \&s\&2
+True if the strings
+.Ar \&s\&1
+and
+.Ar \&s\&2
+are identical.
+.It Ar \&s\&1 Cm \&!= Ar \&s\&2
+True if the strings
+.Ar \&s\&1
+and
+.Ar \&s\&2
+are not identical.
+.It Ar \&s\&1 Cm \&\*[Lt] Ar \&s\&2
+True if string
+.Ar \&s\&1
+comes before
+.Ar \&s\&2
+based on the ASCII value of their characters.
+.It Ar \&s\&1 Cm \&\*[Gt] Ar \&s\&2
+True if string
+.Ar \&s\&1
+comes after
+.Ar \&s\&2
+based on the ASCII value of their characters.
+.It Ar \&n\&1 Fl \&eq Ar \&n\&2
+True if the integers
+.Ar \&n\&1
+and
+.Ar \&n\&2
+are algebraically
+equal.
+.It Ar \&n\&1 Fl \&ne Ar \&n\&2
+True if the integers
+.Ar \&n\&1
+and
+.Ar \&n\&2
+are not
+algebraically equal.
+.It Ar \&n\&1 Fl \&gt Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically
+greater than the integer
+.Ar \&n\&2 .
+.It Ar \&n\&1 Fl \&ge Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically
+greater than or equal to the integer
+.Ar \&n\&2 .
+.It Ar \&n\&1 Fl \&lt Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically less
+than the integer
+.Ar \&n\&2 .
+.It Ar \&n\&1 Fl \&le Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically less
+than or equal to the integer
+.Ar \&n\&2 .
+.El
+.Pp
+These primaries can be combined with the following operators:
+.Bl -tag -width Ar
+.It Cm \&! Ar expression
+True if
+.Ar expression
+is false.
+.It Ar expression1 Fl a Ar expression2
+True if both
+.Ar expression1
+and
+.Ar expression2
+are true.
+.It Ar expression1 Fl o Ar expression2
+True if either
+.Ar expression1
+or
+.Ar expression2
+are true.
+.It Cm \&( Ns Ar expression Ns Cm \&)
+True if expression is true.
+.El
+.Pp
+The
+.Fl a
+operator has higher precedence than the
+.Fl o
+operator.
+.Sh GRAMMAR AMBIGUITY
+The
+.Nm test
+grammar is inherently ambiguous.
+In order to assure a degree of consistency, the cases described in
+.St -p1003.2
+section 4.62.4,
+are evaluated consistently according to the rules specified in the
+standards document.
+All other cases are subject to the ambiguity in the command semantics.
+.Sh EXIT STATUS
+The
+.Nm test
+utility exits with one of the following values:
+.Bl -tag -width Ds
+.It 0
+expression evaluated to true.
+.It 1
+expression evaluated to false or expression was
+missing.
+.It \*[Gt]1
+An error occurred.
+.El
+.Sh STANDARDS
+The
+.Nm test
+utility implements a superset of the
+.St -p1003.2
+specification.
diff --git a/bin/dash/src/bltin/test.c b/bin/dash/src/bltin/test.c
new file mode 100644
index 00000000..c7fc479d
--- /dev/null
+++ b/bin/dash/src/bltin/test.c
@@ -0,0 +1,700 @@
+/*
+ * test(1); version 7-like  --  author Erik Baalbergen
+ * modified by Eric Gisin to be used as built-in.
+ * modified by Arnold Robbins to add SVR3 compatibility
+ * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket).
+ * modified by J.T. Conklin for NetBSD.
+ *
+ * This program is in the Public Domain.
+ */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include "bltin.h"
+
+/* test(1) accepts the following grammar:
+	oexpr	::= aexpr | aexpr "-o" oexpr ;
+	aexpr	::= nexpr | nexpr "-a" aexpr ;
+	nexpr	::= primary | "!" primary
+	primary	::= unary-operator operand
+		| operand binary-operator operand
+		| operand
+		| "(" oexpr ")"
+		;
+	unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"|
+		"-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S";
+
+	binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"|
+			"-nt"|"-ot"|"-ef";
+	operand ::= <any legal UNIX file name>
+*/
+
+enum token {
+	EOI,
+	FILRD,
+	FILWR,
+	FILEX,
+	FILEXIST,
+	FILREG,
+	FILDIR,
+	FILCDEV,
+	FILBDEV,
+	FILFIFO,
+	FILSOCK,
+	FILSYM,
+	FILGZ,
+	FILTT,
+	FILSUID,
+	FILSGID,
+	FILSTCK,
+	FILNT,
+	FILOT,
+	FILEQ,
+	FILUID,
+	FILGID,
+	STREZ,
+	STRNZ,
+	STREQ,
+	STRNE,
+	STRLT,
+	STRGT,
+	INTEQ,
+	INTNE,
+	INTGE,
+	INTGT,
+	INTLE,
+	INTLT,
+	UNOT,
+	BAND,
+	BOR,
+	LPAREN,
+	RPAREN,
+	OPERAND
+};
+
+enum token_types {
+	UNOP,
+	BINOP,
+	BUNOP,
+	BBINOP,
+	PAREN
+};
+
+static struct t_op {
+	const char *op_text;
+	short op_num, op_type;
+} const ops [] = {
+	{"-r",	FILRD,	UNOP},
+	{"-w",	FILWR,	UNOP},
+	{"-x",	FILEX,	UNOP},
+	{"-e",	FILEXIST,UNOP},
+	{"-f",	FILREG,	UNOP},
+	{"-d",	FILDIR,	UNOP},
+	{"-c",	FILCDEV,UNOP},
+	{"-b",	FILBDEV,UNOP},
+	{"-p",	FILFIFO,UNOP},
+	{"-u",	FILSUID,UNOP},
+	{"-g",	FILSGID,UNOP},
+	{"-k",	FILSTCK,UNOP},
+	{"-s",	FILGZ,	UNOP},
+	{"-t",	FILTT,	UNOP},
+	{"-z",	STREZ,	UNOP},
+	{"-n",	STRNZ,	UNOP},
+	{"-h",	FILSYM,	UNOP},		/* for backwards compat */
+	{"-O",	FILUID,	UNOP},
+	{"-G",	FILGID,	UNOP},
+	{"-L",	FILSYM,	UNOP},
+	{"-S",	FILSOCK,UNOP},
+	{"=",	STREQ,	BINOP},
+	{"!=",	STRNE,	BINOP},
+	{"<",	STRLT,	BINOP},
+	{">",	STRGT,	BINOP},
+	{"-eq",	INTEQ,	BINOP},
+	{"-ne",	INTNE,	BINOP},
+	{"-ge",	INTGE,	BINOP},
+	{"-gt",	INTGT,	BINOP},
+	{"-le",	INTLE,	BINOP},
+	{"-lt",	INTLT,	BINOP},
+	{"-nt",	FILNT,	BINOP},
+	{"-ot",	FILOT,	BINOP},
+	{"-ef",	FILEQ,	BINOP},
+	{"!",	UNOT,	BUNOP},
+	{"-a",	BAND,	BBINOP},
+	{"-o",	BOR,	BBINOP},
+	{"(",	LPAREN,	PAREN},
+	{")",	RPAREN,	PAREN},
+	{0,	0,	0}
+};
+
+static char **t_wp;
+static struct t_op const *t_wp_op;
+
+static void syntax(const char *, const char *);
+static int oexpr(enum token);
+static int aexpr(enum token);
+static int nexpr(enum token);
+static int primary(enum token);
+static int binop(void);
+static int filstat(char *, enum token);
+static enum token t_lex(char **);
+static int isoperand(char **);
+static int newerf(const char *, const char *);
+static int olderf(const char *, const char *);
+static int equalf(const char *, const char *);
+#ifdef HAVE_FACCESSAT
+static int test_file_access(const char *, int);
+#else
+static int test_access(const struct stat64 *, int);
+#endif
+
+#ifdef HAVE_FACCESSAT
+# ifdef HAVE_TRADITIONAL_FACCESSAT
+static inline int faccessat_confused_about_superuser(void) { return 1; }
+# else
+static inline int faccessat_confused_about_superuser(void) { return 0; }
+# endif
+#endif
+
+static inline intmax_t getn(const char *s)
+{
+	return atomax10(s);
+}
+
+static const struct t_op *getop(const char *s)
+{
+	const struct t_op *op;
+
+	for (op = ops; op->op_text; op++) {
+		if (strcmp(s, op->op_text) == 0)
+			return op;
+	}
+
+	return NULL;
+}
+
+int
+testcmd(int argc, char **argv)
+{
+	const struct t_op *op;
+	enum token n;
+	int res = 1;
+
+	if (*argv[0] == '[') {
+		if (*argv[--argc] != ']')
+			error("missing ]");
+		argv[argc] = NULL;
+	}
+
+	t_wp_op = NULL;
+
+recheck:
+	argv++;
+	argc--;
+
+	if (argc < 1)
+		return res;
+
+	/*
+	 * POSIX prescriptions: he who wrote this deserves the Nobel
+	 * peace prize.
+	 */
+	switch (argc) {
+	case 3:
+		op = getop(argv[1]);
+		if (op && op->op_type == BINOP) {
+			n = OPERAND;
+			goto eval;
+		}
+		/* fall through */
+
+	case 4:
+		if (!strcmp(argv[0], "(") && !strcmp(argv[argc - 1], ")")) {
+			argv[--argc] = NULL;
+			argv++;
+			argc--;
+		} else if (!strcmp(argv[0], "!")) {
+			res = 0;
+			goto recheck;
+		}
+	}
+
+	n = t_lex(argv);
+
+eval:
+	t_wp = argv;
+	res ^= oexpr(n);
+	argv = t_wp;
+
+	if (argv[0] != NULL && argv[1] != NULL)
+		syntax(argv[0], "unexpected operator");
+
+	return res;
+}
+
+static void
+syntax(const char *op, const char *msg)
+{
+	if (op && *op)
+		error("%s: %s", op, msg);
+	else
+		error("%s", msg);
+}
+
+static int
+oexpr(enum token n)
+{
+	int res = 0;
+
+	for (;;) {
+		res |= aexpr(n);
+		n = t_lex(t_wp + 1);
+		if (n != BOR)
+			break;
+		n = t_lex(t_wp += 2);
+	}
+	return res;
+}
+
+static int
+aexpr(enum token n)
+{
+	int res = 1;
+
+	for (;;) {
+		if (!nexpr(n))
+			res = 0;
+		n = t_lex(t_wp + 1);
+		if (n != BAND)
+			break;
+		n = t_lex(t_wp += 2);
+	}
+	return res;
+}
+
+static int
+nexpr(enum token n)
+{
+	if (n != UNOT)
+		return primary(n);
+
+	n = t_lex(t_wp + 1);
+	if (n != EOI)
+		t_wp++;
+	return !nexpr(n);
+}
+
+static int
+primary(enum token n)
+{
+	enum token nn;
+	int res;
+
+	if (n == EOI)
+		return 0;		/* missing expression */
+	if (n == LPAREN) {
+		if ((nn = t_lex(++t_wp)) == RPAREN)
+			return 0;	/* missing expression */
+		res = oexpr(nn);
+		if (t_lex(++t_wp) != RPAREN)
+			syntax(NULL, "closing paren expected");
+		return res;
+	}
+	if (t_wp_op && t_wp_op->op_type == UNOP) {
+		/* unary expression */
+		if (*++t_wp == NULL)
+			syntax(t_wp_op->op_text, "argument expected");
+		switch (n) {
+		case STREZ:
+			return strlen(*t_wp) == 0;
+		case STRNZ:
+			return strlen(*t_wp) != 0;
+		case FILTT:
+			return isatty(getn(*t_wp));
+#ifdef HAVE_FACCESSAT
+		case FILRD:
+			return test_file_access(*t_wp, R_OK);
+		case FILWR:
+			return test_file_access(*t_wp, W_OK);
+		case FILEX:
+			return test_file_access(*t_wp, X_OK);
+#endif
+		default:
+			return filstat(*t_wp, n);
+		}
+	}
+
+	if (t_lex(t_wp + 1), t_wp_op && t_wp_op->op_type == BINOP) {
+		return binop();
+	}
+
+	return strlen(*t_wp) > 0;
+}
+
+static int
+binop(void)
+{
+	const char *opnd1, *opnd2;
+	struct t_op const *op;
+
+	opnd1 = *t_wp;
+	(void) t_lex(++t_wp);
+	op = t_wp_op;
+
+	if ((opnd2 = *++t_wp) == (char *)0)
+		syntax(op->op_text, "argument expected");
+
+	switch (op->op_num) {
+	default:
+#ifdef DEBUG
+		abort();
+		/* NOTREACHED */
+#endif
+	case STREQ:
+		return strcmp(opnd1, opnd2) == 0;
+	case STRNE:
+		return strcmp(opnd1, opnd2) != 0;
+	case STRLT:
+		return strcmp(opnd1, opnd2) < 0;
+	case STRGT:
+		return strcmp(opnd1, opnd2) > 0;
+	case INTEQ:
+		return getn(opnd1) == getn(opnd2);
+	case INTNE:
+		return getn(opnd1) != getn(opnd2);
+	case INTGE:
+		return getn(opnd1) >= getn(opnd2);
+	case INTGT:
+		return getn(opnd1) > getn(opnd2);
+	case INTLE:
+		return getn(opnd1) <= getn(opnd2);
+	case INTLT:
+		return getn(opnd1) < getn(opnd2);
+	case FILNT:
+		return newerf (opnd1, opnd2);
+	case FILOT:
+		return olderf (opnd1, opnd2);
+	case FILEQ:
+		return equalf (opnd1, opnd2);
+	}
+}
+
+static int
+filstat(char *nm, enum token mode)
+{
+	struct stat64 s;
+
+	if (mode == FILSYM ? lstat64(nm, &s) : stat64(nm, &s))
+		return 0;
+
+	switch (mode) {
+#ifndef HAVE_FACCESSAT
+	case FILRD:
+		return test_access(&s, R_OK);
+	case FILWR:
+		return test_access(&s, W_OK);
+	case FILEX:
+		return test_access(&s, X_OK);
+#endif
+	case FILEXIST:
+		return 1;
+	case FILREG:
+		return S_ISREG(s.st_mode);
+	case FILDIR:
+		return S_ISDIR(s.st_mode);
+	case FILCDEV:
+		return S_ISCHR(s.st_mode);
+	case FILBDEV:
+		return S_ISBLK(s.st_mode);
+	case FILFIFO:
+		return S_ISFIFO(s.st_mode);
+	case FILSOCK:
+		return S_ISSOCK(s.st_mode);
+	case FILSYM:
+		return S_ISLNK(s.st_mode);
+	case FILSUID:
+		return (s.st_mode & S_ISUID) != 0;
+	case FILSGID:
+		return (s.st_mode & S_ISGID) != 0;
+	case FILSTCK:
+		return (s.st_mode & S_ISVTX) != 0;
+	case FILGZ:
+		return !!s.st_size;
+	case FILUID:
+		return s.st_uid == geteuid();
+	case FILGID:
+		return s.st_gid == getegid();
+	default:
+		return 1;
+	}
+}
+
+static enum token t_lex(char **tp)
+{
+	struct t_op const *op;
+	char *s = *tp;
+
+	if (s == 0) {
+		t_wp_op = (struct t_op *)0;
+		return EOI;
+	}
+
+	op = getop(s);
+	if (op && !(op->op_type == UNOP && isoperand(tp)) &&
+	    !(op->op_num == LPAREN && !tp[1])) {
+		t_wp_op = op;
+		return op->op_num;
+	}
+
+	t_wp_op = (struct t_op *)0;
+	return OPERAND;
+}
+
+static int isoperand(char **tp)
+{
+	struct t_op const *op;
+	char *s;
+
+	if (!(s = tp[1]))
+		return 1;
+	if (!tp[2])
+		return 0;
+
+	op = getop(s);
+	return op && op->op_type == BINOP;
+}
+
+static int
+newerf (const char *f1, const char *f2)
+{
+	struct stat64 b1, b2;
+
+#ifdef HAVE_ST_MTIM
+	return (stat64(f1, &b1) == 0 &&
+		stat64(f2, &b2) == 0 &&
+		( b1.st_mtim.tv_sec > b2.st_mtim.tv_sec ||
+		 (b1.st_mtim.tv_sec == b2.st_mtim.tv_sec && (b1.st_mtim.tv_nsec > b2.st_mtim.tv_nsec )))
+	);
+#else
+	return (stat64(f1, &b1) == 0 &&
+		stat64(f2, &b2) == 0 &&
+		b1.st_mtime > b2.st_mtime);
+#endif
+}
+
+static int
+olderf (const char *f1, const char *f2)
+{
+	struct stat64 b1, b2;
+
+#ifdef HAVE_ST_MTIM
+	return (stat64(f1, &b1) == 0 &&
+		stat64(f2, &b2) == 0 &&
+		(b1.st_mtim.tv_sec < b2.st_mtim.tv_sec ||
+		 (b1.st_mtim.tv_sec == b2.st_mtim.tv_sec && (b1.st_mtim.tv_nsec < b2.st_mtim.tv_nsec )))
+	);
+#else
+	return (stat64(f1, &b1) == 0 &&
+		stat64(f2, &b2) == 0 &&
+		b1.st_mtime < b2.st_mtime);
+#endif
+}
+
+static int
+equalf (const char *f1, const char *f2)
+{
+	struct stat64 b1, b2;
+
+	return (stat64(f1, &b1) == 0 &&
+		stat64(f2, &b2) == 0 &&
+		b1.st_dev == b2.st_dev &&
+		b1.st_ino == b2.st_ino);
+}
+
+#ifdef HAVE_FACCESSAT
+static int has_exec_bit_set(const char *path)
+{
+	struct stat64 st;
+
+	if (stat64(path, &st))
+		return 0;
+	return st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH);
+}
+
+static int test_file_access(const char *path, int mode)
+{
+	if (faccessat_confused_about_superuser() &&
+	    mode == X_OK && geteuid() == 0 && !has_exec_bit_set(path))
+		return 0;
+	return !faccessat(AT_FDCWD, path, mode, AT_EACCESS);
+}
+#else	/* HAVE_FACCESSAT */
+/*
+ * The manual, and IEEE POSIX 1003.2, suggests this should check the mode bits,
+ * not use access():
+ *
+ *	True shall indicate only that the write flag is on.  The file is not
+ *	writable on a read-only file system even if this test indicates true.
+ *
+ * Unfortunately IEEE POSIX 1003.1-2001, as quoted in SuSv3, says only:
+ *
+ *	True shall indicate that permission to read from file will be granted,
+ *	as defined in "File Read, Write, and Creation".
+ *
+ * and that section says:
+ *
+ *	When a file is to be read or written, the file shall be opened with an
+ *	access mode corresponding to the operation to be performed.  If file
+ *	access permissions deny access, the requested operation shall fail.
+ *
+ * and of course access permissions are described as one might expect:
+ *
+ *     * If a process has the appropriate privilege:
+ *
+ *        * If read, write, or directory search permission is requested,
+ *          access shall be granted.
+ *
+ *        * If execute permission is requested, access shall be granted if
+ *          execute permission is granted to at least one user by the file
+ *          permission bits or by an alternate access control mechanism;
+ *          otherwise, access shall be denied.
+ *
+ *   * Otherwise:
+ *
+ *        * The file permission bits of a file contain read, write, and
+ *          execute/search permissions for the file owner class, file group
+ *          class, and file other class.
+ *
+ *        * Access shall be granted if an alternate access control mechanism
+ *          is not enabled and the requested access permission bit is set for
+ *          the class (file owner class, file group class, or file other class)
+ *          to which the process belongs, or if an alternate access control
+ *          mechanism is enabled and it allows the requested access; otherwise,
+ *          access shall be denied.
+ *
+ * and when I first read this I thought:  surely we can't go about using
+ * open(O_WRONLY) to try this test!  However the POSIX 1003.1-2001 Rationale
+ * section for test does in fact say:
+ *
+ *	On historical BSD systems, test -w directory always returned false
+ *	because test tried to open the directory for writing, which always
+ *	fails.
+ *
+ * and indeed this is in fact true for Seventh Edition UNIX, UNIX 32V, and UNIX
+ * System III, and thus presumably also for BSD up to and including 4.3.
+ *
+ * Secondly I remembered why using open() and/or access() are bogus.  They
+ * don't work right for detecting read and write permissions bits when called
+ * by root.
+ *
+ * Interestingly the 'test' in 4.4BSD was closer to correct (as per
+ * 1003.2-1992) and it was implemented efficiently with stat() instead of
+ * open().
+ *
+ * This was apparently broken in NetBSD around about 1994/06/30 when the old
+ * 4.4BSD implementation was replaced with a (arguably much better coded)
+ * implementation derived from pdksh.
+ *
+ * Note that modern pdksh is yet different again, but still not correct, at
+ * least not w.r.t. 1003.2-1992.
+ *
+ * As I think more about it and read more of the related IEEE docs I don't like
+ * that wording about 'test -r' and 'test -w' in 1003.1-2001 at all.  I very
+ * much prefer the original wording in 1003.2-1992.  It is much more useful,
+ * and so that's what I've implemented.
+ *
+ * (Note that a strictly conforming implementation of 1003.1-2001 is in fact
+ * totally useless for the case in question since its 'test -w' and 'test -r'
+ * can never fail for root for any existing files, i.e. files for which 'test
+ * -e' succeeds.)
+ *
+ * The rationale for 1003.1-2001 suggests that the wording was "clarified" in
+ * 1003.1-2001 to align with the 1003.2b draft.  1003.2b Draft 12 (July 1999),
+ * which is the latest copy I have, does carry the same suggested wording as is
+ * in 1003.1-2001, with its rationale saying:
+ *
+ * 	This change is a clarification and is the result of interpretation
+ * 	request PASC 1003.2-92 #23 submitted for IEEE Std 1003.2-1992.
+ *
+ * That interpretation can be found here:
+ *
+ *   http://www.pasc.org/interps/unofficial/db/p1003.2/pasc-1003.2-23.html
+ *
+ * Not terribly helpful, unfortunately.  I wonder who that fence sitter was.
+ *
+ * Worse, IMVNSHO, I think the authors of 1003.2b-D12 have mis-interpreted the
+ * PASC interpretation and appear to be gone against at least one widely used
+ * implementation (namely 4.4BSD).  The problem is that for file access by root
+ * this means that if test '-r' and '-w' are to behave as if open() were called
+ * then there's no way for a shell script running as root to check if a file
+ * has certain access bits set other than by the grotty means of interpreting
+ * the output of 'ls -l'.  This was widely considered to be a bug in V7's
+ * "test" and is, I believe, one of the reasons why direct use of access() was
+ * avoided in some more recent implementations!
+ *
+ * I have always interpreted '-r' to match '-w' and '-x' as per the original
+ * wording in 1003.2-1992, not the other way around.  I think 1003.2b goes much
+ * too far the wrong way without any valid rationale and that it's best if we
+ * stick with 1003.2-1992 and test the flags, and not mimic the behaviour of
+ * open() since we already know very well how it will work -- existance of the
+ * file is all that matters to open() for root.
+ *
+ * Unfortunately the SVID is no help at all (which is, I guess, partly why
+ * we're in this mess in the first place :-).
+ *
+ * The SysV implementation (at least in the 'test' builtin in /bin/sh) does use
+ * access(name, 2) even though it also goes to much greater lengths for '-x'
+ * matching the 1003.2-1992 definition (which is no doubt where that definition
+ * came from).
+ *
+ * The ksh93 implementation uses access() for '-r' and '-w' if
+ * (euid==uid&&egid==gid), but uses st_mode for '-x' iff running as root.
+ * i.e. it does strictly conform to 1003.1-2001 (and presumably 1003.2b).
+ */
+static int test_access(const struct stat64 *sp, int stmode)
+{
+	gid_t *groups;
+	register int n;
+	uid_t euid;
+	int maxgroups;
+
+	/*
+	 * I suppose we could use access() if not running as root and if we are
+	 * running with ((euid == uid) && (egid == gid)), but we've already
+	 * done the stat() so we might as well just test the permissions
+	 * directly instead of asking the kernel to do it....
+	 */
+	euid = geteuid();
+	if (euid == 0) {
+		if (stmode != X_OK)
+			return 1;
+
+		/* any bit is good enough */
+		stmode = (stmode << 6) | (stmode << 3) | stmode;
+	} else if (sp->st_uid == euid)
+		stmode <<= 6;
+	else if (sp->st_gid == getegid())
+		stmode <<= 3;
+	else {
+		/* XXX stolen almost verbatim from ksh93.... */
+		/* on some systems you can be in several groups */
+		maxgroups = getgroups(0, NULL);
+		groups = stalloc(maxgroups * sizeof(*groups));
+		n = getgroups(maxgroups, groups);
+		while (--n >= 0) {
+			if (groups[n] == sp->st_gid) {
+				stmode <<= 3;
+				break;
+			}
+		}
+	}
+
+	return sp->st_mode & stmode;
+}
+#endif	/* HAVE_FACCESSAT */
diff --git a/bin/dash/src/bltin/times.c b/bin/dash/src/bltin/times.c
new file mode 100644
index 00000000..1166a68e
--- /dev/null
+++ b/bin/dash/src/bltin/times.c
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 1999 Herbert Xu <herbert@gondor.apana.org.au>
+ * This file contains code for the times builtin.
+ */
+
+#include <sys/times.h>
+#include <unistd.h>
+#ifdef USE_GLIBC_STDIO
+#include <stdio.h>
+#else
+#include "bltin.h"
+#endif
+#include "system.h"
+
+int timescmd() {
+	struct tms buf;
+	long int clk_tck = sysconf(_SC_CLK_TCK);
+	int mutime, mstime, mcutime, mcstime;
+	double utime, stime, cutime, cstime;
+
+	times(&buf);
+
+	utime = (double)buf.tms_utime / clk_tck;
+	mutime = utime / 60;
+	utime -= mutime * 60.0;
+
+	stime = (double)buf.tms_stime / clk_tck;
+	mstime = stime / 60;
+	stime -= mstime * 60.0;
+
+	cutime = (double)buf.tms_cutime / clk_tck;
+	mcutime = cutime / 60;
+	cutime -= mcutime * 60.0;
+
+	cstime = (double)buf.tms_cstime / clk_tck;
+	mcstime = cstime / 60;
+	cstime -= mcstime * 60.0;
+
+	printf("%dm%fs %dm%fs\n%dm%fs %dm%fs\n", mutime, utime, mstime, stime,
+	       mcutime, cutime, mcstime, cstime);
+	return 0;
+}
diff --git a/bin/dash/src/builtins.def.in b/bin/dash/src/builtins.def.in
new file mode 100644
index 00000000..95e420cc
--- /dev/null
+++ b/bin/dash/src/builtins.def.in
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)builtins.def	8.4 (Berkeley) 5/4/95
+ */
+
+/*
+ * This file lists all the builtin commands.  The first column is the name
+ * of a C routine.
+ * The -a flag specifies that this is a posix 'assignment builtin' command.
+ * The -s flag specifies that this is a posix 'special builtin' command.
+ * The -u flag specifies that this is a posix 'standard utility'.
+ * The -n flag specifies that this command has a special entry point.
+ * The rest of the line specifies the command name or names used to run
+ * the command.
+ */
+
+#ifndef JOBS
+#define JOBS 1
+#endif
+
+#if JOBS
+bgcmd		-u bg
+fgcmd		-u fg
+#endif
+
+#ifndef SMALL
+histcmd		-u fc
+#endif
+
+breakcmd	-s break -s continue
+cdcmd		-u cd chdir
+commandcmd	-u command
+dotcmd		-s .
+echocmd		echo
+evalcmd		-ns eval
+execcmd		-s exec
+exitcmd		-s exit
+exportcmd	-as export -as readonly
+falsecmd	-u false
+getoptscmd	-u getopts
+hashcmd		-u hash
+jobscmd		-u jobs
+localcmd	-as local
+printfcmd	printf
+pwdcmd		-u pwd
+readcmd		-u read
+returncmd	-s return
+setcmd		-s set
+shiftcmd	-s shift
+timescmd	-s times
+trapcmd		-s trap
+truecmd		-s : -u true
+typecmd		-u type
+umaskcmd	-u umask
+unaliascmd	-u unalias
+unsetcmd	-s unset
+waitcmd		-u wait
+aliascmd	-au alias
+#ifdef HAVE_GETRLIMIT
+ulimitcmd	-u ulimit
+#endif
+testcmd		test [
+killcmd		-u kill
diff --git a/bin/dash/src/cd.c b/bin/dash/src/cd.c
new file mode 100644
index 00000000..1ef1dc56
--- /dev/null
+++ b/bin/dash/src/cd.c
@@ -0,0 +1,332 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <limits.h>
+#ifdef __CYGWIN__
+#include <sys/cygwin.h>
+#endif
+
+/*
+ * The cd and pwd commands.
+ */
+
+#include "shell.h"
+#include "var.h"
+#include "nodes.h"	/* for jobs.h */
+#include "jobs.h"
+#include "options.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "exec.h"
+#include "redir.h"
+#include "main.h"
+#include "mystring.h"
+#include "show.h"
+#include "cd.h"
+
+#define CD_PHYSICAL 1
+#define CD_PRINT 2
+
+STATIC int docd(const char *, int);
+STATIC const char *updatepwd(const char *);
+STATIC char *getpwd(void);
+STATIC int cdopt(void);
+
+STATIC char *curdir = nullstr;		/* current working directory */
+STATIC char *physdir = nullstr;		/* physical working directory */
+
+STATIC int
+cdopt()
+{
+	int flags = 0;
+	int i, j;
+
+	j = 'L';
+	while ((i = nextopt("LP"))) {
+		if (i != j) {
+			flags ^= CD_PHYSICAL;
+			j = i;
+		}
+	}
+
+	return flags;
+}
+
+int
+cdcmd(int argc, char **argv)
+{
+	const char *dest;
+	const char *path;
+	const char *p;
+	char c;
+	struct stat64 statb;
+	int flags;
+	int len;
+
+	flags = cdopt();
+	dest = *argptr;
+	if (!dest)
+		dest = bltinlookup(homestr);
+	else if (dest[0] == '-' && dest[1] == '\0') {
+		dest = bltinlookup("OLDPWD");
+		flags |= CD_PRINT;
+	}
+	if (!dest)
+		dest = nullstr;
+	if (*dest == '/')
+		goto step6;
+	if (*dest == '.') {
+		c = dest[1];
+dotdot:
+		switch (c) {
+		case '\0':
+		case '/':
+			goto step6;
+		case '.':
+			c = dest[2];
+			if (c != '.')
+				goto dotdot;
+		}
+	}
+	if (!*dest)
+		dest = ".";
+	path = bltinlookup("CDPATH");
+	while (p = path, (len = padvance_magic(&path, dest, 0)) >= 0) {
+		c = *p;
+		p = stalloc(len);
+
+		if (stat64(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
+			if (c && c != ':')
+				flags |= CD_PRINT;
+docd:
+			if (!docd(p, flags))
+				goto out;
+			goto err;
+		}
+	}
+
+step6:
+	p = dest;
+	goto docd;
+
+err:
+	sh_error("can't cd to %s", dest);
+	/* NOTREACHED */
+out:
+	if (flags & CD_PRINT)
+		out1fmt(snlfmt, curdir);
+	return 0;
+}
+
+
+/*
+ * Actually do the chdir.  We also call hashcd to let the routines in exec.c
+ * know that the current directory has changed.
+ */
+
+STATIC int
+docd(const char *dest, int flags)
+{
+	const char *dir = 0;
+	int err;
+
+	TRACE(("docd(\"%s\", %d) called\n", dest, flags));
+
+	INTOFF;
+	if (!(flags & CD_PHYSICAL)) {
+		dir = updatepwd(dest);
+		if (dir)
+			dest = dir;
+	}
+	err = chdir(dest);
+	if (err)
+		goto out;
+	setpwd(dir, 1);
+	hashcd();
+out:
+	INTON;
+	return err;
+}
+
+
+/*
+ * Update curdir (the name of the current directory) in response to a
+ * cd command.
+ */
+
+STATIC const char *
+updatepwd(const char *dir)
+{
+	char *new;
+	char *p;
+	char *cdcomppath;
+	const char *lim;
+
+#ifdef __CYGWIN__
+	/* On cygwin, thanks to drive letters, some absolute paths do
+	   not begin with slash; but cygwin includes a function that
+	   forces normalization to the posix form */
+	char pathbuf[PATH_MAX];
+	if (cygwin_conv_path(CCP_WIN_A_TO_POSIX | CCP_RELATIVE, dir, pathbuf,
+			     sizeof(pathbuf)) < 0)
+		sh_error("can't normalize %s", dir);
+	dir = pathbuf;
+#endif
+
+	cdcomppath = sstrdup(dir);
+	STARTSTACKSTR(new);
+	if (*dir != '/') {
+		if (curdir == nullstr)
+			return 0;
+		new = stputs(curdir, new);
+	}
+	new = makestrspace(strlen(dir) + 2, new);
+	lim = stackblock() + 1;
+	if (*dir != '/') {
+		if (new[-1] != '/')
+			USTPUTC('/', new);
+		if (new > lim && *lim == '/')
+			lim++;
+	} else {
+		USTPUTC('/', new);
+		cdcomppath++;
+		if (dir[1] == '/' && dir[2] != '/') {
+			USTPUTC('/', new);
+			cdcomppath++;
+			lim++;
+		}
+	}
+	p = strtok(cdcomppath, "/");
+	while (p) {
+		switch(*p) {
+		case '.':
+			if (p[1] == '.' && p[2] == '\0') {
+				while (new > lim) {
+					STUNPUTC(new);
+					if (new[-1] == '/')
+						break;
+				}
+				break;
+			} else if (p[1] == '\0')
+				break;
+			/* fall through */
+		default:
+			new = stputs(p, new);
+			USTPUTC('/', new);
+		}
+		p = strtok(0, "/");
+	}
+	if (new > lim)
+		STUNPUTC(new);
+	*new = 0;
+	return stackblock();
+}
+
+
+/*
+ * Find out what the current directory is. If we already know the current
+ * directory, this routine returns immediately.
+ */
+inline
+STATIC char *
+getpwd()
+{
+#ifdef __GLIBC__
+	char *dir = getcwd(0, 0);
+
+	if (dir)
+		return dir;
+#else
+	char buf[PATH_MAX];
+
+	if (getcwd(buf, sizeof(buf)))
+		return savestr(buf);
+#endif
+
+	sh_warnx("getcwd() failed: %s", strerror(errno));
+	return nullstr;
+}
+
+int
+pwdcmd(int argc, char **argv)
+{
+	int flags;
+	const char *dir = curdir;
+
+	flags = cdopt();
+	if (flags) {
+		if (physdir == nullstr)
+			setpwd(dir, 0);
+		dir = physdir;
+	}
+	out1fmt(snlfmt, dir);
+	return 0;
+}
+
+void
+setpwd(const char *val, int setold)
+{
+	char *oldcur, *dir;
+
+	oldcur = dir = curdir;
+
+	if (setold) {
+		setvar("OLDPWD", oldcur, VEXPORT);
+	}
+	INTOFF;
+	if (physdir != nullstr) {
+		if (physdir != oldcur)
+			free(physdir);
+		physdir = nullstr;
+	}
+	if (oldcur == val || !val) {
+		char *s = getpwd();
+		physdir = s;
+		if (!val)
+			dir = s;
+	} else
+		dir = savestr(val);
+	if (oldcur != dir && oldcur != nullstr) {
+		free(oldcur);
+	}
+	curdir = dir;
+	INTON;
+	setvar("PWD", dir, VEXPORT);
+}
diff --git a/bin/dash/src/cd.h b/bin/dash/src/cd.h
new file mode 100644
index 00000000..87631619
--- /dev/null
+++ b/bin/dash/src/cd.h
@@ -0,0 +1,35 @@
+/*-
+ * Copyright (c) 1995
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+int	cdcmd(int, char **);
+int	pwdcmd(int, char **);
+void	setpwd(const char *, int);
diff --git a/bin/dash/src/dash.1 b/bin/dash/src/dash.1
new file mode 100644
index 00000000..9bc51cd6
--- /dev/null
+++ b/bin/dash/src/dash.1
@@ -0,0 +1,2361 @@
+.\" Copyright (c) 1991, 1993
+.\"	The Regents of the University of California.  All rights reserved.
+.\" Copyright (c) 1997-2005
+.\"	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Kenneth Almquist.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\"	@(#)sh.1	8.6 (Berkeley) 5/4/95
+.\"
+.Dd December 22, 2020
+.Os
+.Dt DASH 1
+.Sh NAME
+.Nm dash
+.Nd command interpreter (shell)
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Op Fl aCefnuvxIimqVEb
+.Op Cm +aCefnuvxIimqVEb
+.Ek
+.Bk -words
+.Op Fl o Ar option_name
+.Op Cm +o Ar option_name
+.Ek
+.Bk -words
+.Op Ar command_file Oo Ar argument ... Oc
+.Ek
+.Nm
+.Fl c
+.Bk -words
+.Op Fl aCefnuvxIimqVEb
+.Op Cm +aCefnuvxIimqVEb
+.Ek
+.Bk -words
+.Op Fl o Ar option_name
+.Op Cm +o Ar option_name
+.Ek
+.Bk -words
+.Ar command_string
+.Op Ar command_name Oo Ar argument ... Oc
+.Ek
+.Nm
+.Fl s
+.Bk -words
+.Op Fl aCefnuvxIimqVEb
+.Op Cm +aCefnuvxIimqVEb
+.Ek
+.Bk -words
+.Op Fl o Ar option_name
+.Op Cm +o Ar option_name
+.Ek
+.Bk -words
+.Op Ar argument ...
+.Ek
+.Sh DESCRIPTION
+.Nm
+is the standard command interpreter for the system.
+The current version of
+.Nm
+is in the process of being changed to conform with the
+.Tn POSIX
+1003.2 and 1003.2a specifications for the shell.
+This version has many
+features which make it appear similar in some respects to the Korn shell,
+but it is not a Korn shell clone (see
+.Xr ksh 1 ) .
+Only features designated by
+.Tn POSIX ,
+plus a few Berkeley extensions, are being incorporated into this shell.
+This man page is not intended
+to be a tutorial or a complete specification of the shell.
+.Ss Overview
+The shell is a command that reads lines from either a file or the
+terminal, interprets them, and generally executes other commands.
+It is the program that is running when a user logs into the system
+(although a user can select a different shell with the
+.Xr chsh 1
+command).
+The shell implements a language that has flow control
+constructs, a macro facility that provides a variety of features in
+addition to data storage, along with built in history and line editing
+capabilities.
+It incorporates many features to aid interactive use and
+has the advantage that the interpretative language is common to both
+interactive and non-interactive use (shell scripts).
+That is, commands
+can be typed directly to the running shell or can be put into a file and
+the file can be executed directly by the shell.
+.Ss Invocation
+If no args are present and if the standard input of the shell
+is connected to a terminal (or if the
+.Fl i
+flag is set),
+and the
+.Fl c
+option is not present, the shell is considered an interactive shell.
+An interactive shell generally prompts before each command and handles
+programming and command errors differently (as described below).
+When first starting,
+the shell inspects argument 0, and if it begins with a dash
+.Sq - ,
+the shell is also considered
+a login shell.
+This is normally done automatically by the system
+when the user first logs in.
+A login shell first reads commands
+from the files
+.Pa /etc/profile
+and
+.Pa .profile
+if they exist.
+If the environment variable
+.Ev ENV
+is set on entry to an interactive shell, or is set in the
+.Pa .profile
+of a login shell, the shell next reads
+commands from the file named in
+.Ev ENV .
+Therefore, a user should place commands that are to be executed only at
+login time in the
+.Pa .profile
+file, and commands that are executed for every interactive shell inside the
+.Ev ENV
+file.
+To set the
+.Ev ENV
+variable to some file, place the following line in your
+.Pa .profile
+of your home directory
+.Pp
+.Dl ENV=$HOME/.shinit; export ENV
+.Pp
+substituting for
+.Dq .shinit
+any filename you wish.
+.Pp
+If command line arguments besides the options have been specified, then
+the shell treats the first argument as the name of a file from which to
+read commands (a shell script), and the remaining arguments are set as the
+positional parameters of the shell ($1, $2, etc).
+Otherwise, the shell
+reads commands from its standard input.
+.Ss Argument List Processing
+All of the single letter options that have a corresponding name can be
+used as an argument to the
+.Fl o
+option.
+The set
+.Fl o
+name is provided next to the single letter option in
+the description below.
+Specifying a dash
+.Dq -
+turns the option on, while using a plus
+.Dq +
+disables the option.
+The following options can be set from the command line or
+with the
+.Ic set
+builtin (described later).
+.Bl -tag -width aaaallexportfoo -offset indent
+.It Fl a Em allexport
+Export all variables assigned to.
+.It Fl c
+Read commands from the
+.Ar command_string
+operand instead of from the standard input.
+Special parameter 0 will be set from the
+.Ar command_name
+operand and the positional parameters ($1, $2, etc.)
+set from the remaining argument operands.
+.It Fl C Em noclobber
+Don't overwrite existing files with
+.Dq \*[Gt] .
+.It Fl e Em errexit
+If not interactive, exit immediately if any untested command fails.
+The exit status of a command is considered to be
+explicitly tested if the command is used to control an
+.Ic if ,
+.Ic elif ,
+.Ic while ,
+or
+.Ic until ;
+or if the command is the left hand operand of an
+.Dq &&
+or
+.Dq ||
+operator.
+.It Fl f Em noglob
+Disable pathname expansion.
+.It Fl n Em noexec
+If not interactive, read commands but do not execute them.
+This is useful for checking the syntax of shell scripts.
+.It Fl u Em nounset
+Write a message to standard error when attempting to expand a variable
+that is not set, and if the shell is not interactive, exit immediately.
+.It Fl v Em verbose
+The shell writes its input to standard error as it is read.
+Useful for debugging.
+.It Fl x Em xtrace
+Write each command to standard error (preceded by a
+.Sq +\  )
+before it is executed.
+Useful for debugging.
+.It Fl I Em ignoreeof
+Ignore EOF's from input when interactive.
+.It Fl i Em interactive
+Force the shell to behave interactively.
+.It Fl l
+Make dash act as if it had been invoked as a login shell.
+.It Fl m Em monitor
+Turn on job control (set automatically when interactive).
+.It Fl s Em stdin
+Read commands from standard input (set automatically if no file arguments
+are present).
+This option has no effect when set after the shell has
+already started running (i.e. with
+.Ic set ) .
+.It Fl V Em vi
+Enable the built-in
+.Xr vi 1
+command line editor (disables
+.Fl E
+if it has been set).
+.It Fl E Em emacs
+Enable the built-in
+.Xr emacs 1
+command line editor (disables
+.Fl V
+if it has been set).
+.It Fl b Em notify
+Enable asynchronous notification of background job completion.
+(UNIMPLEMENTED for 4.4alpha)
+.El
+.Ss Lexical Structure
+The shell reads input in terms of lines from a file and breaks it up into
+words at whitespace (blanks and tabs), and at certain sequences of
+characters that are special to the shell called
+.Dq operators .
+There are two types of operators: control operators and redirection
+operators (their meaning is discussed later).
+Following is a list of operators:
+.Bl -ohang -offset indent
+.It "Control operators:"
+.Dl &  &&  \&(  \&)  \&;  ;; | || \*[Lt]newline\*[Gt]
+.It "Redirection operators:"
+.Dl \*[Lt]  \*[Gt]  \*[Gt]|  \*[Lt]\*[Lt]  \*[Gt]\*[Gt]  \*[Lt]&  \*[Gt]&  \*[Lt]\*[Lt]-  \*[Lt]\*[Gt]
+.El
+.Ss Quoting
+Quoting is used to remove the special meaning of certain characters or
+words to the shell, such as operators, whitespace, or keywords.
+There are three types of quoting: matched single quotes,
+matched double quotes, and backslash.
+.Ss Backslash
+A backslash preserves the literal meaning of the following
+character, with the exception of
+.Aq newline .
+A backslash preceding a
+.Aq newline
+is treated as a line continuation.
+.Ss Single Quotes
+Enclosing characters in single quotes preserves the literal meaning of all
+the characters (except single quotes, making it impossible to put
+single-quotes in a single-quoted string).
+.Ss Double Quotes
+Enclosing characters within double quotes preserves the literal
+meaning of all characters except dollarsign
+.Pq $ ,
+backquote
+.Pq ` ,
+and backslash
+.Pq \e .
+The backslash inside double quotes is historically weird, and serves to
+quote only the following characters:
+.Dl $  `  \*q  \e  \*[Lt]newline\*[Gt] .
+Otherwise it remains literal.
+.Ss Reserved Words
+Reserved words are words that have special meaning to the
+shell and are recognized at the beginning of a line and
+after a control operator.
+The following are reserved words:
+.Bl -column while while while while while -offset indent
+.It ! Ta elif Ta fi Ta while Ta case
+.It else Ta for Ta then Ta { Ta }
+.It do Ta done Ta until Ta if Ta esac
+.El
+.Pp
+Their meaning is discussed later.
+.Ss Aliases
+An alias is a name and corresponding value set using the
+.Xr alias 1
+builtin command.
+Whenever a reserved word may occur (see above),
+and after checking for reserved words, the shell
+checks the word to see if it matches an alias.
+If it does, it replaces it in the input stream with its value.
+For example, if there is an alias called
+.Dq lf
+with the value
+.Dq "ls -F" ,
+then the input:
+.Pp
+.Dl lf foobar Aq return
+.Pp
+would become
+.Pp
+.Dl ls -F foobar Aq return
+.Pp
+Aliases provide a convenient way for naive users to create shorthands for
+commands without having to learn how to create functions with arguments.
+They can also be used to create lexically obscure code.
+This use is discouraged.
+.Ss Commands
+The shell interprets the words it reads according to a language, the
+specification of which is outside the scope of this man page (refer to the
+BNF in the
+.Tn POSIX
+1003.2 document).
+Essentially though, a line is read and if the first
+word of the line (or after a control operator) is not a reserved word,
+then the shell has recognized a simple command.
+Otherwise, a complex
+command or some other special construct may have been recognized.
+.Ss Simple Commands
+If a simple command has been recognized, the shell performs
+the following actions:
+.Bl -enum -offset indent
+.It
+Leading words of the form
+.Dq name=value
+are stripped off and assigned to the environment of the simple command.
+Redirection operators and their arguments (as described below) are
+stripped off and saved for processing.
+.It
+The remaining words are expanded as described in
+the section called
+.Dq Expansions ,
+and the first remaining word is considered the command name and the
+command is located.
+The remaining words are considered the arguments of the command.
+If no command name resulted, then the
+.Dq name=value
+variable assignments recognized in item 1 affect the current shell.
+.It
+Redirections are performed as described in the next section.
+.El
+.Ss Redirections
+Redirections are used to change where a command reads its input or sends
+its output.
+In general, redirections open, close, or duplicate an
+existing reference to a file.
+The overall format used for redirection is:
+.Pp
+.Dl [n] Va redir-op Ar file
+.Pp
+where
+.Va redir-op
+is one of the redirection operators mentioned previously.
+Following is a list of the possible redirections.
+The
+.Bq n
+is an optional number between 0 and 9, as in
+.Sq 3
+(not
+.Sq Bq 3 ) ,
+that refers to a file descriptor.
+.Bl -tag -width aaabsfiles -offset indent
+.It [n] Ns \*[Gt] file
+Redirect standard output (or n) to file.
+.It [n] Ns \*[Gt]| file
+Same, but override the
+.Fl C
+option.
+.It [n] Ns \*[Gt]\*[Gt] file
+Append standard output (or n) to file.
+.It [n] Ns \*[Lt] file
+Redirect standard input (or n) from file.
+.It [n1] Ns \*[Lt]& Ns n2
+Copy file descriptor n2 as stdout (or fd n1).
+fd n2.
+.It [n] Ns \*[Lt]&-
+Close standard input (or n).
+.It [n1] Ns \*[Gt]& Ns n2
+Copy file descriptor n2 as stdin (or fd n1).
+fd n2.
+.It [n] Ns \*[Gt]&-
+Close standard output (or n).
+.It [n] Ns \*[Lt]\*[Gt] file
+Open file for reading and writing on standard input (or n).
+.El
+.Pp
+The following redirection is often called a
+.Dq here-document .
+.Bl -item -offset indent
+.It
+.Li [n]\*[Lt]\*[Lt] delimiter
+.Dl here-doc-text ...
+.Li delimiter
+.El
+.Pp
+All the text on successive lines up to the delimiter is saved away and
+made available to the command on standard input, or file descriptor n if
+it is specified.
+If the delimiter as specified on the initial line is
+quoted, then the here-doc-text is treated literally, otherwise the text is
+subjected to parameter expansion, command substitution, and arithmetic
+expansion (as described in the section on
+.Dq Expansions ) .
+If the operator is
+.Dq \*[Lt]\*[Lt]-
+instead of
+.Dq \*[Lt]\*[Lt] ,
+then leading tabs in the here-doc-text are stripped.
+.Ss Search and Execution
+There are three types of commands: shell functions, builtin commands, and
+normal programs -- and the command is searched for (by name) in that order.
+They each are executed in a different way.
+.Pp
+When a shell function is executed, all of the shell positional parameters
+(except $0, which remains unchanged) are set to the arguments of the shell
+function.
+The variables which are explicitly placed in the environment of
+the command (by placing assignments to them before the function name) are
+made local to the function and are set to the values given.
+Then the command given in the function definition is executed.
+The positional parameters are restored to their original values
+when the command completes.
+This all occurs within the current shell.
+.Pp
+Shell builtins are executed internally to the shell, without spawning a
+new process.
+.Pp
+Otherwise, if the command name doesn't match a function or builtin, the
+command is searched for as a normal program in the file system (as
+described in the next section).
+When a normal program is executed, the shell runs the program,
+passing the arguments and the environment to the program.
+If the program is not a normal executable file (i.e., if it does
+not begin with the "magic number" whose
+.Tn ASCII
+representation is "#!", so
+.Xr execve 2
+returns
+.Er ENOEXEC
+then) the shell will interpret the program in a subshell.
+The child shell will reinitialize itself in this case,
+so that the effect will be as if a
+new shell had been invoked to handle the ad-hoc shell script, except that
+the location of hashed commands located in the parent shell will be
+remembered by the child.
+.Pp
+Note that previous versions of this document and the source code itself
+misleadingly and sporadically refer to a shell script without a magic
+number as a "shell procedure".
+.Ss Path Search
+When locating a command, the shell first looks to see if it has a shell
+function by that name.
+Then it looks for a builtin command by that name.
+If a builtin command is not found, one of two things happen:
+.Bl -enum
+.It
+Command names containing a slash are simply executed without performing
+any searches.
+.It
+The shell searches each entry in
+.Ev PATH
+in turn for the command.
+The value of the
+.Ev PATH
+variable should be a series of entries separated by colons.
+Each entry consists of a directory name.
+The current directory may be indicated
+implicitly by an empty directory name, or explicitly by a single period.
+.El
+.Ss Command Exit Status
+Each command has an exit status that can influence the behaviour
+of other shell commands.
+The paradigm is that a command exits
+with zero for normal or success, and non-zero for failure,
+error, or a false indication.
+The man page for each command
+should indicate the various exit codes and what they mean.
+Additionally, the builtin commands return exit codes, as does
+an executed shell function.
+.Pp
+If a command consists entirely of variable assignments then the
+exit status of the command is that of the last command substitution
+if any, otherwise 0.
+.Ss Complex Commands
+Complex commands are combinations of simple commands with control
+operators or reserved words, together creating a larger complex command.
+More generally, a command is one of the following:
+.Bl -bullet
+.It
+simple command
+.It
+pipeline
+.It
+list or compound-list
+.It
+compound command
+.It
+function definition
+.El
+.Pp
+Unless otherwise stated, the exit status of a command is that of the last
+simple command executed by the command.
+.Ss Pipelines
+A pipeline is a sequence of one or more commands separated
+by the control operator |.
+The standard output of all but
+the last command is connected to the standard input
+of the next command.
+The standard output of the last
+command is inherited from the shell, as usual.
+.Pp
+The format for a pipeline is:
+.Pp
+.Dl [!] command1 [ | command2 ...]
+.Pp
+The standard output of command1 is connected to the standard input of
+command2.
+The standard input, standard output, or both of a command is
+considered to be assigned by the pipeline before any redirection specified
+by redirection operators that are part of the command.
+.Pp
+If the pipeline is not in the background (discussed later), the shell
+waits for all commands to complete.
+.Pp
+If the reserved word ! does not precede the pipeline, the exit status is
+the exit status of the last command specified in the pipeline.
+Otherwise, the exit status is the logical NOT of the exit status of the
+last command.
+That is, if the last command returns zero, the exit status
+is 1; if the last command returns greater than zero, the exit status is
+zero.
+.Pp
+Because pipeline assignment of standard input or standard output or both
+takes place before redirection, it can be modified by redirection.
+For example:
+.Pp
+.Dl $ command1 2\*[Gt]&1 | command2
+.Pp
+sends both the standard output and standard error of command1
+to the standard input of command2.
+.Pp
+A ; or
+.Aq newline
+terminator causes the preceding AND-OR-list (described
+next) to be executed sequentially; a & causes asynchronous execution of
+the preceding AND-OR-list.
+.Pp
+Note that unlike some other shells, each process in the pipeline is a
+child of the invoking shell (unless it is a shell builtin, in which case
+it executes in the current shell -- but any effect it has on the
+environment is wiped).
+.Ss Background Commands -- &
+If a command is terminated by the control operator ampersand (&), the
+shell executes the command asynchronously -- that is, the shell does not
+wait for the command to finish before executing the next command.
+.Pp
+The format for running a command in background is:
+.Pp
+.Dl command1 & [command2 & ...]
+.Pp
+If the shell is not interactive, the standard input of an asynchronous
+command is set to
+.Pa /dev/null .
+.Ss Lists -- Generally Speaking
+A list is a sequence of zero or more commands separated by newlines,
+semicolons, or ampersands, and optionally terminated by one of these three
+characters.
+The commands in a list are executed in the order they are written.
+If command is followed by an ampersand, the shell starts the
+command and immediately proceeds onto the next command; otherwise it waits
+for the command to terminate before proceeding to the next one.
+.Ss Short-Circuit List Operators
+.Dq &&
+and
+.Dq ||
+are AND-OR list operators.
+.Dq &&
+executes the first command, and then executes the second command if and only
+if the exit status of the first command is zero.
+.Dq ||
+is similar, but executes the second command if and only if the exit status
+of the first command is nonzero.
+.Dq &&
+and
+.Dq ||
+both have the same priority.
+.Ss Flow-Control Constructs -- if, while, for, case
+The syntax of the if command is
+.Bd -literal -offset indent
+if list
+then list
+[ elif list
+then    list ] ...
+[ else list ]
+fi
+.Ed
+.Pp
+The syntax of the while command is
+.Bd -literal -offset indent
+while list
+do   list
+done
+.Ed
+.Pp
+The two lists are executed repeatedly while the exit status of the
+first list is zero.
+The until command is similar, but has the word
+until in place of while, which causes it to
+repeat until the exit status of the first list is zero.
+.Pp
+The syntax of the for command is
+.Bd -literal -offset indent
+for variable [ in [ word ... ] ]
+do   list
+done
+.Ed
+.Pp
+The words following
+.Pa in
+are expanded, and then the list is executed repeatedly with the
+variable set to each word in turn.
+Omitting in word ... is equivalent to in "$@".
+.Pp
+The syntax of the break and continue command is
+.Bd -literal -offset indent
+break [ num ]
+continue [ num ]
+.Ed
+.Pp
+Break terminates the num innermost for or while loops.
+Continue continues with the next iteration of the innermost loop.
+These are implemented as builtin commands.
+.Pp
+The syntax of the case command is
+.Bd -literal -offset indent
+case word in
+[(]pattern) list ;;
+\&...
+esac
+.Ed
+.Pp
+The pattern can actually be one or more patterns (see
+.Sx Shell Patterns
+described later), separated by
+.Dq \*(Ba
+characters.
+The
+.Do
+(
+.Dc
+character before the pattern is optional.
+.Ss Grouping Commands Together
+Commands may be grouped by writing either
+.Pp
+.Dl (list)
+.Pp
+or
+.Pp
+.Dl { list; }
+.Pp
+The first of these executes the commands in a subshell.
+Builtin commands grouped into a (list) will not affect the current shell.
+The second form does not fork another shell so is slightly more efficient.
+Grouping commands together this way allows you to redirect
+their output as though they were one program:
+.Pp
+.Bd -literal -offset indent
+{ printf \*q hello \*q ; printf \*q world\\n" ; } \*[Gt] greeting
+.Ed
+.Pp
+Note that
+.Dq }
+must follow a control operator (here,
+.Dq \&; )
+so that it is recognized as a reserved word and not as another command argument.
+.Ss Functions
+The syntax of a function definition is
+.Pp
+.Dl name ( ) command
+.Pp
+A function definition is an executable statement; when executed it
+installs a function named name and returns an exit status of zero.
+The command is normally a list enclosed between
+.Dq {
+and
+.Dq } .
+.Pp
+Variables may be declared to be local to a function by using a local
+command.
+This should appear as the first statement of a function, and the syntax is
+.Pp
+.Dl local [ variable | - ] ...
+.Pp
+Local is implemented as a builtin command.
+.Pp
+When a variable is made local, it inherits the initial value and exported
+and readonly flags from the variable with the same name in the surrounding
+scope, if there is one.
+Otherwise, the variable is initially unset.
+The shell uses dynamic scoping, so that if you make the variable x local to
+function f, which then calls function g, references to the variable x made
+inside g will refer to the variable x declared inside f, not to the global
+variable named x.
+.Pp
+The only special parameter that can be made local is
+.Dq - .
+Making
+.Dq -
+local any shell options that are changed via the set command inside the
+function to be restored to their original values when the function
+returns.
+.Pp
+The syntax of the return command is
+.Pp
+.Dl return [ exitstatus ]
+.Pp
+It terminates the currently executing function.
+Return is implemented as a builtin command.
+.Ss Variables and Parameters
+The shell maintains a set of parameters.
+A parameter denoted by a name is called a variable.
+When starting up, the shell turns all the environment
+variables into shell variables.
+New variables can be set using the form
+.Pp
+.Dl name=value
+.Pp
+Variables set by the user must have a name consisting solely of
+alphabetics, numerics, and underscores - the first of which must not be
+numeric.
+A parameter can also be denoted by a number or a special
+character as explained below.
+.Ss Positional Parameters
+A positional parameter is a parameter denoted by a number (n \*[Gt] 0).
+The shell sets these initially to the values of its command line arguments
+that follow the name of the shell script.
+The
+.Ic set
+builtin can also be used to set or reset them.
+.Ss Special Parameters
+A special parameter is a parameter denoted by one of the following special
+characters.
+The value of the parameter is listed next to its character.
+.Bl -tag -width thinhyphena
+.It *
+Expands to the positional parameters, starting from one.
+When the
+expansion occurs within a double-quoted string it expands to a single
+field with the value of each parameter separated by the first character of
+the
+.Ev IFS
+variable, or by a
+.Aq space
+if
+.Ev IFS
+is unset.
+.It @
+Expands to the positional parameters, starting from one.
+When the expansion occurs within double-quotes, each positional
+parameter expands as a separate argument.
+If there are no positional parameters, the
+expansion of @ generates zero arguments, even when @ is
+double-quoted.
+What this basically means, for example, is
+if $1 is
+.Dq abc
+and $2 is
+.Dq def ghi ,
+then
+.Qq $@
+expands to
+the two arguments:
+.Pp
+.Sm off
+.Dl \*q abc \*q \  \*q def\ ghi \*q
+.Sm on
+.It #
+Expands to the number of positional parameters.
+.It ?
+Expands to the exit status of the most recent pipeline.
+.It - (Hyphen.)
+Expands to the current option flags (the single-letter
+option names concatenated into a string) as specified on
+invocation, by the set builtin command, or implicitly
+by the shell.
+.It $
+Expands to the process ID of the invoked shell.
+A subshell retains the same value of $ as its parent.
+.It !
+Expands to the process ID of the most recent background
+command executed from the current shell.
+For a pipeline, the process ID is that of the last command in the pipeline.
+.It 0 (Zero.)
+Expands to the name of the shell or shell script.
+.El
+.Ss Word Expansions
+This clause describes the various expansions that are performed on words.
+Not all expansions are performed on every word, as explained later.
+.Pp
+Tilde expansions, parameter expansions, command substitutions, arithmetic
+expansions, and quote removals that occur within a single word expand to a
+single field.
+It is only field splitting or pathname expansion that can
+create multiple fields from a single word.
+The single exception to this
+rule is the expansion of the special parameter @ within double-quotes, as
+was described above.
+.Pp
+The order of word expansion is:
+.Bl -enum
+.It
+Tilde Expansion, Parameter Expansion, Command Substitution,
+Arithmetic Expansion (these all occur at the same time).
+.It
+Field Splitting is performed on fields
+generated by step (1) unless the
+.Ev IFS
+variable is null.
+.It
+Pathname Expansion (unless set
+.Fl f
+is in effect).
+.It
+Quote Removal.
+.El
+.Pp
+The $ character is used to introduce parameter expansion, command
+substitution, or arithmetic evaluation.
+.Ss Tilde Expansion (substituting a user's home directory)
+A word beginning with an unquoted tilde character (~) is
+subjected to tilde expansion.
+All the characters up to
+a slash (/) or the end of the word are treated as a username
+and are replaced with the user's home directory.
+If the username is missing (as in
+.Pa ~/foobar ) ,
+the tilde is replaced with the value of the
+.Va HOME
+variable (the current user's home directory).
+.Ss Parameter Expansion
+The format for parameter expansion is as follows:
+.Pp
+.Dl ${expression}
+.Pp
+where expression consists of all characters until the matching
+.Dq } .
+Any
+.Dq }
+escaped by a backslash or within a quoted string, and characters in
+embedded arithmetic expansions, command substitutions, and variable
+expansions, are not examined in determining the matching
+.Dq } .
+.Pp
+The simplest form for parameter expansion is:
+.Pp
+.Dl ${parameter}
+.Pp
+The value, if any, of parameter is substituted.
+.Pp
+The parameter name or symbol can be enclosed in braces, which are
+optional except for positional parameters with more than one digit or
+when parameter is followed by a character that could be interpreted as
+part of the name.
+If a parameter expansion occurs inside double-quotes:
+.Bl -enum
+.It
+Pathname expansion is not performed on the results of the expansion.
+.It
+Field splitting is not performed on the results of the
+expansion, with the exception of @.
+.El
+.Pp
+In addition, a parameter expansion can be modified by using one of the
+following formats.
+.Bl -tag -width aaparameterwordaaaaa
+.It ${parameter:-word}
+Use Default Values.
+If parameter is unset or null, the expansion of word
+is substituted; otherwise, the value of parameter is substituted.
+.It ${parameter:=word}
+Assign Default Values.
+If parameter is unset or null, the expansion of
+word is assigned to parameter.
+In all cases, the final value of parameter is substituted.
+Only variables, not positional parameters or special
+parameters, can be assigned in this way.
+.It ${parameter:?[word]}
+Indicate Error if Null or Unset.
+If parameter is unset or null, the
+expansion of word (or a message indicating it is unset if word is omitted)
+is written to standard error and the shell exits with a nonzero exit status.
+Otherwise, the value of parameter is substituted.
+An interactive shell need not exit.
+.It ${parameter:+word}
+Use Alternative Value.
+If parameter is unset or null, null is
+substituted; otherwise, the expansion of word is substituted.
+.El
+.Pp
+In the parameter expansions shown previously, use of the colon in the
+format results in a test for a parameter that is unset or null; omission
+of the colon results in a test for a parameter that is only unset.
+.Bl -tag -width aaparameterwordaaaaa
+.It ${#parameter}
+String Length.
+The length in characters of the value of parameter.
+.El
+.Pp
+The following four varieties of parameter expansion provide for substring
+processing.
+In each case, pattern matching notation (see
+.Sx Shell Patterns ) ,
+rather than regular expression notation, is used to evaluate the patterns.
+If parameter is * or @, the result of the expansion is unspecified.
+Enclosing the full parameter expansion string in double-quotes does not
+cause the following four varieties of pattern characters to be quoted,
+whereas quoting characters within the braces has this effect.
+.Bl -tag -width aaparameterwordaaaaa
+.It ${parameter%word}
+Remove Smallest Suffix Pattern.
+The word is expanded to produce a pattern.
+The parameter expansion then results in parameter, with the
+smallest portion of the suffix matched by the pattern deleted.
+.It ${parameter%%word}
+Remove Largest Suffix Pattern.
+The word is expanded to produce a pattern.
+The parameter expansion then results in parameter, with the largest
+portion of the suffix matched by the pattern deleted.
+.It ${parameter#word}
+Remove Smallest Prefix Pattern.
+The word is expanded to produce a pattern.
+The parameter expansion then results in parameter, with the
+smallest portion of the prefix matched by the pattern deleted.
+.It ${parameter##word}
+Remove Largest Prefix Pattern.
+The word is expanded to produce a pattern.
+The parameter expansion then results in parameter, with the largest
+portion of the prefix matched by the pattern deleted.
+.El
+.Ss Command Substitution
+Command substitution allows the output of a command to be substituted in
+place of the command name itself.
+Command substitution occurs when the command is enclosed as follows:
+.Pp
+.Dl $(command)
+.Pp
+or
+.Po
+.Dq backquoted
+version
+.Pc :
+.Pp
+.Dl `command`
+.Pp
+The shell expands the command substitution by executing command in a
+subshell environment and replacing the command substitution with the
+standard output of the command, removing sequences of one or more
+.Ao newline Ac Ns s
+at the end of the substitution.
+(Embedded
+.Ao newline Ac Ns s
+before
+the end of the output are not removed; however, during field splitting,
+they may be translated into
+.Ao space Ac Ns s ,
+depending on the value of
+.Ev IFS
+and quoting that is in effect.)
+.Ss Arithmetic Expansion
+Arithmetic expansion provides a mechanism for evaluating an arithmetic
+expression and substituting its value.
+The format for arithmetic expansion is as follows:
+.Pp
+.Dl $((expression))
+.Pp
+The expression is treated as if it were in double-quotes, except
+that a double-quote inside the expression is not treated specially.
+The shell expands all tokens in the expression for parameter expansion,
+command substitution, and quote removal.
+.Pp
+Next, the shell treats this as an arithmetic expression and
+substitutes the value of the expression.
+.Ss White Space Splitting (Field Splitting)
+After parameter expansion, command substitution, and
+arithmetic expansion the shell scans the results of
+expansions and substitutions that did not occur in double-quotes for
+field splitting and multiple fields can result.
+.Pp
+The shell treats each character of the
+.Ev IFS
+as a delimiter and uses the delimiters to split the results of parameter
+expansion and command substitution into fields.
+.Ss Pathname Expansion (File Name Generation)
+Unless the
+.Fl f
+flag is set, file name generation is performed after word splitting is
+complete.
+Each word is viewed as a series of patterns, separated by slashes.
+The process of expansion replaces the word with the names of all
+existing files whose names can be formed by replacing each pattern with a
+string that matches the specified pattern.
+There are two restrictions on
+this: first, a pattern cannot match a string containing a slash, and
+second, a pattern cannot match a string starting with a period unless the
+first character of the pattern is a period.
+The next section describes the
+patterns used for both Pathname Expansion and the
+.Ic case
+command.
+.Ss Shell Patterns
+A pattern consists of normal characters, which match themselves,
+and meta-characters.
+The meta-characters are
+.Dq \&! ,
+.Dq * ,
+.Dq \&? ,
+and
+.Dq \&[ .
+These characters lose their special meanings if they are quoted.
+When command or variable substitution is performed
+and the dollar sign or back quotes are not double quoted,
+the value of the variable or the output of
+the command is scanned for these characters and they are turned into
+meta-characters.
+.Pp
+An asterisk
+.Pq Dq *
+matches any string of characters.
+A question mark matches any single character.
+A left bracket
+.Pq Dq \&[
+introduces a character class.
+The end of the character class is indicated by a
+.Pq Dq \&] ;
+if the
+.Dq \&]
+is missing then the
+.Dq \&[
+matches a
+.Dq \&[
+rather than introducing a character class.
+A character class matches any of the characters between the square brackets.
+A range of characters may be specified using a minus sign.
+The character class may be complemented
+by making an exclamation point the first character of the character class.
+.Pp
+To include a
+.Dq \&]
+in a character class, make it the first character listed (after the
+.Dq \&! ,
+if any).
+To include a minus sign, make it the first or last character listed.
+.Ss Builtins
+This section lists the builtin commands which are builtin because they
+need to perform some operation that can't be performed by a separate
+process.
+In addition to these, there are several other commands that may
+be builtin for efficiency (e.g.
+.Xr printf 1 ,
+.Xr echo 1 ,
+.Xr test 1 ,
+etc).
+.Bl -tag -width 5n
+.It :
+.It true
+A null command that returns a 0 (true) exit value.
+.It \&. file
+The commands in the specified file are read and executed by the shell.
+.It alias Op Ar name Ns Op Ar "=string ..."
+If
+.Ar name=string
+is specified, the shell defines the alias
+.Ar name
+with value
+.Ar string .
+If just
+.Ar name
+is specified, the value of the alias
+.Ar name
+is printed.
+With no arguments, the
+.Ic alias
+builtin prints the
+names and values of all defined aliases (see
+.Ic unalias ) .
+.It bg [ Ar job ] ...
+Continue the specified jobs (or the current job if no
+jobs are given) in the background.
+.It Xo command
+.Op Fl p
+.Op Fl v
+.Op Fl V
+.Ar command
+.Op Ar arg ...
+.Xc
+Execute the specified command but ignore shell functions when searching
+for it.
+(This is useful when you
+have a shell function with the same name as a builtin command.)
+.Bl -tag -width 5n
+.It Fl p
+search for command using a
+.Ev PATH
+that guarantees to find all the standard utilities.
+.It Fl V
+Do not execute the command but
+search for the command and print the resolution of the
+command search.
+This is the same as the type builtin.
+.It Fl v
+Do not execute the command but
+search for the command and print the absolute pathname
+of utilities, the name for builtins or the expansion of aliases.
+.El
+.It cd Ar -
+.It Xo cd Op Fl LP
+.Op Ar directory
+.Xc
+Switch to the specified directory (default
+.Ev HOME ) .
+If an entry for
+.Ev CDPATH
+appears in the environment of the
+.Ic cd
+command or the shell variable
+.Ev CDPATH
+is set and the directory name does not begin with a slash, then the
+directories listed in
+.Ev CDPATH
+will be searched for the specified directory.
+The format of
+.Ev CDPATH
+is the same as that of
+.Ev PATH .
+If a single dash is specified as the argument, it will be replaced by the
+value of
+.Ev OLDPWD .
+The
+.Ic cd
+command will print out the name of the
+directory that it actually switched to if this is different from the name
+that the user gave.
+These may be different either because the
+.Ev CDPATH
+mechanism was used or because the argument is a single dash.
+The
+.Fl P
+option causes the physical directory structure to be used, that is, all
+symbolic links are resolved to their respective values.  The
+.Fl L
+option turns off the effect of any preceding
+.Fl P
+options.
+.It Xo echo Op Fl n
+.Ar args... 
+.Xc
+Print the arguments on the standard output, separated by spaces.
+Unless the
+.Fl n
+option is present, a newline is output following the arguments.
+.Pp
+If any of the following sequences of characters is encountered during
+output, the sequence is not output.  Instead, the specified action is
+performed:
+.Bl -tag -width indent
+.It Li \eb
+A backspace character is output.
+.It Li \ec
+Subsequent output is suppressed.  This is normally used at the end of the
+last argument to suppress the trailing newline that
+.Ic echo
+would otherwise output.
+.It Li \ef
+Output a form feed.
+.It Li \en
+Output a newline character.
+.It Li \er
+Output a carriage return.
+.It Li \et
+Output a (horizontal) tab character.
+.It Li \ev
+Output a vertical tab.
+.It Li \e0 Ns Ar digits
+Output the character whose value is given by zero to three octal digits.
+If there are zero digits, a nul character is output.
+.It Li \e\e
+Output a backslash.
+.El
+.Pp
+All other backslash sequences elicit undefined behaviour.
+.It eval Ar string ...
+Concatenate all the arguments with spaces.
+Then re-parse and execute the command.
+.It exec Op Ar command arg ...
+Unless command is omitted, the shell process is replaced with the
+specified program (which must be a real program, not a shell builtin or
+function).
+Any redirections on the
+.Ic exec
+command are marked as permanent, so that they are not undone when the
+.Ic exec
+command finishes.
+.It exit Op Ar exitstatus
+Terminate the shell process.
+If
+.Ar exitstatus
+is given it is used as the exit status of the shell; otherwise the
+exit status of the preceding command is used.
+.It export Ar name ...
+.It export Fl p
+The specified names are exported so that they will appear in the
+environment of subsequent commands.
+The only way to un-export a variable is to unset it.
+The shell allows the value of a variable to be set at the
+same time it is exported by writing
+.Pp
+.Dl export name=value
+.Pp
+With no arguments the export command lists the names of all exported variables.
+With the
+.Fl p
+option specified the output will be formatted suitably for non-interactive use.
+.It Xo fc Op Fl e Ar editor
+.Op Ar first Op Ar last
+.Xc
+.It Xo fc Fl l
+.Op Fl nr
+.Op Ar first Op Ar last
+.Xc
+.It Xo fc Fl s Op Ar old=new
+.Op Ar first
+.Xc
+The
+.Ic fc
+builtin lists, or edits and re-executes, commands previously entered
+to an interactive shell.
+.Bl -tag -width 5n
+.It Fl e No editor
+Use the editor named by editor to edit the commands.
+The editor string is a command name, subject to search via the
+.Ev PATH
+variable.
+The value in the
+.Ev FCEDIT
+variable is used as a default when
+.Fl e
+is not specified.
+If
+.Ev FCEDIT
+is null or unset, the value of the
+.Ev EDITOR
+variable is used.
+If
+.Ev EDITOR
+is null or unset,
+.Xr ed 1
+is used as the editor.
+.It Fl l No (ell)
+List the commands rather than invoking an editor on them.
+The commands are written in the sequence indicated by
+the first and last operands, as affected by
+.Fl r ,
+with each command preceded by the command number.
+.It Fl n
+Suppress command numbers when listing with -l.
+.It Fl r
+Reverse the order of the commands listed (with
+.Fl l )
+or edited (with neither
+.Fl l
+nor
+.Fl s ) .
+.It Fl s
+Re-execute the command without invoking an editor.
+.It first
+.It last
+Select the commands to list or edit.
+The number of previous commands that
+can be accessed are determined by the value of the
+.Ev HISTSIZE
+variable.
+The value of first or last or both are one of the following:
+.Bl -tag -width 5n
+.It [+]number
+A positive number representing a command number; command numbers can be
+displayed with the
+.Fl l
+option.
+.It Fl number
+A negative decimal number representing the command that was executed
+number of commands previously.
+For example, \-1 is the immediately previous command.
+.El
+.It string
+A string indicating the most recently entered command that begins with
+that string.
+If the old=new operand is not also specified with
+.Fl s ,
+the string form of the first operand cannot contain an embedded equal sign.
+.El
+.Pp
+The following environment variables affect the execution of fc:
+.Bl -tag -width HISTSIZE
+.It Ev FCEDIT
+Name of the editor to use.
+.It Ev HISTSIZE
+The number of previous commands that are accessible.
+.El
+.It fg Op Ar job
+Move the specified job or the current job to the foreground.
+.It getopts Ar optstring var
+The
+.Tn POSIX
+.Ic getopts
+command, not to be confused with the
+.Em Bell Labs
+-derived
+.Xr getopt 1 .
+.Pp
+The first argument should be a series of letters, each of which may be
+optionally followed by a colon to indicate that the option requires an
+argument.
+The variable specified is set to the parsed option.
+.Pp
+The
+.Ic getopts
+command deprecates the older
+.Xr getopt 1
+utility due to its handling of arguments containing whitespace.
+.Pp
+The
+.Ic getopts
+builtin may be used to obtain options and their arguments
+from a list of parameters.
+When invoked,
+.Ic getopts
+places the value of the next option from the option string in the list in
+the shell variable specified by
+.Va var
+and its index in the shell variable
+.Ev OPTIND .
+When the shell is invoked,
+.Ev OPTIND
+is initialized to 1.
+For each option that requires an argument, the
+.Ic getopts
+builtin will place it in the shell variable
+.Ev OPTARG .
+If an option is not allowed for in the
+.Va optstring ,
+then
+.Ev OPTARG
+will be unset.
+.Pp
+.Va optstring
+is a string of recognized option letters (see
+.Xr getopt 3 ) .
+If a letter is followed by a colon, the option is expected to have an
+argument which may or may not be separated from it by white space.
+If an option character is not found where expected,
+.Ic getopts
+will set the variable
+.Va var
+to a
+.Dq \&? ;
+.Ic getopts
+will then unset
+.Ev OPTARG
+and write output to standard error.
+By specifying a colon as the first character of
+.Va optstring
+all errors will be ignored.
+.Pp
+After the last option
+.Ic getopts
+will return a non-zero value and set
+.Va var
+to
+.Dq \&? .
+.Pp
+The following code fragment shows how one might process the arguments
+for a command that can take the options
+.Op a
+and
+.Op b ,
+and the option
+.Op c ,
+which requires an argument.
+.Pp
+.Bd -literal -offset indent
+while getopts abc: f
+do
+	case $f in
+	a | b)	flag=$f;;
+	c)	carg=$OPTARG;;
+	\\?)	echo $USAGE; exit 1;;
+	esac
+done
+shift `expr $OPTIND - 1`
+.Ed
+.Pp
+This code will accept any of the following as equivalent:
+.Pp
+.Bd -literal -offset indent
+cmd \-acarg file file
+cmd \-a \-c arg file file
+cmd \-carg -a file file
+cmd \-a \-carg \-\- file file
+.Ed
+.It hash Fl rv Ar command ...
+The shell maintains a hash table which remembers the
+locations of commands.
+With no arguments whatsoever,
+the
+.Ic hash
+command prints out the contents of this table.
+Entries which have not been looked at since the last
+.Ic cd
+command are marked with an asterisk; it is possible for these entries
+to be invalid.
+.Pp
+With arguments, the
+.Ic hash
+command removes the specified commands from the hash table (unless
+they are functions) and then locates them.
+With the
+.Fl v
+option, hash prints the locations of the commands as it finds them.
+The
+.Fl r
+option causes the hash command to delete all the entries in the hash table
+except for functions.
+.It pwd Op Fl LP
+builtin command remembers what the current directory
+is rather than recomputing it each time.
+This makes it faster.
+However, if the current directory is renamed, the builtin version of
+.Ic pwd
+will continue to print the old name for the directory.
+The
+.Fl P
+option causes the physical value of the current working directory to be shown,
+that is, all symbolic links are resolved to their respective values.  The
+.Fl L
+option turns off the effect of any preceding
+.Fl P
+options.
+.It Xo read Op Fl p Ar prompt
+.Op Fl r
+.Ar variable
+.Op Ar ...
+.Xc
+The prompt is printed if the
+.Fl p
+option is specified and the standard input is a terminal.
+Then a line is read from the standard input.
+The trailing newline is deleted from the
+line and the line is split as described in the section on word splitting
+above, and the pieces are assigned to the variables in order.
+At least one variable must be specified.
+If there are more pieces than variables, the remaining pieces
+(along with the characters in
+.Ev IFS
+that separated them) are assigned to the last variable.
+If there are more variables than pieces,
+the remaining variables are assigned the null string.
+The
+.Ic read
+builtin will indicate success unless EOF is encountered on input, in
+which case failure is returned.
+.Pp
+By default, unless the
+.Fl r
+option is specified, the backslash
+.Dq \e
+acts as an escape character, causing the following character to be treated
+literally.
+If a backslash is followed by a newline, the backslash and the
+newline will be deleted.
+.It readonly Ar name ...
+.It readonly Fl p
+The specified names are marked as read only, so that they cannot be
+subsequently modified or unset.
+The shell allows the value of a variable
+to be set at the same time it is marked read only by writing
+.Pp
+.Dl readonly name=value
+.Pp
+With no arguments the readonly command lists the names of all read only
+variables.
+With the
+.Fl p
+option specified the output will be formatted suitably for non-interactive use.
+.Pp
+.It Xo printf Ar format
+.Op Ar arguments  ...
+.Xc
+.Ic printf
+formats and prints its arguments, after the first, under control
+of the
+.Ar format  .
+The
+.Ar format
+is a character string which contains three types of objects: plain characters,
+which are simply copied to standard output, character escape sequences which
+are converted and copied to the standard output, and format specifications,
+each of which causes printing of the next successive
+.Ar argument  .
+.Pp
+The
+.Ar arguments
+after the first are treated as strings if the corresponding format is
+either
+.Cm b ,
+.Cm c
+or
+.Cm s ;
+otherwise it is evaluated as a C constant, with the following extensions:
+.Pp
+.Bl -bullet -offset indent -compact
+.It
+A leading plus or minus sign is allowed.
+.It
+If the leading character is a single or double quote, the value is the
+.Tn ASCII
+code of the next character.
+.El
+.Pp
+The format string is reused as often as necessary to satisfy the
+.Ar arguments  .
+Any extra format specifications are evaluated with zero or the null
+string.
+.Pp
+Character escape sequences are in backslash notation as defined in
+.St -ansiC .
+The characters and their meanings are as follows:
+.Bl -tag -width Ds -offset indent
+.It Cm \ea
+Write a \*[Lt]bell\*[Gt] character.
+.It Cm \eb
+Write a \*[Lt]backspace\*[Gt] character.
+.It Cm \ef
+Write a \*[Lt]form-feed\*[Gt] character.
+.It Cm \en
+Write a \*[Lt]new-line\*[Gt] character.
+.It Cm \er
+Write a \*[Lt]carriage return\*[Gt] character.
+.It Cm \et
+Write a \*[Lt]tab\*[Gt] character.
+.It Cm \ev
+Write a \*[Lt]vertical tab\*[Gt] character.
+.It Cm \e\e
+Write a backslash character.
+.It Cm \e Ns Ar num
+Write an 8\-bit character whose
+.Tn ASCII
+value is the 1\-, 2\-, or 3\-digit
+octal number
+.Ar num .
+.El
+.Pp
+Each format specification is introduced by the percent character
+(``%'').
+The remainder of the format specification includes,
+in the following order:
+.Bl -tag -width Ds
+.It "Zero or more of the following flags:"
+.Bl -tag -width Ds
+.It Cm #
+A `#' character
+specifying that the value should be printed in an ``alternative form''.
+For
+.Cm b ,
+.Cm c ,
+.Cm d ,
+and
+.Cm s
+formats, this option has no effect.
+For the
+.Cm o
+format the precision of the number is increased to force the first
+character of the output string to a zero.
+For the
+.Cm x
+.Pq Cm X
+format, a non-zero result has the string
+.Li 0x
+.Pq Li 0X
+prepended to it.
+For
+.Cm e  ,
+.Cm E ,
+.Cm f  ,
+.Cm g ,
+and
+.Cm G
+formats, the result will always contain a decimal point, even if no
+digits follow the point (normally, a decimal point only appears in the
+results of those formats if a digit follows the decimal point).
+For
+.Cm g
+and
+.Cm G
+formats, trailing zeros are not removed from the result as they
+would otherwise be.
+.It Cm \&\-
+A minus sign `\-' which specifies
+.Em left adjustment
+of the output in the indicated field;
+.It Cm \&+
+A `+' character specifying that there should always be
+a sign placed before the number when using signed formats.
+.It Sq \&\ \&
+A space specifying that a blank should be left before a positive number
+for a signed format.
+A `+' overrides a space if both are used;
+.It Cm \&0
+A zero `0' character indicating that zero-padding should be used
+rather than blank-padding.
+A `\-' overrides a `0' if both are used;
+.El
+.It "Field Width:"
+An optional digit string specifying a
+.Em field width ;
+if the output string has fewer characters than the field width it will
+be blank-padded on the left (or right, if the left-adjustment indicator
+has been given) to make up the field width (note that a leading zero
+is a flag, but an embedded zero is part of a field width);
+.It Precision :
+An optional period,
+.Sq Cm \&.\& ,
+followed by an optional digit string giving a
+.Em precision
+which specifies the number of digits to appear after the decimal point,
+for
+.Cm e
+and
+.Cm f
+formats, or the maximum number of bytes to be printed
+from a string
+.Sm off
+.Pf ( Cm b
+.Sm on
+and
+.Cm s
+formats); if the digit string is missing, the precision is treated
+as zero;
+.It Format :
+A character which indicates the type of format to use (one of
+.Cm diouxXfwEgGbcs ) .
+.El
+.Pp
+A field width or precision may be
+.Sq Cm \&*
+instead of a digit string.
+In this case an
+.Ar argument
+supplies the field width or precision.
+.Pp
+The format characters and their meanings are:
+.Bl -tag -width Fl
+.It Cm diouXx
+The
+.Ar argument
+is printed as a signed decimal (d or i), unsigned octal, unsigned decimal,
+or unsigned hexadecimal (X or x), respectively.
+.It Cm f
+The
+.Ar argument
+is printed in the style
+.Sm off
+.Pf [\-]ddd Cm \&. No ddd
+.Sm on
+where the number of d's
+after the decimal point is equal to the precision specification for
+the argument.
+If the precision is missing, 6 digits are given; if the precision
+is explicitly 0, no digits and no decimal point are printed.
+.It Cm eE
+The
+.Ar argument
+is printed in the style
+.Sm off
+.Pf [\-]d Cm \&. No ddd Cm e No \*(Pmdd
+.Sm on
+where there
+is one digit before the decimal point and the number after is equal to
+the precision specification for the argument; when the precision is
+missing, 6 digits are produced.
+An upper-case E is used for an `E' format.
+.It Cm gG
+The
+.Ar argument
+is printed in style
+.Cm f
+or in style
+.Cm e
+.Pq Cm E
+whichever gives full precision in minimum space.
+.It Cm b
+Characters from the string
+.Ar argument
+are printed with backslash-escape sequences expanded.
+.br
+The following additional backslash-escape sequences are supported:
+.Bl -tag -width Ds
+.It Cm \ec
+Causes
+.Nm
+to ignore any remaining characters in the string operand containing it,
+any remaining string operands, and any additional characters in
+the format operand.
+.It Cm \e0 Ns Ar num
+Write an 8\-bit character whose
+.Tn ASCII
+value is the 1\-, 2\-, or 3\-digit
+octal number
+.Ar num .
+.El
+.It Cm c
+The first character of
+.Ar argument
+is printed.
+.It Cm s
+Characters from the string
+.Ar argument
+are printed until the end is reached or until the number of bytes
+indicated by the precision specification is reached; if the
+precision is omitted, all characters in the string are printed.
+.It Cm \&%
+Print a `%'; no argument is used.
+.El
+.Pp
+In no case does a non-existent or small field width cause truncation of
+a field; padding takes place only if the specified field width exceeds
+the actual width.
+.It Xo set
+.Oo {
+.Fl options | Cm +options | Cm -- }
+.Oc Ar arg ...
+.Xc
+The
+.Ic set
+command performs three different functions.
+.Pp
+With no arguments, it lists the values of all shell variables.
+.Pp
+If options are given, it sets the specified option
+flags, or clears them as described in the section called
+.Sx Argument List Processing .
+As a special case, if the option is -o or +o and no argument is
+supplied, the shell prints the settings of all its options.  If the
+option is -o, the settings are printed in a human-readable format; if
+the option is +o, the settings are printed in a format suitable for
+reinput to the shell to affect the same option settings.
+.Pp
+The third use of the set command is to set the values of the shell's
+positional parameters to the specified args.
+To change the positional
+parameters without changing any options, use
+.Dq --
+as the first argument to set.
+If no args are present, the set command
+will clear all the positional parameters (equivalent to executing
+.Dq shift $# . )
+.It shift Op Ar n
+Shift the positional parameters n times.
+A
+.Ic shift
+sets the value of
+.Va $1
+to the value of
+.Va $2 ,
+the value of
+.Va $2
+to the value of
+.Va $3 ,
+and so on, decreasing
+the value of
+.Va $#
+by one.
+If n is greater than the number of positional parameters,
+.Ic shift
+will issue an error message, and exit with return status 2.
+.It test Ar expression
+.It \&[ Ar expression Cm \&]
+The
+.Ic test
+utility evaluates the expression and, if it evaluates
+to true, returns a zero (true) exit status; otherwise
+it returns 1 (false).
+If there is no expression, test also
+returns 1 (false).
+.Pp
+All operators and flags are separate arguments to the
+.Ic test
+utility.
+.Pp
+The following primaries are used to construct expression:
+.Bl -tag -width Ar
+.It Fl b Ar file
+True if
+.Ar file
+exists and is a block special
+file.
+.It Fl c Ar file
+True if
+.Ar file
+exists and is a character
+special file.
+.It Fl d Ar file
+True if
+.Ar file
+exists and is a directory.
+.It Fl e Ar file
+True if
+.Ar file
+exists (regardless of type).
+.It Fl f Ar file
+True if
+.Ar file
+exists and is a regular file.
+.It Fl g Ar file
+True if
+.Ar file
+exists and its set group ID flag
+is set.
+.It Fl h Ar file
+True if
+.Ar file
+exists and is a symbolic link.
+.It Fl k Ar file
+True if
+.Ar file
+exists and its sticky bit is set.
+.It Fl n Ar string
+True if the length of
+.Ar string
+is nonzero.
+.It Fl p Ar file
+True if
+.Ar file
+is a named pipe
+.Po Tn FIFO Pc .
+.It Fl r Ar file
+True if
+.Ar file
+exists and is readable.
+.It Fl s Ar file
+True if
+.Ar file
+exists and has a size greater
+than zero.
+.It Fl t Ar file_descriptor
+True if the file whose file descriptor number
+is
+.Ar file_descriptor
+is open and is associated with a terminal.
+.It Fl u Ar file
+True if
+.Ar file
+exists and its set user ID flag
+is set.
+.It Fl w Ar file
+True if
+.Ar file
+exists and is writable.
+True
+indicates only that the write flag is on.
+The file is not writable on a read-only file
+system even if this test indicates true.
+.It Fl x Ar file
+True if
+.Ar file
+exists and is executable.
+True
+indicates only that the execute flag is on.
+If
+.Ar file
+is a directory, true indicates that
+.Ar file
+can be searched.
+.It Fl z Ar string
+True if the length of
+.Ar string
+is zero.
+.It Fl L Ar file
+True if
+.Ar file
+exists and is a symbolic link.
+This operator is retained for compatibility with previous versions of
+this program.
+Do not rely on its existence; use
+.Fl h
+instead.
+.It Fl O Ar file
+True if
+.Ar file
+exists and its owner matches the effective user id of this process.
+.It Fl G Ar file
+True if
+.Ar file
+exists and its group matches the effective group id of this process.
+.It Fl S Ar file
+True if
+.Ar file
+exists and is a socket.
+.It Ar file1 Fl nt Ar file2
+True if
+.Ar file1
+and
+.Ar file2
+exist and
+.Ar file1
+is newer than
+.Ar file2 .
+.It Ar file1 Fl ot Ar file2
+True if
+.Ar file1
+and
+.Ar file2
+exist and
+.Ar file1
+is older than
+.Ar file2 .
+.It Ar file1 Fl ef Ar file2
+True if
+.Ar file1
+and
+.Ar file2
+exist and refer to the same file.
+.It Ar string
+True if
+.Ar string
+is not the null
+string.
+.It Ar \&s\&1 Cm \&= Ar \&s\&2
+True if the strings
+.Ar \&s\&1
+and
+.Ar \&s\&2
+are identical.
+.It Ar \&s\&1 Cm \&!= Ar \&s\&2
+True if the strings
+.Ar \&s\&1
+and
+.Ar \&s\&2
+are not identical.
+.It Ar \&s\&1 Cm \&\*[Lt] Ar \&s\&2
+True if string
+.Ar \&s\&1
+comes before
+.Ar \&s\&2
+based on the ASCII value of their characters.
+.It Ar \&s\&1 Cm \&\*[Gt] Ar \&s\&2
+True if string
+.Ar \&s\&1
+comes after
+.Ar \&s\&2
+based on the ASCII value of their characters.
+.It Ar \&n\&1 Fl \&eq Ar \&n\&2
+True if the integers
+.Ar \&n\&1
+and
+.Ar \&n\&2
+are algebraically
+equal.
+.It Ar \&n\&1 Fl \&ne Ar \&n\&2
+True if the integers
+.Ar \&n\&1
+and
+.Ar \&n\&2
+are not
+algebraically equal.
+.It Ar \&n\&1 Fl \&gt Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically
+greater than the integer
+.Ar \&n\&2 .
+.It Ar \&n\&1 Fl \&ge Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically
+greater than or equal to the integer
+.Ar \&n\&2 .
+.It Ar \&n\&1 Fl \&lt Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically less
+than the integer
+.Ar \&n\&2 .
+.It Ar \&n\&1 Fl \&le Ar \&n\&2
+True if the integer
+.Ar \&n\&1
+is algebraically less
+than or equal to the integer
+.Ar \&n\&2 .
+.El
+.Pp
+These primaries can be combined with the following operators:
+.Bl -tag -width Ar
+.It Cm \&! Ar expression
+True if
+.Ar expression
+is false.
+.It Ar expression1 Fl a Ar expression2
+True if both
+.Ar expression1
+and
+.Ar expression2
+are true.
+.It Ar expression1 Fl o Ar expression2
+True if either
+.Ar expression1
+or
+.Ar expression2
+are true.
+.It Cm \&( Ns Ar expression Ns Cm \&)
+True if expression is true.
+.El
+.Pp
+The
+.Fl a
+operator has higher precedence than the
+.Fl o
+operator.
+.It times
+Print the accumulated user and system times for the shell and for processes
+run from the shell.  The return status is 0.
+.It Xo trap
+.Op Ar action Ar signal ...
+.Xc
+Cause the shell to parse and execute action when any of the specified
+signals are received.
+The signals are specified by signal number or as the name of the signal.
+If
+.Ar signal
+is
+.Li 0
+or
+.Li EXIT ,
+the action is executed when the shell exits.
+.Ar action
+may be empty
+.Li ( "''" ) ,
+which causes the specified signals to be ignored.
+With
+.Ar action
+omitted or set to `-' the specified signals are set to their default action.
+When the shell forks off a subshell, it resets trapped (but not ignored)
+signals to the default action.
+The
+.Ic trap
+command has no effect on signals that were
+ignored on entry to the shell.
+.Ic trap
+without any arguments cause it to write a list of signals and their
+associated action to the standard output in a format that is suitable
+as an input to the shell that achieves the same trapping results.
+.Pp
+Examples:
+.Pp
+.Dl trap
+.Pp
+List trapped signals and their corresponding action
+.Pp
+.Dl trap '' INT QUIT tstp 30
+.Pp
+Ignore signals INT QUIT TSTP USR1
+.Pp
+.Dl trap date INT
+.Pp
+Print date upon receiving signal INT
+.It type Op Ar name ...
+Interpret each name as a command and print the resolution of the command
+search.
+Possible resolutions are:
+shell keyword, alias, shell builtin,
+command, tracked alias and not found.
+For aliases the alias expansion is
+printed; for commands and tracked aliases the complete pathname of the
+command is printed.
+.It ulimit Xo
+.Op Fl H \*(Ba Fl S
+.Op Fl a \*(Ba Fl tfdscmlpnv Op Ar value
+.Xc
+Inquire about or set the hard or soft limits on processes or set new
+limits.
+The choice between hard limit (which no process is allowed to
+violate, and which may not be raised once it has been lowered) and soft
+limit (which causes processes to be signaled but not necessarily killed,
+and which may be raised) is made with these flags:
+.Bl -tag -width Fl
+.It Fl H
+set or inquire about hard limits
+.It Fl S
+set or inquire about soft limits.
+If neither
+.Fl H
+nor
+.Fl S
+is specified, the soft limit is displayed or both limits are set.
+If both are specified, the last one wins.
+.El
+.Pp
+The limit to be interrogated or set, then, is chosen by specifying
+any one of these flags:
+.Bl -tag -width Fl
+.It Fl a
+show all the current limits
+.It Fl t
+show or set the limit on CPU time (in seconds)
+.It Fl f
+show or set the limit on the largest file that can be created
+(in 512-byte blocks)
+.It Fl d
+show or set the limit on the data segment size of a process (in kilobytes)
+.It Fl s
+show or set the limit on the stack size of a process (in kilobytes)
+.It Fl c
+show or set the limit on the largest core dump size that can be produced
+(in 512-byte blocks)
+.It Fl m
+show or set the limit on the total physical memory that can be
+in use by a process (in kilobytes)
+.It Fl l
+show or set the limit on how much memory a process can lock with
+.Xr mlock 2
+(in kilobytes)
+.It Fl p
+show or set the limit on the number of processes this user can
+have at one time
+.It Fl n
+show or set the limit on the number files a process can have open at once
+.It Fl v
+show or set the limit on the total virtual memory that can be
+in use by a process (in kilobytes)
+.It Fl r
+show or set the limit on the real-time scheduling priority of a process
+.El
+.Pp
+If none of these is specified, it is the limit on file size that is shown
+or set.
+If value is specified, the limit is set to that number; otherwise
+the current limit is displayed.
+.Pp
+Limits of an arbitrary process can be displayed or set using the
+.Xr sysctl 8
+utility.
+.Pp
+.It umask Op Ar mask
+Set the value of umask (see
+.Xr umask 2 )
+to the specified octal value.
+If the argument is omitted, the umask value is printed.
+.It unalias Xo
+.Op Fl a
+.Op Ar name
+.Xc
+If
+.Ar name
+is specified, the shell removes that alias.
+If
+.Fl a
+is specified, all aliases are removed.
+.It unset Xo
+.Op Fl fv
+.Ar name ...
+.Xc
+The specified variables and functions are unset and unexported.
+If
+.Fl f
+or
+.Fl v
+is specified, the corresponding function or variable is unset, respectively.
+If a given name corresponds to both a variable and a function, and no
+options are given, only the variable is unset.
+.It wait Op Ar job
+Wait for the specified job to complete and return the exit status of the
+last process in the job.
+If the argument is omitted, wait for all jobs to
+complete and return an exit status of zero.
+.El
+.Ss Command Line Editing
+When
+.Nm
+is being used interactively from a terminal, the current command
+and the command history (see
+.Ic fc
+in
+.Sx Builtins )
+can be edited using vi-mode command-line editing.
+This mode uses commands, described below,
+similar to a subset of those described in the vi man page.
+The command
+.Ql set -o vi
+enables vi-mode editing and places sh into vi insert mode.
+With vi-mode
+enabled, sh can be switched between insert mode and command mode.
+It is similar to vi: typing
+.Aq ESC
+enters vi command mode.
+Hitting
+.Aq return
+while in command mode will pass the line to the shell.
+.Sh EXIT STATUS
+Errors that are detected by the shell, such as a syntax error, will cause the
+shell to exit with a non-zero exit status.
+If the shell is not an
+interactive shell, the execution of the shell file will be aborted.
+Otherwise
+the shell will return the exit status of the last command executed, or
+if the exit builtin is used with a numeric argument, it will return the
+argument.
+.Sh ENVIRONMENT
+.Bl -tag -width MAILCHECK
+.It Ev HOME
+Set automatically by
+.Xr login 1
+from the user's login directory in the password file
+.Pq Xr passwd 4 .
+This environment variable also functions as the default argument for the
+cd builtin.
+.It Ev PATH
+The default search path for executables.
+See the above section
+.Sx Path Search .
+.It Ev CDPATH
+The search path used with the cd builtin.
+.It Ev MAIL
+The name of a mail file, that will be checked for the arrival of new mail.
+Overridden by
+.Ev MAILPATH .
+.It Ev MAILCHECK
+The frequency in seconds that the shell checks for the arrival of mail
+in the files specified by the
+.Ev MAILPATH
+or the
+.Ev MAIL
+file.
+If set to 0, the check will occur at each prompt.
+.It Ev MAILPATH
+A colon
+.Dq \&:
+separated list of file names, for the shell to check for incoming mail.
+This environment setting overrides the
+.Ev MAIL
+setting.
+There is a maximum of 10 mailboxes that can be monitored at once.
+.It Ev PS1
+The primary prompt string, which defaults to
+.Dq $\  ,
+unless you are the superuser, in which case it defaults to
+.Dq #\  .
+.It Ev PS2
+The secondary prompt string, which defaults to
+.Dq \*[Gt]\  .
+.It Ev PS4
+Output before each line when execution trace (set -x) is enabled,
+defaults to
+.Dq +\  .
+.It Ev RPS1
+The primary right prompt string.
+.It Ev RPS2
+The secondary right prompt string.
+.It Ev IFS
+Input Field Separators.
+This is normally set to
+.Aq space ,
+.Aq tab ,
+and
+.Aq newline .
+See the
+.Sx White Space Splitting
+section for more details.
+.It Ev TERM
+The default terminal setting for the shell.
+This is inherited by
+children of the shell, and is used in the history editing modes.
+.It Ev HISTSIZE
+The number of lines in the history buffer for the shell.
+.It Ev PWD
+The logical value of the current working directory.  This is set by the
+.Ic cd
+command.
+.It Ev OLDPWD
+The previous logical value of the current working directory.  This is set by
+the
+.Ic cd
+command.
+.It Ev PPID
+The process ID of the parent process of the shell.
+.El
+.Sh FILES
+.Bl -item -width HOMEprofilexxxx
+.It
+.Pa $HOME/.profile
+.It
+.Pa /etc/profile
+.El
+.Sh SEE ALSO
+.Xr csh 1 ,
+.Xr echo 1 ,
+.Xr getopt 1 ,
+.Xr ksh 1 ,
+.Xr login 1 ,
+.Xr printf 1 ,
+.Xr test 1 ,
+.Xr getopt 3 ,
+.Xr passwd 5 ,
+.\" .Xr profile 4 ,
+.Xr environ 7 ,
+.Xr sysctl 8
+.Sh HISTORY
+.Nm
+is a POSIX-compliant implementation of /bin/sh that aims to be as small as
+possible.
+.Nm
+is a direct descendant of the NetBSD version of ash (the Almquist SHell),
+ported to Linux in early 1997.
+It was renamed to
+.Nm
+in 2002.
+.Sh BUGS
+Setuid shell scripts should be avoided at all costs, as they are a
+significant security risk.
+.Pp
+PS1, PS2, and PS4 should be subject to parameter expansion before
+being displayed.
diff --git a/bin/dash/src/error.c b/bin/dash/src/error.c
new file mode 100644
index 00000000..728ff885
--- /dev/null
+++ b/bin/dash/src/error.c
@@ -0,0 +1,238 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Errors and exceptions.
+ */
+
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "jobs.h"
+#include "shell.h"
+#include "main.h"
+#include "options.h"
+#include "output.h"
+#include "error.h"
+#include "show.h"
+#include "eval.h"
+#include "parser.h"
+#include "system.h"
+
+
+/*
+ * Code to handle exceptions in C.
+ */
+
+struct jmploc *handler;
+int exception;
+int suppressint;
+volatile sig_atomic_t intpending;
+int errlinno;
+
+
+static void exverror(int, const char *, va_list)
+    __attribute__((__noreturn__));
+
+/*
+ * Called to raise an exception.  Since C doesn't include exceptions, we
+ * just do a longjmp to the exception handler.  The type of exception is
+ * stored in the global variable "exception".
+ */
+
+void
+exraise(int e)
+{
+#ifdef DEBUG
+	if (handler == NULL)
+		abort();
+#endif
+
+	if (vforked)
+		_exit(exitstatus);
+
+	INTOFF;
+
+	exception = e;
+	longjmp(handler->loc, 1);
+}
+
+
+/*
+ * Called from trap.c when a SIGINT is received.  (If the user specifies
+ * that SIGINT is to be trapped or ignored using the trap builtin, then
+ * this routine is not called.)  Suppressint is nonzero when interrupts
+ * are held using the INTOFF macro.  (The test for iflag is just
+ * defensive programming.)
+ */
+
+void
+onint(void) {
+
+	intpending = 0;
+	sigclearmask();
+	if (!(rootshell && iflag)) {
+		signal(SIGINT, SIG_DFL);
+		raise(SIGINT);
+	}
+	exitstatus = SIGINT + 128;
+	exraise(EXINT);
+	/* NOTREACHED */
+}
+
+static void
+exvwarning2(const char *msg, va_list ap)
+{
+	struct output *errs;
+	const char *name;
+	const char *fmt;
+
+	errs = out2;
+	name = arg0 ? arg0 : "sh";
+	if (!commandname)
+		fmt = "%s: %d: ";
+	else
+		fmt = "%s: %d: %s: ";
+	outfmt(errs, fmt, name, errlinno, commandname);
+	doformat(errs, msg, ap);
+#if FLUSHERR
+	outc('\n', errs);
+#else
+	outcslow('\n', errs);
+#endif
+}
+
+#define exvwarning(a, b, c) exvwarning2(b, c)
+
+/*
+ * Exverror is called to raise the error exception.  If the second argument
+ * is not NULL then error prints an error message using printf style
+ * formatting.  It then raises the error exception.
+ */
+static void
+exverror(int cond, const char *msg, va_list ap)
+{
+#ifdef DEBUG
+	if (msg) {
+		va_list aq;
+		TRACE(("exverror(%d, \"", cond));
+		va_copy(aq, ap);
+		TRACEV((msg, aq));
+		va_end(aq);
+		TRACE(("\") pid=%d\n", getpid()));
+	} else
+		TRACE(("exverror(%d, NULL) pid=%d\n", cond, getpid()));
+	if (msg)
+#endif
+		exvwarning(-1, msg, ap);
+
+	flushall();
+	exraise(cond);
+	/* NOTREACHED */
+}
+
+
+void
+sh_error(const char *msg, ...)
+{
+	va_list ap;
+
+	exitstatus = 2;
+
+	va_start(ap, msg);
+	exverror(EXERROR, msg, ap);
+	/* NOTREACHED */
+	va_end(ap);
+}
+
+
+void
+exerror(int cond, const char *msg, ...)
+{
+	va_list ap;
+
+	va_start(ap, msg);
+	exverror(cond, msg, ap);
+	/* NOTREACHED */
+	va_end(ap);
+}
+
+/*
+ * error/warning routines for external builtins
+ */
+
+void
+sh_warnx(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	exvwarning(-1, fmt, ap);
+	va_end(ap);
+}
+
+
+/*
+ * Return a string describing an error.  The returned string may be a
+ * pointer to a static buffer that will be overwritten on the next call.
+ * Action describes the operation that got the error.
+ */
+
+const char *
+errmsg(int e, int action)
+{
+	if (e != ENOENT && e != ENOTDIR)
+		return strerror(e);
+
+	if (action & E_OPEN)
+		return "No such file";
+	else if (action & E_CREAT)
+		return "Directory nonexistent";
+	else
+		return "not found";
+}
+
+
+#ifdef REALLY_SMALL
+void
+__inton() {
+	if (--suppressint == 0 && intpending) {
+		onint();
+	}
+}
+#endif
diff --git a/bin/dash/src/error.h b/bin/dash/src/error.h
new file mode 100644
index 00000000..94e30a27
--- /dev/null
+++ b/bin/dash/src/error.h
@@ -0,0 +1,129 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)error.h	8.2 (Berkeley) 5/4/95
+ */
+
+#include <setjmp.h>
+#include <signal.h>
+
+/*
+ * Types of operations (passed to the errmsg routine).
+ */
+
+#define E_OPEN 01	/* opening a file */
+#define E_CREAT 02	/* creating a file */
+#define E_EXEC 04	/* executing a program */
+
+
+/*
+ * We enclose jmp_buf in a structure so that we can declare pointers to
+ * jump locations.  The global variable handler contains the location to
+ * jump to when an exception occurs, and the global variable exception
+ * contains a code identifying the exeception.  To implement nested
+ * exception handlers, the user should save the value of handler on entry
+ * to an inner scope, set handler to point to a jmploc structure for the
+ * inner scope, and restore handler on exit from the scope.
+ */
+
+struct jmploc {
+	jmp_buf loc;
+};
+
+extern struct jmploc *handler;
+extern int exception;
+
+/* exceptions */
+#define EXINT 0		/* SIGINT received */
+#define EXERROR 1	/* a generic error */
+#define EXEND 3		/* exit the shell */
+#define EXEXIT 4	/* exit the shell via exitcmd */
+
+
+/*
+ * These macros allow the user to suspend the handling of interrupt signals
+ * over a period of time.  This is similar to SIGHOLD to or sigblock, but
+ * much more efficient and portable.  (But hacking the kernel is so much
+ * more fun than worrying about efficiency and portability. :-))
+ */
+
+extern int suppressint;
+extern volatile sig_atomic_t intpending;
+
+#define barrier() ({ __asm__ __volatile__ ("": : :"memory"); })
+#define INTOFF \
+	({ \
+		suppressint++; \
+		barrier(); \
+		0; \
+	})
+#ifdef REALLY_SMALL
+void __inton(void);
+#define INTON __inton()
+#else
+#define INTON \
+	({ \
+		barrier(); \
+		if (--suppressint == 0 && intpending) onint(); \
+		0; \
+	})
+#endif
+#define FORCEINTON \
+	({ \
+		barrier(); \
+		suppressint = 0; \
+		if (intpending) onint(); \
+		0; \
+	})
+#define SAVEINT(v) ((v) = suppressint)
+#define RESTOREINT(v) \
+	({ \
+		barrier(); \
+		if ((suppressint = (v)) == 0 && intpending) onint(); \
+		0; \
+	})
+#define CLEAR_PENDING_INT intpending = 0
+#define int_pending() intpending
+
+void exraise(int) __attribute__((__noreturn__));
+#ifdef USE_NORETURN
+void onint(void) __attribute__((__noreturn__));
+#else
+void onint(void);
+#endif
+extern int errlinno;
+void sh_error(const char *, ...) __attribute__((__noreturn__));
+void exerror(int, const char *, ...) __attribute__((__noreturn__));
+const char *errmsg(int, int);
+
+void sh_warnx(const char *, ...);
diff --git a/bin/dash/src/eval.c b/bin/dash/src/eval.c
new file mode 100644
index 00000000..1b5d61dc
--- /dev/null
+++ b/bin/dash/src/eval.c
@@ -0,0 +1,1147 @@
+/*-
+ * Copyright (c) 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+/*
+ * Evaluate a command.
+ */
+
+#include "init.h"
+#include "main.h"
+#include "shell.h"
+#include "nodes.h"
+#include "syntax.h"
+#include "expand.h"
+#include "parser.h"
+#include "jobs.h"
+#include "eval.h"
+#include "builtins.h"
+#include "options.h"
+#include "exec.h"
+#include "redir.h"
+#include "input.h"
+#include "output.h"
+#include "trap.h"
+#include "var.h"
+#include "memalloc.h"
+#include "error.h"
+#include "show.h"
+#include "mystring.h"
+#ifndef SMALL
+#include "myhistedit.h"
+#endif
+
+
+int evalskip;			/* set if we are skipping commands */
+STATIC int skipcount;		/* number of levels to skip */
+MKINIT int loopnest;		/* current loop nesting level */
+static int funcline;		/* starting line number of current function, or 0 if not in a function */
+
+
+char *commandname;
+int exitstatus;			/* exit status of last command */
+int back_exitstatus;		/* exit status of backquoted command */
+int savestatus = -1;		/* exit status of last command outside traps */
+
+
+#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3)
+STATIC
+#endif
+void evaltreenr(union node *, int) __attribute__ ((__noreturn__));
+STATIC int evalloop(union node *, int);
+STATIC int evalfor(union node *, int);
+STATIC int evalcase(union node *, int);
+STATIC int evalsubshell(union node *, int);
+STATIC void expredir(union node *);
+STATIC int evalpipe(union node *, int);
+#ifdef notyet
+STATIC int evalcommand(union node *, int, struct backcmd *);
+#else
+STATIC int evalcommand(union node *, int);
+#endif
+STATIC int evalbltin(const struct builtincmd *, int, char **, int);
+STATIC int evalfun(struct funcnode *, int, char **, int);
+STATIC void prehash(union node *);
+STATIC int eprintlist(struct output *, struct strlist *, int);
+STATIC int bltincmd(int, char **);
+
+
+STATIC const struct builtincmd bltin = {
+	.name = nullstr,
+	.builtin = bltincmd,
+	.flags = BUILTIN_REGULAR,
+};
+
+
+/*
+ * Called to reset things after an exception.
+ */
+
+#ifdef mkinit
+INCLUDE "eval.h"
+
+EXITRESET {
+	if (savestatus >= 0) {
+		if (exception == EXEXIT || evalskip == SKIPFUNCDEF)
+			exitstatus = savestatus;
+		savestatus = -1;
+	}
+	evalskip = 0;
+	loopnest = 0;
+}
+#endif
+
+
+
+/*
+ * The eval commmand.
+ */
+
+static int evalcmd(int argc, char **argv, int flags)
+{
+        char *p;
+        char *concat;
+        char **ap;
+
+        if (argc > 1) {
+                p = argv[1];
+                if (argc > 2) {
+                        STARTSTACKSTR(concat);
+                        ap = argv + 2;
+                        for (;;) {
+                        	concat = stputs(p, concat);
+                                if ((p = *ap++) == NULL)
+                                        break;
+                                STPUTC(' ', concat);
+                        }
+                        STPUTC('\0', concat);
+                        p = grabstackstr(concat);
+                }
+                return evalstring(p, flags & EV_TESTED);
+        }
+        return 0;
+}
+
+
+/*
+ * Execute a command or commands contained in a string.
+ */
+
+int
+evalstring(char *s, int flags)
+{
+	union node *n;
+	struct stackmark smark;
+	int status;
+
+	s = sstrdup(s);
+	setinputstring(s);
+	setstackmark(&smark);
+
+	status = 0;
+	for (; (n = parsecmd(0)) != NEOF; popstackmark(&smark)) {
+		int i;
+
+		i = evaltree(n, flags & ~(parser_eof() ? 0 : EV_EXIT));
+		if (n)
+			status = i;
+
+		if (evalskip)
+			break;
+	}
+	popstackmark(&smark);
+	popfile();
+	stunalloc(s);
+
+	return status;
+}
+
+
+
+/*
+ * Evaluate a parse tree.  The value is left in the global variable
+ * exitstatus.
+ */
+
+int
+evaltree(union node *n, int flags)
+{
+	int checkexit = 0;
+	int (*evalfn)(union node *, int);
+	struct stackmark smark;
+	unsigned isor;
+	int status = 0;
+
+	setstackmark(&smark);
+
+	if (n == NULL) {
+		TRACE(("evaltree(NULL) called\n"));
+		goto out;
+	}
+
+	dotrap();
+
+#ifndef SMALL
+	displayhist = 1;	/* show history substitutions done with fc */
+#endif
+	TRACE(("pid %d, evaltree(%p: %d, %d) called\n",
+	    getpid(), n, n->type, flags));
+	switch (n->type) {
+	default:
+#ifdef DEBUG
+		out1fmt("Node type = %d\n", n->type);
+#ifndef USE_GLIBC_STDIO
+		flushout(out1);
+#endif
+		break;
+#endif
+	case NNOT:
+		status = !evaltree(n->nnot.com, EV_TESTED);
+		goto setstatus;
+	case NREDIR:
+		errlinno = lineno = n->nredir.linno;
+		if (funcline)
+			lineno -= funcline - 1;
+		expredir(n->nredir.redirect);
+		pushredir(n->nredir.redirect);
+		status = redirectsafe(n->nredir.redirect, REDIR_PUSH) ?:
+			 evaltree(n->nredir.n, flags & EV_TESTED);
+		if (n->nredir.redirect)
+			popredir(0);
+		goto setstatus;
+	case NCMD:
+#ifdef notyet
+		if (eflag && !(flags & EV_TESTED))
+			checkexit = ~0;
+		status = evalcommand(n, flags, (struct backcmd *)NULL);
+		goto setstatus;
+#else
+		evalfn = evalcommand;
+checkexit:
+		if (eflag && !(flags & EV_TESTED))
+			checkexit = ~0;
+		goto calleval;
+#endif
+	case NFOR:
+		evalfn = evalfor;
+		goto calleval;
+	case NWHILE:
+	case NUNTIL:
+		evalfn = evalloop;
+		goto calleval;
+	case NSUBSHELL:
+	case NBACKGND:
+		evalfn = evalsubshell;
+		goto checkexit;
+	case NPIPE:
+		evalfn = evalpipe;
+		goto checkexit;
+	case NCASE:
+		evalfn = evalcase;
+		goto calleval;
+	case NAND:
+	case NOR:
+	case NSEMI:
+#if NAND + 1 != NOR
+#error NAND + 1 != NOR
+#endif
+#if NOR + 1 != NSEMI
+#error NOR + 1 != NSEMI
+#endif
+		isor = n->type - NAND;
+		status = evaltree(n->nbinary.ch1,
+				  (flags | ((isor >> 1) - 1)) & EV_TESTED);
+		if ((!status) == isor || evalskip)
+			break;
+		n = n->nbinary.ch2;
+evaln:
+		evalfn = evaltree;
+calleval:
+		status = evalfn(n, flags);
+		goto setstatus;
+	case NIF:
+		status = evaltree(n->nif.test, EV_TESTED);
+		if (evalskip)
+			break;
+		if (!status) {
+			n = n->nif.ifpart;
+			goto evaln;
+		} else if (n->nif.elsepart) {
+			n = n->nif.elsepart;
+			goto evaln;
+		}
+		status = 0;
+		goto setstatus;
+	case NDEFUN:
+		defun(n);
+setstatus:
+		exitstatus = status;
+		break;
+	}
+out:
+	dotrap();
+
+	if (checkexit & status)
+		goto exexit;
+
+	if (flags & EV_EXIT) {
+exexit:
+		exraise(EXEND);
+	}
+
+	popstackmark(&smark);
+
+	return exitstatus;
+}
+
+
+#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3)
+STATIC
+#endif
+void evaltreenr(union node *n, int flags)
+#ifdef HAVE_ATTRIBUTE_ALIAS
+	__attribute__ ((alias("evaltree")));
+#else
+{
+	evaltree(n, flags);
+	abort();
+}
+#endif
+
+
+static int skiploop(void)
+{
+	int skip = evalskip;
+
+	switch (skip) {
+	case 0:
+		break;
+
+	case SKIPBREAK:
+	case SKIPCONT:
+		if (likely(--skipcount <= 0)) {
+			evalskip = 0;
+			break;
+		}
+
+		skip = SKIPBREAK;
+		break;
+	}
+
+	return skip;
+}
+
+
+STATIC int
+evalloop(union node *n, int flags)
+{
+	int skip;
+	int status;
+
+	loopnest++;
+	status = 0;
+	flags &= EV_TESTED;
+	do {
+		int i;
+
+		i = evaltree(n->nbinary.ch1, EV_TESTED);
+		skip = skiploop();
+		if (skip == SKIPFUNC)
+			status = i;
+		if (skip)
+			continue;
+		if (n->type != NWHILE)
+			i = !i;
+		if (i != 0)
+			break;
+		status = evaltree(n->nbinary.ch2, flags);
+		skip = skiploop();
+	} while (!(skip & ~SKIPCONT));
+	loopnest--;
+
+	return status;
+}
+
+
+
+STATIC int
+evalfor(union node *n, int flags)
+{
+	struct arglist arglist;
+	union node *argp;
+	struct strlist *sp;
+	int status;
+
+	errlinno = lineno = n->nfor.linno;
+	if (funcline)
+		lineno -= funcline - 1;
+
+	arglist.lastp = &arglist.list;
+	for (argp = n->nfor.args ; argp ; argp = argp->narg.next) {
+		expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+	}
+	*arglist.lastp = NULL;
+
+	status = 0;
+	loopnest++;
+	flags &= EV_TESTED;
+	for (sp = arglist.list ; sp ; sp = sp->next) {
+		setvar(n->nfor.var, sp->text, 0);
+		status = evaltree(n->nfor.body, flags);
+		if (skiploop() & ~SKIPCONT)
+			break;
+	}
+	loopnest--;
+
+	return status;
+}
+
+
+
+STATIC int
+evalcase(union node *n, int flags)
+{
+	union node *cp;
+	union node *patp;
+	struct arglist arglist;
+	int status = 0;
+
+	errlinno = lineno = n->ncase.linno;
+	if (funcline)
+		lineno -= funcline - 1;
+
+	arglist.lastp = &arglist.list;
+	expandarg(n->ncase.expr, &arglist, EXP_TILDE);
+	for (cp = n->ncase.cases ; cp && evalskip == 0 ; cp = cp->nclist.next) {
+		for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) {
+			if (casematch(patp, arglist.list->text)) {
+				/* Ensure body is non-empty as otherwise
+				 * EV_EXIT may prevent us from setting the
+				 * exit status.
+				 */
+				if (evalskip == 0 && cp->nclist.body) {
+					status = evaltree(cp->nclist.body,
+							  flags);
+				}
+				goto out;
+			}
+		}
+	}
+out:
+	return status;
+}
+
+
+
+/*
+ * Kick off a subshell to evaluate a tree.
+ */
+
+STATIC int
+evalsubshell(union node *n, int flags)
+{
+	struct job *jp;
+	int backgnd = (n->type == NBACKGND);
+	int status;
+
+	errlinno = lineno = n->nredir.linno;
+	if (funcline)
+		lineno -= funcline - 1;
+
+	expredir(n->nredir.redirect);
+	INTOFF;
+	if (!backgnd && flags & EV_EXIT && !have_traps()) {
+		forkreset();
+		goto nofork;
+	}
+	jp = makejob(n, 1);
+	if (forkshell(jp, n, backgnd) == 0) {
+		flags |= EV_EXIT;
+		if (backgnd)
+			flags &=~ EV_TESTED;
+nofork:
+		INTON;
+		redirect(n->nredir.redirect, 0);
+		evaltreenr(n->nredir.n, flags);
+		/* never returns */
+	}
+	status = 0;
+	if (! backgnd)
+		status = waitforjob(jp);
+	INTON;
+	return status;
+}
+
+
+
+/*
+ * Compute the names of the files in a redirection list.
+ */
+
+STATIC void
+expredir(union node *n)
+{
+	union node *redir;
+
+	for (redir = n ; redir ; redir = redir->nfile.next) {
+		struct arglist fn;
+		fn.lastp = &fn.list;
+		switch (redir->type) {
+		case NFROMTO:
+		case NFROM:
+		case NTO:
+		case NCLOBBER:
+		case NAPPEND:
+			expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
+			redir->nfile.expfname = fn.list->text;
+			break;
+		case NFROMFD:
+		case NTOFD:
+			if (redir->ndup.vname) {
+				expandarg(redir->ndup.vname, &fn, EXP_TILDE | EXP_REDIR);
+				fixredir(redir, fn.list->text, 1);
+			}
+			break;
+		}
+	}
+}
+
+
+
+/*
+ * Evaluate a pipeline.  All the processes in the pipeline are children
+ * of the process creating the pipeline.  (This differs from some versions
+ * of the shell, which make the last process in a pipeline the parent
+ * of all the rest.)
+ */
+
+STATIC int
+evalpipe(union node *n, int flags)
+{
+	struct job *jp;
+	struct nodelist *lp;
+	int pipelen;
+	int prevfd;
+	int pip[2];
+	int status = 0;
+
+	TRACE(("evalpipe(0x%lx) called\n", (long)n));
+	pipelen = 0;
+	for (lp = n->npipe.cmdlist ; lp ; lp = lp->next)
+		pipelen++;
+	flags |= EV_EXIT;
+	INTOFF;
+	jp = makejob(n, pipelen);
+	prevfd = -1;
+	for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+		prehash(lp->n);
+		pip[1] = -1;
+		if (lp->next) {
+			if (pipe(pip) < 0) {
+				close(prevfd);
+				sh_error("Pipe call failed");
+			}
+		}
+		if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) {
+			INTON;
+			if (pip[1] >= 0) {
+				close(pip[0]);
+			}
+			if (prevfd > 0) {
+				dup2(prevfd, 0);
+				close(prevfd);
+			}
+			if (pip[1] > 1) {
+				dup2(pip[1], 1);
+				close(pip[1]);
+			}
+			evaltreenr(lp->n, flags);
+			/* never returns */
+		}
+		if (prevfd >= 0)
+			close(prevfd);
+		prevfd = pip[0];
+		close(pip[1]);
+	}
+	if (n->npipe.backgnd == 0) {
+		status = waitforjob(jp);
+		TRACE(("evalpipe:  job done exit status %d\n", status));
+	}
+	INTON;
+
+	return status;
+}
+
+
+
+/*
+ * Execute a command inside back quotes.  If it's a builtin command, we
+ * want to save its output in a block obtained from malloc.  Otherwise
+ * we fork off a subprocess and get the output of the command via a pipe.
+ * Should be called with interrupts off.
+ */
+
+void
+evalbackcmd(union node *n, struct backcmd *result)
+{
+	int pip[2];
+	struct job *jp;
+
+	result->fd = -1;
+	result->buf = NULL;
+	result->nleft = 0;
+	result->jp = NULL;
+	if (n == NULL) {
+		goto out;
+	}
+
+	if (pipe(pip) < 0)
+		sh_error("Pipe call failed");
+	jp = makejob(n, 1);
+	if (forkshell(jp, n, FORK_NOJOB) == 0) {
+		FORCEINTON;
+		close(pip[0]);
+		if (pip[1] != 1) {
+			dup2(pip[1], 1);
+			close(pip[1]);
+		}
+		ifsfree();
+		evaltreenr(n, EV_EXIT);
+		/* NOTREACHED */
+	}
+	close(pip[1]);
+	result->fd = pip[0];
+	result->jp = jp;
+
+out:
+	TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
+		result->fd, result->buf, result->nleft, result->jp));
+}
+
+static struct strlist *fill_arglist(struct arglist *arglist,
+				    union node **argpp)
+{
+	struct strlist **lastp = arglist->lastp;
+	union node *argp;
+
+	while ((argp = *argpp)) {
+		expandarg(argp, arglist, EXP_FULL | EXP_TILDE);
+		*argpp = argp->narg.next;
+		if (*lastp)
+			break;
+	}
+
+	return *lastp;
+}
+
+static int parse_command_args(struct arglist *arglist, union node **argpp,
+			      const char **path)
+{
+	struct strlist *sp = arglist->list;
+	char *cp, c;
+
+	for (;;) {
+		sp = unlikely(sp->next) ? sp->next :
+					  fill_arglist(arglist, argpp);
+		if (!sp)
+			return 0;
+		cp = sp->text;
+		if (*cp++ != '-')
+			break;
+		if (!(c = *cp++))
+			break;
+		if (c == '-' && !*cp) {
+			if (likely(!sp->next) && !fill_arglist(arglist, argpp))
+				return 0;
+			sp = sp->next;
+			break;
+		}
+		do {
+			switch (c) {
+			case 'p':
+				*path = defpath;
+				break;
+			default:
+				/* run 'typecmd' for other options */
+				return 0;
+			}
+		} while ((c = *cp++));
+	}
+
+	arglist->list = sp;
+	return DO_NOFUNC;
+}
+
+/*
+ * Execute a simple command.
+ */
+
+STATIC int
+#ifdef notyet
+evalcommand(union node *cmd, int flags, struct backcmd *backcmd)
+#else
+evalcommand(union node *cmd, int flags)
+#endif
+{
+	struct localvar_list *localvar_stop;
+	struct parsefile *file_stop;
+	struct redirtab *redir_stop;
+	union node *argp;
+	struct arglist arglist;
+	struct arglist varlist;
+	char **argv;
+	int argc;
+	struct strlist *osp;
+	struct strlist *sp;
+#ifdef notyet
+	int pip[2];
+#endif
+	struct cmdentry cmdentry;
+	struct job *jp;
+	char *lastarg;
+	const char *path;
+	int spclbltin;
+	int cmd_flag;
+	int execcmd;
+	int status;
+	char **nargv;
+	int vflags;
+	int vlocal;
+
+	errlinno = lineno = cmd->ncmd.linno;
+	if (funcline)
+		lineno -= funcline - 1;
+
+	/* First expand the arguments. */
+	TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
+	file_stop = parsefile;
+	back_exitstatus = 0;
+
+	cmdentry.cmdtype = CMDBUILTIN;
+	cmdentry.u.cmd = &bltin;
+	varlist.lastp = &varlist.list;
+	*varlist.lastp = NULL;
+	arglist.lastp = &arglist.list;
+	*arglist.lastp = NULL;
+
+	cmd_flag = 0;
+	execcmd = 0;
+	spclbltin = -1;
+	vflags = 0;
+	vlocal = 0;
+	path = NULL;
+
+	argc = 0;
+	argp = cmd->ncmd.args;
+	if ((osp = fill_arglist(&arglist, &argp))) {
+		int pseudovarflag = 0;
+
+		for (;;) {
+			find_command(arglist.list->text, &cmdentry,
+				     cmd_flag | DO_REGBLTIN, pathval());
+
+			vlocal++;
+
+			/* implement bltin and command here */
+			if (cmdentry.cmdtype != CMDBUILTIN)
+				break;
+
+			pseudovarflag = cmdentry.u.cmd->flags & BUILTIN_ASSIGN;
+			if (likely(spclbltin < 0)) {
+				spclbltin =
+					cmdentry.u.cmd->flags &
+					BUILTIN_SPECIAL
+				;
+				vlocal = spclbltin ^ BUILTIN_SPECIAL;
+			}
+			execcmd = cmdentry.u.cmd == EXECCMD;
+			if (likely(cmdentry.u.cmd != COMMANDCMD))
+				break;
+
+			cmd_flag = parse_command_args(&arglist, &argp, &path);
+			if (!cmd_flag)
+				break;
+		}
+
+		for (; argp; argp = argp->narg.next)
+			expandarg(argp, &arglist,
+				  pseudovarflag &&
+				  isassignment(argp->narg.text) ?
+				  EXP_VARTILDE : EXP_FULL | EXP_TILDE);
+
+		for (sp = arglist.list; sp; sp = sp->next)
+			argc++;
+
+		if (execcmd && argc > 1)
+			vflags = VEXPORT;
+	}
+
+	localvar_stop = pushlocalvars(vlocal);
+
+	/* Reserve one extra spot at the front for shellexec. */
+	nargv = stalloc(sizeof (char *) * (argc + 2));
+	argv = ++nargv;
+	for (sp = arglist.list ; sp ; sp = sp->next) {
+		TRACE(("evalcommand arg: %s\n", sp->text));
+		*nargv++ = sp->text;
+	}
+	*nargv = NULL;
+
+	lastarg = NULL;
+	if (iflag && funcline == 0 && argc > 0)
+		lastarg = nargv[-1];
+
+	preverrout.fd = 2;
+	expredir(cmd->ncmd.redirect);
+	redir_stop = pushredir(cmd->ncmd.redirect);
+	status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH|REDIR_SAVEFD2);
+
+	if (unlikely(status)) {
+bail:
+		exitstatus = status;
+
+		/* We have a redirection error. */
+		if (spclbltin > 0)
+			exraise(EXERROR);
+
+		goto out;
+	}
+
+	for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) {
+		struct strlist **spp;
+
+		spp = varlist.lastp;
+		expandarg(argp, &varlist, EXP_VARTILDE);
+
+		if (vlocal)
+			mklocal((*spp)->text, VEXPORT);
+		else
+			setvareq((*spp)->text, vflags);
+	}
+
+	/* Print the command if xflag is set. */
+	if (xflag) {
+		struct output *out;
+		int sep;
+
+		out = &preverrout;
+		outstr(expandstr(ps4val()), out);
+		sep = 0;
+		sep = eprintlist(out, varlist.list, sep);
+		eprintlist(out, osp, sep);
+		outcslow('\n', out);
+#ifdef FLUSHERR
+		flushout(out);
+#endif
+	}
+
+	/* Now locate the command. */
+	if (cmdentry.cmdtype != CMDBUILTIN ||
+	    !(cmdentry.u.cmd->flags & BUILTIN_REGULAR)) {
+		path = unlikely(path) ? path : pathval();
+		find_command(argv[0], &cmdentry, cmd_flag | DO_ERR, path);
+	}
+
+	jp = NULL;
+
+	/* Execute the command. */
+	switch (cmdentry.cmdtype) {
+	case CMDUNKNOWN:
+		status = 127;
+#ifdef FLUSHERR
+		flushout(&errout);
+#endif
+		goto bail;
+
+	default:
+		/* Fork off a child process if necessary. */
+		if (!(flags & EV_EXIT) || have_traps()) {
+			INTOFF;
+			jp = vforkexec(cmd, argv, path, cmdentry.u.index);
+			break;
+		}
+		shellexec(argv, path, cmdentry.u.index);
+		/* NOTREACHED */
+
+	case CMDBUILTIN:
+		if (evalbltin(cmdentry.u.cmd, argc, argv, flags) &&
+		    !(exception == EXERROR && spclbltin <= 0)) {
+raise:
+			longjmp(handler->loc, 1);
+		}
+		break;
+
+	case CMDFUNCTION:
+		if (evalfun(cmdentry.u.func, argc, argv, flags))
+			goto raise;
+		break;
+	}
+
+	status = waitforjob(jp);
+	FORCEINTON;
+
+out:
+	if (cmd->ncmd.redirect)
+		popredir(execcmd);
+	unwindredir(redir_stop);
+	unwindfiles(file_stop);
+	unwindlocalvars(localvar_stop);
+	if (lastarg)
+		/* dsl: I think this is intended to be used to support
+		 * '_' in 'vi' command mode during line editing...
+		 * However I implemented that within libedit itself.
+		 */
+		setvar("_", lastarg, 0);
+
+	return status;
+}
+
+STATIC int
+evalbltin(const struct builtincmd *cmd, int argc, char **argv, int flags)
+{
+	char *volatile savecmdname;
+	struct jmploc *volatile savehandler;
+	struct jmploc jmploc;
+	int status;
+	int i;
+
+	savecmdname = commandname;
+	savehandler = handler;
+	if ((i = setjmp(jmploc.loc)))
+		goto cmddone;
+	handler = &jmploc;
+	commandname = argv[0];
+	argptr = argv + 1;
+	optptr = NULL;			/* initialize nextopt */
+	if (cmd == EVALCMD)
+		status = evalcmd(argc, argv, flags);
+	else
+		status = (*cmd->builtin)(argc, argv);
+	flushall();
+	if (outerr(out1))
+		sh_warnx("%s: I/O error", commandname);
+	status |= outerr(out1);
+	exitstatus = status;
+cmddone:
+	freestdout();
+	commandname = savecmdname;
+	handler = savehandler;
+
+	return i;
+}
+
+STATIC int
+evalfun(struct funcnode *func, int argc, char **argv, int flags)
+{
+	volatile struct shparam saveparam;
+	struct jmploc *volatile savehandler;
+	struct jmploc jmploc;
+	int e;
+	int savefuncline;
+	int saveloopnest;
+
+	saveparam = shellparam;
+	savefuncline = funcline;
+	saveloopnest = loopnest;
+	savehandler = handler;
+	if ((e = setjmp(jmploc.loc))) {
+		goto funcdone;
+	}
+	INTOFF;
+	handler = &jmploc;
+	shellparam.malloc = 0;
+	func->count++;
+	funcline = func->n.ndefun.linno;
+	loopnest = 0;
+	INTON;
+	shellparam.nparam = argc - 1;
+	shellparam.p = argv + 1;
+	shellparam.optind = 1;
+	shellparam.optoff = -1;
+	evaltree(func->n.ndefun.body, flags & EV_TESTED);
+funcdone:
+	INTOFF;
+	loopnest = saveloopnest;
+	funcline = savefuncline;
+	freefunc(func);
+	freeparam(&shellparam);
+	shellparam = saveparam;
+	handler = savehandler;
+	INTON;
+	evalskip &= ~(SKIPFUNC | SKIPFUNCDEF);
+	return e;
+}
+
+
+/*
+ * Search for a command.  This is called before we fork so that the
+ * location of the command will be available in the parent as well as
+ * the child.  The check for "goodname" is an overly conservative
+ * check that the name will not be subject to expansion.
+ */
+
+STATIC void
+prehash(union node *n)
+{
+	struct cmdentry entry;
+
+	if (n->type == NCMD && n->ncmd.args)
+		if (goodname(n->ncmd.args->narg.text))
+			find_command(n->ncmd.args->narg.text, &entry, 0,
+				     pathval());
+}
+
+
+
+/*
+ * Builtin commands.  Builtin commands whose functions are closely
+ * tied to evaluation are implemented here.
+ */
+
+/*
+ * No command given.
+ */
+
+STATIC int
+bltincmd(int argc, char **argv)
+{
+	/*
+	 * Preserve exitstatus of a previous possible redirection
+	 * as POSIX mandates
+	 */
+	return back_exitstatus;
+}
+
+
+/*
+ * Handle break and continue commands.  Break, continue, and return are
+ * all handled by setting the evalskip flag.  The evaluation routines
+ * above all check this flag, and if it is set they start skipping
+ * commands rather than executing them.  The variable skipcount is
+ * the number of loops to break/continue, or the number of function
+ * levels to return.  (The latter is always 1.)  It should probably
+ * be an error to break out of more loops than exist, but it isn't
+ * in the standard shell so we don't make it one here.
+ */
+
+int
+breakcmd(int argc, char **argv)
+{
+	int n = argc > 1 ? number(argv[1]) : 1;
+
+	if (n <= 0)
+		badnum(argv[1]);
+	if (n > loopnest)
+		n = loopnest;
+	if (n > 0) {
+		evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK;
+		skipcount = n;
+	}
+	return 0;
+}
+
+
+/*
+ * The return command.
+ */
+
+int
+returncmd(int argc, char **argv)
+{
+	int skip;
+	int status;
+
+	/*
+	 * If called outside a function, do what ksh does;
+	 * skip the rest of the file.
+	 */
+	if (argv[1]) {
+		skip = SKIPFUNC;
+		status = number(argv[1]);
+	} else {
+		skip = SKIPFUNCDEF;
+		status = exitstatus;
+	}
+	evalskip = skip;
+
+	return status;
+}
+
+
+int
+falsecmd(int argc, char **argv)
+{
+	return 1;
+}
+
+
+int
+truecmd(int argc, char **argv)
+{
+	return 0;
+}
+
+
+int
+execcmd(int argc, char **argv)
+{
+	if (argc > 1) {
+		iflag = 0;		/* exit on error */
+		mflag = 0;
+		optschanged();
+		shellexec(argv + 1, pathval(), 0);
+	}
+	return 0;
+}
+
+
+STATIC int
+eprintlist(struct output *out, struct strlist *sp, int sep)
+{
+	while (sp) {
+		const char *p;
+
+		p = " %s";
+		p += (1 - sep);
+		sep |= 1;
+		outfmt(out, p, sp->text);
+		sp = sp->next;
+	}
+
+	return sep;
+}
diff --git a/bin/dash/src/eval.h b/bin/dash/src/eval.h
new file mode 100644
index 00000000..63e7d865
--- /dev/null
+++ b/bin/dash/src/eval.h
@@ -0,0 +1,65 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)eval.h	8.2 (Berkeley) 5/4/95
+ */
+
+extern char *commandname;	/* currently executing command */
+extern int exitstatus;		/* exit status of last command */
+extern int back_exitstatus;	/* exit status of backquoted command */
+extern int savestatus;		/* exit status of last command outside traps */
+
+
+struct backcmd {		/* result of evalbackcmd */
+	int fd;			/* file descriptor to read from */
+	char *buf;		/* buffer */
+	int nleft;		/* number of chars in buffer */
+	struct job *jp;		/* job structure for command */
+};
+
+/* flags in argument to evaltree */
+#define EV_EXIT 01		/* exit after evaluating tree */
+#define EV_TESTED 02		/* exit status is checked; ignore -e flag */
+
+int evalstring(char *, int);
+union node;	/* BLETCH for ansi C */
+int evaltree(union node *, int);
+void evalbackcmd(union node *, struct backcmd *);
+
+extern int evalskip;
+
+/* reasons for skipping commands (see comment on breakcmd routine) */
+#define SKIPBREAK	(1 << 0)
+#define SKIPCONT	(1 << 1)
+#define SKIPFUNC	(1 << 2)
+#define SKIPFUNCDEF	(1 << 3)
diff --git a/bin/dash/src/exec.c b/bin/dash/src/exec.c
new file mode 100644
index 00000000..87354d49
--- /dev/null
+++ b/bin/dash/src/exec.c
@@ -0,0 +1,908 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+
+/*
+ * When commands are first encountered, they are entered in a hash table.
+ * This ensures that a full path search will not have to be done for them
+ * on each invocation.
+ *
+ * We should investigate converting to a linear search, even though that
+ * would make the command name "hash" a misnomer.
+ */
+
+#include "shell.h"
+#include "main.h"
+#include "nodes.h"
+#include "parser.h"
+#include "redir.h"
+#include "eval.h"
+#include "exec.h"
+#include "builtins.h"
+#include "var.h"
+#include "options.h"
+#include "output.h"
+#include "syntax.h"
+#include "memalloc.h"
+#include "error.h"
+#include "init.h"
+#include "mystring.h"
+#include "show.h"
+#include "jobs.h"
+#include "alias.h"
+#include "system.h"
+
+
+#define CMDTABLESIZE 31		/* should be prime */
+#define ARB 1			/* actual size determined at run time */
+
+
+
+struct tblentry {
+	struct tblentry *next;	/* next entry in hash chain */
+	union param param;	/* definition of builtin function */
+	short cmdtype;		/* index identifying command */
+	char rehash;		/* if set, cd done since entry created */
+	char cmdname[ARB];	/* name of command */
+};
+
+
+STATIC struct tblentry *cmdtable[CMDTABLESIZE];
+STATIC int builtinloc = -1;		/* index in path of %builtin, or -1 */
+
+
+STATIC void tryexec(char *, char **, char **);
+STATIC void printentry(struct tblentry *);
+STATIC void clearcmdentry(void);
+STATIC struct tblentry *cmdlookup(const char *, int);
+STATIC void delete_cmd_entry(void);
+STATIC void addcmdentry(char *, struct cmdentry *);
+STATIC int describe_command(struct output *, char *, const char *, int);
+
+
+/*
+ * Exec a program.  Never returns.  If you change this routine, you may
+ * have to change the find_command routine as well.
+ */
+
+void
+shellexec(char **argv, const char *path, int idx)
+{
+	char *cmdname;
+	int e;
+	char **envp;
+	int exerrno;
+
+	envp = environment();
+	if (strchr(argv[0], '/') != NULL) {
+		tryexec(argv[0], argv, envp);
+		e = errno;
+	} else {
+		e = ENOENT;
+		while (padvance(&path, argv[0]) >= 0) {
+			cmdname = stackblock();
+			if (--idx < 0 && pathopt == NULL) {
+				tryexec(cmdname, argv, envp);
+				if (errno != ENOENT && errno != ENOTDIR)
+					e = errno;
+			}
+		}
+	}
+
+	/* Map to POSIX errors */
+	switch (e) {
+	default:
+		exerrno = 126;
+		break;
+	case ELOOP:
+	case ENAMETOOLONG:
+	case ENOENT:
+	case ENOTDIR:
+		exerrno = 127;
+		break;
+	}
+	exitstatus = exerrno;
+	TRACE(("shellexec failed for %s, errno %d, suppressint %d\n",
+		argv[0], e, suppressint ));
+	exerror(EXEND, "%s: %s", argv[0], errmsg(e, E_EXEC));
+	/* NOTREACHED */
+}
+
+
+STATIC void
+tryexec(char *cmd, char **argv, char **envp)
+{
+	char *const path_bshell = _PATH_BSHELL;
+
+repeat:
+#ifdef SYSV
+	do {
+		execve(cmd, argv, envp);
+	} while (errno == EINTR);
+#else
+	execve(cmd, argv, envp);
+#endif
+	if (cmd != path_bshell && errno == ENOEXEC) {
+		*argv-- = cmd;
+		*argv = cmd = path_bshell;
+		goto repeat;
+	}
+}
+
+static const char *legal_pathopt(const char *opt, const char *term, int magic)
+{
+	switch (magic) {
+	case 0:
+		opt = NULL;
+		break;
+
+	case 1:
+		opt = prefix(opt, "builtin") ?: prefix(opt, "func");
+		break;
+
+	default:
+		opt += strcspn(opt, term);
+		break;
+	}
+
+	if (opt && *opt == '%')
+		opt++;
+
+	return opt;
+}
+
+/*
+ * Do a path search.  The variable path (passed by reference) should be
+ * set to the start of the path before the first call; padvance will update
+ * this value as it proceeds.  Successive calls to padvance will return
+ * the possible path expansions in sequence.  If an option (indicated by
+ * a percent sign) appears in the path entry then the global variable
+ * pathopt will be set to point to it; otherwise pathopt will be set to
+ * NULL.
+ *
+ * If magic is 0 then pathopt recognition will be disabled.  If magic is
+ * 1 we shall recognise %builtin/%func.  Otherwise we shall accept any
+ * pathopt.
+ */
+
+const char *pathopt;
+
+int padvance_magic(const char **path, const char *name, int magic)
+{
+	const char *term = "%:";
+	const char *lpathopt;
+	const char *p;
+	char *q;
+	const char *start;
+	size_t qlen;
+	size_t len;
+
+	if (*path == NULL)
+		return -1;
+
+	lpathopt = NULL;
+	start = *path;
+
+	if (*start == '%' && (p = legal_pathopt(start + 1, term, magic))) {
+		lpathopt = start + 1;
+		start = p;
+		term = ":";
+	}
+
+	len = strcspn(start, term);
+	p = start + len;
+
+	if (*p == '%') {
+		size_t extra = strchrnul(p, ':') - p;
+
+		if (legal_pathopt(p + 1, term, magic))
+			lpathopt = p + 1;
+		else
+			len += extra;
+
+		p += extra;
+	}
+
+	pathopt = lpathopt;
+	*path = *p == ':' ? p + 1 : NULL;
+
+	/* "2" is for '/' and '\0' */
+	qlen = len + strlen(name) + 2;
+	q = growstackto(qlen);
+
+	if (likely(len)) {
+		q = mempcpy(q, start, len);
+		*q++ = '/';
+	}
+	strcpy(q, name);
+
+	return qlen;
+}
+
+
+
+/*** Command hashing code ***/
+
+
+int
+hashcmd(int argc, char **argv)
+{
+	struct tblentry **pp;
+	struct tblentry *cmdp;
+	int c;
+	struct cmdentry entry;
+	char *name;
+
+	while ((c = nextopt("r")) != '\0') {
+		clearcmdentry();
+		return 0;
+	}
+	if (*argptr == NULL) {
+		for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
+			for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+				if (cmdp->cmdtype == CMDNORMAL)
+					printentry(cmdp);
+			}
+		}
+		return 0;
+	}
+	c = 0;
+	while ((name = *argptr) != NULL) {
+		if ((cmdp = cmdlookup(name, 0)) &&
+		    (cmdp->cmdtype == CMDNORMAL ||
+		     (cmdp->cmdtype == CMDBUILTIN &&
+		      !(cmdp->param.cmd->flags & BUILTIN_REGULAR) &&
+		      builtinloc > 0)))
+			delete_cmd_entry();
+		find_command(name, &entry, DO_ERR, pathval());
+		if (entry.cmdtype == CMDUNKNOWN)
+			c = 1;
+		argptr++;
+	}
+	return c;
+}
+
+
+STATIC void
+printentry(struct tblentry *cmdp)
+{
+	int idx;
+	const char *path;
+	char *name;
+
+	idx = cmdp->param.index;
+	path = pathval();
+	do {
+		padvance(&path, cmdp->cmdname);
+	} while (--idx >= 0);
+	name = stackblock();
+	out1str(name);
+	out1fmt(snlfmt, cmdp->rehash ? "*" : nullstr);
+}
+
+
+
+/*
+ * Resolve a command name.  If you change this routine, you may have to
+ * change the shellexec routine as well.
+ */
+
+void
+find_command(char *name, struct cmdentry *entry, int act, const char *path)
+{
+	struct tblentry *cmdp;
+	int idx;
+	int prev;
+	char *fullname;
+	struct stat64 statb;
+	int e;
+	int updatetbl;
+	struct builtincmd *bcmd;
+	int len;
+
+	/* If name contains a slash, don't use PATH or hash table */
+	if (strchr(name, '/') != NULL) {
+		entry->u.index = -1;
+		if (act & DO_ABS) {
+			while (stat64(name, &statb) < 0) {
+#ifdef SYSV
+				if (errno == EINTR)
+					continue;
+#endif
+				entry->cmdtype = CMDUNKNOWN;
+				return;
+			}
+		}
+		entry->cmdtype = CMDNORMAL;
+		return;
+	}
+
+	updatetbl = (path == pathval());
+	if (!updatetbl)
+		act |= DO_ALTPATH;
+
+	/* If name is in the table, check answer will be ok */
+	if ((cmdp = cmdlookup(name, 0)) != NULL) {
+		int bit;
+
+		switch (cmdp->cmdtype) {
+		default:
+#if DEBUG
+			abort();
+#endif
+		case CMDNORMAL:
+			bit = DO_ALTPATH | DO_REGBLTIN;
+			break;
+		case CMDFUNCTION:
+			bit = DO_NOFUNC;
+			break;
+		case CMDBUILTIN:
+			bit = cmdp->param.cmd->flags & BUILTIN_REGULAR ?
+			      0 : DO_REGBLTIN;
+			break;
+		}
+		if (act & bit) {
+			if (act & bit & DO_REGBLTIN)
+				goto fail;
+
+			updatetbl = 0;
+			cmdp = NULL;
+		} else if (cmdp->rehash == 0)
+			/* if not invalidated by cd, we're done */
+			goto success;
+	}
+
+	/* If %builtin not in path, check for builtin next */
+	bcmd = find_builtin(name);
+	if (bcmd && ((bcmd->flags & BUILTIN_REGULAR) | (act & DO_ALTPATH) |
+		     (builtinloc <= 0)))
+		goto builtin_success;
+
+	if (act & DO_REGBLTIN)
+		goto fail;
+
+	/* We have to search path. */
+	prev = -1;		/* where to start */
+	if (cmdp && cmdp->rehash) {	/* doing a rehash */
+		if (cmdp->cmdtype == CMDBUILTIN)
+			prev = builtinloc;
+		else
+			prev = cmdp->param.index;
+	}
+
+	e = ENOENT;
+	idx = -1;
+loop:
+	while ((len = padvance(&path, name)) >= 0) {
+		const char *lpathopt = pathopt;
+
+		fullname = stackblock();
+		idx++;
+		if (lpathopt) {
+			if (*lpathopt == 'b') {
+				if (bcmd)
+					goto builtin_success;
+				continue;
+			} else if (!(act & DO_NOFUNC)) {
+				/* handled below */
+			} else {
+				/* ignore unimplemented options */
+				continue;
+			}
+		}
+		/* if rehash, don't redo absolute path names */
+		if (fullname[0] == '/' && idx <= prev) {
+			if (idx < prev)
+				continue;
+			TRACE(("searchexec \"%s\": no change\n", name));
+			goto success;
+		}
+		while (stat64(fullname, &statb) < 0) {
+#ifdef SYSV
+			if (errno == EINTR)
+				continue;
+#endif
+			if (errno != ENOENT && errno != ENOTDIR)
+				e = errno;
+			goto loop;
+		}
+		e = EACCES;	/* if we fail, this will be the error */
+		if (!S_ISREG(statb.st_mode))
+			continue;
+		if (lpathopt) {		/* this is a %func directory */
+			stalloc(len);
+			readcmdfile(fullname);
+			if ((cmdp = cmdlookup(name, 0)) == NULL ||
+			    cmdp->cmdtype != CMDFUNCTION)
+				sh_error("%s not defined in %s", name,
+					 fullname);
+			stunalloc(fullname);
+			goto success;
+		}
+#ifdef notdef
+		/* XXX this code stops root executing stuff, and is buggy
+		   if you need a group from the group list. */
+		if (statb.st_uid == geteuid()) {
+			if ((statb.st_mode & 0100) == 0)
+				goto loop;
+		} else if (statb.st_gid == getegid()) {
+			if ((statb.st_mode & 010) == 0)
+				goto loop;
+		} else {
+			if ((statb.st_mode & 01) == 0)
+				goto loop;
+		}
+#endif
+		TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
+		if (!updatetbl) {
+			entry->cmdtype = CMDNORMAL;
+			entry->u.index = idx;
+			return;
+		}
+		INTOFF;
+		cmdp = cmdlookup(name, 1);
+		cmdp->cmdtype = CMDNORMAL;
+		cmdp->param.index = idx;
+		INTON;
+		goto success;
+	}
+
+	/* We failed.  If there was an entry for this command, delete it */
+	if (cmdp && updatetbl)
+		delete_cmd_entry();
+	if (act & DO_ERR)
+		sh_warnx("%s: %s", name, errmsg(e, E_EXEC));
+fail:
+	entry->cmdtype = CMDUNKNOWN;
+	return;
+
+builtin_success:
+	if (!updatetbl) {
+		entry->cmdtype = CMDBUILTIN;
+		entry->u.cmd = bcmd;
+		return;
+	}
+	INTOFF;
+	cmdp = cmdlookup(name, 1);
+	cmdp->cmdtype = CMDBUILTIN;
+	cmdp->param.cmd = bcmd;
+	INTON;
+success:
+	cmdp->rehash = 0;
+	entry->cmdtype = cmdp->cmdtype;
+	entry->u = cmdp->param;
+}
+
+
+
+/*
+ * Search the table of builtin commands.
+ */
+
+struct builtincmd *
+find_builtin(const char *name)
+{
+	struct builtincmd *bp;
+
+	bp = bsearch(
+		&name, builtincmd, NUMBUILTINS, sizeof(struct builtincmd),
+		pstrcmp
+	);
+	return bp;
+}
+
+
+
+/*
+ * Called when a cd is done.  Marks all commands so the next time they
+ * are executed they will be rehashed.
+ */
+
+void
+hashcd(void)
+{
+	struct tblentry **pp;
+	struct tblentry *cmdp;
+
+	for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) {
+		for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+			if (cmdp->cmdtype == CMDNORMAL || (
+				cmdp->cmdtype == CMDBUILTIN &&
+				!(cmdp->param.cmd->flags & BUILTIN_REGULAR) &&
+				builtinloc > 0
+			))
+				cmdp->rehash = 1;
+		}
+	}
+}
+
+
+
+/*
+ * Fix command hash table when PATH changed.
+ * Called before PATH is changed.  The argument is the new value of PATH;
+ * pathval() still returns the old value at this point.
+ * Called with interrupts off.
+ */
+
+void
+changepath(const char *newval)
+{
+	const char *new;
+	int idx;
+	int bltin;
+
+	new = newval;
+	idx = 0;
+	bltin = -1;
+	for (;;) {
+		if (*new == '%' && prefix(new + 1, "builtin")) {
+			bltin = idx;
+			break;
+		}
+		new = strchr(new, ':');
+		if (!new)
+			break;
+		idx++;
+		new++;
+	}
+	builtinloc = bltin;
+	clearcmdentry();
+}
+
+
+/*
+ * Clear out command entries.  The argument specifies the first entry in
+ * PATH which has changed.
+ */
+
+STATIC void
+clearcmdentry(void)
+{
+	struct tblentry **tblp;
+	struct tblentry **pp;
+	struct tblentry *cmdp;
+
+	INTOFF;
+	for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) {
+		pp = tblp;
+		while ((cmdp = *pp) != NULL) {
+			if (cmdp->cmdtype == CMDNORMAL ||
+			    (cmdp->cmdtype == CMDBUILTIN &&
+			     !(cmdp->param.cmd->flags & BUILTIN_REGULAR) &&
+			     builtinloc > 0)) {
+				*pp = cmdp->next;
+				ckfree(cmdp);
+			} else {
+				pp = &cmdp->next;
+			}
+		}
+	}
+	INTON;
+}
+
+
+
+/*
+ * Locate a command in the command hash table.  If "add" is nonzero,
+ * add the command to the table if it is not already present.  The
+ * variable "lastcmdentry" is set to point to the address of the link
+ * pointing to the entry, so that delete_cmd_entry can delete the
+ * entry.
+ *
+ * Interrupts must be off if called with add != 0.
+ */
+
+struct tblentry **lastcmdentry;
+
+
+STATIC struct tblentry *
+cmdlookup(const char *name, int add)
+{
+	unsigned int hashval;
+	const char *p;
+	struct tblentry *cmdp;
+	struct tblentry **pp;
+
+	p = name;
+	hashval = (unsigned char)*p << 4;
+	while (*p)
+		hashval += (unsigned char)*p++;
+	hashval &= 0x7FFF;
+	pp = &cmdtable[hashval % CMDTABLESIZE];
+	for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) {
+		if (equal(cmdp->cmdname, name))
+			break;
+		pp = &cmdp->next;
+	}
+	if (add && cmdp == NULL) {
+		cmdp = *pp = ckmalloc(sizeof (struct tblentry) - ARB
+					+ strlen(name) + 1);
+		cmdp->next = NULL;
+		cmdp->cmdtype = CMDUNKNOWN;
+		strcpy(cmdp->cmdname, name);
+	}
+	lastcmdentry = pp;
+	return cmdp;
+}
+
+/*
+ * Delete the command entry returned on the last lookup.
+ */
+
+STATIC void
+delete_cmd_entry(void)
+{
+	struct tblentry *cmdp;
+
+	INTOFF;
+	cmdp = *lastcmdentry;
+	*lastcmdentry = cmdp->next;
+	if (cmdp->cmdtype == CMDFUNCTION)
+		freefunc(cmdp->param.func);
+	ckfree(cmdp);
+	INTON;
+}
+
+
+
+#ifdef notdef
+void
+getcmdentry(char *name, struct cmdentry *entry)
+{
+	struct tblentry *cmdp = cmdlookup(name, 0);
+
+	if (cmdp) {
+		entry->u = cmdp->param;
+		entry->cmdtype = cmdp->cmdtype;
+	} else {
+		entry->cmdtype = CMDUNKNOWN;
+		entry->u.index = 0;
+	}
+}
+#endif
+
+
+/*
+ * Add a new command entry, replacing any existing command entry for
+ * the same name - except special builtins.
+ */
+
+STATIC void
+addcmdentry(char *name, struct cmdentry *entry)
+{
+	struct tblentry *cmdp;
+
+	cmdp = cmdlookup(name, 1);
+	if (cmdp->cmdtype == CMDFUNCTION) {
+		freefunc(cmdp->param.func);
+	}
+	cmdp->cmdtype = entry->cmdtype;
+	cmdp->param = entry->u;
+	cmdp->rehash = 0;
+}
+
+
+/*
+ * Define a shell function.
+ */
+
+void
+defun(union node *func)
+{
+	struct cmdentry entry;
+
+	INTOFF;
+	entry.cmdtype = CMDFUNCTION;
+	entry.u.func = copyfunc(func);
+	addcmdentry(func->ndefun.text, &entry);
+	INTON;
+}
+
+
+/*
+ * Delete a function if it exists.
+ */
+
+void
+unsetfunc(const char *name)
+{
+	struct tblentry *cmdp;
+
+	if ((cmdp = cmdlookup(name, 0)) != NULL &&
+	    cmdp->cmdtype == CMDFUNCTION)
+		delete_cmd_entry();
+}
+
+/*
+ * Locate and print what a word is...
+ */
+
+int
+typecmd(int argc, char **argv)
+{
+	int i;
+	int err = 0;
+
+	for (i = 1; i < argc; i++) {
+		err |= describe_command(out1, argv[i], NULL, 1);
+	}
+	return err;
+}
+
+STATIC int
+describe_command(out, command, path, verbose)
+	struct output *out;
+	char *command;
+	const char *path;
+	int verbose;
+{
+	struct cmdentry entry;
+	struct tblentry *cmdp;
+	const struct alias *ap;
+
+	if (verbose) {
+		outstr(command, out);
+	}
+
+	/* First look at the keywords */
+	if (findkwd(command)) {
+		outstr(verbose ? " is a shell keyword" : command, out);
+		goto out;
+	}
+
+	/* Then look at the aliases */
+	if ((ap = lookupalias(command, 0)) != NULL) {
+		if (verbose) {
+			outfmt(out, " is an alias for %s", ap->val);
+		} else {
+			outstr("alias ", out);
+			printalias(ap);
+			return 0;
+		}
+		goto out;
+	}
+
+	/* Then if the standard search path is used, check if it is
+	 * a tracked alias.
+	 */
+	if (path == NULL) {
+		path = pathval();
+		cmdp = cmdlookup(command, 0);
+	} else {
+		cmdp = NULL;
+	}
+
+	if (cmdp != NULL) {
+		entry.cmdtype = cmdp->cmdtype;
+		entry.u = cmdp->param;
+	} else {
+		/* Finally use brute force */
+		find_command(command, &entry, DO_ABS, path);
+	}
+
+	switch (entry.cmdtype) {
+	case CMDNORMAL: {
+		int j = entry.u.index;
+		char *p;
+		if (j == -1) {
+			p = command;
+		} else {
+			do {
+				padvance(&path, command);
+			} while (--j >= 0);
+			p = stackblock();
+		}
+		if (verbose) {
+			outfmt(
+				out, " is%s %s",
+				cmdp ? " a tracked alias for" : nullstr, p
+			);
+		} else {
+			outstr(p, out);
+		}
+		break;
+	}
+
+	case CMDFUNCTION:
+		if (verbose) {
+			outstr(" is a shell function", out);
+		} else {
+			outstr(command, out);
+		}
+		break;
+
+	case CMDBUILTIN:
+		if (verbose) {
+			outfmt(
+				out, " is a %sshell builtin",
+				entry.u.cmd->flags & BUILTIN_SPECIAL ?
+					"special " : nullstr
+			);
+		} else {
+			outstr(command, out);
+		}
+		break;
+
+	default:
+		if (verbose) {
+			outstr(": not found\n", out);
+		}
+		return 127;
+	}
+
+out:
+	outc('\n', out);
+	return 0;
+}
+
+int
+commandcmd(argc, argv)
+	int argc;
+	char **argv;
+{
+	char *cmd;
+	int c;
+	enum {
+		VERIFY_BRIEF = 1,
+		VERIFY_VERBOSE = 2,
+	} verify = 0;
+	const char *path = NULL;
+
+	while ((c = nextopt("pvV")) != '\0')
+		if (c == 'V')
+			verify |= VERIFY_VERBOSE;
+		else if (c == 'v')
+			verify |= VERIFY_BRIEF;
+#ifdef DEBUG
+		else if (c != 'p')
+			abort();
+#endif
+		else
+			path = defpath;
+
+	cmd = *argptr;
+	if (verify && cmd)
+		return describe_command(out1, cmd, path, verify - VERIFY_BRIEF);
+
+	return 0;
+}
diff --git a/bin/dash/src/exec.h b/bin/dash/src/exec.h
new file mode 100644
index 00000000..423b07e6
--- /dev/null
+++ b/bin/dash/src/exec.h
@@ -0,0 +1,84 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)exec.h	8.3 (Berkeley) 6/8/95
+ */
+
+/* values of cmdtype */
+#define CMDUNKNOWN	-1	/* no entry in table for command */
+#define CMDNORMAL	0	/* command is an executable program */
+#define CMDFUNCTION	1	/* command is a shell function */
+#define CMDBUILTIN	2	/* command is a shell builtin */
+
+
+struct cmdentry {
+	int cmdtype;
+	union param {
+		int index;
+		const struct builtincmd *cmd;
+		struct funcnode *func;
+	} u;
+};
+
+
+/* action to find_command() */
+#define DO_ERR		0x01	/* prints errors */
+#define DO_ABS		0x02	/* checks absolute paths */
+#define DO_NOFUNC	0x04	/* don't return shell functions, for command */
+#define DO_ALTPATH	0x08	/* using alternate path */
+#define DO_REGBLTIN	0x10	/* regular built-ins and functions only */
+
+union node;
+
+extern const char *pathopt;	/* set by padvance */
+
+void shellexec(char **, const char *, int)
+    __attribute__((__noreturn__));
+int padvance_magic(const char **path, const char *name, int magic);
+int hashcmd(int, char **);
+void find_command(char *, struct cmdentry *, int, const char *);
+struct builtincmd *find_builtin(const char *);
+void hashcd(void);
+void changepath(const char *);
+#ifdef notdef
+void getcmdentry(char *, struct cmdentry *);
+#endif
+void defun(union node *);
+void unsetfunc(const char *);
+int typecmd(int, char **);
+int commandcmd(int, char **);
+
+static inline int padvance(const char **path, const char *name)
+{
+	return padvance_magic(path, name, 1);
+}
diff --git a/bin/dash/src/expand.c b/bin/dash/src/expand.c
new file mode 100644
index 00000000..1730670e
--- /dev/null
+++ b/bin/dash/src/expand.c
@@ -0,0 +1,1753 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+#ifdef HAVE_GETPWNAM
+#include <pwd.h>
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <string.h>
+#ifdef HAVE_FNMATCH
+#include <fnmatch.h>
+#endif
+#ifdef HAVE_GLOB
+#include <glob.h>
+#endif
+#include <ctype.h>
+#include <stdbool.h>
+
+/*
+ * Routines to expand arguments to commands.  We have to deal with
+ * backquotes, shell variables, and file metacharacters.
+ */
+
+#include "shell.h"
+#include "main.h"
+#include "nodes.h"
+#include "eval.h"
+#include "expand.h"
+#include "syntax.h"
+#include "parser.h"
+#include "jobs.h"
+#include "options.h"
+#include "var.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "show.h"
+#include "system.h"
+
+/*
+ * _rmescape() flags
+ */
+#define RMESCAPE_ALLOC	0x1	/* Allocate a new string */
+#define RMESCAPE_GLOB	0x2	/* Add backslashes for glob */
+#define RMESCAPE_GROW	0x8	/* Grow strings instead of stalloc */
+#define RMESCAPE_HEAP	0x10	/* Malloc strings instead of stalloc */
+
+/* Add CTLESC when necessary. */
+#define QUOTES_ESC	(EXP_FULL | EXP_CASE)
+
+/*
+ * Structure specifying which parts of the string should be searched
+ * for IFS characters.
+ */
+
+struct ifsregion {
+	struct ifsregion *next;	/* next region in list */
+	int begoff;		/* offset of start of region */
+	int endoff;		/* offset of end of region */
+	int nulonly;		/* search for nul bytes only */
+};
+
+/* output of current string */
+static char *expdest;
+/* list of back quote expressions */
+static struct nodelist *argbackq;
+/* first struct in list of ifs regions */
+static struct ifsregion ifsfirst;
+/* last struct in list */
+static struct ifsregion *ifslastp;
+/* holds expanded arg list */
+static struct arglist exparg;
+
+static char *argstr(char *p, int flag);
+static char *exptilde(char *startp, int flag);
+static char *expari(char *start, int flag);
+STATIC void expbackq(union node *, int);
+STATIC char *evalvar(char *, int);
+static size_t strtodest(const char *p, int flags);
+static size_t memtodest(const char *p, size_t len, int flags);
+STATIC ssize_t varvalue(char *, int, int, int);
+STATIC void expandmeta(struct strlist *);
+#ifdef HAVE_GLOB
+STATIC void addglob(const glob_t *);
+#else
+STATIC void expmeta(char *, unsigned, unsigned);
+STATIC struct strlist *expsort(struct strlist *);
+STATIC struct strlist *msort(struct strlist *, int);
+#endif
+STATIC void addfname(char *);
+STATIC int patmatch(char *, const char *);
+#ifndef HAVE_FNMATCH
+STATIC int pmatch(const char *, const char *);
+#else
+#define pmatch(a, b) !fnmatch((a), (b), 0)
+#endif
+static size_t cvtnum(intmax_t num, int flags);
+STATIC size_t esclen(const char *, const char *);
+STATIC char *scanleft(char *, char *, char *, char *, int, int);
+STATIC char *scanright(char *, char *, char *, char *, int, int);
+STATIC void varunset(const char *, const char *, const char *, int)
+	__attribute__((__noreturn__));
+
+
+/*
+ * Prepare a pattern for a glob(3) call.
+ *
+ * Returns an stalloced string.
+ */
+
+STATIC inline char *
+preglob(const char *pattern, int flag) {
+	flag |= RMESCAPE_GLOB;
+	return _rmescapes((char *)pattern, flag);
+}
+
+
+STATIC size_t
+esclen(const char *start, const char *p) {
+	size_t esc = 0;
+
+	while (p > start && *--p == (char)CTLESC) {
+		esc++;
+	}
+	return esc;
+}
+
+
+static inline const char *getpwhome(const char *name)
+{
+#ifdef HAVE_GETPWNAM
+	struct passwd *pw = getpwnam(name);
+	return pw ? pw->pw_dir : 0;
+#else
+	return 0;
+#endif
+}
+
+
+/*
+ * Perform variable substitution and command substitution on an argument,
+ * placing the resulting list of arguments in arglist.  If EXP_FULL is true,
+ * perform splitting and file name expansion.  When arglist is NULL, perform
+ * here document expansion.
+ */
+
+void
+expandarg(union node *arg, struct arglist *arglist, int flag)
+{
+	struct strlist *sp;
+	char *p;
+
+	argbackq = arg->narg.backquote;
+	STARTSTACKSTR(expdest);
+	argstr(arg->narg.text, flag);
+	if (arglist == NULL) {
+		/* here document expanded */
+		goto out;
+	}
+	p = grabstackstr(expdest);
+	exparg.lastp = &exparg.list;
+	/*
+	 * TODO - EXP_REDIR
+	 */
+	if (flag & EXP_FULL) {
+		ifsbreakup(p, -1, &exparg);
+		*exparg.lastp = NULL;
+		exparg.lastp = &exparg.list;
+		expandmeta(exparg.list);
+	} else {
+		sp = (struct strlist *)stalloc(sizeof (struct strlist));
+		sp->text = p;
+		*exparg.lastp = sp;
+		exparg.lastp = &sp->next;
+	}
+	*exparg.lastp = NULL;
+	if (exparg.list) {
+		*arglist->lastp = exparg.list;
+		arglist->lastp = exparg.lastp;
+	}
+
+out:
+	ifsfree();
+}
+
+
+
+/*
+ * Perform variable and command substitution.  If EXP_FULL is set, output CTLESC
+ * characters to allow for further processing.  Otherwise treat
+ * $@ like $* since no splitting will be performed.
+ */
+
+static char *argstr(char *p, int flag)
+{
+	static const char spclchars[] = {
+		'=',
+		':',
+		CTLQUOTEMARK,
+		CTLENDVAR,
+		CTLESC,
+		CTLVAR,
+		CTLBACKQ,
+		CTLARI,
+		CTLENDARI,
+		0
+	};
+	const char *reject = spclchars;
+	int c;
+	int breakall = (flag & (EXP_WORD | EXP_QUOTED)) == EXP_WORD;
+	int inquotes;
+	size_t length;
+	int startloc;
+
+	reject += !!(flag & EXP_VARTILDE2);
+	reject += flag & EXP_VARTILDE ? 0 : 2;
+	inquotes = 0;
+	length = 0;
+	if (flag & EXP_TILDE) {
+		flag &= ~EXP_TILDE;
+tilde:
+		if (*p == '~')
+			p = exptilde(p, flag);
+	}
+start:
+	startloc = expdest - (char *)stackblock();
+	for (;;) {
+		int end;
+
+		length += strcspn(p + length, reject);
+		end = 0;
+		c = (signed char)p[length];
+		if (!(c & 0x80) || c == CTLENDARI || c == CTLENDVAR) {
+			/*
+			 * c == '=' || c == ':' || c == '\0' ||
+			 * c == CTLENDARI || c == CTLENDVAR
+			 */
+			length++;
+			/* c == '\0' || c == CTLENDARI || c == CTLENDVAR */
+			end = !!((c - 1) & 0x80);
+		}
+		if (length > 0 && !(flag & EXP_DISCARD)) {
+			int newloc;
+			char *q;
+
+			q = stnputs(p, length, expdest);
+			q[-1] &= end - 1;
+			expdest = q - (flag & EXP_WORD ? end : 0);
+			newloc = q - (char *)stackblock() - end;
+			if (breakall && !inquotes && newloc > startloc) {
+				recordregion(startloc, newloc, 0);
+			}
+			startloc = newloc;
+		}
+		p += length + 1;
+		length = 0;
+
+		if (end)
+			break;
+
+		switch (c) {
+		case '=':
+			flag |= EXP_VARTILDE2;
+			reject++;
+			/* fall through */
+		case ':':
+			/*
+			 * sort of a hack - expand tildes in variable
+			 * assignments (after the first '=' and after ':'s).
+			 */
+			if (*--p == '~') {
+				goto tilde;
+			}
+			continue;
+		case CTLQUOTEMARK:
+			/* "$@" syntax adherence hack */
+			if (!inquotes && !memcmp(p, dolatstr + 1,
+						 DOLATSTRLEN - 1)) {
+				p = evalvar(p + 1, flag | EXP_QUOTED) + 1;
+				goto start;
+			}
+			inquotes ^= EXP_QUOTED;
+addquote:
+			if (flag & QUOTES_ESC) {
+				p--;
+				length++;
+				startloc++;
+			}
+			break;
+		case CTLESC:
+			startloc++;
+			length++;
+			goto addquote;
+		case CTLVAR:
+			p = evalvar(p, flag | inquotes);
+			goto start;
+		case CTLBACKQ:
+			expbackq(argbackq->n, flag | inquotes);
+			goto start;
+		case CTLARI:
+			p = expari(p, flag | inquotes);
+			goto start;
+		}
+	}
+	return p - 1;
+}
+
+static char *exptilde(char *startp, int flag)
+{
+	signed char c;
+	char *name;
+	const char *home;
+	char *p;
+
+	p = startp;
+	name = p + 1;
+
+	while ((c = *++p) != '\0') {
+		switch(c) {
+		case CTLESC:
+			return (startp);
+		case CTLQUOTEMARK:
+			return (startp);
+		case ':':
+			if (flag & EXP_VARTILDE)
+				goto done;
+			break;
+		case '/':
+		case CTLENDVAR:
+			goto done;
+		}
+	}
+done:
+	if (flag & EXP_DISCARD)
+		goto out;
+	*p = '\0';
+	if (*name == '\0') {
+		home = lookupvar(homestr);
+	} else {
+		home = getpwhome(name);
+	}
+	*p = c;
+	if (!home)
+		goto lose;
+	strtodest(home, flag | EXP_QUOTED);
+out:
+	return (p);
+lose:
+	return (startp);
+}
+
+
+void 
+removerecordregions(int endoff)
+{
+	if (ifslastp == NULL)
+		return;
+
+	if (ifsfirst.endoff > endoff) {
+		while (ifsfirst.next != NULL) {
+			struct ifsregion *ifsp;
+			INTOFF;
+			ifsp = ifsfirst.next->next;
+			ckfree(ifsfirst.next);
+			ifsfirst.next = ifsp;
+			INTON;
+		}
+		if (ifsfirst.begoff > endoff)
+			ifslastp = NULL;
+		else {
+			ifslastp = &ifsfirst;
+			ifsfirst.endoff = endoff;
+		}
+		return;
+	}
+	
+	ifslastp = &ifsfirst;
+	while (ifslastp->next && ifslastp->next->begoff < endoff)
+		ifslastp=ifslastp->next;
+	while (ifslastp->next != NULL) {
+		struct ifsregion *ifsp;
+		INTOFF;
+		ifsp = ifslastp->next->next;
+		ckfree(ifslastp->next);
+		ifslastp->next = ifsp;
+		INTON;
+	}
+	if (ifslastp->endoff > endoff)
+		ifslastp->endoff = endoff;
+}
+
+
+/*
+ * Expand arithmetic expression.  Backup to start of expression,
+ * evaluate, place result in (backed up) result, adjust string position.
+ */
+static char *expari(char *start, int flag)
+{
+	struct stackmark sm;
+	int begoff;
+	int endoff;
+	int len;
+	intmax_t result;
+	char *p;
+
+	p = stackblock();
+	begoff = expdest - p;
+	p = argstr(start, flag & EXP_DISCARD);
+
+	if (flag & EXP_DISCARD)
+		goto out;
+
+	start = stackblock();
+	endoff = expdest - start;
+	start += begoff;
+	STADJUST(start - expdest, expdest);
+
+	removerecordregions(begoff);
+
+	if (likely(flag & QUOTES_ESC))
+		rmescapes(start);
+
+	pushstackmark(&sm, endoff);
+	result = arith(start);
+	popstackmark(&sm);
+
+	len = cvtnum(result, flag);
+
+	if (likely(!(flag & EXP_QUOTED)))
+		recordregion(begoff, begoff + len, 0);
+
+out:
+	return p;
+}
+
+
+/*
+ * Expand stuff in backwards quotes.
+ */
+
+STATIC void
+expbackq(union node *cmd, int flag)
+{
+	struct backcmd in;
+	int i;
+	char buf[128];
+	char *p;
+	char *dest;
+	int startloc;
+	struct stackmark smark;
+
+	if (flag & EXP_DISCARD)
+		goto out;
+
+	INTOFF;
+	startloc = expdest - (char *)stackblock();
+	pushstackmark(&smark, startloc);
+	evalbackcmd(cmd, (struct backcmd *) &in);
+	popstackmark(&smark);
+
+	p = in.buf;
+	i = in.nleft;
+	if (i == 0)
+		goto read;
+	for (;;) {
+		memtodest(p, i, flag);
+read:
+		if (in.fd < 0)
+			break;
+		do {
+			i = read(in.fd, buf, sizeof buf);
+		} while (i < 0 && errno == EINTR);
+		TRACE(("expbackq: read returns %d\n", i));
+		if (i <= 0)
+			break;
+		p = buf;
+	}
+
+	if (in.buf)
+		ckfree(in.buf);
+	if (in.fd >= 0) {
+		close(in.fd);
+		back_exitstatus = waitforjob(in.jp);
+	}
+	INTON;
+
+	/* Eat all trailing newlines */
+	dest = expdest;
+	for (; dest > ((char *)stackblock() + startloc) && dest[-1] == '\n';)
+		STUNPUTC(dest);
+	expdest = dest;
+
+	if (!(flag & EXP_QUOTED))
+		recordregion(startloc, dest - (char *)stackblock(), 0);
+	TRACE(("evalbackq: size=%d: \"%.*s\"\n",
+		(dest - (char *)stackblock()) - startloc,
+		(dest - (char *)stackblock()) - startloc,
+		stackblock() + startloc));
+
+out:
+	argbackq = argbackq->next;
+}
+
+
+STATIC char *
+scanleft(
+	char *startp, char *rmesc, char *rmescend, char *str, int quotes,
+	int zero
+) {
+	char *loc;
+	char *loc2;
+	char c;
+
+	loc = startp;
+	loc2 = rmesc;
+	do {
+		int match;
+		const char *s = loc2;
+		c = *loc2;
+		if (zero) {
+			*loc2 = '\0';
+			s = rmesc;
+		}
+		match = pmatch(str, s);
+		*loc2 = c;
+		if (match)
+			return loc;
+		if (quotes && *loc == (char)CTLESC)
+			loc++;
+		loc++;
+		loc2++;
+	} while (c);
+	return 0;
+}
+
+
+STATIC char *
+scanright(
+	char *startp, char *rmesc, char *rmescend, char *str, int quotes,
+	int zero
+) {
+	int esc = 0;
+	char *loc;
+	char *loc2;
+
+	for (loc = str - 1, loc2 = rmescend; loc >= startp; loc2--) {
+		int match;
+		char c = *loc2;
+		const char *s = loc2;
+		if (zero) {
+			*loc2 = '\0';
+			s = rmesc;
+		}
+		match = pmatch(str, s);
+		*loc2 = c;
+		if (match)
+			return loc;
+		loc--;
+		if (quotes) {
+			if (--esc < 0) {
+				esc = esclen(startp, loc);
+			}
+			if (esc % 2) {
+				esc--;
+				loc--;
+			}
+		}
+	}
+	return 0;
+}
+
+static char *subevalvar(char *start, char *str, int strloc, int startloc,
+			int varflags, int flag)
+{
+	int subtype = varflags & VSTYPE;
+	int quotes = flag & QUOTES_ESC;
+	char *startp;
+	char *loc;
+	long amount;
+	char *rmesc, *rmescend;
+	int zero;
+	char *(*scan)(char *, char *, char *, char *, int , int);
+	char *p;
+
+	p = argstr(start, (flag & EXP_DISCARD) | EXP_TILDE |
+			  (str ?  0 : EXP_CASE));
+	if (flag & EXP_DISCARD)
+		return p;
+
+	startp = stackblock() + startloc;
+
+	switch (subtype) {
+	case VSASSIGN:
+		setvar(str, startp, 0);
+
+		loc = startp;
+		goto out;
+
+	case VSQUESTION:
+		varunset(start, str, startp, varflags);
+		/* NOTREACHED */
+	}
+
+	subtype -= VSTRIMRIGHT;
+#ifdef DEBUG
+	if (subtype < 0 || subtype > 3)
+		abort();
+#endif
+
+	rmesc = startp;
+	rmescend = stackblock() + strloc;
+	if (quotes) {
+		rmesc = _rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW);
+		if (rmesc != startp) {
+			rmescend = expdest;
+			startp = stackblock() + startloc;
+		}
+	}
+	rmescend--;
+	str = stackblock() + strloc;
+	preglob(str, 0);
+
+	/* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */
+	zero = subtype >> 1;
+	/* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */
+	scan = (subtype & 1) ^ zero ? scanleft : scanright;
+
+	loc = scan(startp, rmesc, rmescend, str, quotes, zero);
+	if (loc) {
+		if (zero) {
+			memmove(startp, loc, str - loc);
+			loc = startp + (str - loc) - 1;
+		}
+		*loc = '\0';
+	} else
+		loc = str - 1;
+
+out:
+	amount = loc - expdest;
+	STADJUST(amount, expdest);
+
+	/* Remove any recorded regions beyond start of variable */
+	removerecordregions(startloc);
+
+	return p;
+}
+
+
+/*
+ * Expand a variable, and return a pointer to the next character in the
+ * input string.
+ */
+STATIC char *
+evalvar(char *p, int flag)
+{
+	int subtype;
+	int varflags;
+	char *var;
+	int patloc;
+	int startloc;
+	ssize_t varlen;
+	int discard;
+	int quoted;
+
+	varflags = *p++;
+	subtype = varflags & VSTYPE;
+
+	quoted = flag & EXP_QUOTED;
+	var = p;
+	startloc = expdest - (char *)stackblock();
+	p = strchr(p, '=') + 1;
+
+again:
+	varlen = varvalue(var, varflags, flag, quoted);
+	if (varflags & VSNUL)
+		varlen--;
+
+	discard = varlen < 0 ? EXP_DISCARD : 0;
+
+	switch (subtype) {
+	case VSPLUS:
+		discard ^= EXP_DISCARD;
+		/* fall through */
+
+	case 0:
+	case VSMINUS:
+		p = argstr(p, flag | EXP_TILDE | EXP_WORD |
+			      (discard ^ EXP_DISCARD));
+		goto record;
+
+	case VSASSIGN:
+	case VSQUESTION:
+		p = subevalvar(p, var, 0, startloc, varflags,
+			       (flag & ~QUOTES_ESC) |
+			       (discard ^ EXP_DISCARD));
+
+		if ((flag | ~discard) & EXP_DISCARD)
+			goto record;
+
+		varflags &= ~VSNUL;
+		subtype = VSNORMAL;
+		goto again;
+	}
+
+	if ((discard & ~flag) && uflag)
+		varunset(p, var, 0, 0);
+
+	if (subtype == VSLENGTH) {
+		p++;
+		if (flag & EXP_DISCARD)
+			return p;
+		cvtnum(varlen > 0 ? varlen : 0, flag);
+		goto really_record;
+	}
+
+	if (subtype == VSNORMAL)
+		goto record;
+
+#ifdef DEBUG
+	switch (subtype) {
+	case VSTRIMLEFT:
+	case VSTRIMLEFTMAX:
+	case VSTRIMRIGHT:
+	case VSTRIMRIGHTMAX:
+		break;
+	default:
+		abort();
+	}
+#endif
+
+	flag |= discard;
+	if (!(flag & EXP_DISCARD)) {
+		/*
+		 * Terminate the string and start recording the pattern
+		 * right after it
+		 */
+		STPUTC('\0', expdest);
+	}
+
+	patloc = expdest - (char *)stackblock();
+	p = subevalvar(p, NULL, patloc, startloc, varflags, flag);
+
+record:
+	if ((flag | discard) & EXP_DISCARD)
+		return p;
+
+really_record:
+	if (quoted) {
+		quoted = *var == '@' && shellparam.nparam;
+		if (!quoted)
+			return p;
+	}
+	recordregion(startloc, expdest - (char *)stackblock(), quoted);
+	return p;
+}
+
+
+/*
+ * Put a string on the stack.
+ */
+
+static size_t memtodest(const char *p, size_t len, int flags)
+{
+	const char *syntax = flags & EXP_QUOTED ? DQSYNTAX : BASESYNTAX;
+	char *q;
+	char *s;
+
+	if (unlikely(!len))
+		return 0;
+
+	q = makestrspace(len * 2, expdest);
+	s = q;
+
+	do {
+		int c = (signed char)*p++;
+		if (c) {
+			if ((flags & QUOTES_ESC) &&
+			    ((syntax[c] == CCTL) ||
+			     (flags & EXP_QUOTED && syntax[c] == CBACK)))
+				USTPUTC(CTLESC, q);
+		} else if (!(flags & EXP_KEEPNUL))
+			continue;
+		USTPUTC(c, q);
+	} while (--len);
+
+	expdest = q;
+	return q - s;
+}
+
+
+static size_t strtodest(const char *p, int flags)
+{
+	size_t len = strlen(p);
+	memtodest(p, len, flags);
+	return len;
+}
+
+
+
+/*
+ * Add the value of a specialized variable to the stack string.
+ */
+
+STATIC ssize_t
+varvalue(char *name, int varflags, int flags, int quoted)
+{
+	int num;
+	char *p;
+	int i;
+	int sep;
+	char sepc;
+	char **ap;
+	int subtype = varflags & VSTYPE;
+	int discard = (subtype == VSPLUS || subtype == VSLENGTH) |
+		      (flags & EXP_DISCARD);
+	ssize_t len = 0;
+	char c;
+
+	if (!subtype) {
+		if (discard)
+			return -1;
+
+		sh_error("Bad substitution");
+	}
+
+	flags |= EXP_KEEPNUL;
+	flags &= discard ? ~QUOTES_ESC : ~0;
+	sep = (flags & EXP_FULL) << CHAR_BIT;
+
+	switch (*name) {
+	case '$':
+		num = rootpid;
+		goto numvar;
+	case '?':
+		num = exitstatus;
+		goto numvar;
+	case '#':
+		num = shellparam.nparam;
+		goto numvar;
+	case '!':
+		num = backgndpid;
+		if (num == 0)
+			return -1;
+numvar:
+		len = cvtnum(num, flags);
+		break;
+	case '-':
+		p = makestrspace(NOPTS, expdest);
+		for (i = NOPTS - 1; i >= 0; i--) {
+			if (optlist[i] && optletters[i]) {
+				USTPUTC(optletters[i], p);
+				len++;
+			}
+		}
+		expdest = p;
+		break;
+	case '@':
+		if (quoted && sep)
+			goto param;
+		/* fall through */
+	case '*':
+		/* We will set c to 0 or ~0 depending on whether
+		 * we're doing field splitting.  We won't do field
+		 * splitting if either we're quoted or sep is zero.
+		 *
+		 * Instead of testing (quoted || !sep) the following
+		 * trick optimises away any branches by using the
+		 * fact that EXP_QUOTED (which is the only bit that
+		 * can be set in quoted) is the same as EXP_FULL <<
+		 * CHAR_BIT (which is the only bit that can be set
+		 * in sep).
+		 */
+#if EXP_QUOTED >> CHAR_BIT != EXP_FULL
+#error The following two lines expect EXP_QUOTED == EXP_FULL << CHAR_BIT
+#endif
+		c = !((quoted | ~sep) & EXP_QUOTED) - 1;
+		sep &= ~quoted;
+		sep |= ifsset() ? (unsigned char)(c & ifsval()[0]) : ' ';
+param:
+		sepc = sep;
+		if (!(ap = shellparam.p))
+			return -1;
+		while ((p = *ap++)) {
+			len += strtodest(p, flags);
+
+			if (*ap && sep) {
+				len++;
+				memtodest(&sepc, 1, flags);
+			}
+		}
+		break;
+	case '0':
+	case '1':
+	case '2':
+	case '3':
+	case '4':
+	case '5':
+	case '6':
+	case '7':
+	case '8':
+	case '9':
+		num = atoi(name);
+		if (num < 0 || num > shellparam.nparam)
+			return -1;
+		p = num ? shellparam.p[num - 1] : arg0;
+		goto value;
+	default:
+		p = lookupvar(name);
+value:
+		if (!p)
+			return -1;
+
+		len = strtodest(p, flags);
+		break;
+	}
+
+	if (discard)
+		STADJUST(-len, expdest);
+
+	return len;
+}
+
+
+
+/*
+ * Record the fact that we have to scan this region of the
+ * string for IFS characters.
+ */
+
+void
+recordregion(int start, int end, int nulonly)
+{
+	struct ifsregion *ifsp;
+
+	if (ifslastp == NULL) {
+		ifsp = &ifsfirst;
+	} else {
+		INTOFF;
+		ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion));
+		ifsp->next = NULL;
+		ifslastp->next = ifsp;
+		INTON;
+	}
+	ifslastp = ifsp;
+	ifslastp->begoff = start;
+	ifslastp->endoff = end;
+	ifslastp->nulonly = nulonly;
+}
+
+
+
+/*
+ * Break the argument string into pieces based upon IFS and add the
+ * strings to the argument list.  The regions of the string to be
+ * searched for IFS characters have been stored by recordregion.
+ * If maxargs is non-negative, at most maxargs arguments will be created, by
+ * joining together the last arguments.
+ */
+void
+ifsbreakup(char *string, int maxargs, struct arglist *arglist)
+{
+	struct ifsregion *ifsp;
+	struct strlist *sp;
+	char *start;
+	char *p;
+	char *q;
+	char *r = NULL;
+	const char *ifs, *realifs;
+	int ifsspc;
+	int nulonly;
+
+
+	start = string;
+	if (ifslastp != NULL) {
+		ifsspc = 0;
+		nulonly = 0;
+		realifs = ifsset() ? ifsval() : defifs;
+		ifsp = &ifsfirst;
+		do {
+			int afternul;
+
+			p = string + ifsp->begoff;
+			afternul = nulonly;
+			nulonly = ifsp->nulonly;
+			ifs = nulonly ? nullstr : realifs;
+			ifsspc = 0;
+			while (p < string + ifsp->endoff) {
+				int c;
+				bool isifs;
+				bool isdefifs;
+
+				q = p;
+				c = *p++;
+				if (c == (char)CTLESC)
+					c = *p++;
+
+				isifs = strchr(ifs, c);
+				isdefifs = false;
+				if (isifs)
+					isdefifs = strchr(defifs, c);
+
+				/* If only reading one more argument:
+				 * If we have exactly one field,
+				 * read that field without its terminator.
+				 * If we have more than one field,
+				 * read all fields including their terminators,
+				 * except for trailing IFS whitespace.
+				 *
+				 * This means that if we have only IFS
+				 * characters left, and at most one
+				 * of them is non-whitespace, we stop
+				 * reading here.
+				 * Otherwise, we read all the remaining
+				 * characters except for trailing
+				 * IFS whitespace.
+				 *
+				 * In any case, r indicates the start
+				 * of the characters to remove, or NULL
+				 * if no characters should be removed.
+				 */
+				if (!maxargs) {
+					if (isdefifs) {
+						if (!r)
+							r = q;
+						continue;
+					}
+
+					if (!(isifs && ifsspc))
+						r = NULL;
+
+					ifsspc = 0;
+					continue;
+				}
+
+				if (ifsspc) {
+					if (isifs)
+						q = p;
+
+					start = q;
+
+					if (isdefifs)
+						continue;
+
+					isifs = false;
+				}
+
+				if (isifs) {
+					if (!(afternul || nulonly))
+						ifsspc = isdefifs;
+					/* Ignore IFS whitespace at start */
+					if (q == start && ifsspc) {
+						start = p;
+						ifsspc = 0;
+						continue;
+					}
+					if (maxargs > 0 && !--maxargs) {
+						r = q;
+						continue;
+					}
+					*q = '\0';
+					sp = (struct strlist *)stalloc(sizeof *sp);
+					sp->text = start;
+					*arglist->lastp = sp;
+					arglist->lastp = &sp->next;
+					start = p;
+					continue;
+				}
+
+				ifsspc = 0;
+			}
+		} while ((ifsp = ifsp->next) != NULL);
+		if (nulonly)
+			goto add;
+	}
+
+	if (r)
+		*r = '\0';
+
+	if (!*start)
+		return;
+
+add:
+	sp = (struct strlist *)stalloc(sizeof *sp);
+	sp->text = start;
+	*arglist->lastp = sp;
+	arglist->lastp = &sp->next;
+}
+
+void ifsfree(void)
+{
+	struct ifsregion *p = ifsfirst.next;
+
+	if (!p)
+		goto out;
+
+	INTOFF;
+	do {
+		struct ifsregion *ifsp;
+		ifsp = p->next;
+		ckfree(p);
+		p = ifsp;
+	} while (p);
+	ifsfirst.next = NULL;
+	INTON;
+
+out:
+	ifslastp = NULL;
+}
+
+
+
+/*
+ * Expand shell metacharacters.  At this point, the only control characters
+ * should be escapes.  The results are stored in the list exparg.
+ */
+
+#ifdef HAVE_GLOB
+STATIC void
+expandmeta(struct strlist *str)
+{
+	/* TODO - EXP_REDIR */
+
+	while (str) {
+		const char *p;
+		glob_t pglob;
+		int i;
+
+		if (fflag)
+			goto nometa;
+		INTOFF;
+		p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
+		i = glob(p, GLOB_NOMAGIC, 0, &pglob);
+		if (p != str->text)
+			ckfree(p);
+		switch (i) {
+		case 0:
+			if ((pglob.gl_flags & (GLOB_NOMAGIC | GLOB_NOCHECK)) ==
+			    (GLOB_NOMAGIC | GLOB_NOCHECK))
+				goto nometa2;
+			addglob(&pglob);
+			globfree(&pglob);
+			INTON;
+			break;
+		case GLOB_NOMATCH:
+nometa2:
+			globfree(&pglob);
+			INTON;
+nometa:
+			*exparg.lastp = str;
+			rmescapes(str->text);
+			exparg.lastp = &str->next;
+			break;
+		default:	/* GLOB_NOSPACE */
+			sh_error("Out of space");
+		}
+		str = str->next;
+	}
+}
+
+
+/*
+ * Add the result of glob(3) to the list.
+ */
+
+STATIC void
+addglob(pglob)
+	const glob_t *pglob;
+{
+	char **p = pglob->gl_pathv;
+
+	do {
+		addfname(*p);
+	} while (*++p);
+}
+
+
+#else	/* HAVE_GLOB */
+STATIC char *expdir;
+STATIC unsigned expdir_max;
+
+
+STATIC void
+expandmeta(struct strlist *str)
+{
+	static const char metachars[] = {
+		'*', '?', '[', 0
+	};
+	/* TODO - EXP_REDIR */
+
+	while (str) {
+		struct strlist **savelastp;
+		struct strlist *sp;
+		char *p;
+		unsigned len;
+
+		if (fflag)
+			goto nometa;
+		if (!strpbrk(str->text, metachars))
+			goto nometa;
+		savelastp = exparg.lastp;
+
+		INTOFF;
+		p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
+		len = strlen(p);
+		expdir_max = len + PATH_MAX;
+		expdir = ckmalloc(expdir_max);
+
+		expmeta(p, len, 0);
+		ckfree(expdir);
+		if (p != str->text)
+			ckfree(p);
+		INTON;
+		if (exparg.lastp == savelastp) {
+			/*
+			 * no matches
+			 */
+nometa:
+			*exparg.lastp = str;
+			rmescapes(str->text);
+			exparg.lastp = &str->next;
+		} else {
+			*exparg.lastp = NULL;
+			*savelastp = sp = expsort(*savelastp);
+			while (sp->next != NULL)
+				sp = sp->next;
+			exparg.lastp = &sp->next;
+		}
+		str = str->next;
+	}
+}
+
+
+/*
+ * Do metacharacter (i.e. *, ?, [...]) expansion.
+ */
+
+STATIC void
+expmeta(char *name, unsigned name_len, unsigned expdir_len)
+{
+	char *enddir = expdir + expdir_len;
+	char *p;
+	const char *cp;
+	char *start;
+	char *endname;
+	int metaflag;
+	struct stat64 statb;
+	DIR *dirp;
+	struct dirent64 *dp;
+	int atend;
+	int matchdot;
+	int esc;
+
+	metaflag = 0;
+	start = name;
+	for (p = name; esc = 0, *p; p += esc + 1) {
+		if (*p == '*' || *p == '?')
+			metaflag = 1;
+		else if (*p == '[') {
+			char *q = p + 1;
+			if (*q == '!')
+				q++;
+			for (;;) {
+				if (*q == '\\')
+					q++;
+				if (*q == '/' || *q == '\0')
+					break;
+				if (*++q == ']') {
+					metaflag = 1;
+					break;
+				}
+			}
+		} else {
+			if (*p == '\\' && p[1])
+				esc++;
+			if (p[esc] == '/') {
+				if (metaflag)
+					break;
+				start = p + esc + 1;
+			}
+		}
+	}
+	if (metaflag == 0) {	/* we've reached the end of the file name */
+		if (!expdir_len)
+			return;
+		p = name;
+		do {
+			if (*p == '\\' && p[1])
+				p++;
+			*enddir++ = *p;
+		} while (*p++);
+		if (lstat64(expdir, &statb) >= 0)
+			addfname(expdir);
+		return;
+	}
+	endname = p;
+	if (name < start) {
+		p = name;
+		do {
+			if (*p == '\\' && p[1])
+				p++;
+			*enddir++ = *p++;
+		} while (p < start);
+	}
+	*enddir = 0;
+	cp = expdir;
+	expdir_len = enddir - cp;
+	if (!expdir_len)
+		cp = ".";
+	if ((dirp = opendir(cp)) == NULL)
+		return;
+	if (*endname == 0) {
+		atend = 1;
+	} else {
+		atend = 0;
+		*endname = '\0';
+		endname += esc + 1;
+	}
+	name_len -= endname - name;
+	matchdot = 0;
+	p = start;
+	if (*p == '\\')
+		p++;
+	if (*p == '.')
+		matchdot++;
+	while (! int_pending() && (dp = readdir64(dirp)) != NULL) {
+		if (dp->d_name[0] == '.' && ! matchdot)
+			continue;
+		if (pmatch(start, dp->d_name)) {
+			if (atend) {
+				scopy(dp->d_name, enddir);
+				addfname(expdir);
+			} else {
+				unsigned offset;
+				unsigned len;
+
+				p = stpcpy(enddir, dp->d_name);
+				*p = '/';
+
+				offset = p - expdir + 1;
+				len = offset + name_len + NAME_MAX;
+				if (len > expdir_max) {
+					len += PATH_MAX;
+					expdir = ckrealloc(expdir, len);
+					expdir_max = len;
+				}
+
+				expmeta(endname, name_len, offset);
+				enddir = expdir + expdir_len;
+			}
+		}
+	}
+	closedir(dirp);
+	if (! atend)
+		endname[-esc - 1] = esc ? '\\' : '/';
+}
+#endif	/* HAVE_GLOB */
+
+
+/*
+ * Add a file name to the list.
+ */
+
+STATIC void
+addfname(char *name)
+{
+	struct strlist *sp;
+
+	sp = (struct strlist *)stalloc(sizeof *sp);
+	sp->text = sstrdup(name);
+	*exparg.lastp = sp;
+	exparg.lastp = &sp->next;
+}
+
+
+#ifndef HAVE_GLOB
+/*
+ * Sort the results of file name expansion.  It calculates the number of
+ * strings to sort and then calls msort (short for merge sort) to do the
+ * work.
+ */
+
+STATIC struct strlist *
+expsort(struct strlist *str)
+{
+	int len;
+	struct strlist *sp;
+
+	len = 0;
+	for (sp = str ; sp ; sp = sp->next)
+		len++;
+	return msort(str, len);
+}
+
+
+STATIC struct strlist *
+msort(struct strlist *list, int len)
+{
+	struct strlist *p, *q = NULL;
+	struct strlist **lpp;
+	int half;
+	int n;
+
+	if (len <= 1)
+		return list;
+	half = len >> 1;
+	p = list;
+	for (n = half ; --n >= 0 ; ) {
+		q = p;
+		p = p->next;
+	}
+	q->next = NULL;			/* terminate first half of list */
+	q = msort(list, half);		/* sort first half of list */
+	p = msort(p, len - half);		/* sort second half */
+	lpp = &list;
+	for (;;) {
+		if (strcmp(p->text, q->text) < 0) {
+			*lpp = p;
+			lpp = &p->next;
+			if ((p = *lpp) == NULL) {
+				*lpp = q;
+				break;
+			}
+		} else {
+			*lpp = q;
+			lpp = &q->next;
+			if ((q = *lpp) == NULL) {
+				*lpp = p;
+				break;
+			}
+		}
+	}
+	return list;
+}
+#endif
+
+
+/*
+ * Returns true if the pattern matches the string.
+ */
+
+STATIC inline int
+patmatch(char *pattern, const char *string)
+{
+	return pmatch(preglob(pattern, 0), string);
+}
+
+
+#ifndef HAVE_FNMATCH
+STATIC int ccmatch(const char *p, int chr, const char **r)
+{
+	static const struct class {
+		char name[10];
+		int (*fn)(int);
+	} classes[] = {
+		{ .name = ":alnum:]", .fn = isalnum },
+		{ .name = ":cntrl:]", .fn = iscntrl },
+		{ .name = ":lower:]", .fn = islower },
+		{ .name = ":space:]", .fn = isspace },
+		{ .name = ":alpha:]", .fn = isalpha },
+		{ .name = ":digit:]", .fn = isdigit },
+		{ .name = ":print:]", .fn = isprint },
+		{ .name = ":upper:]", .fn = isupper },
+		{ .name = ":blank:]", .fn = isblank },
+		{ .name = ":graph:]", .fn = isgraph },
+		{ .name = ":punct:]", .fn = ispunct },
+		{ .name = ":xdigit:]", .fn = isxdigit },
+	};
+	const struct class *class, *end;
+
+	end = classes + sizeof(classes) / sizeof(classes[0]);
+	for (class = classes; class < end; class++) {
+		const char *q;
+
+		q = prefix(p, class->name);
+		if (!q)
+			continue;
+		*r = q;
+		return class->fn(chr);
+	}
+
+	*r = 0;
+	return 0;
+}
+
+STATIC int
+pmatch(const char *pattern, const char *string)
+{
+	const char *p, *q;
+	char c;
+
+	p = pattern;
+	q = string;
+	for (;;) {
+		switch (c = *p++) {
+		case '\0':
+			goto breakloop;
+		case '\\':
+			if (*p) {
+				c = *p++;
+			}
+			goto dft;
+		case '?':
+			if (*q++ == '\0')
+				return 0;
+			break;
+		case '*':
+			c = *p;
+			while (c == '*')
+				c = *++p;
+			if (c != '\\' && c != '?' && c != '*' && c != '[') {
+				while (*q != c) {
+					if (*q == '\0')
+						return 0;
+					q++;
+				}
+			}
+			do {
+				if (pmatch(p, q))
+					return 1;
+			} while (*q++ != '\0');
+			return 0;
+		case '[': {
+			const char *startp;
+			int invert, found;
+			char chr;
+
+			startp = p;
+			invert = 0;
+			if (*p == '!') {
+				invert++;
+				p++;
+			}
+			found = 0;
+			chr = *q;
+			if (chr == '\0')
+				return 0;
+			c = *p++;
+			do {
+				if (!c) {
+					p = startp;
+					c = '[';
+					goto dft;
+				}
+				if (c == '[') {
+					const char *r;
+
+					found |= !!ccmatch(p, chr, &r);
+					if (r) {
+						p = r;
+						continue;
+					}
+				} else if (c == '\\')
+					c = *p++;
+				if (*p == '-' && p[1] != ']') {
+					p++;
+					if (*p == '\\')
+						p++;
+					if (chr >= c && chr <= *p)
+						found = 1;
+					p++;
+				} else {
+					if (chr == c)
+						found = 1;
+				}
+			} while ((c = *p++) != ']');
+			if (found == invert)
+				return 0;
+			q++;
+			break;
+		}
+dft:	        default:
+			if (*q++ != c)
+				return 0;
+			break;
+		}
+	}
+breakloop:
+	if (*q != '\0')
+		return 0;
+	return 1;
+}
+#endif
+
+
+
+/*
+ * Remove any CTLESC characters from a string.
+ */
+
+char *
+_rmescapes(char *str, int flag)
+{
+	char *p, *q, *r;
+	int notescaped;
+	int globbing;
+
+	p = strpbrk(str, qchars);
+	if (!p) {
+		return str;
+	}
+	q = p;
+	r = str;
+	if (flag & RMESCAPE_ALLOC) {
+		size_t len = p - str;
+		size_t fulllen = len + strlen(p) + 1;
+
+		if (flag & RMESCAPE_GROW) {
+			int strloc = str - (char *)stackblock();
+
+			r = makestrspace(fulllen, expdest);
+			str = (char *)stackblock() + strloc;
+			p = str + len;
+		} else if (flag & RMESCAPE_HEAP) {
+			r = ckmalloc(fulllen);
+		} else {
+			r = stalloc(fulllen);
+		}
+		q = r;
+		if (len > 0) {
+			q = mempcpy(q, str, len);
+		}
+	}
+	globbing = flag & RMESCAPE_GLOB;
+	notescaped = globbing;
+	while (*p) {
+		if (*p == (char)CTLQUOTEMARK) {
+			p++;
+			notescaped = globbing;
+			continue;
+		}
+		if (*p == '\\') {
+			/* naked back slash */
+			notescaped = 0;
+			goto copy;
+		}
+		if (*p == (char)CTLESC) {
+			p++;
+			if (notescaped)
+				*q++ = '\\';
+		}
+		notescaped = globbing;
+copy:
+		*q++ = *p++;
+	}
+	*q = '\0';
+	if (flag & RMESCAPE_GROW) {
+		expdest = r;
+		STADJUST(q - r + 1, expdest);
+	}
+	return r;
+}
+
+
+
+/*
+ * See if a pattern matches in a case statement.
+ */
+
+int
+casematch(union node *pattern, char *val)
+{
+	struct stackmark smark;
+	int result;
+
+	setstackmark(&smark);
+	argbackq = pattern->narg.backquote;
+	STARTSTACKSTR(expdest);
+	argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
+	ifsfree();
+	result = patmatch(stackblock(), val);
+	popstackmark(&smark);
+	return result;
+}
+
+/*
+ * Our own itoa().
+ */
+
+static size_t cvtnum(intmax_t num, int flags)
+{
+	int len = max_int_length(sizeof(num));
+	char buf[len];
+
+	len = fmtstr(buf, len, "%" PRIdMAX, num);
+	return memtodest(buf, len, flags);
+}
+
+STATIC void
+varunset(const char *end, const char *var, const char *umsg, int varflags)
+{
+	const char *msg;
+	const char *tail;
+
+	tail = nullstr;
+	msg = "parameter not set";
+	if (umsg) {
+		if (*end == (char)CTLENDVAR) {
+			if (varflags & VSNUL)
+				tail = " or null";
+		} else
+			msg = umsg;
+	}
+	sh_error("%.*s: %s%s", end - var - 1, var, msg, tail);
+}
+
+#ifdef mkinit
+
+INCLUDE "expand.h"
+
+EXITRESET {
+	ifsfree();
+}
+
+#endif
diff --git a/bin/dash/src/expand.h b/bin/dash/src/expand.h
new file mode 100644
index 00000000..c44b8481
--- /dev/null
+++ b/bin/dash/src/expand.h
@@ -0,0 +1,83 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)expand.h	8.2 (Berkeley) 5/4/95
+ */
+
+#include <inttypes.h>
+
+struct strlist {
+	struct strlist *next;
+	char *text;
+};
+
+
+struct arglist {
+	struct strlist *list;
+	struct strlist **lastp;
+};
+
+/*
+ * expandarg() flags
+ */
+#define EXP_FULL	0x1	/* perform word splitting & file globbing */
+#define EXP_TILDE	0x2	/* do normal tilde expansion */
+#define	EXP_VARTILDE	0x4	/* expand tildes in an assignment */
+#define	EXP_REDIR	0x8	/* file glob for a redirection (1 match only) */
+#define EXP_CASE	0x10	/* keeps quotes around for CASE pattern */
+#define EXP_VARTILDE2	0x40	/* expand tildes after colons only */
+#define EXP_WORD	0x80	/* expand word in parameter expansion */
+#define EXP_QUOTED	0x100	/* expand word in double quotes */
+#define EXP_KEEPNUL	0x200	/* do not skip NUL characters */
+#define EXP_DISCARD	0x400	/* discard result of expansion */
+
+
+union node;
+void expandarg(union node *, struct arglist *, int);
+#define rmescapes(p) _rmescapes((p), 0)
+char *_rmescapes(char *, int);
+int casematch(union node *, char *);
+void recordregion(int, int, int);
+void removerecordregions(int); 
+void ifsbreakup(char *, int, struct arglist *);
+void ifsfree(void);
+
+/* From arith.y */
+intmax_t arith(const char *);
+int expcmd(int , char **);
+#ifdef USE_LEX
+void arith_lex_reset(void);
+#else
+#define arith_lex_reset()
+#endif
+int yylex(void);
diff --git a/bin/dash/src/funcs/cmv b/bin/dash/src/funcs/cmv
new file mode 100644
index 00000000..91a67c53
--- /dev/null
+++ b/bin/dash/src/funcs/cmv
@@ -0,0 +1,47 @@
+# Copyright (c) 1991, 1993
+#	The Regents of the University of California.  All rights reserved.
+# Copyright (c) 1997-2005
+#	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#	@(#)cmv	8.2 (Berkeley) 5/4/95
+
+# Conditional move--don't replace an existing file.
+
+cmv() {
+	if test $# != 2
+	then	echo "cmv: arg count"
+		return 2
+	fi
+	if test -f "$2" -o -w "$2"
+	then	echo "$2 exists"
+		return 2
+	fi
+	/bin/mv "$1" "$2"
+}
diff --git a/bin/dash/src/funcs/dirs b/bin/dash/src/funcs/dirs
new file mode 100644
index 00000000..5f6ce635
--- /dev/null
+++ b/bin/dash/src/funcs/dirs
@@ -0,0 +1,71 @@
+# Copyright (c) 1991, 1993
+#	The Regents of the University of California.  All rights reserved.
+# Copyright (c) 1997-2005
+#	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#	@(#)dirs	8.2 (Berkeley) 5/4/95
+
+# pushd, popd, and dirs --- written by Chris Bertin
+# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris
+# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW
+
+pushd () {
+	SAVE=`pwd`
+	if [ "$1" = "" ] 
+	then	if [ "$DSTACK" = "" ]
+		then	echo "pushd: directory stack empty."
+			return 1
+		fi
+		set $DSTACK
+		cd $1 || return
+		shift 1
+		DSTACK="$*"
+	else	cd $1 > /dev/null || return
+	fi
+	DSTACK="$SAVE $DSTACK"
+	dirs
+}
+
+popd () {
+	if [ "$DSTACK" = "" ] 
+	then	echo "popd: directory stack empty."
+		return 1
+	fi
+	set $DSTACK
+	cd $1
+	shift
+	DSTACK=$*
+	dirs
+}
+
+dirs () {
+	echo "`pwd` $DSTACK"
+	return 0
+}
diff --git a/bin/dash/src/funcs/kill b/bin/dash/src/funcs/kill
new file mode 100644
index 00000000..c5df95f5
--- /dev/null
+++ b/bin/dash/src/funcs/kill
@@ -0,0 +1,47 @@
+# Copyright (c) 1991, 1993
+#	The Regents of the University of California.  All rights reserved.
+# Copyright (c) 1997-2005
+#	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#	@(#)kill	8.2 (Berkeley) 5/4/95
+
+# Convert job names to process ids and then run /bin/kill.
+
+kill() {
+	local args x
+	args=
+	for x in "$@"
+	do	case $x in
+		%*)	x=`jobid "$x"` ;;
+		esac
+		args="$args $x"
+	done
+	/bin/kill $args
+}
diff --git a/bin/dash/src/funcs/login b/bin/dash/src/funcs/login
new file mode 100644
index 00000000..215e5352
--- /dev/null
+++ b/bin/dash/src/funcs/login
@@ -0,0 +1,36 @@
+# Copyright (c) 1991, 1993
+#	The Regents of the University of California.  All rights reserved.
+# Copyright (c) 1997-2005
+#	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#	@(#)login	8.2 (Berkeley) 5/4/95
+
+# replaces the login builtin in the BSD shell
+login () exec login "$@"
diff --git a/bin/dash/src/funcs/newgrp b/bin/dash/src/funcs/newgrp
new file mode 100644
index 00000000..ec0e7e5a
--- /dev/null
+++ b/bin/dash/src/funcs/newgrp
@@ -0,0 +1,35 @@
+# Copyright (c) 1991, 1993
+#	The Regents of the University of California.  All rights reserved.
+# Copyright (c) 1997-2005
+#	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#	@(#)newgrp	8.2 (Berkeley) 5/4/95
+
+newgrp() exec newgrp "$@"
diff --git a/bin/dash/src/funcs/popd b/bin/dash/src/funcs/popd
new file mode 100644
index 00000000..7bccf50d
--- /dev/null
+++ b/bin/dash/src/funcs/popd
@@ -0,0 +1,71 @@
+# Copyright (c) 1991, 1993
+#	The Regents of the University of California.  All rights reserved.
+# Copyright (c) 1997-2005
+#	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#	@(#)popd	8.2 (Berkeley) 5/4/95
+
+# pushd, popd, and dirs --- written by Chris Bertin
+# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris
+# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW
+
+pushd () {
+	SAVE=`pwd`
+	if [ "$1" = "" ] 
+	then	if [ "$DSTACK" = "" ]
+		then	echo "pushd: directory stack empty."
+			return 1
+		fi
+		set $DSTACK
+		cd $1 || return
+		shift 1
+		DSTACK="$*"
+	else	cd $1 > /dev/null || return
+	fi
+	DSTACK="$SAVE $DSTACK"
+	dirs
+}
+
+popd () {
+	if [ "$DSTACK" = "" ] 
+	then	echo "popd: directory stack empty."
+		return 1
+	fi
+	set $DSTACK
+	cd $1
+	shift
+	DSTACK=$*
+	dirs
+}
+
+dirs () {
+	echo "`pwd` $DSTACK"
+	return 0
+}
diff --git a/bin/dash/src/funcs/pushd b/bin/dash/src/funcs/pushd
new file mode 100644
index 00000000..19ac8e08
--- /dev/null
+++ b/bin/dash/src/funcs/pushd
@@ -0,0 +1,71 @@
+# Copyright (c) 1991, 1993
+#	The Regents of the University of California.  All rights reserved.
+# Copyright (c) 1997-2005
+#	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#	@(#)pushd	8.2 (Berkeley) 5/4/95
+
+# pushd, popd, and dirs --- written by Chris Bertin
+# Pixel Computer Inc. ...!wjh12!pixel!pixutl!chris
+# as modified by Patrick Elam of GTRI and Kenneth Almquist at UW
+
+pushd () {
+	SAVE=`pwd`
+	if [ "$1" = "" ] 
+	then	if [ "$DSTACK" = "" ]
+		then	echo "pushd: directory stack empty."
+			return 1
+		fi
+		set $DSTACK
+		cd $1 || return
+		shift 1
+		DSTACK="$*"
+	else	cd $1 > /dev/null || return
+	fi
+	DSTACK="$SAVE $DSTACK"
+	dirs
+}
+
+popd () {
+	if [ "$DSTACK" = "" ] 
+	then	echo "popd: directory stack empty."
+		return 1
+	fi
+	set $DSTACK
+	cd $1
+	shift
+	DSTACK=$*
+	dirs
+}
+
+dirs () {
+	echo "`pwd` $DSTACK"
+	return 0
+}
diff --git a/bin/dash/src/funcs/suspend b/bin/dash/src/funcs/suspend
new file mode 100644
index 00000000..44844678
--- /dev/null
+++ b/bin/dash/src/funcs/suspend
@@ -0,0 +1,39 @@
+# Copyright (c) 1991, 1993
+#	The Regents of the University of California.  All rights reserved.
+# Copyright (c) 1997-2005
+#	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#	@(#)suspend	8.2 (Berkeley) 5/4/95
+
+suspend() {
+	local -
+	set +j
+	kill -TSTP 0
+}
diff --git a/bin/dash/src/histedit.c b/bin/dash/src/histedit.c
new file mode 100644
index 00000000..58163314
--- /dev/null
+++ b/bin/dash/src/histedit.c
@@ -0,0 +1,503 @@
+/*-
+ * Copyright (c) 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <getopt.h>
+/*
+ * Editline and history functions (and glue).
+ */
+#include "shell.h"
+#include "parser.h"
+#include "var.h"
+#include "options.h"
+#include "main.h"
+#include "output.h"
+#include "mystring.h"
+#include "error.h"
+#ifndef SMALL
+#include "myhistedit.h"
+#include "eval.h"
+#include "memalloc.h"
+
+#define MAXHISTLOOPS	4	/* max recursions through fc */
+#define DEFEDITOR	"ed"	/* default editor *should* be $EDITOR */
+
+History *hist;	/* history cookie */
+EditLine *el;	/* editline cookie */
+int displayhist;
+static FILE *el_in, *el_out;
+
+STATIC const char *fc_replace(const char *, char *, char *);
+
+#ifdef DEBUG
+extern FILE *tracefile;
+#endif
+
+/*
+ * Set history and editing status.  Called whenever the status may
+ * have changed (figures out what to do).
+ */
+void
+histedit(void)
+{
+	FILE *el_err;
+
+#define editing (Eflag || Vflag)
+
+	if (iflag) {
+		if (!hist) {
+			/*
+			 * turn history on
+			 */
+			INTOFF;
+			hist = history_init();
+			INTON;
+
+			if (hist != NULL)
+				sethistsize(histsizeval());
+			else
+				out2str("sh: can't initialize history\n");
+		}
+		if (editing && !el && isatty(0)) { /* && isatty(2) ??? */
+			/*
+			 * turn editing on
+			 */
+			INTOFF;
+			if (el_in == NULL)
+				el_in = fdopen(0, "r");
+			if (el_out == NULL)
+				el_out = fdopen(2, "w");
+			if (el_in == NULL || el_out == NULL)
+				goto bad;
+			el_err = el_out;
+#if DEBUG
+			if (tracefile)
+				el_err = tracefile;
+#endif
+			el = el_init(arg0, el_in, el_out, el_err);
+			if (el != NULL) {
+				if (hist)
+					el_set(el, EL_HIST, history, hist);
+				el_set(el, EL_PROMPT, getprompt);
+				el_set(el, EL_RPROMPT, getrprompt);
+#ifdef HAVE__EL_FN_SH_COMPLETE
+				el_set(el, EL_ADDFN, "sh-complete", "Filename completion",
+					_el_fn_sh_complete);
+#else
+				el_set(el, EL_ADDFN, "sh-complete", "Filename completion",
+					_el_fn_complete);
+#endif
+			} else {
+bad:
+				out2str("sh: can't initialize editing\n");
+			}
+			INTON;
+		} else if (!editing && el) {
+			INTOFF;
+			el_end(el);
+			el = NULL;
+			INTON;
+		}
+		if (el) {
+			if (Vflag)
+				el_set(el, EL_EDITOR, "vi");
+			else if (Eflag)
+				el_set(el, EL_EDITOR, "emacs");
+			el_source(el, NULL);
+			el_set(el, EL_BIND, "^I", "sh-complete", NULL);
+		}
+	} else {
+		INTOFF;
+		if (el) {	/* no editing if not interactive */
+			el_end(el);
+			el = NULL;
+		}
+		if (hist) {
+			history_end(hist);
+			hist = NULL;
+		}
+		INTON;
+	}
+}
+
+
+void
+sethistsize(const char *hs)
+{
+	int histsize;
+	HistEvent he;
+
+	if (hist != NULL) {
+		if (hs == NULL || *hs == '\0' ||
+		   (histsize = atoi(hs)) < 0)
+			histsize = 100;
+		history(hist, &he, H_SETSIZE, histsize);
+	}
+}
+
+void
+setterm(const char *term)
+{
+	if (el != NULL && term != NULL)
+		if (el_set(el, EL_TERMINAL, term) != 0) {
+			outfmt(out2, "sh: Can't set terminal type %s\n", term);
+			outfmt(out2, "sh: Using dumb terminal settings.\n");
+		}
+}
+
+/*
+ *  This command is provided since POSIX decided to standardize
+ *  the Korn shell fc command.  Oh well...
+ */
+int
+histcmd(int argc, char **argv)
+{
+	int ch;
+	const char *editor = NULL;
+	HistEvent he;
+	int lflg = 0, nflg = 0, rflg = 0, sflg = 0;
+	int i, retval;
+	const char *firststr, *laststr;
+	int first, last, direction;
+	char *pat = NULL, *repl;	/* ksh "fc old=new" crap */
+	static int active = 0;
+	struct jmploc jmploc;
+	struct jmploc *volatile savehandler;
+	char editfile[MAXPATHLEN + 1];
+	FILE *efp;
+#ifdef __GNUC__
+	/* Avoid longjmp clobbering */
+	(void) &editor;
+	(void) &lflg;
+	(void) &nflg;
+	(void) &rflg;
+	(void) &sflg;
+	(void) &firststr;
+	(void) &laststr;
+	(void) &pat;
+	(void) &repl;
+	(void) &efp;
+	(void) &argc;
+	(void) &argv;
+#endif
+
+	if (hist == NULL)
+		sh_error("history not active");
+
+	if (argc == 1)
+		sh_error("missing history argument");
+
+#ifdef __GLIBC__
+	optind = 0;
+#else
+	optreset = 1; optind = 1; /* initialize getopt */
+#endif
+	while (not_fcnumber(argv[optind]) &&
+	      (ch = getopt(argc, argv, ":e:lnrs")) != -1)
+		switch ((char)ch) {
+		case 'e':
+			editor = optionarg;
+			break;
+		case 'l':
+			lflg = 1;
+			break;
+		case 'n':
+			nflg = 1;
+			break;
+		case 'r':
+			rflg = 1;
+			break;
+		case 's':
+			sflg = 1;
+			break;
+		case ':':
+			sh_error("option -%c expects argument", optopt);
+			/* NOTREACHED */
+		case '?':
+		default:
+			sh_error("unknown option: -%c", optopt);
+			/* NOTREACHED */
+		}
+	argc -= optind, argv += optind;
+
+	/*
+	 * If executing...
+	 */
+	if (lflg == 0 || editor || sflg) {
+		lflg = 0;	/* ignore */
+		editfile[0] = '\0';
+		/*
+		 * Catch interrupts to reset active counter and
+		 * cleanup temp files.
+		 */
+		if (setjmp(jmploc.loc)) {
+			active = 0;
+			if (*editfile)
+				unlink(editfile);
+			handler = savehandler;
+			longjmp(handler->loc, 1);
+		}
+		savehandler = handler;
+		handler = &jmploc;
+		if (++active > MAXHISTLOOPS) {
+			active = 0;
+			displayhist = 0;
+			sh_error("called recursively too many times");
+		}
+		/*
+		 * Set editor.
+		 */
+		if (sflg == 0) {
+			if (editor == NULL &&
+			    (editor = bltinlookup("FCEDIT")) == NULL &&
+			    (editor = bltinlookup("EDITOR")) == NULL)
+				editor = DEFEDITOR;
+			if (editor[0] == '-' && editor[1] == '\0') {
+				sflg = 1;	/* no edit */
+				editor = NULL;
+			}
+		}
+	}
+
+	/*
+	 * If executing, parse [old=new] now
+	 */
+	if (lflg == 0 && argc > 0 &&
+	     ((repl = strchr(argv[0], '=')) != NULL)) {
+		pat = argv[0];
+		*repl++ = '\0';
+		argc--, argv++;
+	}
+	/*
+	 * determine [first] and [last]
+	 */
+	switch (argc) {
+	case 0:
+		firststr = lflg ? "-16" : "-1";
+		laststr = "-1";
+		break;
+	case 1:
+		firststr = argv[0];
+		laststr = lflg ? "-1" : argv[0];
+		break;
+	case 2:
+		firststr = argv[0];
+		laststr = argv[1];
+		break;
+	default:
+		sh_error("too many args");
+		/* NOTREACHED */
+	}
+	/*
+	 * Turn into event numbers.
+	 */
+	first = str_to_event(firststr, 0);
+	last = str_to_event(laststr, 1);
+
+	if (rflg) {
+		i = last;
+		last = first;
+		first = i;
+	}
+	/*
+	 * XXX - this should not depend on the event numbers
+	 * always increasing.  Add sequence numbers or offset
+	 * to the history element in next (diskbased) release.
+	 */
+	direction = first < last ? H_PREV : H_NEXT;
+
+	/*
+	 * If editing, grab a temp file.
+	 */
+	if (editor) {
+		int fd;
+		INTOFF;		/* easier */
+		sprintf(editfile, "%s_shXXXXXX", _PATH_TMP);
+		if ((fd = mkstemp(editfile)) < 0)
+			sh_error("can't create temporary file %s", editfile);
+		if ((efp = fdopen(fd, "w")) == NULL) {
+			close(fd);
+			sh_error("can't allocate stdio buffer for temp");
+		}
+	}
+
+	/*
+	 * Loop through selected history events.  If listing or executing,
+	 * do it now.  Otherwise, put into temp file and call the editor
+	 * after.
+	 *
+	 * The history interface needs rethinking, as the following
+	 * convolutions will demonstrate.
+	 */
+	history(hist, &he, H_FIRST);
+	retval = history(hist, &he, H_NEXT_EVENT, first);
+	for (;retval != -1; retval = history(hist, &he, direction)) {
+		if (lflg) {
+			if (!nflg)
+				out1fmt("%5d ", he.num);
+			out1str(he.str);
+		} else {
+			const char *s = pat ?
+			   fc_replace(he.str, pat, repl) : he.str;
+
+			if (sflg) {
+				if (displayhist) {
+					out2str(s);
+				}
+
+				evalstring(s, 0);
+				if (displayhist && hist) {
+					/*
+					 *  XXX what about recursive and
+					 *  relative histnums.
+					 */
+					history(hist, &he, H_ENTER, s);
+				}
+			} else
+				fputs(s, efp);
+		}
+		/*
+		 * At end?  (if we were to lose last, we'd sure be
+		 * messed up).
+		 */
+		if (he.num == last)
+			break;
+	}
+	if (editor) {
+		char *editcmd;
+
+		fclose(efp);
+		editcmd = stalloc(strlen(editor) + strlen(editfile) + 2);
+		sprintf(editcmd, "%s %s", editor, editfile);
+		/* XXX - should use no JC command */
+		evalstring(editcmd, 0);
+		INTON;
+		readcmdfile(editfile);	/* XXX - should read back - quick tst */
+		unlink(editfile);
+	}
+
+	if (lflg == 0 && active > 0)
+		--active;
+	if (displayhist)
+		displayhist = 0;
+	return 0;
+}
+
+STATIC const char *
+fc_replace(const char *s, char *p, char *r)
+{
+	char *dest;
+	int plen = strlen(p);
+
+	STARTSTACKSTR(dest);
+	while (*s) {
+		if (*s == *p && strncmp(s, p, plen) == 0) {
+			while (*r)
+				STPUTC(*r++, dest);
+			s += plen;
+			*p = '\0';	/* so no more matches */
+		} else
+			STPUTC(*s++, dest);
+	}
+	STACKSTRNUL(dest);
+	dest = grabstackstr(dest);
+
+	return (dest);
+}
+
+int
+not_fcnumber(char *s)
+{
+	if (s == NULL)
+		return 0;
+        if (*s == '-')
+                s++;
+	return (!is_number(s));
+}
+
+int
+str_to_event(const char *str, int last)
+{
+	HistEvent he;
+	const char *s = str;
+	int relative = 0;
+	int i, retval;
+
+	retval = history(hist, &he, H_FIRST);
+	switch (*s) {
+	case '-':
+		relative = 1;
+		/*FALLTHROUGH*/
+	case '+':
+		s++;
+	}
+	if (is_number(s)) {
+		i = atoi(s);
+		if (relative) {
+			while (retval != -1 && i--) {
+				retval = history(hist, &he, H_NEXT);
+			}
+			if (retval == -1)
+				retval = history(hist, &he, H_LAST);
+		} else {
+			retval = history(hist, &he, H_NEXT_EVENT, i);
+			if (retval == -1) {
+				/*
+				 * the notion of first and last is
+				 * backwards to that of the history package
+				 */
+				retval = history(hist, &he,
+						last ? H_FIRST : H_LAST);
+			}
+		}
+		if (retval == -1)
+			sh_error("history number %s not found (internal error)",
+				 str);
+	} else {
+		/*
+		 * pattern
+		 */
+		retval = history(hist, &he, H_PREV_STR, str);
+		if (retval == -1)
+			sh_error("history pattern not found: %s", str);
+	}
+	return (he.num);
+}
+#endif
diff --git a/bin/dash/src/init.h b/bin/dash/src/init.h
new file mode 100644
index 00000000..d56fb28e
--- /dev/null
+++ b/bin/dash/src/init.h
@@ -0,0 +1,40 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)init.h	8.2 (Berkeley) 5/4/95
+ */
+
+void init(void);
+void exitreset(void);
+void forkreset(void);
+void reset(void);
diff --git a/bin/dash/src/input.c b/bin/dash/src/input.c
new file mode 100644
index 00000000..4167bd18
--- /dev/null
+++ b/bin/dash/src/input.c
@@ -0,0 +1,498 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>	/* defines BUFSIZ */
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * This file implements the input routines used by the parser.
+ */
+
+#include "eval.h"
+#include "shell.h"
+#include "redir.h"
+#include "syntax.h"
+#include "input.h"
+#include "output.h"
+#include "options.h"
+#include "memalloc.h"
+#include "error.h"
+#include "alias.h"
+#include "parser.h"
+#include "main.h"
+#ifndef SMALL
+#include "myhistedit.h"
+#endif
+
+#define EOF_NLEFT -99		/* value of parsenleft when EOF pushed back */
+#define IBUFSIZ (BUFSIZ + 1)
+
+
+MKINIT struct parsefile basepf;	/* top level input file */
+MKINIT char basebuf[IBUFSIZ];	/* buffer for top level input file */
+struct parsefile *parsefile = &basepf;	/* current input file */
+int whichprompt;		/* 1 == PS1, 2 == PS2 */
+
+STATIC void pushfile(void);
+static int preadfd(void);
+static void setinputfd(int fd, int push);
+static int preadbuffer(void);
+
+#ifdef mkinit
+INCLUDE <stdio.h>
+INCLUDE <unistd.h>
+INCLUDE "input.h"
+INCLUDE "error.h"
+
+INIT {
+	basepf.nextc = basepf.buf = basebuf;
+	basepf.linno = 1;
+}
+
+RESET {
+	/* clear input buffer */
+	basepf.lleft = basepf.nleft = 0;
+	popallfiles();
+}
+
+FORKRESET {
+	popallfiles();
+	if (parsefile->fd > 0) {
+		close(parsefile->fd);
+		parsefile->fd = 0;
+	}
+}
+#endif
+
+
+/*
+ * Read a character from the script, returning PEOF on end of file.
+ * Nul characters in the input are silently discarded.
+ */
+
+int
+pgetc(void)
+{
+	int c;
+
+	if (parsefile->unget)
+		return parsefile->lastc[--parsefile->unget];
+
+	if (--parsefile->nleft >= 0)
+		c = (signed char)*parsefile->nextc++;
+	else
+		c = preadbuffer();
+
+	parsefile->lastc[1] = parsefile->lastc[0];
+	parsefile->lastc[0] = c;
+
+	return c;
+}
+
+
+/*
+ * Same as pgetc(), but ignores PEOA.
+ */
+
+int
+pgetc2()
+{
+	int c;
+	do {
+		c = pgetc();
+	} while (c == PEOA);
+	return c;
+}
+
+
+static int
+preadfd(void)
+{
+	int nr;
+	char *buf =  parsefile->buf;
+	parsefile->nextc = buf;
+
+retry:
+#ifndef SMALL
+	if (parsefile->fd == 0 && el) {
+		static const char *rl_cp;
+		static int el_len;
+
+		if (rl_cp == NULL)
+			rl_cp = el_gets(el, &el_len);
+		if (rl_cp == NULL)
+			nr = 0;
+		else {
+			nr = el_len;
+			if (nr > IBUFSIZ - 1)
+				nr = IBUFSIZ - 1;
+			memcpy(buf, rl_cp, nr);
+			if (nr != el_len) {
+				el_len -= nr;
+				rl_cp += nr;
+			} else
+				rl_cp = 0;
+		}
+
+	} else
+#endif
+		nr = read(parsefile->fd, buf, IBUFSIZ - 1);
+
+
+	if (nr < 0) {
+		if (errno == EINTR)
+			goto retry;
+		if (parsefile->fd == 0 && errno == EWOULDBLOCK) {
+			int flags = fcntl(0, F_GETFL, 0);
+			if (flags >= 0 && flags & O_NONBLOCK) {
+				flags &=~ O_NONBLOCK;
+				if (fcntl(0, F_SETFL, flags) >= 0) {
+					out2str("sh: turning off NDELAY mode\n");
+					goto retry;
+				}
+			}
+		}
+	}
+	return nr;
+}
+
+/*
+ * Refill the input buffer and return the next input character:
+ *
+ * 1) If a string was pushed back on the input, pop it;
+ * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading
+ *    from a string so we can't refill the buffer, return EOF.
+ * 3) If the is more stuff in this buffer, use it else call read to fill it.
+ * 4) Process input up to the next newline, deleting nul characters.
+ */
+
+static int preadbuffer(void)
+{
+	char *q;
+	int more;
+#ifndef SMALL
+	int something;
+#endif
+	char savec;
+
+	if (unlikely(parsefile->strpush)) {
+		if (
+			parsefile->nleft == -1 &&
+			parsefile->strpush->ap &&
+			parsefile->nextc[-1] != ' ' &&
+			parsefile->nextc[-1] != '\t'
+		) {
+			return PEOA;
+		}
+		popstring();
+		return pgetc();
+	}
+	if (unlikely(parsefile->nleft == EOF_NLEFT ||
+		     parsefile->buf == NULL))
+		return PEOF;
+	flushall();
+
+	more = parsefile->lleft;
+	if (more <= 0) {
+again:
+		if ((more = preadfd()) <= 0) {
+			parsefile->lleft = parsefile->nleft = EOF_NLEFT;
+			return PEOF;
+		}
+	}
+
+	q = parsefile->nextc;
+
+	/* delete nul characters */
+#ifndef SMALL
+	something = 0;
+#endif
+	for (;;) {
+		int c;
+
+		more--;
+		c = *q;
+
+		if (!c)
+			memmove(q, q + 1, more);
+		else {
+			q++;
+
+			if (c == '\n') {
+				parsefile->nleft = q - parsefile->nextc - 1;
+				break;
+			}
+
+#ifndef SMALL
+			switch (c) {
+			default:
+				something = 1;
+				/* fall through */
+			case '\t':
+			case ' ':
+				break;
+			}
+#endif
+		}
+
+		if (more <= 0) {
+			parsefile->nleft = q - parsefile->nextc - 1;
+			if (parsefile->nleft < 0)
+				goto again;
+			break;
+		}
+	}
+	parsefile->lleft = more;
+
+	savec = *q;
+	*q = '\0';
+
+#ifndef SMALL
+	if (parsefile->fd == 0 && hist && something) {
+		HistEvent he;
+		INTOFF;
+		history(hist, &he, whichprompt == 1? H_ENTER : H_APPEND,
+			parsefile->nextc);
+		INTON;
+	}
+#endif
+
+	if (vflag) {
+		out2str(parsefile->nextc);
+#ifdef FLUSHERR
+		flushout(out2);
+#endif
+	}
+
+	*q = savec;
+
+	return (signed char)*parsefile->nextc++;
+}
+
+/*
+ * Undo a call to pgetc.  Only two characters may be pushed back.
+ * PEOF may be pushed back.
+ */
+
+void
+pungetc(void)
+{
+	parsefile->unget++;
+}
+
+/*
+ * Push a string back onto the input at this current parsefile level.
+ * We handle aliases this way.
+ */
+void
+pushstring(char *s, void *ap)
+{
+	struct strpush *sp;
+	size_t len;
+
+	len = strlen(s);
+	INTOFF;
+/*dprintf("*** calling pushstring: %s, %d\n", s, len);*/
+	if (parsefile->strpush) {
+		sp = ckmalloc(sizeof (struct strpush));
+		sp->prev = parsefile->strpush;
+		parsefile->strpush = sp;
+	} else
+		sp = parsefile->strpush = &(parsefile->basestrpush);
+	sp->prevstring = parsefile->nextc;
+	sp->prevnleft = parsefile->nleft;
+	sp->unget = parsefile->unget;
+	memcpy(sp->lastc, parsefile->lastc, sizeof(sp->lastc));
+	sp->ap = (struct alias *)ap;
+	if (ap) {
+		((struct alias *)ap)->flag |= ALIASINUSE;
+		sp->string = s;
+	}
+	parsefile->nextc = s;
+	parsefile->nleft = len;
+	parsefile->unget = 0;
+	INTON;
+}
+
+void
+popstring(void)
+{
+	struct strpush *sp = parsefile->strpush;
+
+	INTOFF;
+	if (sp->ap) {
+		if (parsefile->nextc[-1] == ' ' ||
+		    parsefile->nextc[-1] == '\t') {
+			checkkwd |= CHKALIAS;
+		}
+		if (sp->string != sp->ap->val) {
+			ckfree(sp->string);
+		}
+		sp->ap->flag &= ~ALIASINUSE;
+		if (sp->ap->flag & ALIASDEAD) {
+			unalias(sp->ap->name);
+		}
+	}
+	parsefile->nextc = sp->prevstring;
+	parsefile->nleft = sp->prevnleft;
+	parsefile->unget = sp->unget;
+	memcpy(parsefile->lastc, sp->lastc, sizeof(sp->lastc));
+/*dprintf("*** calling popstring: restoring to '%s'\n", parsenextc);*/
+	parsefile->strpush = sp->prev;
+	if (sp != &(parsefile->basestrpush))
+		ckfree(sp);
+	INTON;
+}
+
+/*
+ * Set the input to take input from a file.  If push is set, push the
+ * old input onto the stack first.
+ */
+
+int
+setinputfile(const char *fname, int flags)
+{
+	int fd;
+
+	INTOFF;
+	if ((fd = open64(fname, O_RDONLY)) < 0) {
+		if (flags & INPUT_NOFILE_OK)
+			goto out;
+		exitstatus = 127;
+		exerror(EXERROR, "Can't open %s", fname);
+	}
+	if (fd < 10)
+		fd = savefd(fd, fd);
+	setinputfd(fd, flags & INPUT_PUSH_FILE);
+out:
+	INTON;
+	return fd;
+}
+
+
+/*
+ * Like setinputfile, but takes an open file descriptor.  Call this with
+ * interrupts off.
+ */
+
+static void
+setinputfd(int fd, int push)
+{
+	if (push) {
+		pushfile();
+		parsefile->buf = 0;
+	}
+	parsefile->fd = fd;
+	if (parsefile->buf == NULL)
+		parsefile->buf = ckmalloc(IBUFSIZ);
+	parsefile->lleft = parsefile->nleft = 0;
+	plinno = 1;
+}
+
+
+/*
+ * Like setinputfile, but takes input from a string.
+ */
+
+void
+setinputstring(char *string)
+{
+	INTOFF;
+	pushfile();
+	parsefile->nextc = string;
+	parsefile->nleft = strlen(string);
+	parsefile->buf = NULL;
+	plinno = 1;
+	INTON;
+}
+
+
+
+/*
+ * To handle the "." command, a stack of input files is used.  Pushfile
+ * adds a new entry to the stack and popfile restores the previous level.
+ */
+
+STATIC void
+pushfile(void)
+{
+	struct parsefile *pf;
+
+	pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile));
+	pf->prev = parsefile;
+	pf->fd = -1;
+	pf->strpush = NULL;
+	pf->basestrpush.prev = NULL;
+	pf->unget = 0;
+	parsefile = pf;
+}
+
+
+void
+popfile(void)
+{
+	struct parsefile *pf = parsefile;
+
+	INTOFF;
+	if (pf->fd >= 0)
+		close(pf->fd);
+	if (pf->buf)
+		ckfree(pf->buf);
+	while (pf->strpush)
+		popstring();
+	parsefile = pf->prev;
+	ckfree(pf);
+	INTON;
+}
+
+
+void unwindfiles(struct parsefile *stop)
+{
+	while (parsefile != stop)
+		popfile();
+}
+
+
+/*
+ * Return to top level.
+ */
+
+void
+popallfiles(void)
+{
+	unwindfiles(&basepf);
+}
diff --git a/bin/dash/src/input.h b/bin/dash/src/input.h
new file mode 100644
index 00000000..8acc6e9f
--- /dev/null
+++ b/bin/dash/src/input.h
@@ -0,0 +1,101 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)input.h	8.2 (Berkeley) 5/4/95
+ */
+
+/* PEOF (the end of file marker) is defined in syntax.h */
+
+enum {
+	INPUT_PUSH_FILE = 1,
+	INPUT_NOFILE_OK = 2,
+};
+
+struct alias;
+
+struct strpush {
+	struct strpush *prev;	/* preceding string on stack */
+	char *prevstring;
+	int prevnleft;
+	struct alias *ap;	/* if push was associated with an alias */
+	char *string;		/* remember the string since it may change */
+
+	/* Remember last two characters for pungetc. */
+	int lastc[2];
+
+	/* Number of outstanding calls to pungetc. */
+	int unget;
+};
+
+/*
+ * The parsefile structure pointed to by the global variable parsefile
+ * contains information about the current file being read.
+ */
+
+struct parsefile {
+	struct parsefile *prev;	/* preceding file on stack */
+	int linno;		/* current line */
+	int fd;			/* file descriptor (or -1 if string) */
+	int nleft;		/* number of chars left in this line */
+	int lleft;		/* number of chars left in this buffer */
+	char *nextc;		/* next char in buffer */
+	char *buf;		/* input buffer */
+	struct strpush *strpush; /* for pushing strings at this level */
+	struct strpush basestrpush; /* so pushing one is fast */
+
+	/* Remember last two characters for pungetc. */
+	int lastc[2];
+
+	/* Number of outstanding calls to pungetc. */
+	int unget;
+};
+
+extern struct parsefile *parsefile;
+
+/*
+ * The input line number.  Input.c just defines this variable, and saves
+ * and restores it when files are pushed and popped.  The user of this
+ * package must set its value.
+ */
+#define plinno (parsefile->linno)
+
+int pgetc(void);
+int pgetc2(void);
+void pungetc(void);
+void pushstring(char *, void *);
+void popstring(void);
+int setinputfile(const char *, int);
+void setinputstring(char *);
+void popfile(void);
+void unwindfiles(struct parsefile *);
+void popallfiles(void);
diff --git a/bin/dash/src/jobs.c b/bin/dash/src/jobs.c
new file mode 100644
index 00000000..d4c13c0f
--- /dev/null
+++ b/bin/dash/src/jobs.c
@@ -0,0 +1,1545 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <fcntl.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+#include <sys/types.h>
+#include <sys/param.h>
+#ifdef BSD
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#endif
+#include <sys/ioctl.h>
+
+#include "shell.h"
+#if JOBS
+#include <termios.h>
+#undef CEOF			/* syntax.h redefines this */
+#endif
+#include "exec.h"
+#include "eval.h"
+#include "init.h"
+#include "redir.h"
+#include "show.h"
+#include "main.h"
+#include "parser.h"
+#include "nodes.h"
+#include "jobs.h"
+#include "options.h"
+#include "trap.h"
+#include "syntax.h"
+#include "input.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "system.h"
+
+/* mode flags for set_curjob */
+#define CUR_DELETE 2
+#define CUR_RUNNING 1
+#define CUR_STOPPED 0
+
+/* mode flags for dowait */
+#define DOWAIT_NONBLOCK 0
+#define DOWAIT_BLOCK 1
+#define DOWAIT_WAITCMD 2
+
+/* array of jobs */
+static struct job *jobtab;
+/* size of array */
+static unsigned njobs;
+/* pid of last background process */
+pid_t backgndpid;
+
+#if JOBS
+/* pgrp of shell on invocation */
+static int initialpgrp;
+/* control terminal */
+static int ttyfd = -1;
+#endif
+
+/* current job */
+static struct job *curjob;
+
+/* Set if we are in the vforked child */
+int vforked;
+
+STATIC void set_curjob(struct job *, unsigned);
+STATIC int jobno(const struct job *);
+STATIC int sprint_status(char *, int, int);
+STATIC void freejob(struct job *);
+STATIC struct job *getjob(const char *, int);
+STATIC struct job *growjobtab(void);
+STATIC void forkchild(struct job *, union node *, int);
+STATIC void forkparent(struct job *, union node *, int, pid_t);
+STATIC int dowait(int, struct job *);
+#ifdef SYSV
+STATIC int onsigchild(void);
+#endif
+STATIC int waitproc(int, int *);
+STATIC char *commandtext(union node *);
+STATIC void cmdtxt(union node *);
+STATIC void cmdlist(union node *, int);
+STATIC void cmdputs(const char *);
+STATIC void showpipe(struct job *, struct output *);
+STATIC int getstatus(struct job *);
+
+#if JOBS
+static int restartjob(struct job *, int);
+static void xtcsetpgrp(int, pid_t);
+#endif
+
+STATIC void
+set_curjob(struct job *jp, unsigned mode)
+{
+	struct job *jp1;
+	struct job **jpp, **curp;
+
+	/* first remove from list */
+	jpp = curp = &curjob;
+	do {
+		jp1 = *jpp;
+		if (jp1 == jp)
+			break;
+		jpp = &jp1->prev_job;
+	} while (1);
+	*jpp = jp1->prev_job;
+
+	/* Then re-insert in correct position */
+	jpp = curp;
+	switch (mode) {
+	default:
+#ifdef DEBUG
+		abort();
+#endif
+	case CUR_DELETE:
+		/* job being deleted */
+		break;
+	case CUR_RUNNING:
+		/* newly created job or backgrounded job,
+		   put after all stopped jobs. */
+		do {
+			jp1 = *jpp;
+			if (!JOBS || !jp1 || jp1->state != JOBSTOPPED)
+				break;
+			jpp = &jp1->prev_job;
+		} while (1);
+		/* FALLTHROUGH */
+#if JOBS
+	case CUR_STOPPED:
+#endif
+		/* newly stopped job - becomes curjob */
+		jp->prev_job = *jpp;
+		*jpp = jp;
+		break;
+	}
+}
+
+#if JOBS
+/*
+ * Turn job control on and off.
+ *
+ * Note:  This code assumes that the third arg to ioctl is a character
+ * pointer, which is true on Berkeley systems but not System V.  Since
+ * System V doesn't have job control yet, this isn't a problem now.
+ *
+ * Called with interrupts off.
+ */
+
+int jobctl;
+
+void
+setjobctl(int on)
+{
+	int fd;
+	int pgrp;
+
+	if (on == jobctl || rootshell == 0)
+		return;
+	if (on) {
+		int ofd;
+		ofd = fd = open64(_PATH_TTY, O_RDWR);
+		if (fd < 0) {
+			fd += 3;
+			while (!isatty(fd))
+				if (--fd < 0)
+					goto out;
+		}
+		fd = savefd(fd, ofd);
+		do { /* while we are in the background */
+			if ((pgrp = tcgetpgrp(fd)) < 0) {
+out:
+				sh_warnx("can't access tty; job control turned off");
+				mflag = on = 0;
+				goto close;
+			}
+			if (pgrp == getpgrp())
+				break;
+			killpg(0, SIGTTIN);
+		} while (1);
+		initialpgrp = pgrp;
+
+		setsignal(SIGTSTP);
+		setsignal(SIGTTOU);
+		setsignal(SIGTTIN);
+		pgrp = rootpid;
+		setpgid(0, pgrp);
+		xtcsetpgrp(fd, pgrp);
+	} else {
+		/* turning job control off */
+		fd = ttyfd;
+		pgrp = initialpgrp;
+		xtcsetpgrp(fd, pgrp);
+		setpgid(0, pgrp);
+		setsignal(SIGTSTP);
+		setsignal(SIGTTOU);
+		setsignal(SIGTTIN);
+close:
+		close(fd);
+		fd = -1;
+	}
+	ttyfd = fd;
+	jobctl = on;
+}
+#endif
+
+
+int
+killcmd(argc, argv)
+	int argc;
+	char **argv;
+{
+	extern char *signal_names[];
+	int signo = -1;
+	int list = 0;
+	int i;
+	pid_t pid;
+	struct job *jp;
+
+	if (argc <= 1) {
+usage:
+		sh_error(
+"Usage: kill [-s sigspec | -signum | -sigspec] [pid | job]... or\n"
+"kill -l [exitstatus]"
+		);
+	}
+
+	if (**++argv == '-') {
+		signo = decode_signal(*argv + 1, 1);
+		if (signo < 0) {
+			int c;
+
+			while ((c = nextopt("ls:")) != '\0')
+				switch (c) {
+				default:
+#ifdef DEBUG
+					abort();
+#endif
+				case 'l':
+					list = 1;
+					break;
+				case 's':
+					signo = decode_signal(optionarg, 1);
+					if (signo < 0) {
+						sh_error(
+							"invalid signal number or name: %s",
+							optionarg
+						);
+					}
+		                        break;
+				}
+			argv = argptr;
+		} else
+			argv++;
+	}
+
+	if (!list && signo < 0)
+		signo = SIGTERM;
+
+	if ((signo < 0 || !*argv) ^ list) {
+		goto usage;
+	}
+
+	if (list) {
+		struct output *out;
+
+		out = out1;
+		if (!*argv) {
+			outstr("0\n", out);
+			for (i = 1; i < NSIG; i++) {
+				outfmt(out, snlfmt, signal_names[i]);
+			}
+			return 0;
+		}
+		signo = number(*argv);
+		if (signo > 128)
+			signo -= 128;
+		if (0 < signo && signo < NSIG)
+			outfmt(out, snlfmt, signal_names[signo]);
+		else
+			sh_error("invalid signal number or exit status: %s",
+				 *argv);
+		return 0;
+	}
+
+	i = 0;
+	do {
+		if (**argv == '%') {
+			jp = getjob(*argv, 0);
+			pid = -jp->ps[0].pid;
+		} else
+			pid = **argv == '-' ?
+				-number(*argv + 1) : number(*argv);
+		if (kill(pid, signo) != 0) {
+			sh_warnx("%s\n", strerror(errno));
+			i = 1;
+		}
+	} while (*++argv);
+
+	return i;
+}
+
+STATIC int
+jobno(const struct job *jp)
+{
+	return jp - jobtab + 1;
+}
+
+#if JOBS
+int
+fgcmd(int argc, char **argv)
+{
+	struct job *jp;
+	struct output *out;
+	int mode;
+	int retval;
+
+	mode = (**argv == 'f') ? FORK_FG : FORK_BG;
+	nextopt(nullstr);
+	argv = argptr;
+	out = out1;
+	do {
+		jp = getjob(*argv, 1);
+		if (mode == FORK_BG) {
+			set_curjob(jp, CUR_RUNNING);
+			outfmt(out, "[%d] ", jobno(jp));
+		}
+		outstr(jp->ps->cmd, out);
+		showpipe(jp, out);
+		retval = restartjob(jp, mode);
+	} while (*argv && *++argv);
+	return retval;
+}
+
+int bgcmd(int argc, char **argv)
+#ifdef HAVE_ALIAS_ATTRIBUTE
+	__attribute__((__alias__("fgcmd")));
+#else
+{
+	return fgcmd(argc, argv);
+}
+#endif
+
+
+STATIC int
+restartjob(struct job *jp, int mode)
+{
+	struct procstat *ps;
+	int i;
+	int status;
+	pid_t pgid;
+
+	INTOFF;
+	if (jp->state == JOBDONE)
+		goto out;
+	jp->state = JOBRUNNING;
+	pgid = jp->ps->pid;
+	if (mode == FORK_FG)
+		xtcsetpgrp(ttyfd, pgid);
+	killpg(pgid, SIGCONT);
+	ps = jp->ps;
+	i = jp->nprocs;
+	do {
+		if (WIFSTOPPED(ps->status)) {
+			ps->status = -1;
+		}
+	} while (ps++, --i);
+out:
+	status = (mode == FORK_FG) ? waitforjob(jp) : 0;
+	INTON;
+	return status;
+}
+#endif
+
+STATIC int
+sprint_status(char *os, int status, int sigonly)
+{
+	char *s = os;
+	int st;
+
+	st = WEXITSTATUS(status);
+	if (!WIFEXITED(status)) {
+#if JOBS
+		st = WSTOPSIG(status);
+		if (!WIFSTOPPED(status))
+#endif
+			st = WTERMSIG(status);
+		if (sigonly) {
+			if (st == SIGINT || st == SIGPIPE)
+				goto out;
+#if JOBS
+			if (WIFSTOPPED(status))
+				goto out;
+#endif
+		}
+		s = stpncpy(s, strsignal(st), 32);
+#ifdef WCOREDUMP
+		if (WCOREDUMP(status)) {
+			s = stpcpy(s, " (core dumped)");
+		}
+#endif
+	} else if (!sigonly) {
+		if (st)
+			s += fmtstr(s, 16, "Done(%d)", st);
+		else
+			s = stpcpy(s, "Done");
+	}
+
+out:
+	return s - os;
+}
+
+static void
+showjob(struct output *out, struct job *jp, int mode)
+{
+	struct procstat *ps;
+	struct procstat *psend;
+	int col;
+	int indent;
+	char s[80];
+
+	ps = jp->ps;
+
+	if (mode & SHOW_PGID) {
+		/* just output process (group) id of pipeline */
+		outfmt(out, "%d\n", ps->pid);
+		return;
+	}
+
+	col = fmtstr(s, 16, "[%d]   ", jobno(jp));
+	indent = col;
+
+	if (jp == curjob)
+		s[col - 2] = '+';
+	else if (curjob && jp == curjob->prev_job)
+		s[col - 2] = '-';
+
+	if (mode & SHOW_PID)
+		col += fmtstr(s + col, 16, "%d ", ps->pid);
+
+	psend = ps + jp->nprocs;
+
+	if (jp->state == JOBRUNNING) {
+		scopy("Running", s + col);
+		col += strlen("Running");
+	} else {
+		int status = psend[-1].status;
+#if JOBS
+		if (jp->state == JOBSTOPPED)
+			status = jp->stopstatus;
+#endif
+		col += sprint_status(s + col, status, 0);
+	}
+
+	goto start;
+
+	do {
+		/* for each process */
+		col = fmtstr(s, 48, " |\n%*c%d ", indent, ' ', ps->pid) - 3;
+
+start:
+		outfmt(
+			out, "%s%*c%s",
+			s, 33 - col >= 0 ? 33 - col : 0, ' ', ps->cmd
+		);
+		if (!(mode & SHOW_PID)) {
+			showpipe(jp, out);
+			break;
+		}
+		if (++ps == psend) {
+			outcslow('\n', out);
+			break;
+		}
+	} while (1);
+
+	jp->changed = 0;
+
+	if (jp->state == JOBDONE) {
+		TRACE(("showjob: freeing job %d\n", jobno(jp)));
+		freejob(jp);
+	}
+}
+
+
+int
+jobscmd(int argc, char **argv)
+{
+	int mode, m;
+	struct output *out;
+
+	mode = 0;
+	while ((m = nextopt("lp")))
+		if (m == 'l')
+			mode = SHOW_PID;
+		else
+			mode = SHOW_PGID;
+
+	out = out1;
+	argv = argptr;
+	if (*argv)
+		do
+			showjob(out, getjob(*argv,0), mode);
+		while (*++argv);
+	else
+		showjobs(out, mode);
+
+	return 0;
+}
+
+
+/*
+ * Print a list of jobs.  If "change" is nonzero, only print jobs whose
+ * statuses have changed since the last call to showjobs.
+ */
+
+void
+showjobs(struct output *out, int mode)
+{
+	struct job *jp;
+
+	TRACE(("showjobs(%x) called\n", mode));
+
+	/* If not even one job changed, there is nothing to do */
+	dowait(DOWAIT_NONBLOCK, NULL);
+
+	for (jp = curjob; jp; jp = jp->prev_job) {
+		if (!(mode & SHOW_CHANGED) || jp->changed)
+			showjob(out, jp, mode);
+	}
+}
+
+/*
+ * Mark a job structure as unused.
+ */
+
+STATIC void
+freejob(struct job *jp)
+{
+	struct procstat *ps;
+	int i;
+
+	INTOFF;
+	for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) {
+		if (ps->cmd != nullstr)
+			ckfree(ps->cmd);
+	}
+	if (jp->ps != &jp->ps0)
+		ckfree(jp->ps);
+	jp->used = 0;
+	set_curjob(jp, CUR_DELETE);
+	INTON;
+}
+
+
+
+int
+waitcmd(int argc, char **argv)
+{
+	struct job *job;
+	int retval;
+	struct job *jp;
+
+	nextopt(nullstr);
+	retval = 0;
+
+	argv = argptr;
+	if (!*argv) {
+		/* wait for all jobs */
+		for (;;) {
+			jp = curjob;
+			while (1) {
+				if (!jp) {
+					/* no running procs */
+					goto out;
+				}
+				if (jp->state == JOBRUNNING)
+					break;
+				jp->waited = 1;
+				jp = jp->prev_job;
+			}
+			if (!dowait(DOWAIT_WAITCMD, 0))
+				goto sigout;
+		}
+	}
+
+	retval = 127;
+	do {
+		if (**argv != '%') {
+			pid_t pid = number(*argv);
+			job = curjob;
+			goto start;
+			do {
+				if (job->ps[job->nprocs - 1].pid == pid)
+					break;
+				job = job->prev_job;
+start:
+				if (!job)
+					goto repeat;
+			} while (1);
+		} else
+			job = getjob(*argv, 0);
+		/* loop until process terminated or stopped */
+		if (!dowait(DOWAIT_WAITCMD, job))
+			goto sigout;
+		job->waited = 1;
+		retval = getstatus(job);
+repeat:
+		;
+	} while (*++argv);
+
+out:
+	return retval;
+
+sigout:
+	retval = 128 + pending_sig;
+	goto out;
+}
+
+
+
+/*
+ * Convert a job name to a job structure.
+ */
+
+STATIC struct job *
+getjob(const char *name, int getctl)
+{
+	struct job *jp;
+	struct job *found;
+	const char *err_msg = "No such job: %s";
+	unsigned num;
+	int c;
+	const char *p;
+	char *(*match)(const char *, const char *);
+
+	jp = curjob;
+	p = name;
+	if (!p)
+		goto currentjob;
+
+	if (*p != '%')
+		goto err;
+
+	c = *++p;
+	if (!c)
+		goto currentjob;
+
+	if (!p[1]) {
+		if (c == '+' || c == '%') {
+currentjob:
+			err_msg = "No current job";
+			goto check;
+		} else if (c == '-') {
+			if (jp)
+				jp = jp->prev_job;
+			err_msg = "No previous job";
+check:
+			if (!jp)
+				goto err;
+			goto gotit;
+		}
+	}
+
+	if (is_number(p)) {
+		num = atoi(p);
+		if (num > 0 && num <= njobs) {
+			jp = jobtab + num - 1;
+			if (jp->used)
+				goto gotit;
+			goto err;
+		}
+	}
+
+	match = prefix;
+	if (*p == '?') {
+		match = strstr;
+		p++;
+	}
+
+	found = 0;
+	while (jp) {
+		if (match(jp->ps[0].cmd, p)) {
+			if (found)
+				goto err;
+			found = jp;
+			err_msg = "%s: ambiguous";
+		}
+		jp = jp->prev_job;
+	}
+
+	if (!found)
+		goto err;
+	jp = found;
+
+gotit:
+#if JOBS
+	err_msg = "job %s not created under job control";
+	if (getctl && jp->jobctl == 0)
+		goto err;
+#endif
+	return jp;
+err:
+	sh_error(err_msg, name);
+}
+
+
+
+/*
+ * Return a new job structure.
+ * Called with interrupts off.
+ */
+
+struct job *
+makejob(union node *node, int nprocs)
+{
+	int i;
+	struct job *jp;
+
+	for (i = njobs, jp = jobtab ; ; jp++) {
+		if (--i < 0) {
+			jp = growjobtab();
+			break;
+		}
+		if (jp->used == 0)
+			break;
+		if (jp->state != JOBDONE || !jp->waited)
+			continue;
+		if (jobctl)
+			continue;
+		freejob(jp);
+		break;
+	}
+	memset(jp, 0, sizeof(*jp));
+#if JOBS
+	if (jobctl)
+		jp->jobctl = 1;
+#endif
+	jp->prev_job = curjob;
+	curjob = jp;
+	jp->used = 1;
+	jp->ps = &jp->ps0;
+	if (nprocs > 1) {
+		jp->ps = ckmalloc(nprocs * sizeof (struct procstat));
+	}
+	TRACE(("makejob(0x%lx, %d) returns %%%d\n", (long)node, nprocs,
+	    jobno(jp)));
+	return jp;
+}
+
+STATIC struct job *
+growjobtab(void)
+{
+	size_t len;
+	ptrdiff_t offset;
+	struct job *jp, *jq;
+
+	len = njobs * sizeof(*jp);
+	jq = jobtab;
+	jp = ckrealloc(jq, len + 4 * sizeof(*jp));
+
+	offset = (char *)jp - (char *)jq;
+	if (offset) {
+		/* Relocate pointers */
+		size_t l = len;
+
+		jq = (struct job *)((char *)jq + l);
+		while (l) {
+			l -= sizeof(*jp);
+			jq--;
+#define joff(p) ((struct job *)((char *)(p) + l))
+#define jmove(p) (p) = (void *)((char *)(p) + offset)
+			if (likely(joff(jp)->ps == &jq->ps0))
+				jmove(joff(jp)->ps);
+			if (joff(jp)->prev_job)
+				jmove(joff(jp)->prev_job);
+		}
+		if (curjob)
+			jmove(curjob);
+#undef joff
+#undef jmove
+	}
+
+	njobs += 4;
+	jobtab = jp;
+	jp = (struct job *)((char *)jp + len);
+	jq = jp + 3;
+	do {
+		jq->used = 0;
+	} while (--jq >= jp);
+	return jp;
+}
+
+
+/*
+ * Fork off a subshell.  If we are doing job control, give the subshell its
+ * own process group.  Jp is a job structure that the job is to be added to.
+ * N is the command that will be evaluated by the child.  Both jp and n may
+ * be NULL.  The mode parameter can be one of the following:
+ *	FORK_FG - Fork off a foreground process.
+ *	FORK_BG - Fork off a background process.
+ *	FORK_NOJOB - Like FORK_FG, but don't give the process its own
+ *		     process group even if job control is on.
+ *
+ * When job control is turned off, background processes have their standard
+ * input redirected to /dev/null (except for the second and later processes
+ * in a pipeline).
+ *
+ * Called with interrupts off.
+ */
+
+static void forkchild(struct job *jp, union node *n, int mode)
+{
+	int lvforked;
+	int oldlvl;
+
+	TRACE(("Child shell %d\n", getpid()));
+
+	oldlvl = shlvl;
+	lvforked = vforked;
+
+	if (!lvforked) {
+		shlvl++;
+
+		forkreset();
+
+#if JOBS
+		/* do job control only in root shell */
+		jobctl = 0;
+#endif
+	}
+
+#if JOBS
+	if (mode != FORK_NOJOB && jp->jobctl && !oldlvl) {
+		pid_t pgrp;
+
+		if (jp->nprocs == 0)
+			pgrp = getpid();
+		else
+			pgrp = jp->ps[0].pid;
+		/* This can fail because we are doing it in the parent also */
+		(void)setpgid(0, pgrp);
+		if (mode == FORK_FG)
+			xtcsetpgrp(ttyfd, pgrp);
+		setsignal(SIGTSTP);
+		setsignal(SIGTTOU);
+	} else
+#endif
+	if (mode == FORK_BG) {
+		ignoresig(SIGINT);
+		ignoresig(SIGQUIT);
+		if (jp->nprocs == 0) {
+			close(0);
+			if (open64(_PATH_DEVNULL, O_RDONLY) != 0)
+				sh_error("Can't open %s", _PATH_DEVNULL);
+		}
+	}
+	if (!oldlvl && iflag) {
+		if (mode != FORK_BG) {
+			setsignal(SIGINT);
+			setsignal(SIGQUIT);
+		}
+		setsignal(SIGTERM);
+	}
+
+	if (lvforked)
+		return;
+
+	for (jp = curjob; jp; jp = jp->prev_job)
+		freejob(jp);
+}
+
+static void forkparent(struct job *jp, union node *n, int mode, pid_t pid)
+{
+	if (pid < 0) {
+		TRACE(("Fork failed, errno=%d", errno));
+		if (jp)
+			freejob(jp);
+		sh_error("Cannot fork");
+		/* NOTREACHED */
+	}
+
+	TRACE(("In parent shell:  child = %d\n", pid));
+	if (!jp)
+		return;
+#if JOBS
+	if (mode != FORK_NOJOB && jp->jobctl) {
+		int pgrp;
+
+		if (jp->nprocs == 0)
+			pgrp = pid;
+		else
+			pgrp = jp->ps[0].pid;
+		/* This can fail because we are doing it in the child also */
+		(void)setpgid(pid, pgrp);
+	}
+#endif
+	if (mode == FORK_BG) {
+		backgndpid = pid;		/* set $! */
+		set_curjob(jp, CUR_RUNNING);
+	}
+	if (jp) {
+		struct procstat *ps = &jp->ps[jp->nprocs++];
+		ps->pid = pid;
+		ps->status = -1;
+		ps->cmd = nullstr;
+		if (jobctl && n)
+			ps->cmd = commandtext(n);
+	}
+}
+
+int
+forkshell(struct job *jp, union node *n, int mode)
+{
+	int pid;
+
+	TRACE(("forkshell(%%%d, %p, %d) called\n", jobno(jp), n, mode));
+	pid = fork();
+	if (pid == 0)
+		forkchild(jp, n, mode);
+	else
+		forkparent(jp, n, mode, pid);
+
+	return pid;
+}
+
+struct job *vforkexec(union node *n, char **argv, const char *path, int idx)
+{
+	struct job *jp;
+	int pid;
+
+	jp = makejob(n, 1);
+
+	sigblockall(NULL);
+	vforked++;
+
+	pid = vfork();
+
+	if (!pid) {
+		forkchild(jp, n, FORK_FG);
+		sigclearmask();
+		shellexec(argv, path, idx);
+		/* NOTREACHED */
+	}
+
+	vforked = 0;
+	sigclearmask();
+	forkparent(jp, n, FORK_FG, pid);
+
+	return jp;
+}
+
+/*
+ * Wait for job to finish.
+ *
+ * Under job control we have the problem that while a child process is
+ * running interrupts generated by the user are sent to the child but not
+ * to the shell.  This means that an infinite loop started by an inter-
+ * active user may be hard to kill.  With job control turned off, an
+ * interactive user may place an interactive program inside a loop.  If
+ * the interactive program catches interrupts, the user doesn't want
+ * these interrupts to also abort the loop.  The approach we take here
+ * is to have the shell ignore interrupt signals while waiting for a
+ * forground process to terminate, and then send itself an interrupt
+ * signal if the child process was terminated by an interrupt signal.
+ * Unfortunately, some programs want to do a bit of cleanup and then
+ * exit on interrupt; unless these processes terminate themselves by
+ * sending a signal to themselves (instead of calling exit) they will
+ * confuse this approach.
+ *
+ * Called with interrupts off.
+ */
+
+int
+waitforjob(struct job *jp)
+{
+	int st;
+
+	TRACE(("waitforjob(%%%d) called\n", jp ? jobno(jp) : 0));
+	dowait(jp ? DOWAIT_BLOCK : DOWAIT_NONBLOCK, jp);
+	if (!jp)
+		return exitstatus;
+
+	st = getstatus(jp);
+#if JOBS
+	if (jp->jobctl) {
+		xtcsetpgrp(ttyfd, rootpid);
+		/*
+		 * This is truly gross.
+		 * If we're doing job control, then we did a TIOCSPGRP which
+		 * caused us (the shell) to no longer be in the controlling
+		 * session -- so we wouldn't have seen any ^C/SIGINT.  So, we
+		 * intuit from the subprocess exit status whether a SIGINT
+		 * occurred, and if so interrupt ourselves.  Yuck.  - mycroft
+		 */
+		if (jp->sigint)
+			raise(SIGINT);
+	}
+#endif
+	if (! JOBS || jp->state == JOBDONE)
+		freejob(jp);
+	return st;
+}
+
+
+
+/*
+ * Wait for a process to terminate.
+ */
+
+static int waitone(int block, struct job *job)
+{
+	int pid;
+	int status;
+	struct job *jp;
+	struct job *thisjob = NULL;
+	int state;
+
+	INTOFF;
+	TRACE(("dowait(%d) called\n", block));
+	pid = waitproc(block, &status);
+	TRACE(("wait returns pid %d, status=%d\n", pid, status));
+	if (pid <= 0)
+		goto out;
+
+	for (jp = curjob; jp; jp = jp->prev_job) {
+		struct procstat *sp;
+		struct procstat *spend;
+		if (jp->state == JOBDONE)
+			continue;
+		state = JOBDONE;
+		spend = jp->ps + jp->nprocs;
+		sp = jp->ps;
+		do {
+			if (sp->pid == pid) {
+				TRACE(("Job %d: changing status of proc %d from 0x%x to 0x%x\n", jobno(jp), pid, sp->status, status));
+				sp->status = status;
+				thisjob = jp;
+			}
+			if (sp->status == -1)
+				state = JOBRUNNING;
+#if JOBS
+			if (state == JOBRUNNING)
+				continue;
+			if (WIFSTOPPED(sp->status)) {
+				jp->stopstatus = sp->status;
+				state = JOBSTOPPED;
+			}
+#endif
+		} while (++sp < spend);
+		if (thisjob)
+			goto gotjob;
+	}
+	goto out;
+
+gotjob:
+	if (state != JOBRUNNING) {
+		thisjob->changed = 1;
+
+		if (thisjob->state != state) {
+			TRACE(("Job %d: changing state from %d to %d\n", jobno(thisjob), thisjob->state, state));
+			thisjob->state = state;
+#if JOBS
+			if (state == JOBSTOPPED) {
+				set_curjob(thisjob, CUR_STOPPED);
+			}
+#endif
+		}
+	}
+
+out:
+	INTON;
+
+	if (thisjob && thisjob == job) {
+		char s[48 + 1];
+		int len;
+
+		len = sprint_status(s, status, 1);
+		if (len) {
+			s[len] = '\n';
+			s[len + 1] = 0;
+			outstr(s, out2);
+		}
+	}
+	return pid;
+}
+
+static int dowait(int block, struct job *jp)
+{
+	int gotchld = *(volatile int *)&gotsigchld;
+	int rpid;
+	int pid;
+
+	if (jp && jp->state != JOBRUNNING)
+		block = DOWAIT_NONBLOCK;
+
+	if (block == DOWAIT_NONBLOCK && !gotchld)
+		return 1;
+
+	rpid = 1;
+
+	do {
+		pid = waitone(block, jp);
+		rpid &= !!pid;
+
+		if (!pid || (jp && jp->state != JOBRUNNING))
+			block = DOWAIT_NONBLOCK;
+	} while (pid >= 0);
+
+	return rpid;
+}
+
+/*
+ * Do a wait system call.  If block is zero, we return -1 rather than
+ * blocking.  If block is DOWAIT_WAITCMD, we return 0 when a signal
+ * other than SIGCHLD interrupted the wait.
+ *
+ * We use sigsuspend in conjunction with a non-blocking wait3 in
+ * order to ensure that waitcmd exits promptly upon the reception
+ * of a signal.
+ *
+ * For code paths other than waitcmd we either use a blocking wait3
+ * or a non-blocking wait3.  For the latter case the caller of dowait
+ * must ensure that it is called over and over again until all dead
+ * children have been reaped.  Otherwise zombies may linger.
+ */
+
+
+STATIC int
+waitproc(int block, int *status)
+{
+	sigset_t oldmask;
+	int flags = block == DOWAIT_BLOCK ? 0 : WNOHANG;
+	int err;
+
+#if JOBS
+	if (jobctl)
+		flags |= WUNTRACED;
+#endif
+
+	do {
+		gotsigchld = 0;
+		do
+			err = wait3(status, flags, NULL);
+		while (err < 0 && errno == EINTR);
+
+		if (err || (err = -!block))
+			break;
+
+		sigblockall(&oldmask);
+
+		while (!gotsigchld && !pending_sig)
+			sigsuspend(&oldmask);
+
+		sigclearmask();
+	} while (gotsigchld);
+
+	return err;
+}
+
+/*
+ * return 1 if there are stopped jobs, otherwise 0
+ */
+int job_warning;
+int
+stoppedjobs(void)
+{
+	struct job *jp;
+	int retval;
+
+	retval = 0;
+	if (job_warning)
+		goto out;
+	jp = curjob;
+	if (jp && jp->state == JOBSTOPPED) {
+		out2str("You have stopped jobs.\n");
+		job_warning = 2;
+		retval++;
+	}
+
+out:
+	return retval;
+}
+
+/*
+ * Return a string identifying a command (to be printed by the
+ * jobs command).
+ */
+
+STATIC char *cmdnextc;
+
+STATIC char *
+commandtext(union node *n)
+{
+	char *name;
+
+	STARTSTACKSTR(cmdnextc);
+	cmdtxt(n);
+	name = stackblock();
+	TRACE(("commandtext: name %p, end %p\n", name, cmdnextc));
+	return savestr(name);
+}
+
+
+STATIC void
+cmdtxt(union node *n)
+{
+	union node *np;
+	struct nodelist *lp;
+	const char *p;
+	char s[2];
+
+	if (!n)
+		return;
+	switch (n->type) {
+	default:
+#if DEBUG
+		abort();
+#endif
+	case NPIPE:
+		lp = n->npipe.cmdlist;
+		for (;;) {
+			cmdtxt(lp->n);
+			lp = lp->next;
+			if (!lp)
+				break;
+			cmdputs(" | ");
+		}
+		break;
+	case NSEMI:
+		p = "; ";
+		goto binop;
+	case NAND:
+		p = " && ";
+		goto binop;
+	case NOR:
+		p = " || ";
+binop:
+		cmdtxt(n->nbinary.ch1);
+		cmdputs(p);
+		n = n->nbinary.ch2;
+		goto donode;
+	case NREDIR:
+	case NBACKGND:
+		n = n->nredir.n;
+		goto donode;
+	case NNOT:
+		cmdputs("!");
+		n = n->nnot.com;
+donode:
+		cmdtxt(n);
+		break;
+	case NIF:
+		cmdputs("if ");
+		cmdtxt(n->nif.test);
+		cmdputs("; then ");
+		if (n->nif.elsepart) {
+			cmdtxt(n->nif.ifpart);
+			cmdputs("; else ");
+			n = n->nif.elsepart;
+		} else {
+			n = n->nif.ifpart;
+		}
+		p = "; fi";
+		goto dotail;
+	case NSUBSHELL:
+		cmdputs("(");
+		n = n->nredir.n;
+		p = ")";
+		goto dotail;
+	case NWHILE:
+		p = "while ";
+		goto until;
+	case NUNTIL:
+		p = "until ";
+until:
+		cmdputs(p);
+		cmdtxt(n->nbinary.ch1);
+		n = n->nbinary.ch2;
+		p = "; done";
+dodo:
+		cmdputs("; do ");
+dotail:
+		cmdtxt(n);
+		goto dotail2;
+	case NFOR:
+		cmdputs("for ");
+		cmdputs(n->nfor.var);
+		cmdputs(" in ");
+		cmdlist(n->nfor.args, 1);
+		n = n->nfor.body;
+		p = "; done";
+		goto dodo;
+	case NDEFUN:
+		cmdputs(n->ndefun.text);
+		p = "() { ... }";
+		goto dotail2;
+	case NCMD:
+		cmdlist(n->ncmd.args, 1);
+		cmdlist(n->ncmd.redirect, 0);
+		break;
+	case NARG:
+		p = n->narg.text;
+dotail2:
+		cmdputs(p);
+		break;
+	case NHERE:
+	case NXHERE:
+		p = "<<...";
+		goto dotail2;
+	case NCASE:
+		cmdputs("case ");
+		cmdputs(n->ncase.expr->narg.text);
+		cmdputs(" in ");
+		for (np = n->ncase.cases; np; np = np->nclist.next) {
+			cmdtxt(np->nclist.pattern);
+			cmdputs(") ");
+			cmdtxt(np->nclist.body);
+			cmdputs(";; ");
+		}
+		p = "esac";
+		goto dotail2;
+	case NTO:
+		p = ">";
+		goto redir;
+	case NCLOBBER:
+		p = ">|";
+		goto redir;
+	case NAPPEND:
+		p = ">>";
+		goto redir;
+	case NTOFD:
+		p = ">&";
+		goto redir;
+	case NFROM:
+		p = "<";
+		goto redir;
+	case NFROMFD:
+		p = "<&";
+		goto redir;
+	case NFROMTO:
+		p = "<>";
+redir:
+		s[0] = n->nfile.fd + '0';
+		s[1] = '\0';
+		cmdputs(s);
+		cmdputs(p);
+		if (n->type == NTOFD || n->type == NFROMFD) {
+			s[0] = n->ndup.dupfd + '0';
+			p = s;
+			goto dotail2;
+		} else {
+			n = n->nfile.fname;
+			goto donode;
+		}
+	}
+}
+
+STATIC void
+cmdlist(union node *np, int sep)
+{
+	for (; np; np = np->narg.next) {
+		if (!sep)
+			cmdputs(spcstr);
+		cmdtxt(np);
+		if (sep && np->narg.next)
+			cmdputs(spcstr);
+	}
+}
+
+
+STATIC void
+cmdputs(const char *s)
+{
+	const char *p, *str;
+	char cc[2] = " ";
+	char *nextc;
+	signed char c;
+	int subtype = 0;
+	int quoted = 0;
+	static const char vstype[VSTYPE + 1][4] = {
+		"", "}", "-", "+", "?", "=",
+		"%", "%%", "#", "##",
+	};
+
+	nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc);
+	p = s;
+	while ((c = *p++) != 0) {
+		str = 0;
+		switch (c) {
+		case CTLESC:
+			c = *p++;
+			break;
+		case CTLVAR:
+			subtype = *p++;
+			if ((subtype & VSTYPE) == VSLENGTH)
+				str = "${#";
+			else
+				str = "${";
+			goto dostr;
+		case CTLENDVAR:
+			str = "\"}";
+			str += !(quoted & 1);
+			quoted >>= 1;
+			subtype = 0;
+			goto dostr;
+		case CTLBACKQ:
+			str = "$(...)";
+			goto dostr;
+		case CTLARI:
+			str = "$((";
+			goto dostr;
+		case CTLENDARI:
+			str = "))";
+			goto dostr;
+		case CTLQUOTEMARK:
+			quoted ^= 1;
+			c = '"';
+			break;
+		case '=':
+			if (subtype == 0)
+				break;
+			if ((subtype & VSTYPE) != VSNORMAL)
+				quoted <<= 1;
+			str = vstype[subtype & VSTYPE];
+			if (subtype & VSNUL)
+				c = ':';
+			else
+				goto checkstr;
+			break;
+		case '\'':
+		case '\\':
+		case '"':
+		case '$':
+			/* These can only happen inside quotes */
+			cc[0] = c;
+			str = cc;
+			c = '\\';
+			break;
+		default:
+			break;
+		}
+		USTPUTC(c, nextc);
+checkstr:
+		if (!str)
+			continue;
+dostr:
+		while ((c = *str++)) {
+			USTPUTC(c, nextc);
+		}
+	}
+	if (quoted & 1) {
+		USTPUTC('"', nextc);
+	}
+	*nextc = 0;
+	cmdnextc = nextc;
+}
+
+
+STATIC void
+showpipe(struct job *jp, struct output *out)
+{
+	struct procstat *sp;
+	struct procstat *spend;
+
+	spend = jp->ps + jp->nprocs;
+	for (sp = jp->ps + 1; sp < spend; sp++)
+		outfmt(out, " | %s", sp->cmd);
+	outcslow('\n', out);
+	flushall();
+}
+
+
+#if JOBS
+STATIC void
+xtcsetpgrp(int fd, pid_t pgrp)
+{
+	if (tcsetpgrp(fd, pgrp))
+		sh_error("Cannot set tty process group (%s)", strerror(errno));
+}
+#endif
+
+
+STATIC int
+getstatus(struct job *job) {
+	int status;
+	int retval;
+
+	status = job->ps[job->nprocs - 1].status;
+	retval = WEXITSTATUS(status);
+	if (!WIFEXITED(status)) {
+#if JOBS
+		retval = WSTOPSIG(status);
+		if (!WIFSTOPPED(status))
+#endif
+		{
+			/* XXX: limits number of signals */
+			retval = WTERMSIG(status);
+#if JOBS
+			if (retval == SIGINT)
+				job->sigint = 1;
+#endif
+		}
+		retval += 128;
+	}
+	TRACE(("getstatus: job %d, nproc %d, status %x, retval %x\n",
+		jobno(job), job->nprocs, status, retval));
+	return retval;
+}
diff --git a/bin/dash/src/jobs.h b/bin/dash/src/jobs.h
new file mode 100644
index 00000000..6ac6c56d
--- /dev/null
+++ b/bin/dash/src/jobs.h
@@ -0,0 +1,113 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)jobs.h	8.2 (Berkeley) 5/4/95
+ */
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+/* Mode argument to forkshell.  Don't change FORK_FG or FORK_BG. */
+#define FORK_FG 0
+#define FORK_BG 1
+#define FORK_NOJOB 2
+
+/* mode flags for showjob(s) */
+#define	SHOW_PGID	0x01	/* only show pgid - for jobs -p */
+#define	SHOW_PID	0x04	/* include process pid */
+#define	SHOW_CHANGED	0x08	/* only jobs whose state has changed */
+
+
+/*
+ * A job structure contains information about a job.  A job is either a
+ * single process or a set of processes contained in a pipeline.  In the
+ * latter case, pidlist will be non-NULL, and will point to a -1 terminated
+ * array of pids.
+ */
+
+struct procstat {
+	pid_t	pid;		/* process id */
+ 	int	status;		/* last process status from wait() */
+ 	char	*cmd;		/* text of command being run */
+};
+
+struct job {
+	struct procstat ps0;	/* status of process */
+	struct procstat *ps;	/* status or processes when more than one */
+#if JOBS
+	int stopstatus;		/* status of a stopped job */
+#endif
+	uint32_t
+		nprocs: 16,	/* number of processes */
+		state: 8,
+#define	JOBRUNNING	0	/* at least one proc running */
+#define	JOBSTOPPED	1	/* all procs are stopped */
+#define	JOBDONE		2	/* all procs are completed */
+#if JOBS
+		sigint: 1,	/* job was killed by SIGINT */
+		jobctl: 1,	/* job running under job control */
+#endif
+		waited: 1,	/* true if this entry has been waited for */
+		used: 1,	/* true if this entry is in used */
+		changed: 1;	/* true if status has changed */
+	struct job *prev_job;	/* previous job */
+};
+
+union node;
+
+extern pid_t backgndpid;	/* pid of last background process */
+extern int job_warning;		/* user was warned about stopped jobs */
+#if JOBS
+extern int jobctl;		/* true if doing job control */
+#else
+#define jobctl 0
+#endif
+extern int vforked;		/* Set if we are in the vforked child */
+
+void setjobctl(int);
+int killcmd(int, char **);
+int fgcmd(int, char **);
+int bgcmd(int, char **);
+int jobscmd(int, char **);
+struct output;
+void showjobs(struct output *, int);
+int waitcmd(int, char **);
+struct job *makejob(union node *, int);
+int forkshell(struct job *, union node *, int);
+struct job *vforkexec(union node *n, char **argv, const char *path, int idx);
+int waitforjob(struct job *);
+int stoppedjobs(void);
+
+#if ! JOBS
+#define setjobctl(on) ((void)(on))	/* do nothing */
+#endif
diff --git a/bin/dash/src/machdep.h b/bin/dash/src/machdep.h
new file mode 100644
index 00000000..f2ff0ad8
--- /dev/null
+++ b/bin/dash/src/machdep.h
@@ -0,0 +1,47 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)machdep.h	8.2 (Berkeley) 5/4/95
+ */
+
+/*
+ * Most machines require the value returned from malloc to be aligned
+ * in some way.  The following macro will get this right on many machines.
+ */
+
+#define SHELL_SIZE (sizeof(union {int i; char *cp; double d; }) - 1)
+/*
+ * It appears that grabstackstr() will barf with such alignments
+ * because stalloc() will return a string allocated in a new stackblock.
+ */
+#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE)
diff --git a/bin/dash/src/mail.c b/bin/dash/src/mail.c
new file mode 100644
index 00000000..8eacb2d0
--- /dev/null
+++ b/bin/dash/src/mail.c
@@ -0,0 +1,115 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Routines to check for mail.  (Perhaps make part of main.c?)
+ */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+
+#include "shell.h"
+#include "nodes.h"
+#include "exec.h"	/* defines padvance() */
+#include "var.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mail.h"
+#include "mystring.h"
+
+
+#define MAXMBOXES 10
+
+/* times of mailboxes */
+static time_t mailtime[MAXMBOXES];
+/* Set if MAIL or MAILPATH is changed. */
+static int changed;
+
+
+
+/*
+ * Print appropriate message(s) if mail has arrived.  If changed is set,
+ * then the value of MAIL has changed, so we just update the values.
+ */
+
+void
+chkmail(void)
+{
+	const char *mpath;
+	char *p;
+	char *q;
+	time_t *mtp;
+	struct stackmark smark;
+	struct stat64 statb;
+
+	setstackmark(&smark);
+	mpath = mpathset() ? mpathval() : mailval();
+	for (mtp = mailtime; mtp < mailtime + MAXMBOXES; mtp++) {
+		int len;
+
+		len = padvance_magic(&mpath, nullstr, 2);
+		if (!len)
+			break;
+		p = stackblock();
+		if (*p == '\0')
+			continue;
+		for (q = p ; *q ; q++);
+#ifdef DEBUG
+		if (q[-1] != '/')
+			abort();
+#endif
+		q[-1] = '\0';			/* delete trailing '/' */
+		if (stat64(p, &statb) < 0) {
+			*mtp = 0;
+			continue;
+		}
+		if (!changed && statb.st_mtime != *mtp) {
+			outfmt(
+				&errout, snlfmt,
+				pathopt ? pathopt : "you have mail"
+			);
+		}
+		*mtp = statb.st_mtime;
+	}
+	changed = 0;
+	popstackmark(&smark);
+}
+
+
+void
+changemail(const char *val)
+{
+	changed++;
+}
diff --git a/bin/dash/src/mail.h b/bin/dash/src/mail.h
new file mode 100644
index 00000000..3c6b21d2
--- /dev/null
+++ b/bin/dash/src/mail.h
@@ -0,0 +1,38 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)mail.h	8.2 (Berkeley) 5/4/95
+ */
+
+void chkmail(void);
+void changemail(const char *);
diff --git a/bin/dash/src/main.c b/bin/dash/src/main.c
new file mode 100644
index 00000000..7a285346
--- /dev/null
+++ b/bin/dash/src/main.c
@@ -0,0 +1,363 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+
+#include "shell.h"
+#include "main.h"
+#include "mail.h"
+#include "options.h"
+#include "output.h"
+#include "parser.h"
+#include "nodes.h"
+#include "expand.h"
+#include "eval.h"
+#include "jobs.h"
+#include "input.h"
+#include "trap.h"
+#include "var.h"
+#include "show.h"
+#include "memalloc.h"
+#include "error.h"
+#include "init.h"
+#include "mystring.h"
+#include "exec.h"
+#include "cd.h"
+
+#define PROFILE 0
+
+int rootpid;
+int shlvl;
+#ifdef __GLIBC__
+int *dash_errno;
+#endif
+#if PROFILE
+short profile_buf[16384];
+extern int etext();
+#endif
+MKINIT struct jmploc main_handler;
+
+STATIC void read_profile(const char *);
+STATIC char *find_dot_file(char *);
+static int cmdloop(int);
+int main(int, char **);
+
+/*
+ * Main routine.  We initialize things, parse the arguments, execute
+ * profiles if we're a login shell, and then call cmdloop to execute
+ * commands.  The setjmp call sets up the location to jump to when an
+ * exception occurs.  When an exception occurs the variable "state"
+ * is used to figure out how far we had gotten.
+ */
+
+int
+main(int argc, char **argv)
+{
+	char *shinit;
+	volatile int state;
+	struct stackmark smark;
+	int login;
+
+#ifdef __GLIBC__
+	dash_errno = __errno_location();
+#endif
+
+#if PROFILE
+	monitor(4, etext, profile_buf, sizeof profile_buf, 50);
+#endif
+	state = 0;
+	if (unlikely(setjmp(main_handler.loc))) {
+		int e;
+		int s;
+
+		exitreset();
+
+		e = exception;
+
+		s = state;
+		if (e == EXEND || e == EXEXIT || s == 0 || iflag == 0 || shlvl)
+			exitshell();
+
+		reset();
+
+		if (e == EXINT
+#if ATTY
+		 && (! attyset() || equal(termval(), "emacs"))
+#endif
+		 ) {
+			out2c('\n');
+#ifdef FLUSHERR
+			flushout(out2);
+#endif
+		}
+		popstackmark(&smark);
+		FORCEINTON;				/* enable interrupts */
+		if (s == 1)
+			goto state1;
+		else if (s == 2)
+			goto state2;
+		else if (s == 3)
+			goto state3;
+		else
+			goto state4;
+	}
+	handler = &main_handler;
+#ifdef DEBUG
+	opentrace();
+	trputs("Shell args:  ");  trargs(argv);
+#endif
+	rootpid = getpid();
+	init();
+	setstackmark(&smark);
+	login = procargs(argc, argv);
+	if (login) {
+		state = 1;
+		read_profile("/etc/profile");
+state1:
+		state = 2;
+		read_profile("$HOME/.profile");
+	}
+state2:
+	state = 3;
+	if (
+#ifndef linux
+		getuid() == geteuid() && getgid() == getegid() &&
+#endif
+		iflag
+	) {
+		if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') {
+			read_profile(shinit);
+		}
+	}
+	popstackmark(&smark);
+state3:
+	state = 4;
+	if (minusc)
+		evalstring(minusc, sflag ? 0 : EV_EXIT);
+
+	if (sflag || minusc == NULL) {
+state4:	/* XXX ??? - why isn't this before the "if" statement */
+		cmdloop(1);
+	}
+#if PROFILE
+	monitor(0);
+#endif
+#if GPROF
+	{
+		extern void _mcleanup(void);
+		_mcleanup();
+	}
+#endif
+	exitshell();
+	/* NOTREACHED */
+}
+
+
+/*
+ * Read and execute commands.  "Top" is nonzero for the top level command
+ * loop; it turns on prompting if the shell is interactive.
+ */
+
+static int
+cmdloop(int top)
+{
+	union node *n;
+	struct stackmark smark;
+	int inter;
+	int status = 0;
+	int numeof = 0;
+
+	TRACE(("cmdloop(%d) called\n", top));
+	for (;;) {
+		int skip;
+
+		setstackmark(&smark);
+		if (jobctl)
+			showjobs(out2, SHOW_CHANGED);
+		inter = 0;
+		if (iflag && top) {
+			inter++;
+			chkmail();
+		}
+		n = parsecmd(inter);
+		/* showtree(n); DEBUG */
+		if (n == NEOF) {
+			if (!top || numeof >= 50)
+				break;
+			if (!stoppedjobs()) {
+				if (!Iflag) {
+					if (iflag) {
+						out2c('\n');
+#ifdef FLUSHERR
+						flushout(out2);
+#endif
+					}
+					break;
+				}
+				out2str("\nUse \"exit\" to leave shell.\n");
+			}
+			numeof++;
+		} else if (nflag == 0) {
+			int i;
+
+			job_warning = (job_warning == 2) ? 1 : 0;
+			numeof = 0;
+			i = evaltree(n, 0);
+			if (n)
+				status = i;
+		}
+		popstackmark(&smark);
+
+		skip = evalskip;
+		if (skip) {
+			evalskip &= ~(SKIPFUNC | SKIPFUNCDEF);
+			break;
+		}
+	}
+
+	return status;
+}
+
+
+
+/*
+ * Read /etc/profile or .profile.  Return on error.
+ */
+
+STATIC void
+read_profile(const char *name)
+{
+	name = expandstr(name);
+	if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0)
+		return;
+
+	cmdloop(0);
+	popfile();
+}
+
+
+
+/*
+ * Read a file containing shell functions.
+ */
+
+void
+readcmdfile(char *name)
+{
+	setinputfile(name, INPUT_PUSH_FILE);
+	cmdloop(0);
+	popfile();
+}
+
+
+
+/*
+ * Take commands from a file.  To be compatible we should do a path
+ * search for the file, which is necessary to find sub-commands.
+ */
+
+
+STATIC char *
+find_dot_file(char *basename)
+{
+	char *fullname;
+	const char *path = pathval();
+	struct stat64 statb;
+	int len;
+
+	/* don't try this for absolute or relative paths */
+	if (strchr(basename, '/'))
+		return basename;
+
+	while ((len = padvance(&path, basename)) >= 0) {
+		fullname = stackblock();
+		if ((!pathopt || *pathopt == 'f') &&
+		    !stat64(fullname, &statb) && S_ISREG(statb.st_mode)) {
+			/* This will be freed by the caller. */
+			return stalloc(len);
+		}
+	}
+
+	/* not found in the PATH */
+	sh_error("%s: not found", basename);
+	/* NOTREACHED */
+}
+
+int
+dotcmd(int argc, char **argv)
+{
+	int status = 0;
+
+	nextopt(nullstr);
+	argv = argptr;
+
+	if (*argv) {
+		char *fullname;
+
+		fullname = find_dot_file(*argv);
+		setinputfile(fullname, INPUT_PUSH_FILE);
+		commandname = fullname;
+		status = cmdloop(0);
+		popfile();
+	}
+
+	return status;
+}
+
+
+int
+exitcmd(int argc, char **argv)
+{
+	if (stoppedjobs())
+		return 0;
+
+	if (argc > 1)
+		savestatus = number(argv[1]);
+
+	exraise(EXEXIT);
+	/* NOTREACHED */
+}
+
+#ifdef mkinit
+INCLUDE "error.h"
+
+FORKRESET {
+	handler = &main_handler;
+}
+#endif
diff --git a/bin/dash/src/main.h b/bin/dash/src/main.h
new file mode 100644
index 00000000..19e49835
--- /dev/null
+++ b/bin/dash/src/main.h
@@ -0,0 +1,54 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)main.h	8.2 (Berkeley) 5/4/95
+ */
+
+#include <errno.h>
+
+/* pid of main shell */
+extern int rootpid;
+/* shell level: 0 for the main shell, 1 for its children, and so on */
+extern int shlvl;
+#define rootshell (!shlvl)
+
+#ifdef __GLIBC__
+/* glibc sucks */
+extern int *dash_errno;
+#undef errno
+#define errno (*dash_errno)
+#endif
+
+void readcmdfile(char *);
+int dotcmd(int, char **);
+int exitcmd(int, char **);
diff --git a/bin/dash/src/memalloc.c b/bin/dash/src/memalloc.c
new file mode 100644
index 00000000..60637da1
--- /dev/null
+++ b/bin/dash/src/memalloc.c
@@ -0,0 +1,300 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "shell.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "machdep.h"
+#include "mystring.h"
+#include "system.h"
+
+/*
+ * Like malloc, but returns an error when out of space.
+ */
+
+pointer
+ckmalloc(size_t nbytes)
+{
+	pointer p;
+
+	p = malloc(nbytes);
+	if (p == NULL)
+		sh_error("Out of space");
+	return p;
+}
+
+
+/*
+ * Same for realloc.
+ */
+
+pointer
+ckrealloc(pointer p, size_t nbytes)
+{
+	p = realloc(p, nbytes);
+	if (p == NULL)
+		sh_error("Out of space");
+	return p;
+}
+
+
+/*
+ * Make a copy of a string in safe storage.
+ */
+
+char *
+savestr(const char *s)
+{
+	char *p = strdup(s);
+	if (!p)
+		sh_error("Out of space");
+	return p;
+}
+
+
+/*
+ * Parse trees for commands are allocated in lifo order, so we use a stack
+ * to make this more efficient, and also to avoid all sorts of exception
+ * handling code to handle interrupts in the middle of a parse.
+ *
+ * The size 504 was chosen because the Ultrix malloc handles that size
+ * well.
+ */
+
+/* minimum size of a block */
+#define MINSIZE SHELL_ALIGN(504)
+
+struct stack_block {
+	struct stack_block *prev;
+	char space[MINSIZE];
+};
+
+struct stack_block stackbase;
+struct stack_block *stackp = &stackbase;
+char *stacknxt = stackbase.space;
+size_t stacknleft = MINSIZE;
+char *sstrend = stackbase.space + MINSIZE;
+
+pointer
+stalloc(size_t nbytes)
+{
+	char *p;
+	size_t aligned;
+
+	aligned = SHELL_ALIGN(nbytes);
+	if (aligned > stacknleft) {
+		size_t len;
+		size_t blocksize;
+		struct stack_block *sp;
+
+		blocksize = aligned;
+		if (blocksize < MINSIZE)
+			blocksize = MINSIZE;
+		len = sizeof(struct stack_block) - MINSIZE + blocksize;
+		if (len < blocksize)
+			sh_error("Out of space");
+		INTOFF;
+		sp = ckmalloc(len);
+		sp->prev = stackp;
+		stacknxt = sp->space;
+		stacknleft = blocksize;
+		sstrend = stacknxt + blocksize;
+		stackp = sp;
+		INTON;
+	}
+	p = stacknxt;
+	stacknxt += aligned;
+	stacknleft -= aligned;
+	return p;
+}
+
+
+void
+stunalloc(pointer p)
+{
+#ifdef DEBUG
+	if (!p || (stacknxt < (char *)p) || ((char *)p < stackp->space)) {
+		write(2, "stunalloc\n", 10);
+		abort();
+	}
+#endif
+	stacknleft += stacknxt - (char *)p;
+	stacknxt = p;
+}
+
+
+
+void pushstackmark(struct stackmark *mark, size_t len)
+{
+	mark->stackp = stackp;
+	mark->stacknxt = stacknxt;
+	mark->stacknleft = stacknleft;
+	grabstackblock(len);
+}
+
+void setstackmark(struct stackmark *mark)
+{
+	pushstackmark(mark, stacknxt == stackp->space && stackp != &stackbase);
+}
+
+
+void
+popstackmark(struct stackmark *mark)
+{
+	struct stack_block *sp;
+
+	INTOFF;
+	while (stackp != mark->stackp) {
+		sp = stackp;
+		stackp = sp->prev;
+		ckfree(sp);
+	}
+	stacknxt = mark->stacknxt;
+	stacknleft = mark->stacknleft;
+	sstrend = mark->stacknxt + mark->stacknleft;
+	INTON;
+}
+
+
+/*
+ * When the parser reads in a string, it wants to stick the string on the
+ * stack and only adjust the stack pointer when it knows how big the
+ * string is.  Stackblock (defined in stack.h) returns a pointer to a block
+ * of space on top of the stack and stackblocklen returns the length of
+ * this block.  Growstackblock will grow this space by at least one byte,
+ * possibly moving it (like realloc).  Grabstackblock actually allocates the
+ * part of the block that has been used.
+ */
+
+static void growstackblock(size_t min)
+{
+	size_t newlen;
+
+	newlen = stacknleft * 2;
+	if (newlen < stacknleft)
+		sh_error("Out of space");
+	min = SHELL_ALIGN(min | 128);
+	if (newlen < min)
+		newlen += min;
+
+	if (stacknxt == stackp->space && stackp != &stackbase) {
+		struct stack_block *sp;
+		struct stack_block *prevstackp;
+		size_t grosslen;
+
+		INTOFF;
+		sp = stackp;
+		prevstackp = sp->prev;
+		grosslen = newlen + sizeof(struct stack_block) - MINSIZE;
+		sp = ckrealloc((pointer)sp, grosslen);
+		sp->prev = prevstackp;
+		stackp = sp;
+		stacknxt = sp->space;
+		stacknleft = newlen;
+		sstrend = sp->space + newlen;
+		INTON;
+	} else {
+		char *oldspace = stacknxt;
+		int oldlen = stacknleft;
+		char *p = stalloc(newlen);
+
+		/* free the space we just allocated */
+		stacknxt = memcpy(p, oldspace, oldlen);
+		stacknleft += newlen;
+	}
+}
+
+/*
+ * The following routines are somewhat easier to use than the above.
+ * The user declares a variable of type STACKSTR, which may be declared
+ * to be a register.  The macro STARTSTACKSTR initializes things.  Then
+ * the user uses the macro STPUTC to add characters to the string.  In
+ * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is
+ * grown as necessary.  When the user is done, she can just leave the
+ * string there and refer to it using stackblock().  Or she can allocate
+ * the space for it using grabstackstr().  If it is necessary to allow
+ * someone else to use the stack temporarily and then continue to grow
+ * the string, the user should use grabstack to allocate the space, and
+ * then call ungrabstr(p) to return to the previous mode of operation.
+ *
+ * USTPUTC is like STPUTC except that it doesn't check for overflow.
+ * CHECKSTACKSPACE can be called before USTPUTC to ensure that there
+ * is space for at least one character.
+ */
+
+void *
+growstackstr(void)
+{
+	size_t len = stackblocksize();
+
+	growstackblock(0);
+	return stackblock() + len;
+}
+
+char *growstackto(size_t len)
+{
+	if (stackblocksize() < len)
+		growstackblock(len);
+	return stackblock();
+}
+
+/*
+ * Called from CHECKSTRSPACE.
+ */
+
+char *
+makestrspace(size_t newlen, char *p)
+{
+	size_t len = p - stacknxt;
+
+	return growstackto(len + newlen) + len;
+}
+
+char *
+stnputs(const char *s, size_t n, char *p)
+{
+	p = makestrspace(n, p);
+	p = mempcpy(p, s, n);
+	return p;
+}
+
+char *
+stputs(const char *s, char *p)
+{
+	return stnputs(s, strlen(s), p);
+}
diff --git a/bin/dash/src/memalloc.h b/bin/dash/src/memalloc.h
new file mode 100644
index 00000000..b9adf764
--- /dev/null
+++ b/bin/dash/src/memalloc.h
@@ -0,0 +1,101 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)memalloc.h	8.2 (Berkeley) 5/4/95
+ */
+
+#include <stddef.h>
+#include <stdlib.h>
+
+struct stackmark {
+	struct stack_block *stackp;
+	char *stacknxt;
+	size_t stacknleft;
+};
+
+
+extern char *stacknxt;
+extern size_t stacknleft;
+extern char *sstrend;
+
+pointer ckmalloc(size_t);
+pointer ckrealloc(pointer, size_t);
+char *savestr(const char *);
+pointer stalloc(size_t);
+void stunalloc(pointer);
+void pushstackmark(struct stackmark *mark, size_t len);
+void setstackmark(struct stackmark *);
+void popstackmark(struct stackmark *);
+void *growstackstr(void);
+char *growstackto(size_t len);
+char *makestrspace(size_t, char *);
+char *stnputs(const char *, size_t, char *);
+char *stputs(const char *, char *);
+
+
+static inline void grabstackblock(size_t len)
+{
+	stalloc(len);
+}
+
+static inline char *_STPUTC(int c, char *p) {
+	if (p == sstrend)
+		p = growstackstr();
+	*p++ = c;
+	return p;
+}
+
+#define stackblock() ((void *)stacknxt)
+#define stackblocksize() stacknleft
+#define STARTSTACKSTR(p) ((p) = stackblock())
+#define STPUTC(c, p) ((p) = _STPUTC((c), (p)))
+#define CHECKSTRSPACE(n, p) \
+	({ \
+		char *q = (p); \
+		size_t l = (n); \
+		size_t m = sstrend - q; \
+		if (l > m) \
+			(p) = makestrspace(l, q); \
+		0; \
+	})
+#define USTPUTC(c, p)	(*p++ = (c))
+#define STACKSTRNUL(p)	((p) == sstrend? (p = growstackstr(), *p = '\0') : (*p = '\0'))
+#define STUNPUTC(p)	(--p)
+#define STTOPC(p)	p[-1]
+#define STADJUST(amount, p)	(p += (amount))
+
+#define grabstackstr(p)	stalloc((char *)(p) - (char *)stackblock())
+#define ungrabstackstr(s, p) stunalloc((s))
+#define stackstrend() ((void *)sstrend)
+
+#define ckfree(p)	free((pointer)(p))
diff --git a/bin/dash/src/miscbltin.c b/bin/dash/src/miscbltin.c
new file mode 100644
index 00000000..5ccbbcb8
--- /dev/null
+++ b/bin/dash/src/miscbltin.c
@@ -0,0 +1,506 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Miscelaneous builtins.
+ */
+
+#include <sys/types.h>		/* quad_t */
+#include <sys/param.h>		/* BSD4_4 */
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "shell.h"
+#include "options.h"
+#include "var.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "miscbltin.h"
+#include "mystring.h"
+#include "main.h"
+#include "expand.h"
+#include "parser.h"
+#include "trap.h"
+
+#undef rflag
+
+
+/** handle one line of the read command.
+ *  more fields than variables -> remainder shall be part of last variable.
+ *  less fields than variables -> remaining variables unset.
+ *
+ *  @param line complete line of input
+ *  @param ac argument count
+ *  @param ap argument (variable) list
+ *  @param len length of line including trailing '\0'
+ */
+static void
+readcmd_handle_line(char *s, int ac, char **ap)
+{
+	struct arglist arglist;
+	struct strlist *sl;
+
+	s = grabstackstr(s);
+
+	arglist.lastp = &arglist.list;
+	
+	ifsbreakup(s, ac, &arglist);
+	*arglist.lastp = NULL;
+	ifsfree();
+
+	sl = arglist.list;
+
+	do {
+		if (!sl) {
+			/* nullify remaining arguments */
+			do {
+				setvar(*ap, nullstr, 0);
+			} while (*++ap);
+
+			return;
+		}
+
+		/* set variable to field */
+		rmescapes(sl->text);
+		setvar(*ap, sl->text, 0);
+		sl = sl->next;
+	} while (*++ap);
+}
+
+/*
+ * The read builtin.  The -e option causes backslashes to escape the
+ * following character. The -p option followed by an argument prompts
+ * with the argument.
+ *
+ * This uses unbuffered input, which may be avoidable in some cases.
+ */
+
+int
+readcmd(int argc, char **argv)
+{
+	char **ap;
+	char c;
+	int rflag;
+	char *prompt;
+	char *p;
+	int startloc;
+	int newloc;
+	int status;
+	int i;
+
+	rflag = 0;
+	prompt = NULL;
+	while ((i = nextopt("p:r")) != '\0') {
+		if (i == 'p')
+			prompt = optionarg;
+		else
+			rflag = 1;
+	}
+	if (prompt && isatty(0)) {
+		out2str(prompt);
+#ifdef FLUSHERR
+		flushall();
+#endif
+	}
+	if (*(ap = argptr) == NULL)
+		sh_error("arg count");
+
+	status = 0;
+	STARTSTACKSTR(p);
+
+	goto start;
+
+	for (;;) {
+		switch (read(0, &c, 1)) {
+		case 1:
+			break;
+		default:
+			if (errno == EINTR && !pending_sig)
+				continue;
+				/* fall through */
+		case 0:
+			status = 1;
+			goto out;
+		}
+		if (c == '\0')
+			continue;
+		if (newloc >= startloc) {
+			if (c == '\n')
+				goto resetbs;
+			goto put;
+		}
+		if (!rflag && c == '\\') {
+			newloc = p - (char *)stackblock();
+			continue;
+		}
+		if (c == '\n')
+			break;
+put:
+		CHECKSTRSPACE(2, p);
+		if (strchr(qchars, c))
+			USTPUTC(CTLESC, p);
+		USTPUTC(c, p);
+
+		if (newloc >= startloc) {
+resetbs:
+			recordregion(startloc, newloc, 0);
+start:
+			startloc = p - (char *)stackblock();
+			newloc = startloc - 1;
+		}
+	}
+out:
+	recordregion(startloc, p - (char *)stackblock(), 0);
+	STACKSTRNUL(p);
+	readcmd_handle_line(p + 1, argc - (ap - argv), ap);
+	return status;
+}
+
+
+
+/*
+ * umask builtin
+ *
+ * This code was ripped from pdksh 5.2.14 and hacked for use with
+ * dash by Herbert Xu.
+ *
+ * Public domain.
+ */
+
+int
+umaskcmd(int argc, char **argv)
+{
+	char *ap;
+	int mask;
+	int i;
+	int symbolic_mode = 0;
+
+	while ((i = nextopt("S")) != '\0') {
+		symbolic_mode = 1;
+	}
+
+	INTOFF;
+	mask = umask(0);
+	umask(mask);
+	INTON;
+
+	if ((ap = *argptr) == NULL) {
+		if (symbolic_mode) {
+			char buf[18];
+			int j;
+
+			mask = ~mask;
+			ap = buf;
+			for (i = 0; i < 3; i++) {
+				*ap++ = "ugo"[i];
+				*ap++ = '=';
+				for (j = 0; j < 3; j++)
+					if (mask & (1 << (8 - (3*i + j))))
+						*ap++ = "rwx"[j];
+				*ap++ = ',';
+			}
+			ap[-1] = '\0';
+			out1fmt("%s\n", buf);
+		} else {
+			out1fmt("%.4o\n", mask);
+		}
+	} else {
+		int new_mask;
+
+		if (isdigit((unsigned char) *ap)) {
+			new_mask = 0;
+			do {
+				if (*ap >= '8' || *ap < '0')
+					sh_error(illnum, *argptr);
+				new_mask = (new_mask << 3) + (*ap - '0');
+			} while (*++ap != '\0');
+		} else {
+			int positions, new_val;
+			char op;
+
+			mask = ~mask;
+			new_mask = mask;
+			positions = 0;
+			while (*ap) {
+				while (*ap && strchr("augo", *ap))
+					switch (*ap++) {
+					case 'a': positions |= 0111; break;
+					case 'u': positions |= 0100; break;
+					case 'g': positions |= 0010; break;
+					case 'o': positions |= 0001; break;
+					}
+				if (!positions)
+					positions = 0111; /* default is a */
+				if (!strchr("=+-", op = *ap))
+					break;
+				ap++;
+				new_val = 0;
+				while (*ap && strchr("rwxugoXs", *ap))
+					switch (*ap++) {
+					case 'r': new_val |= 04; break;
+					case 'w': new_val |= 02; break;
+					case 'x': new_val |= 01; break;
+					case 'u': new_val |= mask >> 6;
+						  break;
+					case 'g': new_val |= mask >> 3;
+						  break;
+					case 'o': new_val |= mask >> 0;
+						  break;
+					case 'X': if (mask & 0111)
+							new_val |= 01;
+						  break;
+					case 's': /* ignored */
+						  break;
+					}
+				new_val = (new_val & 07) * positions;
+				switch (op) {
+				case '-':
+					new_mask &= ~new_val;
+					break;
+				case '=':
+					new_mask = new_val
+					    | (new_mask & ~(positions * 07));
+					break;
+				case '+':
+					new_mask |= new_val;
+				}
+				if (*ap == ',') {
+					positions = 0;
+					ap++;
+				} else if (!strchr("=+-", *ap))
+					break;
+			}
+			if (*ap) {
+				sh_error("Illegal mode: %s", *argptr);
+				return 1;
+			}
+			new_mask = ~new_mask;
+		}
+		umask(new_mask);
+	}
+	return 0;
+}
+
+#ifdef HAVE_GETRLIMIT
+/*
+ * ulimit builtin
+ *
+ * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and
+ * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with
+ * ash by J.T. Conklin.
+ *
+ * Public domain.
+ */
+
+struct limits {
+	const char *name;
+	int	cmd;
+	int	factor;	/* multiply by to get rlim_{cur,max} values */
+	char	option;
+};
+
+static const struct limits limits[] = {
+#ifdef RLIMIT_CPU
+	{ "time(seconds)",		RLIMIT_CPU,	   1, 't' },
+#endif
+#ifdef RLIMIT_FSIZE
+	{ "file(blocks)",		RLIMIT_FSIZE,	 512, 'f' },
+#endif
+#ifdef RLIMIT_DATA
+	{ "data(kbytes)",		RLIMIT_DATA,	1024, 'd' },
+#endif
+#ifdef RLIMIT_STACK
+	{ "stack(kbytes)",		RLIMIT_STACK,	1024, 's' },
+#endif
+#ifdef RLIMIT_CORE
+	{ "coredump(blocks)",		RLIMIT_CORE,	 512, 'c' },
+#endif
+#ifdef RLIMIT_RSS
+	{ "memory(kbytes)",		RLIMIT_RSS,	1024, 'm' },
+#endif
+#ifdef RLIMIT_MEMLOCK
+	{ "locked memory(kbytes)",	RLIMIT_MEMLOCK, 1024, 'l' },
+#endif
+#ifdef RLIMIT_NPROC
+	{ "process",			RLIMIT_NPROC,      1, 'p' },
+#endif
+#ifdef RLIMIT_NOFILE
+	{ "nofiles",			RLIMIT_NOFILE,     1, 'n' },
+#endif
+#ifdef RLIMIT_AS
+	{ "vmemory(kbytes)",		RLIMIT_AS,	1024, 'v' },
+#endif
+#ifdef RLIMIT_LOCKS
+	{ "locks",			RLIMIT_LOCKS,	   1, 'w' },
+#endif
+#ifdef RLIMIT_RTPRIO
+	{ "rtprio",			RLIMIT_RTPRIO,	   1, 'r' },
+#endif
+	{ (char *) 0,			0,		   0,  '\0' }
+};
+
+enum limtype { SOFT = 0x1, HARD = 0x2 };
+
+static void printlim(enum limtype how, const struct rlimit *limit,
+		     const struct limits *l)
+{
+	rlim_t val;
+
+	val = limit->rlim_max;
+	if (how & SOFT)
+		val = limit->rlim_cur;
+
+	if (val == RLIM_INFINITY)
+		out1fmt("unlimited\n");
+	else {
+		val /= l->factor;
+		out1fmt("%" PRIdMAX "\n", (intmax_t) val);
+	}
+}
+
+int
+ulimitcmd(int argc, char **argv)
+{
+	int	c;
+	rlim_t val = 0;
+	enum limtype how = SOFT | HARD;
+	const struct limits	*l;
+	int		set, all = 0;
+	int		optc, what;
+	struct rlimit	limit;
+
+	what = 'f';
+	while ((optc = nextopt("HSa"
+#ifdef RLIMIT_CPU
+			       "t"
+#endif
+#ifdef RLIMIT_FSIZE
+			       "f"
+#endif
+#ifdef RLIMIT_DATA
+			       "d"
+#endif
+#ifdef RLIMIT_STACK
+			       "s"
+#endif
+#ifdef RLIMIT_CORE
+			       "c"
+#endif
+#ifdef RLIMIT_RSS
+			       "m"
+#endif
+#ifdef RLIMIT_MEMLOCK
+			       "l"
+#endif
+#ifdef RLIMIT_NPROC
+			       "p"
+#endif
+#ifdef RLIMIT_NOFILE
+			       "n"
+#endif
+#ifdef RLIMIT_AS
+			       "v"
+#endif
+#ifdef RLIMIT_LOCKS
+			       "w"
+#endif
+	)) != '\0')
+		switch (optc) {
+		case 'H':
+			how = HARD;
+			break;
+		case 'S':
+			how = SOFT;
+			break;
+		case 'a':
+			all = 1;
+			break;
+		default:
+			what = optc;
+		}
+
+	for (l = limits; l->option != what; l++)
+		;
+
+	set = *argptr ? 1 : 0;
+	if (set) {
+		char *p = *argptr;
+
+		if (all || argptr[1])
+			sh_error("too many arguments");
+		if (strcmp(p, "unlimited") == 0)
+			val = RLIM_INFINITY;
+		else {
+			val = (rlim_t) 0;
+
+			while ((c = *p++) >= '0' && c <= '9')
+			{
+				val = (val * 10) + (long)(c - '0');
+				if (val < (rlim_t) 0)
+					break;
+			}
+			if (c)
+				sh_error("bad number");
+			val *= l->factor;
+		}
+	}
+	if (all) {
+		for (l = limits; l->name; l++) {
+			getrlimit(l->cmd, &limit);
+			out1fmt("%-20s ", l->name);
+			printlim(how, &limit, l);
+		}
+		return 0;
+	}
+
+	getrlimit(l->cmd, &limit);
+	if (set) {
+		if (how & HARD)
+			limit.rlim_max = val;
+		if (how & SOFT)
+			limit.rlim_cur = val;
+		if (setrlimit(l->cmd, &limit) < 0)
+			sh_error("error setting limit (%s)", strerror(errno));
+	} else {
+		printlim(how, &limit, l);
+	}
+	return 0;
+}
+#endif
diff --git a/bin/dash/src/miscbltin.h b/bin/dash/src/miscbltin.h
new file mode 100644
index 00000000..dd9a8d1c
--- /dev/null
+++ b/bin/dash/src/miscbltin.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 1997 Christos Zoulas.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+int readcmd(int, char **);
+int umaskcmd(int, char **);
+int ulimitcmd(int, char **);
diff --git a/bin/dash/src/mkbuiltins b/bin/dash/src/mkbuiltins
new file mode 100644
index 00000000..f1f25932
--- /dev/null
+++ b/bin/dash/src/mkbuiltins
@@ -0,0 +1,120 @@
+#!/bin/sh -
+#	$NetBSD: mkbuiltins,v 1.17 2002/11/24 22:35:41 christos Exp $
+#
+# Copyright (c) 1991, 1993
+#	The Regents of the University of California.  All rights reserved.
+# Copyright (c) 1997-2005
+#	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#	@(#)mkbuiltins	8.2 (Berkeley) 5/4/95
+
+tempfile=mktemp
+if ! type tempfile > /dev/null 2>&1 && ! type mktemp > /dev/null 2>&1; then
+	_my_tempfile()
+	{
+		local index=0
+		while test -f "${TMPDIR:-/tmp}/builtin.$$.$index"; do
+			index=`expr $index + 1`
+		done
+
+		touch "${TMPDIR:-/tmp}/builtin.$$.$index"
+		echo "${TMPDIR:-/tmp}/builtin.$$.$index"
+	}
+
+	tempfile="_my_tempfile"
+elif ! type tempfile > /dev/null 2>&1; then
+	tempfile="mktemp ${TMPDIR:-/tmp}/builtin.XXXXXX"
+fi
+
+trap 'rm -f $temp $temp2' EXIT
+temp=$($tempfile)
+temp2=$($tempfile)
+
+builtins=$1
+
+exec > builtins.c
+cat <<\!
+/*
+ * This file was generated by the mkbuiltins program.
+ */
+
+#include "shell.h"
+#include "builtins.h"
+
+!
+< $builtins sed '/^#/d; /^ *$/d' > $temp
+awk '{	printf "int %s(int, char **);\n", $1}' $temp
+echo '
+const struct builtincmd builtincmd[] = {'
+awk '{	for (i = 2 ; i <= NF ; i++) {
+		line = $i "\t" $1
+		if ($i ~ /^-/)
+			line = $(++i) "\t" line
+		print line
+	}}' $temp | LC_ALL= LC_COLLATE=C sort -k 1,1 | tee $temp2 | awk '{
+		opt = ""
+		if (NF > 2) {
+			opt = substr($2, 2)
+			$2 = $3
+		}
+		mask = 0
+		cmd = $2
+		if (opt ~ /n/) { cmd = "NULL" }
+		if (opt ~ /s/) { mask += 1 }
+		if (opt ~ /[su]/) { mask += 2 }
+		if (opt ~ /a/) { mask += 4 }
+		printf "\t{ \"%s\", %s, %d },\n", $1, cmd, mask
+	}'
+echo '};'
+
+exec > builtins.h
+cat <<\!
+/*
+ * This file was generated by the mkbuiltins program.
+ */
+
+!
+sed 's/	-[a-z]*//' $temp2 | nl -ba -v0 |
+	LC_ALL= LC_COLLATE=C sort -u -k 3,3 |
+	tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ |
+	awk '{	printf "#define %s (builtincmd + %d)\n", $3, $1}'
+printf '\n#define NUMBUILTINS %d\n' $(wc -l < $temp2)
+echo '
+#define BUILTIN_SPECIAL 0x1
+#define BUILTIN_REGULAR 0x2
+#define BUILTIN_ASSIGN 0x4
+
+struct builtincmd {
+	const char *name;
+	int (*builtin)(int, char **);
+	unsigned flags;
+};
+
+extern const struct builtincmd builtincmd[];'
diff --git a/bin/dash/src/mkinit.c b/bin/dash/src/mkinit.c
new file mode 100644
index 00000000..9025862e
--- /dev/null
+++ b/bin/dash/src/mkinit.c
@@ -0,0 +1,493 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This program scans all the source files for code to handle various
+ * special events and combines this code into one file.  This (allegedly)
+ * improves the structure of the program since there is no need for
+ * anyone outside of a module to know that that module performs special
+ * operations on particular events.
+ *
+ * Usage:  mkinit sourcefile...
+ */
+
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+
+/*
+ * OUTFILE is the name of the output file.  Output is initially written
+ * to the file OUTTEMP, which is then moved to OUTFILE.
+ */
+
+#define OUTFILE "init.c"
+#define OUTTEMP "init.c.new"
+
+
+/*
+ * A text structure is basicly just a string that grows as more characters
+ * are added onto the end of it.  It is implemented as a linked list of
+ * blocks of characters.  The routines addstr and addchar append a string
+ * or a single character, respectively, to a text structure.  Writetext
+ * writes the contents of a text structure to a file.
+ */
+
+#define BLOCKSIZE 512
+
+struct text {
+	char *nextc;
+	int nleft;
+	struct block *start;
+	struct block *last;
+};
+
+struct block {
+	struct block *next;
+	char text[BLOCKSIZE];
+};
+
+
+/*
+ * There is one event structure for each event that mkinit handles.
+ */
+
+struct event {
+	char *name;		/* name of event (e.g. INIT) */
+	char *routine;		/* name of routine called on event */
+	char *comment;		/* comment describing routine */
+	struct text code;	/* code for handling event */
+};
+
+
+char writer[] = "\
+/*\n\
+ * This file was generated by the mkinit program.\n\
+ */\n\
+\n";
+
+char init[] = "\
+/*\n\
+ * Initialization code.\n\
+ */\n";
+
+char exitreset[] = "\
+/*\n\
+ * This routine is called when an error or an interrupt occurs in an\n\
+ * interactive shell and control is returned to the main command loop\n\
+ * but prior to exitshell. \n\
+ */\n";
+
+char forkreset[] = "\
+/*\n\
+ * This routine is called when we enter a subshell.\n\
+ */\n";
+
+char reset[] = "\
+/*\n\
+ * This routine is called when an error or an interrupt occurs in an\n\
+ * interactive shell and control is returned to the main command loop.\n\
+ */\n";
+
+
+struct event event[] = {
+	{"INIT", "init", init},
+	{"EXITRESET", "exitreset", exitreset},
+	{"FORKRESET", "forkreset", forkreset},
+	{"RESET", "reset", reset},
+	{NULL, NULL}
+};
+
+
+char *curfile;				/* current file */
+int linno;				/* current line */
+char *header_files[200];		/* list of header files */
+struct text defines;			/* #define statements */
+struct text decls;			/* declarations */
+int amiddecls;				/* for formatting */
+
+
+void readfile(char *);
+int match(char *, char *);
+int gooddefine(char *);
+void doevent(struct event *, FILE *, char *);
+void doinclude(char *);
+void dodecl(char *, FILE *);
+void output(void);
+void addstr(char *, struct text *);
+void addchar(int, struct text *);
+void writetext(struct text *, FILE *);
+FILE *ckfopen(char *, char *);
+void *ckmalloc(int);
+char *savestr(char *);
+static void error(char *);
+int main(int, char **);
+
+#define equal(s1, s2)	(strcmp(s1, s2) == 0)
+
+int
+main(int argc, char **argv)
+{
+	char **ap;
+
+	header_files[0] = "\"shell.h\"";
+	header_files[1] = "\"mystring.h\"";
+	header_files[2] = "\"init.h\"";
+	for (ap = argv + 1 ; *ap ; ap++)
+		readfile(*ap);
+	output();
+	rename(OUTTEMP, OUTFILE);
+	exit(0);
+	/* NOTREACHED */
+}
+
+
+/*
+ * Parse an input file.
+ */
+
+void
+readfile(char *fname)
+{
+	FILE *fp;
+	char line[1024];
+	struct event *ep;
+
+	fp = ckfopen(fname, "r");
+	curfile = fname;
+	linno = 0;
+	amiddecls = 0;
+	while (fgets(line, sizeof line, fp) != NULL) {
+		linno++;
+		for (ep = event ; ep->name ; ep++) {
+			if (line[0] == ep->name[0] && match(ep->name, line)) {
+				doevent(ep, fp, fname);
+				break;
+			}
+		}
+		if (line[0] == 'I' && match("INCLUDE", line))
+			doinclude(line);
+		if (line[0] == 'M' && match("MKINIT", line))
+			dodecl(line, fp);
+		if (line[0] == '#' && gooddefine(line)) {
+		        char *cp;
+			char line2[1024];
+			static const char undef[] = "#undef ";
+
+			strcpy(line2, line);
+			memcpy(line2, undef, sizeof(undef) - 1);
+			cp = line2 + sizeof(undef) - 1;
+			while(*cp && (*cp == ' ' || *cp == '\t'))
+			        cp++;
+			while(*cp && *cp != ' ' && *cp != '\t' && *cp != '\n')
+			        cp++;
+			*cp++ = '\n'; *cp = '\0';
+			addstr(line2, &defines);
+			addstr(line, &defines);
+		}
+	}
+	fclose(fp);
+}
+
+
+int
+match(char *name, char *line)
+{
+	char *p, *q;
+
+	p = name, q = line;
+	while (*p) {
+		if (*p++ != *q++)
+			return 0;
+	}
+	if (*q != '{' && *q != ' ' && *q != '\t' && *q != '\n')
+		return 0;
+	return 1;
+}
+
+
+int
+gooddefine(char *line)
+{
+	char *p;
+
+	if (! match("#define", line))
+		return 0;			/* not a define */
+	p = line + 7;
+	while (*p == ' ' || *p == '\t')
+		p++;
+	while (*p != ' ' && *p != '\t') {
+		if (*p == '(')
+			return 0;		/* macro definition */
+		p++;
+	}
+	while (*p != '\n' && *p != '\0')
+		p++;
+	if (p[-1] == '\\')
+		return 0;			/* multi-line definition */
+	return 1;
+}
+
+
+void
+doevent(struct event *ep, FILE *fp, char *fname)
+{
+	char line[1024];
+	int indent;
+	char *p;
+
+	sprintf(line, "\n      /* from %s: */\n", fname);
+	addstr(line, &ep->code);
+	addstr("      {\n", &ep->code);
+	for (;;) {
+		linno++;
+		if (fgets(line, sizeof line, fp) == NULL)
+			error("Unexpected EOF");
+		if (equal(line, "}\n"))
+			break;
+		indent = 6;
+		for (p = line ; *p == '\t' ; p++)
+			indent += 8;
+		for ( ; *p == ' ' ; p++)
+			indent++;
+		if (*p == '\n' || *p == '#')
+			indent = 0;
+		while (indent >= 8) {
+			addchar('\t', &ep->code);
+			indent -= 8;
+		}
+		while (indent > 0) {
+			addchar(' ', &ep->code);
+			indent--;
+		}
+		addstr(p, &ep->code);
+	}
+	addstr("      }\n", &ep->code);
+}
+
+
+void
+doinclude(char *line)
+{
+	char *p;
+	char *name;
+	char **pp;
+
+	for (p = line ; *p != '"' && *p != '<' && *p != '\0' ; p++);
+	if (*p == '\0')
+		error("Expecting '\"' or '<'");
+	name = p;
+	while (*p != ' ' && *p != '\t' && *p != '\n')
+		p++;
+	if (p[-1] != '"' && p[-1] != '>')
+		error("Missing terminator");
+	*p = '\0';
+
+	/* name now contains the name of the include file */
+	for (pp = header_files ; *pp && ! equal(*pp, name) ; pp++);
+	if (*pp == NULL)
+		*pp = savestr(name);
+}
+
+
+void
+dodecl(char *line1, FILE *fp)
+{
+	char line[1024];
+	char *p, *q;
+
+	if (strcmp(line1, "MKINIT\n") == 0) { /* start of struct/union decl */
+		addchar('\n', &decls);
+		do {
+			linno++;
+			if (fgets(line, sizeof line, fp) == NULL)
+				error("Unterminated structure declaration");
+			addstr(line, &decls);
+		} while (line[0] != '}');
+		amiddecls = 0;
+	} else {
+		if (! amiddecls)
+			addchar('\n', &decls);
+		q = NULL;
+		for (p = line1 + 6 ; *p && strchr("=/\n", *p) == NULL; p++)
+			continue;
+		if (*p == '=') {		/* eliminate initialization */
+			for (q = p ; *q && *q != ';' ; q++);
+			if (*q == '\0')
+				q = NULL;
+			else {
+				while (p[-1] == ' ')
+					p--;
+				*p = '\0';
+			}
+		}
+		addstr("extern", &decls);
+		addstr(line1 + 6, &decls);
+		if (q != NULL)
+			addstr(q, &decls);
+		amiddecls = 1;
+	}
+}
+
+
+
+/*
+ * Write the output to the file OUTTEMP.
+ */
+
+void
+output(void)
+{
+	FILE *fp;
+	char **pp;
+	struct event *ep;
+
+	fp = ckfopen(OUTTEMP, "w");
+	fputs(writer, fp);
+	for (pp = header_files ; *pp ; pp++)
+		fprintf(fp, "#include %s\n", *pp);
+	fputs("\n\n\n", fp);
+	writetext(&defines, fp);
+	fputs("\n\n", fp);
+	writetext(&decls, fp);
+	for (ep = event ; ep->name ; ep++) {
+		fputs("\n\n\n", fp);
+		fputs(ep->comment, fp);
+		fprintf(fp, "\nvoid\n%s() {\n", ep->routine);
+		writetext(&ep->code, fp);
+		fprintf(fp, "}\n");
+	}
+	fclose(fp);
+}
+
+
+/*
+ * A text structure is simply a block of text that is kept in memory.
+ * Addstr appends a string to the text struct, and addchar appends a single
+ * character.
+ */
+
+void
+addstr(char *s, struct text *text)
+{
+	while (*s) {
+		if (--text->nleft < 0)
+			addchar(*s++, text);
+		else
+			*text->nextc++ = *s++;
+	}
+}
+
+
+void
+addchar(int c, struct text *text)
+{
+	struct block *bp;
+
+	if (--text->nleft < 0) {
+		bp = ckmalloc(sizeof *bp);
+		if (text->start == NULL)
+			text->start = bp;
+		else
+			text->last->next = bp;
+		text->last = bp;
+		text->nextc = bp->text;
+		text->nleft = BLOCKSIZE - 1;
+	}
+	*text->nextc++ = c;
+}
+
+/*
+ * Write the contents of a text structure to a file.
+ */
+void
+writetext(struct text *text, FILE *fp)
+{
+	struct block *bp;
+
+	if (text->start != NULL) {
+		for (bp = text->start ; bp != text->last ; bp = bp->next) {
+			if ((fwrite(bp->text, sizeof (char), BLOCKSIZE, fp)) != BLOCKSIZE)
+				error("Can't write data\n");
+		}
+		if ((fwrite(bp->text, sizeof (char), BLOCKSIZE - text->nleft, fp)) != (BLOCKSIZE - text->nleft))
+			error("Can't write data\n");
+	}
+}
+
+FILE *
+ckfopen(char *file, char *mode)
+{
+	FILE *fp;
+
+	if ((fp = fopen(file, mode)) == NULL) {
+		fprintf(stderr, "Can't open %s\n", file);
+		exit(2);
+	}
+	return fp;
+}
+
+void *
+ckmalloc(int nbytes)
+{
+	char *p;
+
+	if ((p = malloc(nbytes)) == NULL)
+		error("Out of space");
+	return p;
+}
+
+char *
+savestr(char *s)
+{
+	char *p;
+
+	p = ckmalloc(strlen(s) + 1);
+	strcpy(p, s);
+	return p;
+}
+
+static void
+error(char *msg)
+{
+	if (curfile != NULL)
+		fprintf(stderr, "%s:%d: ", curfile, linno);
+	fprintf(stderr, "%s\n", msg);
+	exit(2);
+	/* NOTREACHED */
+}
diff --git a/bin/dash/src/mknodes.c b/bin/dash/src/mknodes.c
new file mode 100644
index 00000000..1903a605
--- /dev/null
+++ b/bin/dash/src/mknodes.c
@@ -0,0 +1,448 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This program reads the nodetypes file and nodes.c.pat file.  It generates
+ * the files nodes.h and nodes.c.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#define MAXTYPES 50		/* max number of node types */
+#define MAXFIELDS 20		/* max fields in a structure */
+#define BUFLEN 100		/* size of character buffers */
+
+/* field types */
+#define T_NODE 1		/* union node *field */
+#define T_NODELIST 2		/* struct nodelist *field */
+#define T_STRING 3
+#define T_INT 4			/* int field */
+#define T_OTHER 5		/* other */
+#define T_TEMP 6		/* don't copy this field */
+
+
+struct field {			/* a structure field */
+	char *name;		/* name of field */
+	int type;			/* type of field */
+	char *decl;		/* declaration of field */
+};
+
+
+struct str {			/* struct representing a node structure */
+	char *tag;		/* structure tag */
+	int nfields;		/* number of fields in the structure */
+	struct field field[MAXFIELDS];	/* the fields of the structure */
+	int done;			/* set if fully parsed */
+};
+
+
+static int ntypes;			/* number of node types */
+static char *nodename[MAXTYPES];	/* names of the nodes */
+static struct str *nodestr[MAXTYPES];	/* type of structure used by the node */
+static int nstr;			/* number of structures */
+static struct str str[MAXTYPES];	/* the structures */
+static struct str *curstr;		/* current structure */
+static FILE *infp;
+static char line[1024];
+static int linno;
+static char *linep;
+
+static void parsenode(void);
+static void parsefield(void);
+static void output(char *);
+static void outsizes(FILE *);
+static void outfunc(FILE *, int);
+static void indent(int, FILE *);
+static int nextfield(char *);
+static void skipbl(void);
+static int readline(void);
+static void error(const char *, ...);
+static char *savestr(const char *);
+int main(int, char **);
+
+
+int
+main(int argc, char **argv)
+{
+
+	/*
+	 * some versions of linux complain: initializer element is not
+	 * constant if this is done at compile time.
+	 */
+	infp = stdin;
+
+	if (argc != 3)
+		error("usage: mknodes file");
+	if ((infp = fopen(argv[1], "r")) == NULL)
+		error("Can't open %s", argv[1]);
+	while (readline()) {
+		if (line[0] == ' ' || line[0] == '\t')
+			parsefield();
+		else if (line[0] != '\0')
+			parsenode();
+	}
+	output(argv[2]);
+	exit(0);
+	/* NOTREACHED */
+}
+
+
+
+static void
+parsenode(void)
+{
+	char name[BUFLEN];
+	char tag[BUFLEN];
+	struct str *sp;
+
+	if (curstr && curstr->nfields > 0)
+		curstr->done = 1;
+	nextfield(name);
+	if (! nextfield(tag))
+		error("Tag expected");
+	if (*linep != '\0')
+		error("Garbage at end of line");
+	nodename[ntypes] = savestr(name);
+	for (sp = str ; sp < str + nstr ; sp++) {
+		if (strcmp(sp->tag, tag) == 0)
+			break;
+	}
+	if (sp >= str + nstr) {
+		sp->tag = savestr(tag);
+		sp->nfields = 0;
+		curstr = sp;
+		nstr++;
+	}
+	nodestr[ntypes] = sp;
+	ntypes++;
+}
+
+
+static void
+parsefield(void)
+{
+	char name[BUFLEN];
+	char type[BUFLEN];
+	char decl[2 * BUFLEN];
+	struct field *fp;
+
+	if (curstr == NULL || curstr->done)
+		error("No current structure to add field to");
+	if (! nextfield(name))
+		error("No field name");
+	if (! nextfield(type))
+		error("No field type");
+	fp = &curstr->field[curstr->nfields];
+	fp->name = savestr(name);
+	if (strcmp(type, "nodeptr") == 0) {
+		fp->type = T_NODE;
+		sprintf(decl, "union node *%s", name);
+	} else if (strcmp(type, "nodelist") == 0) {
+		fp->type = T_NODELIST;
+		sprintf(decl, "struct nodelist *%s", name);
+	} else if (strcmp(type, "string") == 0) {
+		fp->type = T_STRING;
+		sprintf(decl, "char *%s", name);
+	} else if (strcmp(type, "int") == 0) {
+		fp->type = T_INT;
+		sprintf(decl, "int %s", name);
+	} else if (strcmp(type, "other") == 0) {
+		fp->type = T_OTHER;
+	} else if (strcmp(type, "temp") == 0) {
+		fp->type = T_TEMP;
+	} else {
+		error("Unknown type %s", type);
+	}
+	if (fp->type == T_OTHER || fp->type == T_TEMP) {
+		skipbl();
+		fp->decl = savestr(linep);
+	} else {
+		if (*linep)
+			error("Garbage at end of line");
+		fp->decl = savestr(decl);
+	}
+	curstr->nfields++;
+}
+
+
+char writer[] = "\
+/*\n\
+ * This file was generated by the mknodes program.\n\
+ */\n\
+\n";
+
+static void
+output(char *file)
+{
+	FILE *hfile;
+	FILE *cfile;
+	FILE *patfile;
+	int i;
+	struct str *sp;
+	struct field *fp;
+	char *p;
+
+	if ((patfile = fopen(file, "r")) == NULL)
+		error("Can't open %s", file);
+	if ((hfile = fopen("nodes.h", "w")) == NULL)
+		error("Can't create nodes.h");
+	if ((cfile = fopen("nodes.c", "w")) == NULL)
+		error("Can't create nodes.c");
+	fputs(writer, hfile);
+	for (i = 0 ; i < ntypes ; i++)
+		fprintf(hfile, "#define %s %d\n", nodename[i], i);
+	fputs("\n\n\n", hfile);
+	for (sp = str ; sp < &str[nstr] ; sp++) {
+		fprintf(hfile, "struct %s {\n", sp->tag);
+		for (i = sp->nfields, fp = sp->field ; --i >= 0 ; fp++) {
+			fprintf(hfile, "      %s;\n", fp->decl);
+		}
+		fputs("};\n\n\n", hfile);
+	}
+	fputs("union node {\n", hfile);
+	fprintf(hfile, "      int type;\n");
+	for (sp = str ; sp < &str[nstr] ; sp++) {
+		fprintf(hfile, "      struct %s %s;\n", sp->tag, sp->tag);
+	}
+	fputs("};\n\n\n", hfile);
+	fputs("struct nodelist {\n", hfile);
+	fputs("\tstruct nodelist *next;\n", hfile);
+	fputs("\tunion node *n;\n", hfile);
+	fputs("};\n\n\n", hfile);
+	fputs("struct funcnode {\n", hfile);
+	fputs("\tint count;\n", hfile);
+	fputs("\tunion node n;\n", hfile);
+	fputs("};\n\n\n", hfile);
+	fputs("struct funcnode *copyfunc(union node *);\n", hfile);
+	fputs("void freefunc(struct funcnode *);\n", hfile);
+
+	fputs(writer, cfile);
+	while (fgets(line, sizeof line, patfile) != NULL) {
+		for (p = line ; *p == ' ' || *p == '\t' ; p++);
+		if (strcmp(p, "%SIZES\n") == 0)
+			outsizes(cfile);
+		else if (strcmp(p, "%CALCSIZE\n") == 0)
+			outfunc(cfile, 1);
+		else if (strcmp(p, "%COPY\n") == 0)
+			outfunc(cfile, 0);
+		else
+			fputs(line, cfile);
+	}
+}
+
+
+
+static void
+outsizes(FILE *cfile)
+{
+	int i;
+
+	fprintf(cfile, "static const short nodesize[%d] = {\n", ntypes);
+	for (i = 0 ; i < ntypes ; i++) {
+		fprintf(cfile, "      SHELL_ALIGN(sizeof (struct %s)),\n",
+		    nodestr[i]->tag);
+	}
+	fprintf(cfile, "};\n");
+}
+
+
+static void
+outfunc(FILE *cfile, int calcsize)
+{
+	struct str *sp;
+	struct field *fp;
+	int i;
+
+	fputs("      if (n == NULL)\n", cfile);
+	if (calcsize)
+		fputs("	    return;\n", cfile);
+	else
+		fputs("	    return NULL;\n", cfile);
+	if (calcsize)
+		fputs("      funcblocksize += nodesize[n->type];\n", cfile);
+	else {
+		fputs("      new = funcblock;\n", cfile);
+		fputs("      funcblock = (char *) funcblock + nodesize[n->type];\n", cfile);
+	}
+	fputs("      switch (n->type) {\n", cfile);
+	for (sp = str ; sp < &str[nstr] ; sp++) {
+		for (i = 0 ; i < ntypes ; i++) {
+			if (nodestr[i] == sp)
+				fprintf(cfile, "      case %s:\n", nodename[i]);
+		}
+		for (i = sp->nfields ; --i >= 1 ; ) {
+			fp = &sp->field[i];
+			switch (fp->type) {
+			case T_NODE:
+				if (calcsize) {
+					indent(12, cfile);
+					fprintf(cfile, "calcsize(n->%s.%s);\n",
+						sp->tag, fp->name);
+				} else {
+					indent(12, cfile);
+					fprintf(cfile, "new->%s.%s = copynode(n->%s.%s);\n",
+						sp->tag, fp->name, sp->tag, fp->name);
+				}
+				break;
+			case T_NODELIST:
+				if (calcsize) {
+					indent(12, cfile);
+					fprintf(cfile, "sizenodelist(n->%s.%s);\n",
+						sp->tag, fp->name);
+				} else {
+					indent(12, cfile);
+					fprintf(cfile, "new->%s.%s = copynodelist(n->%s.%s);\n",
+						sp->tag, fp->name, sp->tag, fp->name);
+				}
+				break;
+			case T_STRING:
+				if (calcsize) {
+					indent(12, cfile);
+					fprintf(cfile, "funcstringsize += strlen(n->%s.%s) + 1;\n",
+						sp->tag, fp->name);
+				} else {
+					indent(12, cfile);
+					fprintf(cfile, "new->%s.%s = nodesavestr(n->%s.%s);\n",
+						sp->tag, fp->name, sp->tag, fp->name);
+				}
+				break;
+			case T_INT:
+			case T_OTHER:
+				if (! calcsize) {
+					indent(12, cfile);
+					fprintf(cfile, "new->%s.%s = n->%s.%s;\n",
+						sp->tag, fp->name, sp->tag, fp->name);
+				}
+				break;
+			}
+		}
+		indent(12, cfile);
+		fputs("break;\n", cfile);
+	}
+	fputs("      };\n", cfile);
+	if (! calcsize)
+		fputs("      new->type = n->type;\n", cfile);
+}
+
+
+static void
+indent(int amount, FILE *fp)
+{
+	while (amount >= 8) {
+		putc('\t', fp);
+		amount -= 8;
+	}
+	while (--amount >= 0) {
+		putc(' ', fp);
+	}
+}
+
+
+static int
+nextfield(char *buf)
+{
+	char *p, *q;
+
+	p = linep;
+	while (*p == ' ' || *p == '\t')
+		p++;
+	q = buf;
+	while (*p != ' ' && *p != '\t' && *p != '\0')
+		*q++ = *p++;
+	*q = '\0';
+	linep = p;
+	return (q > buf);
+}
+
+
+static void
+skipbl(void)
+{
+	while (*linep == ' ' || *linep == '\t')
+		linep++;
+}
+
+
+static int
+readline(void)
+{
+	char *p;
+
+	if (fgets(line, 1024, infp) == NULL)
+		return 0;
+	for (p = line ; *p != '#' && *p != '\n' && *p != '\0' ; p++);
+	while (p > line && (p[-1] == ' ' || p[-1] == '\t'))
+		p--;
+	*p = '\0';
+	linep = line;
+	linno++;
+	if (p - line > BUFLEN)
+		error("Line too long");
+	return 1;
+}
+
+
+
+static void
+error(const char *msg, ...)
+{
+	va_list va;
+
+	va_start(va, msg);
+
+	(void) fprintf(stderr, "line %d: ", linno);
+	(void) vfprintf(stderr, msg, va);
+	(void) fputc('\n', stderr);
+
+	va_end(va);
+
+	exit(2);
+	/* NOTREACHED */
+}
+
+
+
+static char *
+savestr(const char *s)
+{
+	char *p;
+
+	if ((p = malloc(strlen(s) + 1)) == NULL)
+		error("Out of space");
+	(void) strcpy(p, s);
+	return p;
+}
diff --git a/bin/dash/src/mksignames.c b/bin/dash/src/mksignames.c
new file mode 100644
index 00000000..a832eab9
--- /dev/null
+++ b/bin/dash/src/mksignames.c
@@ -0,0 +1,417 @@
+/* signames.c -- Create and write `signames.c', which contains an array of
+   signal names. */
+
+/* Copyright (C) 1992 Free Software Foundation, Inc.
+
+   This file is part of GNU Bash, the Bourne Again SHell.
+
+   Bash is free software; you can redistribute it and/or modify it under
+   the terms of the GNU General Public License as published by the Free
+   Software Foundation; either version 2, or (at your option) any later
+   version.
+
+   Bash is distributed in the hope that it will be useful, but WITHOUT ANY
+   WARRANTY; without even the implied warranty of MERCHANTABILITY or
+   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+   for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with Bash; see the file COPYING.  If not, write to the Free Software
+   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <stdlib.h>
+
+#if !defined (NSIG)
+#  define NSIG 64
+#endif
+
+/*
+ * Special traps:
+ *	EXIT == 0
+ */
+#define LASTSIG NSIG-1
+
+char *signal_names[2 * NSIG + 3];
+
+#define signal_names_size (sizeof(signal_names)/sizeof(signal_names[0]))
+
+char *progname;
+
+/* AIX 4.3 defines SIGRTMIN and SIGRTMAX as 888 and 999 respectively.
+   I don't want to allocate so much unused space for the intervening signal
+   numbers, so we just punt if SIGRTMAX is past the bounds of the
+   signal_names array (handled in configure). */
+#if defined (SIGRTMAX) && defined (UNUSABLE_RT_SIGNALS)
+#  undef SIGRTMAX
+#  undef SIGRTMIN
+#endif
+
+#if defined (SIGRTMAX) || defined (SIGRTMIN)
+#  define RTLEN 14
+#  define RTLIM 256
+#endif
+
+void
+initialize_signames ()
+{
+  register int i;
+#if defined (SIGRTMAX) || defined (SIGRTMIN)
+  int rtmin, rtmax, rtcnt;
+#endif
+
+  for (i = 1; i < signal_names_size; i++)
+    signal_names[i] = (char *)NULL;
+
+  /* `signal' 0 is what we do on exit. */
+  signal_names[0] = "EXIT";
+
+  /* Place signal names which can be aliases for more common signal
+     names first.  This allows (for example) SIGABRT to overwrite SIGLOST. */
+
+  /* POSIX 1003.1b-1993 real time signals, but take care of incomplete
+     implementations. Acoording to the standard, both, SIGRTMIN and
+     SIGRTMAX must be defined, SIGRTMIN must be stricly less than
+     SIGRTMAX, and the difference must be at least 7, that is, there
+     must be at least eight distinct real time signals. */
+
+  /* The generated signal names are SIGRTMIN, SIGRTMIN+1, ...,
+     SIGRTMIN+x, SIGRTMAX-x, ..., SIGRTMAX-1, SIGRTMAX. If the number
+     of RT signals is odd, there is an extra SIGRTMIN+(x+1).
+     These names are the ones used by ksh and /usr/xpg4/bin/sh on SunOS5. */
+
+#if defined (SIGRTMIN)
+  rtmin = SIGRTMIN;
+  signal_names[rtmin] = "RTMIN";
+#endif
+
+#if defined (SIGRTMAX)
+  rtmax = SIGRTMAX;
+  signal_names[rtmax] = "RTMAX";
+#endif
+
+#if defined (SIGRTMAX) && defined (SIGRTMIN)
+  if (rtmax > rtmin)
+    {
+      rtcnt = (rtmax - rtmin - 1) / 2;
+      /* croak if there are too many RT signals */
+      if (rtcnt >= RTLIM/2)
+	{
+	  rtcnt = RTLIM/2-1;
+	  fprintf(stderr, "%s: error: more than %i real time signals, fix `%s'\n",
+		  progname, RTLIM, progname);
+	}
+
+      for (i = 1; i <= rtcnt; i++)
+	{
+	  signal_names[rtmin+i] = (char *)malloc(RTLEN);
+	  if (signal_names[rtmin+i])
+	    sprintf (signal_names[rtmin+i], "RTMIN+%d", i);
+	  signal_names[rtmax-i] = (char *)malloc(RTLEN);
+	  if (signal_names[rtmax-i])
+	    sprintf (signal_names[rtmax-i], "RTMAX-%d", i);
+	}
+
+      if (rtcnt < RTLIM/2-1 && rtcnt != (rtmax-rtmin)/2)
+	{
+	  /* Need an extra RTMIN signal */
+	  signal_names[rtmin+rtcnt+1] = (char *)malloc(RTLEN);
+	  if (signal_names[rtmin+rtcnt+1])
+	    sprintf (signal_names[rtmin+rtcnt+1], "RTMIN+%d", rtcnt+1);
+	}
+    }
+#endif /* SIGRTMIN && SIGRTMAX */
+
+/* AIX */
+#if defined (SIGLOST)	/* resource lost (eg, record-lock lost) */
+  signal_names[SIGLOST] = "LOST";
+#endif
+
+#if defined (SIGMSG)	/* HFT input data pending */
+  signal_names[SIGMSG] = "MSG";
+#endif
+
+#if defined (SIGDANGER)	/* system crash imminent */
+  signal_names[SIGDANGER] = "DANGER";
+#endif
+
+#if defined (SIGMIGRATE) /* migrate process to another CPU */
+  signal_names[SIGMIGRATE] = "MIGRATE";
+#endif
+
+#if defined (SIGPRE)	/* programming error */
+  signal_names[SIGPRE] = "PRE";
+#endif
+
+#if defined (SIGVIRT)	/* AIX virtual time alarm */
+  signal_names[SIGVIRT] = "VIRT";
+#endif
+
+#if defined (SIGALRM1)	/* m:n condition variables */
+  signal_names[SIGALRM1] = "ALRM1";
+#endif
+
+#if defined (SIGWAITING)	/* m:n scheduling */
+  signal_names[SIGWAITING] = "WAITING";
+#endif
+
+#if defined (SIGGRANT)	/* HFT monitor mode granted */
+  signal_names[SIGGRANT] = "GRANT";
+#endif
+
+#if defined (SIGKAP)	/* keep alive poll from native keyboard */
+  signal_names[SIGKAP] = "KAP";
+#endif
+
+#if defined (SIGRETRACT) /* HFT monitor mode retracted */
+  signal_names[SIGRETRACT] = "RETRACT";
+#endif
+
+#if defined (SIGSOUND)	/* HFT sound sequence has completed */
+  signal_names[SIGSOUND] = "SOUND";
+#endif
+
+#if defined (SIGSAK)	/* Secure Attention Key */
+  signal_names[SIGSAK] = "SAK";
+#endif
+
+/* SunOS5 */
+#if defined (SIGLWP)	/* special signal used by thread library */
+  signal_names[SIGLWP] = "LWP";
+#endif
+
+#if defined (SIGFREEZE)	/* special signal used by CPR */
+  signal_names[SIGFREEZE] = "FREEZE";
+#endif
+
+#if defined (SIGTHAW)	/* special signal used by CPR */
+  signal_names[SIGTHAW] = "THAW";
+#endif
+
+#if defined (SIGCANCEL)	/* thread cancellation signal used by libthread */
+  signal_names[SIGCANCEL] = "CANCEL";
+#endif
+
+/* HP-UX */
+#if defined (SIGDIL)	/* DIL signal (?) */
+  signal_names[SIGDIL] = "DIL";
+#endif
+
+/* System V */
+#if defined (SIGCLD)	/* Like SIGCHLD.  */
+  signal_names[SIGCLD] = "CLD";
+#endif
+
+#if defined (SIGPWR)	/* power state indication */
+  signal_names[SIGPWR] = "PWR";
+#endif
+
+#if defined (SIGPOLL)	/* Pollable event (for streams)  */
+  signal_names[SIGPOLL] = "POLL";
+#endif
+
+/* Unknown */
+#if defined (SIGWINDOW)
+  signal_names[SIGWINDOW] = "WINDOW";
+#endif
+
+/* Common */
+#if defined (SIGHUP)	/* hangup */
+  signal_names[SIGHUP] = "HUP";
+#endif
+
+#if defined (SIGINT)	/* interrupt */
+  signal_names[SIGINT] = "INT";
+#endif
+
+#if defined (SIGQUIT)	/* quit */
+  signal_names[SIGQUIT] = "QUIT";
+#endif
+
+#if defined (SIGILL)	/* illegal instruction (not reset when caught) */
+  signal_names[SIGILL] = "ILL";
+#endif
+
+#if defined (SIGTRAP)	/* trace trap (not reset when caught) */
+  signal_names[SIGTRAP] = "TRAP";
+#endif
+
+#if defined (SIGIOT)	/* IOT instruction */
+  signal_names[SIGIOT] = "IOT";
+#endif
+
+#if defined (SIGABRT)	/* Cause current process to dump core. */
+  signal_names[SIGABRT] = "ABRT";
+#endif
+
+#if defined (SIGEMT)	/* EMT instruction */
+  signal_names[SIGEMT] = "EMT";
+#endif
+
+#if defined (SIGFPE)	/* floating point exception */
+  signal_names[SIGFPE] = "FPE";
+#endif
+
+#if defined (SIGKILL)	/* kill (cannot be caught or ignored) */
+  signal_names[SIGKILL] = "KILL";
+#endif
+
+#if defined (SIGBUS)	/* bus error */
+  signal_names[SIGBUS] = "BUS";
+#endif
+
+#if defined (SIGSEGV)	/* segmentation violation */
+  signal_names[SIGSEGV] = "SEGV";
+#endif
+
+#if defined (SIGSYS)	/* bad argument to system call */
+  signal_names[SIGSYS] = "SYS";
+#endif
+
+#if defined (SIGPIPE)	/* write on a pipe with no one to read it */
+  signal_names[SIGPIPE] = "PIPE";
+#endif
+
+#if defined (SIGALRM)	/* alarm clock */
+  signal_names[SIGALRM] = "ALRM";
+#endif
+
+#if defined (SIGTERM)	/* software termination signal from kill */
+  signal_names[SIGTERM] = "TERM";
+#endif
+
+#if defined (SIGURG)	/* urgent condition on IO channel */
+  signal_names[SIGURG] = "URG";
+#endif
+
+#if defined (SIGSTOP)	/* sendable stop signal not from tty */
+  signal_names[SIGSTOP] = "STOP";
+#endif
+
+#if defined (SIGTSTP)	/* stop signal from tty */
+  signal_names[SIGTSTP] = "TSTP";
+#endif
+
+#if defined (SIGCONT)	/* continue a stopped process */
+  signal_names[SIGCONT] = "CONT";
+#endif
+
+#if defined (SIGCHLD)	/* to parent on child stop or exit */
+  signal_names[SIGCHLD] = "CHLD";
+#endif
+
+#if defined (SIGTTIN)	/* to readers pgrp upon background tty read */
+  signal_names[SIGTTIN] = "TTIN";
+#endif
+
+#if defined (SIGTTOU)	/* like TTIN for output if (tp->t_local&LTOSTOP) */
+  signal_names[SIGTTOU] = "TTOU";
+#endif
+
+#if defined (SIGIO)	/* input/output possible signal */
+  signal_names[SIGIO] = "IO";
+#endif
+
+#if defined (SIGXCPU)	/* exceeded CPU time limit */
+  signal_names[SIGXCPU] = "XCPU";
+#endif
+
+#if defined (SIGXFSZ)	/* exceeded file size limit */
+  signal_names[SIGXFSZ] = "XFSZ";
+#endif
+
+#if defined (SIGVTALRM)	/* virtual time alarm */
+  signal_names[SIGVTALRM] = "VTALRM";
+#endif
+
+#if defined (SIGPROF)	/* profiling time alarm */
+  signal_names[SIGPROF] = "PROF";
+#endif
+
+#if defined (SIGWINCH)	/* window changed */
+  signal_names[SIGWINCH] = "WINCH";
+#endif
+
+/* 4.4 BSD */
+#if defined (SIGINFO) && !defined (_SEQUENT_)	/* information request */
+  signal_names[SIGINFO] = "INFO";
+#endif
+
+#if defined (SIGUSR1)	/* user defined signal 1 */
+  signal_names[SIGUSR1] = "USR1";
+#endif
+
+#if defined (SIGUSR2)	/* user defined signal 2 */
+  signal_names[SIGUSR2] = "USR2";
+#endif
+
+#if defined (SIGKILLTHR)	/* BeOS: Kill Thread */
+  signal_names[SIGKILLTHR] = "KILLTHR";
+#endif
+
+  for (i = 0; i < NSIG; i++)
+    if (signal_names[i] == (char *)NULL)
+      {
+	signal_names[i] = (char *)malloc (18);
+	if (signal_names[i])
+	  sprintf (signal_names[i], "%d", i);
+      }
+}
+
+void
+write_signames (stream)
+     FILE *stream;
+{
+  register int i;
+
+  fprintf (stream, "/* This file was automatically created by %s.\n",
+	   progname);
+  fprintf (stream, "   Do not edit.  Edit support/mksignames.c instead. */\n\n");
+  fprintf (stream, "#include <signal.h>\n\n");
+  fprintf (stream,
+	   "/* A translation list so we can be polite to our users. */\n");
+  fprintf (stream, "const char *const signal_names[NSIG + 1] = {\n");
+
+  for (i = 0; i <= LASTSIG; i++)
+    fprintf (stream, "    \"%s\",\n", signal_names[i]);
+
+  fprintf (stream, "    (char *)0x0\n");
+  fprintf (stream, "};\n");
+}
+
+int
+main(int argc, char **argv)
+{
+  char *stream_name;
+  FILE *stream;
+
+  progname = argv[0];
+
+  if (argc == 1)
+    {
+      stream_name = "signames.c";
+    }
+  else if (argc == 2)
+    {
+      stream_name = argv[1];
+    }
+  else
+    {
+      fprintf (stderr, "Usage: %s [output-file]\n", progname);
+      exit (1);
+    }
+
+  stream = fopen (stream_name, "w");
+  if (!stream)
+    {
+      fprintf (stderr, "%s: %s: cannot open for writing\n",
+	       progname, stream_name);
+      exit (2);
+    }
+
+  initialize_signames ();
+  write_signames (stream);
+  exit (0);
+}
diff --git a/bin/dash/src/mksyntax.c b/bin/dash/src/mksyntax.c
new file mode 100644
index 00000000..a23c18ca
--- /dev/null
+++ b/bin/dash/src/mksyntax.c
@@ -0,0 +1,315 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * This program creates syntax.h and syntax.c.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include "parser.h"
+
+
+struct synclass {
+	char *name;
+	char *comment;
+};
+
+/* Syntax classes */
+struct synclass synclass[] = {
+	{ "CWORD",	"character is nothing special" },
+	{ "CNL",	"newline character" },
+	{ "CBACK",	"a backslash character" },
+	{ "CSQUOTE",	"single quote" },
+	{ "CDQUOTE",	"double quote" },
+	{ "CENDQUOTE",	"a terminating quote" },
+	{ "CBQUOTE",	"backwards single quote" },
+	{ "CVAR",	"a dollar sign" },
+	{ "CENDVAR",	"a '}' character" },
+	{ "CLP",	"a left paren in arithmetic" },
+	{ "CRP",	"a right paren in arithmetic" },
+	{ "CEOF",	"end of file" },
+	{ "CCTL",	"like CWORD, except it must be escaped" },
+	{ "CSPCL",	"these terminate a word" },
+	{ "CIGN",	"character should be ignored" },
+	{ NULL,		NULL }
+};
+
+
+/*
+ * Syntax classes for is_ functions.  Warning:  if you add new classes
+ * you may have to change the definition of the is_in_name macro.
+ */
+struct synclass is_entry[] = {
+	{ "ISDIGIT",	"a digit" },
+	{ "ISUPPER",	"an upper case letter" },
+	{ "ISLOWER",	"a lower case letter" },
+	{ "ISUNDER",	"an underscore" },
+	{ "ISSPECL",	"the name of a special parameter" },
+	{ NULL, 	NULL }
+};
+
+static char writer[] = "\
+/*\n\
+ * This file was generated by the mksyntax program.\n\
+ */\n\
+\n";
+
+
+static FILE *cfile;
+static FILE *hfile;
+static char *syntax[513];
+
+static void filltable(char *);
+static void init(void);
+static void add(char *, char *);
+static void print(char *);
+static void output_type_macros(void);
+int main(int, char **);
+
+int
+main(int argc, char **argv)
+{
+	int i;
+	char buf[80];
+	int pos;
+
+	/* Create output files */
+	if ((cfile = fopen("syntax.c", "w")) == NULL) {
+		perror("syntax.c");
+		exit(2);
+	}
+	if ((hfile = fopen("syntax.h", "w")) == NULL) {
+		perror("syntax.h");
+		exit(2);
+	}
+	fputs(writer, hfile);
+	fputs(writer, cfile);
+
+	fputs("#include <ctype.h>\n", hfile);
+	fputs("\n", hfile);
+	fputs("#ifdef CEOF\n", hfile);
+	fputs("#undef CEOF\n", hfile);
+	fputs("#endif\n", hfile);
+	fputs("\n", hfile);
+
+	/* Generate the #define statements in the header file */
+	fputs("/* Syntax classes */\n", hfile);
+	for (i = 0 ; synclass[i].name ; i++) {
+		sprintf(buf, "#define %s %d", synclass[i].name, i);
+		fputs(buf, hfile);
+		for (pos = strlen(buf) ; pos < 32 ; pos = (pos + 8) & ~07)
+			putc('\t', hfile);
+		fprintf(hfile, "/* %s */\n", synclass[i].comment);
+	}
+	putc('\n', hfile);
+	fputs("/* Syntax classes for is_ functions */\n", hfile);
+	for (i = 0 ; is_entry[i].name ; i++) {
+		sprintf(buf, "#define %s %#o", is_entry[i].name, 1 << i);
+		fputs(buf, hfile);
+		for (pos = strlen(buf) ; pos < 32 ; pos = (pos + 8) & ~07)
+			putc('\t', hfile);
+		fprintf(hfile, "/* %s */\n", is_entry[i].comment);
+	}
+	putc('\n', hfile);
+	fprintf(hfile, "#define SYNBASE %d\n", 130);
+	fprintf(hfile, "#define PEOF %d\n\n", -130);
+	fprintf(hfile, "#define PEOA %d\n\n", -129);
+	putc('\n', hfile);
+	fputs("#define BASESYNTAX (basesyntax + SYNBASE)\n", hfile);
+	fputs("#define DQSYNTAX (dqsyntax + SYNBASE)\n", hfile);
+	fputs("#define SQSYNTAX (sqsyntax + SYNBASE)\n", hfile);
+	fputs("#define ARISYNTAX (arisyntax + SYNBASE)\n", hfile);
+	putc('\n', hfile);
+	output_type_macros();		/* is_digit, etc. */
+	putc('\n', hfile);
+
+	/* Generate the syntax tables. */
+	fputs("#include \"shell.h\"\n", cfile);
+	fputs("#include \"syntax.h\"\n\n", cfile);
+	init();
+	fputs("/* syntax table used when not in quotes */\n", cfile);
+	add("\n", "CNL");
+	add("\\", "CBACK");
+	add("'", "CSQUOTE");
+	add("\"", "CDQUOTE");
+	add("`", "CBQUOTE");
+	add("$", "CVAR");
+	add("}", "CENDVAR");
+	add("<>();&| \t", "CSPCL");
+	syntax[1] = "CSPCL";
+	print("basesyntax");
+	init();
+	fputs("\n/* syntax table used when in double quotes */\n", cfile);
+	add("\n", "CNL");
+	add("\\", "CBACK");
+	add("\"", "CENDQUOTE");
+	add("`", "CBQUOTE");
+	add("$", "CVAR");
+	add("}", "CENDVAR");
+	/* ':/' for tilde expansion, '-' for [a\-x] pattern ranges */
+	add("!*?[=~:/-]", "CCTL");
+	print("dqsyntax");
+	init();
+	fputs("\n/* syntax table used when in single quotes */\n", cfile);
+	add("\n", "CNL");
+	add("'", "CENDQUOTE");
+	/* ':/' for tilde expansion, '-' for [a\-x] pattern ranges */
+	add("!*?[=~:/-]\\", "CCTL");
+	print("sqsyntax");
+	init();
+	fputs("\n/* syntax table used when in arithmetic */\n", cfile);
+	add("\n", "CNL");
+	add("\\", "CBACK");
+	add("`", "CBQUOTE");
+	add("$", "CVAR");
+	add("}", "CENDVAR");
+	add("(", "CLP");
+	add(")", "CRP");
+	print("arisyntax");
+	filltable("0");
+	fputs("\n/* character classification table */\n", cfile);
+	add("0123456789", "ISDIGIT");
+	add("abcdefghijklmnopqrstucvwxyz", "ISLOWER");
+	add("ABCDEFGHIJKLMNOPQRSTUCVWXYZ", "ISUPPER");
+	add("_", "ISUNDER");
+	add("#?$!-*@", "ISSPECL");
+	print("is_type");
+	exit(0);
+	/* NOTREACHED */
+}
+
+
+
+/*
+ * Clear the syntax table.
+ */
+
+static void
+filltable(char *dftval)
+{
+	int i;
+
+	for (i = 0 ; i < 258; i++)
+		syntax[i] = dftval;
+}
+
+
+/*
+ * Initialize the syntax table with default values.
+ */
+
+static void
+init(void)
+{
+	int ctl;
+
+	filltable("CWORD");
+	syntax[0] = "CEOF";
+	syntax[1] = "CIGN";
+	for (ctl = CTL_FIRST; ctl <= CTL_LAST; ctl++ )
+		syntax[130 + ctl] = "CCTL";
+}
+
+
+/*
+ * Add entries to the syntax table.
+ */
+
+static void
+add(char *p, char *type)
+{
+	while (*p)
+		syntax[(signed char)*p++ + 130] = type;
+}
+
+
+
+/*
+ * Output the syntax table.
+ */
+
+static void
+print(char *name)
+{
+	int i;
+	int col;
+
+	fprintf(hfile, "extern const char %s[];\n", name);
+	fprintf(cfile, "const char %s[] = {\n", name);
+	col = 0;
+	for (i = 0 ; i < 258; i++) {
+		if (i == 0) {
+			fputs("      ", cfile);
+		} else if ((i & 03) == 0) {
+			fputs(",\n      ", cfile);
+			col = 0;
+		} else {
+			putc(',', cfile);
+			while (++col < 9 * (i & 03))
+				putc(' ', cfile);
+		}
+		fputs(syntax[i], cfile);
+		col += strlen(syntax[i]);
+	}
+	fputs("\n};\n", cfile);
+}
+
+
+
+/*
+ * Output character classification macros (e.g. is_digit).  If digits are
+ * contiguous, we can test for them quickly.
+ */
+
+static char *macro[] = {
+	"#define is_digit(c)\t((unsigned)((c) - '0') <= 9)\n",
+	"#define is_alpha(c)\tisalpha((unsigned char)(c))\n",
+	"#define is_name(c)\t((c) == '_' || isalpha((unsigned char)(c)))\n",
+	"#define is_in_name(c)\t((c) == '_' || isalnum((unsigned char)(c)))\n",
+	"#define is_special(c)\t((is_type+SYNBASE)[(signed char)(c)] & (ISSPECL|ISDIGIT))\n",
+	NULL
+};
+
+static void
+output_type_macros(void)
+{
+	char **pp;
+
+	for (pp = macro ; *pp ; pp++)
+		fputs(*pp, hfile);
+	fputs("#define digit_val(c)\t((c) - '0')\n", hfile);
+}
diff --git a/bin/dash/src/mktokens b/bin/dash/src/mktokens
new file mode 100644
index 00000000..78055be8
--- /dev/null
+++ b/bin/dash/src/mktokens
@@ -0,0 +1,97 @@
+#!/bin/sh -
+# Copyright (c) 1991, 1993
+#	The Regents of the University of California.  All rights reserved.
+# Copyright (c) 1997-2005
+#	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#	@(#)mktokens	8.1 (Berkeley) 5/31/93
+
+# The following is a list of tokens.  The second column is nonzero if the
+# token marks the end of a list.  The third column is the name to print in
+# error messages.
+
+: "${TMPDIR:=/tmp}"
+
+cat > "${TMPDIR}"/ka$$ <<\!
+TEOF	1	end of file
+TNL	0	newline
+TSEMI	0	";"
+TBACKGND 0	"&"
+TAND	0	"&&"
+TOR	0	"||"
+TPIPE	0	"|"
+TLP	0	"("
+TRP	1	")"
+TENDCASE 1	";;"
+TENDBQUOTE 1	"`"
+TREDIR	0	redirection
+TWORD	0	word
+TNOT	0	"!"
+TCASE	0	"case"
+TDO	1	"do"
+TDONE	1	"done"
+TELIF	1	"elif"
+TELSE	1	"else"
+TESAC	1	"esac"
+TFI	1	"fi"
+TFOR	0	"for"
+TIF	0	"if"
+TIN	0	"in"
+TTHEN	1	"then"
+TUNTIL	0	"until"
+TWHILE	0	"while"
+TBEGIN	0	"{"
+TEND	1	"}"
+!
+nl=`wc -l "${TMPDIR}"/ka$$`
+exec > token.h
+awk '{print "#define " $1 " " NR-1}' "${TMPDIR}"/ka$$
+
+exec > token_vars.h
+
+echo '
+/* Array indicating which tokens mark the end of a list */
+static const char tokendlist[] = {'
+awk '{print "\t" $2 ","}' "${TMPDIR}"/ka$$
+echo '};
+
+static const char *const tokname[] = {'
+sed -e 's/"/\\"/g' \
+    -e 's/[^	 ]*[	 ][	 ]*[^	 ]*[	 ][	 ]*\(.*\)/	"\1",/' \
+    "${TMPDIR}"/ka$$
+echo '};
+'
+sed 's/"//g' "${TMPDIR}"/ka$$ | awk '
+/TNOT/{print "#define KWDOFFSET " NR-1; print ""; 
+      print "static const char *const parsekwd[] = {"}
+/TNOT/,/neverfound/{if (last) print "	\"" last "\","; last = $3}
+END{print "	\"" last "\"\n};"}'
+
+rm "${TMPDIR}"/ka$$
diff --git a/bin/dash/src/myhistedit.h b/bin/dash/src/myhistedit.h
new file mode 100644
index 00000000..22e5c438
--- /dev/null
+++ b/bin/dash/src/myhistedit.h
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)myhistedit.h	8.2 (Berkeley) 5/4/95
+ */
+
+#include <histedit.h>
+
+extern History *hist;
+extern EditLine *el;
+extern int displayhist;
+
+void histedit(void);
+void sethistsize(const char *);
+void setterm(const char *);
+int histcmd(int, char **);
+int not_fcnumber(char *);
+int str_to_event(const char *, int);
+
diff --git a/bin/dash/src/mystring.c b/bin/dash/src/mystring.c
new file mode 100644
index 00000000..de624b89
--- /dev/null
+++ b/bin/dash/src/mystring.c
@@ -0,0 +1,257 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * String functions.
+ *
+ *	equal(s1, s2)		Return true if strings are equal.
+ *	scopy(from, to)		Copy a string.
+ *	scopyn(from, to, n)	Like scopy, but checks for overflow.
+ *	number(s)		Convert a string of digits to an integer.
+ *	is_number(s)		Return true if s is a string of digits.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include "shell.h"
+#include "syntax.h"
+#include "error.h"
+#include "mystring.h"
+#include "memalloc.h"
+#include "parser.h"
+#include "system.h"
+
+
+char nullstr[1];		/* zero length string */
+const char spcstr[] = " ";
+const char snlfmt[] = "%s\n";
+const char dolatstr[] = { CTLQUOTEMARK, CTLVAR, VSNORMAL, '@', '=',
+			  CTLQUOTEMARK, '\0' };
+const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 };
+const char illnum[] = "Illegal number: %s";
+const char homestr[] = "HOME";
+
+/*
+ * equal - #defined in mystring.h
+ */
+
+/*
+ * scopy - #defined in mystring.h
+ */
+
+
+#if 0
+/*
+ * scopyn - copy a string from "from" to "to", truncating the string
+ *		if necessary.  "To" is always nul terminated, even if
+ *		truncation is performed.  "Size" is the size of "to".
+ */
+
+void
+scopyn(const char *from, char *to, int size)
+{
+
+	while (--size > 0) {
+		if ((*to++ = *from++) == '\0')
+			return;
+	}
+	*to = '\0';
+}
+#endif
+
+
+/*
+ * prefix -- see if pfx is a prefix of string.
+ */
+
+char *
+prefix(const char *string, const char *pfx)
+{
+	while (*pfx) {
+		if (*pfx++ != *string++)
+			return 0;
+	}
+	return (char *) string;
+}
+
+void badnum(const char *s)
+{
+	sh_error(illnum, s);
+}
+
+/*
+ * Convert a string into an integer of type intmax_t.  Alow trailing spaces.
+ */
+intmax_t atomax(const char *s, int base)
+{
+	char *p;
+	intmax_t r;
+
+	errno = 0;
+	r = strtoimax(s, &p, base);
+
+	if (errno == ERANGE)
+		badnum(s);
+
+	/*
+	 * Disallow completely blank strings in non-arithmetic (base != 0)
+	 * contexts.
+	 */
+	if (p == s && base)
+		badnum(s);
+
+	while (isspace((unsigned char)*p))
+	      p++;
+
+	if (*p)
+		badnum(s);
+
+	return r;
+}
+
+intmax_t atomax10(const char *s)
+{
+	return atomax(s, 10);
+}
+
+/*
+ * Convert a string of digits to an integer, printing an error message on
+ * failure.
+ */
+
+int
+number(const char *s)
+{
+	intmax_t n = atomax10(s);
+
+	if (n < 0 || n > INT_MAX)
+		badnum(s);
+
+	return n;
+}
+
+
+
+/*
+ * Check for a valid number.  This should be elsewhere.
+ */
+
+int
+is_number(const char *p)
+{
+	do {
+		if (! is_digit(*p))
+			return 0;
+	} while (*++p != '\0');
+	return 1;
+}
+
+
+/*
+ * Produce a possibly single quoted string suitable as input to the shell.
+ * The return string is allocated on the stack.
+ */
+
+char *
+single_quote(const char *s) {
+	char *p;
+
+	STARTSTACKSTR(p);
+
+	do {
+		char *q;
+		size_t len;
+
+		len = strchrnul(s, '\'') - s;
+
+		q = p = makestrspace(len + 3, p);
+
+		*q++ = '\'';
+		q = mempcpy(q, s, len);
+		*q++ = '\'';
+		s += len;
+
+		STADJUST(q - p, p);
+
+		len = strspn(s, "'");
+		if (!len)
+			break;
+
+		q = p = makestrspace(len + 3, p);
+
+		*q++ = '"';
+		q = mempcpy(q, s, len);
+		*q++ = '"';
+		s += len;
+
+		STADJUST(q - p, p);
+	} while (*s);
+
+	USTPUTC(0, p);
+
+	return stackblock();
+}
+
+/*
+ * Like strdup but works with the ash stack.
+ */
+
+char *
+sstrdup(const char *p)
+{
+	size_t len = strlen(p) + 1;
+	return memcpy(stalloc(len), p, len);
+}
+
+/*
+ * Wrapper around strcmp for qsort/bsearch/...
+ */
+int
+pstrcmp(const void *a, const void *b)
+{
+	return strcmp(*(const char *const *) a, *(const char *const *) b);
+}
+
+/*
+ * Find a string is in a sorted array.
+ */
+const char *const *
+findstring(const char *s, const char *const *array, size_t nmemb)
+{
+	return bsearch(&s, array, nmemb, sizeof(const char *), pstrcmp);
+}
diff --git a/bin/dash/src/mystring.h b/bin/dash/src/mystring.h
new file mode 100644
index 00000000..083ea98c
--- /dev/null
+++ b/bin/dash/src/mystring.h
@@ -0,0 +1,63 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)mystring.h	8.2 (Berkeley) 5/4/95
+ */
+
+#include <inttypes.h>
+#include <string.h>
+
+extern const char snlfmt[];
+extern const char spcstr[];
+extern const char dolatstr[];
+#define DOLATSTRLEN 6
+extern const char qchars[];
+extern const char illnum[];
+extern const char homestr[];
+
+#if 0
+void scopyn(const char *, char *, int);
+#endif
+char *prefix(const char *, const char *);
+void badnum(const char *s) __attribute__ ((noreturn));
+intmax_t atomax(const char *, int);
+intmax_t atomax10(const char *);
+int number(const char *);
+int is_number(const char *);
+char *single_quote(const char *);
+char *sstrdup(const char *);
+int pstrcmp(const void *, const void *);
+const char *const *findstring(const char *, const char *const *, size_t);
+
+#define equal(s1, s2)	(strcmp(s1, s2) == 0)
+#define scopy(s1, s2)	((void)strcpy(s2, s1))
diff --git a/bin/dash/src/nodes.c.pat b/bin/dash/src/nodes.c.pat
new file mode 100644
index 00000000..9125bc73
--- /dev/null
+++ b/bin/dash/src/nodes.c.pat
@@ -0,0 +1,166 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)nodes.c.pat	8.2 (Berkeley) 5/4/95
+ */
+
+#include <stdlib.h>
+/*
+ * Routine for dealing with parsed shell commands.
+ */
+
+#include "shell.h"
+#include "nodes.h"
+#include "memalloc.h"
+#include "machdep.h"
+#include "mystring.h"
+#include "system.h"
+
+
+int     funcblocksize;		/* size of structures in function */
+int     funcstringsize;		/* size of strings in node */
+pointer funcblock;		/* block to allocate function from */
+char   *funcstring;		/* block to allocate strings from */
+
+%SIZES
+
+
+STATIC void calcsize(union node *);
+STATIC void sizenodelist(struct nodelist *);
+STATIC union node *copynode(union node *);
+STATIC struct nodelist *copynodelist(struct nodelist *);
+STATIC char *nodesavestr(char *);
+
+
+
+/*
+ * Make a copy of a parse tree.
+ */
+
+struct funcnode *
+copyfunc(union node *n)
+{
+	struct funcnode *f;
+	size_t blocksize;
+
+	funcblocksize = offsetof(struct funcnode, n);
+	funcstringsize = 0;
+	calcsize(n);
+	blocksize = funcblocksize;
+	f = ckmalloc(blocksize + funcstringsize);
+	funcblock = (char *) f + offsetof(struct funcnode, n);
+	funcstring = (char *) f + blocksize;
+	copynode(n);
+	f->count = 0;
+	return f;
+}
+
+
+
+STATIC void
+calcsize(n)
+	union node *n;
+{
+	%CALCSIZE
+}
+
+
+
+STATIC void
+sizenodelist(lp)
+	struct nodelist *lp;
+{
+	while (lp) {
+		funcblocksize += SHELL_ALIGN(sizeof(struct nodelist));
+		calcsize(lp->n);
+		lp = lp->next;
+	}
+}
+
+
+
+STATIC union node *
+copynode(n)
+	union node *n;
+{
+	union node *new;
+
+	%COPY
+	return new;
+}
+
+
+STATIC struct nodelist *
+copynodelist(lp)
+	struct nodelist *lp;
+{
+	struct nodelist *start;
+	struct nodelist **lpp;
+
+	lpp = &start;
+	while (lp) {
+		*lpp = funcblock;
+		funcblock = (char *) funcblock +
+		    SHELL_ALIGN(sizeof(struct nodelist));
+		(*lpp)->n = copynode(lp->n);
+		lp = lp->next;
+		lpp = &(*lpp)->next;
+	}
+	*lpp = NULL;
+	return start;
+}
+
+
+
+STATIC char *
+nodesavestr(s)
+	char   *s;
+{
+	char   *rtn = funcstring;
+
+	funcstring = stpcpy(funcstring, s) + 1;
+	return rtn;
+}
+
+
+
+/*
+ * Free a parse tree.
+ */
+
+void
+freefunc(struct funcnode *f)
+{
+	if (f && --f->count < 0)
+		ckfree(f);
+}
diff --git a/bin/dash/src/nodetypes b/bin/dash/src/nodetypes
new file mode 100644
index 00000000..ceaf478c
--- /dev/null
+++ b/bin/dash/src/nodetypes
@@ -0,0 +1,151 @@
+# Copyright (c) 1991, 1993
+#	The Regents of the University of California.  All rights reserved.
+# Copyright (c) 1997-2005
+#	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+#
+# This code is derived from software contributed to Berkeley by
+# Kenneth Almquist.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#	@(#)nodetypes	8.2 (Berkeley) 5/4/95
+
+# This file describes the nodes used in parse trees.  Unindented lines
+# contain a node type followed by a structure tag.  Subsequent indented
+# lines specify the fields of the structure.  Several node types can share
+# the same structure, in which case the fields of the structure should be
+# specified only once.
+#
+# A field of a structure is described by the name of the field followed
+# by a type.  The currently implemented types are:
+#	nodeptr - a pointer to a node
+#	nodelist - a pointer to a list of nodes
+#	string - a pointer to a nul terminated string
+#	int - an integer
+#	other - any type that can be copied by assignment
+#	temp - a field that doesn't have to be copied when the node is copied
+# The last two types should be followed by the text of a C declaration for
+# the field.
+
+NCMD ncmd			# a simple command
+	type	  int
+	linno	  int
+	assign    nodeptr		# variable assignments
+	args	  nodeptr		# the arguments
+	redirect  nodeptr		# list of file redirections
+
+NPIPE npipe			# a pipeline
+	type	  int
+	backgnd	  int			# set to run pipeline in background
+	cmdlist	  nodelist		# the commands in the pipeline
+
+NREDIR nredir			# redirection (of a complex command)
+	type	  int
+	linno	  int
+	n	  nodeptr		# the command
+	redirect  nodeptr		# list of file redirections
+
+NBACKGND nredir			# run command in background
+NSUBSHELL nredir		# run command in a subshell
+
+NAND nbinary			# the && operator
+NOR nbinary			# the || operator
+
+NSEMI nbinary			# two commands separated by a semicolon
+	type	  int
+	ch1	  nodeptr		# the first child
+	ch2	  nodeptr		# the second child
+
+NIF nif				# the if statement.  Elif clauses are handled
+	type	  int		    # using multiple if nodes.
+	test	  nodeptr		# if test
+	ifpart	  nodeptr		# then ifpart
+	elsepart  nodeptr		# else elsepart
+
+NWHILE nbinary			# the while statement.  First child is the test
+NUNTIL nbinary			# the until statement
+
+NFOR nfor			# the for statement
+	type	  int
+	linno	  int
+	args	  nodeptr		# for var in args
+	body	  nodeptr		# do body; done
+	var	  string		# the for variable
+
+NCASE ncase			# a case statement
+	type	  int
+	linno	  int
+	expr	  nodeptr		# the word to switch on
+	cases	  nodeptr		# the list of cases (NCLIST nodes)
+
+NCLIST nclist			# a case
+	type	  int
+	next	  nodeptr		# the next case in list
+	pattern	  nodeptr		# list of patterns for this case
+	body	  nodeptr		# code to execute for this case
+
+
+NDEFUN ndefun			# a function
+	type	  int
+	linno	  int
+	text	  string
+	body	  nodeptr
+
+NARG narg			# represents a word
+	type	  int
+	next	  nodeptr		# next word in list
+	text	  string		# the text of the word
+	backquote nodelist		# list of commands in back quotes
+
+NTO nfile			# fd> fname
+NCLOBBER nfile			# fd>| fname
+NFROM nfile			# fd< fname
+NFROMTO nfile			# fd<> fname
+NAPPEND nfile			# fd>> fname
+	type	  int
+	next	  nodeptr		# next redirection in list
+	fd	  int			# file descriptor being redirected
+	fname	  nodeptr		# file name, in a NARG node
+	expfname  temp	char *expfname	# actual file name
+
+NTOFD ndup			# fd<&dupfd
+NFROMFD ndup			# fd>&dupfd
+	type	  int
+	next	  nodeptr		# next redirection in list
+	fd	  int			# file descriptor being redirected
+	dupfd	  int			# file descriptor to duplicate
+	vname	  nodeptr		# file name if fd>&$var
+
+
+NHERE nhere			# fd<<\!
+NXHERE nhere			# fd<<!
+	type	  int
+	next	  nodeptr		# next redirection in list
+	fd	  int			# file descriptor being redirected
+	doc	  nodeptr		# input to command (NARG node)
+
+NNOT nnot			# ! command  (actually pipeline)
+	type	int
+	com	nodeptr
diff --git a/bin/dash/src/options.c b/bin/dash/src/options.c
new file mode 100644
index 00000000..a46c23b9
--- /dev/null
+++ b/bin/dash/src/options.c
@@ -0,0 +1,558 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "shell.h"
+#define DEFINE_OPTIONS
+#include "options.h"
+#undef DEFINE_OPTIONS
+#include "nodes.h"	/* for other header files */
+#include "eval.h"
+#include "jobs.h"
+#include "input.h"
+#include "output.h"
+#include "trap.h"
+#include "var.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#ifndef SMALL
+#include "myhistedit.h"
+#endif
+#include "show.h"
+
+char *arg0;			/* value of $0 */
+struct shparam shellparam;	/* current positional parameters */
+char **argptr;			/* argument list for builtin commands */
+char *optionarg;		/* set by nextopt (like getopt) */
+char *optptr;			/* used by nextopt */
+
+char *minusc;			/* argument to -c option */
+
+static const char *const optnames[NOPTS] = {
+	"errexit",
+	"noglob",
+	"ignoreeof",
+	"interactive",
+	"monitor",
+	"noexec",
+	"stdin",
+	"xtrace",
+	"verbose",
+	"vi",
+	"emacs",
+	"noclobber",
+	"allexport",
+	"notify",
+	"nounset",
+	"nolog",
+	"debug",
+};
+
+const char optletters[NOPTS] = {
+	'e',
+	'f',
+	'I',
+	'i',
+	'm',
+	'n',
+	's',
+	'x',
+	'v',
+	'V',
+	'E',
+	'C',
+	'a',
+	'b',
+	'u',
+	0,
+	0,
+};
+
+char optlist[NOPTS];
+
+
+static int options(int);
+STATIC void minus_o(char *, int);
+STATIC void setoption(int, int);
+STATIC int getopts(char *, char *, char **);
+
+
+/*
+ * Process the shell command line arguments.
+ */
+
+int
+procargs(int argc, char **argv)
+{
+	int i;
+	const char *xminusc;
+	char **xargv;
+	int login;
+
+	xargv = argv;
+	login = xargv[0] && xargv[0][0] == '-';
+	arg0 = xargv[0];
+	if (argc > 0)
+		xargv++;
+	for (i = 0; i < NOPTS; i++)
+		optlist[i] = 2;
+	argptr = xargv;
+	login |= options(1);
+	xargv = argptr;
+	xminusc = minusc;
+	if (*xargv == NULL) {
+		if (xminusc)
+			sh_error("-c requires an argument");
+		sflag = 1;
+	}
+	if (iflag == 2 && sflag == 1 && isatty(0) && isatty(1))
+		iflag = 1;
+	if (mflag == 2)
+		mflag = iflag;
+	for (i = 0; i < NOPTS; i++)
+		if (optlist[i] == 2)
+			optlist[i] = 0;
+#if DEBUG == 2
+	debug = 1;
+#endif
+	/* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */
+	if (xminusc) {
+		minusc = *xargv++;
+		if (*xargv)
+			goto setarg0;
+	} else if (!sflag) {
+		setinputfile(*xargv, 0);
+setarg0:
+		arg0 = *xargv++;
+	}
+
+	shellparam.p = xargv;
+	shellparam.optind = 1;
+	shellparam.optoff = -1;
+	/* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */
+	while (*xargv) {
+		shellparam.nparam++;
+		xargv++;
+	}
+	optschanged();
+
+	return login;
+}
+
+
+void
+optschanged(void)
+{
+#ifdef DEBUG
+	opentrace();
+#endif
+	setinteractive(iflag);
+#ifndef SMALL
+	histedit();
+#endif
+	setjobctl(mflag);
+}
+
+/*
+ * Process shell options.  The global variable argptr contains a pointer
+ * to the argument list; we advance it past the options.
+ */
+
+STATIC int
+options(int cmdline)
+{
+	char *p;
+	int val;
+	int c;
+	int login = 0;
+
+	if (cmdline)
+		minusc = NULL;
+	while ((p = *argptr) != NULL) {
+		argptr++;
+		if ((c = *p++) == '-') {
+			val = 1;
+                        if (p[0] == '\0' || (p[0] == '-' && p[1] == '\0')) {
+                                if (!cmdline) {
+                                        /* "-" means turn off -x and -v */
+                                        if (p[0] == '\0')
+                                                xflag = vflag = 0;
+                                        /* "--" means reset params */
+                                        else if (*argptr == NULL)
+						setparam(argptr);
+                                }
+				break;	  /* "-" or  "--" terminates options */
+			}
+		} else if (c == '+') {
+			val = 0;
+		} else {
+			argptr--;
+			break;
+		}
+		while ((c = *p++) != '\0') {
+			if (c == 'c' && cmdline) {
+				minusc = p;	/* command is after shell args*/
+			} else if (c == 'l' && cmdline) {
+				login = 1;
+			} else if (c == 'o') {
+				minus_o(*argptr, val);
+				if (*argptr)
+					argptr++;
+			} else {
+				setoption(c, val);
+			}
+		}
+	}
+
+	return login;
+}
+
+STATIC void
+minus_o(char *name, int val)
+{
+	int i;
+
+	if (name == NULL) {
+		if (val) {
+			out1str("Current option settings\n");
+			for (i = 0; i < NOPTS; i++)
+				out1fmt("%-16s%s\n", optnames[i],
+					optlist[i] ? "on" : "off");
+		} else {
+			for (i = 0; i < NOPTS; i++)
+				out1fmt("set %s %s\n",
+					optlist[i] ? "-o" : "+o",
+					optnames[i]);
+
+		}
+	} else {
+		for (i = 0; i < NOPTS; i++)
+			if (equal(name, optnames[i])) {
+				optlist[i] = val;
+				return;
+			}
+		sh_error("Illegal option -o %s", name);
+	}
+}
+
+
+STATIC void
+setoption(int flag, int val)
+{
+	int i;
+
+	for (i = 0; i < NOPTS; i++)
+		if (optletters[i] == flag) {
+			optlist[i] = val;
+			if (val) {
+				/* #%$ hack for ksh semantics */
+				if (flag == 'V')
+					Eflag = 0;
+				else if (flag == 'E')
+					Vflag = 0;
+			}
+			return;
+		}
+	sh_error("Illegal option -%c", flag);
+	/* NOTREACHED */
+}
+
+
+
+/*
+ * Set the shell parameters.
+ */
+
+void
+setparam(char **argv)
+{
+	char **newparam;
+	char **ap;
+	int nparam;
+
+	for (nparam = 0 ; argv[nparam] ; nparam++);
+	ap = newparam = ckmalloc((nparam + 1) * sizeof *ap);
+	while (*argv) {
+		*ap++ = savestr(*argv++);
+	}
+	*ap = NULL;
+	freeparam(&shellparam);
+	shellparam.malloc = 1;
+	shellparam.nparam = nparam;
+	shellparam.p = newparam;
+	shellparam.optind = 1;
+	shellparam.optoff = -1;
+}
+
+
+/*
+ * Free the list of positional parameters.
+ */
+
+void
+freeparam(volatile struct shparam *param)
+{
+	char **ap;
+
+	if (param->malloc) {
+		for (ap = param->p ; *ap ; ap++)
+			ckfree(*ap);
+		ckfree(param->p);
+	}
+}
+
+
+
+/*
+ * The shift builtin command.
+ */
+
+int
+shiftcmd(int argc, char **argv)
+{
+	int n;
+	char **ap1, **ap2;
+
+	n = 1;
+	if (argc > 1)
+		n = number(argv[1]);
+	if (n > shellparam.nparam)
+		sh_error("can't shift that many");
+	INTOFF;
+	shellparam.nparam -= n;
+	for (ap1 = shellparam.p ; --n >= 0 ; ap1++) {
+		if (shellparam.malloc)
+			ckfree(*ap1);
+	}
+	ap2 = shellparam.p;
+	while ((*ap2++ = *ap1++) != NULL);
+	shellparam.optind = 1;
+	shellparam.optoff = -1;
+	INTON;
+	return 0;
+}
+
+
+
+/*
+ * The set command builtin.
+ */
+
+int
+setcmd(int argc, char **argv)
+{
+	if (argc == 1)
+		return showvars(nullstr, 0, VUNSET);
+	INTOFF;
+	options(0);
+	optschanged();
+	if (*argptr != NULL) {
+		setparam(argptr);
+	}
+	INTON;
+	return 0;
+}
+
+
+void
+getoptsreset(value)
+	const char *value;
+{
+	shellparam.optind = number(value) ?: 1;
+	shellparam.optoff = -1;
+}
+
+/*
+ * The getopts builtin.  Shellparam.optnext points to the next argument
+ * to be processed.  Shellparam.optptr points to the next character to
+ * be processed in the current argument.  If shellparam.optnext is NULL,
+ * then it's the first time getopts has been called.
+ */
+
+int
+getoptscmd(int argc, char **argv)
+{
+	char **optbase;
+
+	if (argc < 3)
+		sh_error("Usage: getopts optstring var [arg]");
+	else if (argc == 3) {
+		optbase = shellparam.p;
+		if ((unsigned)shellparam.optind > shellparam.nparam + 1) {
+			shellparam.optind = 1;
+			shellparam.optoff = -1;
+		}
+	}
+	else {
+		optbase = &argv[3];
+		if ((unsigned)shellparam.optind > argc - 2) {
+			shellparam.optind = 1;
+			shellparam.optoff = -1;
+		}
+	}
+
+	return getopts(argv[1], argv[2], optbase);
+}
+
+STATIC int
+getopts(char *optstr, char *optvar, char **optfirst)
+{
+	char *p, *q;
+	char c = '?';
+	int done = 0;
+	char s[2];
+	char **optnext;
+	int ind = shellparam.optind;
+	int off = shellparam.optoff;
+
+	shellparam.optind = -1;
+	optnext = optfirst + ind - 1;
+
+	if (ind <= 1 || off < 0 || strlen(optnext[-1]) < off)
+		p = NULL;
+	else
+		p = optnext[-1] + off;
+	if (p == NULL || *p == '\0') {
+		/* Current word is done, advance */
+		p = *optnext;
+		if (p == NULL || *p != '-' || *++p == '\0') {
+atend:
+			p = NULL;
+			done = 1;
+			goto out;
+		}
+		optnext++;
+		if (p[0] == '-' && p[1] == '\0')	/* check for "--" */
+			goto atend;
+	}
+
+	c = *p++;
+	for (q = optstr; *q != c; ) {
+		if (*q == '\0') {
+			if (optstr[0] == ':') {
+				s[0] = c;
+				s[1] = '\0';
+				setvar("OPTARG", s, 0);
+			} else {
+				outfmt(&errout, "Illegal option -%c\n", c);
+				(void) unsetvar("OPTARG");
+			}
+			c = '?';
+			goto out;
+		}
+		if (*++q == ':')
+			q++;
+	}
+
+	if (*++q == ':') {
+		if (*p == '\0' && (p = *optnext) == NULL) {
+			if (optstr[0] == ':') {
+				s[0] = c;
+				s[1] = '\0';
+				setvar("OPTARG", s, 0);
+				c = ':';
+			} else {
+				outfmt(&errout, "No arg for -%c option\n", c);
+				(void) unsetvar("OPTARG");
+				c = '?';
+			}
+			goto out;
+		}
+
+		if (p == *optnext)
+			optnext++;
+		setvar("OPTARG", p, 0);
+		p = NULL;
+	} else
+		setvar("OPTARG", nullstr, 0);
+
+out:
+	ind = optnext - optfirst + 1;
+	setvarint("OPTIND", ind, VNOFUNC);
+	s[0] = c;
+	s[1] = '\0';
+	setvar(optvar, s, 0);
+
+	shellparam.optoff = p ? p - *(optnext - 1) : -1;
+	shellparam.optind = ind;
+
+	return done;
+}
+
+/*
+ * XXX - should get rid of.  have all builtins use getopt(3).  the
+ * library getopt must have the BSD extension static variable "optreset"
+ * otherwise it can't be used within the shell safely.
+ *
+ * Standard option processing (a la getopt) for builtin routines.  The
+ * only argument that is passed to nextopt is the option string; the
+ * other arguments are unnecessary.  It return the character, or '\0' on
+ * end of input.
+ */
+
+int
+nextopt(const char *optstring)
+{
+	char *p;
+	const char *q;
+	char c;
+
+	if ((p = optptr) == NULL || *p == '\0') {
+		p = *argptr;
+		if (p == NULL || *p != '-' || *++p == '\0')
+			return '\0';
+		argptr++;
+		if (p[0] == '-' && p[1] == '\0')	/* check for "--" */
+			return '\0';
+	}
+	c = *p++;
+	for (q = optstring ; *q != c ; ) {
+		if (*q == '\0')
+			sh_error("Illegal option -%c", c);
+		if (*++q == ':')
+			q++;
+	}
+	if (*++q == ':') {
+		if (*p == '\0' && (p = *argptr++) == NULL)
+			sh_error("No arg for -%c option", c);
+		optionarg = p;
+		p = NULL;
+	}
+	optptr = p;
+	return c;
+}
diff --git a/bin/dash/src/options.h b/bin/dash/src/options.h
new file mode 100644
index 00000000..975fe339
--- /dev/null
+++ b/bin/dash/src/options.h
@@ -0,0 +1,86 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)options.h	8.2 (Berkeley) 5/4/95
+ */
+
+struct shparam {
+	int nparam;		/* # of positional parameters (without $0) */
+	unsigned char malloc;	/* if parameter list dynamically allocated */
+	char **p;		/* parameter list */
+	int optind;		/* next parameter to be processed by getopts */
+	int optoff;		/* used by getopts */
+};
+
+
+
+#define eflag optlist[0]
+#define fflag optlist[1]
+#define Iflag optlist[2]
+#define iflag optlist[3]
+#define mflag optlist[4]
+#define nflag optlist[5]
+#define sflag optlist[6]
+#define xflag optlist[7]
+#define vflag optlist[8]
+#define Vflag optlist[9]
+#define	Eflag optlist[10]
+#define	Cflag optlist[11]
+#define	aflag optlist[12]
+#define	bflag optlist[13]
+#define	uflag optlist[14]
+#define	nolog optlist[15]
+#define	debug optlist[16]
+
+#define NOPTS	17
+
+extern const char optletters[NOPTS];
+extern char optlist[NOPTS];
+
+
+extern char *minusc;		/* argument to -c option */
+extern char *arg0;		/* $0 */
+extern struct shparam shellparam;  /* $@ */
+extern char **argptr;		/* argument list for builtin commands */
+extern char *optionarg;		/* set by nextopt */
+extern char *optptr;		/* used by nextopt */
+
+int procargs(int, char **);
+void optschanged(void);
+void setparam(char **);
+void freeparam(volatile struct shparam *);
+int shiftcmd(int, char **);
+int setcmd(int, char **);
+int getoptscmd(int, char **);
+int nextopt(const char *);
+void getoptsreset(const char *);
diff --git a/bin/dash/src/output.c b/bin/dash/src/output.c
new file mode 100644
index 00000000..e9ee9b4d
--- /dev/null
+++ b/bin/dash/src/output.c
@@ -0,0 +1,429 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * Shell output routines.  We use our own output routines because:
+ *	When a builtin command is interrupted we have to discard
+ *		any pending output.
+ *	When a builtin command appears in back quotes, we want to
+ *		save the output of the command in a region obtained
+ *		via malloc, rather than doing a fork and reading the
+ *		output of the command via a pipe.
+ *	Our output routines may be smaller than the stdio routines.
+ */
+
+#include <sys/types.h>		/* quad_t */
+#include <sys/param.h>		/* BSD4_4 */
+#include <sys/ioctl.h>
+
+#include <stdio.h>	/* defines BUFSIZ */
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#ifdef USE_GLIBC_STDIO
+#include <fcntl.h>
+#endif
+#include <limits.h>
+
+#include "shell.h"
+#include "syntax.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "main.h"
+#include "system.h"
+
+
+#define OUTBUFSIZ BUFSIZ
+#define MEM_OUT -3		/* output to dynamically allocated memory */
+
+
+#ifdef USE_GLIBC_STDIO
+struct output output = {
+	.stream = 0, .nextc = 0, .end = 0, .buf = 0, .bufsize = 0, .fd = 1, .flags = 0
+};
+struct output errout = {
+	.stream = 0, .nextc = 0, .end = 0, .buf = 0, .bufsize = 0, .fd = 2, .flags = 0
+}
+#ifdef notyet
+struct output memout = {
+	.stream = 0, .nextc = 0, .end = 0, .buf = 0, .bufsize = 0, .fd = MEM_OUT, .flags = 0
+};
+#endif
+#else
+struct output output = {
+	.nextc = 0, .end = 0, .buf = 0, .bufsize = OUTBUFSIZ, .fd = 1, .flags = 0
+};
+struct output errout = {
+	.nextc = 0, .end = 0, .buf = 0, .bufsize = 0, .fd = 2, .flags = 0
+};
+struct output preverrout;
+#ifdef notyet
+struct output memout = {
+	.nextc = 0, .end = 0, .buf = 0, .bufsize = 0, .fd = MEM_OUT, .flags = 0
+};
+#endif
+#endif
+struct output *out1 = &output;
+struct output *out2 = &errout;
+
+
+static int xvsnprintf(char *, size_t, const char *, va_list);
+
+
+#ifdef mkinit
+
+INCLUDE "output.h"
+INCLUDE "memalloc.h"
+
+INIT {
+#ifdef USE_GLIBC_STDIO
+	initstreams();
+#endif
+}
+
+RESET {
+#ifdef notyet
+	out1 = &output;
+	out2 = &errout;
+#ifdef USE_GLIBC_STDIO
+	if (memout.stream != NULL)
+		__closememout();
+#endif
+	if (memout.buf != NULL) {
+		ckfree(memout.buf);
+		memout.buf = NULL;
+	}
+#endif
+}
+
+#endif
+
+
+void
+outmem(const char *p, size_t len, struct output *dest)
+{
+#ifdef USE_GLIBC_STDIO
+	INTOFF;
+	fwrite(p, 1, len, dest->stream);
+	INTON;
+#else
+	size_t bufsize;
+	size_t offset;
+	size_t nleft;
+
+	nleft = dest->end - dest->nextc;
+	if (likely(nleft >= len)) {
+buffered:
+		dest->nextc = mempcpy(dest->nextc, p, len);
+		return;
+	}
+
+	bufsize = dest->bufsize;
+	if (!bufsize) {
+		;
+	} else if (dest->buf == NULL) {
+#ifdef notyet
+		if (dest->fd == MEM_OUT && len > bufsize) {
+			bufsize = len;
+		}
+#endif
+		offset = 0;
+#ifdef notyet
+		goto alloc;
+	} else if (dest->fd == MEM_OUT) {
+		offset = bufsize;
+		if (bufsize >= len) {
+			bufsize <<= 1;
+		} else {
+			bufsize += len;
+		}
+		if (bufsize < offset)
+			goto err;
+alloc:
+#endif
+		INTOFF;
+		dest->buf = ckrealloc(dest->buf, bufsize);
+		dest->bufsize = bufsize;
+		dest->end = dest->buf + bufsize;
+		dest->nextc = dest->buf + offset;
+		INTON;
+	} else {
+		flushout(dest);
+	}
+
+	nleft = dest->end - dest->nextc;
+	if (nleft > len)
+		goto buffered;
+
+	if ((xwrite(dest->fd, p, len))) {
+#ifdef notyet
+err:
+#endif
+		dest->flags |= OUTPUT_ERR;
+	}
+#endif
+}
+
+
+void
+outstr(const char *p, struct output *file)
+{
+#ifdef USE_GLIBC_STDIO
+	INTOFF;
+	fputs(p, file->stream);
+	INTON;
+#else
+	size_t len;
+
+	len = strlen(p);
+	outmem(p, len, file);
+#endif
+}
+
+
+#ifndef USE_GLIBC_STDIO
+
+
+void
+outcslow(int c, struct output *dest)
+{
+	char buf = c;
+	outmem(&buf, 1, dest);
+}
+#endif
+
+
+void
+flushall(void)
+{
+	flushout(&output);
+#ifdef FLUSHERR
+	flushout(&errout);
+#endif
+}
+
+
+void
+flushout(struct output *dest)
+{
+#ifdef USE_GLIBC_STDIO
+	INTOFF;
+	fflush(dest->stream);
+	INTON;
+#else
+	size_t len;
+
+	len = dest->nextc - dest->buf;
+	if (!len || dest->fd < 0)
+		return;
+	dest->nextc = dest->buf;
+	if ((xwrite(dest->fd, dest->buf, len)))
+		dest->flags |= OUTPUT_ERR;
+#endif
+}
+
+
+void
+outfmt(struct output *file, const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	doformat(file, fmt, ap);
+	va_end(ap);
+}
+
+
+void
+out1fmt(const char *fmt, ...)
+{
+	va_list ap;
+
+	va_start(ap, fmt);
+	doformat(out1, fmt, ap);
+	va_end(ap);
+}
+
+
+int
+fmtstr(char *outbuf, size_t length, const char *fmt, ...)
+{
+	va_list ap;
+	int ret;
+
+	va_start(ap, fmt);
+	ret = xvsnprintf(outbuf, length, fmt, ap);
+	va_end(ap);
+	return ret > (int)length ? length : ret;
+}
+
+
+static int xvasprintf(char **sp, size_t size, const char *f, va_list ap)
+{
+	char *s;
+	int len;
+	va_list ap2;
+
+	va_copy(ap2, ap);
+	len = xvsnprintf(*sp, size, f, ap2);
+	va_end(ap2);
+	if (len < 0)
+		sh_error("xvsnprintf failed");
+	if (len < size)
+		return len;
+
+	s = stalloc((len >= stackblocksize() ? len : stackblocksize()) + 1);
+	*sp = s;
+	len = xvsnprintf(s, len + 1, f, ap);
+	return len;
+}
+
+
+int xasprintf(char **sp, const char *f, ...)
+{
+	va_list ap;
+	int ret;
+
+	va_start(ap, f);
+	ret = xvasprintf(sp, 0, f, ap);
+	va_end(ap);
+	return ret;
+}
+
+
+#ifndef USE_GLIBC_STDIO
+void
+doformat(struct output *dest, const char *f, va_list ap)
+{
+	struct stackmark smark;
+	char *s;
+	int len;
+	int olen;
+
+	setstackmark(&smark);
+	s = dest->nextc;
+	olen = dest->end - dest->nextc;
+	len = xvasprintf(&s, olen, f, ap);
+	if (likely(olen > len)) {
+		dest->nextc += len;
+		goto out;
+	}
+	outmem(s, len, dest);
+out:
+	popstackmark(&smark);
+}
+#endif
+
+
+
+/*
+ * Version of write which resumes after a signal is caught.
+ */
+
+int
+xwrite(int fd, const void *p, size_t n)
+{
+	const char *buf = p;
+
+	while (n) {
+		ssize_t i;
+		size_t m;
+
+		m = n;
+		if (m > SSIZE_MAX)
+			m = SSIZE_MAX;
+		do {
+			i = write(fd, buf, m);
+		} while (i < 0 && errno == EINTR);
+		if (i < 0)
+			return -1;
+		buf += i;
+		n -= i;
+	}
+	return 0;
+}
+
+
+#ifdef notyet
+#ifdef USE_GLIBC_STDIO
+void initstreams() {
+	output.stream = stdout;
+	errout.stream = stderr;
+}
+
+
+void
+openmemout(void) {
+	INTOFF;
+	memout.stream = open_memstream(&memout.buf, &memout.bufsize);
+	INTON;
+}
+
+
+int
+__closememout(void) {
+	int error;
+	error = fclose(memout.stream);
+	memout.stream = NULL;
+	return error;
+}
+#endif
+#endif
+
+
+static int
+xvsnprintf(char *outbuf, size_t length, const char *fmt, va_list ap)
+{
+	int ret;
+
+#ifdef __sun
+	/*
+	 * vsnprintf() on older versions of Solaris returns -1 when
+	 * passed a length of 0.  To avoid this, use a dummy
+	 * 1-character buffer instead.
+	 */
+	char dummy[1];
+
+	if (length == 0) {
+		outbuf = dummy;
+		length = sizeof(dummy);
+	}
+#endif
+
+	INTOFF;
+	ret = vsnprintf(outbuf, length, fmt, ap);
+	INTON;
+	return ret;
+}
diff --git a/bin/dash/src/output.h b/bin/dash/src/output.h
new file mode 100644
index 00000000..c43d4937
--- /dev/null
+++ b/bin/dash/src/output.h
@@ -0,0 +1,126 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)output.h	8.2 (Berkeley) 5/4/95
+ */
+
+#ifndef OUTPUT_INCL
+
+#include <stdarg.h>
+#ifdef USE_GLIBC_STDIO
+#include <stdio.h>
+#endif
+#include <sys/types.h>
+
+struct output {
+#ifdef USE_GLIBC_STDIO
+	FILE *stream;
+#endif
+	char *nextc;
+	char *end;
+	char *buf;
+	size_t bufsize;
+	int fd;
+	int flags;
+};
+
+extern struct output output;
+extern struct output errout;
+extern struct output preverrout;
+#ifdef notyet
+extern struct output memout;
+#endif
+extern struct output *out1;
+extern struct output *out2;
+
+void outmem(const char *, size_t, struct output *);
+void outstr(const char *, struct output *);
+#ifndef USE_GLIBC_STDIO
+void outcslow(int, struct output *);
+#endif
+void flushall(void);
+void flushout(struct output *);
+void outfmt(struct output *, const char *, ...)
+    __attribute__((__format__(__printf__,2,3)));
+void out1fmt(const char *, ...)
+    __attribute__((__format__(__printf__,1,2)));
+int fmtstr(char *, size_t, const char *, ...)
+    __attribute__((__format__(__printf__,3,4)));
+int xasprintf(char **, const char *, ...);
+#ifndef USE_GLIBC_STDIO
+void doformat(struct output *, const char *, va_list);
+#endif
+int xwrite(int, const void *, size_t);
+#ifdef notyet
+#ifdef USE_GLIBC_STDIO
+void initstreams(void);
+void openmemout(void);
+int __closememout(void);
+#endif
+#endif
+
+static inline void
+freestdout()
+{
+	output.nextc = output.buf;
+	output.flags = 0;
+}
+
+#define OUTPUT_ERR 01		/* error occurred on output */
+
+#ifdef USE_GLIBC_STDIO
+static inline void outc(int ch, struct output *file)
+{
+	putc(ch, file->stream);
+}
+#define doformat(d, f, a)	vfprintf((d)->stream, (f), (a))
+#else
+static inline void outc(int ch, struct output *file)
+{
+	if (file->nextc == file->end)
+		outcslow(ch, file);
+	else {
+		*file->nextc = ch;
+		file->nextc++;
+	}
+}
+#endif
+#define out1c(c)	outc((c), out1)
+#define out2c(c)	outcslow((c), out2)
+#define out1mem(s, l)	outmem((s), (l), out1)
+#define out1str(s)	outstr((s), out1)
+#define out2str(s)	outstr((s), out2)
+#define outerr(f)	(f)->flags
+
+#define OUTPUT_INCL
+#endif
diff --git a/bin/dash/src/parser.c b/bin/dash/src/parser.c
new file mode 100644
index 00000000..499f7f8e
--- /dev/null
+++ b/bin/dash/src/parser.c
@@ -0,0 +1,1677 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if HAVE_ALLOCA_H
+#include <alloca.h>
+#endif
+
+#include <stdlib.h>
+
+#include "shell.h"
+#include "parser.h"
+#include "nodes.h"
+#include "expand.h"	/* defines rmescapes() */
+#include "exec.h"	/* defines find_builtin() */
+#include "syntax.h"
+#include "options.h"
+#include "input.h"
+#include "output.h"
+#include "var.h"
+#include "error.h"
+#include "memalloc.h"
+#include "mystring.h"
+#include "alias.h"
+#include "show.h"
+#include "builtins.h"
+#include "system.h"
+#ifndef SMALL
+#include "myhistedit.h"
+#endif
+
+/*
+ * Shell command parser.
+ */
+
+/* values returned by readtoken */
+#include "token_vars.h"
+
+
+
+/* Used by expandstr to get here-doc like behaviour. */
+#define FAKEEOFMARK (char *)1
+
+
+
+struct heredoc {
+	struct heredoc *next;	/* next here document in list */
+	union node *here;		/* redirection node */
+	char *eofmark;		/* string indicating end of input */
+	int striptabs;		/* if set, strip leading tabs */
+};
+
+struct synstack {
+	const char *syntax;
+	struct synstack *prev;
+	struct synstack *next;
+	int innerdq;
+	int varpushed;
+	int dblquote;
+	int varnest;		/* levels of variables expansion */
+	int parenlevel;		/* levels of parens in arithmetic */
+	int dqvarnest;		/* levels of variables expansion within double quotes */
+};
+
+
+
+struct heredoc *heredoclist;	/* list of here documents to read */
+int doprompt;			/* if set, prompt the user */
+int needprompt;			/* true if interactive and at start of line */
+int lasttoken;			/* last token read */
+int tokpushback;		/* last token pushed back */
+char *wordtext;			/* text of last word returned by readtoken */
+int checkkwd;
+struct nodelist *backquotelist;
+union node *redirnode;
+struct heredoc *heredoc;
+int quoteflag;			/* set if (part of) last token was quoted */
+
+
+static char *promptcache;
+static char *rpromptcache;
+
+
+STATIC union node *list(int);
+STATIC union node *andor(void);
+STATIC union node *pipeline(void);
+STATIC union node *command(void);
+STATIC union node *simplecmd(void);
+STATIC union node *makename(void);
+STATIC void parsefname(void);
+STATIC void parseheredoc(void);
+STATIC int readtoken(void);
+STATIC int xxreadtoken(void);
+STATIC int pgetc_eatbnl();
+STATIC int readtoken1(int, char const *, char *, int);
+STATIC void synexpect(int) __attribute__((__noreturn__));
+STATIC void synerror(const char *) __attribute__((__noreturn__));
+STATIC void setprompt(int);
+
+
+int isassignment(const char *p)
+{
+	const char *q = endofname(p);
+	if (p == q)
+		return 0;
+	return *q == '=';
+}
+
+static inline int realeofmark(const char *eofmark)
+{
+	return eofmark && eofmark != FAKEEOFMARK;
+}
+
+
+/*
+ * Read and parse a command.  Returns NEOF on end of file.  (NULL is a
+ * valid parse tree indicating a blank line.)
+ */
+
+union node *
+parsecmd(int interact)
+{
+	tokpushback = 0;
+	checkkwd = 0;
+	heredoclist = 0;
+	doprompt = interact;
+	if (doprompt)
+		setprompt(doprompt);
+	needprompt = 0;
+	return list(1);
+}
+
+
+STATIC union node *
+list(int nlflag)
+{
+	int chknl = nlflag & 1 ? 0 : CHKNL;
+	union node *n1, *n2, *n3;
+	int tok;
+
+	n1 = NULL;
+	for (;;) {
+		checkkwd = chknl | CHKKWD | CHKALIAS;
+		tok = readtoken();
+		switch (tok) {
+		case TNL:
+			parseheredoc();
+			return n1;
+
+		case TEOF:
+			if (!n1 && !chknl)
+				n1 = NEOF;
+out_eof:
+			parseheredoc();
+			tokpushback++;
+			lasttoken = TEOF;
+			return n1;
+		}
+
+		tokpushback++;
+		if (nlflag == 2 && tokendlist[tok])
+			return n1;
+		nlflag |= 2;
+
+		n2 = andor();
+		tok = readtoken();
+		if (tok == TBACKGND) {
+			if (n2->type == NPIPE) {
+				n2->npipe.backgnd = 1;
+			} else {
+				if (n2->type != NREDIR) {
+					n3 = stalloc(sizeof(struct nredir));
+					n3->nredir.n = n2;
+					n3->nredir.redirect = NULL;
+					n2 = n3;
+				}
+				n2->type = NBACKGND;
+			}
+		}
+		if (n1 == NULL) {
+			n1 = n2;
+		}
+		else {
+			n3 = (union node *)stalloc(sizeof (struct nbinary));
+			n3->type = NSEMI;
+			n3->nbinary.ch1 = n1;
+			n3->nbinary.ch2 = n2;
+			n1 = n3;
+		}
+		switch (tok) {
+		case TEOF:
+			goto out_eof;
+		case TNL:
+			tokpushback++;
+			/* fall through */
+		case TBACKGND:
+		case TSEMI:
+			break;
+		default:
+			if (!chknl)
+				synexpect(-1);
+			tokpushback++;
+			return n1;
+		}
+	}
+}
+
+
+
+STATIC union node *
+andor(void)
+{
+	union node *n1, *n2, *n3;
+	int t;
+
+	n1 = pipeline();
+	for (;;) {
+		if ((t = readtoken()) == TAND) {
+			t = NAND;
+		} else if (t == TOR) {
+			t = NOR;
+		} else {
+			tokpushback++;
+			return n1;
+		}
+		checkkwd = CHKNL | CHKKWD | CHKALIAS;
+		n2 = pipeline();
+		n3 = (union node *)stalloc(sizeof (struct nbinary));
+		n3->type = t;
+		n3->nbinary.ch1 = n1;
+		n3->nbinary.ch2 = n2;
+		n1 = n3;
+	}
+}
+
+
+
+STATIC union node *
+pipeline(void)
+{
+	union node *n1, *n2, *pipenode;
+	struct nodelist *lp, *prev;
+	int negate;
+
+	negate = 0;
+	TRACE(("pipeline: entered\n"));
+	if (readtoken() == TNOT) {
+		negate = !negate;
+		checkkwd = CHKKWD | CHKALIAS;
+	} else
+		tokpushback++;
+	n1 = command();
+	if (readtoken() == TPIPE) {
+		pipenode = (union node *)stalloc(sizeof (struct npipe));
+		pipenode->type = NPIPE;
+		pipenode->npipe.backgnd = 0;
+		lp = (struct nodelist *)stalloc(sizeof (struct nodelist));
+		pipenode->npipe.cmdlist = lp;
+		lp->n = n1;
+		do {
+			prev = lp;
+			lp = (struct nodelist *)stalloc(sizeof (struct nodelist));
+			checkkwd = CHKNL | CHKKWD | CHKALIAS;
+			lp->n = command();
+			prev->next = lp;
+		} while (readtoken() == TPIPE);
+		lp->next = NULL;
+		n1 = pipenode;
+	}
+	tokpushback++;
+	if (negate) {
+		n2 = (union node *)stalloc(sizeof (struct nnot));
+		n2->type = NNOT;
+		n2->nnot.com = n1;
+		return n2;
+	} else
+		return n1;
+}
+
+
+
+STATIC union node *
+command(void)
+{
+	union node *n1, *n2;
+	union node *ap, **app;
+	union node *cp, **cpp;
+	union node *redir, **rpp;
+	union node **rpp2;
+	int t;
+	int savelinno;
+
+	redir = NULL;
+	rpp2 = &redir;
+
+	savelinno = plinno;
+
+	switch (readtoken()) {
+	default:
+		synexpect(-1);
+		/* NOTREACHED */
+	case TIF:
+		n1 = (union node *)stalloc(sizeof (struct nif));
+		n1->type = NIF;
+		n1->nif.test = list(0);
+		if (readtoken() != TTHEN)
+			synexpect(TTHEN);
+		n1->nif.ifpart = list(0);
+		n2 = n1;
+		while (readtoken() == TELIF) {
+			n2->nif.elsepart = (union node *)stalloc(sizeof (struct nif));
+			n2 = n2->nif.elsepart;
+			n2->type = NIF;
+			n2->nif.test = list(0);
+			if (readtoken() != TTHEN)
+				synexpect(TTHEN);
+			n2->nif.ifpart = list(0);
+		}
+		if (lasttoken == TELSE)
+			n2->nif.elsepart = list(0);
+		else {
+			n2->nif.elsepart = NULL;
+			tokpushback++;
+		}
+		t = TFI;
+		break;
+	case TWHILE:
+	case TUNTIL: {
+		int got;
+		n1 = (union node *)stalloc(sizeof (struct nbinary));
+		n1->type = (lasttoken == TWHILE)? NWHILE : NUNTIL;
+		n1->nbinary.ch1 = list(0);
+		if ((got=readtoken()) != TDO) {
+TRACE(("expecting DO got %s %s\n", tokname[got], got == TWORD ? wordtext : ""));
+			synexpect(TDO);
+		}
+		n1->nbinary.ch2 = list(0);
+		t = TDONE;
+		break;
+	}
+	case TFOR:
+		if (readtoken() != TWORD || quoteflag || ! goodname(wordtext))
+			synerror("Bad for loop variable");
+		n1 = (union node *)stalloc(sizeof (struct nfor));
+		n1->type = NFOR;
+		n1->nfor.linno = savelinno;
+		n1->nfor.var = wordtext;
+		checkkwd = CHKNL | CHKKWD | CHKALIAS;
+		if (readtoken() == TIN) {
+			app = &ap;
+			while (readtoken() == TWORD) {
+				n2 = (union node *)stalloc(sizeof (struct narg));
+				n2->type = NARG;
+				n2->narg.text = wordtext;
+				n2->narg.backquote = backquotelist;
+				*app = n2;
+				app = &n2->narg.next;
+			}
+			*app = NULL;
+			n1->nfor.args = ap;
+			if (lasttoken != TNL && lasttoken != TSEMI)
+				synexpect(-1);
+		} else {
+			n2 = (union node *)stalloc(sizeof (struct narg));
+			n2->type = NARG;
+			n2->narg.text = (char *)dolatstr;
+			n2->narg.backquote = NULL;
+			n2->narg.next = NULL;
+			n1->nfor.args = n2;
+			/*
+			 * Newline or semicolon here is optional (but note
+			 * that the original Bourne shell only allowed NL).
+			 */
+			if (lasttoken != TSEMI)
+				tokpushback++;
+		}
+		checkkwd = CHKNL | CHKKWD | CHKALIAS;
+		if (readtoken() != TDO)
+			synexpect(TDO);
+		n1->nfor.body = list(0);
+		t = TDONE;
+		break;
+	case TCASE:
+		n1 = (union node *)stalloc(sizeof (struct ncase));
+		n1->type = NCASE;
+		n1->ncase.linno = savelinno;
+		if (readtoken() != TWORD)
+			synexpect(TWORD);
+		n1->ncase.expr = n2 = (union node *)stalloc(sizeof (struct narg));
+		n2->type = NARG;
+		n2->narg.text = wordtext;
+		n2->narg.backquote = backquotelist;
+		n2->narg.next = NULL;
+		checkkwd = CHKNL | CHKKWD | CHKALIAS;
+		if (readtoken() != TIN)
+			synexpect(TIN);
+		cpp = &n1->ncase.cases;
+next_case:
+		checkkwd = CHKNL | CHKKWD;
+		t = readtoken();
+		while(t != TESAC) {
+			if (lasttoken == TLP)
+				readtoken();
+			*cpp = cp = (union node *)stalloc(sizeof (struct nclist));
+			cp->type = NCLIST;
+			app = &cp->nclist.pattern;
+			for (;;) {
+				*app = ap = (union node *)stalloc(sizeof (struct narg));
+				ap->type = NARG;
+				ap->narg.text = wordtext;
+				ap->narg.backquote = backquotelist;
+				if (readtoken() != TPIPE)
+					break;
+				app = &ap->narg.next;
+				readtoken();
+			}
+			ap->narg.next = NULL;
+			if (lasttoken != TRP)
+				synexpect(TRP);
+			cp->nclist.body = list(2);
+
+			cpp = &cp->nclist.next;
+
+			checkkwd = CHKNL | CHKKWD;
+			if ((t = readtoken()) != TESAC) {
+				if (t != TENDCASE)
+					synexpect(TENDCASE);
+				else
+					goto next_case;
+			}
+		}
+		*cpp = NULL;
+		goto redir;
+	case TLP:
+		n1 = (union node *)stalloc(sizeof (struct nredir));
+		n1->type = NSUBSHELL;
+		n1->nredir.linno = savelinno;
+		n1->nredir.n = list(0);
+		n1->nredir.redirect = NULL;
+		t = TRP;
+		break;
+	case TBEGIN:
+		n1 = list(0);
+		t = TEND;
+		break;
+	case TWORD:
+	case TREDIR:
+		tokpushback++;
+		return simplecmd();
+	}
+
+	if (readtoken() != t)
+		synexpect(t);
+
+redir:
+	/* Now check for redirection which may follow command */
+	checkkwd = CHKKWD | CHKALIAS;
+	rpp = rpp2;
+	while (readtoken() == TREDIR) {
+		*rpp = n2 = redirnode;
+		rpp = &n2->nfile.next;
+		parsefname();
+	}
+	tokpushback++;
+	*rpp = NULL;
+	if (redir) {
+		if (n1->type != NSUBSHELL) {
+			n2 = (union node *)stalloc(sizeof (struct nredir));
+			n2->type = NREDIR;
+			n2->nredir.linno = savelinno;
+			n2->nredir.n = n1;
+			n1 = n2;
+		}
+		n1->nredir.redirect = redir;
+	}
+
+	return n1;
+}
+
+
+STATIC union node *
+simplecmd(void) {
+	union node *args, **app;
+	union node *n = NULL;
+	union node *vars, **vpp;
+	union node **rpp, *redir;
+	int savecheckkwd;
+	int savelinno;
+
+	args = NULL;
+	app = &args;
+	vars = NULL;
+	vpp = &vars;
+	redir = NULL;
+	rpp = &redir;
+
+	savecheckkwd = CHKALIAS;
+	savelinno = plinno;
+	for (;;) {
+		checkkwd = savecheckkwd;
+		switch (readtoken()) {
+		case TWORD:
+			n = (union node *)stalloc(sizeof (struct narg));
+			n->type = NARG;
+			n->narg.text = wordtext;
+			n->narg.backquote = backquotelist;
+			if (savecheckkwd && isassignment(wordtext)) {
+				*vpp = n;
+				vpp = &n->narg.next;
+			} else {
+				*app = n;
+				app = &n->narg.next;
+				savecheckkwd = 0;
+			}
+			break;
+		case TREDIR:
+			*rpp = n = redirnode;
+			rpp = &n->nfile.next;
+			parsefname();	/* read name of redirection file */
+			break;
+		case TLP:
+			if (
+				args && app == &args->narg.next &&
+				!vars && !redir
+			) {
+				struct builtincmd *bcmd;
+				const char *name;
+
+				/* We have a function */
+				if (readtoken() != TRP)
+					synexpect(TRP);
+				name = n->narg.text;
+				if (
+					!goodname(name) || (
+						(bcmd = find_builtin(name)) &&
+						bcmd->flags & BUILTIN_SPECIAL
+					)
+				)
+					synerror("Bad function name");
+				n->type = NDEFUN;
+				checkkwd = CHKNL | CHKKWD | CHKALIAS;
+				n->ndefun.text = n->narg.text;
+				n->ndefun.linno = plinno;
+				n->ndefun.body = command();
+				return n;
+			}
+			/* fall through */
+		default:
+			tokpushback++;
+			goto out;
+		}
+	}
+out:
+	*app = NULL;
+	*vpp = NULL;
+	*rpp = NULL;
+	n = (union node *)stalloc(sizeof (struct ncmd));
+	n->type = NCMD;
+	n->ncmd.linno = savelinno;
+	n->ncmd.args = args;
+	n->ncmd.assign = vars;
+	n->ncmd.redirect = redir;
+	return n;
+}
+
+STATIC union node *
+makename(void)
+{
+	union node *n;
+
+	n = (union node *)stalloc(sizeof (struct narg));
+	n->type = NARG;
+	n->narg.next = NULL;
+	n->narg.text = wordtext;
+	n->narg.backquote = backquotelist;
+	return n;
+}
+
+void fixredir(union node *n, const char *text, int err)
+	{
+	TRACE(("Fix redir %s %d\n", text, err));
+	if (!err)
+		n->ndup.vname = NULL;
+
+	if (is_digit(text[0]) && text[1] == '\0')
+		n->ndup.dupfd = digit_val(text[0]);
+	else if (text[0] == '-' && text[1] == '\0')
+		n->ndup.dupfd = -1;
+	else {
+
+		if (err)
+			synerror("Bad fd number");
+		else
+			n->ndup.vname = makename();
+	}
+}
+
+
+STATIC void
+parsefname(void)
+{
+	union node *n = redirnode;
+
+	if (n->type == NHERE)
+		checkkwd = CHKEOFMARK;
+	if (readtoken() != TWORD)
+		synexpect(-1);
+	if (n->type == NHERE) {
+		struct heredoc *here = heredoc;
+		struct heredoc *p;
+
+		if (quoteflag == 0)
+			n->type = NXHERE;
+		TRACE(("Here document %d\n", n->type));
+		rmescapes(wordtext);
+		here->eofmark = wordtext;
+		here->next = NULL;
+		if (heredoclist == NULL)
+			heredoclist = here;
+		else {
+			for (p = heredoclist ; p->next ; p = p->next);
+			p->next = here;
+		}
+	} else if (n->type == NTOFD || n->type == NFROMFD) {
+		fixredir(n, wordtext, 0);
+	} else {
+		n->nfile.fname = makename();
+	}
+}
+
+
+/*
+ * Input any here documents.
+ */
+
+STATIC void
+parseheredoc(void)
+{
+	struct heredoc *here;
+	union node *n;
+
+	here = heredoclist;
+	heredoclist = 0;
+
+	while (here) {
+		if (needprompt) {
+			setprompt(2);
+		}
+		if (here->here->type == NHERE)
+			readtoken1(pgetc(), SQSYNTAX, here->eofmark, here->striptabs);
+		else
+			readtoken1(pgetc_eatbnl(), DQSYNTAX, here->eofmark, here->striptabs);
+		n = (union node *)stalloc(sizeof (struct narg));
+		n->narg.type = NARG;
+		n->narg.next = NULL;
+		n->narg.text = wordtext;
+		n->narg.backquote = backquotelist;
+		here->here->nhere.doc = n;
+		here = here->next;
+	}
+}
+
+STATIC int
+readtoken(void)
+{
+	int t;
+	int kwd = checkkwd;
+#ifdef DEBUG
+	int alreadyseen = tokpushback;
+#endif
+
+top:
+	t = xxreadtoken();
+
+	/*
+	 * eat newlines
+	 */
+	if (kwd & CHKNL) {
+		while (t == TNL) {
+			parseheredoc();
+			checkkwd = 0;
+			t = xxreadtoken();
+		}
+	}
+
+	kwd |= checkkwd;
+	checkkwd = 0;
+
+	if (t != TWORD || quoteflag) {
+		goto out;
+	}
+
+	/*
+	 * check for keywords
+	 */
+	if (kwd & CHKKWD) {
+		const char *const *pp;
+
+		if ((pp = findkwd(wordtext))) {
+			lasttoken = t = pp - parsekwd + KWDOFFSET;
+			TRACE(("keyword %s recognized\n", tokname[t]));
+			goto out;
+		}
+	}
+
+	if (kwd & CHKALIAS) {
+		struct alias *ap;
+		if ((ap = lookupalias(wordtext, 1)) != NULL) {
+			if (*ap->val) {
+				pushstring(ap->val, ap);
+			}
+			goto top;
+		}
+	}
+out:
+#ifdef DEBUG
+	if (!alreadyseen)
+	    TRACE(("token %s %s\n", tokname[t], t == TWORD ? wordtext : ""));
+	else
+	    TRACE(("reread token %s %s\n", tokname[t], t == TWORD ? wordtext : ""));
+#endif
+	return (t);
+}
+
+static void nlprompt(void)
+{
+	plinno++;
+	if (doprompt)
+		setprompt(2);
+}
+
+static void nlnoprompt(void)
+{
+	plinno++;
+	needprompt = doprompt;
+}
+
+
+/*
+ * Read the next input token.
+ * If the token is a word, we set backquotelist to the list of cmds in
+ *	backquotes.  We set quoteflag to true if any part of the word was
+ *	quoted.
+ * If the token is TREDIR, then we set redirnode to a structure containing
+ *	the redirection.
+ *
+ * [Change comment:  here documents and internal procedures]
+ * [Readtoken shouldn't have any arguments.  Perhaps we should make the
+ *  word parsing code into a separate routine.  In this case, readtoken
+ *  doesn't need to have any internal procedures, but parseword does.
+ *  We could also make parseoperator in essence the main routine, and
+ *  have parseword (readtoken1?) handle both words and redirection.]
+ */
+
+#define RETURN(token)	return lasttoken = token
+
+STATIC int
+xxreadtoken(void)
+{
+	int c;
+
+	if (tokpushback) {
+		tokpushback = 0;
+		return lasttoken;
+	}
+	if (needprompt) {
+		setprompt(2);
+	}
+	for (;;) {	/* until token or start of word found */
+		c = pgetc_eatbnl();
+		switch (c) {
+		case ' ': case '\t':
+		case PEOA:
+			continue;
+		case '#':
+			while ((c = pgetc()) != '\n' && c != PEOF);
+			pungetc();
+			continue;
+		case '\n':
+			nlnoprompt();
+			RETURN(TNL);
+		case PEOF:
+			RETURN(TEOF);
+		case '&':
+			if (pgetc_eatbnl() == '&')
+				RETURN(TAND);
+			pungetc();
+			RETURN(TBACKGND);
+		case '|':
+			if (pgetc_eatbnl() == '|')
+				RETURN(TOR);
+			pungetc();
+			RETURN(TPIPE);
+		case ';':
+			if (pgetc_eatbnl() == ';')
+				RETURN(TENDCASE);
+			pungetc();
+			RETURN(TSEMI);
+		case '(':
+			RETURN(TLP);
+		case ')':
+			RETURN(TRP);
+		}
+		break;
+	}
+	return readtoken1(c, BASESYNTAX, (char *)NULL, 0);
+#undef RETURN
+}
+
+static int pgetc_eatbnl(void)
+{
+	int c;
+
+	while ((c = pgetc()) == '\\') {
+		if (pgetc2() != '\n') {
+			pungetc();
+			break;
+		}
+
+		nlprompt();
+	}
+
+	return c;
+}
+
+static int pgetc_top(struct synstack *stack)
+{
+	return stack->syntax == SQSYNTAX ? pgetc() : pgetc_eatbnl();
+}
+
+static void synstack_push(struct synstack **stack, struct synstack *next,
+			  const char *syntax)
+{
+	memset(next, 0, sizeof(*next));
+	next->syntax = syntax;
+	next->next = *stack;
+	(*stack)->prev = next;
+	*stack = next;
+}
+
+static void synstack_pop(struct synstack **stack)
+{
+	*stack = (*stack)->next;
+}
+
+
+
+/*
+ * If eofmark is NULL, read a word or a redirection symbol.  If eofmark
+ * is not NULL, read a here document.  In the latter case, eofmark is the
+ * word which marks the end of the document and striptabs is true if
+ * leading tabs should be stripped from the document.  The argument firstc
+ * is the first character of the input token or document.
+ *
+ * Because C does not have internal subroutines, I have simulated them
+ * using goto's to implement the subroutine linkage.  The following macros
+ * will run code that appears at the end of readtoken1.
+ */
+
+#define CHECKEND()	{goto checkend; checkend_return:;}
+#define PARSEREDIR()	{goto parseredir; parseredir_return:;}
+#define PARSESUB()	{goto parsesub; parsesub_return:;}
+#define PARSEBACKQOLD()	{oldstyle = 1; goto parsebackq; parsebackq_oldreturn:;}
+#define PARSEBACKQNEW()	{oldstyle = 0; goto parsebackq; parsebackq_newreturn:;}
+#define	PARSEARITH()	{goto parsearith; parsearith_return:;}
+
+STATIC int
+readtoken1(int firstc, char const *syntax, char *eofmark, int striptabs)
+{
+	int c = firstc;
+	char *out;
+	size_t len;
+	struct nodelist *bqlist;
+	int quotef;
+	int oldstyle;
+	/* syntax stack */
+	struct synstack synbase = { .syntax = syntax };
+	struct synstack *synstack = &synbase;
+
+	if (syntax == DQSYNTAX)
+		synstack->dblquote = 1;
+	quotef = 0;
+	bqlist = NULL;
+
+	STARTSTACKSTR(out);
+	loop: {	/* for each line, until end of word */
+#if ATTY
+		if (c == '\034' && doprompt
+		 && attyset() && ! equal(termval(), "emacs")) {
+			attyline();
+			if (synstack->syntax == BASESYNTAX)
+				return readtoken();
+			c = pgetc_top(synstack);
+			goto loop;
+		}
+#endif
+		CHECKEND();	/* set c to PEOF if at end of here document */
+		for (;;) {	/* until end of line or end of word */
+			CHECKSTRSPACE(4, out);	/* permit 4 calls to USTPUTC */
+			switch(synstack->syntax[c]) {
+			case CNL:	/* '\n' */
+				if (synstack->syntax == BASESYNTAX &&
+				    !synstack->varnest)
+					goto endword;	/* exit outer loop */
+				USTPUTC(c, out);
+				nlprompt();
+				c = pgetc_top(synstack);
+				goto loop;		/* continue outer loop */
+			case CWORD:
+				USTPUTC(c, out);
+				break;
+			case CCTL:
+				if ((!eofmark) | synstack->dblquote |
+				    synstack->varnest)
+					USTPUTC(CTLESC, out);
+				USTPUTC(c, out);
+				break;
+			/* backslash */
+			case CBACK:
+				c = pgetc2();
+				if (c == PEOF) {
+					USTPUTC(CTLESC, out);
+					USTPUTC('\\', out);
+					pungetc();
+				} else {
+					if (
+						synstack->dblquote &&
+						c != '\\' && c != '`' &&
+						c != '$' && (
+							c != '"' ||
+							(eofmark != NULL &&
+							 !synstack->varnest)
+						) && (
+							c != '}' ||
+							!synstack->varnest
+						)
+					) {
+						USTPUTC(CTLESC, out);
+						USTPUTC('\\', out);
+					}
+					USTPUTC(CTLESC, out);
+					USTPUTC(c, out);
+					quotef++;
+				}
+				break;
+			case CSQUOTE:
+				synstack->syntax = SQSYNTAX;
+quotemark:
+				if (eofmark == NULL) {
+					USTPUTC(CTLQUOTEMARK, out);
+				}
+				break;
+			case CDQUOTE:
+				synstack->syntax = DQSYNTAX;
+				synstack->dblquote = 1;
+toggledq:
+				if (synstack->varnest)
+					synstack->innerdq ^= 1;
+				goto quotemark;
+			case CENDQUOTE:
+				if (eofmark && !synstack->varnest) {
+					USTPUTC(c, out);
+					break;
+				}
+
+				if (synstack->dqvarnest == 0) {
+					synstack->syntax = BASESYNTAX;
+					synstack->dblquote = 0;
+				}
+
+				quotef++;
+
+				if (c == '"')
+					goto toggledq;
+
+				goto quotemark;
+			case CVAR:	/* '$' */
+				PARSESUB();		/* parse substitution */
+				break;
+			case CENDVAR:	/* '}' */
+				if (!synstack->innerdq &&
+				    synstack->varnest > 0) {
+					if (!--synstack->varnest &&
+					    synstack->varpushed)
+						synstack_pop(&synstack);
+					else if (synstack->dqvarnest > 0)
+						synstack->dqvarnest--;
+					USTPUTC(CTLENDVAR, out);
+				} else {
+					USTPUTC(c, out);
+				}
+				break;
+			case CLP:	/* '(' in arithmetic */
+				synstack->parenlevel++;
+				USTPUTC(c, out);
+				break;
+			case CRP:	/* ')' in arithmetic */
+				if (synstack->parenlevel > 0) {
+					USTPUTC(c, out);
+					--synstack->parenlevel;
+				} else {
+					if (pgetc_eatbnl() == ')') {
+						USTPUTC(CTLENDARI, out);
+						synstack_pop(&synstack);
+					} else {
+						/*
+						 * unbalanced parens
+						 *  (don't 2nd guess - no error)
+						 */
+						pungetc();
+						USTPUTC(')', out);
+					}
+				}
+				break;
+			case CBQUOTE:	/* '`' */
+				if (checkkwd & CHKEOFMARK) {
+					USTPUTC('`', out);
+					break;
+				}
+
+				PARSEBACKQOLD();
+				break;
+			case CEOF:
+				goto endword;		/* exit outer loop */
+			case CIGN:
+				break;
+			default:
+				if (synstack->varnest == 0)
+					goto endword;	/* exit outer loop */
+				if (c != PEOA) {
+					USTPUTC(c, out);
+				}
+			}
+			c = pgetc_top(synstack);
+		}
+	}
+endword:
+	if (synstack->syntax == ARISYNTAX)
+		synerror("Missing '))'");
+	if (synstack->syntax != BASESYNTAX && eofmark == NULL)
+		synerror("Unterminated quoted string");
+	if (synstack->varnest != 0) {
+		/* { */
+		synerror("Missing '}'");
+	}
+	USTPUTC('\0', out);
+	len = out - (char *)stackblock();
+	out = stackblock();
+	if (eofmark == NULL) {
+		if ((c == '>' || c == '<')
+		 && quotef == 0
+		 && len <= 2
+		 && (*out == '\0' || is_digit(*out))) {
+			PARSEREDIR();
+			return lasttoken = TREDIR;
+		} else {
+			pungetc();
+		}
+	}
+	quoteflag = quotef;
+	backquotelist = bqlist;
+	grabstackblock(len);
+	wordtext = out;
+	return lasttoken = TWORD;
+/* end of readtoken routine */
+
+
+
+/*
+ * Check to see whether we are at the end of the here document.  When this
+ * is called, c is set to the first character of the next input line.  If
+ * we are at the end of the here document, this routine sets the c to PEOF.
+ */
+
+checkend: {
+	if (realeofmark(eofmark)) {
+		int markloc;
+		char *p;
+
+		if (c == PEOA) {
+			c = pgetc2();
+		}
+		if (striptabs) {
+			while (c == '\t') {
+				c = pgetc2();
+			}
+		}
+
+		markloc = out - (char *)stackblock();
+		for (p = eofmark; STPUTC(c, out), *p; p++) {
+			if (c != *p)
+				goto more_heredoc;
+
+			c = pgetc2();
+		}
+
+		if (c == '\n' || c == PEOF) {
+			c = PEOF;
+			nlnoprompt();
+		} else {
+			int len;
+
+more_heredoc:
+			p = (char *)stackblock() + markloc + 1;
+			len = out - p;
+
+			if (len) {
+				len -= c < 0;
+				c = p[-1];
+
+				if (len) {
+					char *str;
+
+					str = alloca(len + 1);
+					*(char *)mempcpy(str, p, len) = 0;
+
+					pushstring(str, NULL);
+				}
+			}
+		}
+
+		STADJUST((char *)stackblock() + markloc - out, out);
+	}
+	goto checkend_return;
+}
+
+
+/*
+ * Parse a redirection operator.  The variable "out" points to a string
+ * specifying the fd to be redirected.  The variable "c" contains the
+ * first character of the redirection operator.
+ */
+
+parseredir: {
+	char fd = *out;
+	union node *np;
+
+	np = (union node *)stalloc(sizeof (struct nfile));
+	if (c == '>') {
+		np->nfile.fd = 1;
+		c = pgetc_eatbnl();
+		if (c == '>')
+			np->type = NAPPEND;
+		else if (c == '|')
+			np->type = NCLOBBER;
+		else if (c == '&')
+			np->type = NTOFD;
+		else {
+			np->type = NTO;
+			pungetc();
+		}
+	} else {	/* c == '<' */
+		np->nfile.fd = 0;
+		switch (c = pgetc_eatbnl()) {
+		case '<':
+			if (sizeof (struct nfile) != sizeof (struct nhere)) {
+				np = (union node *)stalloc(sizeof (struct nhere));
+				np->nfile.fd = 0;
+			}
+			np->type = NHERE;
+			heredoc = (struct heredoc *)stalloc(sizeof (struct heredoc));
+			heredoc->here = np;
+			if ((c = pgetc_eatbnl()) == '-') {
+				heredoc->striptabs = 1;
+			} else {
+				heredoc->striptabs = 0;
+				pungetc();
+			}
+			break;
+
+		case '&':
+			np->type = NFROMFD;
+			break;
+
+		case '>':
+			np->type = NFROMTO;
+			break;
+
+		default:
+			np->type = NFROM;
+			pungetc();
+			break;
+		}
+	}
+	if (fd != '\0')
+		np->nfile.fd = digit_val(fd);
+	redirnode = np;
+	goto parseredir_return;
+}
+
+
+/*
+ * Parse a substitution.  At this point, we have read the dollar sign
+ * and nothing else.
+ */
+
+parsesub: {
+	int subtype;
+	int typeloc;
+	char *p;
+	static const char types[] = "}-+?=";
+
+	c = pgetc_eatbnl();
+	if (
+		(checkkwd & CHKEOFMARK) ||
+		c <= PEOA  ||
+		(c != '(' && c != '{' && !is_name(c) && !is_special(c))
+	) {
+		USTPUTC('$', out);
+		pungetc();
+	} else if (c == '(') {	/* $(command) or $((arith)) */
+		if (pgetc_eatbnl() == '(') {
+			PARSEARITH();
+		} else {
+			pungetc();
+			PARSEBACKQNEW();
+		}
+	} else {
+		const char *newsyn = synstack->syntax;
+
+		USTPUTC(CTLVAR, out);
+		typeloc = out - (char *)stackblock();
+		STADJUST(1, out);
+		subtype = VSNORMAL;
+		if (likely(c == '{')) {
+			c = pgetc_eatbnl();
+			subtype = 0;
+		}
+varname:
+		if (is_name(c)) {
+			do {
+				STPUTC(c, out);
+				c = pgetc_eatbnl();
+			} while (is_in_name(c));
+		} else if (is_digit(c)) {
+			do {
+				STPUTC(c, out);
+				c = pgetc_eatbnl();
+			} while (!subtype && is_digit(c));
+		} else if (c != '}') {
+			int cc = c;
+
+			c = pgetc_eatbnl();
+
+			if (!subtype && cc == '#') {
+				subtype = VSLENGTH;
+
+				if (c == '_' || isalnum(c))
+					goto varname;
+
+				cc = c;
+				c = pgetc_eatbnl();
+				if (cc == '}' || c != '}') {
+					pungetc();
+					subtype = 0;
+					c = cc;
+					cc = '#';
+				}
+			}
+
+			if (!is_special(cc)) {
+				if (subtype == VSLENGTH)
+					subtype = 0;
+				goto badsub;
+			}
+
+			USTPUTC(cc, out);
+		} else
+			goto badsub;
+
+		if (subtype == 0) {
+			int cc = c;
+
+			switch (c) {
+			case ':':
+				subtype = VSNUL;
+				c = pgetc_eatbnl();
+				/*FALLTHROUGH*/
+			default:
+				p = strchr(types, c);
+				if (p == NULL)
+					break;
+				subtype |= p - types + VSNORMAL;
+				break;
+			case '%':
+			case '#':
+				subtype = c == '#' ? VSTRIMLEFT :
+						     VSTRIMRIGHT;
+				c = pgetc_eatbnl();
+				if (c == cc)
+					subtype++;
+				else
+					pungetc();
+
+				newsyn = BASESYNTAX;
+				break;
+			}
+		} else {
+badsub:
+			pungetc();
+		}
+
+		if (newsyn == ARISYNTAX)
+			newsyn = DQSYNTAX;
+
+		if ((newsyn != synstack->syntax || synstack->innerdq) &&
+		    subtype != VSNORMAL) {
+			synstack_push(&synstack,
+				      synstack->prev ?:
+				      alloca(sizeof(*synstack)),
+				      newsyn);
+
+			synstack->varpushed++;
+			synstack->dblquote = newsyn != BASESYNTAX;
+		}
+
+		*((char *)stackblock() + typeloc) = subtype;
+		if (subtype != VSNORMAL) {
+			synstack->varnest++;
+			if (synstack->dblquote)
+				synstack->dqvarnest++;
+		}
+		STPUTC('=', out);
+	}
+	goto parsesub_return;
+}
+
+
+/*
+ * Called to parse command substitutions.  Newstyle is set if the command
+ * is enclosed inside $(...); nlpp is a pointer to the head of the linked
+ * list of commands (passed by reference), and savelen is the number of
+ * characters on the top of the stack which must be preserved.
+ */
+
+parsebackq: {
+	struct nodelist **nlpp;
+	union node *n;
+	char *str;
+	size_t savelen;
+	struct heredoc *saveheredoclist;
+	int uninitialized_var(saveprompt);
+
+	str = NULL;
+	savelen = out - (char *)stackblock();
+	if (savelen > 0) {
+		str = alloca(savelen);
+		memcpy(str, stackblock(), savelen);
+	}
+        if (oldstyle) {
+                /* We must read until the closing backquote, giving special
+                   treatment to some slashes, and then push the string and
+                   reread it as input, interpreting it normally.  */
+                char *pout;
+                int pc;
+                size_t psavelen;
+                char *pstr;
+
+
+                STARTSTACKSTR(pout);
+		for (;;) {
+			if (needprompt) {
+				setprompt(2);
+			}
+			switch (pc = pgetc_eatbnl()) {
+			case '`':
+				goto done;
+
+			case '\\':
+                                pc = pgetc();
+                                if (pc != '\\' && pc != '`' && pc != '$'
+                                    && (!synstack->dblquote || pc != '"'))
+                                        STPUTC('\\', pout);
+				if (pc > PEOA) {
+					break;
+				}
+				/* fall through */
+
+			case PEOF:
+			case PEOA:
+				synerror("EOF in backquote substitution");
+
+			case '\n':
+				nlnoprompt();
+				break;
+
+			default:
+				break;
+			}
+			STPUTC(pc, pout);
+                }
+done:
+                STPUTC('\0', pout);
+                psavelen = pout - (char *)stackblock();
+                if (psavelen > 0) {
+			pstr = grabstackstr(pout);
+			setinputstring(pstr);
+                }
+        }
+	nlpp = &bqlist;
+	while (*nlpp)
+		nlpp = &(*nlpp)->next;
+	*nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist));
+	(*nlpp)->next = NULL;
+
+	saveheredoclist = heredoclist;
+	heredoclist = NULL;
+
+	if (oldstyle) {
+		saveprompt = doprompt;
+		doprompt = 0;
+	}
+
+	n = list(2);
+
+	if (oldstyle)
+		doprompt = saveprompt;
+	else {
+		if (readtoken() != TRP)
+			synexpect(TRP);
+		setinputstring(nullstr);
+	}
+
+	parseheredoc();
+	heredoclist = saveheredoclist;
+
+	(*nlpp)->n = n;
+	/* Start reading from old file again. */
+	popfile();
+	/* Ignore any pushed back tokens left from the backquote parsing. */
+	if (oldstyle)
+		tokpushback = 0;
+	out = growstackto(savelen + 1);
+	if (str) {
+		memcpy(out, str, savelen);
+		STADJUST(savelen, out);
+	}
+	USTPUTC(CTLBACKQ, out);
+	if (oldstyle)
+		goto parsebackq_oldreturn;
+	else
+		goto parsebackq_newreturn;
+}
+
+/*
+ * Parse an arithmetic expansion (indicate start of one and set state)
+ */
+parsearith: {
+
+	synstack_push(&synstack,
+		      synstack->prev ?: alloca(sizeof(*synstack)),
+		      ARISYNTAX);
+	synstack->dblquote = 1;
+	USTPUTC(CTLARI, out);
+	goto parsearith_return;
+}
+
+} /* end of readtoken */
+
+
+
+#ifdef mkinit
+INCLUDE "parser.h"
+#endif
+
+
+/*
+ * Return of a legal variable name (a letter or underscore followed by zero or
+ * more letters, underscores, and digits).
+ */
+
+char *
+endofname(const char *name)
+	{
+	char *p;
+
+	p = (char *) name;
+	if (! is_name(*p))
+		return p;
+	while (*++p) {
+		if (! is_in_name(*p))
+			break;
+	}
+	return p;
+}
+
+
+/*
+ * Called when an unexpected token is read during the parse.  The argument
+ * is the token that is expected, or -1 if more than one type of token can
+ * occur at this point.
+ */
+
+STATIC void
+synexpect(int token)
+{
+	char msg[64];
+
+	if (token >= 0) {
+		fmtstr(msg, 64, "%s unexpected (expecting %s)",
+			tokname[lasttoken], tokname[token]);
+	} else {
+		fmtstr(msg, 64, "%s unexpected", tokname[lasttoken]);
+	}
+	synerror(msg);
+	/* NOTREACHED */
+}
+
+
+STATIC void
+synerror(const char *msg)
+{
+	errlinno = plinno;
+	sh_error("Syntax error: %s", msg);
+	/* NOTREACHED */
+}
+
+const char *
+expandstr(const char *ps)
+{
+	struct parsefile *file_stop;
+	struct jmploc *volatile savehandler;
+	struct heredoc *saveheredoclist;
+	const char *result;
+	int saveprompt;
+	struct jmploc jmploc;
+	union node n;
+	int err;
+
+	file_stop = parsefile;
+
+	/* XXX Fix (char *) cast. */
+	setinputstring((char *)ps);
+
+	saveheredoclist = heredoclist;
+	heredoclist = NULL;
+	saveprompt = doprompt;
+	doprompt = 0;
+	result = ps;
+	savehandler = handler;
+	if (unlikely(err = setjmp(jmploc.loc)))
+		goto out;
+	handler = &jmploc;
+
+	readtoken1(pgetc_eatbnl(), DQSYNTAX, FAKEEOFMARK, 0);
+
+	n.narg.type = NARG;
+	n.narg.next = NULL;
+	n.narg.text = wordtext;
+	n.narg.backquote = backquotelist;
+
+	expandarg(&n, NULL, EXP_QUOTED);
+	result = stackblock();
+
+out:
+	handler = savehandler;
+	if (err && exception != EXERROR)
+		longjmp(handler->loc, 1);
+
+	doprompt = saveprompt;
+	unwindfiles(file_stop);
+	heredoclist = saveheredoclist;
+
+	return result;
+}
+
+STATIC void
+setprompt(int which)
+{
+	struct stackmark smark;
+	const char *prompt, *rprompt, *nl;
+	int show;
+
+	needprompt = 0;
+	whichprompt = which;
+
+	switch (whichprompt) {
+	default:
+#ifdef DEBUG
+		prompt = "<internal prompt error>";
+		rprompt = prompt;
+		break;
+#endif
+	case 0:
+		prompt = nullstr;
+		rprompt = nullstr;
+		break;
+	case 1:
+		prompt = ps1val();
+		rprompt = rps1val();
+		break;
+	case 2:
+		prompt = ps2val();
+		rprompt = rps2val();
+		break;
+	}
+
+#ifdef SMALL
+	show = 1;
+#else
+	show = !el;
+#endif
+	pushstackmark(&smark, stackblocksize());
+	if (show) {
+		out2str(expandstr(prompt));
+	} else {
+		free(promptcache);
+		free(rpromptcache);
+		promptcache = savestr(expandstr(prompt));
+		rpromptcache = savestr(expandstr(rprompt));
+
+		nl = strrchr(promptcache, '\n');
+		if (nl)
+			outmem(promptcache, &nl[1] - promptcache, out2);
+	}
+	popstackmark(&smark);
+}
+
+/*
+ * called by editline -- return the cached prompt expansion.
+ */
+const char *
+getprompt(void *unused)
+{
+	const char *nl;
+
+	nl = strrchr(promptcache, '\n');
+
+	if (nl)
+		return &nl[1];
+	else
+		return promptcache;
+}
+
+const char *
+getrprompt(void *unused)
+{
+	return rpromptcache;
+}
+
+const char *const *
+findkwd(const char *s)
+{
+	return findstring(
+		s, parsekwd, sizeof(parsekwd) / sizeof(const char *)
+	);
+}
diff --git a/bin/dash/src/parser.h b/bin/dash/src/parser.h
new file mode 100644
index 00000000..6597faa8
--- /dev/null
+++ b/bin/dash/src/parser.h
@@ -0,0 +1,103 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)parser.h	8.3 (Berkeley) 5/4/95
+ */
+
+#include "token.h"
+
+/* control characters in argument strings */
+#define CTL_FIRST -127		/* first 'special' character */
+#define CTLESC -127		/* escape next character */
+#define CTLVAR -126		/* variable defn */
+#define CTLENDVAR -125
+#define CTLBACKQ -124
+#define	CTLARI -122		/* arithmetic expression */
+#define	CTLENDARI -121
+#define	CTLQUOTEMARK -120
+#define	CTL_LAST -120		/* last 'special' character */
+
+/* variable substitution byte (follows CTLVAR) */
+#define VSTYPE	0x0f		/* type of variable substitution */
+#define VSNUL	0x10		/* colon--treat the empty string as unset */
+
+/* values of VSTYPE field */
+#define VSNORMAL	0x1		/* normal variable:  $var or ${var} */
+#define VSMINUS		0x2		/* ${var-text} */
+#define VSPLUS		0x3		/* ${var+text} */
+#define VSQUESTION	0x4		/* ${var?message} */
+#define VSASSIGN	0x5		/* ${var=text} */
+#define VSTRIMRIGHT	0x6		/* ${var%pattern} */
+#define VSTRIMRIGHTMAX 	0x7		/* ${var%%pattern} */
+#define VSTRIMLEFT	0x8		/* ${var#pattern} */
+#define VSTRIMLEFTMAX	0x9		/* ${var##pattern} */
+#define VSLENGTH	0xa		/* ${#var} */
+
+/* values of checkkwd variable */
+#define CHKALIAS	0x1
+#define CHKKWD		0x2
+#define CHKNL		0x4
+#define CHKEOFMARK	0x8
+
+
+/*
+ * NEOF is returned by parsecmd when it encounters an end of file.  It
+ * must be distinct from NULL, so we use the address of a variable that
+ * happens to be handy.
+ */
+extern int lasttoken;
+extern int tokpushback;
+#define NEOF ((union node *)&tokpushback)
+extern int whichprompt;		/* 1 == PS1, 2 == PS2 */
+extern int checkkwd;
+
+
+int isassignment(const char *p);
+union node *parsecmd(int);
+void fixredir(union node *, const char *, int);
+const char *getprompt(void *);
+const char *getrprompt(void *);
+const char *const *findkwd(const char *);
+char *endofname(const char *);
+const char *expandstr(const char *);
+
+static inline int
+goodname(const char *p)
+{
+	return !*endofname(p);
+}
+
+static inline int parser_eof(void)
+{
+	return tokpushback && lasttoken == TEOF;
+}
diff --git a/bin/dash/src/redir.c b/bin/dash/src/redir.c
new file mode 100644
index 00000000..895140c3
--- /dev/null
+++ b/bin/dash/src/redir.c
@@ -0,0 +1,483 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/param.h>	/* PIPE_BUF */
+#include <signal.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+/*
+ * Code for dealing with input/output redirection.
+ */
+
+#include "main.h"
+#include "shell.h"
+#include "nodes.h"
+#include "jobs.h"
+#include "options.h"
+#include "expand.h"
+#include "redir.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+
+
+#define EMPTY -2		/* marks an unused slot in redirtab */
+#define CLOSED -1		/* fd opened for redir needs to be closed */
+
+#ifndef PIPE_BUF
+# define PIPESIZE 4096		/* amount of buffering in a pipe */
+#else
+# define PIPESIZE PIPE_BUF
+#endif
+
+
+MKINIT
+struct redirtab {
+	struct redirtab *next;
+	int renamed[10];
+};
+
+
+MKINIT struct redirtab *redirlist;
+
+/* Bit map of currently closed file descriptors. */
+static unsigned closed_redirs;
+
+STATIC int openredirect(union node *);
+#ifdef notyet
+STATIC void dupredirect(union node *, int, char[10]);
+#else
+STATIC void dupredirect(union node *, int);
+#endif
+STATIC int openhere(union node *);
+
+
+static unsigned update_closed_redirs(int fd, int nfd)
+{
+	unsigned val = closed_redirs;
+	unsigned bit = 1 << fd;
+
+	if (nfd >= 0)
+		closed_redirs &= ~bit;
+	else
+		closed_redirs |= bit;
+
+	return val & bit;
+}
+
+
+/*
+ * Process a list of redirection commands.  If the REDIR_PUSH flag is set,
+ * old file descriptors are stashed away so that the redirection can be
+ * undone by calling popredir.  If the REDIR_BACKQ flag is set, then the
+ * standard output, and the standard error if it becomes a duplicate of
+ * stdout, is saved in memory.
+ */
+
+void
+redirect(union node *redir, int flags)
+{
+	union node *n;
+	struct redirtab *sv;
+	int i;
+	int fd;
+	int newfd;
+	int *p;
+#if notyet
+	char memory[10];	/* file descriptors to write to memory */
+
+	for (i = 10 ; --i >= 0 ; )
+		memory[i] = 0;
+	memory[1] = flags & REDIR_BACKQ;
+#endif
+	if (!redir)
+		return;
+	sv = NULL;
+	INTOFF;
+	if (likely(flags & REDIR_PUSH))
+		sv = redirlist;
+	n = redir;
+	do {
+		newfd = openredirect(n);
+		if (newfd < -1)
+			continue;
+
+		fd = n->nfile.fd;
+
+		if (sv) {
+			int closed;
+
+			p = &sv->renamed[fd];
+			i = *p;
+
+			closed = update_closed_redirs(fd, newfd);
+
+			if (likely(i == EMPTY)) {
+				i = CLOSED;
+				if (fd != newfd && !closed) {
+					i = savefd(fd, fd);
+					fd = -1;
+				}
+			}
+
+			*p = i;
+		}
+
+		if (fd == newfd)
+			continue;
+
+#ifdef notyet
+		dupredirect(n, newfd, memory);
+#else
+		dupredirect(n, newfd);
+#endif
+	} while ((n = n->nfile.next));
+	INTON;
+#ifdef notyet
+	if (memory[1])
+		out1 = &memout;
+	if (memory[2])
+		out2 = &memout;
+#endif
+	if (flags & REDIR_SAVEFD2 && sv->renamed[2] >= 0)
+		preverrout.fd = sv->renamed[2];
+}
+
+
+STATIC int
+openredirect(union node *redir)
+{
+	struct stat64 sb;
+	char *fname;
+	int f;
+
+	switch (redir->nfile.type) {
+	case NFROM:
+		fname = redir->nfile.expfname;
+		if ((f = open64(fname, O_RDONLY)) < 0)
+			goto eopen;
+		break;
+	case NFROMTO:
+		fname = redir->nfile.expfname;
+		if ((f = open64(fname, O_RDWR|O_CREAT, 0666)) < 0)
+			goto ecreate;
+		break;
+	case NTO:
+		/* Take care of noclobber mode. */
+		if (Cflag) {
+			fname = redir->nfile.expfname;
+			if (stat64(fname, &sb) < 0) {
+				if ((f = open64(fname, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0)
+					goto ecreate;
+			} else if (!S_ISREG(sb.st_mode)) {
+				if ((f = open64(fname, O_WRONLY, 0666)) < 0)
+					goto ecreate;
+				if (!fstat64(f, &sb) && S_ISREG(sb.st_mode)) {
+					close(f);
+					errno = EEXIST;
+					goto ecreate;
+				}
+			} else {
+				errno = EEXIST;
+				goto ecreate;
+			}
+			break;
+		}
+		/* FALLTHROUGH */
+	case NCLOBBER:
+		fname = redir->nfile.expfname;
+		if ((f = open64(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0)
+			goto ecreate;
+		break;
+	case NAPPEND:
+		fname = redir->nfile.expfname;
+		if ((f = open64(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0)
+			goto ecreate;
+		break;
+	case NTOFD:
+	case NFROMFD:
+		f = redir->ndup.dupfd;
+		if (f == redir->nfile.fd)
+			f = -2;
+		break;
+	default:
+#ifdef DEBUG
+		abort();
+#endif
+		/* Fall through to eliminate warning. */
+	case NHERE:
+	case NXHERE:
+		f = openhere(redir);
+		break;
+	}
+
+	return f;
+ecreate:
+	sh_error("cannot create %s: %s", fname, errmsg(errno, E_CREAT));
+eopen:
+	sh_error("cannot open %s: %s", fname, errmsg(errno, E_OPEN));
+}
+
+
+STATIC void
+#ifdef notyet
+dupredirect(redir, f, memory)
+#else
+dupredirect(redir, f)
+#endif
+	union node *redir;
+	int f;
+#ifdef notyet
+	char memory[10];
+#endif
+	{
+	int fd = redir->nfile.fd;
+	int err = 0;
+
+#ifdef notyet
+	memory[fd] = 0;
+#endif
+	if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
+		/* if not ">&-" */
+		if (f >= 0) {
+#ifdef notyet
+			if (memory[f])
+				memory[fd] = 1;
+			else
+#endif
+				if (dup2(f, fd) < 0) {
+					err = errno;
+					goto err;
+				}
+			return;
+		}
+		f = fd;
+	} else if (dup2(f, fd) < 0)
+		err = errno;
+
+	close(f);
+	if (err < 0)
+		goto err;
+
+	return;
+
+err:
+	sh_error("%d: %s", f, strerror(err));
+}
+
+
+/*
+ * Handle here documents.  Normally we fork off a process to write the
+ * data to a pipe.  If the document is short, we can stuff the data in
+ * the pipe without forking.
+ */
+
+STATIC int
+openhere(union node *redir)
+{
+	char *p;
+	int pip[2];
+	size_t len = 0;
+
+	if (pipe(pip) < 0)
+		sh_error("Pipe call failed");
+
+	p = redir->nhere.doc->narg.text;
+	if (redir->type == NXHERE) {
+		expandarg(redir->nhere.doc, NULL, EXP_QUOTED);
+		p = stackblock();
+	}
+
+	len = strlen(p);
+	if (len <= PIPESIZE) {
+		xwrite(pip[1], p, len);
+		goto out;
+	}
+
+	if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) {
+		close(pip[0]);
+		signal(SIGINT, SIG_IGN);
+		signal(SIGQUIT, SIG_IGN);
+		signal(SIGHUP, SIG_IGN);
+#ifdef SIGTSTP
+		signal(SIGTSTP, SIG_IGN);
+#endif
+		signal(SIGPIPE, SIG_DFL);
+		xwrite(pip[1], p, len);
+		_exit(0);
+	}
+out:
+	close(pip[1]);
+	return pip[0];
+}
+
+
+
+/*
+ * Undo the effects of the last redirection.
+ */
+
+void
+popredir(int drop)
+{
+	struct redirtab *rp;
+	int i;
+
+	INTOFF;
+	rp = redirlist;
+	for (i = 0 ; i < 10 ; i++) {
+		int closed;
+
+		if (rp->renamed[i] == EMPTY)
+			continue;
+
+		closed = drop ? 1 : update_closed_redirs(i, rp->renamed[i]);
+
+		switch (rp->renamed[i]) {
+		case CLOSED:
+			if (!closed)
+				close(i);
+			break;
+		default:
+			if (!drop)
+				dup2(rp->renamed[i], i);
+			close(rp->renamed[i]);
+			break;
+		}
+	}
+	redirlist = rp->next;
+	ckfree(rp);
+	INTON;
+}
+
+/*
+ * Undo all redirections.  Called on error or interrupt.
+ */
+
+#ifdef mkinit
+
+INCLUDE "redir.h"
+
+EXITRESET {
+	/*
+	 * Discard all saved file descriptors.
+	 */
+	unwindredir(0);
+}
+
+FORKRESET {
+	redirlist = NULL;
+}
+
+#endif
+
+
+
+/*
+ * Move a file descriptor to > 10.  Invokes sh_error on error unless
+ * the original file dscriptor is not open.
+ */
+
+int
+savefd(int from, int ofd)
+{
+	int newfd;
+	int err;
+
+	newfd = fcntl(from, F_DUPFD, 10);
+	err = newfd < 0 ? errno : 0;
+	if (err != EBADF) {
+		close(ofd);
+		if (err)
+			sh_error("%d: %s", from, strerror(err));
+		else
+			fcntl(newfd, F_SETFD, FD_CLOEXEC);
+	}
+
+	return newfd;
+}
+
+
+int
+redirectsafe(union node *redir, int flags)
+{
+	int err;
+	volatile int saveint;
+	struct jmploc *volatile savehandler = handler;
+	struct jmploc jmploc;
+
+	SAVEINT(saveint);
+	if (!(err = setjmp(jmploc.loc) * 2)) {
+		handler = &jmploc;
+		redirect(redir, flags);
+	}
+	handler = savehandler;
+	if (err && exception != EXERROR)
+		longjmp(handler->loc, 1);
+	RESTOREINT(saveint);
+	return err;
+}
+
+
+void unwindredir(struct redirtab *stop)
+{
+	while (redirlist != stop)
+		popredir(0);
+}
+
+
+struct redirtab *pushredir(union node *redir)
+{
+	struct redirtab *sv;
+	struct redirtab *q;
+	int i;
+
+	q = redirlist;
+	if (!redir)
+		goto out;
+
+	sv = ckmalloc(sizeof (struct redirtab));
+	sv->next = q;
+	redirlist = sv;
+	for (i = 0; i < 10; i++)
+		sv->renamed[i] = EMPTY;
+
+out:
+	return q;
+}
diff --git a/bin/dash/src/redir.h b/bin/dash/src/redir.h
new file mode 100644
index 00000000..1cf27616
--- /dev/null
+++ b/bin/dash/src/redir.h
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)redir.h	8.2 (Berkeley) 5/4/95
+ */
+
+/* flags passed to redirect */
+#define REDIR_PUSH 01		/* save previous values of file descriptors */
+#ifdef notyet
+#define REDIR_BACKQ 02		/* save the command output in memory */
+#endif
+#define REDIR_SAVEFD2 03	/* set preverrout */
+
+struct redirtab;
+union node;
+void redirect(union node *, int);
+void popredir(int);
+int savefd(int, int);
+int redirectsafe(union node *, int);
+void unwindredir(struct redirtab *stop);
+struct redirtab *pushredir(union node *redir);
+
diff --git a/bin/dash/src/shell.h b/bin/dash/src/shell.h
new file mode 100644
index 00000000..98edc8be
--- /dev/null
+++ b/bin/dash/src/shell.h
@@ -0,0 +1,104 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)shell.h	8.2 (Berkeley) 5/4/95
+ */
+
+/*
+ * The follow should be set to reflect the type of system you have:
+ *	JOBS -> 1 if you have Berkeley job control, 0 otherwise.
+ *	SHORTNAMES -> 1 if your linker cannot handle long names.
+ *	define BSD if you are running 4.2 BSD or later.
+ *	define SYSV if you are running under System V.
+ *	define DEBUG=1 to compile in debugging ('set -o debug' to turn on)
+ *	define DEBUG=2 to compile in and turn on debugging.
+ *	define DO_SHAREDVFORK to indicate that vfork(2) shares its address
+ *	       with its parent.
+ *
+ * When debugging is on, debugging info will be written to ./trace and
+ * a quit signal will generate a core dump.
+ */
+
+#include <sys/param.h>
+
+#ifndef JOBS
+#define JOBS 1
+#endif
+#ifndef BSD
+#define BSD 1
+#endif
+
+#ifndef DO_SHAREDVFORK
+#if __NetBSD_Version__ >= 104000000
+#define DO_SHAREDVFORK
+#endif
+#endif
+
+typedef void *pointer;
+#ifndef NULL
+#define NULL (void *)0
+#endif
+#define STATIC static
+#define MKINIT	/* empty */
+
+extern char nullstr[1];		/* null string */
+
+
+#ifdef DEBUG
+#define TRACE(param)	trace param
+#define TRACEV(param)	tracev param
+#else
+#define TRACE(param)
+#define TRACEV(param)
+#endif
+
+#if defined(__GNUC__) && __GNUC__ < 3
+#define va_copy __va_copy
+#endif
+
+#if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
+#define __builtin_expect(x, expected_value) (x)
+#endif
+
+#define likely(x)	__builtin_expect(!!(x),1)
+#define unlikely(x)	__builtin_expect(!!(x),0)
+
+/*
+ * Hack to calculate maximum length.
+ * (length * 8 - 1) * log10(2) + 1 + 1 + 12
+ * The second 1 is for the minus sign and the 12 is a safety margin.
+ */
+static inline int max_int_length(int bytes)
+{
+	return (bytes * 8 - 1) * 0.30102999566398119521 + 14;
+}
diff --git a/bin/dash/src/show.c b/bin/dash/src/show.c
new file mode 100644
index 00000000..4a049e93
--- /dev/null
+++ b/bin/dash/src/show.c
@@ -0,0 +1,406 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "shell.h"
+#include "parser.h"
+#include "nodes.h"
+#include "mystring.h"
+#include "show.h"
+#include "options.h"
+
+
+#ifdef DEBUG
+static void shtree(union node *, int, char *, FILE*);
+static void shcmd(union node *, FILE *);
+static void sharg(union node *, FILE *);
+static void indent(int, char *, FILE *);
+static void trstring(char *);
+
+
+void
+showtree(union node *n)
+{
+	trputs("showtree called\n");
+	shtree(n, 1, NULL, stdout);
+}
+
+
+static void
+shtree(union node *n, int ind, char *pfx, FILE *fp)
+{
+	struct nodelist *lp;
+	const char *s;
+
+	if (n == NULL)
+		return;
+
+	indent(ind, pfx, fp);
+	switch(n->type) {
+	case NSEMI:
+		s = "; ";
+		goto binop;
+	case NAND:
+		s = " && ";
+		goto binop;
+	case NOR:
+		s = " || ";
+binop:
+		shtree(n->nbinary.ch1, ind, NULL, fp);
+	   /*    if (ind < 0) */
+			fputs(s, fp);
+		shtree(n->nbinary.ch2, ind, NULL, fp);
+		break;
+	case NCMD:
+		shcmd(n, fp);
+		if (ind >= 0)
+			putc('\n', fp);
+		break;
+	case NPIPE:
+		for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+			shcmd(lp->n, fp);
+			if (lp->next)
+				fputs(" | ", fp);
+		}
+		if (n->npipe.backgnd)
+			fputs(" &", fp);
+		if (ind >= 0)
+			putc('\n', fp);
+		break;
+	default:
+		fprintf(fp, "<node type %d>", n->type);
+		if (ind >= 0)
+			putc('\n', fp);
+		break;
+	}
+}
+
+
+
+static void
+shcmd(union node *cmd, FILE *fp)
+{
+	union node *np;
+	int first;
+	const char *s;
+	int dftfd;
+
+	first = 1;
+	for (np = cmd->ncmd.args ; np ; np = np->narg.next) {
+		if (! first)
+			putchar(' ');
+		sharg(np, fp);
+		first = 0;
+	}
+	for (np = cmd->ncmd.redirect ; np ; np = np->nfile.next) {
+		if (! first)
+			putchar(' ');
+		switch (np->nfile.type) {
+			case NTO:	s = ">";  dftfd = 1; break;
+			case NCLOBBER:	s = ">|"; dftfd = 1; break;
+			case NAPPEND:	s = ">>"; dftfd = 1; break;
+			case NTOFD:	s = ">&"; dftfd = 1; break;
+			case NFROM:	s = "<";  dftfd = 0; break;
+			case NFROMFD:	s = "<&"; dftfd = 0; break;
+			case NFROMTO:	s = "<>"; dftfd = 0; break;
+			default:  	s = "*error*"; dftfd = 0; break;
+		}
+		if (np->nfile.fd != dftfd)
+			fprintf(fp, "%d", np->nfile.fd);
+		fputs(s, fp);
+		if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) {
+			fprintf(fp, "%d", np->ndup.dupfd);
+		} else {
+			sharg(np->nfile.fname, fp);
+		}
+		first = 0;
+	}
+}
+
+
+
+static void
+sharg(union node *arg, FILE *fp)
+{
+	char *p;
+	struct nodelist *bqlist;
+	int subtype;
+
+	if (arg->type != NARG) {
+		printf("<node type %d>\n", arg->type);
+		abort();
+	}
+	bqlist = arg->narg.backquote;
+	for (p = arg->narg.text ; *p ; p++) {
+		switch ((signed char)*p) {
+		case CTLESC:
+			putc(*++p, fp);
+			break;
+		case CTLVAR:
+			putc('$', fp);
+			putc('{', fp);
+			subtype = *++p;
+			if (subtype == VSLENGTH)
+				putc('#', fp);
+
+			while (*p != '=')
+				putc(*p++, fp);
+
+			if (subtype & VSNUL)
+				putc(':', fp);
+
+			switch (subtype & VSTYPE) {
+			case VSNORMAL:
+				putc('}', fp);
+				break;
+			case VSMINUS:
+				putc('-', fp);
+				break;
+			case VSPLUS:
+				putc('+', fp);
+				break;
+			case VSQUESTION:
+				putc('?', fp);
+				break;
+			case VSASSIGN:
+				putc('=', fp);
+				break;
+			case VSTRIMLEFT:
+				putc('#', fp);
+				break;
+			case VSTRIMLEFTMAX:
+				putc('#', fp);
+				putc('#', fp);
+				break;
+			case VSTRIMRIGHT:
+				putc('%', fp);
+				break;
+			case VSTRIMRIGHTMAX:
+				putc('%', fp);
+				putc('%', fp);
+				break;
+			case VSLENGTH:
+				break;
+			default:
+				printf("<subtype %d>", subtype);
+			}
+			break;
+		case CTLENDVAR:
+		     putc('}', fp);
+		     break;
+		case CTLBACKQ:
+			putc('$', fp);
+			putc('(', fp);
+			shtree(bqlist->n, -1, NULL, fp);
+			putc(')', fp);
+			break;
+		default:
+			putc(*p, fp);
+			break;
+		}
+	}
+}
+
+
+static void
+indent(int amount, char *pfx, FILE *fp)
+{
+	int i;
+
+	for (i = 0 ; i < amount ; i++) {
+		if (pfx && i == amount - 1)
+			fputs(pfx, fp);
+		putc('\t', fp);
+	}
+}
+
+
+
+/*
+ * Debugging stuff.
+ */
+
+
+FILE *tracefile;
+
+
+void
+trputc(int c)
+{
+	if (debug != 1)
+		return;
+	putc(c, tracefile);
+}
+
+void
+trace(const char *fmt, ...)
+{
+	va_list va;
+
+	if (debug != 1)
+		return;
+	va_start(va, fmt);
+	(void) vfprintf(tracefile, fmt, va);
+	va_end(va);
+}
+
+void
+tracev(const char *fmt, va_list va)
+{
+	if (debug != 1)
+		return;
+	(void) vfprintf(tracefile, fmt, va);
+}
+
+
+void
+trputs(const char *s)
+{
+	if (debug != 1)
+		return;
+	fputs(s, tracefile);
+}
+
+
+static void
+trstring(char *s)
+{
+	char *p;
+	char c;
+
+	if (debug != 1)
+		return;
+	putc('"', tracefile);
+	for (p = s ; *p ; p++) {
+		switch ((signed char)*p) {
+		case '\n':  c = 'n';  goto backslash;
+		case '\t':  c = 't';  goto backslash;
+		case '\r':  c = 'r';  goto backslash;
+		case '"':  c = '"';  goto backslash;
+		case '\\':  c = '\\';  goto backslash;
+		case CTLESC:  c = 'e';  goto backslash;
+		case CTLVAR:  c = 'v';  goto backslash;
+		case CTLBACKQ:  c = 'q';  goto backslash;
+backslash:	  putc('\\', tracefile);
+			putc(c, tracefile);
+			break;
+		default:
+			if (*p >= ' ' && *p <= '~')
+				putc(*p, tracefile);
+			else {
+				putc('\\', tracefile);
+				putc(*p >> 6 & 03, tracefile);
+				putc(*p >> 3 & 07, tracefile);
+				putc(*p & 07, tracefile);
+			}
+			break;
+		}
+	}
+	putc('"', tracefile);
+}
+
+
+void
+trargs(char **ap)
+{
+	if (debug != 1)
+		return;
+	while (*ap) {
+		trstring(*ap++);
+		if (*ap)
+			putc(' ', tracefile);
+		else
+			putc('\n', tracefile);
+	}
+}
+
+
+void
+opentrace(void)
+{
+	char s[100];
+#ifdef O_APPEND
+	int flags;
+#endif
+
+	if (debug != 1) {
+		if (tracefile)
+			fflush(tracefile);
+		/* leave open because libedit might be using it */
+		return;
+	}
+#ifdef not_this_way
+	{
+		char *p;
+		if ((p = getenv(homestr)) == NULL) {
+			if (geteuid() == 0)
+				p = "/";
+			else
+				p = "/tmp";
+		}
+		scopy(p, s);
+		strcat(s, "/trace");
+	}
+#else
+	scopy("./trace", s);
+#endif /* not_this_way */
+	if (tracefile) {
+#ifndef __KLIBC__
+		if (!freopen(s, "a", tracefile)) {
+#else
+		if (!(!fclose(tracefile) && (tracefile = fopen(s, "a")))) {
+#endif /* __KLIBC__ */
+			fprintf(stderr, "Can't re-open %s\n", s);
+			debug = 0;
+			return;
+		}
+	} else {
+		if ((tracefile = fopen(s, "a")) == NULL) {
+			fprintf(stderr, "Can't open %s\n", s);
+			debug = 0;
+			return;
+		}
+	}
+#ifdef O_APPEND
+	if ((flags = fcntl(fileno(tracefile), F_GETFL, 0)) >= 0)
+		fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND);
+#endif
+#ifndef __KLIBC__
+	setlinebuf(tracefile);
+#endif /* __KLIBC__ */
+	fputs("\nTracing started.\n", tracefile);
+}
+#endif /* DEBUG */
diff --git a/bin/dash/src/show.h b/bin/dash/src/show.h
new file mode 100644
index 00000000..d0ccac79
--- /dev/null
+++ b/bin/dash/src/show.h
@@ -0,0 +1,45 @@
+/*-
+ * Copyright (c) 1995
+ *      The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)show.h	1.1 (Berkeley) 5/4/95
+ */
+
+#include <stdarg.h>
+
+#ifdef DEBUG
+union node;
+void showtree(union node *);
+void trace(const char *, ...);
+void tracev(const char *, va_list);
+void trargs(char **);
+void trputc(int);
+void trputs(const char *);
+void opentrace(void);
+#endif
diff --git a/bin/dash/src/system.c b/bin/dash/src/system.c
new file mode 100644
index 00000000..844a6410
--- /dev/null
+++ b/bin/dash/src/system.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright (c) 2004
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef HAVE_ISALPHA
+#define isalnum _isalnum
+#define iscntrl _iscntrl
+#define islower _islower
+#define isspace _isspace
+#define isalpha _isalpha
+#define isdigit _isdigit
+#define isprint _isprint
+#define isupper _isupper
+#define isblank _isblank
+#define isgraph _isgraph
+#define ispunct _ispunct
+#define isxdigit _isxdigit
+#include <ctype.h>
+#undef isalnum
+#undef iscntrl
+#undef islower
+#undef isspace
+#undef isalpha
+#undef isdigit
+#undef isprint
+#undef isupper
+#undef isblank
+#undef isgraph
+#undef ispunct
+#undef isxdigit
+#endif
+
+#include <signal.h>
+#include <string.h>
+
+#include "error.h"
+#include "output.h"
+#include "system.h"
+
+#ifndef HAVE_MEMPCPY
+void *mempcpy(void *dest, const void *src, size_t n)
+{
+	return memcpy(dest, src, n) + n;
+}
+#endif
+
+#ifndef HAVE_STPCPY
+char *stpcpy(char *dest, const char *src)
+{
+	size_t len = strlen(src);
+	dest[len] = 0;
+	return mempcpy(dest, src, len);
+}
+#endif
+
+#ifndef HAVE_STRCHRNUL
+char *strchrnul(const char *s, int c)
+{
+	char *p = strchr(s, c);
+	if (!p)
+		p = (char *)s + strlen(s);
+	return p;
+}
+#endif
+
+#ifndef HAVE_STRSIGNAL
+char *strsignal(int sig)
+{
+	static char buf[19];
+
+	if ((unsigned)sig < NSIG && sys_siglist[sig])
+		return (char *)sys_siglist[sig];
+	fmtstr(buf, sizeof(buf), "Signal %d", sig); 
+	return buf;
+}
+#endif
+
+#ifndef HAVE_BSEARCH
+void *bsearch(const void *key, const void *base, size_t nmemb,
+	      size_t size, int (*cmp)(const void *, const void *))
+{
+	while (nmemb) {
+		size_t mididx = nmemb / 2;
+		const void *midobj = base + mididx * size;
+		int diff = cmp(key, midobj);
+
+		if (diff == 0)
+			return (void *)midobj;
+
+		if (diff > 0) {
+			base = midobj + size;
+			nmemb -= mididx + 1;
+		} else
+			nmemb = mididx;
+	}
+
+	return 0;
+}
+#endif
+
+#ifndef HAVE_SYSCONF
+long sysconf(int name)
+{
+	sh_error("no sysconf for: %d", name);
+}
+#endif
+
+#ifndef HAVE_ISALPHA
+int isalnum(int c) {
+	return _isalnum(c);
+}
+
+
+int iscntrl(int c) {
+	return _iscntrl(c);
+}
+
+
+int islower(int c) {
+	return _islower(c);
+}
+
+
+int isspace(int c) {
+	return _isspace(c);
+}
+
+
+int isalpha(int c) {
+	return _isalpha(c);
+}
+
+
+int isdigit(int c) {
+	return _isdigit(c);
+}
+
+
+int isprint(int c) {
+	return _isprint(c);
+}
+
+
+int isupper(int c) {
+	return _isupper(c);
+}
+
+
+#if HAVE_DECL_ISBLANK
+int isblank(int c) {
+	return _isblank(c);
+}
+#endif
+
+
+int isgraph(int c) {
+	return _isgraph(c);
+}
+
+
+int ispunct(int c) {
+	return _ispunct(c);
+}
+
+
+int isxdigit(int c) {
+	return _isxdigit(c);
+}
+#endif
+
+#if !HAVE_DECL_ISBLANK
+int isblank(int c) {
+	return c == ' ' || c == '\t';
+}
+#endif
diff --git a/bin/dash/src/system.h b/bin/dash/src/system.h
new file mode 100644
index 00000000..007952c5
--- /dev/null
+++ b/bin/dash/src/system.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2004
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <limits.h>
+#include <signal.h>
+#include <sys/types.h>
+
+#ifndef SSIZE_MAX
+#define SSIZE_MAX ((ssize_t)((size_t)-1 >> 1))
+#endif
+
+static inline void sigclearmask(void)
+{
+#if defined(HAVE_SIGSETMASK) && \
+    (!defined(__GLIBC__) || \
+     (defined(__GNUC__) && (__GNUC__ * 1000 + __GNUC_MINOR__) >= 4006))
+#ifdef __GLIBC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+	sigsetmask(0);
+#ifdef __GLIBC__
+#pragma GCC diagnostic pop
+#endif
+#else
+	sigset_t set;
+	sigemptyset(&set);
+	sigprocmask(SIG_SETMASK, &set, 0);
+#endif
+}
+
+#ifndef HAVE_MEMPCPY
+void *mempcpy(void *, const void *, size_t);
+#endif
+
+#ifndef HAVE_STPCPY
+char *stpcpy(char *, const char *);
+#endif
+
+#ifndef HAVE_STRCHRNUL
+char *strchrnul(const char *, int);
+#endif
+
+#ifndef HAVE_STRSIGNAL
+char *strsignal(int);
+#endif
+
+#ifndef HAVE_STRTOD
+static inline double strtod(const char *nptr, char **endptr)
+{
+	*endptr = (char *)nptr;
+	return 0;
+}
+#endif
+
+#ifndef HAVE_STRTOIMAX
+#define strtoimax strtoll
+#endif
+
+#ifndef HAVE_STRTOUMAX
+#define strtoumax strtoull
+#endif
+
+#ifndef HAVE_BSEARCH
+void *bsearch(const void *, const void *, size_t, size_t,
+	      int (*)(const void *, const void *));
+#endif
+
+#ifndef HAVE_KILLPG
+static inline int killpg(pid_t pid, int signal)
+{
+#ifdef DEBUG
+	if (pid < 0)
+		abort();
+#endif
+	return kill(-pid, signal);
+}
+#endif
+
+#ifndef HAVE_SYSCONF
+#define _SC_CLK_TCK 2
+long sysconf(int) __attribute__((__noreturn__));
+#endif
+
+#if !HAVE_DECL_ISBLANK
+int isblank(int c);
+#endif
+
+/*
+ * A trick to suppress uninitialized variable warning without generating any
+ * code
+ */
+#define uninitialized_var(x) x = x
diff --git a/bin/dash/src/trap.c b/bin/dash/src/trap.c
new file mode 100644
index 00000000..cd84814f
--- /dev/null
+++ b/bin/dash/src/trap.c
@@ -0,0 +1,446 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <signal.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "shell.h"
+#include "main.h"
+#include "nodes.h"	/* for other headers */
+#include "eval.h"
+#include "init.h"
+#include "jobs.h"
+#include "show.h"
+#include "options.h"
+#include "syntax.h"
+#include "output.h"
+#include "memalloc.h"
+#include "error.h"
+#include "trap.h"
+#include "mystring.h"
+
+/*
+ * Sigmode records the current value of the signal handlers for the various
+ * modes.  A value of zero means that the current handler is not known.
+ * S_HARD_IGN indicates that the signal was ignored on entry to the shell,
+ */
+
+#define S_DFL 1			/* default signal handling (SIG_DFL) */
+#define S_CATCH 2		/* signal is caught */
+#define S_IGN 3			/* signal is ignored (SIG_IGN) */
+#define S_HARD_IGN 4		/* signal is ignored permenantly */
+#define S_RESET 5		/* temporary - to reset a hard ignored sig */
+
+
+/* trap handler commands */
+MKINIT char *trap[NSIG];
+/* number of non-null traps */
+int trapcnt;
+/* current value of signal */
+char sigmode[NSIG - 1];
+/* indicates specified signal received */
+static char gotsig[NSIG - 1];
+/* last pending signal */
+volatile sig_atomic_t pending_sig;
+/* received SIGCHLD */
+volatile sig_atomic_t gotsigchld;
+
+extern char *signal_names[];
+
+static int decode_signum(const char *);
+
+#ifdef mkinit
+INCLUDE "memalloc.h"
+INCLUDE "trap.h"
+
+INIT {
+	sigmode[SIGCHLD - 1] = S_DFL;
+	setsignal(SIGCHLD);
+}
+
+FORKRESET {
+	char **tp;
+
+	INTOFF;
+	for (tp = trap ; tp < &trap[NSIG] ; tp++) {
+		if (*tp && **tp) {	/* trap not NULL or SIG_IGN */
+			ckfree(*tp);
+			*tp = NULL;
+			if (tp != &trap[0])
+				setsignal(tp - trap);
+		}
+	}
+	trapcnt = 0;
+	INTON;
+}
+#endif
+
+/*
+ * The trap builtin.
+ */
+
+int
+trapcmd(int argc, char **argv)
+{
+	char *action;
+	char **ap;
+	int signo;
+
+	nextopt(nullstr);
+	ap = argptr;
+	if (!*ap) {
+		for (signo = 0 ; signo < NSIG ; signo++) {
+			if (trap[signo] != NULL) {
+				out1fmt(
+					"trap -- %s %s\n",
+					single_quote(trap[signo]),
+					signal_names[signo]
+				);
+			}
+		}
+		return 0;
+	}
+	if (!ap[1] || decode_signum(*ap) >= 0)
+		action = NULL;
+	else
+		action = *ap++;
+	while (*ap) {
+		if ((signo = decode_signal(*ap, 0)) < 0) {
+			outfmt(out2, "trap: %s: bad trap\n", *ap);
+			return 1;
+		}
+		INTOFF;
+		if (action) {
+			if (action[0] == '-' && action[1] == '\0')
+				action = NULL;
+			else {
+				if (*action)
+					trapcnt++;
+				action = savestr(action);
+			}
+		}
+		if (trap[signo]) {
+			if (*trap[signo])
+				trapcnt--;
+			ckfree(trap[signo]);
+		}
+		trap[signo] = action;
+		if (signo != 0)
+			setsignal(signo);
+		INTON;
+		ap++;
+	}
+	return 0;
+}
+
+
+
+/*
+ * Set the signal handler for the specified signal.  The routine figures
+ * out what it should be set to.
+ */
+
+void
+setsignal(int signo)
+{
+	int action;
+	int lvforked;
+	char *t, tsig;
+	struct sigaction act;
+
+	lvforked = vforked;
+
+	if ((t = trap[signo]) == NULL)
+		action = S_DFL;
+	else if (*t != '\0')
+		action = S_CATCH;
+	else
+		action = S_IGN;
+	if (rootshell && action == S_DFL && !lvforked) {
+		switch (signo) {
+		case SIGINT:
+			if (iflag || minusc || sflag == 0)
+				action = S_CATCH;
+			break;
+		case SIGQUIT:
+#ifdef DEBUG
+			if (debug)
+				break;
+#endif
+			/* FALLTHROUGH */
+		case SIGTERM:
+			if (iflag)
+				action = S_IGN;
+			break;
+#if JOBS
+		case SIGTSTP:
+		case SIGTTOU:
+			if (mflag)
+				action = S_IGN;
+			break;
+#endif
+		}
+	}
+
+	if (signo == SIGCHLD)
+		action = S_CATCH;
+
+	t = &sigmode[signo - 1];
+	tsig = *t;
+	if (tsig == 0) {
+		/*
+		 * current setting unknown
+		 */
+		if (sigaction(signo, 0, &act) == -1) {
+			/*
+			 * Pretend it worked; maybe we should give a warning
+			 * here, but other shells don't. We don't alter
+			 * sigmode, so that we retry every time.
+			 */
+			return;
+		}
+		if (act.sa_handler == SIG_IGN) {
+			if (mflag && (signo == SIGTSTP ||
+			     signo == SIGTTIN || signo == SIGTTOU)) {
+				tsig = S_IGN;	/* don't hard ignore these */
+			} else
+				tsig = S_HARD_IGN;
+		} else {
+			tsig = S_RESET;	/* force to be set */
+		}
+	}
+	if (tsig == S_HARD_IGN || tsig == action)
+		return;
+	switch (action) {
+	case S_CATCH:
+		act.sa_handler = onsig;
+		break;
+	case S_IGN:
+		act.sa_handler = SIG_IGN;
+		break;
+	default:
+		act.sa_handler = SIG_DFL;
+	}
+	if (!lvforked)
+		*t = action;
+	act.sa_flags = 0;
+	sigfillset(&act.sa_mask);
+	sigaction(signo, &act, 0);
+}
+
+/*
+ * Ignore a signal.
+ */
+
+void
+ignoresig(int signo)
+{
+	if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) {
+		signal(signo, SIG_IGN);
+	}
+	if (!vforked)
+		sigmode[signo - 1] = S_HARD_IGN;
+}
+
+
+
+/*
+ * Signal handler.
+ */
+
+void
+onsig(int signo)
+{
+	if (vforked)
+		return;
+
+	if (signo == SIGCHLD) {
+		gotsigchld = 1;
+		if (!trap[SIGCHLD])
+			return;
+	}
+
+	gotsig[signo - 1] = 1;
+	pending_sig = signo;
+
+	if (signo == SIGINT && !trap[SIGINT]) {
+		if (!suppressint)
+			onint();
+		intpending = 1;
+	}
+}
+
+
+
+/*
+ * Called to execute a trap.  Perhaps we should avoid entering new trap
+ * handlers while we are executing a trap handler.
+ */
+
+void dotrap(void)
+{
+	char *p;
+	char *q;
+	int i;
+	int status, last_status;
+
+	if (!pending_sig)
+		return;
+
+	status = savestatus;
+	last_status = status;
+	if (likely(status < 0)) {
+		status = exitstatus;
+		savestatus = status;
+	}
+	pending_sig = 0;
+	barrier();
+
+	for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) {
+		if (!*q)
+			continue;
+
+		if (evalskip) {
+			pending_sig = i + 1;
+			break;
+		}
+
+		*q = 0;
+
+		p = trap[i + 1];
+		if (!p)
+			continue;
+		evalstring(p, 0);
+		if (evalskip != SKIPFUNC)
+			exitstatus = status;
+	}
+
+	savestatus = last_status;
+}
+
+
+
+/*
+ * Controls whether the shell is interactive or not.
+ */
+
+
+void
+setinteractive(int on)
+{
+	static int is_interactive;
+
+	if (++on == is_interactive)
+		return;
+	is_interactive = on;
+	setsignal(SIGINT);
+	setsignal(SIGQUIT);
+	setsignal(SIGTERM);
+}
+
+
+
+/*
+ * Called to exit the shell.
+ */
+
+void
+exitshell(void)
+{
+	struct jmploc loc;
+	char *p;
+
+	savestatus = exitstatus;
+	TRACE(("pid %d, exitshell(%d)\n", getpid(), savestatus));
+	if (setjmp(loc.loc))
+		goto out;
+	handler = &loc;
+	if ((p = trap[0])) {
+		trap[0] = NULL;
+		evalskip = 0;
+		evalstring(p, 0);
+		evalskip = SKIPFUNCDEF;
+	}
+out:
+	exitreset();
+	/*
+	 * Disable job control so that whoever had the foreground before we
+	 * started can get it back.
+	 */
+	if (likely(!setjmp(loc.loc)))
+		setjobctl(0);
+	flushall();
+	_exit(exitstatus);
+	/* NOTREACHED */
+}
+
+static int decode_signum(const char *string)
+{
+	int signo = -1;
+
+	if (is_number(string)) {
+		signo = atoi(string);
+		if (signo >= NSIG)
+			signo = -1;
+	}
+
+	return signo;
+}
+
+int decode_signal(const char *string, int minsig)
+{
+	int signo;
+
+	signo = decode_signum(string);
+	if (signo >= 0)
+		return signo;
+
+	for (signo = minsig; signo < NSIG; signo++) {
+		if (!strcasecmp(string, signal_names[signo])) {
+			return signo;
+		}
+	}
+
+	return -1;
+}
+
+void sigblockall(sigset_t *oldmask)
+{
+	sigset_t mask;
+
+	sigfillset(&mask);
+	sigprocmask(SIG_SETMASK, &mask, oldmask);
+}
diff --git a/bin/dash/src/trap.h b/bin/dash/src/trap.h
new file mode 100644
index 00000000..beaf6605
--- /dev/null
+++ b/bin/dash/src/trap.h
@@ -0,0 +1,57 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)trap.h	8.3 (Berkeley) 6/5/95
+ */
+
+#include <signal.h>
+
+extern int trapcnt;
+extern char sigmode[];
+extern volatile sig_atomic_t pending_sig;
+extern volatile sig_atomic_t gotsigchld;
+
+int trapcmd(int, char **);
+void setsignal(int);
+void ignoresig(int);
+void onsig(int);
+void dotrap(void);
+void setinteractive(int);
+void exitshell(void) __attribute__((__noreturn__));
+int decode_signal(const char *, int);
+void sigblockall(sigset_t *oldmask);
+
+static inline int have_traps(void)
+{
+	return trapcnt;
+}
diff --git a/bin/dash/src/var.c b/bin/dash/src/var.c
new file mode 100644
index 00000000..ca9504ef
--- /dev/null
+++ b/bin/dash/src/var.c
@@ -0,0 +1,676 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_PATHS_H
+#include <paths.h>
+#endif
+
+/*
+ * Shell variables.
+ */
+
+#include "shell.h"
+#include "output.h"
+#include "expand.h"
+#include "nodes.h"	/* for other headers */
+#include "exec.h"
+#include "syntax.h"
+#include "options.h"
+#include "mail.h"
+#include "var.h"
+#include "memalloc.h"
+#include "error.h"
+#include "mystring.h"
+#include "parser.h"
+#include "show.h"
+#ifndef SMALL
+#include "myhistedit.h"
+#endif
+#include "system.h"
+
+
+#define VTABSIZE 39
+
+
+struct localvar_list {
+	struct localvar_list *next;
+	struct localvar *lv;
+};
+
+MKINIT struct localvar_list *localvar_stack;
+
+const char defpathvar[] =
+	"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
+char defifsvar[] = "IFS= \t\n";
+MKINIT char defoptindvar[] = "OPTIND=1";
+
+int lineno;
+char linenovar[sizeof("LINENO=")+sizeof(int)*CHAR_BIT/3+1] = "LINENO=";
+
+/* Some macros in var.h depend on the order, add new variables to the end. */
+struct var varinit[] = {
+#if ATTY
+	{ 0,	VSTRFIXED|VTEXTFIXED|VUNSET,	"ATTY\0",	0 },
+#endif
+	{ 0,	VSTRFIXED|VTEXTFIXED,		defifsvar,	0 },
+	{ 0,	VSTRFIXED|VTEXTFIXED|VUNSET,	"MAIL\0",	changemail },
+	{ 0,	VSTRFIXED|VTEXTFIXED|VUNSET,	"MAILPATH\0",	changemail },
+	{ 0,	VSTRFIXED|VTEXTFIXED,		defpathvar,	changepath },
+	{ 0,	VSTRFIXED|VTEXTFIXED,		"PS1=$ ",	0 },
+	{ 0,	VSTRFIXED|VTEXTFIXED,		"PS2=> ",	0 },
+	{ 0,	VSTRFIXED|VTEXTFIXED,		"PS4=+ ",	0 },
+	{ 0,	VSTRFIXED|VTEXTFIXED|VUNSET,	"RPS1\0",	0 },
+	{ 0,	VSTRFIXED|VTEXTFIXED|VUNSET,	"RPS2\0",	0 },
+	{ 0,	VSTRFIXED|VTEXTFIXED,		defoptindvar,	getoptsreset },
+#ifdef WITH_LINENO
+	{ 0,	VSTRFIXED|VTEXTFIXED,		linenovar,	0 },
+#endif
+#ifndef SMALL
+	{ 0,	VSTRFIXED|VTEXTFIXED|VUNSET,	"TERM\0",	0 },
+	{ 0,	VSTRFIXED|VTEXTFIXED|VUNSET,	"HISTSIZE\0",	sethistsize },
+#endif
+};
+
+STATIC struct var *vartab[VTABSIZE];
+
+STATIC struct var **hashvar(const char *);
+STATIC int vpcmp(const void *, const void *);
+STATIC struct var **findvar(struct var **, const char *);
+
+/*
+ * Initialize the varable symbol tables and import the environment
+ */
+
+#ifdef mkinit
+INCLUDE <unistd.h>
+INCLUDE <sys/types.h>
+INCLUDE <sys/stat.h>
+INCLUDE "cd.h"
+INCLUDE "output.h"
+INCLUDE "var.h"
+MKINIT char **environ;
+INIT {
+	char **envp;
+	static char ppid[32] = "PPID=";
+	const char *p;
+	struct stat64 st1, st2;
+
+	initvar();
+	for (envp = environ ; *envp ; envp++) {
+		p = endofname(*envp);
+		if (p != *envp && *p == '=') {
+			setvareq(*envp, VEXPORT|VTEXTFIXED);
+		}
+	}
+
+	setvareq(defifsvar, VTEXTFIXED);
+	setvareq(defoptindvar, VTEXTFIXED);
+
+	fmtstr(ppid + 5, sizeof(ppid) - 5, "%ld", (long) getppid());
+	setvareq(ppid, VTEXTFIXED);
+
+	p = lookupvar("PWD");
+	if (p)
+		if (*p != '/' || stat64(p, &st1) || stat64(".", &st2) ||
+		    st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino)
+			p = 0;
+	setpwd(p, 0);
+}
+
+RESET {
+	unwindlocalvars(0);
+}
+#endif
+
+
+/*
+ * This routine initializes the builtin variables.  It is called when the
+ * shell is initialized.
+ */
+
+void
+initvar(void)
+{
+	struct var *vp;
+	struct var *end;
+	struct var **vpp;
+
+	vp = varinit;
+	end = vp + sizeof(varinit) / sizeof(varinit[0]);
+	do {
+		vpp = hashvar(vp->text);
+		vp->next = *vpp;
+		*vpp = vp;
+	} while (++vp < end);
+	/*
+	 * PS1 depends on uid
+	 */
+	if (!geteuid())
+		vps1.text = "PS1=# ";
+}
+
+/*
+ * Set the value of a variable.  The flags argument is ored with the
+ * flags of the variable.  If val is NULL, the variable is unset.
+ */
+
+struct var *setvar(const char *name, const char *val, int flags)
+{
+	char *p, *q;
+	size_t namelen;
+	char *nameeq;
+	size_t vallen;
+	struct var *vp;
+
+	q = endofname(name);
+	p = strchrnul(q, '=');
+	namelen = p - name;
+	if (!namelen || p != q)
+		sh_error("%.*s: bad variable name", namelen, name);
+	vallen = 0;
+	if (val == NULL) {
+		flags |= VUNSET;
+	} else {
+		vallen = strlen(val);
+	}
+	INTOFF;
+	p = mempcpy(nameeq = ckmalloc(namelen + vallen + 2), name, namelen);
+	if (val) {
+		*p++ = '=';
+		p = mempcpy(p, val, vallen);
+	}
+	*p = '\0';
+	vp = setvareq(nameeq, flags | VNOSAVE);
+	INTON;
+
+	return vp;
+}
+
+/*
+ * Set the given integer as the value of a variable.  The flags argument is
+ * ored with the flags of the variable.
+ */
+
+intmax_t setvarint(const char *name, intmax_t val, int flags)
+{
+	int len = max_int_length(sizeof(val));
+	char buf[len];
+
+	fmtstr(buf, len, "%" PRIdMAX, val);
+	setvar(name, buf, flags);
+	return val;
+}
+
+
+
+/*
+ * Same as setvar except that the variable and value are passed in
+ * the first argument as name=value.  Since the first argument will
+ * be actually stored in the table, it should not be a string that
+ * will go away.
+ * Called with interrupts off.
+ */
+
+struct var *setvareq(char *s, int flags)
+{
+	struct var *vp, **vpp;
+
+	vpp = hashvar(s);
+	flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1));
+	vpp = findvar(vpp, s);
+	vp = *vpp;
+	if (vp) {
+		if (vp->flags & VREADONLY) {
+			const char *n;
+
+			if (flags & VNOSAVE)
+				free(s);
+			n = vp->text;
+			sh_error("%.*s: is read only", strchrnul(n, '=') - n,
+				 n);
+		}
+
+		if (flags & VNOSET)
+			goto out;
+
+		if (vp->func && (flags & VNOFUNC) == 0)
+			(*vp->func)(strchrnul(s, '=') + 1);
+
+		if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
+			ckfree(vp->text);
+
+		if (((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) |
+		     (vp->flags & VSTRFIXED)) == VUNSET) {
+			*vpp = vp->next;
+			ckfree(vp);
+out_free:
+			if ((flags & (VTEXTFIXED|VSTACK|VNOSAVE)) == VNOSAVE)
+				ckfree(s);
+			goto out;
+		}
+
+		flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET);
+	} else {
+		if (flags & VNOSET)
+			goto out;
+		if ((flags & (VEXPORT|VREADONLY|VSTRFIXED|VUNSET)) == VUNSET)
+			goto out_free;
+		/* not found */
+		vp = ckmalloc(sizeof (*vp));
+		vp->next = *vpp;
+		vp->func = NULL;
+		*vpp = vp;
+	}
+	if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE)))
+		s = savestr(s);
+	vp->text = s;
+	vp->flags = flags;
+
+out:
+	return vp;
+}
+
+/*
+ * Find the value of a variable.  Returns NULL if not set.
+ */
+
+char *
+lookupvar(const char *name)
+{
+	struct var *v;
+
+	if ((v = *findvar(hashvar(name), name)) && !(v->flags & VUNSET)) {
+#ifdef WITH_LINENO
+		if (v == &vlineno && v->text == linenovar) {
+			fmtstr(linenovar+7, sizeof(linenovar)-7, "%d", lineno);
+		}
+#endif
+		return strchrnul(v->text, '=') + 1;
+	}
+	return NULL;
+}
+
+intmax_t lookupvarint(const char *name)
+{
+	return atomax(lookupvar(name) ?: nullstr, 0);
+}
+
+
+
+/*
+ * Generate a list of variables satisfying the given conditions.
+ */
+
+char **
+listvars(int on, int off, char ***end)
+{
+	struct var **vpp;
+	struct var *vp;
+	char **ep;
+	int mask;
+
+	STARTSTACKSTR(ep);
+	vpp = vartab;
+	mask = on | off;
+	do {
+		for (vp = *vpp ; vp ; vp = vp->next)
+			if ((vp->flags & mask) == on) {
+				if (ep == stackstrend())
+					ep = growstackstr();
+				*ep++ = (char *) vp->text;
+			}
+	} while (++vpp < vartab + VTABSIZE);
+	if (ep == stackstrend())
+		ep = growstackstr();
+	if (end)
+		*end = ep;
+	*ep++ = NULL;
+	return grabstackstr(ep);
+}
+
+
+
+/*
+ * POSIX requires that 'set' (but not export or readonly) output the
+ * variables in lexicographic order - by the locale's collating order (sigh).
+ * Maybe we could keep them in an ordered balanced binary tree
+ * instead of hashed lists.
+ * For now just roll 'em through qsort for printing...
+ */
+
+int
+showvars(const char *prefix, int on, int off)
+{
+	const char *sep;
+	char **ep, **epend;
+
+	ep = listvars(on, off, &epend);
+	qsort(ep, epend - ep, sizeof(char *), vpcmp);
+
+	sep = *prefix ? spcstr : prefix;
+
+	for (; ep < epend; ep++) {
+		const char *p;
+		const char *q;
+
+		p = strchrnul(*ep, '=');
+		q = nullstr;
+		if (*p)
+			q = single_quote(++p);
+
+		out1fmt("%s%s%.*s%s\n", prefix, sep, (int)(p - *ep), *ep, q);
+	}
+
+	return 0;
+}
+
+
+
+/*
+ * The export and readonly commands.
+ */
+
+int
+exportcmd(int argc, char **argv)
+{
+	struct var *vp;
+	char *name;
+	const char *p;
+	char **aptr;
+	int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT;
+	int notp;
+
+	notp = nextopt("p") - 'p';
+	if (notp && ((name = *(aptr = argptr)))) {
+		do {
+			if ((p = strchr(name, '=')) != NULL) {
+				p++;
+			} else {
+				if ((vp = *findvar(hashvar(name), name))) {
+					vp->flags |= flag;
+					continue;
+				}
+			}
+			setvar(name, p, flag);
+		} while ((name = *++aptr) != NULL);
+	} else {
+		showvars(argv[0], flag, 0);
+	}
+	return 0;
+}
+
+
+/*
+ * The "local" command.
+ */
+
+int
+localcmd(int argc, char **argv)
+{
+	char *name;
+
+	if (!localvar_stack)
+		sh_error("not in a function");
+
+	argv = argptr;
+	while ((name = *argv++) != NULL) {
+		mklocal(name, 0);
+	}
+	return 0;
+}
+
+
+/*
+ * Make a variable a local variable.  When a variable is made local, it's
+ * value and flags are saved in a localvar structure.  The saved values
+ * will be restored when the shell function returns.  We handle the name
+ * "-" as a special case.
+ */
+
+void mklocal(char *name, int flags)
+{
+	struct localvar *lvp;
+	struct var **vpp;
+	struct var *vp;
+
+	INTOFF;
+	lvp = ckmalloc(sizeof (struct localvar));
+	if (name[0] == '-' && name[1] == '\0') {
+		char *p;
+		p = ckmalloc(sizeof(optlist));
+		lvp->text = memcpy(p, optlist, sizeof(optlist));
+		vp = NULL;
+	} else {
+		char *eq;
+
+		vpp = hashvar(name);
+		vp = *findvar(vpp, name);
+		eq = strchr(name, '=');
+		if (vp == NULL) {
+			if (eq)
+				vp = setvareq(name, VSTRFIXED | flags);
+			else
+				vp = setvar(name, NULL, VSTRFIXED | flags);
+			lvp->flags = VUNSET;
+		} else {
+			lvp->text = vp->text;
+			lvp->flags = vp->flags;
+			vp->flags |= VSTRFIXED|VTEXTFIXED;
+			if (eq)
+				setvareq(name, flags);
+		}
+	}
+	lvp->vp = vp;
+	lvp->next = localvar_stack->lv;
+	localvar_stack->lv = lvp;
+	INTON;
+}
+
+
+/*
+ * Called after a function returns.
+ * Interrupts must be off.
+ */
+
+static void
+poplocalvars(void)
+{
+	struct localvar_list *ll;
+	struct localvar *lvp, *next;
+	struct var *vp;
+
+	INTOFF;
+	ll = localvar_stack;
+	localvar_stack = ll->next;
+
+	next = ll->lv;
+	ckfree(ll);
+
+	while ((lvp = next) != NULL) {
+		next = lvp->next;
+		vp = lvp->vp;
+		TRACE(("poplocalvar %s\n", vp ? vp->text : "-"));
+		if (vp == NULL) {	/* $- saved */
+			memcpy(optlist, lvp->text, sizeof(optlist));
+			ckfree(lvp->text);
+			optschanged();
+		} else if (lvp->flags == VUNSET) {
+			vp->flags &= ~(VSTRFIXED|VREADONLY);
+			unsetvar(vp->text);
+		} else {
+			if (vp->func)
+				(*vp->func)(strchrnul(lvp->text, '=') + 1);
+			if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
+				ckfree(vp->text);
+			vp->flags = lvp->flags;
+			vp->text = lvp->text;
+		}
+		ckfree(lvp);
+	}
+	INTON;
+}
+
+
+/*
+ * Create a new localvar environment.
+ */
+struct localvar_list *pushlocalvars(int push)
+{
+	struct localvar_list *ll;
+	struct localvar_list *top;
+
+	top = localvar_stack;
+	if (!push)
+		goto out;
+
+	INTOFF;
+	ll = ckmalloc(sizeof(*ll));
+	ll->lv = NULL;
+	ll->next = top;
+	localvar_stack = ll;
+	INTON;
+
+out:
+	return top;
+}
+
+
+void unwindlocalvars(struct localvar_list *stop)
+{
+	while (localvar_stack != stop)
+		poplocalvars();
+}
+
+
+/*
+ * The unset builtin command.  We unset the function before we unset the
+ * variable to allow a function to be unset when there is a readonly variable
+ * with the same name.
+ */
+
+int
+unsetcmd(int argc, char **argv)
+{
+	char **ap;
+	int i;
+	int flag = 0;
+
+	while ((i = nextopt("vf")) != '\0') {
+		flag = i;
+	}
+
+	for (ap = argptr; *ap ; ap++) {
+		if (flag != 'f') {
+			unsetvar(*ap);
+			continue;
+		}
+		if (flag != 'v')
+			unsetfunc(*ap);
+	}
+	return 0;
+}
+
+
+/*
+ * Unset the specified variable.
+ */
+
+void unsetvar(const char *s)
+{
+	setvar(s, 0, 0);
+}
+
+
+
+/*
+ * Find the appropriate entry in the hash table from the name.
+ */
+
+STATIC struct var **
+hashvar(const char *p)
+{
+	unsigned int hashval;
+
+	hashval = ((unsigned char) *p) << 4;
+	while (*p && *p != '=')
+		hashval += (unsigned char) *p++;
+	return &vartab[hashval % VTABSIZE];
+}
+
+
+
+/*
+ * Compares two strings up to the first = or '\0'.  The first
+ * string must be terminated by '='; the second may be terminated by
+ * either '=' or '\0'.
+ */
+
+int
+varcmp(const char *p, const char *q)
+{
+	int c, d;
+
+	while ((c = *p) == (d = *q)) {
+		if (!c || c == '=')
+			goto out;
+		p++;
+		q++;
+	}
+	if (c == '=')
+		c = 0;
+	if (d == '=')
+		d = 0;
+out:
+	return c - d;
+}
+
+STATIC int
+vpcmp(const void *a, const void *b)
+{
+	return varcmp(*(const char **)a, *(const char **)b);
+}
+
+STATIC struct var **
+findvar(struct var **vpp, const char *name)
+{
+	for (; *vpp; vpp = &(*vpp)->next) {
+		if (varequal((*vpp)->text, name)) {
+			break;
+		}
+	}
+	return vpp;
+}
diff --git a/bin/dash/src/var.h b/bin/dash/src/var.h
new file mode 100644
index 00000000..1d1d87a5
--- /dev/null
+++ b/bin/dash/src/var.h
@@ -0,0 +1,173 @@
+/*-
+ * Copyright (c) 1991, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 1997-2005
+ *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Kenneth Almquist.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *	@(#)var.h	8.2 (Berkeley) 5/4/95
+ */
+
+#include <inttypes.h>
+
+/*
+ * Shell variables.
+ */
+
+/* flags */
+#define VEXPORT		0x01	/* variable is exported */
+#define VREADONLY	0x02	/* variable cannot be modified */
+#define VSTRFIXED	0x04	/* variable struct is statically allocated */
+#define VTEXTFIXED	0x08	/* text is statically allocated */
+#define VSTACK		0x10	/* text is allocated on the stack */
+#define VUNSET		0x20	/* the variable is not set */
+#define VNOFUNC		0x40	/* don't call the callback function */
+#define VNOSET		0x80	/* do not set variable - just readonly test */
+#define VNOSAVE		0x100	/* when text is on the heap before setvareq */
+
+
+struct var {
+	struct var *next;		/* next entry in hash list */
+	int flags;			/* flags are defined above */
+	const char *text;		/* name=value */
+	void (*func)(const char *);
+					/* function to be called when  */
+					/* the variable gets set/unset */
+};
+
+
+struct localvar {
+	struct localvar *next;		/* next local variable in list */
+	struct var *vp;			/* the variable that was made local */
+	int flags;			/* saved flags */
+	const char *text;		/* saved text */
+};
+
+struct localvar_list;
+
+
+extern struct localvar *localvars;
+extern struct var varinit[];
+
+#if ATTY
+#define vatty varinit[0]
+#define vifs varinit[1]
+#else
+#define vifs varinit[0]
+#endif
+#define vmail (&vifs)[1]
+#define vmpath (&vmail)[1]
+#define vpath (&vmpath)[1]
+#define vps1 (&vpath)[1]
+#define vps2 (&vps1)[1]
+#define vps4 (&vps2)[1]
+#define vrps1 (&vps4)[1]
+#define vrps2 (&vrps1)[1]
+#define voptind (&vrps2)[1]
+#ifdef WITH_LINENO
+#define vlineno (&voptind)[1]
+#endif
+#ifndef SMALL
+#ifdef WITH_LINENO
+#define vterm (&vlineno)[1]
+#else
+#define vterm (&voptind)[1]
+#endif
+#define vhistsize (&vterm)[1]
+#endif
+
+extern char defifsvar[];
+#define defifs (defifsvar + 4)
+extern const char defpathvar[];
+#define defpath (defpathvar + 36)
+
+extern int lineno;
+extern char linenovar[];
+
+/*
+ * The following macros access the values of the above variables.
+ * They have to skip over the name.  They return the null string
+ * for unset variables.
+ */
+
+#define ifsval()	(vifs.text + 4)
+#define ifsset()	((vifs.flags & VUNSET) == 0)
+#define mailval()	(vmail.text + 5)
+#define mpathval()	(vmpath.text + 9)
+#define pathval()	(vpath.text + 5)
+#define ps1val()	(vps1.text + 4)
+#define ps2val()	(vps2.text + 4)
+#define ps4val()	(vps4.text + 4)
+#define rps1val()	(vrps1.text + 5)
+#define rps2val()	(vrps2.text + 5)
+#define optindval()	(voptind.text + 7)
+#define linenoval()	(vlineno.text + 7)
+#ifndef SMALL
+#define histsizeval()	(vhistsize.text + 9)
+#define termval()	(vterm.text + 5)
+#endif
+
+#if ATTY
+#define attyset()	((vatty.flags & VUNSET) == 0)
+#endif
+#define mpathset()	((vmpath.flags & VUNSET) == 0)
+
+void initvar(void);
+struct var *setvar(const char *name, const char *val, int flags);
+intmax_t setvarint(const char *, intmax_t, int);
+struct var *setvareq(char *s, int flags);
+struct strlist;
+char *lookupvar(const char *);
+intmax_t lookupvarint(const char *);
+char **listvars(int, int, char ***);
+#define environment() listvars(VEXPORT, VUNSET, 0)
+int showvars(const char *, int, int);
+int exportcmd(int, char **);
+int localcmd(int, char **);
+void mklocal(char *name, int flags);
+struct localvar_list *pushlocalvars(int push);
+void unwindlocalvars(struct localvar_list *stop);
+int unsetcmd(int, char **);
+void unsetvar(const char *);
+int varcmp(const char *, const char *);
+
+static inline int varequal(const char *a, const char *b) {
+	return !varcmp(a, b);
+}
+
+/*
+ * Search the environment of a builtin command.
+ */
+
+static inline char *bltinlookup(const char *name)
+{
+	return lookupvar(name);
+}
+
+
diff --git a/bin/dtch.c b/bin/dtch.c
new file mode 100644
index 00000000..2aea53ae
--- /dev/null
+++ b/bin/dtch.c
@@ -0,0 +1,271 @@
+/* Copyright (C) 2017-2019  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <unistd.h>
+
+#if defined __FreeBSD__
+#include <libutil.h>
+#elif defined __linux__
+#include <pty.h>
+#else
+#include <util.h>
+#endif
+
+static char _;
+static struct iovec iov = { .iov_base = &_, .iov_len = 1 };
+
+static ssize_t sendfd(int sock, int fd) {
+	size_t len = CMSG_SPACE(sizeof(int));
+	char buf[len];
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+		.msg_control = buf,
+		.msg_controllen = len,
+	};
+
+	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+	cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+	cmsg->cmsg_level = SOL_SOCKET;
+	cmsg->cmsg_type = SCM_RIGHTS;
+	*(int *)CMSG_DATA(cmsg) = fd;
+
+	return sendmsg(sock, &msg, 0);
+}
+
+static int recvfd(int sock) {
+	size_t len = CMSG_SPACE(sizeof(int));
+	char buf[len];
+	struct msghdr msg = {
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+		.msg_control = buf,
+		.msg_controllen = len,
+	};
+	if (0 > recvmsg(sock, &msg, 0)) return -1;
+
+	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
+	if (!cmsg || cmsg->cmsg_type != SCM_RIGHTS) {
+		errno = ENOMSG;
+		return -1;
+	}
+	return *(int *)CMSG_DATA(cmsg);
+}
+
+static struct sockaddr_un addr = { .sun_family = AF_UNIX };
+
+static void handler(int sig) {
+	unlink(addr.sun_path);
+	_exit(-sig);
+}
+
+static void detach(int server, bool sink, char *argv[]) {
+	int pty;
+	pid_t pid = forkpty(&pty, NULL, NULL, NULL);
+	if (pid < 0) err(EX_OSERR, "forkpty");
+
+	if (!pid) {
+		execvp(argv[0], argv);
+		err(EX_NOINPUT, "%s", argv[0]);
+	}
+
+	signal(SIGINT, handler);
+	signal(SIGTERM, handler);
+
+	int error = listen(server, 0);
+	if (error) err(EX_OSERR, "listen");
+
+	struct pollfd fds[] = {
+		{ .events = POLLIN, .fd = server },
+		{ .events = POLLIN, .fd = pty },
+	};
+	while (0 < poll(fds, (sink ? 2 : 1), -1)) {
+		if (fds[0].revents) {
+			int client = accept(server, NULL, NULL);
+			if (client < 0) err(EX_IOERR, "accept");
+
+			ssize_t len = sendfd(client, pty);
+			if (len < 0) warn("sendfd");
+
+			len = recv(client, &_, sizeof(_), 0);
+			if (len < 0) warn("recv");
+
+			close(client);
+		}
+
+		if (fds[1].revents) {
+			char buf[4096];
+			ssize_t len = read(pty, buf, sizeof(buf));
+			if (len < 0) err(EX_IOERR, "read");
+		}
+
+		int status;
+		pid_t dead = waitpid(pid, &status, WNOHANG);
+		if (dead < 0) err(EX_OSERR, "waitpid");
+		if (dead) {
+			unlink(addr.sun_path);
+			exit(WIFEXITED(status) ? WEXITSTATUS(status) : -WTERMSIG(status));
+		}
+	}
+	err(EX_IOERR, "poll");
+}
+
+static struct termios saveTerm;
+static void restoreTerm(void) {
+	tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm);
+	fprintf(stderr, "\33c"); // RIS
+	warnx("detached");
+}
+
+static void nop(int sig) {
+	(void)sig;
+}
+
+static void attach(int client) {
+	int error;
+
+	int pty = recvfd(client);
+	if (pty < 0) err(EX_IOERR, "recvfd");
+	warnx("attached");
+
+	struct winsize window;
+	error = ioctl(STDIN_FILENO, TIOCGWINSZ, &window);
+	if (error) err(EX_IOERR, "ioctl");
+
+	struct winsize redraw = { .ws_row = 1, .ws_col = 1 };
+	error = ioctl(pty, TIOCSWINSZ, &redraw);
+	if (error) err(EX_IOERR, "ioctl");
+
+	error = ioctl(pty, TIOCSWINSZ, &window);
+	if (error) err(EX_IOERR, "ioctl");
+
+	error = tcgetattr(STDIN_FILENO, &saveTerm);
+	if (error) err(EX_IOERR, "tcgetattr");
+	atexit(restoreTerm);
+
+	struct termios raw = saveTerm;
+	cfmakeraw(&raw);
+	error = tcsetattr(STDIN_FILENO, TCSADRAIN, &raw);
+	if (error) err(EX_IOERR, "tcsetattr");
+
+	signal(SIGWINCH, nop);
+
+	char buf[4096];
+	struct pollfd fds[] = {
+		{ .events = POLLIN, .fd = STDIN_FILENO },
+		{ .events = POLLIN, .fd = pty },
+	};
+	for (;;) {
+		int nfds = poll(fds, 2, -1);
+		if (nfds < 0) {
+			if (errno != EINTR) err(EX_IOERR, "poll");
+
+			error = ioctl(STDIN_FILENO, TIOCGWINSZ, &window);
+			if (error) err(EX_IOERR, "ioctl");
+
+			error = ioctl(pty, TIOCSWINSZ, &window);
+			if (error) err(EX_IOERR, "ioctl");
+
+			continue;
+		}
+
+		if (fds[0].revents) {
+			ssize_t len = read(STDIN_FILENO, buf, sizeof(buf));
+			if (len < 0) err(EX_IOERR, "read");
+			if (!len) break;
+
+			if (len == 1 && buf[0] == CTRL('Q')) break;
+
+			len = write(pty, buf, len);
+			if (len < 0) err(EX_IOERR, "write");
+		}
+
+		if (fds[1].revents) {
+			ssize_t len = read(pty, buf, sizeof(buf));
+			if (len < 0) err(EX_IOERR, "read");
+			if (!len) break;
+
+			len = write(STDOUT_FILENO, buf, len);
+			if (len < 0) err(EX_IOERR, "write");
+		}
+	}
+}
+
+int main(int argc, char *argv[]) {
+	int error;
+
+	bool atch = false;
+	bool sink = false;
+
+	int opt;
+	while (0 < (opt = getopt(argc, argv, "as"))) {
+		switch (opt) {
+			break; case 'a': atch = true;
+			break; case 's': sink = true;
+			break; default:  return EX_USAGE;
+		}
+	}
+	if (optind == argc) errx(EX_USAGE, "no session name");
+	const char *name = argv[optind++];
+
+	if (optind == argc) {
+		argv[--optind] = getenv("SHELL");
+		if (!argv[optind]) errx(EX_CONFIG, "SHELL unset");
+	}
+
+	const char *home = getenv("HOME");
+	if (!home) errx(EX_CONFIG, "HOME unset");
+
+	int fd = open(home, 0);
+	if (fd < 0) err(EX_CANTCREAT, "%s", home);
+
+	error = mkdirat(fd, ".dtch", 0700);
+	if (error && errno != EEXIST) err(EX_CANTCREAT, "%s/.dtch", home);
+
+	close(fd);
+
+	int sock = socket(PF_UNIX, SOCK_STREAM, 0);
+	if (sock < 0) err(EX_OSERR, "socket");
+	fcntl(sock, F_SETFD, FD_CLOEXEC);
+
+	snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/.dtch/%s", home, name);
+
+	if (atch) {
+		error = connect(sock, (struct sockaddr *)&addr, SUN_LEN(&addr));
+		if (error) err(EX_NOINPUT, "%s", addr.sun_path);
+		attach(sock);
+	} else {
+		error = bind(sock, (struct sockaddr *)&addr, SUN_LEN(&addr));
+		if (error) err(EX_CANTCREAT, "%s", addr.sun_path);
+		detach(sock, sink, &argv[optind]);
+	}
+}
diff --git a/bin/ever.c b/bin/ever.c
new file mode 100644
index 00000000..258b058b
--- /dev/null
+++ b/bin/ever.c
@@ -0,0 +1,116 @@
+/* Copyright (C) 2017  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/types.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <sys/event.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+static int watch(int kq, char *path) {
+	int fd = open(path, O_CLOEXEC);
+	if (fd < 0) err(EX_NOINPUT, "%s", path);
+
+	struct kevent event;
+	EV_SET(
+		&event,
+		fd,
+		EVFILT_VNODE,
+		EV_ADD | EV_CLEAR,
+		NOTE_WRITE | NOTE_DELETE,
+		0,
+		path
+	);
+	int nevents = kevent(kq, &event, 1, NULL, 0, NULL);
+	if (nevents < 0) err(EX_OSERR, "kevent");
+
+	return fd;
+}
+
+static void exec(int fd, char *const argv[]) {
+	pid_t pid = fork();
+	if (pid < 0) err(EX_OSERR, "fork");
+
+	if (!pid) {
+		dup2(fd, STDIN_FILENO);
+		execvp(*argv, argv);
+		err(EX_NOINPUT, "%s", *argv);
+	}
+
+	int status;
+	pid = wait(&status);
+	if (pid < 0) err(EX_OSERR, "wait");
+
+	if (WIFEXITED(status)) {
+		warnx("exit %d\n", WEXITSTATUS(status));
+	} else if (WIFSIGNALED(status)) {
+		warnx("signal %d\n", WTERMSIG(status));
+	} else {
+		warnx("status %d\n", status);
+	}
+}
+
+int main(int argc, char *argv[]) {
+	bool input = false;
+
+	for (int opt; 0 < (opt = getopt(argc, argv, "i"));) {
+		switch (opt) {
+			break; case 'i': input = true;
+			break; default:  return EX_USAGE;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+	if (argc < 2) return EX_USAGE;
+
+	int kq = kqueue();
+	if (kq < 0) err(EX_OSERR, "kqueue");
+
+	int i;
+	for (i = 0; i < argc - 1; ++i) {
+		if (argv[i][0] == '-') {
+			i++;
+			break;
+		}
+		watch(kq, argv[i]);
+	}
+
+	if (!input) {
+		exec(STDIN_FILENO, &argv[i]);
+	}
+
+	for (;;) {
+		struct kevent event;
+		int nevents = kevent(kq, NULL, 0, &event, 1, NULL);
+		if (nevents < 0) err(EX_OSERR, "kevent");
+
+		if (event.fflags & NOTE_DELETE) {
+			close(event.ident);
+			sleep(1);
+			event.ident = watch(kq, (char *)event.udata);
+		} else if (input) {
+			off_t off = lseek(event.ident, 0, SEEK_SET);
+			if (off < 0) err(EX_IOERR, "lseek");
+		}
+
+		exec((input ? event.ident : STDIN_FILENO), &argv[i]);
+	}
+}
diff --git a/bin/fbatt.c b/bin/fbatt.c
new file mode 100644
index 00000000..9feffe47
--- /dev/null
+++ b/bin/fbatt.c
@@ -0,0 +1,123 @@
+/* Copyright (C) 2018  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <dirent.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fb.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "scheme.h"
+
+static const char *Class = "/sys/class/power_supply";
+
+static const uint32_t Right  = 5 * 8 + 1; // fbclock width.
+static const uint32_t Width  = 8;
+static const uint32_t Height = 16;
+
+int main() {
+	int error;
+
+	DIR *dir = opendir(Class);
+	if (!dir) err(EX_OSFILE, "%s", Class);
+
+	FILE *chargeFull = NULL;
+	FILE *chargeNow = NULL;
+
+	const struct dirent *entry;
+	while (NULL != (errno = 0, entry = readdir(dir))) {
+		if (entry->d_name[0] == '.') continue;
+
+		error = chdir(Class);
+		if (error) err(EX_OSFILE, "%s", Class);
+
+		error = chdir(entry->d_name);
+		if (error) err(EX_OSFILE, "%s/%s", Class, entry->d_name);
+
+		chargeFull = fopen("charge_full", "r");
+		chargeNow = fopen("charge_now", "r");
+		if (chargeFull && chargeNow) break;
+	}
+	if (!chargeFull || !chargeNow) {
+		if (errno) err(EX_OSFILE, "%s", Class);
+		errx(EX_CONFIG, "%s: empty", Class);
+	}
+	closedir(dir);
+
+	const char *path = getenv("FRAMEBUFFER");
+	if (!path) path = "/dev/fb0";
+
+	int fb = open(path, O_RDWR);
+	if (fb < 0) err(EX_OSFILE, "%s", path);
+
+	struct fb_var_screeninfo info;
+	error = ioctl(fb, FBIOGET_VSCREENINFO, &info);
+	if (error) err(EX_IOERR, "%s", path);
+
+	size_t size = 4 * info.xres * info.yres;
+	uint32_t *buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fb, 0);
+	if (buf == MAP_FAILED) err(EX_IOERR, "%s", path);
+
+	for (;;) {
+		int match;
+
+		rewind(chargeFull);
+		fflush(chargeFull);
+		uint32_t full;
+		match = fscanf(chargeFull, "%u", &full);
+		if (match == EOF) err(EX_IOERR, "charge_full");
+		if (match < 1) errx(EX_DATAERR, "charge_full");
+
+		rewind(chargeNow);
+		fflush(chargeNow);
+		uint32_t now;
+		match = fscanf(chargeNow, "%u", &now);
+		if (match == EOF) err(EX_IOERR, "charge_now");
+		if (match < 1) errx(EX_DATAERR, "charge_now");
+
+		uint32_t percent = 100 * now / full;
+		uint32_t height = 16 * now / full;
+
+		for (int i = 0; i < 60; ++i, sleep(1)) {
+			uint32_t left = info.xres - Right - Width;
+
+			for (uint32_t y = 0; y <= Height; ++y) {
+				buf[y * info.xres + left - 1] = DarkWhite;
+				buf[y * info.xres + left + Width] = DarkWhite;
+			}
+			for (uint32_t x = left; x < left + Width; ++x) {
+				buf[Height * info.xres + x] = DarkWhite;
+			}
+
+			for (uint32_t y = 0; y < Height; ++y) {
+				for (uint32_t x = left; x < left + Width; ++x) {
+					buf[y * info.xres + x] =
+						(Height - 1 - y > height) ? DarkBlack
+						: (percent <= 10) ? DarkRed
+						: (percent <= 30) ? DarkYellow
+						: LightBlack;
+				}
+			}
+		}
+	}
+}
diff --git a/bin/fbclock.c b/bin/fbclock.c
new file mode 100644
index 00000000..ddc32db6
--- /dev/null
+++ b/bin/fbclock.c
@@ -0,0 +1,132 @@
+/* Copyright (C) 2018  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <fcntl.h>
+#include <linux/fb.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sysexits.h>
+#include <time.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#include "scheme.h"
+
+static const uint32_t PSF2Magic = 0x864AB572;
+struct PSF2Header {
+	uint32_t magic;
+	uint32_t version;
+	uint32_t headerSize;
+	uint32_t flags;
+	uint32_t glyphCount;
+	uint32_t glyphSize;
+	uint32_t glyphHeight;
+	uint32_t glyphWidth;
+};
+
+int main() {
+	size_t len;
+
+	const char *fontPath = getenv("FONT");
+	if (!fontPath) {
+		fontPath = "/usr/share/kbd/consolefonts/Lat2-Terminus16.psfu.gz";
+	}
+
+	gzFile font = gzopen(fontPath, "r");
+	if (!font) err(EX_NOINPUT, "%s", fontPath);
+
+	struct PSF2Header header;
+	len = gzfread(&header, sizeof(header), 1, font);
+	if (!len && gzeof(font)) errx(EX_DATAERR, "%s: missing header", fontPath);
+	if (!len) errx(EX_IOERR, "%s", gzerror(font, NULL));
+
+	if (header.magic != PSF2Magic) {
+		errx(
+			EX_DATAERR, "%s: invalid header magic %08X",
+			fontPath, header.magic
+		);
+	}
+	if (header.headerSize != sizeof(struct PSF2Header)) {
+		errx(
+			EX_DATAERR, "%s: weird header size %d",
+			fontPath, header.headerSize
+		);
+	}
+
+	uint8_t glyphs[128][header.glyphSize];
+	len = gzfread(glyphs, header.glyphSize, 128, font);
+	if (!len && gzeof(font)) errx(EX_DATAERR, "%s: missing glyphs", fontPath);
+	if (!len) errx(EX_IOERR, "%s", gzerror(font, NULL));
+
+	gzclose(font);
+
+	const char *fbPath = getenv("FRAMEBUFFER");
+	if (!fbPath) fbPath = "/dev/fb0";
+
+	int fb = open(fbPath, O_RDWR);
+	if (fb < 0) err(EX_OSFILE, "%s", fbPath);
+
+	struct fb_var_screeninfo info;
+	int error = ioctl(fb, FBIOGET_VSCREENINFO, &info);
+	if (error) err(EX_IOERR, "%s", fbPath);
+
+	size_t size = 4 * info.xres * info.yres;
+	uint32_t *buf = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fb, 0);
+	if (buf == MAP_FAILED) err(EX_IOERR, "%s", fbPath);
+
+	for (;;) {
+		time_t t = time(NULL);
+		if (t < 0) err(EX_OSERR, "time");
+		const struct tm *local = localtime(&t);
+		if (!local) err(EX_OSERR, "localtime");
+
+		char str[64];
+		len = strftime(str, sizeof(str), "%H:%M", local);
+		assert(len);
+
+		for (int i = 0; i < (60 - local->tm_sec); ++i, sleep(1)) {
+			uint32_t left = info.xres - header.glyphWidth * len;
+			uint32_t bottom = header.glyphHeight;
+
+			for (uint32_t y = 0; y < bottom; ++y) {
+				buf[y * info.xres + left - 1] = DarkWhite;
+			}
+			for (uint32_t x = left - 1; x < info.xres; ++x) {
+				buf[bottom * info.xres + x] = DarkWhite;
+			}
+
+			for (const char *s = str; *s; ++s) {
+				const uint8_t *glyph = glyphs[(unsigned)*s];
+				uint32_t stride = header.glyphSize / header.glyphHeight;
+				for (uint32_t y = 0; y < header.glyphHeight; ++y) {
+					for (uint32_t x = 0; x < header.glyphWidth; ++x) {
+						uint8_t bits = glyph[y * stride + x / 8];
+						uint8_t bit = bits >> (7 - x % 8) & 1;
+						buf[y * info.xres + left + x] = bit
+							? DarkWhite
+							: DarkBlack;
+					}
+				}
+				left += header.glyphWidth;
+			}
+		}
+	}
+}
diff --git a/bin/glitch.c b/bin/glitch.c
new file mode 100644
index 00000000..9747f35a
--- /dev/null
+++ b/bin/glitch.c
@@ -0,0 +1,538 @@
+/* Copyright (C) 2018  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <arpa/inet.h>
+#include <err.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#define PACKED __attribute__((packed))
+
+#define CRC_INIT (crc32(0, Z_NULL, 0))
+
+static const char *path;
+static FILE *file;
+static uint32_t crc;
+
+static void readExpect(void *ptr, size_t size, const char *expect) {
+	fread(ptr, size, 1, file);
+	if (ferror(file)) err(EX_IOERR, "%s", path);
+	if (feof(file)) errx(EX_DATAERR, "%s: missing %s", path, expect);
+	crc = crc32(crc, ptr, size);
+}
+
+static void writeExpect(const void *ptr, size_t size) {
+	fwrite(ptr, size, 1, file);
+	if (ferror(file)) err(EX_IOERR, "%s", path);
+	crc = crc32(crc, ptr, size);
+}
+
+static const uint8_t Signature[8] = "\x89PNG\r\n\x1A\n";
+
+static void readSignature(void) {
+	uint8_t signature[8];
+	readExpect(signature, 8, "signature");
+	if (0 != memcmp(signature, Signature, 8)) {
+		errx(EX_DATAERR, "%s: invalid signature", path);
+	}
+}
+
+static void writeSignature(void) {
+	writeExpect(Signature, sizeof(Signature));
+}
+
+struct PACKED Chunk {
+	uint32_t size;
+	char type[4];
+};
+
+static const char *typeStr(struct Chunk chunk) {
+	static char buf[5];
+	memcpy(buf, chunk.type, 4);
+	return buf;
+}
+
+static struct Chunk readChunk(void) {
+	struct Chunk chunk;
+	readExpect(&chunk, sizeof(chunk), "chunk");
+	chunk.size = ntohl(chunk.size);
+	crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type));
+	return chunk;
+}
+
+static void writeChunk(struct Chunk chunk) {
+	chunk.size = htonl(chunk.size);
+	writeExpect(&chunk, sizeof(chunk));
+	crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type));
+}
+
+static void readCrc(void) {
+	uint32_t expected = crc;
+	uint32_t found;
+	readExpect(&found, sizeof(found), "CRC32");
+	found = ntohl(found);
+	if (found != expected) {
+		errx(
+			EX_DATAERR, "%s: expected CRC32 %08X, found %08X",
+			path, expected, found
+		);
+	}
+}
+
+static void writeCrc(void) {
+	uint32_t net = htonl(crc);
+	writeExpect(&net, sizeof(net));
+}
+
+static void skipChunk(struct Chunk chunk) {
+	uint8_t discard[chunk.size];
+	readExpect(discard, sizeof(discard), "chunk data");
+	readCrc();
+}
+
+static struct PACKED {
+	uint32_t width;
+	uint32_t height;
+	uint8_t depth;
+	enum PACKED {
+		Grayscale      = 0,
+		Truecolor      = 2,
+		Indexed        = 3,
+		GrayscaleAlpha = 4,
+		TruecolorAlpha = 6,
+	} color;
+	uint8_t compression;
+	uint8_t filter;
+	uint8_t interlace;
+} header;
+_Static_assert(13 == sizeof(header), "header size");
+
+static size_t pixelBits(void) {
+	switch (header.color) {
+		case Grayscale:      return 1 * header.depth;
+		case Truecolor:      return 3 * header.depth;
+		case Indexed:        return 1 * header.depth;
+		case GrayscaleAlpha: return 2 * header.depth;
+		case TruecolorAlpha: return 4 * header.depth;
+		default: abort();
+	}
+}
+
+static size_t pixelSize(void) {
+	return (pixelBits() + 7) / 8;
+}
+
+static size_t lineSize(void) {
+	return (header.width * pixelBits() + 7) / 8;
+}
+
+static size_t dataSize(void) {
+	return (1 + lineSize()) * header.height;
+}
+
+static void readHeader(void) {
+	struct Chunk ihdr = readChunk();
+	if (0 != memcmp(ihdr.type, "IHDR", 4)) {
+		errx(EX_DATAERR, "%s: expected IHDR, found %s", path, typeStr(ihdr));
+	}
+	if (ihdr.size != sizeof(header)) {
+		errx(
+			EX_DATAERR, "%s: expected IHDR size %zu, found %u",
+			path, sizeof(header), ihdr.size
+		);
+	}
+	readExpect(&header, sizeof(header), "header");
+	readCrc();
+	header.width = ntohl(header.width);
+	header.height = ntohl(header.height);
+	if (!header.width) errx(EX_DATAERR, "%s: invalid width 0", path);
+	if (!header.height) errx(EX_DATAERR, "%s: invalid height 0", path);
+}
+
+static void writeHeader(void) {
+	struct Chunk ihdr = { .size = sizeof(header), .type = "IHDR" };
+	writeChunk(ihdr);
+	header.width = htonl(header.width);
+	header.height = htonl(header.height);
+	writeExpect(&header, sizeof(header));
+	writeCrc();
+	header.width = ntohl(header.width);
+	header.height = ntohl(header.height);
+}
+
+static struct {
+	uint32_t len;
+	uint8_t entries[256][3];
+} palette;
+
+static void readPalette(void) {
+	struct Chunk chunk;
+	for (;;) {
+		chunk = readChunk();
+		if (0 == memcmp(chunk.type, "PLTE", 4)) break;
+		skipChunk(chunk);
+	}
+	palette.len = chunk.size / 3;
+	readExpect(palette.entries, chunk.size, "palette data");
+	readCrc();
+}
+
+static void writePalette(void) {
+	struct Chunk plte = { .size = 3 * palette.len, .type = "PLTE" };
+	writeChunk(plte);
+	writeExpect(palette.entries, plte.size);
+	writeCrc();
+}
+
+static uint8_t *data;
+
+static void readData(void) {
+	data = malloc(dataSize());
+	if (!data) err(EX_OSERR, "malloc(%zu)", dataSize());
+
+	struct z_stream_s stream = { .next_out = data, .avail_out = dataSize() };
+	int error = inflateInit(&stream);
+	if (error != Z_OK) errx(EX_SOFTWARE, "%s: inflateInit: %s", path, stream.msg);
+
+	for (;;) {
+		struct Chunk chunk = readChunk();
+		if (0 == memcmp(chunk.type, "IDAT", 4)) {
+			uint8_t *idat = malloc(chunk.size);
+			if (!idat) err(EX_OSERR, "malloc");
+
+			readExpect(idat, chunk.size, "image data");
+			readCrc();
+
+			stream.next_in = idat;
+			stream.avail_in = chunk.size;
+			int error = inflate(&stream, Z_SYNC_FLUSH);
+			free(idat);
+
+			if (error == Z_STREAM_END) break;
+			if (error != Z_OK) errx(EX_DATAERR, "%s: inflate: %s", path, stream.msg);
+
+		} else if (0 == memcmp(chunk.type, "IEND", 4)) {
+			errx(EX_DATAERR, "%s: missing IDAT chunk", path);
+		} else {
+			skipChunk(chunk);
+		}
+	}
+
+	inflateEnd(&stream);
+	if ((size_t)stream.total_out != dataSize()) {
+		errx(
+			EX_DATAERR, "%s: expected data size %zu, found %zu",
+			path, dataSize(), (size_t)stream.total_out
+		);
+	}
+}
+
+static void writeData(void) {
+	uLong size = compressBound(dataSize());
+	uint8_t *deflate = malloc(size);
+	if (!deflate) err(EX_OSERR, "malloc");
+
+	int error = compress2(deflate, &size, data, dataSize(), Z_BEST_SPEED);
+	if (error != Z_OK) errx(EX_SOFTWARE, "%s: compress2: %d", path, error);
+
+	struct Chunk idat = { .size = size, .type = "IDAT" };
+	writeChunk(idat);
+	writeExpect(deflate, size);
+	writeCrc();
+
+	free(deflate);
+}
+
+static void writeEnd(void) {
+	struct Chunk iend = { .size = 0, .type = "IEND" };
+	writeChunk(iend);
+	writeCrc();
+}
+
+enum PACKED Filter {
+	None,
+	Sub,
+	Up,
+	Average,
+	Paeth,
+	FilterCount,
+};
+
+static struct {
+	bool brokenPaeth;
+	bool filt;
+	bool recon;
+	uint8_t declareFilter;
+	uint8_t applyFilter;
+	enum Filter declareFilters[255];
+	enum Filter applyFilters[255];
+	bool invert;
+	bool mirror;
+	bool zeroX;
+	bool zeroY;
+} options;
+
+struct Bytes {
+	uint8_t x;
+	uint8_t a;
+	uint8_t b;
+	uint8_t c;
+};
+
+static uint8_t paethPredictor(struct Bytes f) {
+	int32_t p = (int32_t)f.a + (int32_t)f.b - (int32_t)f.c;
+	int32_t pa = abs(p - (int32_t)f.a);
+	int32_t pb = abs(p - (int32_t)f.b);
+	int32_t pc = abs(p - (int32_t)f.c);
+	if (pa <= pb && pa <= pc) return f.a;
+	if (options.brokenPaeth) {
+		if (pb < pc) return f.b;
+	} else {
+		if (pb <= pc) return f.b;
+	}
+	return f.c;
+}
+
+static uint8_t recon(enum Filter type, struct Bytes f) {
+	switch (type) {
+		case None:    return f.x;
+		case Sub:     return f.x + f.a;
+		case Up:      return f.x + f.b;
+		case Average: return f.x + ((uint32_t)f.a + (uint32_t)f.b) / 2;
+		case Paeth:   return f.x + paethPredictor(f);
+		default:      abort();
+	}
+}
+
+static uint8_t filt(enum Filter type, struct Bytes f) {
+	switch (type) {
+		case None:    return f.x;
+		case Sub:     return f.x - f.a;
+		case Up:      return f.x - f.b;
+		case Average: return f.x - ((uint32_t)f.a + (uint32_t)f.b) / 2;
+		case Paeth:   return f.x - paethPredictor(f);
+		default:      abort();
+	}
+}
+
+static struct Line {
+	enum Filter type;
+	uint8_t data[];
+} **lines;
+
+static void scanlines(void) {
+	lines = calloc(header.height, sizeof(*lines));
+	if (!lines) err(EX_OSERR, "calloc(%u, %zu)", header.height, sizeof(*lines));
+
+	size_t stride = 1 + lineSize();
+	for (uint32_t y = 0; y < header.height; ++y) {
+		lines[y] = (struct Line *)&data[y * stride];
+		if (lines[y]->type >= FilterCount) {
+			errx(EX_DATAERR, "%s: invalid filter type %hhu", path, lines[y]->type);
+		}
+	}
+}
+
+static struct Bytes origBytes(uint32_t y, size_t i) {
+	bool a = (i >= pixelSize()), b = (y > 0), c = (a && b);
+	return (struct Bytes) {
+		.x = lines[y]->data[i],
+		.a = a ? lines[y]->data[i - pixelSize()] : 0,
+		.b = b ? lines[y - 1]->data[i] : 0,
+		.c = c ? lines[y - 1]->data[i - pixelSize()] : 0,
+	};
+}
+
+static void reconData(void) {
+	for (uint32_t y = 0; y < header.height; ++y) {
+		for (size_t i = 0; i < lineSize(); ++i) {
+			if (options.filt) {
+				lines[y]->data[i] = filt(lines[y]->type, origBytes(y, i));
+			} else {
+				lines[y]->data[i] = recon(lines[y]->type, origBytes(y, i));
+			}
+		}
+		lines[y]->type = None;
+	}
+}
+
+static void filterData(void) {
+	for (uint32_t y = header.height - 1; y < header.height; --y) {
+		uint8_t filter[FilterCount][lineSize()];
+		uint32_t heuristic[FilterCount] = {0};
+		enum Filter minType = None;
+		for (enum Filter type = None; type < FilterCount; ++type) {
+			for (size_t i = 0; i < lineSize(); ++i) {
+				if (options.recon) {
+					filter[type][i] = recon(type, origBytes(y, i));
+				} else {
+					filter[type][i] = filt(type, origBytes(y, i));
+				}
+				heuristic[type] += abs((int8_t)filter[type][i]);
+			}
+			if (heuristic[type] < heuristic[minType]) minType = type;
+		}
+
+		if (options.declareFilter) {
+			lines[y]->type = options.declareFilters[y % options.declareFilter];
+		} else {
+			lines[y]->type = minType;
+		}
+
+		if (options.applyFilter) {
+			enum Filter type = options.applyFilters[y % options.applyFilter];
+			memcpy(lines[y]->data, filter[type], lineSize());
+		} else {
+			memcpy(lines[y]->data, filter[minType], lineSize());
+		}
+	}
+}
+
+static void invert(void) {
+	for (uint32_t y = 0; y < header.height; ++y) {
+		for (size_t i = 0; i < lineSize(); ++i) {
+			lines[y]->data[i] ^= 0xFF;
+		}
+	}
+}
+
+static void mirror(void) {
+	for (uint32_t y = 0; y < header.height; ++y) {
+		for (size_t i = 0, j = lineSize() - 1; i < j; ++i, --j) {
+			uint8_t t = lines[y]->data[i];
+			lines[y]->data[i] = lines[y]->data[j];
+			lines[y]->data[j] = t;
+		}
+	}
+}
+
+static void zeroX(void) {
+	for (uint32_t y = 0; y < header.height; ++y) {
+		memset(lines[y]->data, 0, pixelSize());
+	}
+}
+
+static void zeroY(void) {
+	memset(lines[0]->data, 0, lineSize());
+}
+
+static void glitch(const char *inPath, const char *outPath) {
+	if (inPath) {
+		path = inPath;
+		file = fopen(path, "r");
+		if (!file) err(EX_NOINPUT, "%s", path);
+	} else {
+		path = "(stdin)";
+		file = stdin;
+	}
+
+	readSignature();
+	readHeader();
+	if (header.color == Indexed) readPalette();
+	readData();
+	fclose(file);
+
+	scanlines();
+	reconData();
+	filterData();
+	if (options.invert) invert();
+	if (options.mirror) mirror();
+	if (options.zeroX) zeroX();
+	if (options.zeroY) zeroY();
+	free(lines);
+
+	if (outPath) {
+		path = outPath;
+		file = fopen(path, "w");
+		if (!file) err(EX_CANTCREAT, "%s", path);
+	} else {
+		path = "(stdout)";
+		file = stdout;
+	}
+
+	writeSignature();
+	writeHeader();
+	if (header.color == Indexed) writePalette();
+	writeData();
+	writeEnd();
+	free(data);
+
+	int error = fclose(file);
+	if (error) err(EX_IOERR, "%s", path);
+}
+
+static enum Filter parseFilter(const char *s) {
+	switch (s[0]) {
+		case 'N': case 'n': return None;
+		case 'S': case 's': return Sub;
+		case 'U': case 'u': return Up;
+		case 'A': case 'a': return Average;
+		case 'P': case 'p': return Paeth;
+		default: errx(EX_USAGE, "invalid filter type %s", s);
+	}
+}
+
+static uint8_t parseFilters(enum Filter *filters, const char *s) {
+	uint8_t len = 0;
+	do {
+		filters[len++] = parseFilter(s);
+		s = strchr(s, ',');
+	} while (s++);
+	return len;
+}
+
+int main(int argc, char *argv[]) {
+	bool stdio = false;
+	char *output = NULL;
+
+	int opt;
+	while (0 < (opt = getopt(argc, argv, "a:cd:fimo:prxy"))) {
+		switch (opt) {
+			break; case 'a':
+				options.applyFilter = parseFilters(options.applyFilters, optarg);
+			break; case 'c': stdio = true;
+			break; case 'd':
+				options.declareFilter = parseFilters(options.declareFilters, optarg);
+			break; case 'f': options.filt = true;
+			break; case 'i': options.invert = true;
+			break; case 'm': options.mirror = true;
+			break; case 'o': output = optarg;
+			break; case 'p': options.brokenPaeth = true;
+			break; case 'r': options.recon = true;
+			break; case 'x': options.zeroX = true;
+			break; case 'y': options.zeroY = true;
+			break; default: return EX_USAGE;
+		}
+	}
+
+	if (argc - optind == 1 && (output || stdio)) {
+		glitch(argv[optind], output);
+	} else if (optind < argc) {
+		for (int i = optind; i < argc; ++i) {
+			glitch(argv[i], argv[i]);
+		}
+	} else {
+		glitch(NULL, output);
+	}
+
+	return EX_OK;
+}
diff --git a/bin/hi.c b/bin/hi.c
new file mode 100644
index 00000000..b39c0af4
--- /dev/null
+++ b/bin/hi.c
@@ -0,0 +1,766 @@
+/* vim: set foldmethod=marker foldlevel=0: */
+/* Copyright (C) 2019  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <locale.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
+
+typedef unsigned Set;
+#define SET(x) ((Set)1 << (x))
+
+#define ENUM_CLASS \
+	X(Normal)  \
+	X(Keyword) \
+	X(Macro)   \
+	X(Tag)     \
+	X(String)  \
+	X(Escape)  \
+	X(Format)  \
+	X(Interp)  \
+	X(Comment) \
+	X(Todo)    \
+	X(DiffOld) \
+	X(DiffNew)
+
+enum Class {
+#define X(class) class,
+	ENUM_CLASS
+#undef X
+	ClassLen,
+};
+
+static const char *ClassName[ClassLen] = {
+#define X(class) [class] = #class,
+	ENUM_CLASS
+#undef X
+};
+
+struct Syntax {
+	enum Class class;
+	Set parent;
+	bool newline;
+	size_t subexp;
+	const char *pattern;
+};
+
+#define WB "(^|[^_[:alnum:]]|\n)"
+#define BL0 "[[:blank:]]*"
+#define BL1 "[[:blank:]]+"
+#define SP0 "[[:space:]]*"
+#define SP1 "[[:space:]]+"
+#define PATTERN_ID "[_[:alpha:]][_[:alnum:]]*"
+#define PATTERN_SQ "'([^']|[\\]')*'"
+#define PATTERN_DQ "\"([^\"]|[\\]\")*\""
+#define PATTERN_BC "/[*]" "([^*]|[*][^/])*" "[*]+/"
+#define PATTERN_TODO "FIXME|TODO|XXX"
+
+// C syntax {{{
+static const struct Syntax CSyntax[] = {
+	{ Keyword, .subexp = 2, .pattern = WB
+		"(" "auto|extern|register|static|(_T|t)hread_local|typedef"
+		"|" "_Atomic|const|restrict|volatile"
+		"|" "inline|(_N|n)oreturn"
+		"|" "(_A|a)lignas"
+		"|" "enum|struct|union"
+		"|" "do|else|for|if|switch|while"
+		"|" "break|case|continue|default|goto|return"
+		")" WB },
+	{ Macro,
+		.pattern = "^" BL0 "#(.|[\\]\n)*" },
+	{ Tag, .parent = SET(Macro), .subexp = 1,
+		.pattern = "define" BL1 "(" PATTERN_ID ")" "[(]" },
+	{ Tag, .subexp = 2,
+		.pattern = "(enum|struct|union)" SP1 "(" PATTERN_ID ")" SP0 "[{]" },
+	{ Tag, .parent = ~SET(Keyword), .newline = true, .subexp = 1,
+		.pattern = "(" PATTERN_ID ")" SP0 "[(][^()]*[)]" SP0 "[{]" },
+	{ Tag, .newline = true, .subexp = 3, .pattern =
+		"(static|typedef)" SP1
+		"(" "(" PATTERN_ID ")" SP0
+		"(" "[*]" "|" "[[][^]]*[]]" "|" "[{][^}]*[}]" "|" SP0 ")*" ")+" },
+	{ String, .parent = SET(Macro), .subexp = 2,
+		.pattern = "(include|import)" BL0 "(<[^>]*>)" },
+	{ String,
+		.pattern = "[LUu]?" PATTERN_SQ },
+	{ String, .parent = ~SET(String),
+		.pattern = "([LU]|u8?)?" PATTERN_DQ },
+	{ Escape, .parent = SET(String),
+		.pattern = "[\\]([\"'?\\abfnrtv]|[0-7]{1,3}|x[0-9A-Fa-f]+)" },
+	{ Escape, .parent = SET(String),
+		.pattern = "[\\](U[0-9A-Fa-f]{8}|u[0-9A-Fa-f]{4})" },
+	{ Format, .parent = SET(String), .pattern =
+		"%%|%[ #+-0]*"         // flags
+		"([*]|[0-9]+)?"        // field width
+		"([.]([*]|[0-9]+))?"   // precision
+		"([Lhjltz]|hh|ll)?"    // length modifier
+		"[AEFGXacdefginopsux]" // format specifier
+	},
+	{ Comment, .parent = ~SET(String),
+		.pattern = "//(.|[\\]\n)*" },
+	{ Comment, .parent = ~SET(String), .newline = true,
+		.pattern = PATTERN_BC },
+	{ Todo, .parent = SET(Comment),
+		.pattern = PATTERN_TODO },
+};
+// }}}
+
+// diff syntax {{{
+static const struct Syntax DiffSyntax[] = {
+	{ Keyword, .pattern = "^[^ ].*" },
+	{ Comment, .pattern = "^@@.*" },
+	{ DiffOld, .pattern = "^[-].*" },
+	{ DiffNew, .pattern = "^[+].*" },
+};
+// }}}
+
+// make syntax {{{
+#define MAKE_TARGET "[-./_[:alnum:]]+"
+static const struct Syntax MakeSyntax[] = {
+	{ Keyword, .subexp = 2,
+		.pattern = WB "([.](PHONY|PRECIOUS|SUFFIXES))" WB },
+	{ Macro,
+		.pattern = "^ *-?include" },
+	{ Tag, .parent = ~SET(Keyword), .subexp = 1, .pattern =
+		"(" MAKE_TARGET ")" "(" BL1 MAKE_TARGET ")*" BL0 ":([^=]|$)" },
+	{ String, .subexp = 1,
+		.pattern = "[._[:alnum:]]+" BL0 "[!+:?]?=" BL0 "(.*)" },
+	{ Normal,
+		.pattern = "^\t.*" },
+	{ String,
+		.pattern = PATTERN_SQ },
+	{ String,
+		.pattern = PATTERN_DQ },
+	{ Interp,
+		.pattern = "[$]." },
+	// Support one level of nesting with the same delimiter.
+	{ Interp,
+		.pattern = "[$][(](" "[^$)]" "|" "[$]." "|" "[$][(][^)]*[)]" ")*[)]" },
+	{ Interp,
+		.pattern = "[$][{](" "[^$}]" "|" "[$]." "|" "[$][{][^}]*[}]" ")*[}]" },
+	{ Escape,
+		.pattern = "[$][$]" },
+	{ Comment, .parent = ~SET(String),
+		.pattern = "#.*" },
+	{ Todo, .parent = SET(Comment),
+		.pattern = PATTERN_TODO },
+};
+// }}}
+
+// mdoc syntax {{{
+static const struct Syntax MdocSyntax[] = {
+	{ Keyword, .subexp = 2, .pattern = WB
+		"(" "D[dt]|N[dm]|Os"
+		"|" "S[hsx]|[LP]p|Xr"
+		"|" "%[ABCDIJNOPQRTUV]|[BE][dl]|D[1l]|It|Ql|R[es]|Ta"
+		"|" "Ap|[BE]k|Ns|Pf|Sm"
+		"|" "Ar|Cm|Ev|Fl|O[cop]|Pa"
+		"|" "Dv|Er|F[acdnot]|In|Lb|V[at]"
+		"|" "A[dn]|Cd|Lk|M[st]"
+		"|" "[BE]f|Em|Li|No|Sy"
+		"|" "(Br|[ABDPQS])[coq]|E[co]"
+		"|" "At|(Bs|[BDEFNO])x|Rv|St"
+		")" WB },
+	{ Tag, .subexp = 1,
+		.pattern = "^[.]S[hs]" BL1 "(.+)" },
+	{ String,
+		.pattern = PATTERN_DQ },
+	{ Normal,
+		.pattern = "^[^.].*" },
+	{ String,
+		.pattern = "[\\](" "." "|" "[(].{2}" "|" "[[][^]]*[]]" ")" },
+	{ Comment,
+		.pattern = "^[.][\\]\".*" },
+	{ Todo, .parent = SET(Comment),
+		.pattern = PATTERN_TODO },
+};
+// }}}
+
+// Rust syntax {{{
+static const struct Syntax RustSyntax[] = {
+	{ Keyword, .subexp = 2, .pattern = WB
+		"(" "'?static|[Ss]elf|abstract|as|async|await|become|box|break|const"
+		"|" "continue|crate|do|dyn|else|enum|extern|false|final|fn|for|if"
+		"|" "impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref"
+		"|" "return|struct|super|trait|true|try|type(of)?|union|uns(afe|ized)"
+		"|" "use|virtual|where|while|yield"
+		")" WB },
+	{ Tag, .subexp = 2, .pattern =
+		"(enum|fn|macro_rules!|mod|struct|type|union)" SP1 "(" PATTERN_ID ")" },
+	{ Macro, .newline = true,
+		.pattern = "#!?[[][^]]*[]]" },
+	{ Macro,
+		.pattern = PATTERN_ID "!" },
+	{ Interp,
+		.pattern = "[$]" PATTERN_ID },
+	{ String,
+		.pattern = "b?'([^']|[\\]')'" },
+	{ String,
+		.pattern = "b?" "\"([^\"]|[\\][\n\"])*\"" },
+	{ Escape, .parent = SET(String),
+		.pattern = "[\\]([\"'0\\nrt]|u[{][0-9A-Fa-f]{1,6}[}]|x[0-9A-Fa-f]{2})" },
+	{ Format, .parent = SET(String),
+		.pattern = "[{][{]|[{][^{}]*[}]|[}][}]" },
+	{ String, .parent = ~SET(String), .newline = true,
+		.pattern = "b?r\"[^\"]*\"" },
+	{ String, .parent = ~SET(String), .newline = true,
+		.pattern = "b?r#+\"" "([^\"]|\"[^#])*" "\"+#+" },
+	{ Comment, .parent = ~SET(String),
+		.pattern = "//.*" },
+	{ Comment, .parent = ~SET(String), .newline = true,
+		.pattern = PATTERN_BC },
+	{ Todo, .parent = SET(Comment),
+		.pattern = PATTERN_TODO },
+};
+// }}}
+
+// sh syntax {{{
+static const struct Syntax ShSyntax[] = {
+	{ Keyword, .subexp = 2, .pattern = WB
+		"(" "!|case|do|done|elif|else|esac|fi|for|if|in|then|until|while"
+		"|" "alias|bg|cd|command|false|fc|fg|getopts|jobs|kill|newgrp|pwd|read"
+		"|" "true|type|ulimit|umask|unalias|wait"
+		"|" "[.:]|break|continue|eval|exec|exit|export|local|readonly|return"
+		"|" "set|shift|times|trap|unset"
+		")" WB },
+	{ Tag, .subexp = 2,
+		.pattern = WB "(" PATTERN_ID ")" BL0 "[(]" BL0 "[)]" },
+	{ String, .newline = true, .subexp = 1, .pattern =
+		"<<-?" BL0 "EOF[^\n]*\n"
+		"(([^\n]|\n\t*[^E]|\n\t*E[^O]|\n\t*EO[^F]|\n\t*EOF[^\n])*)"
+		"\n\t*EOF\n" },
+	{ String, .parent = ~SET(String), .newline = true,
+		.pattern = PATTERN_DQ },
+	{ Escape, .parent = SET(String),
+		.pattern = "[\\][\"$\\`]" },
+	{ Interp, .parent = ~SET(Escape),
+		.pattern = "[$][(][^)]*[)]" "|" "`[^`]*`" },
+	{ Interp, .parent = ~SET(Escape),
+		.pattern = "[$][(][(]([^)]|[)][^)])*[)][)]" },
+	{ String, .parent = SET(Interp),
+		.pattern = PATTERN_DQ },
+	{ Interp, .parent = ~SET(Escape),
+		.pattern = "[$]([!#$*?@-]|[_[:alnum:]]+|[{][^}]*[}])" },
+	{ String, .parent = ~SET(Escape),
+		.pattern = "[\\]." },
+	{ String, .subexp = 1, .newline = true, .pattern =
+		"<<-?" BL0 "'EOF'[^\n]*\n"
+		"(([^\n]|\n\t*[^E]|\n\t*E[^O]|\n\t*EO[^F]|\n\t*EOF[^\n])*)"
+		"\n\t*EOF\n" },
+	{ String, .parent = ~SET(String), .newline = true,
+		.pattern = "'[^']*'" },
+	{ Comment, .parent = ~SET(String), .subexp = 2,
+		.pattern = "(^|[[:blank:]]+)(#.*)" },
+	{ Todo, .parent = SET(Comment),
+		.pattern = PATTERN_TODO },
+};
+// }}}
+
+static const struct Language {
+	const char *name;
+	const char *pattern;
+	const struct Syntax *syntax;
+	size_t len;
+} Languages[] = {
+	{ "c",    "[.][chlmy]$", CSyntax, ARRAY_LEN(CSyntax) },
+	{ "diff", "[.](diff|patch)$", DiffSyntax, ARRAY_LEN(DiffSyntax) },
+	{ "make", "[.]mk$|^Makefile$", MakeSyntax, ARRAY_LEN(MakeSyntax) },
+	{ "mdoc", "[.][1-9]$", MdocSyntax, ARRAY_LEN(MdocSyntax) },
+	{ "rust", "[.]rs$", RustSyntax, ARRAY_LEN(RustSyntax) },
+	{ "sh",   "[.](sh|in)$|^[.](profile|shrc)$", ShSyntax, ARRAY_LEN(ShSyntax) },
+	{ "text", "[.]txt$", NULL, 0 },
+};
+
+static regex_t compile(const char *pattern, int flags) {
+	regex_t regex;
+	int error = regcomp(&regex, pattern, REG_EXTENDED | flags);
+	if (!error) return regex;
+	char buf[256];
+	regerror(error, &regex, buf, sizeof(buf));
+	errx(EX_SOFTWARE, "regcomp: %s: %s", buf, pattern);
+}
+
+enum { SubsLen = 8 };
+static void highlight(struct Language lang, enum Class *hi, const char *str) {
+	for (size_t i = 0; i < lang.len; ++i) {
+		struct Syntax syn = lang.syntax[i];
+		regex_t regex = compile(syn.pattern, syn.newline ? 0 : REG_NEWLINE);
+		assert(syn.subexp < SubsLen);
+		assert(syn.subexp <= regex.re_nsub);
+		regmatch_t subs[SubsLen] = {{0}};
+		for (size_t offset = 0; str[offset]; offset += subs[syn.subexp].rm_eo) {
+			int error = regexec(
+				&regex, &str[offset], SubsLen, subs, offset ? REG_NOTBOL : 0
+			);
+			if (error == REG_NOMATCH) break;
+			if (error) errx(EX_SOFTWARE, "regexec: %d", error);
+			regmatch_t *sub = &subs[syn.subexp];
+			if (syn.parent && !(syn.parent & SET(hi[offset + sub->rm_so]))) {
+				sub->rm_eo = sub->rm_so + 1;
+				continue;
+			}
+			for (regoff_t j = sub->rm_so; j < sub->rm_eo; ++j) {
+				hi[offset + j] = lang.syntax[i].class;
+			}
+		}
+		regfree(&regex);
+	}
+}
+
+static void check(void) {
+	for (size_t i = 0; i < ARRAY_LEN(Languages); ++i) {
+		regex_t regex = compile(Languages[i].pattern, REG_NOSUB);
+		regfree(&regex);
+		for (size_t j = 0; j < Languages[i].len; ++j) {
+			struct Syntax syn = Languages[i].syntax[j];
+			regex = compile(syn.pattern, 0);
+			if (syn.subexp >= SubsLen || syn.subexp > regex.re_nsub) {
+				errx(
+					EX_SOFTWARE, "subexpression %zu out of bounds: %s",
+					syn.subexp, syn.pattern
+				);
+			}
+			regfree(&regex);
+		}
+	}
+}
+
+#define ENUM_OPTION \
+	X(Anchor, "anchor") \
+	X(CSS, "css") \
+	X(Document, "document") \
+	X(Inline, "inline") \
+	X(Monospace, "monospace") \
+	X(Tab, "tab") \
+	X(Title, "title")
+
+enum Option {
+#define X(option, _) option,
+	ENUM_OPTION
+#undef X
+	OptionLen,
+};
+
+static const char *OptionKey[OptionLen + 1] = {
+#define X(option, key) [option] = key,
+	ENUM_OPTION
+#undef X
+	NULL,
+};
+
+typedef void HeaderFn(const char *opts[]);
+typedef void
+OutputFn(const char *opts[], enum Class class, const char *str, size_t len);
+
+// ANSI format {{{
+
+enum SGR {
+	SGRBoldOn = 1,
+	SGRUnderlineOn = 4,
+	SGRBoldOff = 22,
+	SGRUnderlineOff = 24,
+	SGRBlack = 30,
+	SGRRed,
+	SGRGreen,
+	SGRYellow,
+	SGRBlue,
+	SGRMagenta,
+	SGRCyan,
+	SGRWhite,
+	SGRDefault = 39,
+};
+
+static const enum SGR ANSIStyle[ClassLen][3] = {
+	[Normal]  = { SGRDefault },
+	[Keyword] = { SGRWhite },
+	[Macro]   = { SGRGreen },
+	[Tag]     = { SGRDefault, SGRUnderlineOn, SGRUnderlineOff },
+	[String]  = { SGRCyan },
+	[Escape]  = { SGRDefault },
+	[Format]  = { SGRCyan, SGRBoldOn, SGRBoldOff },
+	[Interp]  = { SGRYellow },
+	[Comment] = { SGRBlue },
+	[Todo]    = { SGRBlue, SGRBoldOn, SGRBoldOff },
+	[DiffOld] = { SGRRed },
+	[DiffNew] = { SGRGreen },
+};
+
+static void
+ansiOutput(const char *opts[], enum Class class, const char *str, size_t len) {
+	(void)opts;
+	if (ANSIStyle[class][1]) {
+		printf(
+			"\x1B[%d;%dm%.*s\x1B[%dm",
+			ANSIStyle[class][0], ANSIStyle[class][1],
+			(int)len, str,
+			ANSIStyle[class][2]
+		);
+	} else {
+		printf("\x1B[%dm%.*s", ANSIStyle[class][0], (int)len, str);
+	}
+}
+
+// }}}
+
+// IRC format {{{
+
+enum IRC {
+	IRCWhite,
+	IRCBlack,
+	IRCBlue,
+	IRCGreen,
+	IRCRed,
+	IRCBrown,
+	IRCMagenta,
+	IRCOrange,
+	IRCYellow,
+	IRCLightGreen,
+	IRCCyan,
+	IRCLightCyan,
+	IRCLightBlue,
+	IRCPink,
+	IRCGray,
+	IRCLightGray,
+	IRCBold = 0x02,
+	IRCColor = 0x03,
+	IRCMonospace = 0x11,
+	IRCUnderline = 0x1F,
+};
+
+static const enum IRC SGRIRC[] = {
+	[SGRBoldOn]  = IRCBold,
+	[SGRBoldOff] = IRCBold,
+	[SGRUnderlineOn]  = IRCUnderline,
+	[SGRUnderlineOff] = IRCUnderline,
+	[SGRBlack]   = IRCBlack,
+	[SGRRed]     = IRCRed,
+	[SGRGreen]   = IRCGreen,
+	[SGRYellow]  = IRCYellow,
+	[SGRBlue]    = IRCBlue,
+	[SGRMagenta] = IRCMagenta,
+	[SGRCyan]    = IRCCyan,
+	[SGRWhite]   = IRCGray,
+	[SGRDefault] = 0,
+};
+
+static void ircHeader(const char *opts[]) {
+	if (opts[Monospace]) printf("%c", IRCMonospace);
+}
+
+static void
+ircOutput(const char *opts[], enum Class class, const char *str, size_t len) {
+	char cc[3] = "";
+	if (ANSIStyle[class][0] != SGRDefault) {
+		snprintf(cc, sizeof(cc), "%d", SGRIRC[ANSIStyle[class][0]]);
+	}
+	// Prevent trailing formatting after newline ...
+	bool newline = (str[len - 1] == '\n');
+	if (ANSIStyle[class][1]) {
+		printf(
+			"%c%s%c%.*s%c%s",
+			IRCColor, cc, SGRIRC[ANSIStyle[class][1]],
+			(int)(newline ? len - 1 : len), str,
+			SGRIRC[ANSIStyle[class][2]],
+			(newline ? "\n" : "")
+		);
+	} else {
+		// Double-toggle bold to prevent str being interpreted as color.
+		printf("%c%s%c%c%.*s", IRCColor, cc, IRCBold, IRCBold, (int)len, str);
+	}
+	// ... except for monospace, at the beginning of each line.
+	if (newline && opts[Monospace]) printf("%c", IRCMonospace);
+}
+
+// }}}
+
+// HTML format {{{
+
+static void htmlEscape(const char *str, size_t len) {
+	while (len) {
+		size_t run = strcspn(str, "\"&<>");
+		if (run > len) run = len;
+		switch (str[0]) {
+			break; case '"': run = 1; printf("&quot;");
+			break; case '&': run = 1; printf("&amp;");
+			break; case '<': run = 1; printf("&lt;");
+			break; case '>': run = 1; printf("&gt;");
+			break; default:  printf("%.*s", (int)run, str);
+		}
+		str += run;
+		len -= run;
+	}
+}
+
+static const char *HTMLStyle[ClassLen] = {
+	[Keyword] = "color: dimgray;",
+	[Macro]   = "color: green;",
+	[Tag]     = "color: inherit; text-decoration: underline;",
+	[String]  = "color: teal;",
+	[Format]  = "color: teal; font-weight: bold;",
+	[Interp]  = "color: olive;",
+	[Comment] = "color: navy;",
+	[Todo]    = "color: navy; font-weight: bold;",
+	[DiffOld] = "color: red;",
+	[DiffNew] = "color: green;",
+};
+
+static void htmlTabSize(const char *tab) {
+	printf("-moz-tab-size: ");
+	htmlEscape(tab, strlen(tab));
+	printf("; tab-size: ");
+	htmlEscape(tab, strlen(tab));
+	printf(";");
+}
+
+static void htmlHeader(const char *opts[]) {
+	if (!opts[Document]) goto body;
+	printf("<!DOCTYPE html>\n<title>");
+	if (opts[Title]) htmlEscape(opts[Title], strlen(opts[Title]));
+	printf("</title>\n");
+	if (opts[CSS]) {
+		printf("<link rel=\"stylesheet\" href=\"");
+		htmlEscape(opts[CSS], strlen(opts[CSS]));
+		printf("\">\n");
+	} else if (!opts[Inline]) {
+		printf("<style>\n");
+		if (opts[Tab]) {
+			printf("pre.hi { ");
+			htmlTabSize(opts[Tab]);
+			printf(" }\n");
+		}
+		for (enum Class class = 0; class < ClassLen; ++class) {
+			if (!HTMLStyle[class]) continue;
+			printf(".hi.%s { %s }\n", ClassName[class], HTMLStyle[class]);
+		}
+		printf(
+			".hi.%s:target { color: goldenrod; outline: none; }\n",
+			ClassName[Tag]
+		);
+		printf("</style>\n");
+	}
+body:
+	if (opts[Inline] && opts[Tab]) {
+		printf("<pre class=\"hi\" style=\"");
+		htmlTabSize(opts[Tab]);
+		printf("\">");
+	} else {
+		printf("<pre class=\"hi\">");
+	}
+}
+
+static void htmlFooter(const char *opts[]) {
+	(void)opts;
+	printf("</pre>\n");
+}
+
+static void htmlAnchor(const char *opts[], const char *str, size_t len) {
+	if (opts[Inline]) {
+		printf("<a style=\"%s\" id=\"", HTMLStyle[Tag] ? HTMLStyle[Tag] : "");
+	} else {
+		printf("<a class=\"hi %s\" id=\"", ClassName[Tag]);
+	}
+	htmlEscape(str, len);
+	printf("\" href=\"#");
+	htmlEscape(str, len);
+	printf("\">");
+	htmlEscape(str, len);
+	printf("</a>");
+}
+
+static void
+htmlOutput(const char *opts[], enum Class class, const char *str, size_t len) {
+	if (opts[Anchor] && class == Tag) {
+		htmlAnchor(opts, str, len);
+		return;
+	}
+	if (opts[Inline]) {
+		printf("<span style=\"%s\">", HTMLStyle[class] ? HTMLStyle[class] : "");
+	} else {
+		printf("<span class=\"hi %s\">", ClassName[class]);
+	}
+	htmlEscape(str, len);
+	printf("</span>");
+}
+
+// }}}
+
+// Debug format {{{
+static void
+debugOutput(const char *opts[], enum Class class, const char *str, size_t len) {
+	(void)opts;
+	printf("%s\t\"", ClassName[class]);
+	while (len) {
+		size_t run = strcspn(str, "\t\n\"\\");
+		if (run > len) run = len;
+		switch (str[0]) {
+			break; case '\t': run = 1; printf("\\t");
+			break; case '\n': run = 1; printf("\\n");
+			break; case '"':  run = 1; printf("\\\"");
+			break; case '\\': run = 1; printf("\\\\");
+			break; default:   printf("%.*s", (int)run, str);
+		}
+		str += run;
+		len -= run;
+	}
+	printf("\"\n");
+}
+// }}}
+
+static const struct Format {
+	const char *name;
+	OutputFn *output;
+	HeaderFn *header;
+	HeaderFn *footer;
+} Formats[] = {
+	{ "ansi",  ansiOutput, NULL, NULL },
+	{ "irc",   ircOutput, ircHeader, NULL },
+	{ "html",  htmlOutput, htmlHeader, htmlFooter },
+	{ "debug", debugOutput, NULL, NULL },
+};
+
+static bool findLanguage(struct Language *lang, const char *name) {
+	for (size_t i = 0; i < ARRAY_LEN(Languages); ++i) {
+		if (strcmp(name, Languages[i].name)) continue;
+		*lang = Languages[i];
+		return true;
+	}
+	return false;
+}
+
+static bool matchLanguage(struct Language *lang, const char *name) {
+	for (size_t i = 0; i < ARRAY_LEN(Languages); ++i) {
+		regex_t regex = compile(Languages[i].pattern, REG_NOSUB);
+		int error = regexec(&regex, name, 0, NULL, 0);
+		regfree(&regex);
+		if (error == REG_NOMATCH) continue;
+		if (error) errx(EX_SOFTWARE, "regexec: %d", error);
+		*lang = Languages[i];
+		return true;
+	}
+	return false;
+}
+
+static bool findFormat(struct Format *format, const char *name) {
+	for (size_t i = 0; i < ARRAY_LEN(Formats); ++i) {
+		if (strcmp(name, Formats[i].name)) continue;
+		*format = Formats[i];
+		return true;
+	}
+	return false;
+}
+
+int main(int argc, char *argv[]) {
+	setlocale(LC_CTYPE, "");
+
+	bool text = false;
+	const char *name = NULL;
+	struct Language lang = {0};
+	struct Format format = Formats[0];
+	const char *opts[OptionLen] = {0};
+
+	int opt;
+	while (0 < (opt = getopt(argc, argv, "cf:l:n:o:t"))) {
+		switch (opt) {
+			break; case 'c': check(); return EX_OK;
+			break; case 'f': {
+				if (!findFormat(&format, optarg)) {
+					errx(EX_USAGE, "no such format %s", optarg);
+				}
+			}
+			break; case 'l': {
+				if (!findLanguage(&lang, optarg)) {
+					errx(EX_USAGE, "no such language %s", optarg);
+				}
+			}
+			break; case 'n': name = optarg;
+			break; case 'o': {
+				char *val;
+				enum Option key;
+				while (optarg[0]) {
+					key = getsubopt(&optarg, (char *const *)OptionKey, &val);
+					if (key >= OptionLen) {
+						errx(EX_USAGE, "no such option %s", val);
+					}
+					opts[key] = (val ? val : "");
+				}
+			}
+			break; case 't': text = true;
+			break; default: return EX_USAGE;
+		}
+	}
+
+	const char *path = "(stdin)";
+	FILE *file = stdin;
+	if (optind < argc) {
+		path = argv[optind];
+		file = fopen(path, "r");
+		if (!file) err(EX_NOINPUT, "%s", path);
+	}
+
+	if (!name) {
+		name = strrchr(path, '/');
+		name = (name ? &name[1] : path);
+	}
+	if (!lang.name && !matchLanguage(&lang, name) && !text) {
+		errx(EX_USAGE, "cannot infer language for %s", name);
+	}
+	if (!opts[Title]) opts[Title] = name;
+
+	struct stat stat;
+	int error = fstat(fileno(file), &stat);
+	if (error) err(EX_IOERR, "fstat");
+
+	size_t cap = (stat.st_mode & S_IFREG ? stat.st_size + 1 : 4096);
+	char *str = malloc(cap);
+	if (!str) err(EX_OSERR, "malloc");
+
+	size_t len = 0, read;
+	while (0 < (read = fread(&str[len], 1, cap - len - 1, file))) {
+		len += read;
+		if (len + 1 < cap) continue;
+		cap *= 2;
+		str = realloc(str, cap);
+		if (!str) err(EX_OSERR, "realloc");
+	}
+	if (ferror(file)) err(EX_IOERR, "fread");
+	if (memchr(str, 0, len)) errx(EX_DATAERR, "input is binary");
+	str[len] = '\0';
+
+	enum Class *hi = calloc(len, sizeof(*hi));
+	if (!hi) err(EX_OSERR, "calloc");
+
+	highlight(lang, hi, str);
+
+	size_t run = 0;
+	if (format.header) format.header(opts);
+	for (size_t i = 0; i < len; i += run) {
+		for (run = 1; i + run < len; ++run) {
+			if (hi[i + run] != hi[i]) break;
+			if (str[i + run - 1] == '\n') break;
+		}
+		format.output(opts, hi[i], &str[i], run);
+	}
+	if (format.footer) format.footer(opts);
+}
diff --git a/bin/hnel.c b/bin/hnel.c
new file mode 100644
index 00000000..4a869c2d
--- /dev/null
+++ b/bin/hnel.c
@@ -0,0 +1,120 @@
+/* Copyright (C) 2017  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <unistd.h>
+
+#if defined __FreeBSD__
+#include <libutil.h>
+#elif defined __linux__
+#include <pty.h>
+#else
+#include <util.h>
+#endif
+
+typedef unsigned char byte;
+
+static struct termios saveTerm;
+static void restoreTerm(void) {
+	tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm);
+}
+
+int main(int argc, char *argv[]) {
+	int error;
+
+	if (argc < 4) return EX_USAGE;
+
+	byte table[256] = {0};
+	if (strlen(argv[1]) != strlen(argv[2])) return EX_USAGE;
+	for (const char *from = argv[1], *to = argv[2]; *from; ++from, ++to) {
+		table[(byte)*from] = *to;
+	}
+
+	error = tcgetattr(STDERR_FILENO, &saveTerm);
+	if (error) err(EX_IOERR, "tcgetattr");
+	atexit(restoreTerm);
+
+	struct termios raw = saveTerm;
+	cfmakeraw(&raw);
+	error = tcsetattr(STDERR_FILENO, TCSADRAIN, &raw);
+	if (error) err(EX_IOERR, "tcsetattr");
+
+	struct winsize window;
+	error = ioctl(STDERR_FILENO, TIOCGWINSZ, &window);
+	if (error) err(EX_IOERR, "TIOCGWINSZ");
+
+	int pty;
+	pid_t pid = forkpty(&pty, NULL, NULL, &window);
+	if (pid < 0) err(EX_OSERR, "forkpty");
+
+	if (!pid) {
+		execvp(argv[3], &argv[3]);
+		err(EX_NOINPUT, "%s", argv[3]);
+	}
+
+	bool enable = true;
+
+	byte buf[4096];
+	struct pollfd fds[2] = {
+		{ .fd = STDIN_FILENO, .events = POLLIN },
+		{ .fd = pty, .events = POLLIN },
+	};
+	while (0 < poll(fds, 2, -1)) {
+		if (fds[0].revents & POLLIN) {
+			ssize_t readSize = read(STDIN_FILENO, buf, sizeof(buf));
+			if (readSize < 0) err(EX_IOERR, "read(%d)", STDIN_FILENO);
+
+			if (readSize == 1) {
+				if (buf[0] == CTRL('S')) {
+					enable ^= true;
+					continue;
+				}
+
+				byte c = buf[0];
+				if (enable && table[c]) buf[0] = table[c];
+			}
+
+			ssize_t writeSize = write(pty, buf, readSize);
+			if (writeSize < 0) err(EX_IOERR, "write(%d)", pty);
+			if (writeSize < readSize) errx(EX_IOERR, "short write(%d)", pty);
+		}
+
+		if (fds[1].revents & POLLIN) {
+			ssize_t readSize = read(pty, buf, sizeof(buf));
+			if (readSize < 0) err(EX_IOERR, "read(%d)", pty);
+
+			ssize_t writeSize = write(STDOUT_FILENO, buf, readSize);
+			if (writeSize < 0) err(EX_IOERR, "write(%d)", STDOUT_FILENO);
+			if (writeSize < readSize) {
+				errx(EX_IOERR, "short write(%d)", STDOUT_FILENO);
+			}
+		}
+
+		int status;
+		pid_t dead = waitpid(pid, &status, WNOHANG);
+		if (dead < 0) err(EX_OSERR, "waitpid(%d)", pid);
+		if (dead) return WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE;
+	}
+	err(EX_IOERR, "poll");
+}
diff --git a/bin/html.sh b/bin/html.sh
new file mode 100644
index 00000000..e96a7210
--- /dev/null
+++ b/bin/html.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+set -eu
+
+readonly GitURL='https://git.causal.agency/src/tree/bin'
+
+src=$1
+man=${2:-}
+
+cat <<EOF
+<!DOCTYPE html>
+<title>${src}</title>
+<style>
+$(./scheme -st)
+html {
+	font-family: monospace;
+	color: var(--ansi17);
+	background-color: var(--ansi16);
+}
+a { color: var(--ansi4); }
+a:visited { color: var(--ansi5); }
+pre.hi {
+	-moz-tab-size: 4;
+	tab-size: 4;
+}
+.hi.Keyword { color: var(--ansi7); }
+.hi.Macro { color: var(--ansi2); }
+.hi.Tag { color: inherit; text-decoration: underline; }
+.hi.Tag:target { color: var(--ansi11); outline: none; }
+.hi.String { color: var(--ansi6); }
+.hi.Format { color: var(--ansi14); }
+.hi.Interp { color: var(--ansi3); }
+.hi.Comment { color: var(--ansi4); }
+.hi.Todo { color: var(--ansi12); }
+.hi.DiffOld { color: var(--ansi1); }
+.hi.DiffNew { color: var(--ansi2); }
+</style>
+<a href="${GitURL}/${src}">${src} in git</a>
+EOF
+
+[ -f "$man" ] && man -P cat "${PWD}/${man}" | ./ttpre
+./hi -t -f html -o anchor "$src"
diff --git a/bin/man1/beef.1 b/bin/man1/beef.1
new file mode 100644
index 00000000..ea52cfa0
--- /dev/null
+++ b/bin/man1/beef.1
@@ -0,0 +1,91 @@
+.Dd August 28, 2019
+.Dt BEEF 1
+.Os
+.
+.Sh NAME
+.Nm beef
+.Nd Befunge-93 interpreter
+.
+.Sh SYNOPSIS
+.Nm
+.Op Ar file
+.
+.Sh DESCRIPTION
+.Nm
+is a Befunge-93 interpreter.
+If no
+.Ar file
+is provided,
+the program is read from standard input.
+.
+.Ss Befunge-93 Command Summary
+.Bl -tag -width "0-9" -compact
+.It \(dq
+toggle string mode
+.It 0-9
+push value
+.It +
+add
+.It -
+subtract
+.It *
+multiply
+.It /
+divide
+.It %
+modulo
+.It !
+not
+.It `
+greater than
+.It >
+right
+.It <
+left
+.It ^
+up
+.It v
+down
+.It ?
+random
+.It _
+horizontal (left) if
+.It |
+vertical (up) if
+.It :
+duplicate
+.It \e
+swap
+.It $
+drop
+.It .
+output integer
+.It ,
+output ASCII
+.It #
+bridge
+.It g
+get (y, x)
+.It p
+put (y, x) = v
+.It &
+input integer
+.It ~
+input ASCII
+.It @
+exit
+.El
+.
+.Sh EXIT STATUS
+.Nm
+exits with the top value left on the stack,
+or 0 if the stack is left empty.
+.
+.Sh STANDARDS
+.Rs
+.%A Chris Pressey
+.%Q Cat's Eye Technologies
+.%T Befunge-93
+.%D September, 1993
+.%U https://github.com/catseye/Befunge-93/blob/master/doc/Befunge-93.markdown
+.Re
diff --git a/bin/man1/bibsort.1 b/bin/man1/bibsort.1
new file mode 100644
index 00000000..9d036920
--- /dev/null
+++ b/bin/man1/bibsort.1
@@ -0,0 +1,37 @@
+.Dd December 15, 2020
+.Dt BIBSORT 1
+.Os
+.
+.Sh NAME
+.Nm bibsort
+.Nd reformat bibliography
+.
+.Sh SYNOPSIS
+.Nm
+.Op Ar file
+.
+.Sh DESCRIPTION
+.Nm
+reformats on standard output
+the
+.Em STANDARDS
+section of the
+.Xr mdoc 7
+manual page
+.Ar file
+or standard input.
+Bibliographic references
+are sorted by author last names,
+and formatted in an item list
+with macro lines appearing
+in the order they are formatted by
+.Xr mandoc 1 .
+Additionally,
+.Ic \&%N
+macros referencing RFC numbers
+are rewritten to
+.Ic \&%R
+macros.
+.
+.Sh EXAMPLES
+.Dl :%!bibsort
diff --git a/bin/man1/bit.1 b/bin/man1/bit.1
new file mode 100644
index 00000000..b61bc704
--- /dev/null
+++ b/bin/man1/bit.1
@@ -0,0 +1,45 @@
+.Dd June 7, 2019
+.Dt BIT 1
+.Os
+.
+.Sh NAME
+.Nm bit
+.Nd a calculator
+.
+.Sh SYNOPSIS
+.Nm
+.
+.Sh DESCRIPTION
+.Nm
+is an integer calculator.
+Its syntax resembles that of C expressions,
+with the following changes:
+.
+.Bl -bullet
+.It
+Underscores are allowed in integer literals.
+.It
+The
+.Sy 0b
+prefix is used for binary literals.
+.It
+The
+.Sy ->
+operator is used for arithmetic shift.
+.It
+The postfix operators
+.Sy K ,
+.Sy M ,
+.Sy G ,
+.Sy T
+are used as constant multipliers.
+.It
+Single-letter (lower case) variables
+can be assigned.
+The variable
+.Sy _
+stores the previous result.
+.El
+.
+.Sh SEE ALSO
+.Xr operator 7
diff --git a/bin/man1/bri.1 b/bin/man1/bri.1
new file mode 100644
index 00000000..54a02322
--- /dev/null
+++ b/bin/man1/bri.1
@@ -0,0 +1,44 @@
+.Dd September 7, 2018
+.Dt BRI 1
+.Os
+.
+.Sh NAME
+.Nm bri
+.Nd backlight brightness control
+.
+.Sh SYNOPSIS
+.Nm
+.Op Ar brightness
+.Nm
+.Cm +
+.Nm
+.Cm -
+.
+.Sh DESCRIPTION
+.Nm
+controls the backlight brightness on Linux.
+.
+.Pp
+With no argument,
+the current brightness is printed.
+With a numerical
+.Ar brightness
+argument,
+the brightness is set.
+.
+.Pp
+The
+.Cm +
+and
+.Cm -
+arguments
+may be repeated any number of times
+and adjust the brightness
+in increments of 16.
+.
+.Sh FILES
+.Bl -tag
+.It Pa /sys/class/backlight
+Files under the first subdirectory found
+are used to control the backlight brightness.
+.El
diff --git a/bin/man1/c.1 b/bin/man1/c.1
new file mode 100644
index 00000000..3ae10945
--- /dev/null
+++ b/bin/man1/c.1
@@ -0,0 +1,40 @@
+.Dd May 31, 2020
+.Dt C 1
+.Os
+.
+.Sh NAME
+.Nm c
+.Nd run C
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl e Ar expr
+.Op Fl i Ar include
+.Op Ar stmts ...
+.
+.Sh DESCRIPTION
+The
+.Nm
+utility compiles and runs
+C statements wrapped in
+.Fn main
+with common includes.
+If no
+.Ar expr
+or
+.Ar stmts
+are provided,
+statements are read from standard input.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl e Ar expr
+Print the result of the C expression
+.Ar expr
+after executing
+.Ar stmts .
+.It Fl i Ar include
+Add the include file
+.Ar include .
+.El
diff --git a/bin/man1/dtch.1 b/bin/man1/dtch.1
new file mode 100644
index 00000000..e27713e1
--- /dev/null
+++ b/bin/man1/dtch.1
@@ -0,0 +1,67 @@
+.Dd August 12, 2019
+.Dt DTCH 1
+.Os
+.
+.Sh NAME
+.Nm dtch
+.Nd detached sessions
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl s
+.Ar name
+.Op Ar command ...
+.Nm
+.Fl a
+.Ar name
+.
+.Sh DESCRIPTION
+.Nm
+spawns a
+.Ar command
+in a detachable session.
+If no
+.Ar command
+is given,
+the value of
+.Ev SHELL
+is used.
+The
+.Nm
+process
+should be run as a background job
+or with
+.Xr nohup 1 .
+.
+.Pp
+To attach to an existing session,
+pass the
+.Fl a
+flag.
+To detach from the session,
+type
+.Ic ^Q .
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Attach to an existing session.
+.It Fl s
+Sink the output of
+.Ar command
+while detached.
+.El
+.
+.Sh FILES
+.Bl -tag -width Ds
+.It Pa ~/.dtch
+Directory of UNIX-domain sockets
+for each session.
+.El
+.
+.Sh EXAMPLES
+.Bd -literal -offset indent
+dtch foo vim &
+dtch -a foo
+.Ed
diff --git a/bin/man1/ever.1 b/bin/man1/ever.1
new file mode 100644
index 00000000..7689c5fb
--- /dev/null
+++ b/bin/man1/ever.1
@@ -0,0 +1,49 @@
+.Dd June  1, 2020
+.Dt EVER 1
+.Os
+.
+.Sh NAME
+.Nm ever
+.Nd watch files
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl i
+.Ar
+.Ar command
+.Nm
+.Op Fl i
+.Ar
+.Fl -
+.Ar command
+.Op Ar argument ...
+.
+.Sh DESCRIPTION
+.Nm
+executes the
+.Ar command
+whenever
+.Ar file
+is modified.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl i
+Attach the
+.Ar file
+which was modified
+to the standard input of
+.Ar command .
+.El
+.
+.Sh EXAMPLES
+.Dl ever ever.c make
+.Dl ever when.y ever.c -- make when ever
+.Dl ever -i ever.1 mandoc
+.
+.Sh CAVEATS
+.Nm
+does not support Linux
+since it uses
+.Xr kqueue 2 .
diff --git a/bin/man1/fbatt.1 b/bin/man1/fbatt.1
new file mode 100644
index 00000000..2d30cba7
--- /dev/null
+++ b/bin/man1/fbatt.1
@@ -0,0 +1,34 @@
+.Dd September 7, 2018
+.Dt FBATT 1
+.Os
+.
+.Sh NAME
+.Nm fbatt
+.Nd framebuffer battery indicator
+.
+.Sh SYNOPSIS
+.Nm
+.
+.Sh DESCRIPTION
+.Nm
+displays a battery charge indicator
+in the top-right corner
+of the Linux framebuffer.
+.
+.Sh ENVIRONMENT
+.Bl -tag
+.It Ev FRAMEBUFFER
+The framebuffer device path.
+.El
+.
+.Sh FILES
+.Bl -tag
+.It Pa /dev/fb0
+The default framebuffer device path.
+.It Pa /sys/class/power_supply
+The first subdirectory containing
+.Pa charge_full
+and
+.Pa charge_now
+is used to read the battery charge.
+.El
diff --git a/bin/man1/fbclock.1 b/bin/man1/fbclock.1
new file mode 100644
index 00000000..3195eb42
--- /dev/null
+++ b/bin/man1/fbclock.1
@@ -0,0 +1,36 @@
+.Dd September 7, 2018
+.Dt FBCLOCK 1
+.Os
+.
+.Sh NAME
+.Nm fbclock
+.Nd framebuffer clock
+.
+.Sh SYNOPSIS
+.Nm
+.
+.Sh DESCRIPTION
+.Nm
+displays a clock
+in the top-right corner
+of the Linux framebuffer.
+.
+.Sh ENVIRONMENT
+.Bl -tag
+.It Ev FONT
+Path to gzipped PSF file.
+.
+.It Ev FRAMEBUFFER
+The framebuffer device path.
+.El
+.
+.Sh FILES
+.Bl -tag
+.It Pa /dev/fb0
+The default framebuffer device path.
+.It Pa /usr/share/kbd/consolefonts/Lat2-Terminus16.psfu.gz
+The default font path.
+.El
+.
+.Sh SEE ALSO
+.Xr setfont 8
diff --git a/bin/man1/glitch.1 b/bin/man1/glitch.1
new file mode 100644
index 00000000..6562c4dc
--- /dev/null
+++ b/bin/man1/glitch.1
@@ -0,0 +1,77 @@
+.Dd September 7, 2018
+.Dt GLITCH 1
+.Os
+.
+.Sh NAME
+.Nm glitch
+.Nd PNG glitcher
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl cfimprxy
+.Op Fl a Ar filters
+.Op Fl d Ar filters
+.Op Fl o Ar file
+.Op Ar
+.
+.Sh DESCRIPTION
+.Nm
+misinterprets PNG files
+according to the options given
+to create natural glitch effects.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl a Ar filters
+Apply a pattern of comma-separated filters.
+Filters are
+.Cm none ,
+.Cm sub ,
+.Cm up ,
+.Cm average ,
+.Cm paeth .
+.
+.It Fl c
+Write to standard output.
+.
+.It Fl d Ar filters
+Declare a pattern of comma-separated filters.
+See
+.Fl a
+for list of filters.
+.
+.It Fl f
+Apply filtering in place of reconstruction.
+.
+.It Fl i
+Invert image data after filtering.
+.
+.It Fl m
+Mirror scanlines after filtering.
+.
+.It Fl o Ar file
+Write to
+.Ar file .
+.
+.It Fl p
+Use a broken Paeth predictor function.
+.
+.It Fl r
+Apply reconstruction in place of filtering.
+.
+.It Fl x
+Zero first pixel of each scanline after filtering.
+.
+.It Fl y
+Zero first scanline after filtering.
+.El
+.
+.Sh EXAMPLES
+.Dl glitch -m -a sub -d sub
+.
+.Sh SEE ALSO
+.Xr pngo 1
+.
+.Sh BUGS
+More wanted.
diff --git a/bin/man1/hi.1 b/bin/man1/hi.1
new file mode 100644
index 00000000..517fbab9
--- /dev/null
+++ b/bin/man1/hi.1
@@ -0,0 +1,173 @@
+.Dd December 15, 2019
+.Dt HI 1
+.Os
+.
+.Sh NAME
+.Nm hi
+.Nd syntax highlighter
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl t
+.Op Fl f Ar format
+.Op Fl l Ar lang
+.Op Fl n Ar name
+.Op Fl o Ar opts
+.Op Ar file
+.Nm
+.Fl c
+.
+.Sh DESCRIPTION
+.Nm
+highlights the contents of a
+.Ar file
+or standard input
+and formats it
+on standard output.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width "-f format"
+.It Fl c
+Compile all regular expressions and exit.
+.It Fl f Ar format
+Set the output format.
+.It Fl l Ar lang
+Set the input language.
+.It Fl n Ar name
+Override the name used
+to infer the input language.
+.It Fl o Ar opts
+Set output format options.
+.Ar opts
+is a comma-separated list of options.
+.It Fl t
+Default to
+.Cm text
+if the input language cannot be inferred.
+.El
+.
+.Ss Output Formats
+The default output format is
+.Cm ansi .
+.
+.Bl -tag -width Ds
+.It Cm ansi
+Output ANSI terminal escape codes.
+.
+.It Cm irc
+Output IRC formatting codes.
+.Pp
+The options are as follows:
+.Bl -tag -width "monospace"
+.It Cm monospace
+Use the monospace formatting code
+introduced by IRCCloud.
+.El
+.
+.It Cm html
+Output HTML
+.Sy <pre>
+with
+.Sy <span>
+classes.
+.
+.Pp
+The options are as follows:
+.Bl -tag -width "title=..."
+.It Cm anchor
+Output tags
+(top-level definition names)
+as anchor links.
+.
+.It Cm css Ns = Ns Ar url
+With
+.Cm document ,
+output a
+.Sy <link>
+element for the external stylesheet
+.Ar url .
+If unset,
+output default styles in a
+.Sy <style>
+element.
+.
+.It Cm document
+Output an HTML document.
+.
+.It Cm inline
+Output inline
+.Sy style
+attributes rather than classes.
+.
+.It Cm tab Ns = Ns Ar n
+With
+.Cm document
+or
+.Cm inline ,
+set the
+.Sy tab-size
+property to
+.Ar n .
+.
+.It Cm title Ns = Ns Ar ...
+With
+.Cm document ,
+set the
+.Sy <title>
+element text.
+The default title is the
+.Ar file
+name.
+.El
+.El
+.
+.Ss Input Languages
+If no input language is set with
+.Fl l ,
+it may be inferred from the name set by
+.Fl m
+or from the provided
+.Ar file
+name.
+.
+.Bl -tag -width Ds
+.It Cm c
+The C11 language.
+.
+.It Cm diff
+The output of
+.Xr diff 1
+with the
+.Fl u
+flag.
+.
+.It Cm make
+The portable subset of
+.Xr make 1 .
+Variable substitution supports
+one level of nesting with the same delimiter.
+.
+.It Cm mdoc
+The
+.Xr mdoc 7
+language.
+.
+.It Cm rust
+The Rust 2018 language.
+Nested raw strings and block comments
+are not highlighted correctly.
+.
+.It Cm sh
+The POSIX
+.Xr sh 1
+language.
+Here-documents are correctly highlighted
+only with a delimiter of
+.Ql EOF .
+Arbitrarily nested strings and command substitutions
+are not highlighted correctly.
+.
+.It Cm text
+Plain text.
+.El
diff --git a/bin/man1/hnel.1 b/bin/man1/hnel.1
new file mode 100644
index 00000000..82305be8
--- /dev/null
+++ b/bin/man1/hnel.1
@@ -0,0 +1,36 @@
+.Dd July 8, 2019
+.Dt HNEL 1
+.Os
+.
+.Sh NAME
+.Nm hnel
+.Nd PTY input remapper
+.
+.Sh SYNOPSIS
+.Nm
+.Ar string1
+.Ar string2
+.Ar command ...
+.
+.Sh DESCRIPTION
+.Nm
+runs the
+.Ar command
+in a new PTY,
+remapping input characters in
+.Ar string1
+to the corresponding characters in
+.Ar string2 .
+Type
+.Ic ^S
+to toggle remapping.
+.
+.Sh EXAMPLES
+.Dl hnel '[]{}' '{}[]' vim
+.
+.Sh SEE ALSO
+.Xr tr 1
+.
+.Sh BUGS
+Window size changes are not propagated
+to the child PTY.
diff --git a/bin/man1/modem.1 b/bin/man1/modem.1
new file mode 100644
index 00000000..a4bbc3f1
--- /dev/null
+++ b/bin/man1/modem.1
@@ -0,0 +1,31 @@
+.Dd December  8, 2020
+.Dt MODEM 1
+.Os
+.
+.Sh NAME
+.Nm modem
+.Nd fixed baud rate wrapper
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl r Ar rate
+.Ar command ...
+.
+.Sh DESCRIPTION
+.Nm
+runs the
+.Ar command
+in a new PTY
+with a fixed baud rate.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl r Ar rate
+Set the baud rate.
+The default is 19200.
+.El
+.
+.Sh BUGS
+Window size changes are not propagated
+to the child PTY.
diff --git a/bin/man1/nudge.1 b/bin/man1/nudge.1
new file mode 100644
index 00000000..3ca4294a
--- /dev/null
+++ b/bin/man1/nudge.1
@@ -0,0 +1,44 @@
+.Dd September  4, 2020
+.Dt NUDGE 1
+.Os
+.
+.Sh NAME
+.Nm nudge
+.Nd terminal vibrator
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl f Ar file
+.Op Fl n Ar count
+.Op Fl s Ar shake
+.Op Fl t Ar delay
+.
+.Sh DESCRIPTION
+The
+.Nm
+utility
+nudges the terminal.
+An
+.Xr xterm 1
+compatible terminal is required.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl f Ar file
+Open the terminal named by
+.Ar file .
+The default is
+.Pa /dev/tty .
+.It Fl n Ar count
+Set the number of times
+to nudge the terminal.
+The default is 2.
+.It Fl s Ar shake
+Set the shake intensity in pixels.
+The default is 10.
+.It Fl t Ar delay
+Set the interval between shakes
+in milliseconds.
+The default is 20.
+.El
diff --git a/bin/man1/order.1 b/bin/man1/order.1
new file mode 100644
index 00000000..8e0f9661
--- /dev/null
+++ b/bin/man1/order.1
@@ -0,0 +1,38 @@
+.Dd July 18, 2020
+.Dt ORDER 1
+.Os
+.
+.Sh NAME
+.Nm order
+.Nd operator precedence
+.
+.Sh SYNOPSIS
+.Nm
+.Op Ar expr ...
+.
+.Sh DESCRIPTION
+.Nm
+parses C expressions
+and prints them with parentheses
+according to the precedence rules in
+.Xr operator 7 .
+If no
+.Ar expr
+are given,
+each line of standard input
+is parsed and printed.
+.
+.Sh EXAMPLES
+.Bd -literal
+$ order 'a & b << 1'
+(a & (b << 1))
+.Ed
+.
+.Sh SEE ALSO
+.Xr operator 7
+.
+.Sh CAVEATS
+.Nm
+does not support the
+.Sy (type)
+operator.
diff --git a/bin/man1/pbd.1 b/bin/man1/pbd.1
new file mode 100644
index 00000000..bbc7b785
--- /dev/null
+++ b/bin/man1/pbd.1
@@ -0,0 +1,59 @@
+.Dd September 7, 2018
+.Dt PBD 1
+.Os
+.
+.Sh NAME
+.Nm pbd ,
+.Nm pbcopy ,
+.Nm pbpaste ,
+.Nm open
+.Nd macOS pasteboard daemon
+.
+.Sh SYNOPSIS
+.Nm
+.Nm pbcopy
+.Nm pbpaste
+.Nm open
+.Ar
+.
+.Sh DESCRIPTION
+.Nm
+is a daemon which pipes into macOS
+.Xr pbcopy 1 ,
+from
+.Xr pbpaste 1
+and invokes
+.Xr open 1
+in response to messages
+sent over TCP port 7062.
+.
+.Pp
+The socket can be forwarded through
+.Xr ssh 1
+and the stub implementations of
+.Nm pbcopy ,
+.Nm pbpaste
+and
+.Nm open
+can be used remotely
+to access the local pasteboard
+and open URLs.
+.
+.Pp
+Forwarding can be configured with:
+.Pp
+.Dl RemoteForward 7062 127.0.0.1:7062
+.
+.Sh EXAMPLES
+.Bd -literal -offset indent
+pbd &
+ssh -R 7062:127.0.0.1:7062 tux.local
+pbpaste
+.Ed
+.
+.Sh SEE ALSO
+.Xr open 1 ,
+.Xr pbcopy 1 ,
+.Xr pbpaste 1 ,
+.Xr ssh 1 ,
+.Xr ssh_config 5
diff --git a/bin/man1/pngo.1 b/bin/man1/pngo.1
new file mode 100644
index 00000000..cec13160
--- /dev/null
+++ b/bin/man1/pngo.1
@@ -0,0 +1,56 @@
+.Dd September 17, 2018
+.Dt PNGO 1
+.Os
+.
+.Sh NAME
+.Nm pngo
+.Nd PNG optimizer
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl cv
+.Op Fl o Ar file
+.Op Ar
+.
+.Sh DESCRIPTION
+.Nm
+optimizes PNG files for size.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl c
+Write to standard output.
+.It Fl o Ar file
+Write to
+.Ar file .
+.It Fl v
+Output PNG header information.
+.El
+.
+.Pp
+.Nm
+performs the following optimizations:
+.Bl -bullet
+.It
+Discard ancillary chunks.
+.It
+Discard unnecessary alpha channel.
+.It
+Convert unnecessary truecolor to grayscale.
+.It
+Palletize color and alpha if possible.
+.It
+Reduce bit depth if possible.
+.It
+Apply a simple filter heuristic.
+.It
+Apply zlib's best compresion.
+.El
+.
+.Sh SEE ALSO
+.Xr glitch 1
+.
+.Sh CAVEATS
+.Nm
+does not support interlaced PNGs.
diff --git a/bin/man1/psf2png.1 b/bin/man1/psf2png.1
new file mode 100644
index 00000000..db74c6e2
--- /dev/null
+++ b/bin/man1/psf2png.1
@@ -0,0 +1,53 @@
+.Dd September 28, 2018
+.Dt PSF2PNG 1
+.Os
+.
+.Sh NAME
+.Nm psf2png
+.Nd PSF2 to PNG renderer
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl b Ar bg
+.Op Fl c Ar cols
+.Op Fl f Ar fg
+.Op Fl s Ar str
+.Op Ar file
+.
+.Sh DESCRIPTION
+.Nm
+renders the PSF2 font
+.Ar file
+or standard input
+to PNG
+on standard output.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl b Ar bg
+Use
+.Ar bg
+(hexadecimal RGB)
+as background color.
+The default background color is black.
+.It Fl c Ar cols
+Arrange glyphs in
+.Ar cols
+columns.
+The default number of columns is 32.
+.It Fl f Ar fg
+Use
+.Ar fg
+(hexadecimal RGB)
+as foreground color.
+The default foreground color is white.
+.It Fl s Ar str
+Render glyphs for string
+.Ar str
+rather than all glyphs.
+.El
+.
+.Sh SEE ALSO
+.Xr pngo 1 ,
+.Xr psfed 1
diff --git a/bin/man1/psfed.1 b/bin/man1/psfed.1
new file mode 100644
index 00000000..3fbc4710
--- /dev/null
+++ b/bin/man1/psfed.1
@@ -0,0 +1,166 @@
+.Dd January 14, 2019
+.Dt PSFED 1
+.Os
+.
+.Sh NAME
+.Nm psfed
+.Nd PSF2 font editor
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl H Ar height
+.Op Fl g Ar glyphs
+.Op Fl h Ar height
+.Op Fl w Ar width
+.Ar file
+.
+.Sh DESCRIPTION
+.Nm
+is a PSF2 font editor
+for the Linux framebuffer.
+.
+.Pp
+The arguments are as follows:
+.
+.Bl -tag -width Ds
+.It Fl H Ar height
+Modify the height of an existing font.
+Only increasing the height is allowed.
+.
+.It Fl g Ar glyphs
+Set the number of glyphs in a new font.
+The default number of glyphs is 256.
+.
+.It Fl h Ar height
+Set the height of a new font.
+The default height is 16.
+.
+.It Fl w Ar width
+Set the width of a new font.
+The default width is 8.
+.El
+.
+.Ss Normal Mode
+In normal mode,
+each glyph is displayed in a grid.
+.
+.Pp
+.Bl -tag -width Ds -compact
+.It Ic q
+Quit.
+.Nm
+will ask for confirmation
+if the font has been modified
+since the last write.
+.
+.It Ic w
+Write font to
+.Ar file .
+.
+.It Ic - Ic +
+Adjust display scale.
+.
+.It Ic h Ic l
+Select previous/next glyph.
+.
+.It Ic k Ic j
+Select glyph in previous/next row.
+.
+.It Ic f
+Select glyph of next input character.
+.
+.It Ic '
+Return to previously selected glyph.
+.
+.It Ic y
+Copy selected glyph.
+.
+.It Ic e
+Edit selected glyph in
+.Sx Edit Mode .
+.
+.It Ic i
+Enter
+.Sx Preview Mode .
+.El
+.
+.Ss Edit Mode
+In edit mode,
+the selected glyph is displayed for editing
+surrounded by a checked border.
+The glyph is also displayed unscaled
+in the bottom-right corner.
+.
+.Pp
+.Bl -tag -width Ds -compact
+.It Ic ESC
+Return to
+.Sx Normal Mode .
+.
+.It Ic - Ic +
+Adjust display scale.
+.
+.It Ic g Ic G
+Toggle guide on selected column/row.
+.
+.It Ic h Ic l
+Select previous/next bit in row.
+.
+.It Ic k Ic j
+Select previous/next bit in column.
+.
+.It Ic SPACE
+Flip selected bit.
+.
+.It Ic r
+Invert glyph.
+.
+.It Ic H Ic L
+Move glyph left/right.
+.
+.It Ic K Ic J
+Move glyph up/down.
+.
+.It Ic p
+Paste the copied glyph.
+.
+.It Ic u
+Revert glyph to initial state.
+.El
+.
+.Ss Preview Mode
+In preview mode,
+arbitrary text may be entered
+for preview.
+Press
+.Ic ESC
+to return to
+.Sx Normal Mode .
+.
+.Sh ENVIRONMENT
+.Bl -tag -width FRAMEBUFFER
+.It Ev FRAMEBUFFER
+The framebuffer device path.
+The default path is
+.Pa /dev/fb0 .
+.El
+.
+.Sh SEE ALSO
+.Xr psfaddtable 1 ,
+.Xr psfgettable 1 ,
+.Xr psfstriptable 1 ,
+.Xr setfont 8
+.
+.Sh CAVEATS
+.Nm
+does not support Unicode tables.
+Use
+.Xr psfaddtable 1
+to add Unicode tables
+to fonts created by
+.Nm .
+.
+.Sh BUGS
+.Nm
+makes no attempt to convert header fields
+to and from little-endian format.
diff --git a/bin/man1/ptee.1 b/bin/man1/ptee.1
new file mode 100644
index 00000000..04f9cdac
--- /dev/null
+++ b/bin/man1/ptee.1
@@ -0,0 +1,40 @@
+.Dd July 17, 2019
+.Dt PTEE 1
+.Os
+.
+.Sh NAME
+.Nm ptee
+.Nd tee for PTYs
+.
+.Sh SYNOPSIS
+.Nm
+.Ar command ...
+.Cm >
+.Ar file
+.
+.Sh DESCRIPTION
+.Nm
+runs
+.Ar command
+in a new PTY
+which is mirrored to
+the current PTY
+and standard output.
+Standard output must be redirected
+to a file or pipe.
+.
+.Pp
+Type
+.Ic ^S
+to toggle writing to standard output.
+Type
+.Ic ^Q
+to write the media copy sequence for
+.Xr shotty 1 .
+.
+.Sh SEE ALSO
+.Xr tee 1
+.
+.Sh BUGS
+Window size changes are not propagated
+to the child PTY.
diff --git a/bin/man1/relay.1 b/bin/man1/relay.1
new file mode 100644
index 00000000..402c4726
--- /dev/null
+++ b/bin/man1/relay.1
@@ -0,0 +1,48 @@
+.Dd April 28, 2019
+.Dt RELAY 1
+.Os
+.
+.Sh NAME
+.Nm relay
+.Nd IRC relay bot
+.
+.Sh SYNOPSIS
+.Nm
+.Ar host
+.Ar port
+.Ar nick
+.Ar chan
+.
+.Sh DESCRIPTION
+.Nm
+is one half of an IRC relay pair.
+It connects to
+.Ar host Ns : Ns Ar port
+over TLS
+as
+.Ar nick
+and joins
+.Ar chan .
+.
+.Pp
+.Nm
+outputs messages from
+.Ar chan
+to standard output
+and sends messages to
+.Ar chan
+from standard input.
+Two
+.Nm
+processes can be connected with
+.Xr mkfifo 1 .
+.
+.Sh EXAMPLES
+.Bd -literal -offset indent
+mkfifo a b
+relay a.example.com 6697 relay '#example' <>a >b
+relay b.example.com 6697 relay '#example' <>b >a
+.Ed
+.
+.Sh SEE ALSO
+.Xr mkfifo 1
diff --git a/bin/man1/scheme.1 b/bin/man1/scheme.1
new file mode 100644
index 00000000..9aa4f054
--- /dev/null
+++ b/bin/man1/scheme.1
@@ -0,0 +1,56 @@
+.Dd July 6, 2019
+.Dt SCHEME 1
+.Os
+.
+.Sh NAME
+.Nm scheme
+.Nd color scheme
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl acghilmstx
+.Op Fl p Ar n
+.
+.Sh DESCRIPTION
+.Nm
+generates a color scheme
+and outputs it in a number of formats.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Generate the 16 ANSI colors.
+This is the default.
+.It Fl c
+Output a C enum.
+.It Fl g
+Output a swatch PNG.
+.It Fl h
+Output floating point HSV.
+.It Fl i
+Swap black and white.
+.It Fl l
+Output Linux console OSC sequences.
+.It Fl m
+Output a
+.Xr mintty 1
+theme.
+Use with
+.Fl t .
+.It Fl p Ar n
+Generate only the color
+.Ar n .
+.It Fl s
+Output CSS
+for classes named
+.Sy fg Ns Ar n
+and
+.Sy bg Ns Ar n .
+.It Fl t
+Generate the 16 ANSI colors as well as
+background, foreground, bold, selection and cursor colors.
+.It Fl x
+Output hexadecimal RGB.
+This is the default.
+.El
diff --git a/bin/man1/shotty.1 b/bin/man1/shotty.1
new file mode 100644
index 00000000..d5eaa780
--- /dev/null
+++ b/bin/man1/shotty.1
@@ -0,0 +1,92 @@
+.Dd November 25, 2019
+.Dt SHOTTY 1
+.Os
+.
+.Sh NAME
+.Nm shotty
+.Nd terminal capture
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl Bdns
+.Op Fl b Ar bg
+.Op Fl f Ar fg
+.Op Fl h Ar rows
+.Op Fl w Ar cols
+.Op Ar file
+.
+.Sh DESCRIPTION
+.Nm
+interprets terminal output from
+.Ar file
+or standard input
+and produces HTML
+.Sy <pre>
+on standard output.
+.
+.Pp
+Terminal output
+can be captured with
+.Xr ptee 1 .
+.Nm
+targets compatibility with
+.Ev TERM Ns = Ns Cm xterm
+and
+.Ev TERM Ns = Ns Cm xterm-256color
+as used by
+.Xr ncurses 3 .
+A snapshot of the terminal
+is output each time
+a media copy sequence occurs,
+or once at the end of the capture.
+.
+.Pp
+HTML output uses the classes
+.Sy bg Ns Va n
+and
+.Sy fg Ns Va n ,
+and inline styles for
+bold, italic and underline.
+CSS for colors can be generated with
+.Xr scheme 1 .
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width "-w cols"
+.It Fl B
+Replace bold with bright colors.
+.
+.It Fl b Ar bg
+Set the default background color.
+The default value is 0 (black).
+.
+.It Fl d
+Output the terminal state
+following each control sequence.
+.
+.It Fl f Ar fg
+Set the default foreground color.
+The default value is 7 (white).
+.
+.It Fl h Ar rows
+Set the terminal height.
+The default value is 24.
+.
+.It Fl n
+Do not show the cursor.
+.
+.It Fl s
+Set the terminal size
+from the current terminal size.
+.
+.It Fl w Ar cols
+Set the terminal width.
+The default value is 80.
+.El
+.
+.Sh EXAMPLES
+.Dl ptee htop | shotty -s > htop.html
+.
+.Sh SEE ALSO
+.Xr ptee 1 ,
+.Xr scheme 1
diff --git a/bin/man1/title.1 b/bin/man1/title.1
new file mode 100644
index 00000000..43ecc5e2
--- /dev/null
+++ b/bin/man1/title.1
@@ -0,0 +1,51 @@
+.Dd September 10, 2019
+.Dt TITLE 1
+.Os
+.
+.Sh NAME
+.Nm title
+.Nd page titles
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl v
+.Op Fl x Ar pattern
+.Op Ar url
+.
+.Sh DESCRIPTION
+.Nm
+fetches HTML page titles
+over HTTP and HTTPS.
+.Nm
+scans standard input for URLs
+and writes their titles to standard output.
+If a
+.Ar url
+argument is given,
+.Nm
+exits after fetching its title.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl x Ar pattern
+Exclude URLs matching
+.Ar pattern ,
+which is a modern regular expression.
+See
+.Xr re_format 7 .
+.It Fl v
+Enable
+.Xr libcurl 3
+verbose output.
+.El
+.
+.Sh EXAMPLES
+.Bd -literal -offset indent
+mkfifo snarf titles
+relay irc.example.org 6697 snarf '#example' <>titles >snarf
+title <snarf >titles
+.Ed
+.
+.Sh SEE ALSO
+.Xr relay 1
diff --git a/bin/man1/ttpre.1 b/bin/man1/ttpre.1
new file mode 100644
index 00000000..bd178525
--- /dev/null
+++ b/bin/man1/ttpre.1
@@ -0,0 +1,32 @@
+.Dd October 8, 2018
+.Dt TTPRE 1
+.Os
+.
+.Sh NAME
+.Nm ttpre
+.Nd man output to HTML
+.
+.Sh SYNOPSIS
+.Nm
+.
+.Sh DESCRIPTION
+.Nm
+formats
+.Xr man 1
+output on standard input
+as an HTML
+.Sy <pre>
+fragment on standard output.
+Ampersands and angle brackets
+are replaced with corresponding HTML entities,
+and backspace formatting sequences
+are replaced with
+.Sy <i>
+and
+.Sy <b>
+tags.
+.
+.Sh SEE ALSO
+.Xr less 1 ,
+.Xr man 1 ,
+.Xr mandoc 1
diff --git a/bin/man1/up.1 b/bin/man1/up.1
new file mode 100644
index 00000000..58b5359f
--- /dev/null
+++ b/bin/man1/up.1
@@ -0,0 +1,76 @@
+.Dd July 15, 2019
+.Dt UP 1
+.Os
+.
+.Sh NAME
+.Nm up
+.Nd upload file
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl h
+.Op Ar file
+.
+.Nm
+.Fl c | t
+.Ar command ...
+.
+.Nm
+.Fl s
+.
+.Sh DESCRIPTION
+.Nm
+uploads a file
+to temp.causal.agency with
+.Xr scp 1 .
+If no
+.Ar file
+is provided,
+standard input is read
+and uploaded as text.
+.
+.Pp
+The destination file name
+is chosen using
+.Xr date 1
+and
+.Xr openssl 1
+.Cm rand .
+The URL of the uploaded file is printed
+and copied to the pasteboard with
+.Xr pbcopy 1
+if available.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl c
+Run a command
+to produce a text file for upload.
+.It Fl h
+Use
+.Xr hi 1
+to produce an HTML file for upload.
+.It Fl s
+Use
+.Xr screencapture 1
+to produce a PNG file for upload.
+The file is optimized by
+.Xr pngo 1
+if available.
+.It Fl t
+Run a command with
+.Xr ptee 1
+and
+.Xr shotty 1
+to produce an HTML file for upload.
+.El
+.
+.Pp
+Any arguments after
+.Ql \-\-
+are passed to
+.Xr hi 1
+and
+.Xr screencapture 1 ,
+respectively.
diff --git a/bin/man1/when.1 b/bin/man1/when.1
new file mode 100644
index 00000000..0b473573
--- /dev/null
+++ b/bin/man1/when.1
@@ -0,0 +1,76 @@
+.Dd July 24, 2019
+.Dt WHEN 1
+.Os
+.
+.Sh NAME
+.Nm when
+.Nd date calculator
+.
+.Sh SYNOPSIS
+.Nm
+.Op Ar expr
+.
+.Sh DESCRIPTION
+.Nm
+is a date calculator.
+If no
+.Ar expr
+is given,
+expressions are read
+from standard input.
+.
+.Pp
+The grammar is as follows:
+.Bl -tag -width Ds
+.It Sy \&.
+Today's date.
+.
+.It Ar month Ar date Op Ar year
+A full date,
+or a date in the current year.
+.Ar month
+must be at least three letters.
+.
+.It Ar day
+A day of the week
+in the current week.
+.Ar day
+must be at least three letters.
+.
+.It Sy < Ar date
+The date one week before.
+.
+.It Sy > Ar date
+The date one week after.
+.
+.It Ar date Sy + Ar interval
+The date after some interval.
+.
+.It Ar date Sy - Ar interval
+The date before some interval.
+.
+.It Ar date Sy - Ar date
+The interval between two dates.
+.
+.It Ar num Sy d
+A number of days.
+.
+.It Ar num Sy w
+A number of weeks.
+.
+.It Ar num Sy m
+A number of months.
+.
+.It Ar num Sy y
+A number of years.
+.El
+.
+.Sh EXAMPLES
+.Bl -tag -width "Dec 25 - ."
+.It Ic Dec 25 - \&.
+How long until Christmas.
+.It Ic >Fri
+The date next Friday.
+.It Ic \&. + 2w
+Your last day at work.
+.El
diff --git a/bin/man1/xx.1 b/bin/man1/xx.1
new file mode 100644
index 00000000..d38789a7
--- /dev/null
+++ b/bin/man1/xx.1
@@ -0,0 +1,68 @@
+.Dd September 7, 2018
+.Dt XX 1
+.Os
+.
+.Sh NAME
+.Nm xx
+.Nd hexdump
+.
+.Sh SYNOPSIS
+.Nm
+.Op Fl arsz
+.Op Fl c Ar cols
+.Op Fl g Ar group
+.Op Fl p Ar count
+.Op Ar file
+.
+.Sh DESCRIPTION
+.Nm
+dumps the contents of a
+.Ar file
+or standard input
+in hexadecimal format.
+.
+.Pp
+The arguments are as follows:
+.Bl -tag -width Ds
+.It Fl a
+Toggle ASCII output.
+.
+.It Fl c Ar cols
+Output
+.Ar cols
+bytes per line.
+The default
+.Ar cols
+is 16.
+.
+.It Fl g Ar group
+Output extra space after every
+.Ar group
+bytes.
+The default
+.Ar group
+is 8.
+.
+.It Fl p Ar count
+Output a blank line after every
+.Ar count
+bytes.
+.Ar count
+must be a multiple of
+.Ar cols .
+.
+.It Fl r
+Reverse hexdump.
+Read hexadecimal input
+and write byte output.
+.
+.It Fl s
+Toggle offset output.
+.
+.It Fl z
+Skip output of lines containing only zeros.
+.El
+.
+.Sh SEE ALSO
+.Xr hexdump 1 ,
+.Xr xxd 1
diff --git a/bin/man3/png.3 b/bin/man3/png.3
new file mode 100644
index 00000000..accffbd7
--- /dev/null
+++ b/bin/man3/png.3
@@ -0,0 +1,90 @@
+.Dd July 25, 2019
+.Dt PNG 3
+.Os
+.
+.Sh NAME
+.Nm png
+.Nd basic PNG output
+.
+.Sh SYNOPSIS
+.In png.h
+.
+.Ft void
+.Fo pngHead
+.Fa "FILE *file"
+.Fa "uint32_t width"
+.Fa "uint32_t height"
+.Fa "uint8_t depth"
+.Fa "uint8_t color"
+.Fc
+.
+.Ft void
+.Fn pngPalette "FILE *file" "const uint8_t *pal" "uint32_t len"
+.
+.Ft void
+.Fn pngData "FILE *file" "const uint8_t *data" "uint32_t len"
+.
+.Ft void
+.Fn pngTail "FILE *file"
+.
+.Sh DESCRIPTION
+The
+.Fn pngHead
+function
+writes the
+.Sy IHDR
+chunk to
+.Fa file .
+The
+.Fa color
+parameter can be one of
+.Dv PNGGrayscale ,
+.Dv PNGTruecolor
+optionally
+.Em or Ns 'ed
+with
+.Dv PNGAlpha ,
+or
+.Dv PNGIndexed .
+.
+.Pp
+The
+.Fn pngPalette
+function
+writes the
+.Sy PLTE
+chunk to
+.Fa file .
+.
+.Pp
+The
+.Fn pngData
+function
+writes the
+.Sy IDAT
+chunk to
+.Fa file
+without compression.
+The constants
+.Dv PNGNone ,
+.Dv PNGSub ,
+.Dv PNGUp ,
+.Dv PNGAverage ,
+.Dv PNGPaeth
+are defined
+for use in PNG data.
+.
+.Pp
+The
+.Fn pngTail
+function
+writes the
+.Sy IEND
+chunk to
+.Fa file .
+.
+.Sh ERRORS
+Any errors from writing to
+.Fa file
+are handled by calling
+.Xr err 3 .
diff --git a/bin/modem.c b/bin/modem.c
new file mode 100644
index 00000000..2133ae08
--- /dev/null
+++ b/bin/modem.c
@@ -0,0 +1,102 @@
+/* Copyright (C) 2018  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <unistd.h>
+
+#if defined __FreeBSD__
+#include <libutil.h>
+#elif defined __linux__
+#include <pty.h>
+#else
+#include <util.h>
+#endif
+
+typedef unsigned uint;
+typedef unsigned char byte;
+
+static struct termios saveTerm;
+static void restoreTerm(void) {
+	tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm);
+}
+
+int main(int argc, char *argv[]) {
+	int error;
+
+	uint baudRate = 19200;
+	for (int opt; 0 < (opt = getopt(argc, argv, "r:"));) {
+		switch (opt) {
+			break; case 'r': baudRate = strtoul(optarg, NULL, 10);
+			break; default:  return EX_USAGE;
+		}
+	}
+	if (argc - optind < 1) return EX_USAGE;
+
+	error = tcgetattr(STDIN_FILENO, &saveTerm);
+	if (error) err(EX_IOERR, "tcgetattr");
+	atexit(restoreTerm);
+
+	struct termios raw = saveTerm;
+	cfmakeraw(&raw);
+	error = tcsetattr(STDIN_FILENO, TCSADRAIN, &raw);
+	if (error) err(EX_IOERR, "tcsetattr");
+
+	struct winsize window;
+	error = ioctl(STDIN_FILENO, TIOCGWINSZ, &window);
+	if (error) err(EX_IOERR, "TIOCGWINSZ");
+
+	int pty;
+	pid_t pid = forkpty(&pty, NULL, NULL, &window);
+	if (pid < 0) err(EX_OSERR, "forkpty");
+
+	if (!pid) {
+		execvp(argv[optind], &argv[optind]);
+		err(EX_NOINPUT, "%s", argv[optind]);
+	}
+
+	byte c;
+	struct pollfd fds[2] = {
+		{ .events = POLLIN, .fd = STDIN_FILENO },
+		{ .events = POLLIN, .fd = pty },
+	};
+	while (usleep(8 * 1000000 / baudRate), 0 < poll(fds, 2, -1)) {
+		if (fds[0].revents) {
+			ssize_t size = read(STDIN_FILENO, &c, 1);
+			if (size < 0) err(EX_IOERR, "read(%d)", STDIN_FILENO);
+			size = write(pty, &c, 1);
+			if (size < 0) err(EX_IOERR, "write(%d)", pty);
+		}
+
+		if (fds[1].revents) {
+			ssize_t size = read(pty, &c, 1);
+			if (size < 0) err(EX_IOERR, "read(%d)", pty);
+			if (!size) break;
+			size = write(STDOUT_FILENO, &c, 1);
+			if (size < 0) err(EX_IOERR, "write(%d)", STDOUT_FILENO);
+		}
+	}
+
+	int status;
+	pid_t dead = waitpid(pid, &status, 0);
+	if (dead < 0) err(EX_OSERR, "waitpid");
+	return WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE;
+}
diff --git a/bin/nudge.c b/bin/nudge.c
new file mode 100644
index 00000000..108d2459
--- /dev/null
+++ b/bin/nudge.c
@@ -0,0 +1,78 @@
+/* Copyright (C) 2020  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <unistd.h>
+
+static int shake = 10;
+static int delay = 20000;
+static int count = 2;
+
+static void move(int tty, int x, int y) {
+	dprintf(tty, "\33[3;%d;%dt", x, y);
+	usleep(delay);
+}
+
+int main(int argc, char *argv[]) {
+	const char *path = "/dev/tty";
+	for (int opt; 0 < (opt = getopt(argc, argv, "f:n:s:t:"));) {
+		switch (opt) {
+			break; case 'f': path = optarg;
+			break; case 'n': count = atoi(optarg);
+			break; case 's': shake = atoi(optarg);
+			break; case 't': delay = atoi(optarg) * 1000;
+			break; default:  return EX_USAGE;
+		}
+	}
+
+	int tty = open(path, O_RDWR);
+	if (tty < 0) err(EX_OSFILE, "%s", path);
+
+	struct termios save;
+	int error = tcgetattr(tty, &save);
+	if (error) err(EX_IOERR, "tcgetattr");
+
+	struct termios raw = save;
+	cfmakeraw(&raw);
+	error = tcsetattr(tty, TCSAFLUSH, &raw);
+	if (error) err(EX_IOERR, "tcsetattr");
+
+	char buf[256];
+	dprintf(tty, "\33[13t");
+	ssize_t len = read(tty, buf, sizeof(buf) - 1);
+	buf[(len < 0 ? 0 : len)] = '\0';
+
+	int x, y;
+	int n = sscanf(buf, "\33[3;%d;%dt", &x, &y);
+
+	error = tcsetattr(tty, TCSANOW, &save);
+	if (error) err(EX_IOERR, "tcsetattr");
+	if (n < 2) return EX_CONFIG;
+
+	dprintf(tty, "\33[5t");
+	for (int i = 0; i < count; ++i) {
+		move(tty, x - shake, y);
+		move(tty, x, y + shake);
+		move(tty, x + shake, y);
+		move(tty, x, y - shake);
+		move(tty, x, y);
+	}
+}
diff --git a/bin/order.y b/bin/order.y
new file mode 100644
index 00000000..b798e634
--- /dev/null
+++ b/bin/order.y
@@ -0,0 +1,193 @@
+/* Copyright (C) 2019  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+%{
+
+#include <ctype.h>
+#include <err.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+
+int vasprintf(char **ret, const char *format, va_list ap);
+
+static void yyerror(const char *str) {
+	errx(EX_DATAERR, "%s", str);
+}
+
+#define YYSTYPE char *
+
+static char *fmt(const char *format, ...) {
+	char *str = NULL;
+	va_list ap;
+	va_start(ap, format);
+	vasprintf(&str, format, ap);
+	va_end(ap);
+	if (!str) err(EX_OSERR, "vasprintf");
+	return str;
+}
+
+static int yylex(void);
+
+%}
+
+%left ','
+%right '=' MulAss DivAss ModAss AddAss SubAss ShlAss ShrAss AndAss XorAss OrAss
+%right '?' ':'
+%left Or
+%left And
+%left '|'
+%left '^'
+%left '&'
+%left Eq Ne
+%left '<' Le '>' Ge
+%left Shl Shr
+%left '+' '-'
+%left '*' '/' '%'
+%right '!' '~' Inc Dec Sizeof
+%left '(' ')' '[' ']' Arr '.'
+
+%token Var
+
+%%
+
+start:
+	expr { printf("%s\n", $1); }
+	;
+
+expr:
+	Var
+	| '(' expr ')' { $$ = $2; }
+	| expr '[' expr ']' { $$ = fmt("(%s[%s])", $1, $3); }
+	| expr Arr Var { $$ = fmt("(%s->%s)", $1, $3); }
+	| expr '.' Var { $$ = fmt("(%s.%s)", $1, $3); }
+	| '!' expr { $$ = fmt("(!%s)", $2); }
+	| '~' expr { $$ = fmt("(~%s)", $2); }
+	| Inc expr { $$ = fmt("(++%s)", $2); }
+	| Dec expr { $$ = fmt("(--%s)", $2); }
+	| expr Inc { $$ = fmt("(%s++)", $1); }
+	| expr Dec { $$ = fmt("(%s--)", $1); }
+	| '-' expr %prec '!' { $$ = fmt("(-%s)", $2); }
+	| '*' expr %prec '!' { $$ = fmt("(*%s)", $2); }
+	| '&' expr %prec '!' { $$ = fmt("(&%s)", $2); }
+	| Sizeof expr { $$ = fmt("(sizeof %s)", $2); }
+	| expr '*' expr { $$ = fmt("(%s * %s)", $1, $3); }
+	| expr '/' expr { $$ = fmt("(%s / %s)", $1, $3); }
+	| expr '%' expr { $$ = fmt("(%s %% %s)", $1, $3); }
+	| expr '+' expr { $$ = fmt("(%s + %s)", $1, $3); }
+	| expr '-' expr { $$ = fmt("(%s - %s)", $1, $3); }
+	| expr Shl expr { $$ = fmt("(%s << %s)", $1, $3); }
+	| expr Shr expr { $$ = fmt("(%s >> %s)", $1, $3); }
+	| expr '<' expr { $$ = fmt("(%s < %s)", $1, $3); }
+	| expr Le expr { $$ = fmt("(%s <= %s)", $1, $3); }
+	| expr '>' expr { $$ = fmt("(%s > %s)", $1, $3); }
+	| expr Ge expr { $$ = fmt("(%s >= %s)", $1, $3); }
+	| expr Eq expr { $$ = fmt("(%s == %s)", $1, $3); }
+	| expr Ne expr { $$ = fmt("(%s != %s)", $1, $3); }
+	| expr '&' expr { $$ = fmt("(%s & %s)", $1, $3); }
+	| expr '^' expr { $$ = fmt("(%s ^ %s)", $1, $3); }
+	| expr '|' expr { $$ = fmt("(%s | %s)", $1, $3); }
+	| expr And expr { $$ = fmt("(%s && %s)", $1, $3); }
+	| expr Or expr { $$ = fmt("(%s || %s)", $1, $3); }
+	| expr '?' expr ':' expr { $$ = fmt("(%s ? %s : %s)", $1, $3, $5); }
+	| expr ass expr %prec '=' { $$ = fmt("(%s %s %s)", $1, $2, $3); }
+	| expr ',' expr { $$ = fmt("(%s, %s)", $1, $3); }
+	;
+
+ass:
+	'=' { $$ = "="; }
+	| MulAss { $$ = "*="; }
+	| DivAss { $$ = "/="; }
+	| ModAss { $$ = "%="; }
+	| AddAss { $$ = "+="; }
+	| SubAss { $$ = "-="; }
+	| ShlAss { $$ = "<<="; }
+	| ShrAss { $$ = ">>="; }
+	| AndAss { $$ = "&="; }
+	| XorAss { $$ = "^="; }
+	| OrAss { $$ = "|="; }
+	;
+
+%%
+
+#define T(a, b) ((int)(a) << 8 | (int)(b))
+
+static const char *input;
+
+static int yylex(void) {
+	while (isspace(input[0])) input++;
+	if (!input[0]) return EOF;
+
+	int len;
+	for (len = 0; isalnum(input[len]) || input[len] == '_'; ++len);
+	if (len) {
+		if (!strncmp(input, "sizeof", len)) {
+			input += len;
+			return Sizeof;
+		}
+		yylval = fmt("%.*s", len, input);
+		input += len;
+		return Var;
+	}
+
+	int tok;
+	switch (T(input[0], input[1])) {
+		break; case T('-', '>'): tok = Arr;
+		break; case T('+', '+'): tok = Inc;
+		break; case T('-', '-'): tok = Dec;
+		break; case T('<', '<'): tok = Shl;
+		break; case T('>', '>'): tok = Shr;
+		break; case T('<', '='): tok = Le;
+		break; case T('>', '='): tok = Ge;
+		break; case T('=', '='): tok = Eq;
+		break; case T('!', '='): tok = Ne;
+		break; case T('&', '&'): tok = And;
+		break; case T('|', '|'): tok = Or;
+		break; case T('*', '='): tok = MulAss;
+		break; case T('/', '='): tok = DivAss;
+		break; case T('%', '='): tok = ModAss;
+		break; case T('+', '='): tok = AddAss;
+		break; case T('-', '='): tok = SubAss;
+		break; case T('&', '='): tok = AndAss;
+		break; case T('^', '='): tok = XorAss;
+		break; case T('|', '='): tok = OrAss;
+		break; default: return *input++;
+	}
+	input += 2;
+
+	switch (T(tok, input[0])) {
+		case T(Shl, '='): input++; return ShlAss;
+		case T(Shr, '='): input++; return ShrAss;
+	}
+
+	return tok;
+}
+
+int main(int argc, char *argv[]) {
+	for (int i = 1; i < argc; ++i) {
+		input = argv[i];
+		yyparse();
+	}
+	if (argc > 1) return EX_OK;
+	size_t cap = 0;
+	char *buf = NULL;
+	while (0 < getline(&buf, &cap, stdin)) {
+		input = buf;
+		yyparse();
+	}
+}
diff --git a/bin/pbd.c b/bin/pbd.c
new file mode 100644
index 00000000..2ba333fd
--- /dev/null
+++ b/bin/pbd.c
@@ -0,0 +1,152 @@
+/* Copyright (C) 2017  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <arpa/inet.h>
+#include <err.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+typedef unsigned char byte;
+
+static void spawn(const char *cmd, const char *arg, int dest, int src) {
+	pid_t pid = fork();
+	if (pid < 0) err(EX_OSERR, "fork");
+
+	if (pid) {
+		int status;
+		pid_t dead = waitpid(pid, &status, 0);
+		if (dead < 0) err(EX_OSERR, "waitpid(%d)", pid);
+		if (status) warnx("%s: status %d", cmd, status);
+
+	} else {
+		int fd = dup2(src, dest);
+		if (fd < 0) err(EX_OSERR, "dup2");
+
+		execlp(cmd, cmd, arg, NULL);
+		err(EX_UNAVAILABLE, "%s", cmd);
+	}
+}
+
+static int pbd(void) {
+	int error;
+
+	int server = socket(PF_INET, SOCK_STREAM, 0);
+	if (server < 0) err(EX_OSERR, "socket");
+
+	error = fcntl(server, F_SETFD, FD_CLOEXEC);
+	if (error) err(EX_IOERR, "fcntl");
+
+	struct sockaddr_in addr = {
+		.sin_family = AF_INET,
+		.sin_port = htons(7062),
+		.sin_addr = { .s_addr = htonl(0x7F000001) },
+	};
+	error = bind(server, (struct sockaddr *)&addr, sizeof(addr));
+	if (error) err(EX_UNAVAILABLE, "bind");
+
+	error = listen(server, 0);
+	if (error) err(EX_UNAVAILABLE, "listen");
+
+	for (;;) {
+		int client = accept(server, NULL, NULL);
+		if (client < 0) err(EX_IOERR, "accept");
+
+		error = fcntl(client, F_SETFD, FD_CLOEXEC);
+		if (error) err(EX_IOERR, "fcntl");
+
+		char c = 0;
+		ssize_t size = read(client, &c, 1);
+		if (size < 0) warn("read");
+
+		switch (c) {
+			break; case 'p': spawn("pbpaste", NULL, STDOUT_FILENO, client);
+			break; case 'c': spawn("pbcopy", NULL, STDIN_FILENO, client);
+			break; case 'o': spawn("xargs", "open", STDIN_FILENO, client);
+		}
+
+		close(client);
+	}
+}
+
+static int pbdClient(char c) {
+	int client = socket(PF_INET, SOCK_STREAM, 0);
+	if (client < 0) err(EX_OSERR, "socket");
+
+	struct sockaddr_in addr = {
+		.sin_family = AF_INET,
+		.sin_port = htons(7062),
+		.sin_addr = { .s_addr = htonl(0x7F000001) },
+	};
+	int error = connect(client, (struct sockaddr *)&addr, sizeof(addr));
+	if (error) err(EX_UNAVAILABLE, "connect");
+
+	ssize_t size = write(client, &c, 1);
+	if (size < 0) err(EX_IOERR, "write");
+
+	return client;
+}
+
+static void copy(int out, int in) {
+	byte buf[4096];
+	ssize_t readSize;
+	while (0 < (readSize = read(in, buf, sizeof(buf)))) {
+		ssize_t writeSize = write(out, buf, readSize);
+		if (writeSize < 0) err(EX_IOERR, "write(%d)", out);
+	}
+	if (readSize < 0) err(EX_IOERR, "read(%d)", in);
+}
+
+static int pbcopy(void) {
+	int client = pbdClient('c');
+	copy(client, STDIN_FILENO);
+	return EX_OK;
+}
+
+static int pbpaste(void) {
+	int client = pbdClient('p');
+	copy(STDOUT_FILENO, client);
+	return EX_OK;
+}
+
+static int open1(const char *url) {
+	if (!url) return EX_USAGE;
+	int client = pbdClient('o');
+	ssize_t size = write(client, url, strlen(url));
+	if (size < 0) err(EX_IOERR, "write");
+	return EX_OK;
+}
+
+int main(int argc, char *argv[]) {
+	(void)argc;
+	if (strchr(argv[0], '/')) {
+		argv[0] = strrchr(argv[0], '/') + 1;
+	}
+	switch (argv[0][0] && argv[0][1] ? argv[0][2] : 0) {
+		case 'd': return pbd();
+		case 'c': return pbcopy();
+		case 'p': return pbpaste();
+		case 'e': return open1(argv[1]);
+		default:  return EX_USAGE;
+	}
+}
diff --git a/bin/png.h b/bin/png.h
new file mode 100644
index 00000000..15b6d13d
--- /dev/null
+++ b/bin/png.h
@@ -0,0 +1,108 @@
+/* Copyright (C) 2018  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+
+static inline uint32_t pngCRCTable(uint8_t n) {
+	static uint32_t table[256];
+	if (table[1]) return table[n];
+	for (int i = 0; i < 256; ++i) {
+		table[i] = i;
+		for (int j = 0; j < 8; ++j) {
+			table[i] = (table[i] >> 1) ^ (table[i] & 1 ? 0xEDB88320 : 0);
+		}
+	}
+	return table[n];
+}
+
+static uint32_t pngCRC;
+
+static inline void pngWrite(FILE *file, const uint8_t *ptr, uint32_t len) {
+	if (!fwrite(ptr, len, 1, file)) err(EX_IOERR, "pngWrite");
+	for (uint32_t i = 0; i < len; ++i) {
+		pngCRC = pngCRCTable(pngCRC ^ ptr[i]) ^ (pngCRC >> 8);
+	}
+}
+static inline void pngInt32(FILE *file, uint32_t n) {
+	pngWrite(file, (uint8_t []) { n >> 24, n >> 16, n >> 8, n }, 4);
+}
+static inline void pngChunk(FILE *file, char type[static 4], uint32_t len) {
+	pngInt32(file, len);
+	pngCRC = ~0;
+	pngWrite(file, (uint8_t *)type, 4);
+}
+
+enum {
+	PNGGrayscale,
+	PNGTruecolor = 2,
+	PNGIndexed,
+	PNGAlpha,
+};
+
+static inline void pngHead(
+	FILE *file, uint32_t width, uint32_t height, uint8_t depth, uint8_t color
+) {
+	pngWrite(file, (uint8_t *)"\x89PNG\r\n\x1A\n", 8);
+	pngChunk(file, "IHDR", 13);
+	pngInt32(file, width);
+	pngInt32(file, height);
+	pngWrite(file, &depth, 1);
+	pngWrite(file, &color, 1);
+	pngWrite(file, (uint8_t []) { 0, 0, 0 }, 3);
+	pngInt32(file, ~pngCRC);
+}
+
+static inline void pngPalette(FILE *file, const uint8_t *pal, uint32_t len) {
+	pngChunk(file, "PLTE", len);
+	pngWrite(file, pal, len);
+	pngInt32(file, ~pngCRC);
+}
+
+enum {
+	PNGNone,
+	PNGSub,
+	PNGUp,
+	PNGAverage,
+	PNGPaeth,
+};
+
+static inline void pngData(FILE *file, const uint8_t *data, uint32_t len) {
+	uint32_t adler1 = 1, adler2 = 0;
+	for (uint32_t i = 0; i < len; ++i) {
+		adler1 = (adler1 + data[i]) % 65521;
+		adler2 = (adler1 + adler2) % 65521;
+	}
+	uint32_t zlen = 2 + 5 * ((len + 0xFFFE) / 0xFFFF) + len + 4;
+	pngChunk(file, "IDAT", zlen);
+	pngWrite(file, (uint8_t []) { 0x08, 0x1D }, 2);
+	for (; len > 0xFFFF; data += 0xFFFF, len -= 0xFFFF) {
+		pngWrite(file, (uint8_t []) { 0x00, 0xFF, 0xFF, 0x00, 0x00 }, 5);
+		pngWrite(file, data, 0xFFFF);
+	}
+	pngWrite(file, (uint8_t []) { 0x01, len, len >> 8, ~len, ~len >> 8 }, 5);
+	pngWrite(file, data, len);
+	pngInt32(file, adler2 << 16 | adler1);
+	pngInt32(file, ~pngCRC);
+}
+
+static inline void pngTail(FILE *file) {
+	pngChunk(file, "IEND", 0);
+	pngInt32(file, ~pngCRC);
+}
diff --git a/bin/pngo.c b/bin/pngo.c
new file mode 100644
index 00000000..322cb1ba
--- /dev/null
+++ b/bin/pngo.c
@@ -0,0 +1,812 @@
+/* Copyright (C) 2018  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <arpa/inet.h>
+#include <err.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#define PACKED __attribute__((packed))
+#define PAIR(a, b) ((uint16_t)(a) << 8 | (uint16_t)(b))
+
+#define CRC_INIT (crc32(0, Z_NULL, 0))
+
+static bool verbose;
+static const char *path;
+static FILE *file;
+static uint32_t crc;
+
+static void readExpect(void *ptr, size_t size, const char *expect) {
+	fread(ptr, size, 1, file);
+	if (ferror(file)) err(EX_IOERR, "%s", path);
+	if (feof(file)) errx(EX_DATAERR, "%s: missing %s", path, expect);
+	crc = crc32(crc, ptr, size);
+}
+
+static void writeExpect(const void *ptr, size_t size) {
+	fwrite(ptr, size, 1, file);
+	if (ferror(file)) err(EX_IOERR, "%s", path);
+	crc = crc32(crc, ptr, size);
+}
+
+static const uint8_t Signature[8] = "\x89PNG\r\n\x1A\n";
+
+static void readSignature(void) {
+	uint8_t signature[8];
+	readExpect(signature, 8, "signature");
+	if (0 != memcmp(signature, Signature, 8)) {
+		errx(EX_DATAERR, "%s: invalid signature", path);
+	}
+}
+
+static void writeSignature(void) {
+	writeExpect(Signature, sizeof(Signature));
+}
+
+struct PACKED Chunk {
+	uint32_t size;
+	char type[4];
+};
+
+static const char *typeStr(struct Chunk chunk) {
+	static char buf[5];
+	memcpy(buf, chunk.type, 4);
+	return buf;
+}
+
+static struct Chunk readChunk(void) {
+	struct Chunk chunk;
+	readExpect(&chunk, sizeof(chunk), "chunk");
+	chunk.size = ntohl(chunk.size);
+	crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type));
+	return chunk;
+}
+
+static void writeChunk(struct Chunk chunk) {
+	chunk.size = htonl(chunk.size);
+	writeExpect(&chunk, sizeof(chunk));
+	crc = crc32(CRC_INIT, (Byte *)chunk.type, sizeof(chunk.type));
+}
+
+static void readCrc(void) {
+	uint32_t expected = crc;
+	uint32_t found;
+	readExpect(&found, sizeof(found), "CRC32");
+	found = ntohl(found);
+	if (found != expected) {
+		errx(
+			EX_DATAERR, "%s: expected CRC32 %08X, found %08X",
+			path, expected, found
+		);
+	}
+}
+
+static void writeCrc(void) {
+	uint32_t net = htonl(crc);
+	writeExpect(&net, sizeof(net));
+}
+
+static void skipChunk(struct Chunk chunk) {
+	if (!(chunk.type[0] & 0x20)) {
+		errx(EX_CONFIG, "%s: unsupported critical chunk %s", path, typeStr(chunk));
+	}
+	uint8_t discard[4096];
+	while (chunk.size > sizeof(discard)) {
+		readExpect(discard, sizeof(discard), "chunk data");
+		chunk.size -= sizeof(discard);
+	}
+	if (chunk.size) readExpect(discard, chunk.size, "chunk data");
+	readCrc();
+}
+
+static struct PACKED {
+	uint32_t width;
+	uint32_t height;
+	uint8_t depth;
+	enum PACKED {
+		Grayscale      = 0,
+		Truecolor      = 2,
+		Indexed        = 3,
+		GrayscaleAlpha = 4,
+		TruecolorAlpha = 6,
+	} color;
+	enum PACKED { Deflate } compression;
+	enum PACKED { Adaptive } filter;
+	enum PACKED { Progressive, Adam7 } interlace;
+} header;
+_Static_assert(13 == sizeof(header), "header size");
+
+static size_t pixelBits(void) {
+	switch (header.color) {
+		case Grayscale:      return 1 * header.depth;
+		case Truecolor:      return 3 * header.depth;
+		case Indexed:        return 1 * header.depth;
+		case GrayscaleAlpha: return 2 * header.depth;
+		case TruecolorAlpha: return 4 * header.depth;
+		default: abort();
+	}
+}
+
+static size_t pixelSize(void) {
+	return (pixelBits() + 7) / 8;
+}
+
+static size_t lineSize(void) {
+	return (header.width * pixelBits() + 7) / 8;
+}
+
+static size_t dataSize(void) {
+	return (1 + lineSize()) * header.height;
+}
+
+static const char *ColorStr[] = {
+	[Grayscale] = "grayscale",
+	[Truecolor] = "truecolor",
+	[Indexed] = "indexed",
+	[GrayscaleAlpha] = "grayscale alpha",
+	[TruecolorAlpha] = "truecolor alpha",
+};
+static void printHeader(void) {
+	fprintf(
+		stderr,
+		"%s: %ux%u %hhu-bit %s\n",
+		path,
+		header.width, header.height,
+		header.depth, ColorStr[header.color]
+	);
+}
+
+static void readHeader(struct Chunk chunk) {
+	if (chunk.size != sizeof(header)) {
+		errx(
+			EX_DATAERR, "%s: expected IHDR size %zu, found %u",
+			path, sizeof(header), chunk.size
+		);
+	}
+	readExpect(&header, sizeof(header), "header");
+	readCrc();
+
+	header.width = ntohl(header.width);
+	header.height = ntohl(header.height);
+
+	if (!header.width) errx(EX_DATAERR, "%s: invalid width 0", path);
+	if (!header.height) errx(EX_DATAERR, "%s: invalid height 0", path);
+	switch (PAIR(header.color, header.depth)) {
+		case PAIR(Grayscale, 1):
+		case PAIR(Grayscale, 2):
+		case PAIR(Grayscale, 4):
+		case PAIR(Grayscale, 8):
+		case PAIR(Grayscale, 16):
+		case PAIR(Truecolor, 8):
+		case PAIR(Truecolor, 16):
+		case PAIR(Indexed, 1):
+		case PAIR(Indexed, 2):
+		case PAIR(Indexed, 4):
+		case PAIR(Indexed, 8):
+		case PAIR(GrayscaleAlpha, 8):
+		case PAIR(GrayscaleAlpha, 16):
+		case PAIR(TruecolorAlpha, 8):
+		case PAIR(TruecolorAlpha, 16):
+			break;
+		default:
+			errx(
+				EX_DATAERR, "%s: invalid color type %hhu and bit depth %hhu",
+				path, header.color, header.depth
+			);
+	}
+	if (header.compression != Deflate) {
+		errx(
+			EX_DATAERR, "%s: invalid compression method %hhu",
+			path, header.compression
+		);
+	}
+	if (header.filter != Adaptive) {
+		errx(EX_DATAERR, "%s: invalid filter method %hhu", path, header.filter);
+	}
+	if (header.interlace > Adam7) {
+		errx(EX_DATAERR, "%s: invalid interlace method %hhu", path, header.interlace);
+	}
+
+	if (verbose) printHeader();
+}
+
+static void writeHeader(void) {
+	if (verbose) printHeader();
+
+	struct Chunk ihdr = { .size = sizeof(header), .type = "IHDR" };
+	writeChunk(ihdr);
+	header.width = htonl(header.width);
+	header.height = htonl(header.height);
+	writeExpect(&header, sizeof(header));
+	writeCrc();
+
+	header.width = ntohl(header.width);
+	header.height = ntohl(header.height);
+}
+
+static struct {
+	uint32_t len;
+	uint8_t entries[256][3];
+} palette;
+
+static struct {
+	uint32_t len;
+	uint8_t alpha[256];
+} trans;
+
+static void paletteClear(void) {
+	palette.len = 0;
+	trans.len = 0;
+}
+
+static uint32_t paletteIndex(bool alpha, const uint8_t *rgba) {
+	uint32_t i;
+	for (i = 0; i < palette.len; ++i) {
+		if (alpha && i < trans.len && trans.alpha[i] != rgba[3]) continue;
+		if (0 == memcmp(palette.entries[i], rgba, 3)) break;
+	}
+	return i;
+}
+
+static bool paletteAdd(bool alpha, const uint8_t *rgba) {
+	uint32_t i = paletteIndex(alpha, rgba);
+	if (i < palette.len) return true;
+	if (i == 256) return false;
+	memcpy(palette.entries[i], rgba, 3);
+	palette.len++;
+	if (alpha) {
+		trans.alpha[i] = rgba[3];
+		trans.len++;
+	}
+	return true;
+}
+
+static void transCompact(void) {
+	uint32_t i;
+	for (i = 0; i < trans.len; ++i) {
+		if (trans.alpha[i] == 0xFF) break;
+	}
+	if (i == trans.len) return;
+
+	for (uint32_t j = i + 1; j < trans.len; ++j) {
+		if (trans.alpha[j] == 0xFF) continue;
+
+		uint8_t alpha = trans.alpha[i];
+		trans.alpha[i] = trans.alpha[j];
+		trans.alpha[j] = alpha;
+
+		uint8_t rgb[3];
+		memcpy(rgb, palette.entries[i], 3);
+		memcpy(palette.entries[i], palette.entries[j], 3);
+		memcpy(palette.entries[j], rgb, 3);
+
+		i++;
+	}
+	trans.len = i;
+}
+
+static void readPalette(struct Chunk chunk) {
+	if (chunk.size % 3) {
+		errx(EX_DATAERR, "%s: PLTE size %u not divisible by 3", path, chunk.size);
+	}
+
+	palette.len = chunk.size / 3;
+	if (palette.len > 256) {
+		errx(EX_DATAERR, "%s: PLTE length %u > 256", path, palette.len);
+	}
+
+	readExpect(palette.entries, chunk.size, "palette data");
+	readCrc();
+
+	if (verbose) fprintf(stderr, "%s: palette length %u\n", path, palette.len);
+}
+
+static void writePalette(void) {
+	if (verbose) fprintf(stderr, "%s: palette length %u\n", path, palette.len);
+	struct Chunk plte = { .size = 3 * palette.len, .type = "PLTE" };
+	writeChunk(plte);
+	writeExpect(palette.entries, plte.size);
+	writeCrc();
+}
+
+static void readTrans(struct Chunk chunk) {
+	trans.len = chunk.size;
+	if (trans.len > 256) {
+		errx(EX_DATAERR, "%s: tRNS length %u > 256", path, trans.len);
+	}
+	readExpect(trans.alpha, chunk.size, "transparency alpha");
+	readCrc();
+	if (verbose) fprintf(stderr, "%s: transparency length %u\n", path, trans.len);
+}
+
+static void writeTrans(void) {
+	if (verbose) fprintf(stderr, "%s: transparency length %u\n", path, trans.len);
+	struct Chunk trns = { .size = trans.len, .type = "tRNS" };
+	writeChunk(trns);
+	writeExpect(trans.alpha, trns.size);
+	writeCrc();
+}
+
+static uint8_t *data;
+
+static void allocData(void) {
+	data = malloc(dataSize());
+	if (!data) err(EX_OSERR, "malloc(%zu)", dataSize());
+}
+
+static void readData(struct Chunk chunk) {
+	if (verbose) fprintf(stderr, "%s: data size %zu\n", path, dataSize());
+
+	struct z_stream_s stream = { .next_out = data, .avail_out = dataSize() };
+	int error = inflateInit(&stream);
+	if (error != Z_OK) errx(EX_SOFTWARE, "%s: inflateInit: %s", path, stream.msg);
+
+	for (;;) {
+		if (0 != memcmp(chunk.type, "IDAT", 4)) {
+			errx(EX_DATAERR, "%s: missing IDAT chunk", path);
+		}
+
+		uint8_t *idat = malloc(chunk.size);
+		if (!idat) err(EX_OSERR, "malloc");
+
+		readExpect(idat, chunk.size, "image data");
+		readCrc();
+
+		stream.next_in = idat;
+		stream.avail_in = chunk.size;
+		int error = inflate(&stream, Z_SYNC_FLUSH);
+		free(idat);
+
+		if (error == Z_STREAM_END) break;
+		if (error != Z_OK) {
+			errx(EX_DATAERR, "%s: inflate: %s", path, stream.msg);
+		}
+
+		chunk = readChunk();
+	}
+
+	inflateEnd(&stream);
+	if ((size_t)stream.total_out != dataSize()) {
+		errx(
+			EX_DATAERR, "%s: expected data size %zu, found %zu",
+			path, dataSize(), (size_t)stream.total_out
+		);
+	}
+
+	if (verbose) {
+		fprintf(
+			stderr, "%s: deflate size %zu\n", path, (size_t)stream.total_in
+		);
+	}
+}
+
+static void writeData(void) {
+	if (verbose) fprintf(stderr, "%s: data size %zu\n", path, dataSize());
+
+	uLong size = compressBound(dataSize());
+	uint8_t *deflate = malloc(size);
+	if (!deflate) err(EX_OSERR, "malloc");
+
+	int error = compress2(deflate, &size, data, dataSize(), Z_BEST_COMPRESSION);
+	if (error != Z_OK) errx(EX_SOFTWARE, "%s: compress2: %d", path, error);
+
+	struct Chunk idat = { .size = size, .type = "IDAT" };
+	writeChunk(idat);
+	writeExpect(deflate, size);
+	writeCrc();
+
+	free(deflate);
+
+	if (verbose) fprintf(stderr, "%s: deflate size %lu\n", path, size);
+}
+
+static void writeEnd(void) {
+	struct Chunk iend = { .size = 0, .type = "IEND" };
+	writeChunk(iend);
+	writeCrc();
+}
+
+enum PACKED Filter {
+	None,
+	Sub,
+	Up,
+	Average,
+	Paeth,
+	FilterCount,
+};
+
+struct Bytes {
+	uint8_t x;
+	uint8_t a;
+	uint8_t b;
+	uint8_t c;
+};
+
+static uint8_t paethPredictor(struct Bytes f) {
+	int32_t p = (int32_t)f.a + (int32_t)f.b - (int32_t)f.c;
+	int32_t pa = abs(p - (int32_t)f.a);
+	int32_t pb = abs(p - (int32_t)f.b);
+	int32_t pc = abs(p - (int32_t)f.c);
+	if (pa <= pb && pa <= pc) return f.a;
+	if (pb <= pc) return f.b;
+	return f.c;
+}
+
+static uint8_t recon(enum Filter type, struct Bytes f) {
+	switch (type) {
+		case None:    return f.x;
+		case Sub:     return f.x + f.a;
+		case Up:      return f.x + f.b;
+		case Average: return f.x + ((uint32_t)f.a + (uint32_t)f.b) / 2;
+		case Paeth:   return f.x + paethPredictor(f);
+		default:      abort();
+	}
+}
+
+static uint8_t filt(enum Filter type, struct Bytes f) {
+	switch (type) {
+		case None:    return f.x;
+		case Sub:     return f.x - f.a;
+		case Up:      return f.x - f.b;
+		case Average: return f.x - ((uint32_t)f.a + (uint32_t)f.b) / 2;
+		case Paeth:   return f.x - paethPredictor(f);
+		default:      abort();
+	}
+}
+
+static struct Line {
+	enum Filter type;
+	uint8_t data[];
+} **lines;
+
+static void allocLines(void) {
+	lines = calloc(header.height, sizeof(*lines));
+	if (!lines) err(EX_OSERR, "calloc(%u, %zu)", header.height, sizeof(*lines));
+}
+
+static void scanlines(void) {
+	size_t stride = 1 + lineSize();
+	for (uint32_t y = 0; y < header.height; ++y) {
+		lines[y] = (struct Line *)&data[y * stride];
+		if (lines[y]->type >= FilterCount) {
+			errx(EX_DATAERR, "%s: invalid filter type %hhu", path, lines[y]->type);
+		}
+	}
+}
+
+static struct Bytes origBytes(uint32_t y, size_t i) {
+	bool a = (i >= pixelSize()), b = (y > 0), c = (a && b);
+	return (struct Bytes) {
+		.x = lines[y]->data[i],
+		.a = a ? lines[y]->data[i - pixelSize()] : 0,
+		.b = b ? lines[y - 1]->data[i] : 0,
+		.c = c ? lines[y - 1]->data[i - pixelSize()] : 0,
+	};
+}
+
+static void reconData(void) {
+	for (uint32_t y = 0; y < header.height; ++y) {
+		for (size_t i = 0; i < lineSize(); ++i) {
+			lines[y]->data[i] =
+				recon(lines[y]->type, origBytes(y, i));
+		}
+		lines[y]->type = None;
+	}
+}
+
+static void filterData(void) {
+	if (header.color == Indexed || header.depth < 8) return;
+	for (uint32_t y = header.height - 1; y < header.height; --y) {
+		uint8_t filter[FilterCount][lineSize()];
+		uint32_t heuristic[FilterCount] = {0};
+		enum Filter minType = None;
+		for (enum Filter type = None; type < FilterCount; ++type) {
+			for (size_t i = 0; i < lineSize(); ++i) {
+				filter[type][i] = filt(type, origBytes(y, i));
+				heuristic[type] += abs((int8_t)filter[type][i]);
+			}
+			if (heuristic[type] < heuristic[minType]) minType = type;
+		}
+		lines[y]->type = minType;
+		memcpy(lines[y]->data, filter[minType], lineSize());
+	}
+}
+
+static void discardAlpha(void) {
+	if (header.color != GrayscaleAlpha && header.color != TruecolorAlpha) return;
+	size_t sampleSize = header.depth / 8;
+	size_t colorSize = pixelSize() - sampleSize;
+	for (uint32_t y = 0; y < header.height; ++y) {
+		for (uint32_t x = 0; x < header.width; ++x) {
+			for (size_t i = 0; i < sampleSize; ++i) {
+				if (lines[y]->data[x * pixelSize() + colorSize + i] != 0xFF) return;
+			}
+		}
+	}
+
+	uint8_t *ptr = data;
+	for (uint32_t y = 0; y < header.height; ++y) {
+		*ptr++ = lines[y]->type;
+		for (uint32_t x = 0; x < header.width; ++x) {
+			memmove(ptr, &lines[y]->data[x * pixelSize()], colorSize);
+			ptr += colorSize;
+		}
+	}
+	header.color = (header.color == GrayscaleAlpha) ? Grayscale : Truecolor;
+	scanlines();
+}
+
+static void discardColor(void) {
+	if (header.color != Truecolor && header.color != TruecolorAlpha) return;
+	size_t sampleSize = header.depth / 8;
+	for (uint32_t y = 0; y < header.height; ++y) {
+		for (uint32_t x = 0; x < header.width; ++x) {
+			uint8_t *r = &lines[y]->data[x * pixelSize()];
+			uint8_t *g = r + sampleSize;
+			uint8_t *b = g + sampleSize;
+			if (0 != memcmp(r, g, sampleSize)) return;
+			if (0 != memcmp(g, b, sampleSize)) return;
+		}
+	}
+
+	uint8_t *ptr = data;
+	for (uint32_t y = 0; y < header.height; ++y) {
+		*ptr++ = lines[y]->type;
+		for (uint32_t x = 0; x < header.width; ++x) {
+			uint8_t *pixel = &lines[y]->data[x * pixelSize()];
+			memmove(ptr, pixel, sampleSize);
+			ptr += sampleSize;
+			if (header.color == TruecolorAlpha) {
+				memmove(ptr, pixel + 3 * sampleSize, sampleSize);
+				ptr += sampleSize;
+			}
+		}
+	}
+	header.color = (header.color == Truecolor) ? Grayscale : GrayscaleAlpha;
+	scanlines();
+}
+
+static void indexColor(void) {
+	if (header.color != Truecolor && header.color != TruecolorAlpha) return;
+	if (header.depth != 8) return;
+	bool alpha = (header.color == TruecolorAlpha);
+	for (uint32_t y = 0; y < header.height; ++y) {
+		for (uint32_t x = 0; x < header.width; ++x) {
+			if (!paletteAdd(alpha, &lines[y]->data[x * pixelSize()])) return;
+		}
+	}
+	transCompact();
+
+	uint8_t *ptr = data;
+	for (uint32_t y = 0; y < header.height; ++y) {
+		*ptr++ = lines[y]->type;
+		for (uint32_t x = 0; x < header.width; ++x) {
+			*ptr++ = paletteIndex(alpha, &lines[y]->data[x * pixelSize()]);
+		}
+	}
+	header.color = Indexed;
+	scanlines();
+}
+
+static void reduceDepth8(void) {
+	if (header.color != Grayscale && header.color != Indexed) return;
+	if (header.depth != 8) return;
+	if (header.color == Grayscale) {
+		for (uint32_t y = 0; y < header.height; ++y) {
+			for (size_t i = 0; i < lineSize(); ++i) {
+				uint8_t a = lines[y]->data[i];
+				if ((a >> 4) != (a & 0x0F)) return;
+			}
+		}
+	} else if (palette.len > 16) {
+		return;
+	}
+
+	uint8_t *ptr = data;
+	for (uint32_t y = 0; y < header.height; ++y) {
+		*ptr++ = lines[y]->type;
+		for (size_t i = 0; i < lineSize(); i += 2) {
+			uint8_t iByte = lines[y]->data[i];
+			uint8_t jByte = (i + 1 < lineSize()) ? lines[y]->data[i + 1] : 0;
+			uint8_t a = iByte & 0x0F;
+			uint8_t b = jByte & 0x0F;
+			*ptr++ = a << 4 | b;
+		}
+	}
+	header.depth = 4;
+	scanlines();
+}
+
+static void reduceDepth4(void) {
+	if (header.depth != 4) return;
+	if (header.color == Grayscale) {
+		for (uint32_t y = 0; y < header.height; ++y) {
+			for (size_t i = 0; i < lineSize(); ++i) {
+				uint8_t a = lines[y]->data[i] >> 4;
+				uint8_t b = lines[y]->data[i] & 0x0F;
+				if ((a >> 2) != (a & 0x03)) return;
+				if ((b >> 2) != (b & 0x03)) return;
+			}
+		}
+	} else if (palette.len > 4) {
+		return;
+	}
+
+	uint8_t *ptr = data;
+	for (uint32_t y = 0; y < header.height; ++y) {
+		*ptr++ = lines[y]->type;
+		for (size_t i = 0; i < lineSize(); i += 2) {
+			uint8_t iByte = lines[y]->data[i];
+			uint8_t jByte = (i + 1 < lineSize()) ? lines[y]->data[i + 1] : 0;
+			uint8_t a = iByte >> 4 & 0x03, b = iByte & 0x03;
+			uint8_t c = jByte >> 4 & 0x03, d = jByte & 0x03;
+			*ptr++ = a << 6 | b << 4 | c << 2 | d;
+		}
+	}
+	header.depth = 2;
+	scanlines();
+}
+
+static void reduceDepth2(void) {
+	if (header.depth != 2) return;
+	if (header.color == Grayscale) {
+		for (uint32_t y = 0; y < header.height; ++y) {
+			for (size_t i = 0; i < lineSize(); ++i) {
+				uint8_t a = lines[y]->data[i] >> 6;
+				uint8_t b = lines[y]->data[i] >> 4 & 0x03;
+				uint8_t c = lines[y]->data[i] >> 2 & 0x03;
+				uint8_t d = lines[y]->data[i] & 0x03;
+				if ((a >> 1) != (a & 0x01)) return;
+				if ((b >> 1) != (b & 0x01)) return;
+				if ((c >> 1) != (c & 0x01)) return;
+				if ((d >> 1) != (d & 0x01)) return;
+			}
+		}
+	} else if (palette.len > 2) {
+		return;
+	}
+
+	uint8_t *ptr = data;
+	for (uint32_t y = 0; y < header.height; ++y) {
+		*ptr++ = lines[y]->type;
+		for (size_t i = 0; i < lineSize(); i += 2) {
+			uint8_t iByte = lines[y]->data[i];
+			uint8_t jByte = (i + 1 < lineSize()) ? lines[y]->data[i + 1] : 0;
+			uint8_t a = iByte >> 6 & 0x01, b = iByte >> 4 & 0x01;
+			uint8_t c = iByte >> 2 & 0x01, d = iByte & 0x01;
+			uint8_t e = jByte >> 6 & 0x01, f = jByte >> 4 & 0x01;
+			uint8_t g = jByte >> 2 & 0x01, h = jByte & 0x01;
+			*ptr++ = a << 7 | b << 6 | c << 5 | d << 4 | e << 3 | f << 2 | g << 1 | h;
+		}
+	}
+	header.depth = 1;
+	scanlines();
+}
+
+static void reduceDepth(void) {
+	reduceDepth8();
+	reduceDepth4();
+	reduceDepth2();
+}
+
+static void optimize(const char *inPath, const char *outPath) {
+	if (inPath) {
+		path = inPath;
+		file = fopen(path, "r");
+		if (!file) err(EX_NOINPUT, "%s", path);
+	} else {
+		path = "(stdin)";
+		file = stdin;
+	}
+
+	readSignature();
+	struct Chunk ihdr = readChunk();
+	if (0 != memcmp(ihdr.type, "IHDR", 4)) {
+		errx(EX_DATAERR, "%s: expected IHDR, found %s", path, typeStr(ihdr));
+	}
+	readHeader(ihdr);
+	if (header.interlace != Progressive) {
+		errx(
+			EX_CONFIG, "%s: unsupported interlace method %hhu",
+			path, header.interlace
+		);
+	}
+
+	paletteClear();
+	allocData();
+	for (;;) {
+		struct Chunk chunk = readChunk();
+		if (0 == memcmp(chunk.type, "PLTE", 4)) {
+			readPalette(chunk);
+		} else if (0 == memcmp(chunk.type, "tRNS", 4)) {
+			readTrans(chunk);
+		} else if (0 == memcmp(chunk.type, "IDAT", 4)) {
+			readData(chunk);
+		} else if (0 != memcmp(chunk.type, "IEND", 4)) {
+			skipChunk(chunk);
+		} else {
+			break;
+		}
+	}
+
+	fclose(file);
+
+	allocLines();
+	scanlines();
+	reconData();
+
+	discardAlpha();
+	discardColor();
+	indexColor();
+	reduceDepth();
+	filterData();
+	free(lines);
+
+	if (outPath) {
+		path = outPath;
+		file = fopen(path, "w");
+		if (!file) err(EX_CANTCREAT, "%s", path);
+	} else {
+		path = "(stdout)";
+		file = stdout;
+	}
+
+	writeSignature();
+	writeHeader();
+	if (header.color == Indexed) {
+		writePalette();
+		if (trans.len) writeTrans();
+	}
+	writeData();
+	writeEnd();
+	free(data);
+
+	int error = fclose(file);
+	if (error) err(EX_IOERR, "%s", path);
+}
+
+int main(int argc, char *argv[]) {
+	bool stdio = false;
+	char *output = NULL;
+
+	int opt;
+	while (0 < (opt = getopt(argc, argv, "co:v"))) {
+		switch (opt) {
+			break; case 'c': stdio = true;
+			break; case 'o': output = optarg;
+			break; case 'v': verbose = true;
+			break; default: return EX_USAGE;
+		}
+	}
+
+	if (argc - optind == 1 && (output || stdio)) {
+		optimize(argv[optind], output);
+	} else if (optind < argc) {
+		for (int i = optind; i < argc; ++i) {
+			optimize(argv[i], argv[i]);
+		}
+	} else {
+		optimize(NULL, output);
+	}
+
+	return EX_OK;
+}
diff --git a/bin/psf2png.c b/bin/psf2png.c
new file mode 100644
index 00000000..1aaa8635
--- /dev/null
+++ b/bin/psf2png.c
@@ -0,0 +1,107 @@
+/* Copyright (C) 2018  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "png.h"
+
+int main(int argc, char *argv[]) {
+	uint32_t cols = 32;
+	const char *str = NULL;
+	uint32_t fg = 0xFFFFFF;
+	uint32_t bg = 0x000000;
+
+	int opt;
+	while (0 < (opt = getopt(argc, argv, "b:c:f:s:"))) {
+		switch (opt) {
+			break; case 'b': bg = strtoul(optarg, NULL, 16);
+			break; case 'c': cols = strtoul(optarg, NULL, 0);
+			break; case 'f': fg = strtoul(optarg, NULL, 16);
+			break; case 's': str = optarg;
+			break; default:  return EX_USAGE;
+		}
+	}
+	if (!cols && str) cols = strlen(str);
+	if (!cols) return EX_USAGE;
+
+	const char *path = NULL;
+	if (optind < argc) path = argv[optind];
+	
+	FILE *file = path ? fopen(path, "r") : stdin;
+	if (!file) err(EX_NOINPUT, "%s", path);
+	if (!path) path = "(stdin)";
+
+	struct {
+		uint32_t magic;
+		uint32_t version;
+		uint32_t size;
+		uint32_t flags;
+		struct {
+			uint32_t len;
+			uint32_t size;
+			uint32_t height;
+			uint32_t width;
+		} glyph;
+	} header;
+	size_t len = fread(&header, sizeof(header), 1, file);
+	if (ferror(file)) err(EX_IOERR, "%s", path);
+	if (len < 1) errx(EX_DATAERR, "%s: truncated header", path);
+
+	uint32_t widthBytes = (header.glyph.width + 7) / 8;
+	uint8_t glyphs[header.glyph.len][header.glyph.height][widthBytes];
+	len = fread(glyphs, header.glyph.size, header.glyph.len, file);
+	if (ferror(file)) err(EX_IOERR, "%s", path);
+	if (len < header.glyph.len) {
+		errx(EX_DATAERR, "%s: truncated glyphs", path);
+	}
+	fclose(file);
+
+	uint32_t count = (str ? strlen(str) : header.glyph.len);
+	uint32_t width = header.glyph.width * cols;
+	uint32_t rows = (count + cols - 1) / cols;
+	uint32_t height = header.glyph.height * rows;
+
+	pngHead(stdout, width, height, 8, PNGIndexed);
+	uint8_t pal[] = {
+		bg >> 16, bg >> 8, bg,
+		fg >> 16, fg >> 8, fg,
+	};
+	pngPalette(stdout, pal, sizeof(pal));
+
+	uint8_t data[height][1 + width];
+	memset(data, PNGNone, sizeof(data));
+
+	for (uint32_t i = 0; i < count; ++i) {
+		uint32_t row = header.glyph.height * (i / cols);
+		uint32_t col = 1 + header.glyph.width * (i % cols);
+		uint32_t g = (str ? str[i] : i);
+		for (uint32_t y = 0; y < header.glyph.height; ++y) {
+			for (uint32_t x = 0; x < header.glyph.width; ++x) {
+				uint8_t bit = glyphs[g][y][x / 8] >> (7 - x % 8) & 1;
+				data[row + y][col + x] = bit;
+			}
+		}
+	}
+
+	pngData(stdout, (uint8_t *)data, sizeof(data));
+	pngTail(stdout);
+}
diff --git a/bin/psfed.c b/bin/psfed.c
new file mode 100644
index 00000000..4f46b500
--- /dev/null
+++ b/bin/psfed.c
@@ -0,0 +1,577 @@
+/* Copyright (C) 2018  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/fb.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <unistd.h>
+#include <wchar.h>
+
+static const wchar_t CP437[256] =
+	L"\0☺☻♥♦♣♠•◘○◙♂♀♪♫☼"
+	L"►◄↕‼¶§▬↨↑↓→â†âˆŸâ†”▲▼"
+	L" !\"#$%&'()*+,-./"
+	L"0123456789:;<=>?"
+	L"@ABCDEFGHIJKLMNO"
+	L"PQRSTUVWXYZ[\\]^_"
+	L"`abcdefghijklmno"
+	L"pqrstuvwxyz{|}~⌂"
+	L"ÇüéâäàåçêëèïîìÄÅ"
+	L"ÉæÆôöòûùÿÖÜ¢£¥₧ƒ"
+	L"áíóúñѪº¿âŒÂ¬Â½Â¼Â¡Â«Â»"
+	L"░▒▓│┤╡╢╖╕╣║╗â•â•œâ•›â”"
+	L"└┴┬├─┼╞╟╚╔╩╦╠â•â•¬â•§"
+	L"╨╤╥╙╘╒╓╫╪┘┌█▄▌â–â–€"
+	L"αßΓπΣσµτΦΘΩδ∞φε∩"
+	L"≡±≥≤⌠⌡÷≈°∙·√â¿Â²â– \0";
+
+static struct {
+	uint32_t width;
+	uint32_t height;
+	uint32_t *buffer;
+	uint32_t background;
+} frame;
+
+static void frameClear(void) {
+	for (uint32_t i = 0; i < frame.width * frame.height; ++i) {
+		frame.buffer[i] = frame.background;
+	}
+}
+
+static void frameOpen(void) {
+	const char *dev = getenv("FRAMEBUFFER");
+	if (!dev) dev = "/dev/fb0";
+
+	int fd = open(dev, O_RDWR);
+	if (fd < 0) err(EX_OSFILE, "%s", dev);
+
+	struct fb_var_screeninfo info;
+	int error = ioctl(fd, FBIOGET_VSCREENINFO, &info);
+	if (error) err(EX_IOERR, "%s", dev);
+
+	frame.width = info.xres;
+	frame.height = 3 * info.yres / 4;
+	frame.buffer = mmap(
+		NULL, sizeof(*frame.buffer) * frame.width * frame.height,
+		PROT_READ | PROT_WRITE, MAP_SHARED,
+		fd, 0
+	);
+	if (frame.buffer == MAP_FAILED) err(EX_IOERR, "%s", dev);
+	close(fd);
+
+	frame.background = frame.buffer[0];
+	atexit(frameClear);
+}
+
+static const uint32_t Magic = 0x864AB572;
+static const uint32_t Version = 0;
+static const uint32_t FlagUnicode = 1 << 0;
+static uint32_t bytes(uint32_t bits) {
+	return (bits + 7) / 8;
+}
+
+static char *path;
+static struct {
+	uint32_t magic;
+	uint32_t version;
+	uint32_t size;
+	uint32_t flags;
+	struct {
+		uint32_t len;
+		uint32_t size;
+		uint32_t height;
+		uint32_t width;
+	} glyph;
+} header;
+static uint8_t *glyphs;
+
+static void fileRead(uint32_t newLen, uint32_t newWidth, uint32_t newHeight) {
+	FILE *file = fopen(path, "r");
+	if (file) {
+		size_t len = fread(&header, sizeof(header), 1, file);
+		if (ferror(file)) err(EX_IOERR, "%s", path);
+		if (len < 1) errx(EX_DATAERR, "%s: truncated header", path);
+
+	} else {
+		if (errno != ENOENT) err(EX_NOINPUT, "%s", path);
+		header.magic = Magic;
+		header.version = Version;
+		header.size = sizeof(header);
+		header.flags = 0;
+		header.glyph.len = newLen;
+		header.glyph.size = bytes(newWidth) * newHeight;
+		header.glyph.height = newHeight;
+		header.glyph.width = newWidth;
+	}
+
+	if (header.magic != Magic) {
+		errx(EX_DATAERR, "%s: invalid magic %08X", path, header.magic);
+	}
+	if (header.version != Version) {
+		errx(EX_DATAERR, "%s: unsupported version %u", path, header.version);
+	}
+	if (header.flags & FlagUnicode) {
+		errx(EX_DATAERR, "%s: unsupported unicode table", path);
+	}
+	if (header.flags) {
+		errx(EX_DATAERR, "%s: unsupported flags %08X", path, header.flags);
+	}
+
+	if (file && header.size > sizeof(header)) {
+		int error = fseek(file, header.size, SEEK_SET);
+		if (error) err(EX_IOERR, "%s", path);
+
+		warnx("%s: truncating long header", path);
+		header.size = sizeof(header);
+	}
+
+	glyphs = calloc(header.glyph.len, header.glyph.size);
+	if (!glyphs) err(EX_OSERR, "calloc");
+
+	if (file) {
+		size_t len = fread(glyphs, header.glyph.size, header.glyph.len, file);
+		if (ferror(file)) err(EX_IOERR, "%s", path);
+		if (len < header.glyph.len) {
+			errx(EX_DATAERR, "%s: truncated glyphs", path);
+		}
+		fclose(file);
+	}
+}
+
+static void fileWrite(void) {
+	FILE *file = fopen(path, "w");
+	if (!file) err(EX_CANTCREAT, "%s", path);
+
+	fwrite(&header, sizeof(header), 1, file);
+	if (ferror(file)) err(EX_IOERR, "%s", path);
+
+	fwrite(glyphs, header.glyph.size, header.glyph.len, file);
+	if (ferror(file)) err(EX_IOERR, "%s", path);
+
+	int error = fclose(file);
+	if (error) err(EX_IOERR, "%s", path);
+}
+
+static uint8_t *glyph(uint32_t index) {
+	return &glyphs[header.glyph.size * index];
+}
+static uint8_t *bitByte(uint32_t index, uint32_t x, uint32_t y) {
+	return &glyph(index)[bytes(header.glyph.width) * y + x / 8];
+}
+static uint8_t bitGet(uint32_t index, uint32_t x, uint32_t y) {
+	return *bitByte(index, x, y) >> (7 - x % 8) & 1;
+}
+static void bitFlip(uint32_t index, uint32_t x, uint32_t y) {
+	*bitByte(index, x, y) ^= 1 << (7 - x % 8);
+}
+static void bitSet(uint32_t index, uint32_t x, uint32_t y, uint8_t bit) {
+	*bitByte(index, x, y) &= ~(1 << (7 - x % 8));
+	*bitByte(index, x, y) |= bit << (7 - x % 8);
+}
+
+static void drawGlyph(
+	uint32_t destX, uint32_t destY, uint32_t scale, uint32_t index,
+	uint32_t selectX, uint32_t selectY, uint32_t guideX, uint32_t guideY
+) {
+	destX <<= scale;
+	destY <<= scale;
+
+	for (uint32_t y = 0; y < (header.glyph.height << scale); ++y) {
+		if (destY + y >= frame.height) break;
+		for (uint32_t x = 0; x < (header.glyph.width << scale); ++x) {
+			if (destX + x >= frame.width) break;
+
+			uint32_t glyphX = x >> scale;
+			uint32_t glyphY = y >> scale;
+			uint32_t fill = -bitGet(index, glyphX, glyphY);
+			if (selectX & 1 << glyphX && selectY & 1 << glyphY) fill ^= 0x77;
+			if (guideX & 1 << glyphX || guideY & 1 << glyphY) fill ^= 0x3300;
+
+			frame.buffer[frame.width * (destY + y) + destX + x] = fill;
+		}
+	}
+}
+
+static void drawBorder(uint32_t destX, uint32_t destY, uint32_t scale) {
+	destX <<= scale;
+	destY <<= scale;
+
+	for (uint32_t y = 0; y < destY; ++y) {
+		if (y >= frame.height) break;
+		uint32_t fill = -(y >> scale & 1) ^ 0x555555;
+		for (uint32_t x = 0; x < (uint32_t)(1 << scale); ++x) {
+			if (destX + x >= frame.width) break;
+			frame.buffer[frame.width * y + destX + x] = fill;
+		}
+	}
+
+	for (uint32_t x = 0; x < destX; ++x) {
+		if (x >= frame.width) break;
+		uint32_t fill = -(x >> scale & 1) ^ 0x555555;
+		for (uint32_t y = 0; y < (uint32_t)(1 << scale); ++y) {
+			if (destY + y >= frame.height) break;
+			frame.buffer[frame.width * (destY + y) + x] = fill;
+		}
+	}
+}
+
+enum { LF = '\n', Esc = '\33', Del = '\177' };
+
+static enum {
+	Normal,
+	Edit,
+	Preview,
+	Discard,
+} mode;
+
+static struct {
+	uint32_t scale;
+	uint32_t index;
+	bool modified;
+	bool to;
+	uint32_t from;
+} normal;
+
+static struct {
+	uint32_t scale;
+	uint32_t index;
+	uint32_t x;
+	uint32_t y;
+	uint32_t guideX;
+	uint32_t guideY;
+	uint8_t *undo;
+	uint8_t *copy;
+} edit = {
+	.scale = 4,
+};
+
+static const uint32_t NormalCols = 32;
+static void drawNormal(void) {
+	for (uint32_t i = 0; i < header.glyph.len; ++i) {
+		drawGlyph(
+			header.glyph.width * (i % NormalCols),
+			header.glyph.height * (i  / NormalCols),
+			normal.scale, i,
+			-(i == normal.index), -(i == normal.index), 0, 0
+		);
+	}
+}
+
+static void normalDec(uint32_t n) {
+	if (normal.index >= n) normal.index -= n;
+}
+static void normalInc(uint32_t n) {
+	if (normal.index + n < header.glyph.len) normal.index += n;
+}
+static void normalPrint(const char *prefix) {
+	if (normal.index <= 256) {
+		printf(
+			"%s: %02X '%lc'\n",
+			prefix, normal.index, (wint_t)CP437[normal.index]
+		);
+	} else {
+		printf("%s: %02X\n", prefix, normal.index);
+	}
+}
+
+static void inputNormal(char ch) {
+	if (normal.to) {
+		if (ch < header.glyph.len) normal.index = ch;
+		normalPrint("index");
+		normal.to = false;
+		return;
+	}
+
+	switch (ch) {
+		break; case 'q': {
+			if (!normal.modified) exit(EX_OK);
+			mode = Discard;
+		}
+		break; case 'w': {
+			fileWrite();
+			printf("write: %s\n", path);
+			normal.modified = false;
+		}
+		break; case '-': if (normal.scale) normal.scale--; frameClear();
+		break; case '+': normal.scale++;
+		break; case 'h': normalDec(1); normalPrint("index");
+		break; case 'l': normalInc(1); normalPrint("index");
+		break; case 'k': normalDec(NormalCols); normalPrint("index");
+		break; case 'j': normalInc(NormalCols); normalPrint("index");
+		break; case 'f': normal.from = normal.index; normal.to = true;
+		break; case 047: normal.index = normal.from; normalPrint("index");
+		break; case 'y': {
+			if (!edit.copy) edit.copy = malloc(header.glyph.size);
+			if (!edit.copy) err(EX_OSERR, "malloc");
+			memcpy(edit.copy, glyph(normal.index), header.glyph.size);
+			normalPrint("copy");
+		}
+		break; case 'e': {
+			normal.modified = true;
+			edit.index = normal.index;
+			if (!edit.undo) edit.undo = malloc(header.glyph.size);
+			if (!edit.undo) err(EX_OSERR, "malloc");
+			memcpy(edit.undo, glyph(edit.index), header.glyph.size);
+			mode = Edit;
+			frameClear();
+		}
+		break; case 'i': mode = Preview; frameClear();
+	}
+}
+
+static void drawEdit(void) {
+	drawGlyph(
+		0, 0, edit.scale, edit.index,
+		1 << edit.x, 1 << edit.y, edit.guideX, edit.guideY
+	);
+	drawBorder(header.glyph.width, header.glyph.height, edit.scale);
+	drawGlyph(
+		header.glyph.width << edit.scale,
+		header.glyph.height << edit.scale,
+		0, edit.index,
+		0, 0, 0, 0
+	);
+}
+
+static void inputEdit(char ch) {
+	switch (ch) {
+		break; case Esc: mode = Normal; frameClear();
+
+		break; case '-': if (edit.scale) edit.scale--; frameClear();
+		break; case '+': edit.scale++;
+		break; case 'g': edit.guideY ^= 1 << edit.y;
+		break; case 'G': edit.guideX ^= 1 << edit.x;
+
+		break; case 'h': if (edit.x) edit.x--;
+		break; case 'l': if (edit.x + 1 < header.glyph.width) edit.x++;
+		break; case 'k': if (edit.y) edit.y--;
+		break; case 'j': if (edit.y + 1 < header.glyph.height) edit.y++;
+		break; case ' ': bitFlip(edit.index, edit.x, edit.y);
+
+		break; case 'r': {
+			for (uint32_t y = 0; y < header.glyph.height; ++y) {
+				for (uint32_t x = 0; x < header.glyph.width; ++x) {
+					bitFlip(edit.index, x, y);
+				}
+			}
+		}
+
+		break; case 'H': {
+			for (uint32_t x = 0; x < header.glyph.width; ++x) {
+				for (uint32_t y = 0; y < header.glyph.height; ++y) {
+					if (x + 1 < header.glyph.width) {
+						bitSet(edit.index, x, y, bitGet(edit.index, x + 1, y));
+					} else {
+						bitSet(edit.index, x, y, 0);
+					}
+				}
+			}
+		}
+
+		break; case 'L': {
+			uint32_t width = header.glyph.width;
+			for (uint32_t x = width - 1; x < width; --x) {
+				for (uint32_t y = 0; y < header.glyph.height; ++y) {
+					if (x - 1 < width) {
+						bitSet(edit.index, x, y, bitGet(edit.index, x - 1, y));
+					} else {
+						bitSet(edit.index, x, y, 0);
+					}
+				}
+			}
+		}
+
+		break; case 'K': {
+			for (uint32_t y = 0; y < header.glyph.height; ++y) {
+				for (uint32_t x = 0; x < header.glyph.width; ++x) {
+					if (y + 1 < header.glyph.height) {
+						bitSet(edit.index, x, y, bitGet(edit.index, x, y + 1));
+					} else {
+						bitSet(edit.index, x, y, 0);
+					}
+				}
+			}
+		}
+
+		break; case 'J': {
+			uint32_t height = header.glyph.height;
+			for (uint32_t y = height - 1; y < height; --y) {
+				for (uint32_t x = 0; x < header.glyph.width; ++x) {
+					if (y - 1 < height) {
+						bitSet(edit.index, x, y, bitGet(edit.index, x, y - 1));
+					} else {
+						bitSet(edit.index, x, y, 0);
+					}
+				}
+			}
+		}
+
+		break; case 'p': {
+			if (!edit.copy) break;
+			memcpy(glyph(edit.index), edit.copy, header.glyph.size);
+		}
+		break; case 'u': {
+			if (!edit.undo) break;
+			memcpy(glyph(edit.index), edit.undo, header.glyph.size);
+		}
+	}
+}
+
+enum { PreviewRows = 8, PreviewCols = 64 };
+static struct {
+	uint32_t glyphs[PreviewRows * PreviewCols];
+	uint32_t index;
+} preview;
+
+static void drawPreview(void) {
+	for (uint32_t i = 0; i < PreviewRows * PreviewCols; ++i) {
+		drawGlyph(
+			header.glyph.width * (i % PreviewCols),
+			header.glyph.height * (i / PreviewCols),
+			0, preview.glyphs[i],
+			-(i == preview.index), -(i == preview.index), 0, 0
+		);
+	}
+}
+
+static void inputPreview(char ch) {
+	switch (ch) {
+		break; case Esc: mode = Normal; frameClear();
+		break; case Del: {
+			if (preview.index) preview.index--;
+			preview.glyphs[preview.index] = 0;
+		}
+		break; case LF: {
+			uint32_t tail = PreviewCols - (preview.index % PreviewCols);
+			memset(
+				&preview.glyphs[preview.index],
+				0, sizeof(preview.glyphs[0]) * tail
+			);
+			preview.index += tail;
+		}
+		break; default: preview.glyphs[preview.index++] = ch;
+	}
+	preview.index %= PreviewRows * PreviewCols;
+}
+
+static void drawDiscard(void) {
+	printf("discard modifications? ");
+	fflush(stdout);
+}
+
+static void inputDiscard(char ch) {
+	printf("%c\n", ch);
+	if (ch == 'Y' || ch == 'y') exit(EX_OK);
+	mode = Normal;
+}
+
+static void draw(void) {
+	switch (mode) {
+		break; case Normal: drawNormal();
+		break; case Edit: drawEdit();
+		break; case Preview: drawPreview();
+		break; case Discard: drawDiscard();
+	}
+}
+
+static void input(char ch) {
+	switch (mode) {
+		break; case Normal: inputNormal(ch);
+		break; case Edit: inputEdit(ch);
+		break; case Preview: inputPreview(ch);
+		break; case Discard: inputDiscard(ch);
+	}
+}
+
+static struct termios saveTerm;
+static void restoreTerm(void) {
+	tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm);
+}
+
+int main(int argc, char *argv[]) {
+	setlocale(LC_CTYPE, "");
+
+	uint32_t newLen = 256;
+	uint32_t newWidth = 8;
+	uint32_t newHeight = 16;
+	uint32_t setHeight = 0;
+
+	int opt;
+	while (0 < (opt = getopt(argc, argv, "H:g:h:w:"))) {
+		switch (opt) {
+			break; case 'H': setHeight = strtoul(optarg, NULL, 0);
+			break; case 'g': newLen = strtoul(optarg, NULL, 0);
+			break; case 'h': newHeight = strtoul(optarg, NULL, 0);
+			break; case 'w': newWidth = strtoul(optarg, NULL, 0);
+			break; default:  return EX_USAGE;
+		}
+	}
+	if (!newLen || !newWidth || !newHeight) return EX_USAGE;
+	if (optind == argc) return EX_USAGE;
+
+	path = strdup(argv[optind]);
+	fileRead(newLen, newWidth, newHeight);
+
+	if (setHeight) {
+		if (setHeight < header.glyph.height) {
+			errx(EX_CONFIG, "cannot decrease height");
+		}
+
+		uint32_t setSize = bytes(header.glyph.width) * setHeight;
+		uint8_t *setGlyphs = calloc(header.glyph.len, setSize);
+		for (uint32_t i = 0; i < header.glyph.len; ++i) {
+			memcpy(&setGlyphs[setSize * i], glyph(i), header.glyph.size);
+		}
+		free(glyphs);
+		glyphs = setGlyphs;
+
+		header.glyph.height = setHeight;
+		header.glyph.size = setSize;
+		normal.modified = true;
+	}
+
+	frameOpen();
+	frameClear();
+
+	int error = tcgetattr(STDIN_FILENO, &saveTerm);
+	if (error) err(EX_IOERR, "tcgetattr");
+	atexit(restoreTerm);
+
+	struct termios term = saveTerm;
+	term.c_lflag &= ~(ICANON | ECHO);
+	error = tcsetattr(STDIN_FILENO, TCSADRAIN, &term);
+	if (error) err(EX_IOERR, "tcsetattr");
+
+	for (;;) {
+		draw();
+		char ch;
+		ssize_t size = read(STDIN_FILENO, &ch, 1);
+		if (size < 0) err(EX_IOERR, "read");
+		if (!size) return EX_SOFTWARE;
+		input(ch);
+	}
+}
diff --git a/bin/ptee.c b/bin/ptee.c
new file mode 100644
index 00000000..6a9a16b4
--- /dev/null
+++ b/bin/ptee.c
@@ -0,0 +1,116 @@
+/* Copyright (C) 2019  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <termios.h>
+#include <unistd.h>
+
+#if defined __FreeBSD__
+#include <libutil.h>
+#elif defined __linux__
+#include <pty.h>
+#else
+#include <util.h>
+#endif
+
+typedef unsigned char byte;
+
+static struct termios saveTerm;
+static void restoreTerm(void) {
+	tcsetattr(STDIN_FILENO, TCSADRAIN, &saveTerm);
+}
+
+int main(int argc, char *argv[]) {
+	if (argc < 2) return EX_USAGE;
+	if (isatty(STDOUT_FILENO)) errx(EX_USAGE, "stdout is not redirected");
+
+	int error = tcgetattr(STDIN_FILENO, &saveTerm);
+	if (error) err(EX_IOERR, "tcgetattr");
+	atexit(restoreTerm);
+
+	struct termios raw = saveTerm;
+	cfmakeraw(&raw);
+	error = tcsetattr(STDIN_FILENO, TCSADRAIN, &raw);
+	if (error) err(EX_IOERR, "tcsetattr");
+
+	struct winsize window;
+	error = ioctl(STDIN_FILENO, TIOCGWINSZ, &window);
+	if (error) err(EX_IOERR, "ioctl");
+
+	int pty;
+	pid_t pid = forkpty(&pty, NULL, NULL, &window);
+	if (pid < 0) err(EX_OSERR, "forkpty");
+
+	if (!pid) {
+		execvp(argv[1], &argv[1]);
+		err(EX_NOINPUT, "%s", argv[1]);
+	}
+
+	bool stop = false;
+
+	byte buf[4096];
+	struct pollfd fds[2] = {
+		{ .events = POLLIN, .fd = STDIN_FILENO },
+		{ .events = POLLIN, .fd = pty },
+	};
+	while (0 < poll(fds, 2, -1)) {
+		if (fds[0].revents & POLLIN) {
+			ssize_t rlen = read(STDIN_FILENO, buf, sizeof(buf));
+			if (rlen < 0) err(EX_IOERR, "read");
+
+			if (rlen == 1 && buf[0] == CTRL('S')) {
+				stop ^= true;
+				continue;
+			}
+
+			if (rlen == 1 && buf[0] == CTRL('Q')) {
+				char dump[] = "\x1B[10i";
+				ssize_t wlen = write(STDOUT_FILENO, dump, sizeof(dump) - 1);
+				if (wlen < 0) err(EX_IOERR, "write");
+				continue;
+			}
+
+			ssize_t wlen = write(pty, buf, rlen);
+			if (wlen < 0) err(EX_IOERR, "write");
+		}
+
+		if (fds[1].revents & POLLIN) {
+			ssize_t rlen = read(pty, buf, sizeof(buf));
+			if (rlen < 0) err(EX_IOERR, "read");
+
+			ssize_t wlen = write(STDIN_FILENO, buf, rlen);
+			if (wlen < 0) err(EX_IOERR, "write");
+
+			if (!stop) {
+				wlen = write(STDOUT_FILENO, buf, rlen);
+				if (wlen < 0) err(EX_IOERR, "write");
+			}
+		}
+
+		int status;
+		pid_t dead = waitpid(pid, &status, WNOHANG);
+		if (dead < 0) err(EX_OSERR, "waitpid");
+		if (dead) return WIFEXITED(status) ? WEXITSTATUS(status) : EX_SOFTWARE;
+	}
+	err(EX_IOERR, "poll");
+}
diff --git a/bin/relay.c b/bin/relay.c
new file mode 100644
index 00000000..aa7913c9
--- /dev/null
+++ b/bin/relay.c
@@ -0,0 +1,218 @@
+/* Copyright (C) 2019  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Additional permission under GNU GPL version 3 section 7:
+ *
+ * If you modify this Program, or any covered work, by linking or
+ * combining it with LibreSSL (or a modified version of that library),
+ * containing parts covered by the terms of the OpenSSL License and the
+ * original SSLeay license, the licensors of this Program grant you
+ * additional permission to convey the resulting work. Corresponding
+ * Source for a non-source form of such a combination shall include the
+ * source code for the parts of LibreSSL used as well as that of the
+ * covered work.
+ */
+
+#include <err.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sysexits.h>
+#include <tls.h>
+#include <unistd.h>
+
+#ifdef __FreeBSD__
+#include <sys/capsicum.h>
+#endif
+
+static void clientWrite(struct tls *client, const char *ptr, size_t len) {
+	while (len) {
+		ssize_t ret = tls_write(client, ptr, len);
+		if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) continue;
+		if (ret < 0) errx(EX_IOERR, "tls_write: %s", tls_error(client));
+		ptr += ret;
+		len -= ret;
+	}
+}
+
+static void clientFormat(struct tls *client, const char *format, ...) {
+	char buf[1024];
+	va_list ap;
+	va_start(ap, format);
+	int len = vsnprintf(buf, sizeof(buf), format, ap);
+	va_end(ap);
+	if ((size_t)len > sizeof(buf) - 1) errx(EX_DATAERR, "message too large");
+	clientWrite(client, buf, len);
+}
+
+static void clientHandle(struct tls *client, const char *chan, char *line) {
+	char *prefix = NULL;
+	if (line[0] == ':') {
+		prefix = strsep(&line, " ") + 1;
+		if (!line) errx(EX_PROTOCOL, "unexpected eol");
+	}
+
+	char *command = strsep(&line, " ");
+	if (!strcmp(command, "001") || !strcmp(command, "INVITE")) {
+		clientFormat(client, "JOIN :%s\r\n", chan);
+	} else if (!strcmp(command, "PING")) {
+		clientFormat(client, "PONG %s\r\n", line);
+	}
+	if (strcmp(command, "PRIVMSG") && strcmp(command, "NOTICE")) return;
+
+	if (!prefix) errx(EX_PROTOCOL, "message without prefix");
+	char *nick = strsep(&prefix, "!");
+
+	if (!line) errx(EX_PROTOCOL, "message without destination");
+	char *dest = strsep(&line, " ");
+	if (strcmp(dest, chan)) return;
+
+	if (!line || line[0] != ':') errx(EX_PROTOCOL, "message without message");
+	line = &line[1];
+
+	if (!strncmp(line, "\1ACTION ", 8)) {
+		line = &line[8];
+		size_t len = strcspn(line, "\1");
+		printf("* %c\u200C%s %.*s\n", nick[0], &nick[1], (int)len, line);
+	} else if (command[0] == 'N') {
+		printf("-%c\u200C%s- %s\n", nick[0], &nick[1], line);
+	} else {
+		printf("<%c\u200C%s> %s\n", nick[0], &nick[1], line);
+	}
+}
+
+#ifdef __FreeBSD__
+static void limit(int fd, const cap_rights_t *rights) {
+	int error = cap_rights_limit(fd, rights);
+	if (error) err(EX_OSERR, "cap_rights_limit");
+}
+#endif
+
+int main(int argc, char *argv[]) {
+	int error;
+
+	if (argc < 5) return EX_USAGE;
+	const char *host = argv[1];
+	const char *port = argv[2];
+	const char *nick = argv[3];
+	const char *chan = argv[4];
+
+	setlinebuf(stdout);
+	signal(SIGPIPE, SIG_IGN);
+
+	struct tls_config *config = tls_config_new();
+	if (!config) errx(EX_SOFTWARE, "tls_config_new");
+
+	error = tls_config_set_ciphers(config, "compat");
+	if (error) {
+		errx(EX_SOFTWARE, "tls_config_set_ciphers: %s", tls_config_error(config));
+	}
+
+	struct tls *client = tls_client();
+	if (!client) errx(EX_SOFTWARE, "tls_client");
+
+	error = tls_configure(client, config);
+	if (error) errx(EX_SOFTWARE, "tls_configure: %s", tls_error(client));
+	tls_config_free(config);
+
+	struct addrinfo *head;
+	struct addrinfo hints = {
+		.ai_family = AF_UNSPEC,
+		.ai_socktype = SOCK_STREAM,
+		.ai_protocol = IPPROTO_TCP,
+	};
+	error = getaddrinfo(host, port, &hints, &head);
+	if (error) errx(EX_NOHOST, "getaddrinfo: %s", gai_strerror(error));
+
+	int sock = -1;
+	for (struct addrinfo *ai = head; ai; ai = ai->ai_next) {
+		sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+		if (sock < 0) err(EX_OSERR, "socket");
+
+		error = connect(sock, ai->ai_addr, ai->ai_addrlen);
+		if (!error) break;
+
+		close(sock);
+		sock = -1;
+	}
+	if (sock < 0) err(EX_UNAVAILABLE, "connect");
+	freeaddrinfo(head);
+
+	error = tls_connect_socket(client, sock, host);
+	if (error) errx(EX_PROTOCOL, "tls_connect: %s", tls_error(client));
+
+#ifdef __FreeBSD__
+	error = cap_enter();
+	if (error) err(EX_OSERR, "cap_enter");
+
+	cap_rights_t rights;
+	cap_rights_init(&rights, CAP_WRITE);
+	limit(STDOUT_FILENO, &rights);
+	limit(STDERR_FILENO, &rights);
+
+	cap_rights_init(&rights, CAP_EVENT, CAP_READ);
+	limit(STDIN_FILENO, &rights);
+
+	cap_rights_set(&rights, CAP_WRITE);
+	limit(sock, &rights);
+#endif
+
+	clientFormat(client, "NICK :%s\r\nUSER %s 0 * :%s\r\n", nick, nick, nick);
+
+	char *input = NULL;
+	size_t cap = 0;
+
+	char buf[4096];
+	size_t len = 0;
+
+	struct pollfd fds[2] = {
+		{ .events = POLLIN, .fd = STDIN_FILENO },
+		{ .events = POLLIN, .fd = sock },
+	};
+	while (0 < poll(fds, 2, -1)) {
+		if (fds[0].revents) {
+			ssize_t len = getline(&input, &cap, stdin);
+			if (len < 0) err(EX_IOERR, "getline");
+			input[len - 1] = '\0';
+			clientFormat(client, "NOTICE %s :%s\r\n", chan, input);
+		}
+		if (!fds[1].revents) continue;
+
+		ssize_t read = tls_read(client, &buf[len], sizeof(buf) - len);
+		if (read == TLS_WANT_POLLIN || read == TLS_WANT_POLLOUT) continue;
+		if (read < 0) errx(EX_IOERR, "tls_read: %s", tls_error(client));
+		if (!read) return EX_UNAVAILABLE;
+		len += read;
+
+		char *crlf;
+		char *line = buf;
+		for (;;) {
+			crlf = memmem(line, &buf[len] - line, "\r\n", 2);
+			if (!crlf) break;
+			crlf[0] = '\0';
+			clientHandle(client, chan, line);
+			line = &crlf[2];
+		}
+		len -= line - buf;
+		memmove(buf, line, len);
+	}
+	err(EX_IOERR, "poll");
+}
diff --git a/bin/scheme.c b/bin/scheme.c
new file mode 100644
index 00000000..ec5a2eec
--- /dev/null
+++ b/bin/scheme.c
@@ -0,0 +1,256 @@
+/* Copyright (C) 2018, 2019  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "png.h"
+
+typedef unsigned uint;
+typedef unsigned char byte;
+
+struct HSV {
+	double h, s, v;
+};
+
+struct RGB {
+	byte r, g, b;
+};
+
+static struct RGB convert(struct HSV o) {
+	double c = o.v * o.s;
+	double h = o.h / 60.0;
+	double x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
+	double m = o.v - c;
+	double r = m, g = m, b = m;
+	if      (h <= 1.0) { r += c; g += x; }
+	else if (h <= 2.0) { r += x; g += c; }
+	else if (h <= 3.0) { g += c; b += x; }
+	else if (h <= 4.0) { g += x; b += c; }
+	else if (h <= 5.0) { r += x; b += c; }
+	else if (h <= 6.0) { r += c; b += x; }
+	return (struct RGB) { r * 255.0, g * 255.0, b * 255.0 };
+}
+
+static const struct HSV
+R = {   0.0, 1.0, 1.0 },
+Y = {  60.0, 1.0, 1.0 },
+G = { 120.0, 1.0, 1.0 },
+C = { 180.0, 1.0, 1.0 },
+B = { 240.0, 1.0, 1.0 },
+M = { 300.0, 1.0, 1.0 };
+
+static struct HSV x(struct HSV o, double hd, double sf, double vf) {
+	return (struct HSV) {
+		fmod(o.h + hd, 360.0),
+		fmin(o.s * sf, 1.0),
+		fmin(o.v * vf, 1.0),
+	};
+}
+
+enum {
+	Black, Red, Green, Yellow, Blue, Magenta, Cyan, White,
+	Dark = 0,
+	Light = 8,
+	Background = 16,
+	Foreground,
+	Bold,
+	Selection,
+	Cursor,
+	SchemeLen,
+};
+static struct HSV scheme[SchemeLen];
+static struct HSV *dark = &scheme[Dark];
+static struct HSV *light = &scheme[Light];
+
+static void generate(void) {
+	light[Black]   = x(R, +45.0, 0.3, 0.3);
+	light[Red]     = x(R, +10.0, 0.9, 0.8);
+	light[Green]   = x(G, -55.0, 0.8, 0.6);
+	light[Yellow]  = x(Y, -20.0, 0.8, 0.8);
+	light[Blue]    = x(B, -55.0, 0.4, 0.5);
+	light[Magenta] = x(M, +45.0, 0.4, 0.6);
+	light[Cyan]    = x(C, -60.0, 0.3, 0.6);
+	light[White]   = x(R, +45.0, 0.3, 0.8);
+
+	dark[Black] = x(light[Black], 0.0, 1.0, 0.3);
+	dark[White] = x(light[White], 0.0, 1.0, 0.7);
+	for (uint i = Red; i < White; ++i) {
+		dark[i] = x(light[i], 0.0, 1.0, 0.8);
+	}
+
+	scheme[Background] = x(dark[Black],  0.0, 1.0, 0.9);
+	scheme[Foreground] = x(light[White], 0.0, 1.0, 0.9);
+	scheme[Bold]       = x(light[White], 0.0, 1.0, 1.0);
+	scheme[Selection]  = x(light[Red], +10.0, 1.0, 0.8);
+	scheme[Cursor]     = x(dark[White],  0.0, 1.0, 0.8);
+}
+
+static void swap(struct HSV *a, struct HSV *b) {
+	struct HSV c = *a;
+	*a = *b;
+	*b = c;
+}
+
+static void invert(void) {
+	swap(&dark[Black], &light[White]);
+	swap(&dark[White], &light[Black]);
+}
+
+typedef void OutputFn(const struct HSV *hsv, uint len);
+
+static void outputHSV(const struct HSV *hsv, uint len) {
+	for (uint i = 0; i < len; ++i) {
+		printf("%g,%g,%g\n", hsv[i].h, hsv[i].s, hsv[i].v);
+	}
+}
+
+#define FORMAT_RGB "%02hhX%02hhX%02hhX"
+
+static void outputRGB(const struct HSV *hsv, uint len) {
+	for (uint i = 0; i < len; ++i) {
+		struct RGB rgb = convert(hsv[i]);
+		printf(FORMAT_RGB "\n", rgb.r, rgb.g, rgb.b);
+	}
+}
+
+static void outputLinux(const struct HSV *hsv, uint len) {
+	for (uint i = 0; i < len; ++i) {
+		struct RGB rgb = convert(hsv[i]);
+		printf("\x1B]P%X" FORMAT_RGB, i, rgb.r, rgb.g, rgb.b);
+	}
+}
+
+static const char *Enum[SchemeLen] = {
+	"DarkBlack", "DarkRed", "DarkGreen", "DarkYellow",
+	"DarkBlue", "DarkMagenta", "DarkCyan", "DarkWhite",
+	"LightBlack", "LightRed", "LightGreen", "LightYellow",
+	"LightBlue", "LightMagenta", "LightCyan", "LightWhite",
+	"Background", "Foreground", "Bold", "Selection", "Cursor",
+};
+
+static void outputEnum(const struct HSV *hsv, uint len) {
+	printf("enum {\n");
+	for (uint i = 0; i < len; ++i) {
+		struct RGB rgb = convert(hsv[i]);
+		printf("\t%s = 0x" FORMAT_RGB ",\n", Enum[i], rgb.r, rgb.g, rgb.b);
+	}
+	printf("};\n");
+}
+
+static const char *Mintty[SchemeLen] = {
+	"Black", "Red", "Green", "Yellow",
+	"Blue", "Magenta", "Cyan", "White",
+	"BoldBlack", "BoldRed", "BoldGreen", "BoldYellow",
+	"BoldBlue", "BoldMagenta", "BoldCyan", "BoldWhite",
+	[Background] = "BackgroundColour",
+	[Foreground] = "ForegroundColour",
+	[Cursor]     = "CursorColour",
+};
+
+static void outputMintty(const struct HSV *hsv, uint len) {
+	for (uint i = 0; i < len; ++i) {
+		if (!Mintty[i]) continue;
+		struct RGB rgb = convert(hsv[i]);
+		printf("%s=%hhu,%hhu,%hhu\n", Mintty[i], rgb.r, rgb.g, rgb.b);
+	}
+}
+
+static void outputCSS(const struct HSV *hsv, uint len) {
+	printf(":root {\n");
+	for (uint i = 0; i < len; ++i) {
+		struct RGB rgb = convert(hsv[i]);
+		printf("\t--ansi%u: #" FORMAT_RGB ";\n", i, rgb.r, rgb.g, rgb.b);
+	}
+	printf("}\n");
+	for (uint i = 0; i < len; ++i) {
+		printf(
+			".fg%u { color: var(--ansi%u); }\n"
+			".bg%u { background-color: var(--ansi%u); }\n",
+			i, i, i, i
+		);
+	}
+}
+
+enum {
+	SwatchWidth = 64,
+	SwatchHeight = 64,
+	SwatchCols = 8,
+};
+
+static void outputPNG(const struct HSV *hsv, uint len) {
+	uint rows = (len + SwatchCols - 1) / SwatchCols;
+	uint width = SwatchWidth * SwatchCols;
+	uint height = SwatchHeight * rows;
+	pngHead(stdout, width, height, 8, PNGIndexed);
+
+	struct RGB pal[len];
+	for (uint i = 0; i < len; ++i) {
+		pal[i] = convert(hsv[i]);
+	}
+	pngPalette(stdout, (byte *)pal, sizeof(pal));
+
+	byte data[height][1 + width];
+	memset(data, 0, sizeof(data));
+	for (uint y = 0; y < height; ++y) {
+		data[y][0] = (y % SwatchHeight ? PNGUp : PNGSub);
+	}
+	for (uint i = 0; i < len; ++i) {
+		uint y = SwatchHeight * (i / SwatchCols);
+		uint x = SwatchWidth * (i % SwatchCols);
+		data[y][1 + x] = (x ? 1 : i);
+	}
+	pngData(stdout, (byte *)data, sizeof(data));
+	pngTail(stdout);
+}
+
+int main(int argc, char *argv[]) {
+	generate();
+
+	OutputFn *output = outputRGB;
+	const struct HSV *hsv = scheme;
+	uint len = 16;
+
+	int opt;
+	while (0 < (opt = getopt(argc, argv, "acghilmp:stx"))) {
+		switch (opt) {
+			break; case 'a': len = 16;
+			break; case 'c': output = outputEnum;
+			break; case 'g': output = outputPNG;
+			break; case 'h': output = outputHSV;
+			break; case 'i': invert();
+			break; case 'l': output = outputLinux;
+			break; case 'm': output = outputMintty;
+			break; case 'p': {
+				uint p = strtoul(optarg, NULL, 0);
+				if (p >= SchemeLen) return EX_USAGE;
+				hsv = &scheme[p];
+				len = 1;
+			}
+			break; case 's': output = outputCSS;
+			break; case 't': len = SchemeLen;
+			break; case 'x': output = outputRGB;
+			break; default:  return EX_USAGE;
+		}
+	}
+
+	output(hsv, len);
+}
diff --git a/bin/shotty.c b/bin/shotty.c
new file mode 100644
index 00000000..83b00313
--- /dev/null
+++ b/bin/shotty.c
@@ -0,0 +1,648 @@
+/* Copyright (C) 2019  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <locale.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <wchar.h>
+
+#define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit
+
+typedef unsigned uint;
+
+enum {
+	NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL,
+	BS, HT, NL, VT, NP, CR, SO, SI,
+	DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB,
+	CAN, EM, SUB, ESC, FS, GS, RS, US,
+	DEL = 0x7F,
+};
+
+enum Attr {
+	BIT(Bold),
+	BIT(Dim),
+	BIT(Italic),
+	BIT(Underline),
+	BIT(Blink),
+	BIT(Reverse),
+};
+
+struct Style {
+	enum Attr attr;
+	int bg, fg;
+};
+
+struct Cell {
+	struct Style style;
+	wchar_t ch;
+};
+
+static uint rows = 24, cols = 80;
+static struct Cell *cells;
+
+static struct Cell *cell(uint y, uint x) {
+	assert(y <= rows);
+	assert(x <= cols);
+	assert(y * cols + x <= rows * cols);
+	return &cells[y * cols + x];
+}
+
+static uint y, x;
+static struct Style style = { .bg = -1, .fg = -1 };
+
+static struct {
+	uint y, x;
+} save;
+
+enum { ParamCap = 16 };
+static struct {
+	uint s[ParamCap];
+	uint n, i;
+} param;
+
+static uint p(uint i, uint z) {
+	return (i < param.n ? param.s[i] : z);
+}
+
+static uint min(uint a, uint b) {
+	return (a < b ? a : b);
+}
+
+#define _ch ch __attribute__((unused))
+typedef void Action(wchar_t ch);
+
+static void nop(wchar_t _ch) {
+}
+
+static void csi(wchar_t _ch) {
+	memset(&param, 0, sizeof(param));
+}
+
+static void csiSep(wchar_t _ch) {
+	if (param.n == ParamCap) return;
+	if (!param.n) param.n++;
+	param.n++;
+	param.i++;
+}
+
+static void csiDigit(wchar_t ch) {
+	param.s[param.i] *= 10;
+	param.s[param.i] += ch - L'0';
+	if (!param.n) param.n++;
+}
+
+static void bs(wchar_t _ch)  { if (x) x--; }
+static void ht(wchar_t _ch)  { x = min(x - x % 8 + 8, cols - 1); }
+static void cr(wchar_t _ch)  { x = 0; }
+static void cuu(wchar_t _ch) { y -= min(p(0, 1), y); }
+static void cud(wchar_t _ch) { y  = min(y + p(0, 1), rows - 1); }
+static void cuf(wchar_t _ch) { x  = min(x + p(0, 1), cols - 1); }
+static void cub(wchar_t _ch) { x -= min(p(0, 1), x); }
+static void cnl(wchar_t _ch) { x = 0; cud(0); }
+static void cpl(wchar_t _ch) { x = 0; cuu(0); }
+static void cha(wchar_t _ch) { x = min(p(0, 1) - 1, cols - 1); }
+static void vpa(wchar_t _ch) { y = min(p(0, 1) - 1, rows - 1); }
+static void cup(wchar_t _ch) {
+	y = min(p(0, 1) - 1, rows - 1);
+	x = min(p(1, 1) - 1, cols - 1);
+}
+static void decsc(wchar_t _ch) {
+	save.y = y;
+	save.x = x;
+}
+static void decrc(wchar_t _ch) {
+	y = save.y;
+	x = save.x;
+}
+
+static void move(struct Cell *dst, struct Cell *src, size_t len) {
+	memmove(dst, src, sizeof(*dst) * len);
+}
+
+static void erase(struct Cell *at, struct Cell *to) {
+	for (; at < to; ++at) {
+		at->style = style;
+		at->ch = L' ';
+	}
+}
+
+static void ed(wchar_t _ch) {
+	erase(
+		(p(0, 0) == 0 ? cell(y, x) : cell(0, 0)),
+		(p(0, 0) == 1 ? cell(y, x) : cell(rows - 1, cols))
+	);
+}
+static void el(wchar_t _ch) {
+	erase(
+		(p(0, 0) == 0 ? cell(y, x) : cell(y, 0)),
+		(p(0, 0) == 1 ? cell(y, x) : cell(y, cols))
+	);
+}
+static void ech(wchar_t _ch) {
+	erase(cell(y, x), cell(y, min(x + p(0, 1), cols)));
+}
+
+static void dch(wchar_t _ch) {
+	uint n = min(p(0, 1), cols - x);
+	move(cell(y, x), cell(y, x + n), cols - x - n);
+	erase(cell(y, cols - n), cell(y, cols));
+}
+static void ich(wchar_t _ch) {
+	uint n = min(p(0, 1), cols - x);
+	move(cell(y, x + n), cell(y, x), cols - x - n);
+	erase(cell(y, x), cell(y, x + n));
+}
+
+static struct {
+	uint top, bot;
+} scroll;
+
+static void scrollUp(uint top, uint n) {
+	n = min(n, scroll.bot - top);
+	move(cell(top, 0), cell(top + n, 0), cols * (scroll.bot - top - n));
+	erase(cell(scroll.bot - n, 0), cell(scroll.bot, 0));
+}
+
+static void scrollDown(uint top, uint n) {
+	n = min(n, scroll.bot - top);
+	move(cell(top + n, 0), cell(top, 0), cols * (scroll.bot - top - n));
+	erase(cell(top, 0), cell(top + n, 0));
+}
+
+static void decstbm(wchar_t _ch) {
+	scroll.bot = min(p(1, rows), rows);
+	scroll.top = min(p(0, 1) - 1, scroll.bot);
+}
+
+static void su(wchar_t _ch) { scrollUp(scroll.top, p(0, 1)); }
+static void sd(wchar_t _ch) { scrollDown(scroll.top, p(0, 1)); }
+static void dl(wchar_t _ch) { scrollUp(min(y, scroll.bot), p(0, 1)); }
+static void il(wchar_t _ch) { scrollDown(min(y, scroll.bot), p(0, 1)); }
+
+static void nl(wchar_t _ch) {
+	if (y + 1 == scroll.bot) {
+		scrollUp(scroll.top, 1);
+	} else {
+		y = min(y + 1, rows - 1);
+	}
+}
+static void ri(wchar_t _ch) {
+	if (y == scroll.top) {
+		scrollDown(scroll.top, 1);
+	} else {
+		if (y) y--;
+	}
+}
+
+static enum Mode {
+	BIT(Insert),
+	BIT(Wrap),
+	BIT(Cursor),
+} mode = Wrap | Cursor;
+
+static enum Mode paramMode(void) {
+	enum Mode mode = 0;
+	for (uint i = 0; i < param.n; ++i) {
+		switch (param.s[i]) {
+			break; case 4: mode |= Insert;
+			break; default: warnx("unhandled SM/RM %u", param.s[i]);
+		}
+	}
+	return mode;
+}
+
+static enum Mode paramDECMode(void) {
+	enum Mode mode = 0;
+	for (uint i = 0; i < param.n; ++i) {
+		switch (param.s[i]) {
+			break; case 1: // DECCKM
+			break; case 7: mode |= Wrap;
+			break; case 12: // "Start Blinking Cursor"
+			break; case 25: mode |= Cursor;
+			break; default: {
+				if (param.s[i] < 1000) {
+					warnx("unhandled DECSET/DECRST %u", param.s[i]);
+				}
+			}
+		}
+	}
+	return mode;
+}
+
+static void sm(wchar_t _ch) { mode |= paramMode(); }
+static void rm(wchar_t _ch) { mode &= ~paramMode(); }
+static void decset(wchar_t _ch) { mode |= paramDECMode(); }
+static void decrst(wchar_t _ch) { mode &= ~paramDECMode(); }
+
+enum {
+	Reset,
+	SetBold,
+	SetDim,
+	SetItalic,
+	SetUnderline,
+	SetBlink,
+	SetReverse = 7,
+
+	UnsetBoldDim = 22,
+	UnsetItalic,
+	UnsetUnderline,
+	UnsetBlink,
+	UnsetReverse = 27,
+
+	SetFg0 = 30,
+	SetFg7 = 37,
+	SetFg,
+	ResetFg,
+	SetBg0 = 40,
+	SetBg7 = 47,
+	SetBg,
+	ResetBg,
+
+	SetFg8 = 90,
+	SetFgF = 97,
+	SetBg8 = 100,
+	SetBgF = 107,
+
+	Color256 = 5,
+};
+
+static void sgr(wchar_t _ch) {
+	uint n = param.i + 1;
+	for (uint i = 0; i < n; ++i) {
+		switch (param.s[i]) {
+			break; case Reset: style = (struct Style) { .bg = -1, .fg = -1 };
+
+			break; case SetBold:      style.attr |= Bold; style.attr &= ~Dim;
+			break; case SetDim:       style.attr |= Dim; style.attr &= ~Bold;
+			break; case SetItalic:    style.attr |= Italic;
+			break; case SetUnderline: style.attr |= Underline;
+			break; case SetBlink:     style.attr |= Blink;
+			break; case SetReverse:   style.attr |= Reverse;
+
+			break; case UnsetBoldDim:   style.attr &= ~(Bold | Dim);
+			break; case UnsetItalic:    style.attr &= ~Italic;
+			break; case UnsetUnderline: style.attr &= ~Underline;
+			break; case UnsetBlink:     style.attr &= ~Blink;
+			break; case UnsetReverse:   style.attr &= ~Reverse;
+
+			break; case SetFg: {
+				if (++i < n && param.s[i] == Color256) {
+					if (++i < n) style.fg = param.s[i];
+				}
+			}
+			break; case SetBg: {
+				if (++i < n && param.s[i] == Color256) {
+					if (++i < n) style.bg = param.s[i];
+				}
+			}
+
+			break; case ResetFg: style.fg = -1;
+			break; case ResetBg: style.bg = -1;
+
+			break; default: {
+				uint p = param.s[i];
+				if (p >= SetFg0 && p <= SetFg7) {
+					style.fg = p - SetFg0;
+				} else if (p >= SetBg0 && p <= SetBg7) {
+					style.bg = p - SetBg0;
+				} else if (p >= SetFg8 && p <= SetFgF) {
+					style.fg = 8 + p - SetFg8;
+				} else if (p >= SetBg8 && p <= SetBgF) {
+					style.bg = 8 + p - SetBg8;
+				} else {
+					warnx("unhandled SGR %u", p);
+				}
+			}
+		}
+	}
+}
+
+static enum {
+	USASCII,
+	DECSpecial,
+} charset;
+
+static void usascii(wchar_t _ch) { charset = USASCII; }
+static void decSpecial(wchar_t _ch) { charset = DECSpecial; }
+
+static const wchar_t AltCharset[128] = {
+	['`'] = L'â—†', ['a'] = L'â–’', ['f'] = L'°', ['g'] = L'±', ['i'] = L'â‹',
+	['j'] = L'┘', ['k'] = L'â”', ['l'] = L'┌', ['m'] = L'â””', ['n'] = L'┼',
+	['o'] = L'⎺', ['p'] = L'⎻', ['q'] = L'─', ['r'] = L'⎼', ['s'] = L'⎽',
+	['t'] = L'├', ['u'] = L'┤', ['v'] = L'┴', ['w'] = L'┬', ['x'] = L'│',
+	['y'] = L'≤', ['z'] = L'≥', ['{'] = L'π', ['|'] = L'≠', ['}'] = L'£',
+	['~'] = L'·',
+};
+
+static void add(wchar_t ch) {
+	if (charset == DECSpecial && ch < 128 && AltCharset[ch]) {
+		ch = AltCharset[ch];
+	}
+
+	int width = wcwidth(ch);
+	if (width < 0) {
+		warnx("unhandled \\u%02X", ch);
+		return;
+	}
+
+	if (mode & Insert) {
+		uint n = min(width, cols - x);
+		move(cell(y, x + n), cell(y, x), cols - x - n);
+	}
+	if (mode & Wrap && x + width > cols) {
+		cr(0);
+		nl(0);
+	}
+
+	cell(y, x)->style = style;
+	cell(y, x)->ch = ch;
+	for (int i = 1; i < width && x + i < cols; ++i) {
+		cell(y, x + i)->style = style;
+		cell(y, x + i)->ch = L'\0';
+	}
+	x = min(x + width, (mode & Wrap ? cols : cols - 1));
+}
+
+static void html(void);
+static void mc(wchar_t _ch) {
+	if (p(0, 0) == 10) {
+		html();
+	} else {
+		warnx("unhandled CSI %u MC", p(0, 0));
+	}
+}
+
+static enum {
+	Data,
+	Esc,
+	G0,
+	CSI,
+	CSILt,
+	CSIEq,
+	CSIGt,
+	CSIQm,
+	CSIInter,
+	OSC,
+	OSCEsc,
+} state;
+
+static void escDefault(wchar_t ch) {
+	warnx("unhandled ESC %lc", ch);
+}
+
+static void g0Default(wchar_t ch) {
+	warnx("unhandled G0 %lc", ch);
+	charset = USASCII;
+}
+
+static void csiInter(wchar_t ch) {
+	switch (state) {
+		break; case CSI: warnx("unhandled CSI %lc ...", ch);
+		break; case CSILt: warnx("unhandled CSI < %lc ...", ch);
+		break; case CSIEq: warnx("unhandled CSI = %lc ...", ch);
+		break; case CSIGt: warnx("unhandled CSI > %lc ...", ch);
+		break; case CSIQm: warnx("unhandled CSI ? %lc ...", ch);
+		break; default: abort();
+	}
+}
+
+static void csiFinal(wchar_t ch) {
+	switch (state) {
+		break; case CSI: warnx("unhandled CSI %lc", ch);
+		break; case CSILt: warnx("unhandled CSI < %lc", ch);
+		break; case CSIEq: warnx("unhandled CSI = %lc", ch);
+		break; case CSIGt: warnx("unhandled CSI > %lc", ch);
+		break; case CSIQm: warnx("unhandled CSI ? %lc", ch);
+		break; case CSIInter: warnx("unhandled CSI ... %lc", ch);
+		break; default: abort();
+	}
+}
+
+#define S(s) break; case s: switch (ch)
+#define A(c, a, s) break; case c: a(ch); state = s
+#define D(a, s) break; default: a(ch); state = s
+static void update(wchar_t ch) {
+	switch (state) {
+		default: abort();
+
+		S(Data) {
+			A(BEL, nop, Data);
+			A(BS,  bs,  Data);
+			A(HT,  ht,  Data);
+			A(NL,  nl,  Data);
+			A(CR,  cr,  Data);
+			A(ESC, nop, Esc);
+			D(add, Data);
+		}
+
+		S(Esc) {
+			A('(', nop, G0);
+			A('7', decsc, Data);
+			A('8', decrc, Data);
+			A('=', nop, Data);
+			A('>', nop, Data);
+			A('M', ri,  Data);
+			A('[', csi, CSI);
+			A(']', nop, OSC);
+			D(escDefault, Data);
+		}
+		S(G0) {
+			A('0', decSpecial, Data);
+			A('B', usascii, Data);
+			D(g0Default, Data);
+		}
+
+		S(CSI) {
+			A(' ' ... '/', csiInter, CSIInter);
+			A('0' ... '9', csiDigit, CSI);
+			A(':', nop, CSI);
+			A(';', csiSep, CSI);
+			A('<', nop, CSILt);
+			A('=', nop, CSIEq);
+			A('>', nop, CSIGt);
+			A('?', nop, CSIQm);
+			A('@', ich, Data);
+			A('A', cuu, Data);
+			A('B', cud, Data);
+			A('C', cuf, Data);
+			A('D', cub, Data);
+			A('E', cnl, Data);
+			A('F', cpl, Data);
+			A('G', cha, Data);
+			A('H', cup, Data);
+			A('J', ed,  Data);
+			A('K', el,  Data);
+			A('L', il,  Data);
+			A('M', dl,  Data);
+			A('P', dch, Data);
+			A('S', su,  Data);
+			A('T', sd,  Data);
+			A('X', ech, Data);
+			A('d', vpa, Data);
+			A('h', sm,  Data);
+			A('i', mc,  Data);
+			A('l', rm,  Data);
+			A('m', sgr, Data);
+			A('r', decstbm, Data);
+			A('t', nop, Data);
+			D(csiFinal, Data);
+		}
+
+		S(CSILt ... CSIGt) {
+			A(' ' ... '/', csiInter, CSIInter);
+			A('0' ... '9', csiDigit, state);
+			A(':', nop, state);
+			A(';', csiSep, state);
+			D(csiFinal, Data);
+		}
+
+		S(CSIQm) {
+			A(' ' ... '/', csiInter, CSIInter);
+			A('0' ... '9', csiDigit, CSIQm);
+			A(':', nop, CSIQm);
+			A(';', csiSep, CSIQm);
+			A('h', decset, Data);
+			A('l', decrst, Data);
+			D(csiFinal, Data);
+		}
+
+		S(CSIInter) {
+			D(csiFinal, Data);
+		}
+
+		S(OSC) {
+			A(BEL, nop, Data);
+			A(ESC, nop, OSCEsc);
+			D(nop, OSC);
+		}
+		S(OSCEsc) {
+			A('\\', nop, Data);
+			D(nop, OSC);
+		}
+	}
+}
+
+static bool bright;
+static int defaultBg = 0;
+static int defaultFg = 7;
+
+static void span(const struct Style *prev, const struct Cell *cell) {
+	struct Style style = cell->style;
+	if (!prev || memcmp(prev, &style, sizeof(*prev))) {
+		if (prev) printf("</span>");
+		if (style.bg < 0) style.bg = defaultBg;
+		if (style.fg < 0) style.fg = defaultFg;
+		if (bright && style.attr & Bold) {
+			if (style.fg < 8) style.fg += 8;
+			style.attr ^= Bold;
+		}
+		printf(
+			"<span style=\"%s%s%s\" class=\"bg%u fg%u\">",
+			(style.attr & Bold ? "font-weight:bold;" : ""),
+			(style.attr & Italic ? "font-style:italic;" : ""),
+			(style.attr & Underline ? "text-decoration:underline;" : ""),
+			(style.attr & Reverse ? style.fg : style.bg),
+			(style.attr & Reverse ? style.bg : style.fg)
+		);
+	}
+	switch (cell->ch) {
+		break; case '&': printf("&amp;");
+		break; case '<': printf("&lt;");
+		break; case '>': printf("&gt;");
+		break; default:  printf("%lc", (wint_t)cell->ch);
+	}
+}
+
+static bool mediaCopy;
+static void html(void) {
+	mediaCopy = true;
+	if (mode & Cursor) cell(y, x)->style.attr ^= Reverse;
+	printf(
+		"<pre style=\"width: %uch;\" class=\"bg%u fg%u\">",
+		cols, defaultBg, defaultFg
+	);
+	for (uint y = 0; y < rows; ++y) {
+		for (uint x = 0; x < cols; ++x) {
+			if (!cell(y, x)->ch) continue;
+			span(x ? &cell(y, x - 1)->style : NULL, cell(y, x));
+		}
+		printf("</span>\n");
+	}
+	printf("</pre>\n");
+	if (mode & Cursor) cell(y, x)->style.attr ^= Reverse;
+}
+
+int main(int argc, char *argv[]) {
+	setlocale(LC_CTYPE, "");
+
+	bool debug = false;
+	bool size = false;
+	bool hide = false;
+
+	int opt;
+	while (0 < (opt = getopt(argc, argv, "Bb:df:h:nsw:"))) {
+		switch (opt) {
+			break; case 'B': bright = true;
+			break; case 'b': defaultBg = strtol(optarg, NULL, 0);
+			break; case 'd': debug = true;
+			break; case 'f': defaultFg = strtol(optarg, NULL, 0);
+			break; case 'h': rows = strtoul(optarg, NULL, 0);
+			break; case 'n': hide = true;
+			break; case 's': size = true;
+			break; case 'w': cols = strtoul(optarg, NULL, 0);
+			break; default:  return EX_USAGE;
+		}
+	}
+
+	FILE *file = stdin;
+	if (optind < argc) {
+		file = fopen(argv[optind], "r");
+		if (!file) err(EX_NOINPUT, "%s", argv[optind]);
+	}
+
+	if (size) {
+		struct winsize window;
+		int error = ioctl(STDERR_FILENO, TIOCGWINSZ, &window);
+		if (error) err(EX_IOERR, "ioctl");
+		rows = window.ws_row;
+		cols = window.ws_col;
+	}
+	scroll.bot = rows;
+
+	cells = calloc(rows * cols, sizeof(*cells));
+	if (!cells) err(EX_OSERR, "calloc");
+	erase(cell(0, 0), cell(rows - 1, cols));
+
+	wint_t ch;
+	while (WEOF != (ch = getwc(file))) {
+		uint prev = state;
+		update(ch);
+		if (debug && state != prev && state == Data) html();
+	}
+	if (ferror(file)) err(EX_IOERR, "getwc");
+
+	if (!mediaCopy) {
+		if (hide) mode &= ~Cursor;
+		html();
+	}
+}
diff --git a/bin/title.c b/bin/title.c
new file mode 100644
index 00000000..82f89d95
--- /dev/null
+++ b/bin/title.c
@@ -0,0 +1,211 @@
+/* Copyright (C) 2019  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <curl/curl.h>
+#include <err.h>
+#include <locale.h>
+#include <regex.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <wchar.h>
+
+static regex_t regex(const char *pattern, int flags) {
+	regex_t regex;
+	int error = regcomp(&regex, pattern, REG_EXTENDED | flags);
+	if (!error) return regex;
+
+	char buf[256];
+	regerror(error, &regex, buf, sizeof(buf));
+	errx(EX_SOFTWARE, "regcomp: %s: %s", buf, pattern);
+}
+
+static const struct Entity {
+	wchar_t ch;
+	const char *name;
+} Entities[] = {
+	{ L'"', "&quot;" },
+	{ L'&', "&amp;" },
+	{ L'<', "&lt;" },
+	{ L'>', "&gt;" },
+	{ L'â¤', "&#10;" },
+};
+
+static wchar_t entity(const char *name) {
+	for (size_t i = 0; i < sizeof(Entities) / sizeof(Entities[0]); ++i) {
+		struct Entity entity = Entities[i];
+		if (strncmp(name, entity.name, strlen(entity.name))) continue;
+		return entity.ch;
+	}
+	if (!strncmp(name, "&#x", 3)) return strtoul(&name[3], NULL, 16);
+	if (!strncmp(name, "&#", 2)) return strtoul(&name[2], NULL, 10);
+	return 0;
+}
+
+static const char EntityPattern[] = {
+	"[[:space:]]+|&([[:alpha:]]+|#([[:digit:]]+|x[[:xdigit:]]+));"
+};
+static regex_t EntityRegex;
+
+static void showTitle(const char *title) {
+	regmatch_t match = {0};
+	for (; *title; title += match.rm_eo) {
+		if (regexec(&EntityRegex, title, 1, &match, 0)) break;
+		if (title[match.rm_so] != '&') {
+			printf("%.*s ", (int)match.rm_so, title);
+			continue;
+		}
+		wchar_t ch = entity(&title[match.rm_so]);
+		if (ch) {
+			printf("%.*s%lc", (int)match.rm_so, title, (wint_t)ch);
+		} else {
+			printf("%.*s", (int)match.rm_eo, title);
+		}
+	}
+	printf("%s\n", title);
+}
+
+static CURL *curl;
+static bool title;
+static struct {
+	char buf[64 * 1024];
+	size_t len;
+} body;
+
+// HE COMES
+static const char TitlePattern[] = "<title>([^<]*)</title>";
+static regex_t TitleRegex;
+
+static size_t handleBody(char *buf, size_t size, size_t nitems, void *user) {
+	(void)user;
+	size_t len = size * nitems;
+	size_t cap = sizeof(body.buf) - body.len - 1;
+	size_t new = (len < cap ? len : cap);
+	if (title || !new) return len;
+
+	memcpy(&body.buf[body.len], buf, new);
+	body.len += new;
+	body.buf[body.len] = '\0';
+
+	regmatch_t match[2];
+	if (regexec(&TitleRegex, body.buf, 2, match, 0)) return len;
+	body.buf[match[1].rm_eo] = '\0';
+	showTitle(&body.buf[match[1].rm_so]);
+	title = true;
+
+	return len;
+}
+
+static CURLcode fetchTitle(const char *url) {
+	CURLcode code = curl_easy_setopt(curl, CURLOPT_URL, url);
+	if (code) return code;
+
+	curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
+	code = curl_easy_perform(curl);
+	if (code) return code;
+
+	char *type;
+	code = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &type);
+	if (code) return code;
+	if (!type || strncmp(type, "text/html", 9)) return CURLE_OK;
+
+	char *dest;
+	curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &dest);
+	dest = strdup(dest);
+	if (!dest) err(EX_OSERR, "strdup");
+
+	code = curl_easy_setopt(curl, CURLOPT_URL, dest);
+	if (code) return code;
+	free(dest);
+
+	body.len = 0;
+	title = false;
+	curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
+	code = curl_easy_perform(curl);
+	return code;
+}
+
+int main(int argc, char *argv[]) {
+	EntityRegex = regex(EntityPattern, 0);
+	TitleRegex = regex(TitlePattern, REG_ICASE);
+
+	setlocale(LC_CTYPE, "");
+	setlinebuf(stdout);
+
+	CURLcode code = curl_global_init(CURL_GLOBAL_ALL);
+	if (code) errx(EX_OSERR, "curl_global_init: %s", curl_easy_strerror(code));
+
+	curl = curl_easy_init();
+	if (!curl) errx(EX_SOFTWARE, "curl_easy_init");
+
+	static char error[CURL_ERROR_SIZE];
+	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, error);
+
+	curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
+	curl_easy_setopt(
+		curl, CURLOPT_USERAGENT,
+		"curl/7.54.0 facebookexternalhit/1.1 Twitterbot/1.0"
+	);
+	curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
+	curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
+	curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 3L);
+	curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
+
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handleBody);
+
+	bool exclude = false;
+	regex_t excludeRegex;
+
+	int opt;
+	while (0 < (opt = getopt(argc, argv, "x:v"))) {
+		switch (opt) {
+			break; case 'x': {
+				exclude = true;
+				excludeRegex = regex(optarg, REG_NOSUB);
+			}
+			break; case 'v': curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+			break; default:  return EX_USAGE;
+		}
+	}
+
+	if (optind < argc) {
+		code = fetchTitle(argv[optind]);
+		if (!code) return EX_OK;
+		errx(EX_DATAERR, "curl_easy_perform: %s", error);
+	}
+
+	char *buf = NULL;
+	size_t cap = 0;
+
+	regex_t urlRegex = regex("https?://([^[:space:]>\"()]|[(][^)]*[)])+", 0);
+	while (0 < getline(&buf, &cap, stdin)) {
+		regmatch_t match = {0};
+		for (char *ptr = buf; *ptr; ptr += match.rm_eo) {
+			if (regexec(&urlRegex, ptr, 1, &match, 0)) break;
+			ptr[match.rm_eo] = '\0';
+			const char *url = &ptr[match.rm_so];
+			if (!exclude || regexec(&excludeRegex, url, 0, NULL, 0)) {
+				code = fetchTitle(url);
+				if (code) warnx("curl_easy_perform: %s", error);
+			}
+			ptr[match.rm_eo] = ' ';
+		}
+	}
+	if (ferror(stdin)) err(EX_IOERR, "getline");
+}
diff --git a/bin/ttpre.c b/bin/ttpre.c
new file mode 100644
index 00000000..4917be37
--- /dev/null
+++ b/bin/ttpre.c
@@ -0,0 +1,66 @@
+/* Copyright (C) 2018  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+static void put(wchar_t ch) {
+	switch (ch) {
+		break; case L'&': printf("&amp;");
+		break; case L'<': printf("&lt;");
+		break; case L'>': printf("&gt;");
+		break; default:   printf("%lc", (wint_t)ch);
+	}
+}
+
+static void tag(const char *open) {
+	static const char *close = NULL;
+	if (close == open) return;
+	if (close) printf("</%s>", close);
+	if (open) printf("<%s>", open);
+	close = open;
+}
+
+static void push(wchar_t ch) {
+	static wchar_t q[3];
+	if (q[1] == L'\b' && q[0] == L'_') {
+		tag("i");
+		put(q[2]);
+		q[0] = q[1] = q[2] = 0;
+	} else if (q[1] == L'\b' && q[0] == q[2]) {
+		tag("b");
+		put(q[2]);
+		q[0] = q[1] = q[2] = 0;
+	} else if (q[0]) {
+		tag(NULL);
+		put(q[0]);
+	}
+	q[0] = q[1];
+	q[1] = q[2];
+	q[2] = ch;
+}
+
+int main(void) {
+	setlocale(LC_CTYPE, "");
+	printf("<pre>");
+	wint_t ch;
+	while (WEOF != (ch = getwchar())) push(ch);
+	push(0); push(0); push(0);
+	printf("</pre>\n");
+	return EXIT_SUCCESS;
+}
diff --git a/bin/up.sh b/bin/up.sh
new file mode 100644
index 00000000..e65d4522
--- /dev/null
+++ b/bin/up.sh
@@ -0,0 +1,76 @@
+#!/bin/sh
+set -eu
+
+readonly Host='temp.causal.agency'
+
+upload() {
+	local src ext ts rand url
+	src=$1
+	ext=${src##*.}
+	ts=$(date +'%s')
+	rand=$(openssl rand -hex 4)
+	url=$(printf '%s/%x%s.%s' "$Host" "$ts" "$rand" "$ext")
+	scp -q "$src" "${Host}:/usr/local/www/${url}"
+	echo "https://${url}"
+}
+
+temp() {
+	temp=$(mktemp -d)
+	trap "rm -r '$temp'" EXIT
+}
+
+uploadText() {
+	temp
+	cat > "${temp}/input.txt"
+	upload "${temp}/input.txt"
+}
+
+uploadCommand() {
+	temp
+	echo "$ $*" > "${temp}/exec.txt"
+	"$@" >> "${temp}/exec.txt" 2>&1 || true
+	upload "${temp}/exec.txt"
+}
+
+uploadHi() {
+	temp
+	hi -f html -o document,anchor,tab=4 "$@" > "${temp}/hi.html"
+	upload "${temp}/hi.html"
+}
+
+uploadScreen() {
+	temp
+	screencapture -i "$@" "${temp}/capture.png"
+	pngo "${temp}/capture.png" || true
+	upload "${temp}/capture.png"
+}
+
+uploadTerminal() {
+	temp
+	cat > "${temp}/term.html" <<-EOF
+	<!DOCTYPE html>
+	<title>${1}</title>
+	<style>
+	$(scheme -s)
+	</style>
+	EOF
+	ptee "$@" | shotty -Bs >> "${temp}/term.html"
+	upload "${temp}/term.html"
+}
+
+while getopts 'chst' opt; do
+	case "$opt" in
+		(c) fn=uploadCommand;;
+		(h) fn=uploadHi;;
+		(s) fn=uploadScreen;;
+		(t) fn=uploadTerminal;;
+		(?) exit 1;;
+	esac
+done
+shift $((OPTIND - 1))
+[ $# -eq 0 ] && : ${fn:=uploadText}
+: ${fn:=upload}
+
+url=$($fn "$@")
+printf '%s' "$url" | pbcopy || true
+echo "$url"
diff --git a/bin/when.y b/bin/when.y
new file mode 100644
index 00000000..5367b3b3
--- /dev/null
+++ b/bin/when.y
@@ -0,0 +1,250 @@
+/* Copyright (C) 2019  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+%{
+
+#include <ctype.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <sysexits.h>
+#include <time.h>
+
+static void yyerror(const char *str);
+static int yylex(void);
+
+#define YYSTYPE struct tm
+
+static const char *Days[7] = {
+	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
+};
+
+static const char *Months[12] = {
+	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+};
+
+static const struct tm Week = { .tm_mday = 7 };
+
+static struct tm normalize(struct tm date) {
+	time_t time = timegm(&date);
+	struct tm *norm = gmtime(&time);
+	if (!norm) err(EX_OSERR, "gmtime");
+	return *norm;
+}
+
+static struct tm today(void) {
+	time_t now = time(NULL);
+	struct tm *local = localtime(&now);
+	if (!local) err(EX_OSERR, "localtime");
+	struct tm date = {
+		.tm_year = local->tm_year,
+		.tm_mon = local->tm_mon,
+		.tm_mday = local->tm_mday,
+	};
+	return normalize(date);
+}
+
+static struct tm monthDay(int month, int day) {
+	struct tm date = today();
+	date.tm_mon = month;
+	date.tm_mday = day;
+	return normalize(date);
+}
+
+static struct tm monthDayYear(int month, int day, int year) {
+	struct tm date = today();
+	date.tm_mon = month;
+	date.tm_mday = day;
+	date.tm_year = year - 1900;
+	return normalize(date);
+}
+
+static struct tm weekDay(int day) {
+	struct tm date = today();
+	date.tm_mday += day - date.tm_wday;
+	return normalize(date);
+}
+
+static struct tm scalarAdd(struct tm a, struct tm b) {
+	a.tm_mday += b.tm_mday;
+	a.tm_mon += b.tm_mon;
+	a.tm_year += b.tm_year;
+	return a;
+}
+
+static struct tm scalarSub(struct tm a, struct tm b) {
+	a.tm_mday -= b.tm_mday;
+	a.tm_mon -= b.tm_mon;
+	a.tm_year -= b.tm_year;
+	return a;
+}
+
+static struct tm dateAdd(struct tm date, struct tm scalar) {
+	return normalize(scalarAdd(date, scalar));
+}
+
+static struct tm dateSub(struct tm date, struct tm scalar) {
+	return normalize(scalarSub(date, scalar));
+}
+
+static struct tm dateDiff(struct tm a, struct tm b) {
+	struct tm diff = {
+		.tm_year = a.tm_year - b.tm_year,
+		.tm_mon = a.tm_mon - b.tm_mon,
+		.tm_mday = a.tm_mday - b.tm_mday,
+	};
+	if (a.tm_mon < b.tm_mon) {
+		diff.tm_year--;
+		diff.tm_mon += 12;
+	}
+	if (a.tm_mday < b.tm_mday) {
+		diff.tm_mon--;
+		diff.tm_mday = 0;
+		while (dateAdd(b, diff).tm_mday != a.tm_mday) diff.tm_mday++;
+	}
+	time_t atime = timegm(&a), btime = timegm(&b);
+	diff.tm_yday = (atime - btime) / 24 / 60 / 60;
+	return diff;
+}
+
+static void printDate(struct tm date) {
+	printf(
+		"%s %s %d %d\n",
+		Days[date.tm_wday], Months[date.tm_mon],
+		date.tm_mday, 1900 + date.tm_year
+	);
+}
+
+static void printScalar(struct tm scalar) {
+	if (scalar.tm_year) printf("%dy ", scalar.tm_year);
+	if (scalar.tm_mon) printf("%dm ", scalar.tm_mon);
+	if (scalar.tm_mday % 7) {
+		printf("%dd ", scalar.tm_mday);
+	} else if (scalar.tm_mday) {
+		printf("%dw ", scalar.tm_mday / 7);
+	}
+	if (scalar.tm_yday && scalar.tm_mon) printf("(%dd) ", scalar.tm_yday);
+	printf("\n");
+}
+
+%}
+
+%token Number Month Day
+%left '+' '-'
+%right '<' '>'
+
+%%
+
+expr:
+	date { printDate($1); }
+	| scalar { printScalar($1); }
+	;
+
+date:
+	dateLit
+	| '(' date ')' { $$ = $2; }
+	| '<' date { $$ = dateSub($2, Week); }
+	| '>' date { $$ = dateAdd($2, Week); }
+	| date '+' scalar { $$ = dateAdd($1, $3); }
+	| date '-' scalar { $$ = dateSub($1, $3); }
+	;
+
+scalar:
+	scalarLit
+	| '(' scalar ')' { $$ = $2; }
+	| scalar '+' scalar { $$ = scalarAdd($1, $3); }
+	| scalar '-' scalar { $$ = scalarSub($1, $3); }
+	| date '-' date { $$ = dateDiff($1, $3); }
+	;
+
+dateLit:
+	{ $$ = today(); }
+	| '.' { $$ = today(); }
+	| Month Number { $$ = monthDay($1.tm_mon, $2.tm_sec); }
+	| Month Number Number { $$ = monthDayYear($1.tm_mon, $2.tm_sec, $3.tm_sec); }
+	| Day { $$ = weekDay($1.tm_wday); }
+	;
+
+scalarLit:
+	Number 'd' { $$ = (struct tm) { .tm_mday = $1.tm_sec }; }
+	| Number 'w' { $$ = (struct tm) { .tm_mday = 7 * $1.tm_sec }; }
+	| Number 'm' { $$ = (struct tm) { .tm_mon = $1.tm_sec }; }
+	| Number 'y' { $$ = (struct tm) { .tm_year = $1.tm_sec }; }
+	;
+
+%%
+
+static void yyerror(const char *str) {
+	warnx("%s", str);
+}
+
+static const char *input;
+
+static int yylex(void) {
+	while (isspace(*input)) input++;
+	if (!*input) return EOF;
+
+	if (isdigit(*input)) {
+		char *rest;
+		yylval.tm_sec = strtol(input, &rest, 10);
+		input = rest;
+		return Number;
+	}
+
+	for (int i = 0; i < 7; ++i) {
+		if (strncasecmp(input, Days[i], 3)) continue;
+		while (isalpha(*input)) input++;
+		yylval.tm_wday = i;
+		return Day;
+	}
+
+	for (int i = 0; i < 12; ++i) {
+		if (strncasecmp(input, Months[i], 3)) continue;
+		while (isalpha(*input)) input++;
+		yylval.tm_mon = i;
+		return Month;
+	}
+
+	return *input++;
+}
+
+int main(int argc, char *argv[]) {
+	if (argc > 1) {
+		input = argv[1];
+		return yyparse();
+	}
+
+	struct tm date = today();
+	printDate(date);
+	printf("\n");
+
+	char *line = NULL;
+	size_t cap = 0;
+	while (0 < getline(&line, &cap, stdin)) {
+		if (line[0] == '\n') continue;
+
+		if (today().tm_mday != date.tm_mday) {
+			warnx("the date has changed");
+			date = today();
+		}
+
+		input = line;
+		yyparse();
+		printf("\n");
+	}
+}
diff --git a/bin/xx.c b/bin/xx.c
new file mode 100644
index 00000000..6d04f2f5
--- /dev/null
+++ b/bin/xx.c
@@ -0,0 +1,142 @@
+/* Copyright (C) 2017  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+#include <err.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+typedef unsigned char byte;
+
+static bool zero(const byte *ptr, size_t size) {
+	for (size_t i = 0; i < size; ++i) {
+		if (ptr[i]) return false;
+	}
+	return true;
+}
+
+static struct {
+	size_t cols;
+	size_t group;
+	size_t blank;
+	bool ascii;
+	bool offset;
+	bool skip;
+} options = { 16, 8, 0, true, true, false };
+
+static void dump(FILE *file) {
+	bool skip = false;
+
+	byte buf[options.cols];
+	size_t offset = 0;
+	for (
+		size_t size;
+		(size = fread(buf, 1, sizeof(buf), file));
+		offset += size
+	) {
+		if (options.skip) {
+			if (zero(buf, size)) {
+				if (!skip) printf("*\n");
+				skip = true;
+				continue;
+			} else {
+				skip = false;
+			}
+		}
+
+		if (options.blank) {
+			if (offset && offset % options.blank == 0) {
+				printf("\n");
+			}
+		}
+
+		if (options.offset) {
+			printf("%08zX:  ", offset);
+		}
+
+		for (size_t i = 0; i < sizeof(buf); ++i) {
+			if (options.group) {
+				if (i && !(i % options.group)) {
+					printf(" ");
+				}
+			}
+			if (i < size) {
+				printf("%02hhX ", buf[i]);
+			} else {
+				printf("   ");
+			}
+		}
+
+		if (options.ascii) {
+			printf(" ");
+			for (size_t i = 0; i < size; ++i) {
+				if (options.group) {
+					if (i && !(i % options.group)) {
+						printf(" ");
+					}
+				}
+				printf("%c", isprint(buf[i]) ? buf[i] : '.');
+			}
+		}
+
+		printf("\n");
+	}
+}
+
+static void undump(FILE *file) {
+	byte c;
+	int match;
+	while (0 < (match = fscanf(file, " %hhx", &c))) {
+		printf("%c", c);
+	}
+	if (!match) errx(EX_DATAERR, "invalid input");
+}
+
+int main(int argc, char *argv[]) {
+	bool reverse = false;
+	const char *path = NULL;
+
+	int opt;
+	while (0 < (opt = getopt(argc, argv, "ac:g:p:rsz"))) {
+		switch (opt) {
+			break; case 'a': options.ascii ^= true;
+			break; case 'c': options.cols = strtoul(optarg, NULL, 0);
+			break; case 'g': options.group = strtoul(optarg, NULL, 0);
+			break; case 'p': options.blank = strtoul(optarg, NULL, 0);
+			break; case 'r': reverse = true;
+			break; case 's': options.offset ^= true;
+			break; case 'z': options.skip ^= true;
+			break; default: return EX_USAGE;
+		}
+	}
+	if (argc > optind) path = argv[optind];
+	if (!options.cols) return EX_USAGE;
+
+	FILE *file = path ? fopen(path, "r") : stdin;
+	if (!file) err(EX_NOINPUT, "%s", path);
+
+	if (reverse) {
+		undump(file);
+	} else {
+		dump(file);
+	}
+	if (ferror(file)) err(EX_IOERR, "%s", path);
+
+	return EX_OK;
+}
diff --git a/doc/pdf/.gitignore b/doc/pdf/.gitignore
new file mode 100644
index 00000000..a1363379
--- /dev/null
+++ b/doc/pdf/.gitignore
@@ -0,0 +1 @@
+*.pdf
diff --git a/doc/pdf/Makefile b/doc/pdf/Makefile
new file mode 100644
index 00000000..7afbdcf2
--- /dev/null
+++ b/doc/pdf/Makefile
@@ -0,0 +1,31 @@
+PDFS += abi.pdf
+PDFS += c11.pdf
+PDFS += elf.pdf
+PDFS += intel-64-opt.pdf
+PDFS += intel-64-sdm-vol-1.pdf
+PDFS += intel-64-sdm-vol-2.pdf
+PDFS += intel-64-sdm-vol-3.pdf
+PDFS += intel-64-sdm-vol-4.pdf
+PDFS += multiboot.pdf
+
+ELF = https://refspecs.linuxbase.org/elf
+INTEL = https://software.intel.com/sites/default/files/managed
+
+URL.abi.pdf = ${ELF}/x86_64-abi-0.99.pdf
+URL.c11.pdf = http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
+URL.elf.pdf = ${ELF}/elf.pdf
+URL.intel-64-opt.pdf = ${INTEL}/9e/bc/64-ia-32-architectures-optimization-manual.pdf
+URL.intel-64-sdm-vol-1.pdf = ${INTEL}/a4/60/253665-sdm-vol-1.pdf
+URL.intel-64-sdm-vol-2.pdf = ${INTEL}/a4/60/325383-sdm-vol-2abcd.pdf
+URL.intel-64-sdm-vol-3.pdf = ${INTEL}/a4/60/325384-sdm-vol-3abcd.pdf
+URL.intel-64-sdm-vol-4.pdf = ${INTEL}/22/0d/335592-sdm-vol-4.pdf
+URL.multiboot.pdf = https://www.gnu.org/software/grub/manual/multiboot/multiboot.pdf
+
+all: ${PDFS}
+
+${PDFS}:
+	curl -Lf -o $@ ${URL.$@}
+	chmod a-w $@
+
+clean:
+	rm -f ${PDFS}
diff --git a/doc/rfc/.gitignore b/doc/rfc/.gitignore
new file mode 100644
index 00000000..808cd63e
--- /dev/null
+++ b/doc/rfc/.gitignore
@@ -0,0 +1,3 @@
+*.txt
+*.txt.gz
+tags
diff --git a/doc/rfc/Makefile b/doc/rfc/Makefile
new file mode 100644
index 00000000..87462a6c
--- /dev/null
+++ b/doc/rfc/Makefile
@@ -0,0 +1,21 @@
+PREFIX ?= ~/.local
+
+MODULE = ftp.rfc-editor.org::rfcs-text-only
+
+tags: rfctags.pl rfc-index.txt.gz
+	perl rfctags.pl | sort -f > $@
+
+rfc-index.txt.gz:
+	rsync -ptz ${MODULE}/rfc-index.txt ${MODULE}/'rfc[1-9]*.txt' .
+	gzip -9f *.txt
+
+clean:
+	rm -f tags *.txt *.txt.gz
+
+install: tags rfc.vim
+	install -d ${PREFIX}/share/rfc ${PREFIX}/share/nvim/site/plugin
+	ln -f tags *.txt.gz ${PREFIX}/share/rfc
+	install -m 644 rfc.vim ${PREFIX}/share/nvim/site/plugin
+
+uninstall:
+	rm -fr ${PREFIX}/share/rfc ${PREFIX}/share/nvim/site/plugin/rfc.vim
diff --git a/doc/rfc/rfc.vim b/doc/rfc/rfc.vim
new file mode 100644
index 00000000..2455d8a6
--- /dev/null
+++ b/doc/rfc/rfc.vim
@@ -0,0 +1,30 @@
+if !exists('g:rfc_path')
+	let g:rfc_path = expand('<sfile>:h:h:h:h') . '/rfc'
+endif
+
+function! s:RFC(number)
+	if !empty(a:number)
+		let number = str2nr(matchstr(a:number, '\d\+'), 10)
+	else
+		let number = '-index'
+	endif
+	let path = expand(g:rfc_path . '/rfc' . number . '.txt.gz')
+	if filereadable(path)
+		execute 'silent' 'noswapfile' 'view' path
+	else
+		echohl ErrorMsg | echo 'No such RFC' a:number | echohl None
+	endif
+endfunction
+
+function! s:BufRead()
+	setlocal readonly
+	setlocal keywordprg=:RFC
+	setlocal iskeyword=a-z,A-Z,48-57,.,[,],-,_
+	nmap <buffer> <silent> gO :call search('^Table of Contents', 'bcs')<CR>
+endfunction
+
+command! -bar -nargs=? RFC call s:RFC(<q-args>)
+augroup RFC
+	autocmd!
+	autocmd BufRead rfc*.txt.gz call s:BufRead()
+augroup END
diff --git a/doc/rfc/rfctags.pl b/doc/rfc/rfctags.pl
new file mode 100644
index 00000000..01324a0d
--- /dev/null
+++ b/doc/rfc/rfctags.pl
@@ -0,0 +1,28 @@
+use strict;
+use warnings;
+use open ':encoding(ISO-8859-1)';
+
+use IO::Uncompress::Gunzip qw($GunzipError);
+
+($,, $\) = ("\t", "\n");
+print '!_TAG_FILE_SORTED', 2, $0; # Promise to pipe this through sort -f
+for my $rfc (<*.txt.gz>) {
+	my $handle = new IO::Uncompress::Gunzip $rfc
+		or die "${rfc}: ${GunzipError}";
+	while (<$handle>) {
+		chomp;
+		# Section headings
+		if (/^([\d.]+|[A-Z][.])\s+([^\t]+)?/) {
+			print $1, $rfc, $.;
+			print $2, $rfc, $. if $2;
+			print $1, $rfc, $. if $1 =~ /^([\d.]+)[.]$/;
+		}
+		# References
+		if (/^\s*(\[[\w-]+\])\s{2,}/) {
+			print $1, $rfc, $.;
+			print "\\$1", $rfc, $.; # vim ^] prepends \ to [
+		}
+	}
+	die "${rfc}: $!" if $!;
+	close $handle;
+}
diff --git a/Makefile b/doc/zlib/Makefile
index 6cfd4a42..6cfd4a42 100644
--- a/Makefile
+++ b/doc/zlib/Makefile
diff --git a/adler32.3 b/doc/zlib/adler32.3
index d713d952..d713d952 100644
--- a/adler32.3
+++ b/doc/zlib/adler32.3
diff --git a/adler32_combine.3 b/doc/zlib/adler32_combine.3
index 861f235b..861f235b 100644
--- a/adler32_combine.3
+++ b/doc/zlib/adler32_combine.3
diff --git a/compress.3 b/doc/zlib/compress.3
index 22b229ee..22b229ee 100644
--- a/compress.3
+++ b/doc/zlib/compress.3
diff --git a/compressBound.3 b/doc/zlib/compressBound.3
index 5800e2ba..5800e2ba 100644
--- a/compressBound.3
+++ b/doc/zlib/compressBound.3
diff --git a/crc32.3 b/doc/zlib/crc32.3
index 3c9cc8c4..3c9cc8c4 100644
--- a/crc32.3
+++ b/doc/zlib/crc32.3
diff --git a/crc32_combine.3 b/doc/zlib/crc32_combine.3
index 2f79f623..2f79f623 100644
--- a/crc32_combine.3
+++ b/doc/zlib/crc32_combine.3
diff --git a/deflate.3 b/doc/zlib/deflate.3
index 7df313ee..7df313ee 100644
--- a/deflate.3
+++ b/doc/zlib/deflate.3
diff --git a/deflateBound.3 b/doc/zlib/deflateBound.3
index 63e80246..63e80246 100644
--- a/deflateBound.3
+++ b/doc/zlib/deflateBound.3
diff --git a/deflateCopy.3 b/doc/zlib/deflateCopy.3
index f30d6301..f30d6301 100644
--- a/deflateCopy.3
+++ b/doc/zlib/deflateCopy.3
diff --git a/deflateEnd.3 b/doc/zlib/deflateEnd.3
index e24259a3..e24259a3 100644
--- a/deflateEnd.3
+++ b/doc/zlib/deflateEnd.3
diff --git a/deflateGetDictionary.3 b/doc/zlib/deflateGetDictionary.3
index 403f6d10..403f6d10 100644
--- a/deflateGetDictionary.3
+++ b/doc/zlib/deflateGetDictionary.3
diff --git a/deflateInit.3 b/doc/zlib/deflateInit.3
index a893dd91..a893dd91 100644
--- a/deflateInit.3
+++ b/doc/zlib/deflateInit.3
diff --git a/deflateInit2.3 b/doc/zlib/deflateInit2.3
index 6a581ef8..6a581ef8 100644
--- a/deflateInit2.3
+++ b/doc/zlib/deflateInit2.3
diff --git a/deflateParams.3 b/doc/zlib/deflateParams.3
index 8e770d4e..8e770d4e 100644
--- a/deflateParams.3
+++ b/doc/zlib/deflateParams.3
diff --git a/deflatePending.3 b/doc/zlib/deflatePending.3
index 1ce40fc2..1ce40fc2 100644
--- a/deflatePending.3
+++ b/doc/zlib/deflatePending.3
diff --git a/deflatePrime.3 b/doc/zlib/deflatePrime.3
index 639e715a..639e715a 100644
--- a/deflatePrime.3
+++ b/doc/zlib/deflatePrime.3
diff --git a/deflateReset.3 b/doc/zlib/deflateReset.3
index 7309ac15..7309ac15 100644
--- a/deflateReset.3
+++ b/doc/zlib/deflateReset.3
diff --git a/deflateSetDictionary.3 b/doc/zlib/deflateSetDictionary.3
index c2c9d7c2..c2c9d7c2 100644
--- a/deflateSetDictionary.3
+++ b/doc/zlib/deflateSetDictionary.3
diff --git a/deflateSetHeader.3 b/doc/zlib/deflateSetHeader.3
index 6fec645c..6fec645c 100644
--- a/deflateSetHeader.3
+++ b/doc/zlib/deflateSetHeader.3
diff --git a/deflateTune.3 b/doc/zlib/deflateTune.3
index 7269dec0..7269dec0 100644
--- a/deflateTune.3
+++ b/doc/zlib/deflateTune.3
diff --git a/gzbuffer.3 b/doc/zlib/gzbuffer.3
index de7c706a..de7c706a 100644
--- a/gzbuffer.3
+++ b/doc/zlib/gzbuffer.3
diff --git a/gzclose.3 b/doc/zlib/gzclose.3
index 77eae11e..77eae11e 100644
--- a/gzclose.3
+++ b/doc/zlib/gzclose.3
diff --git a/gzdirect.3 b/doc/zlib/gzdirect.3
index 8fa26aae..8fa26aae 100644
--- a/gzdirect.3
+++ b/doc/zlib/gzdirect.3
diff --git a/gzeof.3 b/doc/zlib/gzeof.3
index 26c415fe..26c415fe 100644
--- a/gzeof.3
+++ b/doc/zlib/gzeof.3
diff --git a/gzerror.3 b/doc/zlib/gzerror.3
index 13dcddd4..13dcddd4 100644
--- a/gzerror.3
+++ b/doc/zlib/gzerror.3
diff --git a/gzflush.3 b/doc/zlib/gzflush.3
index b93c03e7..b93c03e7 100644
--- a/gzflush.3
+++ b/doc/zlib/gzflush.3
diff --git a/gzfread.3 b/doc/zlib/gzfread.3
index 66231cc3..66231cc3 100644
--- a/gzfread.3
+++ b/doc/zlib/gzfread.3
diff --git a/gzfwrite.3 b/doc/zlib/gzfwrite.3
index 38383a33..38383a33 100644
--- a/gzfwrite.3
+++ b/doc/zlib/gzfwrite.3
diff --git a/gzgetc.3 b/doc/zlib/gzgetc.3
index 93a90edd..93a90edd 100644
--- a/gzgetc.3
+++ b/doc/zlib/gzgetc.3
diff --git a/gzgets.3 b/doc/zlib/gzgets.3
index 2a329e9e..2a329e9e 100644
--- a/gzgets.3
+++ b/doc/zlib/gzgets.3
diff --git a/gzoffset.3 b/doc/zlib/gzoffset.3
index cbb78a77..cbb78a77 100644
--- a/gzoffset.3
+++ b/doc/zlib/gzoffset.3
diff --git a/gzopen.3 b/doc/zlib/gzopen.3
index e3cb4cbd..e3cb4cbd 100644
--- a/gzopen.3
+++ b/doc/zlib/gzopen.3
diff --git a/gzprintf.3 b/doc/zlib/gzprintf.3
index 26961f34..26961f34 100644
--- a/gzprintf.3
+++ b/doc/zlib/gzprintf.3
diff --git a/gzputc.3 b/doc/zlib/gzputc.3
index 161e5631..161e5631 100644
--- a/gzputc.3
+++ b/doc/zlib/gzputc.3
diff --git a/gzputs.3 b/doc/zlib/gzputs.3
index f5d1fd84..f5d1fd84 100644
--- a/gzputs.3
+++ b/doc/zlib/gzputs.3
diff --git a/gzread.3 b/doc/zlib/gzread.3
index 84439eaa..84439eaa 100644
--- a/gzread.3
+++ b/doc/zlib/gzread.3
diff --git a/gzseek.3 b/doc/zlib/gzseek.3
index cd85fd4c..cd85fd4c 100644
--- a/gzseek.3
+++ b/doc/zlib/gzseek.3
diff --git a/gzsetparams.3 b/doc/zlib/gzsetparams.3
index ff544d23..ff544d23 100644
--- a/gzsetparams.3
+++ b/doc/zlib/gzsetparams.3
diff --git a/gzungetc.3 b/doc/zlib/gzungetc.3
index 90cdafc7..90cdafc7 100644
--- a/gzungetc.3
+++ b/doc/zlib/gzungetc.3
diff --git a/gzwrite.3 b/doc/zlib/gzwrite.3
index 606d89f4..606d89f4 100644
--- a/gzwrite.3
+++ b/doc/zlib/gzwrite.3
diff --git a/inflate.3 b/doc/zlib/inflate.3
index ca90c270..ca90c270 100644
--- a/inflate.3
+++ b/doc/zlib/inflate.3
diff --git a/inflateBack.3 b/doc/zlib/inflateBack.3
index 59d5f8cb..59d5f8cb 100644
--- a/inflateBack.3
+++ b/doc/zlib/inflateBack.3
diff --git a/inflateBackEnd.3 b/doc/zlib/inflateBackEnd.3
index eeb88636..eeb88636 100644
--- a/inflateBackEnd.3
+++ b/doc/zlib/inflateBackEnd.3
diff --git a/inflateBackInit.3 b/doc/zlib/inflateBackInit.3
index 483edda5..483edda5 100644
--- a/inflateBackInit.3
+++ b/doc/zlib/inflateBackInit.3
diff --git a/inflateCopy.3 b/doc/zlib/inflateCopy.3
index 53b30edf..53b30edf 100644
--- a/inflateCopy.3
+++ b/doc/zlib/inflateCopy.3
diff --git a/inflateEnd.3 b/doc/zlib/inflateEnd.3
index 9b18226b..9b18226b 100644
--- a/inflateEnd.3
+++ b/doc/zlib/inflateEnd.3
diff --git a/inflateGetDictionary.3 b/doc/zlib/inflateGetDictionary.3
index e70ee736..e70ee736 100644
--- a/inflateGetDictionary.3
+++ b/doc/zlib/inflateGetDictionary.3
diff --git a/inflateGetHeader.3 b/doc/zlib/inflateGetHeader.3
index f77670f2..f77670f2 100644
--- a/inflateGetHeader.3
+++ b/doc/zlib/inflateGetHeader.3
diff --git a/inflateInit.3 b/doc/zlib/inflateInit.3
index 186b058a..186b058a 100644
--- a/inflateInit.3
+++ b/doc/zlib/inflateInit.3
diff --git a/inflateInit2.3 b/doc/zlib/inflateInit2.3
index a630f12a..a630f12a 100644
--- a/inflateInit2.3
+++ b/doc/zlib/inflateInit2.3
diff --git a/inflateMark.3 b/doc/zlib/inflateMark.3
index 2d15993d..2d15993d 100644
--- a/inflateMark.3
+++ b/doc/zlib/inflateMark.3
diff --git a/inflatePrime.3 b/doc/zlib/inflatePrime.3
index c89dc2c5..c89dc2c5 100644
--- a/inflatePrime.3
+++ b/doc/zlib/inflatePrime.3
diff --git a/inflateReset.3 b/doc/zlib/inflateReset.3
index a8d2e219..a8d2e219 100644
--- a/inflateReset.3
+++ b/doc/zlib/inflateReset.3
diff --git a/inflateSetDictionary.3 b/doc/zlib/inflateSetDictionary.3
index 0e3c60c7..0e3c60c7 100644
--- a/inflateSetDictionary.3
+++ b/doc/zlib/inflateSetDictionary.3
diff --git a/inflateSync.3 b/doc/zlib/inflateSync.3
index 35264ddd..35264ddd 100644
--- a/inflateSync.3
+++ b/doc/zlib/inflateSync.3
diff --git a/uncompress.3 b/doc/zlib/uncompress.3
index d951da9b..d951da9b 100644
--- a/uncompress.3
+++ b/doc/zlib/uncompress.3
diff --git a/zlibCompileFlags.3 b/doc/zlib/zlibCompileFlags.3
index 465195c2..465195c2 100644
--- a/zlibCompileFlags.3
+++ b/doc/zlib/zlibCompileFlags.3
diff --git a/zlibVersion.3 b/doc/zlib/zlibVersion.3
index 35a9854b..35a9854b 100644
--- a/zlibVersion.3
+++ b/doc/zlib/zlibVersion.3
diff --git a/etc/CodeQWERTY.bundle/Contents/Info.plist b/etc/CodeQWERTY.bundle/Contents/Info.plist
new file mode 100644
index 00000000..f78351e8
--- /dev/null
+++ b/etc/CodeQWERTY.bundle/Contents/Info.plist
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+	<dict>
+		<key>CFBundleIdentifier</key>
+		<string>agency.causal.keyboardlayout.code</string>
+		<key>CFBundleName</key>
+		<string>Code QWERTY</string>
+		<key>CFBundleVersion</key>
+		<string>1.0</string>
+		<key>KLInfo_Code QWERTY</key>
+		<dict>
+			<key>TISInputSourceID</key>
+			<string>agency.causal.keyboardlayout.code.qwerty</string>
+			<key>TISIntendedLanguage</key>
+			<string>en-CA</string>
+		</dict>
+	</dict>
+</plist>
diff --git a/etc/CodeQWERTY.bundle/Contents/Resources/CodeQWERTY.keylayout b/etc/CodeQWERTY.bundle/Contents/Resources/CodeQWERTY.keylayout
new file mode 100644
index 00000000..393a86dd
--- /dev/null
+++ b/etc/CodeQWERTY.bundle/Contents/Resources/CodeQWERTY.keylayout
@@ -0,0 +1,1178 @@
+<?xml version="1.1" encoding="UTF-8"?>
+<!DOCTYPE keyboard PUBLIC "" "file://localhost/System/Library/DTDs/KeyboardLayout.dtd">
+<keyboard group="0" id="5069" maxout="1" name="Code QWERTY">
+    <layouts>
+        <layout first="0" last="17" mapSet="16c" modifiers="f4"/>
+        <layout first="18" last="18" mapSet="994" modifiers="f4"/>
+        <layout first="21" last="23" mapSet="994" modifiers="f4"/>
+        <layout first="30" last="30" mapSet="994" modifiers="f4"/>
+        <layout first="194" last="194" mapSet="994" modifiers="f4"/>
+        <layout first="197" last="197" mapSet="994" modifiers="f4"/>
+        <layout first="200" last="201" mapSet="994" modifiers="f4"/>
+        <layout first="206" last="207" mapSet="994" modifiers="f4"/>
+    </layouts>
+    <modifierMap defaultIndex="7" id="f4">
+        <keyMapSelect mapIndex="8">
+            <modifier keys="command?"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="0">
+            <modifier keys="anyShift? caps? command"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="9">
+            <modifier keys="anyShift caps?"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="2">
+            <modifier keys="caps"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="3">
+            <modifier keys="anyOption"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="4">
+            <modifier keys="anyShift caps? anyOption command?"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="5">
+            <modifier keys="caps anyOption"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="6">
+            <modifier keys="caps? anyOption command"/>
+        </keyMapSelect>
+        <keyMapSelect mapIndex="7">
+            <modifier keys="anyShift caps? option? command? control"/>
+            <modifier keys="shift? caps? anyOption command? control"/>
+            <modifier keys="caps? anyOption? command? control"/>
+        </keyMapSelect>
+    </modifierMap>
+    <keyMapSet id="16c">
+        <keyMap index="0">
+            <key action="13" code="0"/>
+            <key code="1" output="s"/>
+            <key code="2" output="d"/>
+            <key code="3" output="f"/>
+            <key code="4" output="h"/>
+            <key code="5" output="g"/>
+            <key code="6" output="z"/>
+            <key code="7" output="x"/>
+            <key code="8" output="c"/>
+            <key code="9" output="v"/>
+            <key code="10" output="§"/>
+            <key code="11" output="b"/>
+            <key code="12" output="q"/>
+            <key code="13" output="w"/>
+            <key action="14" code="14"/>
+            <key code="15" output="r"/>
+            <key action="19" code="16"/>
+            <key code="17" output="t"/>
+            <key code="18" output="1"/>
+            <key code="19" output="2"/>
+            <key code="20" output="3"/>
+            <key code="21" output="4"/>
+            <key code="22" output="6"/>
+            <key code="23" output="5"/>
+            <key code="24" output="="/>
+            <key code="25" output="9"/>
+            <key code="26" output="7"/>
+            <key code="27" output="-"/>
+            <key code="28" output="8"/>
+            <key code="29" output="0"/>
+            <key code="30" output="]"/>
+            <key action="17" code="31"/>
+            <key action="18" code="32"/>
+            <key code="33" output="["/>
+            <key action="15" code="34"/>
+            <key code="35" output="p"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="l"/>
+            <key code="38" output="j"/>
+            <key code="39" output="&#x0027;"/>
+            <key code="40" output="k"/>
+            <key code="41" output=";"/>
+            <key code="42" output="\"/>
+            <key code="43" output=","/>
+            <key code="44" output="/"/>
+            <key action="16" code="45"/>
+            <key code="46" output="m"/>
+            <key code="47" output="."/>
+            <key code="48" output="&#x0009;"/>
+            <key action="5" code="49"/>
+            <key code="50" output="`"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="&#x001D;"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="&#x001C;"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="&#x001F;"/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="&#x001E;"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="1">
+            <key action="6" code="0"/>
+            <key code="1" output="S"/>
+            <key code="2" output="D"/>
+            <key code="3" output="F"/>
+            <key code="4" output="H"/>
+            <key code="5" output="G"/>
+            <key code="6" output="Z"/>
+            <key code="7" output="X"/>
+            <key code="8" output="C"/>
+            <key code="9" output="V"/>
+            <key code="10" output="±"/>
+            <key code="11" output="B"/>
+            <key code="12" output="Q"/>
+            <key code="13" output="W"/>
+            <key action="7" code="14"/>
+            <key code="15" output="R"/>
+            <key action="12" code="16"/>
+            <key code="17" output="T"/>
+            <key code="18" output="!"/>
+            <key code="19" output="@"/>
+            <key code="20" output="#"/>
+            <key code="21" output="$"/>
+            <key code="22" output="^"/>
+            <key code="23" output="%"/>
+            <key code="24" output="+"/>
+            <key code="25" output="("/>
+            <key code="26" output="&#x0026;"/>
+            <key code="27" output="_"/>
+            <key code="28" output="*"/>
+            <key code="29" output=")"/>
+            <key code="30" output="}"/>
+            <key action="10" code="31"/>
+            <key action="11" code="32"/>
+            <key code="33" output="{"/>
+            <key action="8" code="34"/>
+            <key code="35" output="P"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="L"/>
+            <key code="38" output="J"/>
+            <key code="39" output="&#x0022;"/>
+            <key code="40" output="K"/>
+            <key code="41" output=":"/>
+            <key code="42" output="|"/>
+            <key code="43" output="&#x003C;"/>
+            <key code="44" output="?"/>
+            <key action="9" code="45"/>
+            <key code="46" output="M"/>
+            <key code="47" output="&#x003E;"/>
+            <key code="48" output="&#x0009;"/>
+            <key action="5" code="49"/>
+            <key code="50" output="~"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="*"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="+"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="="/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="/"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="2">
+            <key action="6" code="0"/>
+            <key code="1" output="S"/>
+            <key code="2" output="D"/>
+            <key code="3" output="F"/>
+            <key code="4" output="H"/>
+            <key code="5" output="G"/>
+            <key code="6" output="Z"/>
+            <key code="7" output="X"/>
+            <key code="8" output="C"/>
+            <key code="9" output="V"/>
+            <key code="10" output="§"/>
+            <key code="11" output="B"/>
+            <key code="12" output="Q"/>
+            <key code="13" output="W"/>
+            <key action="7" code="14"/>
+            <key code="15" output="R"/>
+            <key action="12" code="16"/>
+            <key code="17" output="T"/>
+            <key code="18" output="1"/>
+            <key code="19" output="2"/>
+            <key code="20" output="3"/>
+            <key code="21" output="4"/>
+            <key code="22" output="6"/>
+            <key code="23" output="5"/>
+            <key code="24" output="="/>
+            <key code="25" output="9"/>
+            <key code="26" output="7"/>
+            <key code="27" output="-"/>
+            <key code="28" output="8"/>
+            <key code="29" output="0"/>
+            <key code="30" output="]"/>
+            <key action="10" code="31"/>
+            <key action="11" code="32"/>
+            <key code="33" output="["/>
+            <key action="8" code="34"/>
+            <key code="35" output="P"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="L"/>
+            <key code="38" output="J"/>
+            <key code="39" output="&#x0027;"/>
+            <key code="40" output="K"/>
+            <key code="41" output=";"/>
+            <key code="42" output="\"/>
+            <key code="43" output=","/>
+            <key code="44" output="/"/>
+            <key action="9" code="45"/>
+            <key code="46" output="M"/>
+            <key code="47" output="."/>
+            <key code="48" output="&#x0009;"/>
+            <key action="5" code="49"/>
+            <key code="50" output="`"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="&#x001D;"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="&#x001C;"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="&#x001F;"/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="&#x001E;"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="3">
+            <key code="0" output="Ã¥"/>
+            <key code="1" output="ß"/>
+            <key code="2" output="∂"/>
+            <key code="3" output="Æ’"/>
+            <key code="4" output="Ë™"/>
+            <key code="5" output="©"/>
+            <key code="6" output="Ω"/>
+            <key code="7" output="≈"/>
+            <key code="8" output="ç"/>
+            <key code="9" output="√"/>
+            <key code="10" output="§"/>
+            <key code="11" output="∫"/>
+            <key code="12" output="Å“"/>
+            <key code="13" output="∑"/>
+            <key action="0" code="14"/>
+            <key code="15" output="®"/>
+            <key code="16" output="Â¥"/>
+            <key code="17" output="†"/>
+            <key code="18" output="¡"/>
+            <key code="19" output="â„¢"/>
+            <key code="20" output="£"/>
+            <key code="21" output="¢"/>
+            <key code="22" output="§"/>
+            <key code="23" output="∞"/>
+            <key code="24" output="≠"/>
+            <key code="25" output="ª"/>
+            <key code="26" output="¶"/>
+            <key code="27" output="–"/>
+            <key code="28" output="•"/>
+            <key code="29" output="º"/>
+            <key code="30" output="‘"/>
+            <key code="31" output="ø"/>
+            <key action="3" code="32"/>
+            <key code="33" output="“"/>
+            <key action="2" code="34"/>
+            <key code="35" output="Ï€"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="¬"/>
+            <key code="38" output="∆"/>
+            <key code="39" output="æ"/>
+            <key code="40" output="Ëš"/>
+            <key code="41" output="…"/>
+            <key code="42" output="«"/>
+            <key code="43" output="≤"/>
+            <key code="44" output="÷"/>
+            <key action="4" code="45"/>
+            <key code="46" output="µ"/>
+            <key code="47" output="≥"/>
+            <key code="48" output="&#x0009;"/>
+            <key code="49" output=" "/>
+            <key action="1" code="50"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="&#x001D;"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="&#x001C;"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="&#x001F;"/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="&#x001E;"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="4">
+            <key code="0" output="Ã…"/>
+            <key code="1" output="Ã"/>
+            <key code="2" output="ÃŽ"/>
+            <key code="3" output="Ã"/>
+            <key code="4" output="Ó"/>
+            <key code="5" output="Ë"/>
+            <key code="6" output="¸"/>
+            <key code="7" output="Ë›"/>
+            <key code="8" output="Ç"/>
+            <key code="9" output="â—Š"/>
+            <key code="10" output="±"/>
+            <key code="11" output="ı"/>
+            <key code="12" output="Å’"/>
+            <key code="13" output="„"/>
+            <key code="14" output="´"/>
+            <key code="15" output="‰"/>
+            <key code="16" output="Ã"/>
+            <key code="17" output="ˇ"/>
+            <key code="18" output="â„"/>
+            <key code="19" output="€"/>
+            <key code="20" output="‹"/>
+            <key code="21" output="›"/>
+            <key code="22" output="fl"/>
+            <key code="23" output="ï¬"/>
+            <key code="24" output="±"/>
+            <key code="25" output="·"/>
+            <key code="26" output="‡"/>
+            <key code="27" output="—"/>
+            <key code="28" output="°"/>
+            <key code="29" output="‚"/>
+            <key code="30" output="’"/>
+            <key code="31" output="Ø"/>
+            <key code="32" output="¨"/>
+            <key code="33" output="â€"/>
+            <key code="34" output="ˆ"/>
+            <key code="35" output="âˆ"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="Ã’"/>
+            <key code="38" output="Ô"/>
+            <key code="39" output="Æ"/>
+            <key code="40" output=""/>
+            <key code="41" output="Ú"/>
+            <key code="42" output="»"/>
+            <key code="43" output="¯"/>
+            <key code="44" output="¿"/>
+            <key code="45" output="˜"/>
+            <key code="46" output="Â"/>
+            <key code="47" output="˘"/>
+            <key code="48" output="&#x0009;"/>
+            <key code="49" output=" "/>
+            <key code="50" output="`"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="*"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="+"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="="/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="/"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="5">
+            <key code="0" output="Ã…"/>
+            <key code="1" output="Ã"/>
+            <key code="2" output="ÃŽ"/>
+            <key code="3" output="Ã"/>
+            <key code="4" output="Ó"/>
+            <key code="5" output="©"/>
+            <key code="6" output="Ω"/>
+            <key code="7" output="≈"/>
+            <key code="8" output="Ç"/>
+            <key code="9" output="√"/>
+            <key code="10" output="§"/>
+            <key code="11" output="ı"/>
+            <key code="12" output="Å’"/>
+            <key code="13" output="∑"/>
+            <key code="14" output="´"/>
+            <key code="15" output="®"/>
+            <key code="16" output="Ã"/>
+            <key code="17" output="†"/>
+            <key code="18" output="¡"/>
+            <key code="19" output="â„¢"/>
+            <key code="20" output="£"/>
+            <key code="21" output="¢"/>
+            <key code="22" output="§"/>
+            <key code="23" output="∞"/>
+            <key code="24" output="≠"/>
+            <key code="25" output="ª"/>
+            <key code="26" output="¶"/>
+            <key code="27" output="–"/>
+            <key code="28" output="•"/>
+            <key code="29" output="º"/>
+            <key code="30" output="‘"/>
+            <key code="31" output="Ø"/>
+            <key code="32" output="¨"/>
+            <key code="33" output="“"/>
+            <key code="34" output="ˆ"/>
+            <key code="35" output="âˆ"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="Ã’"/>
+            <key code="38" output="Ô"/>
+            <key code="39" output="Æ"/>
+            <key code="40" output="Ëš"/>
+            <key code="41" output="…"/>
+            <key code="42" output="«"/>
+            <key code="43" output="≤"/>
+            <key code="44" output="÷"/>
+            <key code="45" output="˜"/>
+            <key code="46" output="Â"/>
+            <key code="47" output="≥"/>
+            <key code="48" output="&#x0009;"/>
+            <key code="49" output=" "/>
+            <key code="50" output="`"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="&#x001D;"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="&#x001C;"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="&#x001F;"/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="&#x001E;"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="6">
+            <key code="0" output="Ã¥"/>
+            <key code="1" output="ß"/>
+            <key code="2" output="∂"/>
+            <key code="3" output="Æ’"/>
+            <key code="4" output="Ë™"/>
+            <key code="5" output="©"/>
+            <key code="6" output="Ω"/>
+            <key code="7" output="≈"/>
+            <key code="8" output="ç"/>
+            <key code="9" output="√"/>
+            <key code="10" output="§"/>
+            <key code="11" output="∫"/>
+            <key code="12" output="Å“"/>
+            <key code="13" output="∑"/>
+            <key code="14" output="´"/>
+            <key code="15" output="®"/>
+            <key code="16" output="Â¥"/>
+            <key code="17" output="†"/>
+            <key code="18" output="¡"/>
+            <key code="19" output="â„¢"/>
+            <key code="20" output="£"/>
+            <key code="21" output="¢"/>
+            <key code="22" output="§"/>
+            <key code="23" output="∞"/>
+            <key code="24" output="≠"/>
+            <key code="25" output="ª"/>
+            <key code="26" output="¶"/>
+            <key code="27" output="–"/>
+            <key code="28" output="•"/>
+            <key code="29" output="º"/>
+            <key code="30" output="‘"/>
+            <key code="31" output="ø"/>
+            <key code="32" output="¨"/>
+            <key code="33" output="“"/>
+            <key code="34" output="^"/>
+            <key code="35" output="Ï€"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="¬"/>
+            <key code="38" output="∆"/>
+            <key code="39" output="æ"/>
+            <key code="40" output="Ëš"/>
+            <key code="41" output="…"/>
+            <key code="42" output="«"/>
+            <key code="43" output="≤"/>
+            <key code="44" output="÷"/>
+            <key code="45" output="~"/>
+            <key code="46" output="µ"/>
+            <key code="47" output="≥"/>
+            <key code="48" output="&#x0009;"/>
+            <key code="49" output=" "/>
+            <key code="50" output="`"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="&#x001D;"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="&#x001C;"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="&#x001F;"/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="&#x001E;"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="7">
+            <key code="0" output="&#x0001;"/>
+            <key code="1" output="&#x0013;"/>
+            <key code="2" output="&#x0004;"/>
+            <key code="3" output="&#x0006;"/>
+            <key code="4" output="&#x0008;"/>
+            <key code="5" output="&#x0007;"/>
+            <key code="6" output="&#x001A;"/>
+            <key code="7" output="&#x0018;"/>
+            <key code="8" output="&#x0003;"/>
+            <key code="9" output="&#x0016;"/>
+            <key code="10" output="0"/>
+            <key code="11" output="&#x0002;"/>
+            <key code="12" output="&#x0011;"/>
+            <key code="13" output="&#x0017;"/>
+            <key code="14" output="&#x0005;"/>
+            <key code="15" output="&#x0012;"/>
+            <key code="16" output="&#x0019;"/>
+            <key code="17" output="&#x0014;"/>
+            <key code="18" output="1"/>
+            <key code="19" output="2"/>
+            <key code="20" output="3"/>
+            <key code="21" output="4"/>
+            <key code="22" output="6"/>
+            <key code="23" output="5"/>
+            <key code="24" output="="/>
+            <key code="25" output="9"/>
+            <key code="26" output="7"/>
+            <key code="27" output="&#x001F;"/>
+            <key code="28" output="8"/>
+            <key code="29" output="0"/>
+            <key code="30" output="&#x001D;"/>
+            <key code="31" output="&#x000F;"/>
+            <key code="32" output="&#x0015;"/>
+            <key code="33" output="&#x001B;"/>
+            <key code="34" output="&#x0009;"/>
+            <key code="35" output="&#x0010;"/>
+            <key code="36" output="&#x000D;"/>
+            <key code="37" output="&#x000C;"/>
+            <key code="38" output="&#x000A;"/>
+            <key code="39" output="&#x0027;"/>
+            <key code="40" output="&#x000B;"/>
+            <key code="41" output=";"/>
+            <key code="42" output="&#x001C;"/>
+            <key code="43" output=","/>
+            <key code="44" output="/"/>
+            <key code="45" output="&#x000E;"/>
+            <key code="46" output="&#x000D;"/>
+            <key code="47" output="."/>
+            <key code="48" output="&#x0009;"/>
+            <key action="5" code="49"/>
+            <key code="50" output="`"/>
+            <key code="51" output="&#x0008;"/>
+            <key code="52" output="&#x0003;"/>
+            <key code="53" output="&#x001B;"/>
+            <key code="64" output="&#x0010;"/>
+            <key code="65" output="."/>
+            <key code="66" output="&#x001D;"/>
+            <key code="67" output="*"/>
+            <key code="69" output="+"/>
+            <key code="70" output="&#x001C;"/>
+            <key code="71" output="&#x001B;"/>
+            <key code="72" output="&#x001F;"/>
+            <key code="75" output="/"/>
+            <key code="76" output="&#x0003;"/>
+            <key code="77" output="&#x001E;"/>
+            <key code="78" output="-"/>
+            <key code="79" output="&#x0010;"/>
+            <key code="80" output="&#x0010;"/>
+            <key code="81" output="="/>
+            <key code="82" output="0"/>
+            <key code="83" output="1"/>
+            <key code="84" output="2"/>
+            <key code="85" output="3"/>
+            <key code="86" output="4"/>
+            <key code="87" output="5"/>
+            <key code="88" output="6"/>
+            <key code="89" output="7"/>
+            <key code="91" output="8"/>
+            <key code="92" output="9"/>
+            <key code="96" output="&#x0010;"/>
+            <key code="97" output="&#x0010;"/>
+            <key code="98" output="&#x0010;"/>
+            <key code="99" output="&#x0010;"/>
+            <key code="100" output="&#x0010;"/>
+            <key code="101" output="&#x0010;"/>
+            <key code="102" output="&#x0010;"/>
+            <key code="103" output="&#x0010;"/>
+            <key code="104" output="&#x0010;"/>
+            <key code="105" output="&#x0010;"/>
+            <key code="106" output="&#x0010;"/>
+            <key code="107" output="&#x0010;"/>
+            <key code="108" output="&#x0010;"/>
+            <key code="109" output="&#x0010;"/>
+            <key code="110" output="&#x0010;"/>
+            <key code="111" output="&#x0010;"/>
+            <key code="112" output="&#x0010;"/>
+            <key code="113" output="&#x0010;"/>
+            <key code="114" output="&#x0005;"/>
+            <key code="115" output="&#x0001;"/>
+            <key code="116" output="&#x000B;"/>
+            <key code="117" output="&#x007F;"/>
+            <key code="118" output="&#x0010;"/>
+            <key code="119" output="&#x0004;"/>
+            <key code="120" output="&#x0010;"/>
+            <key code="121" output="&#x000C;"/>
+            <key code="122" output="&#x0010;"/>
+            <key code="123" output="&#x001C;"/>
+            <key code="124" output="&#x001D;"/>
+            <key code="125" output="&#x001F;"/>
+            <key code="126" output="&#x001E;"/>
+        </keyMap>
+        <keyMap index="8" baseMapSet="16c" baseIndex="0">
+            <key code="18" output="!"/>
+            <key code="19" output="@"/>
+            <key code="20" output="#"/>
+            <key code="21" output="$"/>
+            <key code="22" output="^"/>
+            <key code="23" output="%"/>
+            <key code="25" output="("/>
+            <key code="26" output="&#x0026;"/>
+            <key code="27" output="_"/>
+            <key code="28" output="*"/>
+            <key code="29" output=")"/>
+            <key code="30" output="}"/>
+            <key code="33" output="{"/>
+            <key code="42" output="|"/>
+        </keyMap>
+        <keyMap index="9" baseMapSet="16c" baseIndex="1">
+            <key code="18" output="1"/>
+            <key code="19" output="2"/>
+            <key code="20" output="3"/>
+            <key code="21" output="4"/>
+            <key code="22" output="6"/>
+            <key code="23" output="5"/>
+            <key code="25" output="9"/>
+            <key code="26" output="7"/>
+            <key code="27" output="-"/>
+            <key code="28" output="8"/>
+            <key code="29" output="0"/>
+            <key code="30" output="]"/>
+            <key code="33" output="["/>
+            <key code="42" output="\"/>
+        </keyMap>
+    </keyMapSet>
+    <keyMapSet id="994">
+        <keyMap baseIndex="0" baseMapSet="16c" index="0">
+            <key code="24" output="^"/>
+            <key code="30" output="["/>
+            <key code="33" output="@"/>
+            <key code="39" output=":"/>
+            <key code="42" output="]"/>
+            <key code="93" output="Â¥"/>
+            <key code="94" output="_"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+        <keyMap baseIndex="1" baseMapSet="16c" index="1">
+            <key code="19" output="&#x0022;"/>
+            <key code="22" output="&#x0026;"/>
+            <key code="24" output="~"/>
+            <key code="25" output=")"/>
+            <key code="26" output="&#x0027;"/>
+            <key code="27" output="="/>
+            <key code="28" output="("/>
+            <key code="29" output="0"/>
+            <key code="30" output="{"/>
+            <key code="33" output="`"/>
+            <key code="39" output="*"/>
+            <key code="41" output="+"/>
+            <key code="42" output="}"/>
+            <key code="93" output="|"/>
+            <key code="94" output="_"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+        <keyMap baseIndex="2" baseMapSet="16c" index="2">
+            <key code="24" output="^"/>
+            <key code="30" output="["/>
+            <key code="33" output="@"/>
+            <key code="39" output=":"/>
+            <key code="42" output="]"/>
+            <key code="93" output="Â¥"/>
+            <key code="94" output="_"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+        <keyMap baseIndex="3" baseMapSet="16c" index="3">
+            <key code="93" output="\"/>
+            <key action="1" code="94"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+        <keyMap baseIndex="4" baseMapSet="16c" index="4">
+            <key code="93" output="|"/>
+            <key code="94" output="`"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+        <keyMap baseIndex="5" baseMapSet="16c" index="5">
+            <key code="93" output="\"/>
+            <key code="94" output="`"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+        <keyMap baseIndex="6" baseMapSet="16c" index="6">
+            <key code="93" output="\"/>
+            <key code="94" output="_"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+        <keyMap baseIndex="7" baseMapSet="16c" index="7">
+            <key code="93" output="|"/>
+            <key code="94" output="_"/>
+            <key code="95" output=","/>
+            <key action="5" code="102"/>
+            <key action="5" code="104"/>
+        </keyMap>
+    </keyMapSet>
+    <actions>
+        <action id="0">
+            <when next="s1" state="none"/>
+        </action>
+        <action id="1">
+            <when next="s2" state="none"/>
+        </action>
+        <action id="10">
+            <when output="O" state="none"/>
+            <when output="Ó" state="s1"/>
+            <when output="Ã’" state="s2"/>
+            <when output="Ô" state="s3"/>
+            <when output="Ö" state="s4"/>
+            <when output="Õ" state="s5"/>
+        </action>
+        <action id="11">
+            <when output="U" state="none"/>
+            <when output="Ú" state="s1"/>
+            <when output="Ù" state="s2"/>
+            <when output="Û" state="s3"/>
+            <when output="Ü" state="s4"/>
+        </action>
+        <action id="12">
+            <when output="Y" state="none"/>
+            <when output="Ÿ" state="s4"/>
+        </action>
+        <action id="13">
+            <when output="a" state="none"/>
+            <when output="á" state="s1"/>
+            <when output="à" state="s2"/>
+            <when output="â" state="s3"/>
+            <when output="ä" state="s4"/>
+            <when output="ã" state="s5"/>
+        </action>
+        <action id="14">
+            <when output="e" state="none"/>
+            <when output="é" state="s1"/>
+            <when output="è" state="s2"/>
+            <when output="ê" state="s3"/>
+            <when output="ë" state="s4"/>
+        </action>
+        <action id="15">
+            <when output="i" state="none"/>
+            <when output="í" state="s1"/>
+            <when output="ì" state="s2"/>
+            <when output="î" state="s3"/>
+            <when output="ï" state="s4"/>
+        </action>
+        <action id="16">
+            <when output="n" state="none"/>
+            <when output="ñ" state="s5"/>
+        </action>
+        <action id="17">
+            <when output="o" state="none"/>
+            <when output="ó" state="s1"/>
+            <when output="ò" state="s2"/>
+            <when output="ô" state="s3"/>
+            <when output="ö" state="s4"/>
+            <when output="õ" state="s5"/>
+        </action>
+        <action id="18">
+            <when output="u" state="none"/>
+            <when output="ú" state="s1"/>
+            <when output="ù" state="s2"/>
+            <when output="û" state="s3"/>
+            <when output="ü" state="s4"/>
+        </action>
+        <action id="19">
+            <when output="y" state="none"/>
+            <when output="ÿ" state="s4"/>
+        </action>
+        <action id="2">
+            <when next="s3" state="none"/>
+        </action>
+        <action id="3">
+            <when next="s4" state="none"/>
+        </action>
+        <action id="4">
+            <when next="s5" state="none"/>
+        </action>
+        <action id="5">
+            <when output=" " state="none"/>
+            <when output="´" state="s1"/>
+            <when output="`" state="s2"/>
+            <when output="ˆ" state="s3"/>
+            <when output="¨" state="s4"/>
+            <when output="˜" state="s5"/>
+        </action>
+        <action id="6">
+            <when output="A" state="none"/>
+            <when output="Ã" state="s1"/>
+            <when output="À" state="s2"/>
+            <when output="Â" state="s3"/>
+            <when output="Ä" state="s4"/>
+            <when output="Ã" state="s5"/>
+        </action>
+        <action id="7">
+            <when output="E" state="none"/>
+            <when output="É" state="s1"/>
+            <when output="È" state="s2"/>
+            <when output="Ê" state="s3"/>
+            <when output="Ë" state="s4"/>
+        </action>
+        <action id="8">
+            <when output="I" state="none"/>
+            <when output="Ã" state="s1"/>
+            <when output="Ì" state="s2"/>
+            <when output="ÃŽ" state="s3"/>
+            <when output="Ã" state="s4"/>
+        </action>
+        <action id="9">
+            <when output="N" state="none"/>
+            <when output="Ñ" state="s5"/>
+        </action>
+    </actions>
+    <terminators>
+        <when output="´" state="s1"/>
+        <when output="`" state="s2"/>
+        <when output="ˆ" state="s3"/>
+        <when output="¨" state="s4"/>
+        <when output="˜" state="s5"/>
+    </terminators>
+</keyboard>
diff --git a/etc/Dark.terminal b/etc/Dark.terminal
new file mode 100644
index 00000000..8e06eb2f
--- /dev/null
+++ b/etc/Dark.terminal
@@ -0,0 +1,1684 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>ANSIBlackColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECswLjA4NjQxNDgy
+	MTQ1IDAuMDgyNjczMjQ0MTggMC4wNjE3NjQxODgxMSAxTxApMC4wNjg1NjU0ODc4NiAw
+	LjA2NjU1MDExMzI2IDAuMDUzMTg5MzQ0NwAQAYACgAbTFBUNFhcYVE5TSURVTlNJQ0MQ
+	B4ADgAXSGg0bHFdOUy5kYXRhTxEMSAAADEhMaW5vAhAAAG1udHJSR0IgWFlaIAfOAAIA
+	CQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAAAAD21gABAAAAANMt
+	SFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	EWNwcnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJrcHQAAAIEAAAAFHJY
+	WVoAAAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJUAAAAcGRtZGQA
+	AALEAAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAAFG1lYXMAAAQM
+	AAAAJHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJUUkMAAAQ8AAAI
+	DHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2FyZCBDb21wYW55
+	AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAEnNSR0IgSUVD
+	NjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAAAAAAAAAAAAAAWFla
+	IAAAAAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAAAAAAAAAkoAAA
+	D4QAALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAW
+	SUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVmYXVsdCBS
+	R0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVm
+	YXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAAAAAAAAAAAABk
+	ZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVDNjE5NjYt
+	Mi4xAAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYx
+	OTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAAE6T+ABRfLgAQ
+	zxQAA+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFzAAAAAAAAAAEA
+	AAAAAAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAAAAAABAAAAAAF
+	AAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABtAHIAdwB8AIEA
+	hgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA8AD2APsBAQEH
+	AQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGSAZoBoQGpAbEB
+	uQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKY
+	AqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64D
+	ugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE0wThBPAE/gUN
+	BRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0G
+	rwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8IMghGCFoIbgiC
+	CJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9ClQKagqBCpgK
+	rgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0N
+	DSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MP
+	zw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLD
+	EuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMW
+	JhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3
+	Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1HR4dRx1wHZkd
+	wx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1IaEhziH7
+	IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXHJfcmJyZXJocm
+	tyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsqzysCKzYraSud
+	K9EsBSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsx
+	EjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjau
+	Nuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuqO+g8JzxlPKQ8
+	4z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5CMEJyQrVC90M6
+	Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1JY0mpSfBK
+	N0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91QJ1BxULtRBlFQ
+	UZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeSV+BYL1h9WMtZ
+	GllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8
+	YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNp
+	mmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw4HE6cZVx8HJL
+	cqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnnekZ6pXsEe2N7
+	wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VH
+	hauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7OjzaP
+	npAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4mSSZkJn8
+	mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMGo3aj5qRWpMel
+	OKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1
+	sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7urW7LrunvCG8
+	m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dBx7/IPci8
+	yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V
+	0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb
+	42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw
+	5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c
+	/23//4AE0h4fICFaJGNsYXNzbmFtZVgkY2xhc3Nlc11OU011dGFibGVEYXRhoyAiI1ZO
+	U0RhdGFYTlNPYmplY3TSHh8lJlxOU0NvbG9yU3BhY2WiJyNcTlNDb2xvclNwYWNl0h4f
+	KSpXTlNDb2xvcqIpI18QD05TS2V5ZWRBcmNoaXZlctEtLlRyb290gAEACAARABoAIwAt
+	ADIANwA/AEUAUABdAGMAcACFAIwAugDmAOgA6gDsAPMA+AD+AQABAgEEAQkBEQ1dDV8N
+	ZA1vDXgNhg2KDZENmg2fDawNrw28DcENyQ3MDd4N4Q3mAAAAAAAAAgEAAAAAAAAALwAA
+	AAAAAAAAAAAAAAAADeg=
+	</data>
+	<key>ANSIBlueColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECgwLjIzNzg3NzEz
+	MDUgMC4zODQ0ODYzMTc2IDAuNDAxODE3NTYwMiAxTxAnMC4xODUxNzgwMTE3IDAuMzEy
+	NjgzODIwNyAwLjMyNzM1MzE0OTcAEAGAAoAG0xQVDRYXGFROU0lEVU5TSUNDEAeAA4AF
+	0hoNGxxXTlMuZGF0YU8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAx
+	AABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0
+	AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAAC
+	GAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAA
+	AIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0
+	ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0
+	AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVz
+	YwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2
+	LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAA
+	AABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2
+	z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBo
+	dHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNv
+	bG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQg
+	UkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAA
+	AAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAA
+	AAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0y
+	LjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPt
+	zAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAA
+	AAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8A
+	FAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQ
+	AJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMB
+	GQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJ
+	AdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwC
+	tgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPT
+	A+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsF
+	OgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbR
+	BuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoI
+	vgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrc
+	CvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUAN
+	Wg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJ
+	ECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMT
+	IxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZs
+	Fo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQa
+	KhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4W
+	HkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUi
+	giKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcY
+	J0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUs
+	OSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGC
+	Mbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3
+	YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1h
+	PaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BE
+	A0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrE
+	SwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZS
+	MVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4
+	WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh
+	9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpI
+	ap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFz
+	XXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyB
+	fOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6G
+	cobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBu
+	kNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWb
+	QpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYa
+	poum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx
+	1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2P
+	vgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnK
+	OMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY
+	11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vk
+	c+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/
+	8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t//+A
+	BNIeHyAhWiRjbGFzc25hbWVYJGNsYXNzZXNdTlNNdXRhYmxlRGF0YaMgIiNWTlNEYXRh
+	WE5TT2JqZWN00h4fJSZcTlNDb2xvclNwYWNloicjXE5TQ29sb3JTcGFjZdIeHykqV05T
+	Q29sb3KiKSNfEA9OU0tleWVkQXJjaGl2ZXLRLS5Ucm9vdIABAAgAEQAaACMALQAyADcA
+	PwBFAFAAXQBjAHAAhQCMALcA4QDjAOUA5wDuAPMA+QD7AP0A/wEEAQwNWA1aDV8Nag1z
+	DYENhQ2MDZUNmg2nDaoNtw28DcQNxw3ZDdwN4QAAAAAAAAIBAAAAAAAAAC8AAAAAAAAA
+	AAAAAAAAAA3j
+	</data>
+	<key>ANSIBrightBlackColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECgwLjI5ODc1MjYw
+	NTkgMC4yNzU0NDMxMzY3IDAuMjA4Mjg5OTIxMyAxTxAnMC4yMzI1NTg4MTY3IDAuMjEz
+	OTgzMDU4OSAwLjE1NzM3MjYyMzcAEAGAAoAG0xQVDRYXGFROU0lEVU5TSUNDEAeAA4AF
+	0hoNGxxXTlMuZGF0YU8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAx
+	AABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0
+	AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAAC
+	GAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAA
+	AIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0
+	ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0
+	AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVz
+	YwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2
+	LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAA
+	AABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2
+	z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBo
+	dHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNv
+	bG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQg
+	UkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAA
+	AAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAA
+	AAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0y
+	LjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPt
+	zAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAA
+	AAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8A
+	FAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQ
+	AJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMB
+	GQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJ
+	AdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwC
+	tgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPT
+	A+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsF
+	OgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbR
+	BuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoI
+	vgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrc
+	CvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUAN
+	Wg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJ
+	ECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMT
+	IxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZs
+	Fo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQa
+	KhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4W
+	HkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUi
+	giKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcY
+	J0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUs
+	OSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGC
+	Mbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3
+	YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1h
+	PaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BE
+	A0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrE
+	SwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZS
+	MVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4
+	WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh
+	9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpI
+	ap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFz
+	XXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyB
+	fOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6G
+	cobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBu
+	kNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWb
+	QpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYa
+	poum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx
+	1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2P
+	vgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnK
+	OMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY
+	11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vk
+	c+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/
+	8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t//+A
+	BNIeHyAhWiRjbGFzc25hbWVYJGNsYXNzZXNdTlNNdXRhYmxlRGF0YaMgIiNWTlNEYXRh
+	WE5TT2JqZWN00h4fJSZcTlNDb2xvclNwYWNloicjXE5TQ29sb3JTcGFjZdIeHykqV05T
+	Q29sb3KiKSNfEA9OU0tleWVkQXJjaGl2ZXLRLS5Ucm9vdIABAAgAEQAaACMALQAyADcA
+	PwBFAFAAXQBjAHAAhQCMALcA4QDjAOUA5wDuAPMA+QD7AP0A/wEEAQwNWA1aDV8Nag1z
+	DYENhQ2MDZUNmg2nDaoNtw28DcQNxw3ZDdwN4QAAAAAAAAIBAAAAAAAAAC8AAAAAAAAA
+	AAAAAAAAAA3j
+	</data>
+	<key>ANSIBrightBlueColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECgwLjI5OTI5MDg5
+	NTUgMC40ODI3MTYzODE1IDAuNDk2MTAyODA5OSAxTxAnMC4yMzg5NDMwNzAyIDAuNDA5
+	NTA4Mzc3MyAwLjQyMDQxODk3NzcAEAGAAoAG0xQVDRYXGFROU0lEVU5TSUNDEAeAA4AF
+	0hoNGxxXTlMuZGF0YU8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAx
+	AABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0
+	AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAAC
+	GAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAA
+	AIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0
+	ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0
+	AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVz
+	YwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2
+	LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAA
+	AABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2
+	z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBo
+	dHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNv
+	bG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQg
+	UkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAA
+	AAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAA
+	AAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0y
+	LjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPt
+	zAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAA
+	AAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8A
+	FAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQ
+	AJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMB
+	GQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJ
+	AdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwC
+	tgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPT
+	A+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsF
+	OgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbR
+	BuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoI
+	vgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrc
+	CvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUAN
+	Wg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJ
+	ECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMT
+	IxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZs
+	Fo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQa
+	KhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4W
+	HkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUi
+	giKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcY
+	J0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUs
+	OSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGC
+	Mbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3
+	YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1h
+	PaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BE
+	A0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrE
+	SwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZS
+	MVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4
+	WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh
+	9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpI
+	ap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFz
+	XXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyB
+	fOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6G
+	cobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBu
+	kNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWb
+	QpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYa
+	poum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx
+	1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2P
+	vgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnK
+	OMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY
+	11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vk
+	c+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/
+	8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t//+A
+	BNIeHyAhWiRjbGFzc25hbWVYJGNsYXNzZXNdTlNNdXRhYmxlRGF0YaMgIiNWTlNEYXRh
+	WE5TT2JqZWN00h4fJSZcTlNDb2xvclNwYWNloicjXE5TQ29sb3JTcGFjZdIeHykqV05T
+	Q29sb3KiKSNfEA9OU0tleWVkQXJjaGl2ZXLRLS5Ucm9vdIABAAgAEQAaACMALQAyADcA
+	PwBFAFAAXQBjAHAAhQCMALcA4QDjAOUA5wDuAPMA+QD7AP0A/wEEAQwNWA1aDV8Nag1z
+	DYENhQ2MDZUNmg2nDaoNtw28DcQNxw3ZDdwN4QAAAAAAAAIBAAAAAAAAAC8AAAAAAAAA
+	AAAAAAAAAA3j
+	</data>
+	<key>ANSIBrightCyanColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECgwLjQxODI5MzY1
+	NDkgMC41OTkyNjI5NTI4IDAuNDIxMTYzNDY5NiAxTxAnMC4zNDk1MDUyMTU5IDAuNTM3
+	MzI0Nzg2MiAwLjM0NjU0MzYzOTkAEAGAAoAG0xQVDRYXGFROU0lEVU5TSUNDEAeAA4AF
+	0hoNGxxXTlMuZGF0YU8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAx
+	AABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0
+	AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAAC
+	GAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAA
+	AIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0
+	ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0
+	AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVz
+	YwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2
+	LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAA
+	AABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2
+	z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBo
+	dHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNv
+	bG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQg
+	UkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAA
+	AAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAA
+	AAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0y
+	LjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPt
+	zAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAA
+	AAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8A
+	FAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQ
+	AJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMB
+	GQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJ
+	AdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwC
+	tgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPT
+	A+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsF
+	OgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbR
+	BuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoI
+	vgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrc
+	CvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUAN
+	Wg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJ
+	ECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMT
+	IxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZs
+	Fo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQa
+	KhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4W
+	HkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUi
+	giKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcY
+	J0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUs
+	OSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGC
+	Mbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3
+	YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1h
+	PaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BE
+	A0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrE
+	SwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZS
+	MVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4
+	WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh
+	9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpI
+	ap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFz
+	XXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyB
+	fOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6G
+	cobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBu
+	kNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWb
+	QpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYa
+	poum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx
+	1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2P
+	vgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnK
+	OMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY
+	11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vk
+	c+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/
+	8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t//+A
+	BNIeHyAhWiRjbGFzc25hbWVYJGNsYXNzZXNdTlNNdXRhYmxlRGF0YaMgIiNWTlNEYXRh
+	WE5TT2JqZWN00h4fJSZcTlNDb2xvclNwYWNloicjXE5TQ29sb3JTcGFjZdIeHykqV05T
+	Q29sb3KiKSNfEA9OU0tleWVkQXJjaGl2ZXLRLS5Ucm9vdIABAAgAEQAaACMALQAyADcA
+	PwBFAFAAXQBjAHAAhQCMALcA4QDjAOUA5wDuAPMA+QD7AP0A/wEEAQwNWA1aDV8Nag1z
+	DYENhQ2MDZUNmg2nDaoNtw28DcQNxw3ZDdwN4QAAAAAAAAIBAAAAAAAAAC8AAAAAAAAA
+	AAAAAAAAAA3j
+	</data>
+	<key>ANSIBrightGreenColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECgwLjU1NjU1MTQ1
+	NjUgMC42MDA4OTg1NjM5IDAuMTE2MjQxOTY5MiAxTxAoMC40ODUyMTc3NTAxIDAuNTQx
+	MTE2ODkzMyAwLjA5MjAwNzkyMDE1ABABgAKABtMUFQ0WFxhUTlNJRFVOU0lDQxAHgAOA
+	BdIaDRscV05TLmRhdGFPEQxIAAAMSExpbm8CEAAAbW50clJHQiBYWVogB84AAgAJAAYA
+	MQAAYWNzcE1TRlQAAAAASUVDIHNSR0IAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1IUCAg
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARY3By
+	dAAAAVAAAAAzZGVzYwAAAYQAAABsd3RwdAAAAfAAAAAUYmtwdAAAAgQAAAAUclhZWgAA
+	AhgAAAAUZ1hZWgAAAiwAAAAUYlhZWgAAAkAAAAAUZG1uZAAAAlQAAABwZG1kZAAAAsQA
+	AACIdnVlZAAAA0wAAACGdmlldwAAA9QAAAAkbHVtaQAAA/gAAAAUbWVhcwAABAwAAAAk
+	dGVjaAAABDAAAAAMclRSQwAABDwAAAgMZ1RSQwAABDwAAAgMYlRSQwAABDwAAAgMdGV4
+	dAAAAABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1QYWNrYXJkIENvbXBhbnkAAGRl
+	c2MAAAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAASc1JHQiBJRUM2MTk2
+	Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAFhZWiAAAAAAAADzUQABAAAAARbMWFlaIAAAAAAAAAAAAAAAAAAAAABYWVogAAAA
+	AAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAA
+	ts9kZXNjAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAABZJRUMg
+	aHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBj
+	b2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0
+	IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MA
+	AAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0yLjEA
+	AAAAAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVDNjE5NjYt
+	Mi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2aWV3AAAAAAATpP4AFF8uABDPFAAD
+	7cwABBMLAANcngAAAAFYWVogAAAAAABMCVYAUAAAAFcf521lYXMAAAAAAAAAAQAAAAAA
+	AAAAAAAAAAAAAAAAAAKPAAAAAnNpZyAAAAAAQ1JUIGN1cnYAAAAAAAAEAAAAAAUACgAP
+	ABQAGQAeACMAKAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBoAG0AcgB3AHwAgQCGAIsA
+	kACVAJoAnwCkAKkArgCyALcAvADBAMYAywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQET
+	ARkBHwElASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1AXwBgwGLAZIBmgGhAakBsQG5AcEB
+	yQHRAdkB4QHpAfIB+gIDAgwCFAIdAiYCLwI4AkECSwJUAl0CZwJxAnoChAKOApgCogKs
+	ArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNPA1oDZgNyA34DigOWA6IDrgO6A8cD
+	0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSoBLYExATTBOEE8AT+BQ0FHAUr
+	BToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkGagZ7BowGnQavBsAG
+	0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgyCEYIWghuCIIIlgiq
+	CL4I0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQonCj0KVApqCoEKmAquCsUK
+	3ArzCwsLIgs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUMjgynDMAM2QzzDQ0NJg1A
+	DVoNdA2ODakNww3eDfgOEw4uDkkOZA5/DpsOtg7SDu4PCQ8lD0EPXg96D5YPsw/PD+wQ
+	CRAmEEMQYRB+EJsQuRDXEPURExExEU8RbRGMEaoRyRHoEgcSJhJFEmQShBKjEsMS4xMD
+	EyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIVNBVWFXgVmxW9FeAWAxYmFkkW
+	bBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY+hkgGUUZaxmRGbcZ3RoE
+	GioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzMHPUdHh1HHXAdmR3DHewe
+	Fh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAhHCFIIXUhoSHOIfsiJyJV
+	IoIiryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWXJccl9yYnJlcmhya3Jugn
+	GCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgpaymdKdAqAio1KmgqmyrPKwIrNitpK50r0SwF
+	LDksbiyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8kL1ovkS/HL/4wNTBsMKQw2zESMUox
+	gjG6MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01hzXCNf02NzZyNq426Tck
+	N2A3nDfXOBQ4UDiMOMg5BTlCOX85vDn5OjY6dDqyOu87LTtrO6o76DwnPGU8pDzjPSI9
+	YT2hPeA+ID5gPqA+4D8hP2E/oj/iQCNAZECmQOdBKUFqQaxB7kIwQnJCtUL3QzpDfUPA
+	RANER0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVHe0fASAVIS0iRSNdJHUljSalJ8Eo3Sn1K
+	xEsMS1NLmkviTCpMcky6TQJNSk2TTdxOJU5uTrdPAE9JT5NP3VAnUHFQu1EGUVBRm1Hm
+	UjFSfFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1ZcVqlW91dEV5JX4FgvWH1Yy1kaWWlZ
+	uFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69Xw9fYV+zYAVgV2CqYPxhT2Gi
+	YfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeTZ+loP2iWaOxpQ2maafFq
+	SGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtwhnDgcTpxlXHwcktypnMB
+	c11zuHQUdHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJeed6RnqlewR7Y3vCfCF8
+	gXzhfUF9oX4BfmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSDV4O6hB2EgITjhUeFq4YO
+	hnKG14c7h5+IBIhpiM6JM4mZif6KZIrKizCLlov8jGOMyo0xjZiN/45mjs6PNo+ekAaQ
+	bpDWkT+RqJIRknqS45NNk7aUIJSKlPSVX5XJljSWn5cKl3WX4JhMmLiZJJmQmfyaaJrV
+	m0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobaiJqKWowajdqPmpFakx6U4pamm
+	GqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1ErbiuLa6hrxavi7AAsHWw6rFg
+	sdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnCuju6tbsuu6e8IbybvRW9
+	j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjGRsbDx0HHv8g9yLzJOsm5
+	yjjKt8s2y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW
+	2Ndc1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W3hzeot8p36/gNuC94UThzOJT4tvjY+Pr
+	5HPk/OWE5g3mlucf56noMui86Ubp0Opb6uXrcOv77IbtEe2c7ijutO9A78zwWPDl8XLx
+	//KM8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8B/yY/Sn9uv5L/tz/bf//
+	gATSHh8gIVokY2xhc3NuYW1lWCRjbGFzc2VzXU5TTXV0YWJsZURhdGGjICIjVk5TRGF0
+	YVhOU09iamVjdNIeHyUmXE5TQ29sb3JTcGFjZaInI1xOU0NvbG9yU3BhY2XSHh8pKldO
+	U0NvbG9yoikjXxAPTlNLZXllZEFyY2hpdmVy0S0uVHJvb3SAAQAIABEAGgAjAC0AMgA3
+	AD8ARQBQAF0AYwBwAIUAjAC3AOIA5ADmAOgA7wD0APoA/AD+AQABBQENDVkNWw1gDWsN
+	dA2CDYYNjQ2WDZsNqA2rDbgNvQ3FDcgN2g3dDeIAAAAAAAACAQAAAAAAAAAvAAAAAAAA
+	AAAAAAAAAAAN5A==
+	</data>
+	<key>ANSIBrightMagentaColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECgwLjYwMDY0NDIz
+	MDggMC4zNTgyNDcxNjA5IDAuNDE4NDg3MzEwNCAxTxAnMC41MjUyNzc3OTM0IDAuMjc3
+	OTIxMzc4NiAwLjM0MzkxMTY3NzYAEAGAAoAG0xQVDRYXGFROU0lEVU5TSUNDEAeAA4AF
+	0hoNGxxXTlMuZGF0YU8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAx
+	AABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0
+	AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAAC
+	GAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAA
+	AIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0
+	ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0
+	AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVz
+	YwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2
+	LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAA
+	AABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2
+	z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBo
+	dHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNv
+	bG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQg
+	UkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAA
+	AAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAA
+	AAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0y
+	LjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPt
+	zAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAA
+	AAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8A
+	FAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQ
+	AJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMB
+	GQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJ
+	AdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwC
+	tgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPT
+	A+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsF
+	OgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbR
+	BuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoI
+	vgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrc
+	CvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUAN
+	Wg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJ
+	ECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMT
+	IxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZs
+	Fo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQa
+	KhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4W
+	HkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUi
+	giKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcY
+	J0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUs
+	OSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGC
+	Mbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3
+	YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1h
+	PaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BE
+	A0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrE
+	SwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZS
+	MVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4
+	WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh
+	9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpI
+	ap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFz
+	XXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyB
+	fOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6G
+	cobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBu
+	kNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWb
+	QpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYa
+	poum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx
+	1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2P
+	vgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnK
+	OMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY
+	11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vk
+	c+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/
+	8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t//+A
+	BNIeHyAhWiRjbGFzc25hbWVYJGNsYXNzZXNdTlNNdXRhYmxlRGF0YaMgIiNWTlNEYXRh
+	WE5TT2JqZWN00h4fJSZcTlNDb2xvclNwYWNloicjXE5TQ29sb3JTcGFjZdIeHykqV05T
+	Q29sb3KiKSNfEA9OU0tleWVkQXJjaGl2ZXLRLS5Ucm9vdIABAAgAEQAaACMALQAyADcA
+	PwBFAFAAXQBjAHAAhQCMALcA4QDjAOUA5wDuAPMA+QD7AP0A/wEEAQwNWA1aDV8Nag1z
+	DYENhQ2MDZUNmg2nDaoNtw28DcQNxw3ZDdwN4QAAAAAAAAIBAAAAAAAAAC8AAAAAAAAA
+	AAAAAAAAAA3j
+	</data>
+	<key>ANSIBrightRedColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECkwLjgwMDg5NjQ2
+	NTggMC4xOTc5Nzc1NDI5IDAuMDc4OTg3MTU4ODMgMU8QKDAuNzQ2NjI0MTEyMSAwLjEx
+	OTYyMTgwNTggMC4wNjg5MzI5ODc3NQAQAYACgAbTFBUNFhcYVE5TSURVTlNJQ0MQB4AD
+	gAXSGg0bHFdOUy5kYXRhTxEMSAAADEhMaW5vAhAAAG1udHJSR0IgWFlaIAfOAAIACQAG
+	ADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAAAAD21gABAAAAANMtSFAg
+	IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWNw
+	cnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJrcHQAAAIEAAAAFHJYWVoA
+	AAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJUAAAAcGRtZGQAAALE
+	AAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAAFG1lYXMAAAQMAAAA
+	JHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJUUkMAAAQ8AAAIDHRl
+	eHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2FyZCBDb21wYW55AABk
+	ZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAEnNSR0IgSUVDNjE5
+	NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAAAAAAAAAAAAAAWFlaIAAA
+	AAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAAAAAAAAAkoAAAD4QA
+	ALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAWSUVD
+	IGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVmYXVsdCBSR0Ig
+	Y29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVmYXVs
+	dCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNj
+	AAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVDNjE5NjYtMi4x
+	AAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2
+	LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAAE6T+ABRfLgAQzxQA
+	A+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFzAAAAAAAAAAEAAAAA
+	AAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAAAAAABAAAAAAFAAoA
+	DwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABtAHIAdwB8AIEAhgCL
+	AJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA8AD2APsBAQEHAQ0B
+	EwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGSAZoBoQGpAbEBuQHB
+	AckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKYAqIC
+	rAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64DugPH
+	A9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE0wThBPAE/gUNBRwF
+	KwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0GrwbA
+	BtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8IMghGCFoIbgiCCJYI
+	qgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9ClQKagqBCpgKrgrF
+	CtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0NDSYN
+	QA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/s
+	EAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMT
+	AxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJ
+	FmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3Gd0a
+	BBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1HR4dRx1wHZkdwx3s
+	HhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1IaEhziH7Iici
+	VSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXHJfcmJyZXJocmtybo
+	JxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsqzysCKzYraSudK9Es
+	BSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFK
+	MYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjauNuk3
+	JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuqO+g8JzxlPKQ84z0i
+	PWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5CMEJyQrVC90M6Q31D
+	wEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1JY0mpSfBKN0p9
+	SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91QJ1BxULtRBlFQUZtR
+	5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeSV+BYL1h9WMtZGllp
+	WbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8YU9h
+	omH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNpmmnx
+	akhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw4HE6cZVx8HJLcqZz
+	AXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnnekZ6pXsEe2N7wnwh
+	fIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VHhauG
+	DoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7OjzaPnpAG
+	kG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4mSSZkJn8mmia
+	1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMGo3aj5qRWpMelOKWp
+	phqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1sOqx
+	YLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7urW7LrunvCG8m70V
+	vY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dBx7/IPci8yTrJ
+	uco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV
+	1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj
+	6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy
+	8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c/23/
+	/4AE0h4fICFaJGNsYXNzbmFtZVgkY2xhc3Nlc11OU011dGFibGVEYXRhoyAiI1ZOU0Rh
+	dGFYTlNPYmplY3TSHh8lJlxOU0NvbG9yU3BhY2WiJyNcTlNDb2xvclNwYWNl0h4fKSpX
+	TlNDb2xvcqIpI18QD05TS2V5ZWRBcmNoaXZlctEtLlRyb290gAEACAARABoAIwAtADIA
+	NwA/AEUAUABdAGMAcACFAIwAuADjAOUA5wDpAPAA9QD7AP0A/wEBAQYBDg1aDVwNYQ1s
+	DXUNgw2HDY4Nlw2cDakNrA25Db4Nxg3JDdsN3g3jAAAAAAAAAgEAAAAAAAAALwAAAAAA
+	AAAAAAAAAAAADeU=
+	</data>
+	<key>ANSIBrightWhiteColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECgwLjgwMTk4NTU2
+	MTggMC43MzU3NzAwNDY3IDAuNTU1MDUzNTMyMSAxTxAnMC43NTY0MTcxNTUzIDAuNjg1
+	MjI5MjQxOCAwLjQ4MjY3MjM5MzMAEAGAAoAG0xQVDRYXGFROU0lEVU5TSUNDEAeAA4AF
+	0hoNGxxXTlMuZGF0YU8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAx
+	AABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0
+	AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAAC
+	GAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAA
+	AIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0
+	ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0
+	AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVz
+	YwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2
+	LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAA
+	AABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2
+	z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBo
+	dHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNv
+	bG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQg
+	UkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAA
+	AAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAA
+	AAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0y
+	LjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPt
+	zAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAA
+	AAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8A
+	FAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQ
+	AJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMB
+	GQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJ
+	AdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwC
+	tgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPT
+	A+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsF
+	OgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbR
+	BuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoI
+	vgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrc
+	CvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUAN
+	Wg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJ
+	ECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMT
+	IxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZs
+	Fo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQa
+	KhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4W
+	HkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUi
+	giKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcY
+	J0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUs
+	OSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGC
+	Mbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3
+	YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1h
+	PaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BE
+	A0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrE
+	SwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZS
+	MVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4
+	WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh
+	9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpI
+	ap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFz
+	XXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyB
+	fOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6G
+	cobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBu
+	kNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWb
+	QpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYa
+	poum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx
+	1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2P
+	vgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnK
+	OMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY
+	11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vk
+	c+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/
+	8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t//+A
+	BNIeHyAhWiRjbGFzc25hbWVYJGNsYXNzZXNdTlNNdXRhYmxlRGF0YaMgIiNWTlNEYXRh
+	WE5TT2JqZWN00h4fJSZcTlNDb2xvclNwYWNloicjXE5TQ29sb3JTcGFjZdIeHykqV05T
+	Q29sb3KiKSNfEA9OU0tleWVkQXJjaGl2ZXLRLS5Ucm9vdIABAAgAEQAaACMALQAyADcA
+	PwBFAFAAXQBjAHAAhQCMALcA4QDjAOUA5wDuAPMA+QD7AP0A/wEEAQwNWA1aDV8Nag1z
+	DYENhQ2MDZUNmg2nDaoNtw28DcQNxw3ZDdwN4QAAAAAAAAIBAAAAAAAAAC8AAAAAAAAA
+	AAAAAAAAAA3j
+	</data>
+	<key>ANSIBrightYellowColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECgwLjgwMTIwNzQy
+	MzIgMC41ODM2NjYxNDU4IDAuMTU3MjcwOTk3OCAxTxAnMC43NTIwNzE5NzY3IDAuNTE1
+	MzE4NzUxMyAwLjEyMjE5NTczNTYAEAGAAoAG0xQVDRYXGFROU0lEVU5TSUNDEAeAA4AF
+	0hoNGxxXTlMuZGF0YU8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAx
+	AABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0
+	AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAAC
+	GAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAA
+	AIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0
+	ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0
+	AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVz
+	YwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2
+	LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAA
+	AABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2
+	z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBo
+	dHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNv
+	bG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQg
+	UkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAA
+	AAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAA
+	AAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0y
+	LjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPt
+	zAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAA
+	AAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8A
+	FAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQ
+	AJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMB
+	GQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJ
+	AdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwC
+	tgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPT
+	A+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsF
+	OgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbR
+	BuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoI
+	vgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrc
+	CvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUAN
+	Wg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJ
+	ECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMT
+	IxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZs
+	Fo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQa
+	KhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4W
+	HkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUi
+	giKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcY
+	J0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUs
+	OSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGC
+	Mbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3
+	YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1h
+	PaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BE
+	A0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrE
+	SwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZS
+	MVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4
+	WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh
+	9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpI
+	ap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFz
+	XXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyB
+	fOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6G
+	cobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBu
+	kNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWb
+	QpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYa
+	poum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx
+	1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2P
+	vgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnK
+	OMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY
+	11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vk
+	c+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/
+	8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t//+A
+	BNIeHyAhWiRjbGFzc25hbWVYJGNsYXNzZXNdTlNNdXRhYmxlRGF0YaMgIiNWTlNEYXRh
+	WE5TT2JqZWN00h4fJSZcTlNDb2xvclNwYWNloicjXE5TQ29sb3JTcGFjZdIeHykqV05T
+	Q29sb3KiKSNfEA9OU0tleWVkQXJjaGl2ZXLRLS5Ucm9vdIABAAgAEQAaACMALQAyADcA
+	PwBFAFAAXQBjAHAAhQCMALcA4QDjAOUA5wDuAPMA+QD7AP0A/wEEAQwNWA1aDV8Nag1z
+	DYENhQ2MDZUNmg2nDaoNtw28DcQNxw3ZDdwN4QAAAAAAAAIBAAAAAAAAAC8AAAAAAAAA
+	AAAAAAAAAA3j
+	</data>
+	<key>ANSICyanColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECcwLjMzMzEyNjMw
+	NjUgMC40NzcwOTI5ODEzIDAuMzMyMDk3ODg4IDFPECcwLjI2ODExMDA5NjUgMC40MDc4
+	NTk0NDQ2IDAuMjYyOTM4MDIyNgAQAYACgAbTFBUNFhcYVE5TSURVTlNJQ0MQB4ADgAXS
+	Gg0bHFdOUy5kYXRhTxEMSAAADEhMaW5vAhAAAG1udHJSR0IgWFlaIAfOAAIACQAGADEA
+	AGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAAAAD21gABAAAAANMtSFAgIAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWNwcnQA
+	AAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJrcHQAAAIEAAAAFHJYWVoAAAIY
+	AAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJUAAAAcGRtZGQAAALEAAAA
+	iHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAAFG1lYXMAAAQMAAAAJHRl
+	Y2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJUUkMAAAQ8AAAIDHRleHQA
+	AAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2FyZCBDb21wYW55AABkZXNj
+	AAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAEnNSR0IgSUVDNjE5NjYt
+	Mi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAAAAAAAAAAAAAAWFlaIAAAAAAA
+	AG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAAAAAAAAAkoAAAD4QAALbP
+	ZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAWSUVDIGh0
+	dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVmYXVsdCBSR0IgY29s
+	b3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVmYXVsdCBS
+	R0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNjAAAA
+	AAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVDNjE5NjYtMi4xAAAA
+	AAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIu
+	MQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAAE6T+ABRfLgAQzxQAA+3M
+	AAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFzAAAAAAAAAAEAAAAAAAAA
+	AAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAAAAAABAAAAAAFAAoADwAU
+	ABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABtAHIAdwB8AIEAhgCLAJAA
+	lQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA8AD2APsBAQEHAQ0BEwEZ
+	AR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGSAZoBoQGpAbEBuQHBAckB
+	0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKYAqICrAK2
+	AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64DugPHA9MD
+	4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE0wThBPAE/gUNBRwFKwU6
+	BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0GrwbABtEG
+	4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8IMghGCFoIbgiCCJYIqgi+
+	CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9ClQKagqBCpgKrgrFCtwK
+	8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0NDSYNQA1a
+	DXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/sEAkQ
+	JhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMTAxMj
+	E0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJFmwW
+	jxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3Gd0aBBoq
+	GlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1HR4dRx1wHZkdwx3sHhYe
+	QB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1IaEhziH7IiciVSKC
+	Iq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXHJfcmJyZXJocmtyboJxgn
+	SSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsqzysCKzYraSudK9EsBSw5
+	LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFKMYIx
+	ujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjauNuk3JDdg
+	N5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuqO+g8JzxlPKQ84z0iPWE9
+	oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5CMEJyQrVC90M6Q31DwEQD
+	REdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1JY0mpSfBKN0p9SsRL
+	DEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91QJ1BxULtRBlFQUZtR5lIx
+	UnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeSV+BYL1h9WMtZGllpWbha
+	B1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8YU9homH1
+	YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNpmmnxakhq
+	n2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw4HE6cZVx8HJLcqZzAXNd
+	c7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnnekZ6pXsEe2N7wnwhfIF8
+	4X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VHhauGDoZy
+	hteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7OjzaPnpAGkG6Q
+	1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4mSSZkJn8mmia1ZtC
+	m6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMGo3aj5qRWpMelOKWpphqm
+	i6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1sOqxYLHW
+	skuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7urW7LrunvCG8m70VvY++
+	Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dBx7/IPci8yTrJuco4
+	yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV1tjX
+	XNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj6+Rz
+	5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy8f/y
+	jPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c/23//4AE
+	0h4fICFaJGNsYXNzbmFtZVgkY2xhc3Nlc11OU011dGFibGVEYXRhoyAiI1ZOU0RhdGFY
+	TlNPYmplY3TSHh8lJlxOU0NvbG9yU3BhY2WiJyNcTlNDb2xvclNwYWNl0h4fKSpXTlND
+	b2xvcqIpI18QD05TS2V5ZWRBcmNoaXZlctEtLlRyb290gAEACAARABoAIwAtADIANwA/
+	AEUAUABdAGMAcACFAIwAtgDgAOIA5ADmAO0A8gD4APoA/AD+AQMBCw1XDVkNXg1pDXIN
+	gA2EDYsNlA2ZDaYNqQ22DbsNww3GDdgN2w3gAAAAAAAAAgEAAAAAAAAALwAAAAAAAAAA
+	AAAAAAAADeI=
+	</data>
+	<key>ANSIGreenColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECkwLjQ0NjkyMDM5
+	NDkgMC40NzgzODYyMjMzIDAuMDkzNTM1OTM3MzcgMU8QKDAuMzcyNzA2MDU1NiAwLjQx
+	MDYwNDExOTMgMC4wNzU3MTQ2MzI4NwAQAYACgAbTFBUNFhcYVE5TSURVTlNJQ0MQB4AD
+	gAXSGg0bHFdOUy5kYXRhTxEMSAAADEhMaW5vAhAAAG1udHJSR0IgWFlaIAfOAAIACQAG
+	ADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAAAAD21gABAAAAANMtSFAg
+	IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWNw
+	cnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJrcHQAAAIEAAAAFHJYWVoA
+	AAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJUAAAAcGRtZGQAAALE
+	AAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAAFG1lYXMAAAQMAAAA
+	JHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJUUkMAAAQ8AAAIDHRl
+	eHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2FyZCBDb21wYW55AABk
+	ZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAEnNSR0IgSUVDNjE5
+	NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAAAAAAAAAAAAAAWFlaIAAA
+	AAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAAAAAAAAAkoAAAD4QA
+	ALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAWSUVD
+	IGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVmYXVsdCBSR0Ig
+	Y29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVmYXVs
+	dCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNj
+	AAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVDNjE5NjYtMi4x
+	AAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2
+	LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAAE6T+ABRfLgAQzxQA
+	A+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFzAAAAAAAAAAEAAAAA
+	AAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAAAAAABAAAAAAFAAoA
+	DwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABtAHIAdwB8AIEAhgCL
+	AJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA8AD2APsBAQEHAQ0B
+	EwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGSAZoBoQGpAbEBuQHB
+	AckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKYAqIC
+	rAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64DugPH
+	A9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE0wThBPAE/gUNBRwF
+	KwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0GrwbA
+	BtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8IMghGCFoIbgiCCJYI
+	qgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9ClQKagqBCpgKrgrF
+	CtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0NDSYN
+	QA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/s
+	EAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMT
+	AxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJ
+	FmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3Gd0a
+	BBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1HR4dRx1wHZkdwx3s
+	HhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1IaEhziH7Iici
+	VSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXHJfcmJyZXJocmtybo
+	JxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsqzysCKzYraSudK9Es
+	BSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFK
+	MYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjauNuk3
+	JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuqO+g8JzxlPKQ84z0i
+	PWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5CMEJyQrVC90M6Q31D
+	wEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1JY0mpSfBKN0p9
+	SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91QJ1BxULtRBlFQUZtR
+	5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeSV+BYL1h9WMtZGllp
+	WbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8YU9h
+	omH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNpmmnx
+	akhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw4HE6cZVx8HJLcqZz
+	AXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnnekZ6pXsEe2N7wnwh
+	fIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VHhauG
+	DoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7OjzaPnpAG
+	kG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4mSSZkJn8mmia
+	1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMGo3aj5qRWpMelOKWp
+	phqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1sOqx
+	YLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7urW7LrunvCG8m70V
+	vY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dBx7/IPci8yTrJ
+	uco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV
+	1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj
+	6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy
+	8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c/23/
+	/4AE0h4fICFaJGNsYXNzbmFtZVgkY2xhc3Nlc11OU011dGFibGVEYXRhoyAiI1ZOU0Rh
+	dGFYTlNPYmplY3TSHh8lJlxOU0NvbG9yU3BhY2WiJyNcTlNDb2xvclNwYWNl0h4fKSpX
+	TlNDb2xvcqIpI18QD05TS2V5ZWRBcmNoaXZlctEtLlRyb290gAEACAARABoAIwAtADIA
+	NwA/AEUAUABdAGMAcACFAIwAuADjAOUA5wDpAPAA9QD7AP0A/wEBAQYBDg1aDVwNYQ1s
+	DXUNgw2HDY4Nlw2cDakNrA25Db4Nxg3JDdsN3g3jAAAAAAAAAgEAAAAAAAAALwAAAAAA
+	AAAAAAAAAAAADeU=
+	</data>
+	<key>ANSIMagentaColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECgwLjQ3ODEyOTQ0
+	NjUgMC4yODgyOTk1NjA1IDAuMzMyMzUxODMzNiAxTxAnMC4zOTg3NjkxNDAyIDAuMjE3
+	NjQ0NzUxMSAwLjI2MzEzMjE4NDcAEAGAAoAG0xQVDRYXGFROU0lEVU5TSUNDEAeAA4AF
+	0hoNGxxXTlMuZGF0YU8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAx
+	AABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0
+	AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAAC
+	GAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAA
+	AIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0
+	ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0
+	AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVz
+	YwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2
+	LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAA
+	AABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2
+	z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBo
+	dHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNv
+	bG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQg
+	UkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAA
+	AAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAA
+	AAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0y
+	LjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPt
+	zAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAA
+	AAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8A
+	FAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQ
+	AJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMB
+	GQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJ
+	AdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwC
+	tgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPT
+	A+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsF
+	OgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbR
+	BuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoI
+	vgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrc
+	CvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUAN
+	Wg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJ
+	ECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMT
+	IxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZs
+	Fo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQa
+	KhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4W
+	HkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUi
+	giKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcY
+	J0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUs
+	OSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGC
+	Mbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3
+	YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1h
+	PaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BE
+	A0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrE
+	SwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZS
+	MVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4
+	WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh
+	9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpI
+	ap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFz
+	XXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyB
+	fOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6G
+	cobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBu
+	kNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWb
+	QpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYa
+	poum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx
+	1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2P
+	vgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnK
+	OMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY
+	11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vk
+	c+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/
+	8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t//+A
+	BNIeHyAhWiRjbGFzc25hbWVYJGNsYXNzZXNdTlNNdXRhYmxlRGF0YaMgIiNWTlNEYXRh
+	WE5TT2JqZWN00h4fJSZcTlNDb2xvclNwYWNloicjXE5TQ29sb3JTcGFjZdIeHykqV05T
+	Q29sb3KiKSNfEA9OU0tleWVkQXJjaGl2ZXLRLS5Ucm9vdIABAAgAEQAaACMALQAyADcA
+	PwBFAFAAXQBjAHAAhQCMALcA4QDjAOUA5wDuAPMA+QD7AP0A/wEEAQwNWA1aDV8Nag1z
+	DYENhQ2MDZUNmg2nDaoNtw28DcQNxw3ZDdwN4QAAAAAAAAIBAAAAAAAAAC8AAAAAAAAA
+	AAAAAAAAAA3j
+	</data>
+	<key>ANSIRedColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECkwLjYzODQyMzk3
+	OTMgMC4xNTYyMTk5NTkzIDAuMDYzNTUzODE3NTcgMU8QKTAuNTYzOTM3NzgzMiAwLjA5
+	NTk2MjQ3OTcxIDAuMDU3NzQ0NjQ0NTgAEAGAAoAG0xQVDRYXGFROU0lEVU5TSUNDEAeA
+	A4AF0hoNGxxXTlMuZGF0YU8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkA
+	BgAxAABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQ
+	ICAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFj
+	cHJ0AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFla
+	AAACGAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAAC
+	xAAAAIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAA
+	ACR0ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0
+	ZXh0AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAA
+	ZGVzYwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYx
+	OTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAA
+	AAAAAABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+E
+	AAC2z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklF
+	QyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdC
+	IGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1
+	bHQgUkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVz
+	YwAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIu
+	MQAAAAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2
+	Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8U
+	AAPtzAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAA
+	AAAAAAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAK
+	AA8AFAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYA
+	iwCQAJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwEN
+	ARMBGQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkB
+	wQHJAdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKi
+	AqwCtgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oD
+	xwPTA+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUc
+	BSsFOgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8G
+	wAbRBuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiW
+	CKoIvgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4K
+	xQrcCvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0m
+	DUANWg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P
+	7BAJECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLj
+	EwMTIxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYW
+	SRZsFo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxnd
+	GgQaKhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd
+	7B4WHkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yIn
+	IlUigiKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm
+	6CcYJ0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvR
+	LAUsOSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIx
+	SjGCMbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbp
+	NyQ3YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9
+	Ij1hPaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9
+	Q8BEA0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdK
+	fUrESwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGb
+	UeZSMVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZ
+	aVm4WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFP
+	YaJh9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp
+	8WpIap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3Km
+	cwFzXXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8
+	IXyBfOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wr
+	hg6GcobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56Q
+	BpBukNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/Jpo
+	mtWbQpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTil
+	qaYapoum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDq
+	sWCx1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9
+	Fb2Pvgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6
+	ybnKOMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHW
+	VdbY11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj
+	4+vkc+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXx
+	cvH/8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t
+	//+ABNIeHyAhWiRjbGFzc25hbWVYJGNsYXNzZXNdTlNNdXRhYmxlRGF0YaMgIiNWTlNE
+	YXRhWE5TT2JqZWN00h4fJSZcTlNDb2xvclNwYWNloicjXE5TQ29sb3JTcGFjZdIeHykq
+	V05TQ29sb3KiKSNfEA9OU0tleWVkQXJjaGl2ZXLRLS5Ucm9vdIABAAgAEQAaACMALQAy
+	ADcAPwBFAFAAXQBjAHAAhQCMALgA5ADmAOgA6gDxAPYA/AD+AQABAgEHAQ8NWw1dDWIN
+	bQ12DYQNiA2PDZgNnQ2qDa0Nug2/DccNyg3cDd8N5AAAAAAAAAIBAAAAAAAAAC8AAAAA
+	AAAAAAAAAAAAAA3m
+	</data>
+	<key>ANSIWhiteColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECgwLjU1ODA4ODcx
+	OTggMC41MTk0NDU3NzY5IDAuMzg5Mjk3NjA0NiAxTxAnMC40ODQxOTY0ODQxIDAuNDQ3
+	NjYxMzQwMiAwLjMxNjI0MDY2ODMAEAGAAoAG0xQVDRYXGFROU0lEVU5TSUNDEAeAA4AF
+	0hoNGxxXTlMuZGF0YU8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAx
+	AABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0
+	AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAAC
+	GAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAA
+	AIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0
+	ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0
+	AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVz
+	YwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2
+	LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAA
+	AABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2
+	z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBo
+	dHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNv
+	bG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQg
+	UkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAA
+	AAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAA
+	AAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0y
+	LjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPt
+	zAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAA
+	AAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8A
+	FAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQ
+	AJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMB
+	GQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJ
+	AdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwC
+	tgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPT
+	A+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsF
+	OgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbR
+	BuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoI
+	vgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrc
+	CvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUAN
+	Wg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJ
+	ECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMT
+	IxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZs
+	Fo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQa
+	KhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4W
+	HkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUi
+	giKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcY
+	J0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUs
+	OSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGC
+	Mbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3
+	YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1h
+	PaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BE
+	A0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrE
+	SwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZS
+	MVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4
+	WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh
+	9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpI
+	ap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFz
+	XXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyB
+	fOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6G
+	cobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBu
+	kNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWb
+	QpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYa
+	poum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx
+	1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2P
+	vgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnK
+	OMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY
+	11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vk
+	c+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/
+	8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t//+A
+	BNIeHyAhWiRjbGFzc25hbWVYJGNsYXNzZXNdTlNNdXRhYmxlRGF0YaMgIiNWTlNEYXRh
+	WE5TT2JqZWN00h4fJSZcTlNDb2xvclNwYWNloicjXE5TQ29sb3JTcGFjZdIeHykqV05T
+	Q29sb3KiKSNfEA9OU0tleWVkQXJjaGl2ZXLRLS5Ucm9vdIABAAgAEQAaACMALQAyADcA
+	PwBFAFAAXQBjAHAAhQCMALcA4QDjAOUA5wDuAPMA+QD7AP0A/wEEAQwNWA1aDV8Nag1z
+	DYENhQ2MDZUNmg2nDaoNtw28DcQNxw3ZDdwN4QAAAAAAAAIBAAAAAAAAAC8AAAAAAAAA
+	AAAAAAAAAA3j
+	</data>
+	<key>ANSIYellowColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECgwLjYzOTQxMzA1
+	ODggMC40NjU0MDE4MjgzIDAuMTI1ODc5NzM0OCAxTxAoMC41Njg4Njk1OTA4IDAuMzky
+	MjI2OTY0MiAwLjA5ODMyNTU2NTQ2ABABgAKABtMUFQ0WFxhUTlNJRFVOU0lDQxAHgAOA
+	BdIaDRscV05TLmRhdGFPEQxIAAAMSExpbm8CEAAAbW50clJHQiBYWVogB84AAgAJAAYA
+	MQAAYWNzcE1TRlQAAAAASUVDIHNSR0IAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1IUCAg
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARY3By
+	dAAAAVAAAAAzZGVzYwAAAYQAAABsd3RwdAAAAfAAAAAUYmtwdAAAAgQAAAAUclhZWgAA
+	AhgAAAAUZ1hZWgAAAiwAAAAUYlhZWgAAAkAAAAAUZG1uZAAAAlQAAABwZG1kZAAAAsQA
+	AACIdnVlZAAAA0wAAACGdmlldwAAA9QAAAAkbHVtaQAAA/gAAAAUbWVhcwAABAwAAAAk
+	dGVjaAAABDAAAAAMclRSQwAABDwAAAgMZ1RSQwAABDwAAAgMYlRSQwAABDwAAAgMdGV4
+	dAAAAABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1QYWNrYXJkIENvbXBhbnkAAGRl
+	c2MAAAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAASc1JHQiBJRUM2MTk2
+	Ni0yLjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAFhZWiAAAAAAAADzUQABAAAAARbMWFlaIAAAAAAAAAAAAAAAAAAAAABYWVogAAAA
+	AAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAA
+	ts9kZXNjAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAABZJRUMg
+	aHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAZGVzYwAAAAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBj
+	b2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0
+	IFJHQiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MA
+	AAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0yLjEA
+	AAAAAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVDNjE5NjYt
+	Mi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2aWV3AAAAAAATpP4AFF8uABDPFAAD
+	7cwABBMLAANcngAAAAFYWVogAAAAAABMCVYAUAAAAFcf521lYXMAAAAAAAAAAQAAAAAA
+	AAAAAAAAAAAAAAAAAAKPAAAAAnNpZyAAAAAAQ1JUIGN1cnYAAAAAAAAEAAAAAAUACgAP
+	ABQAGQAeACMAKAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBoAG0AcgB3AHwAgQCGAIsA
+	kACVAJoAnwCkAKkArgCyALcAvADBAMYAywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQET
+	ARkBHwElASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1AXwBgwGLAZIBmgGhAakBsQG5AcEB
+	yQHRAdkB4QHpAfIB+gIDAgwCFAIdAiYCLwI4AkECSwJUAl0CZwJxAnoChAKOApgCogKs
+	ArYCwQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNPA1oDZgNyA34DigOWA6IDrgO6A8cD
+	0wPgA+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSoBLYExATTBOEE8AT+BQ0FHAUr
+	BToFSQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkGagZ7BowGnQavBsAG
+	0QbjBvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgyCEYIWghuCIIIlgiq
+	CL4I0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQonCj0KVApqCoEKmAquCsUK
+	3ArzCwsLIgs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUMjgynDMAM2QzzDQ0NJg1A
+	DVoNdA2ODakNww3eDfgOEw4uDkkOZA5/DpsOtg7SDu4PCQ8lD0EPXg96D5YPsw/PD+wQ
+	CRAmEEMQYRB+EJsQuRDXEPURExExEU8RbRGMEaoRyRHoEgcSJhJFEmQShBKjEsMS4xMD
+	EyMTQxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIVNBVWFXgVmxW9FeAWAxYmFkkW
+	bBaPFrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY+hkgGUUZaxmRGbcZ3RoE
+	GioaURp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzMHPUdHh1HHXAdmR3DHewe
+	Fh5AHmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAhHCFIIXUhoSHOIfsiJyJV
+	IoIiryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWXJccl9yYnJlcmhya3Jugn
+	GCdJJ3onqyfcKA0oPyhxKKIo1CkGKTgpaymdKdAqAio1KmgqmyrPKwIrNitpK50r0SwF
+	LDksbiyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8kL1ovkS/HL/4wNTBsMKQw2zESMUox
+	gjG6MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01hzXCNf02NzZyNq426Tck
+	N2A3nDfXOBQ4UDiMOMg5BTlCOX85vDn5OjY6dDqyOu87LTtrO6o76DwnPGU8pDzjPSI9
+	YT2hPeA+ID5gPqA+4D8hP2E/oj/iQCNAZECmQOdBKUFqQaxB7kIwQnJCtUL3QzpDfUPA
+	RANER0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVHe0fASAVIS0iRSNdJHUljSalJ8Eo3Sn1K
+	xEsMS1NLmkviTCpMcky6TQJNSk2TTdxOJU5uTrdPAE9JT5NP3VAnUHFQu1EGUVBRm1Hm
+	UjFSfFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1ZcVqlW91dEV5JX4FgvWH1Yy1kaWWlZ
+	uFoHWlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69Xw9fYV+zYAVgV2CqYPxhT2Gi
+	YfViSWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeTZ+loP2iWaOxpQ2maafFq
+	SGqfavdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtwhnDgcTpxlXHwcktypnMB
+	c11zuHQUdHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJeed6RnqlewR7Y3vCfCF8
+	gXzhfUF9oX4BfmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSDV4O6hB2EgITjhUeFq4YO
+	hnKG14c7h5+IBIhpiM6JM4mZif6KZIrKizCLlov8jGOMyo0xjZiN/45mjs6PNo+ekAaQ
+	bpDWkT+RqJIRknqS45NNk7aUIJSKlPSVX5XJljSWn5cKl3WX4JhMmLiZJJmQmfyaaJrV
+	m0Kbr5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobaiJqKWowajdqPmpFakx6U4pamm
+	GqaLpv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1ErbiuLa6hrxavi7AAsHWw6rFg
+	sdayS7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnCuju6tbsuu6e8IbybvRW9
+	j74KvoS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjGRsbDx0HHv8g9yLzJOsm5
+	yjjKt8s2y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW
+	2Ndc1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W3hzeot8p36/gNuC94UThzOJT4tvjY+Pr
+	5HPk/OWE5g3mlucf56noMui86Ubp0Opb6uXrcOv77IbtEe2c7ijutO9A78zwWPDl8XLx
+	//KM8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8B/yY/Sn9uv5L/tz/bf//
+	gATSHh8gIVokY2xhc3NuYW1lWCRjbGFzc2VzXU5TTXV0YWJsZURhdGGjICIjVk5TRGF0
+	YVhOU09iamVjdNIeHyUmXE5TQ29sb3JTcGFjZaInI1xOU0NvbG9yU3BhY2XSHh8pKldO
+	U0NvbG9yoikjXxAPTlNLZXllZEFyY2hpdmVy0S0uVHJvb3SAAQAIABEAGgAjAC0AMgA3
+	AD8ARQBQAF0AYwBwAIUAjAC3AOIA5ADmAOgA7wD0APoA/AD+AQABBQENDVkNWw1gDWsN
+	dA2CDYYNjQ2WDZsNqA2rDbgNvQ3FDcgN2g3dDeIAAAAAAAACAQAAAAAAAAAvAAAAAAAA
+	AAAAAAAAAAAN5A==
+	</data>
+	<key>BackgroundColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECswLjA3ODU3MTg5
+	MzI3IDAuMDc0ODI3NTQ0MzkgMC4wNTM5MTM1MTEzNCAxTxApMC4wNjM1NDA2MzAwNCAw
+	LjA2MTU1OTQwODkgMC4wNDg0NzkxODQ1MQAQAYACgAbTFBUNFhcYVE5TSURVTlNJQ0MQ
+	B4ADgAXSGg0bHFdOUy5kYXRhTxEMSAAADEhMaW5vAhAAAG1udHJSR0IgWFlaIAfOAAIA
+	CQAGADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAAAAD21gABAAAAANMt
+	SFAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	EWNwcnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJrcHQAAAIEAAAAFHJY
+	WVoAAAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJUAAAAcGRtZGQA
+	AALEAAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAAFG1lYXMAAAQM
+	AAAAJHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJUUkMAAAQ8AAAI
+	DHRleHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2FyZCBDb21wYW55
+	AABkZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAEnNSR0IgSUVD
+	NjE5NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAAAAAAAAAAAAAAWFla
+	IAAAAAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAAAAAAAAAkoAAA
+	D4QAALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAW
+	SUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVmYXVsdCBS
+	R0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVm
+	YXVsdCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAAAAAAAAAAAABk
+	ZXNjAAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVDNjE5NjYt
+	Mi4xAAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYx
+	OTY2LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAAE6T+ABRfLgAQ
+	zxQAA+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFzAAAAAAAAAAEA
+	AAAAAAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAAAAAABAAAAAAF
+	AAoADwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABtAHIAdwB8AIEA
+	hgCLAJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA8AD2APsBAQEH
+	AQ0BEwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGSAZoBoQGpAbEB
+	uQHBAckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKY
+	AqICrAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64D
+	ugPHA9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE0wThBPAE/gUN
+	BRwFKwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0G
+	rwbABtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8IMghGCFoIbgiC
+	CJYIqgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9ClQKagqBCpgK
+	rgrFCtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0N
+	DSYNQA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MP
+	zw/sEAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLD
+	EuMTAxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMW
+	JhZJFmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3
+	Gd0aBBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1HR4dRx1wHZkd
+	wx3sHhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1IaEhziH7
+	IiciVSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXHJfcmJyZXJocm
+	tyboJxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsqzysCKzYraSud
+	K9EsBSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsx
+	EjFKMYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjau
+	Nuk3JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuqO+g8JzxlPKQ8
+	4z0iPWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5CMEJyQrVC90M6
+	Q31DwEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1JY0mpSfBK
+	N0p9SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91QJ1BxULtRBlFQ
+	UZtR5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeSV+BYL1h9WMtZ
+	GllpWbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8
+	YU9homH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNp
+	mmnxakhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw4HE6cZVx8HJL
+	cqZzAXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnnekZ6pXsEe2N7
+	wnwhfIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VH
+	hauGDoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7OjzaP
+	npAGkG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4mSSZkJn8
+	mmia1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMGo3aj5qRWpMel
+	OKWpphqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1
+	sOqxYLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7urW7LrunvCG8
+	m70VvY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dBx7/IPci8
+	yTrJuco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V
+	0dZV1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb
+	42Pj6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw
+	5fFy8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c
+	/23//4AE0h4fICFaJGNsYXNzbmFtZVgkY2xhc3Nlc11OU011dGFibGVEYXRhoyAiI1ZO
+	U0RhdGFYTlNPYmplY3TSHh8lJlxOU0NvbG9yU3BhY2WiJyNcTlNDb2xvclNwYWNl0h4f
+	KSpXTlNDb2xvcqIpI18QD05TS2V5ZWRBcmNoaXZlctEtLlRyb290gAEACAARABoAIwAt
+	ADIANwA/AEUAUABdAGMAcACFAIwAugDmAOgA6gDsAPMA+AD+AQABAgEEAQkBEQ1dDV8N
+	ZA1vDXgNhg2KDZENmg2fDawNrw28DcENyQ3MDd4N4Q3mAAAAAAAAAgEAAAAAAAAALwAA
+	AAAAAAAAAAAAAAAADeg=
+	</data>
+	<key>Bell</key>
+	<false/>
+	<key>CursorColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECgwLjQ0ODEzNDAw
+	NTEgMC40MTMyMDIxNjY2IDAuMzA4MjAzMzA5OCAxTxAnMC4zNzE5NDUwMjM1IDAuMzQw
+	NzI1NTcwOSAwLjI0MTcyNTMyNTYAEAGAAoAG0xQVDRYXGFROU0lEVU5TSUNDEAeAA4AF
+	0hoNGxxXTlMuZGF0YU8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAx
+	AABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0
+	AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAAC
+	GAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAA
+	AIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0
+	ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0
+	AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVz
+	YwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2
+	LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAA
+	AABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2
+	z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBo
+	dHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNv
+	bG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQg
+	UkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAA
+	AAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAA
+	AAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0y
+	LjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPt
+	zAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAA
+	AAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8A
+	FAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQ
+	AJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMB
+	GQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJ
+	AdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwC
+	tgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPT
+	A+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsF
+	OgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbR
+	BuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoI
+	vgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrc
+	CvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUAN
+	Wg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJ
+	ECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMT
+	IxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZs
+	Fo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQa
+	KhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4W
+	HkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUi
+	giKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcY
+	J0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUs
+	OSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGC
+	Mbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3
+	YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1h
+	PaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BE
+	A0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrE
+	SwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZS
+	MVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4
+	WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh
+	9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpI
+	ap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFz
+	XXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyB
+	fOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6G
+	cobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBu
+	kNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWb
+	QpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYa
+	poum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx
+	1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2P
+	vgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnK
+	OMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY
+	11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vk
+	c+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/
+	8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t//+A
+	BNIeHyAhWiRjbGFzc25hbWVYJGNsYXNzZXNdTlNNdXRhYmxlRGF0YaMgIiNWTlNEYXRh
+	WE5TT2JqZWN00h4fJSZcTlNDb2xvclNwYWNloicjXE5TQ29sb3JTcGFjZdIeHykqV05T
+	Q29sb3KiKSNfEA9OU0tleWVkQXJjaGl2ZXLRLS5Ucm9vdIABAAgAEQAaACMALQAyADcA
+	PwBFAFAAXQBjAHAAhQCMALcA4QDjAOUA5wDuAPMA+QD7AP0A/wEEAQwNWA1aDV8Nag1z
+	DYENhQ2MDZUNmg2nDaoNtw28DcQNxw3ZDdwN4QAAAAAAAAIBAAAAAAAAAC8AAAAAAAAA
+	AAAAAAAAAA3j
+	</data>
+	<key>DisableANSIColor</key>
+	<false/>
+	<key>Font</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGGBlYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKQHCBESVSRudWxs1AkKCwwNDg8QVk5TU2l6ZVhOU2ZGbGFnc1ZOU05hbWVWJGNs
+	YXNzI0AmAAAAAAAAEBCAAoADVkdvTW9ub9ITFBUWWiRjbGFzc25hbWVYJGNsYXNzZXNW
+	TlNGb250ohUXWE5TT2JqZWN0XxAPTlNLZXllZEFyY2hpdmVy0RobVHJvb3SAAQgRGiMt
+	Mjc8QktSW2JpcnR2eH+Ej5ifoqu9wMUAAAAAAAABAQAAAAAAAAAcAAAAAAAAAAAAAAAA
+	AAAAxw==
+	</data>
+	<key>FontAntialias</key>
+	<true/>
+	<key>FontHeightSpacing</key>
+	<integer>1</integer>
+	<key>FontWidthSpacing</key>
+	<integer>1</integer>
+	<key>ProfileCurrentVersion</key>
+	<real>2.0600000000000001</real>
+	<key>SelectionColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECkwLjYzNzg1MDc2
+	MTQgMC4yNTM4NjgzNzEyIDAuMDYwOTk5MzExNTEgMU8QKDAuNTY0MDcwNzAxNiAwLjE4
+	NDY3ODM2MDggMC4wNTY1MzkxODUzNQAQAYACgAbTFBUNFhcYVE5TSURVTlNJQ0MQB4AD
+	gAXSGg0bHFdOUy5kYXRhTxEMSAAADEhMaW5vAhAAAG1udHJSR0IgWFlaIAfOAAIACQAG
+	ADEAAGFjc3BNU0ZUAAAAAElFQyBzUkdCAAAAAAAAAAAAAAAAAAD21gABAAAAANMtSFAg
+	IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEWNw
+	cnQAAAFQAAAAM2Rlc2MAAAGEAAAAbHd0cHQAAAHwAAAAFGJrcHQAAAIEAAAAFHJYWVoA
+	AAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFGRtbmQAAAJUAAAAcGRtZGQAAALE
+	AAAAiHZ1ZWQAAANMAAAAhnZpZXcAAAPUAAAAJGx1bWkAAAP4AAAAFG1lYXMAAAQMAAAA
+	JHRlY2gAAAQwAAAADHJUUkMAAAQ8AAAIDGdUUkMAAAQ8AAAIDGJUUkMAAAQ8AAAIDHRl
+	eHQAAAAAQ29weXJpZ2h0IChjKSAxOTk4IEhld2xldHQtUGFja2FyZCBDb21wYW55AABk
+	ZXNjAAAAAAAAABJzUkdCIElFQzYxOTY2LTIuMQAAAAAAAAAAAAAAEnNSR0IgSUVDNjE5
+	NjYtMi4xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAABYWVogAAAAAAAA81EAAQAAAAEWzFhZWiAAAAAAAAAAAAAAAAAAAAAAWFlaIAAA
+	AAAAAG+iAAA49QAAA5BYWVogAAAAAAAAYpkAALeFAAAY2lhZWiAAAAAAAAAkoAAAD4QA
+	ALbPZGVzYwAAAAAAAAAWSUVDIGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAWSUVD
+	IGh0dHA6Ly93d3cuaWVjLmNoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAAAAAGRlc2MAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVmYXVsdCBSR0Ig
+	Y29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAALklFQyA2MTk2Ni0yLjEgRGVmYXVs
+	dCBSR0IgY29sb3VyIHNwYWNlIC0gc1JHQgAAAAAAAAAAAAAAAAAAAAAAAAAAAABkZXNj
+	AAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVDNjE5NjYtMi4x
+	AAAAAAAAAAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2
+	LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdmlldwAAAAAAE6T+ABRfLgAQzxQA
+	A+3MAAQTCwADXJ4AAAABWFlaIAAAAAAATAlWAFAAAABXH+dtZWFzAAAAAAAAAAEAAAAA
+	AAAAAAAAAAAAAAAAAAACjwAAAAJzaWcgAAAAAENSVCBjdXJ2AAAAAAAABAAAAAAFAAoA
+	DwAUABkAHgAjACgALQAyADcAOwBAAEUASgBPAFQAWQBeAGMAaABtAHIAdwB8AIEAhgCL
+	AJAAlQCaAJ8ApACpAK4AsgC3ALwAwQDGAMsA0ADVANsA4ADlAOsA8AD2APsBAQEHAQ0B
+	EwEZAR8BJQErATIBOAE+AUUBTAFSAVkBYAFnAW4BdQF8AYMBiwGSAZoBoQGpAbEBuQHB
+	AckB0QHZAeEB6QHyAfoCAwIMAhQCHQImAi8COAJBAksCVAJdAmcCcQJ6AoQCjgKYAqIC
+	rAK2AsECywLVAuAC6wL1AwADCwMWAyEDLQM4A0MDTwNaA2YDcgN+A4oDlgOiA64DugPH
+	A9MD4APsA/kEBgQTBCAELQQ7BEgEVQRjBHEEfgSMBJoEqAS2BMQE0wThBPAE/gUNBRwF
+	KwU6BUkFWAVnBXcFhgWWBaYFtQXFBdUF5QX2BgYGFgYnBjcGSAZZBmoGewaMBp0GrwbA
+	BtEG4wb1BwcHGQcrBz0HTwdhB3QHhgeZB6wHvwfSB+UH+AgLCB8IMghGCFoIbgiCCJYI
+	qgi+CNII5wj7CRAJJQk6CU8JZAl5CY8JpAm6Cc8J5Qn7ChEKJwo9ClQKagqBCpgKrgrF
+	CtwK8wsLCyILOQtRC2kLgAuYC7ALyAvhC/kMEgwqDEMMXAx1DI4MpwzADNkM8w0NDSYN
+	QA1aDXQNjg2pDcMN3g34DhMOLg5JDmQOfw6bDrYO0g7uDwkPJQ9BD14Peg+WD7MPzw/s
+	EAkQJhBDEGEQfhCbELkQ1xD1ERMRMRFPEW0RjBGqEckR6BIHEiYSRRJkEoQSoxLDEuMT
+	AxMjE0MTYxODE6QTxRPlFAYUJxRJFGoUixStFM4U8BUSFTQVVhV4FZsVvRXgFgMWJhZJ
+	FmwWjxayFtYW+hcdF0EXZReJF64X0hf3GBsYQBhlGIoYrxjVGPoZIBlFGWsZkRm3Gd0a
+	BBoqGlEadxqeGsUa7BsUGzsbYxuKG7Ib2hwCHCocUhx7HKMczBz1HR4dRx1wHZkdwx3s
+	HhYeQB5qHpQevh7pHxMfPh9pH5Qfvx/qIBUgQSBsIJggxCDwIRwhSCF1IaEhziH7Iici
+	VSKCIq8i3SMKIzgjZiOUI8Ij8CQfJE0kfCSrJNolCSU4JWgllyXHJfcmJyZXJocmtybo
+	JxgnSSd6J6sn3CgNKD8ocSiiKNQpBik4KWspnSnQKgIqNSpoKpsqzysCKzYraSudK9Es
+	BSw5LG4soizXLQwtQS12Last4S4WLkwugi63Lu4vJC9aL5Evxy/+MDUwbDCkMNsxEjFK
+	MYIxujHyMioyYzKbMtQzDTNGM38zuDPxNCs0ZTSeNNg1EzVNNYc1wjX9Njc2cjauNuk3
+	JDdgN5w31zgUOFA4jDjIOQU5Qjl/Obw5+To2OnQ6sjrvOy07azuqO+g8JzxlPKQ84z0i
+	PWE9oT3gPiA+YD6gPuA/IT9hP6I/4kAjQGRApkDnQSlBakGsQe5CMEJyQrVC90M6Q31D
+	wEQDREdEikTORRJFVUWaRd5GIkZnRqtG8Ec1R3tHwEgFSEtIkUjXSR1JY0mpSfBKN0p9
+	SsRLDEtTS5pL4kwqTHJMuk0CTUpNk03cTiVObk63TwBPSU+TT91QJ1BxULtRBlFQUZtR
+	5lIxUnxSx1MTU19TqlP2VEJUj1TbVShVdVXCVg9WXFapVvdXRFeSV+BYL1h9WMtZGllp
+	WbhaB1pWWqZa9VtFW5Vb5Vw1XIZc1l0nXXhdyV4aXmxevV8PX2Ffs2AFYFdgqmD8YU9h
+	omH1YklinGLwY0Njl2PrZEBklGTpZT1lkmXnZj1mkmboZz1nk2fpaD9olmjsaUNpmmnx
+	akhqn2r3a09rp2v/bFdsr20IbWBtuW4SbmtuxG8eb3hv0XArcIZw4HE6cZVx8HJLcqZz
+	AXNdc7h0FHRwdMx1KHWFdeF2Pnabdvh3VnezeBF4bnjMeSp5iXnnekZ6pXsEe2N7wnwh
+	fIF84X1BfaF+AX5ifsJ/I3+Ef+WAR4CogQqBa4HNgjCCkoL0g1eDuoQdhICE44VHhauG
+	DoZyhteHO4efiASIaYjOiTOJmYn+imSKyoswi5aL/IxjjMqNMY2Yjf+OZo7OjzaPnpAG
+	kG6Q1pE/kaiSEZJ6kuOTTZO2lCCUipT0lV+VyZY0lp+XCpd1l+CYTJi4mSSZkJn8mmia
+	1ZtCm6+cHJyJnPedZJ3SnkCerp8dn4uf+qBpoNihR6G2oiailqMGo3aj5qRWpMelOKWp
+	phqmi6b9p26n4KhSqMSpN6mpqhyqj6sCq3Wr6axcrNCtRK24ri2uoa8Wr4uwALB1sOqx
+	YLHWskuywrM4s660JbSctRO1irYBtnm28Ldot+C4WbjRuUq5wro7urW7LrunvCG8m70V
+	vY++Cr6Evv+/er/1wHDA7MFnwePCX8Lbw1jD1MRRxM7FS8XIxkbGw8dBx7/IPci8yTrJ
+	uco4yrfLNsu2zDXMtc01zbXONs62zzfPuNA50LrRPNG+0j/SwdNE08bUSdTL1U7V0dZV
+	1tjXXNfg2GTY6Nls2fHadtr724DcBdyK3RDdlt4c3qLfKd+v4DbgveFE4cziU+Lb42Pj
+	6+Rz5PzlhOYN5pbnH+ep6DLovOlG6dDqW+rl63Dr++yG7RHtnO4o7rTvQO/M8Fjw5fFy
+	8f/yjPMZ86f0NPTC9VD13vZt9vv3ivgZ+Kj5OPnH+lf65/t3/Af8mP0p/br+S/7c/23/
+	/4AE0h4fICFaJGNsYXNzbmFtZVgkY2xhc3Nlc11OU011dGFibGVEYXRhoyAiI1ZOU0Rh
+	dGFYTlNPYmplY3TSHh8lJlxOU0NvbG9yU3BhY2WiJyNcTlNDb2xvclNwYWNl0h4fKSpX
+	TlNDb2xvcqIpI18QD05TS2V5ZWRBcmNoaXZlctEtLlRyb290gAEACAARABoAIwAtADIA
+	NwA/AEUAUABdAGMAcACFAIwAuADjAOUA5wDpAPAA9QD7AP0A/wEBAQYBDg1aDVwNYQ1s
+	DXUNgw2HDY4Nlw2cDakNrA25Db4Nxg3JDdsN3g3jAAAAAAAAAgEAAAAAAAAALwAAAAAA
+	AAAAAAAAAAAADeU=
+	</data>
+	<key>ShowActiveProcessInTabTitle</key>
+	<false/>
+	<key>ShowActiveProcessInTitle</key>
+	<false/>
+	<key>ShowActivityIndicatorInTab</key>
+	<false/>
+	<key>ShowCommandKeyInTitle</key>
+	<false/>
+	<key>ShowComponentsWhenTabHasCustomTitle</key>
+	<false/>
+	<key>ShowDimensionsInTitle</key>
+	<false/>
+	<key>ShowRepresentedURLInTabTitle</key>
+	<false/>
+	<key>ShowRepresentedURLInTitle</key>
+	<false/>
+	<key>ShowRepresentedURLPathInTitle</key>
+	<false/>
+	<key>ShowShellCommandInTitle</key>
+	<false/>
+	<key>ShowTTYNameInTitle</key>
+	<false/>
+	<key>ShowWindowSettingsNameInTitle</key>
+	<false/>
+	<key>TerminalType</key>
+	<string>xterm</string>
+	<key>TextBoldColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECgwLjgwMTk4NTU2
+	MTggMC43MzU3NzAwNDY3IDAuNTU1MDUzNTMyMSAxTxAnMC43NTY0MTcxNTUzIDAuNjg1
+	MjI5MjQxOCAwLjQ4MjY3MjM5MzMAEAGAAoAG0xQVDRYXGFROU0lEVU5TSUNDEAeAA4AF
+	0hoNGxxXTlMuZGF0YU8RDEgAAAxITGlubwIQAABtbnRyUkdCIFhZWiAHzgACAAkABgAx
+	AABhY3NwTVNGVAAAAABJRUMgc1JHQgAAAAAAAAAAAAAAAAAA9tYAAQAAAADTLUhQICAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFjcHJ0
+	AAABUAAAADNkZXNjAAABhAAAAGx3dHB0AAAB8AAAABRia3B0AAACBAAAABRyWFlaAAAC
+	GAAAABRnWFlaAAACLAAAABRiWFlaAAACQAAAABRkbW5kAAACVAAAAHBkbWRkAAACxAAA
+	AIh2dWVkAAADTAAAAIZ2aWV3AAAD1AAAACRsdW1pAAAD+AAAABRtZWFzAAAEDAAAACR0
+	ZWNoAAAEMAAAAAxyVFJDAAAEPAAACAxnVFJDAAAEPAAACAxiVFJDAAAEPAAACAx0ZXh0
+	AAAAAENvcHlyaWdodCAoYykgMTk5OCBIZXdsZXR0LVBhY2thcmQgQ29tcGFueQAAZGVz
+	YwAAAAAAAAASc1JHQiBJRUM2MTk2Ni0yLjEAAAAAAAAAAAAAABJzUkdCIElFQzYxOTY2
+	LTIuMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAWFlaIAAAAAAAAPNRAAEAAAABFsxYWVogAAAAAAAAAAAAAAAAAAAAAFhZWiAAAAAA
+	AABvogAAOPUAAAOQWFlaIAAAAAAAAGKZAAC3hQAAGNpYWVogAAAAAAAAJKAAAA+EAAC2
+	z2Rlc2MAAAAAAAAAFklFQyBodHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAFklFQyBo
+	dHRwOi8vd3d3LmllYy5jaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAAABkZXNjAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQgUkdCIGNv
+	bG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAC5JRUMgNjE5NjYtMi4xIERlZmF1bHQg
+	UkdCIGNvbG91ciBzcGFjZSAtIHNSR0IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGVzYwAA
+	AAAAAAAsUmVmZXJlbmNlIFZpZXdpbmcgQ29uZGl0aW9uIGluIElFQzYxOTY2LTIuMQAA
+	AAAAAAAAAAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0y
+	LjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHZpZXcAAAAAABOk/gAUXy4AEM8UAAPt
+	zAAEEwsAA1yeAAAAAVhZWiAAAAAAAEwJVgBQAAAAVx/nbWVhcwAAAAAAAAABAAAAAAAA
+	AAAAAAAAAAAAAAAAAo8AAAACc2lnIAAAAABDUlQgY3VydgAAAAAAAAQAAAAABQAKAA8A
+	FAAZAB4AIwAoAC0AMgA3ADsAQABFAEoATwBUAFkAXgBjAGgAbQByAHcAfACBAIYAiwCQ
+	AJUAmgCfAKQAqQCuALIAtwC8AMEAxgDLANAA1QDbAOAA5QDrAPAA9gD7AQEBBwENARMB
+	GQEfASUBKwEyATgBPgFFAUwBUgFZAWABZwFuAXUBfAGDAYsBkgGaAaEBqQGxAbkBwQHJ
+	AdEB2QHhAekB8gH6AgMCDAIUAh0CJgIvAjgCQQJLAlQCXQJnAnECegKEAo4CmAKiAqwC
+	tgLBAssC1QLgAusC9QMAAwsDFgMhAy0DOANDA08DWgNmA3IDfgOKA5YDogOuA7oDxwPT
+	A+AD7AP5BAYEEwQgBC0EOwRIBFUEYwRxBH4EjASaBKgEtgTEBNME4QTwBP4FDQUcBSsF
+	OgVJBVgFZwV3BYYFlgWmBbUFxQXVBeUF9gYGBhYGJwY3BkgGWQZqBnsGjAadBq8GwAbR
+	BuMG9QcHBxkHKwc9B08HYQd0B4YHmQesB78H0gflB/gICwgfCDIIRghaCG4IggiWCKoI
+	vgjSCOcI+wkQCSUJOglPCWQJeQmPCaQJugnPCeUJ+woRCicKPQpUCmoKgQqYCq4KxQrc
+	CvMLCwsiCzkLUQtpC4ALmAuwC8gL4Qv5DBIMKgxDDFwMdQyODKcMwAzZDPMNDQ0mDUAN
+	Wg10DY4NqQ3DDd4N+A4TDi4OSQ5kDn8Omw62DtIO7g8JDyUPQQ9eD3oPlg+zD88P7BAJ
+	ECYQQxBhEH4QmxC5ENcQ9RETETERTxFtEYwRqhHJEegSBxImEkUSZBKEEqMSwxLjEwMT
+	IxNDE2MTgxOkE8UT5RQGFCcUSRRqFIsUrRTOFPAVEhU0FVYVeBWbFb0V4BYDFiYWSRZs
+	Fo8WshbWFvoXHRdBF2UXiReuF9IX9xgbGEAYZRiKGK8Y1Rj6GSAZRRlrGZEZtxndGgQa
+	KhpRGncanhrFGuwbFBs7G2MbihuyG9ocAhwqHFIcexyjHMwc9R0eHUcdcB2ZHcMd7B4W
+	HkAeah6UHr4e6R8THz4faR+UH78f6iAVIEEgbCCYIMQg8CEcIUghdSGhIc4h+yInIlUi
+	giKvIt0jCiM4I2YjlCPCI/AkHyRNJHwkqyTaJQklOCVoJZclxyX3JicmVyaHJrcm6CcY
+	J0kneierJ9woDSg/KHEooijUKQYpOClrKZ0p0CoCKjUqaCqbKs8rAis2K2krnSvRLAUs
+	OSxuLKIs1y0MLUEtdi2rLeEuFi5MLoIuty7uLyQvWi+RL8cv/jA1MGwwpDDbMRIxSjGC
+	Mbox8jIqMmMymzLUMw0zRjN/M7gz8TQrNGU0njTYNRM1TTWHNcI1/TY3NnI2rjbpNyQ3
+	YDecN9c4FDhQOIw4yDkFOUI5fzm8Ofk6Njp0OrI67zstO2s7qjvoPCc8ZTykPOM9Ij1h
+	PaE94D4gPmA+oD7gPyE/YT+iP+JAI0BkQKZA50EpQWpBrEHuQjBCckK1QvdDOkN9Q8BE
+	A0RHRIpEzkUSRVVFmkXeRiJGZ0arRvBHNUd7R8BIBUhLSJFI10kdSWNJqUnwSjdKfUrE
+	SwxLU0uaS+JMKkxyTLpNAk1KTZNN3E4lTm5Ot08AT0lPk0/dUCdQcVC7UQZRUFGbUeZS
+	MVJ8UsdTE1NfU6pT9lRCVI9U21UoVXVVwlYPVlxWqVb3V0RXklfgWC9YfVjLWRpZaVm4
+	WgdaVlqmWvVbRVuVW+VcNVyGXNZdJ114XcleGl5sXr1fD19hX7NgBWBXYKpg/GFPYaJh
+	9WJJYpxi8GNDY5dj62RAZJRk6WU9ZZJl52Y9ZpJm6Gc9Z5Nn6Wg/aJZo7GlDaZpp8WpI
+	ap9q92tPa6dr/2xXbK9tCG1gbbluEm5rbsRvHm94b9FwK3CGcOBxOnGVcfByS3KmcwFz
+	XXO4dBR0cHTMdSh1hXXhdj52m3b4d1Z3s3gReG54zHkqeYl553pGeqV7BHtje8J8IXyB
+	fOF9QX2hfgF+Yn7CfyN/hH/lgEeAqIEKgWuBzYIwgpKC9INXg7qEHYSAhOOFR4Wrhg6G
+	cobXhzuHn4gEiGmIzokziZmJ/opkisqLMIuWi/yMY4zKjTGNmI3/jmaOzo82j56QBpBu
+	kNaRP5GokhGSepLjk02TtpQglIqU9JVflcmWNJaflwqXdZfgmEyYuJkkmZCZ/JpomtWb
+	QpuvnByciZz3nWSd0p5Anq6fHZ+Ln/qgaaDYoUehtqImopajBqN2o+akVqTHpTilqaYa
+	poum/adup+CoUqjEqTepqaocqo+rAqt1q+msXKzQrUStuK4trqGvFq+LsACwdbDqsWCx
+	1rJLssKzOLOutCW0nLUTtYq2AbZ5tvC3aLfguFm40blKucK6O7q1uy67p7whvJu9Fb2P
+	vgq+hL7/v3q/9cBwwOzBZ8Hjwl/C28NYw9TEUcTOxUvFyMZGxsPHQce/yD3IvMk6ybnK
+	OMq3yzbLtsw1zLXNNc21zjbOts83z7jQOdC60TzRvtI/0sHTRNPG1EnUy9VO1dHWVdbY
+	11zX4Nhk2OjZbNnx2nba+9uA3AXcit0Q3ZbeHN6i3ynfr+A24L3hROHM4lPi2+Nj4+vk
+	c+T85YTmDeaW5x/nqegy6LzpRunQ6lvq5etw6/vshu0R7ZzuKO6070DvzPBY8OXxcvH/
+	8ozzGfOn9DT0wvVQ9d72bfb794r4Gfio+Tj5x/pX+uf7d/wH/Jj9Kf26/kv+3P9t//+A
+	BNIeHyAhWiRjbGFzc25hbWVYJGNsYXNzZXNdTlNNdXRhYmxlRGF0YaMgIiNWTlNEYXRh
+	WE5TT2JqZWN00h4fJSZcTlNDb2xvclNwYWNloicjXE5TQ29sb3JTcGFjZdIeHykqV05T
+	Q29sb3KiKSNfEA9OU0tleWVkQXJjaGl2ZXLRLS5Ucm9vdIABAAgAEQAaACMALQAyADcA
+	PwBFAFAAXQBjAHAAhQCMALcA4QDjAOUA5wDuAPMA+QD7AP0A/wEEAQwNWA1aDV8Nag1z
+	DYENhQ2MDZUNmg2nDaoNtw28DcQNxw3ZDdwN4QAAAAAAAAIBAAAAAAAAAC8AAAAAAAAA
+	AAAAAAAAAA3j
+	</data>
+	<key>TextColor</key>
+	<data>
+	YnBsaXN0MDDUAQIDBAUGKyxYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3AS
+	AAGGoKcHCBMZHSQoVSRudWxs1QkKCwwNDg8QERJcTlNDb21wb25lbnRzVU5TUkdCXE5T
+	Q29sb3JTcGFjZV8QEk5TQ3VzdG9tQ29sb3JTcGFjZVYkY2xhc3NPECYwLjcxOTQwMjEz
+	NDQgMC42NjA5ODgxNTIgMC41MDEzMjYyNjMgMU8QJzAuNjYwODU1MjkzMyAwLjYwMDE1
+	MjY3MTMgMC40MjY4MTE1NzU5ABABgAKABtMUFQ0WFxhUTlNJRFVOU0lDQxAHgAOABdIa
+	DRscV05TLmRhdGFPEQxIAAAMSExpbm8CEAAAbW50clJHQiBYWVogB84AAgAJAAYAMQAA
+	YWNzcE1TRlQAAAAASUVDIHNSR0IAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1IUCAgAAAA
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARY3BydAAA
+	AVAAAAAzZGVzYwAAAYQAAABsd3RwdAAAAfAAAAAUYmtwdAAAAgQAAAAUclhZWgAAAhgA
+	AAAUZ1hZWgAAAiwAAAAUYlhZWgAAAkAAAAAUZG1uZAAAAlQAAABwZG1kZAAAAsQAAACI
+	dnVlZAAAA0wAAACGdmlldwAAA9QAAAAkbHVtaQAAA/gAAAAUbWVhcwAABAwAAAAkdGVj
+	aAAABDAAAAAMclRSQwAABDwAAAgMZ1RSQwAABDwAAAgMYlRSQwAABDwAAAgMdGV4dAAA
+	AABDb3B5cmlnaHQgKGMpIDE5OTggSGV3bGV0dC1QYWNrYXJkIENvbXBhbnkAAGRlc2MA
+	AAAAAAAAEnNSR0IgSUVDNjE5NjYtMi4xAAAAAAAAAAAAAAASc1JHQiBJRUM2MTk2Ni0y
+	LjEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AFhZWiAAAAAAAADzUQABAAAAARbMWFlaIAAAAAAAAAAAAAAAAAAAAABYWVogAAAAAAAA
+	b6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9k
+	ZXNjAAAAAAAAABZJRUMgaHR0cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAABZJRUMgaHR0
+	cDovL3d3dy5pZWMuY2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+	AAAAAAAAAAAAZGVzYwAAAAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJHQiBjb2xv
+	dXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAuSUVDIDYxOTY2LTIuMSBEZWZhdWx0IFJH
+	QiBjb2xvdXIgc3BhY2UgLSBzUkdCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRlc2MAAAAA
+	AAAALFJlZmVyZW5jZSBWaWV3aW5nIENvbmRpdGlvbiBpbiBJRUM2MTk2Ni0yLjEAAAAA
+	AAAAAAAAACxSZWZlcmVuY2UgVmlld2luZyBDb25kaXRpb24gaW4gSUVDNjE5NjYtMi4x
+	AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2aWV3AAAAAAATpP4AFF8uABDPFAAD7cwA
+	BBMLAANcngAAAAFYWVogAAAAAABMCVYAUAAAAFcf521lYXMAAAAAAAAAAQAAAAAAAAAA
+	AAAAAAAAAAAAAAKPAAAAAnNpZyAAAAAAQ1JUIGN1cnYAAAAAAAAEAAAAAAUACgAPABQA
+	GQAeACMAKAAtADIANwA7AEAARQBKAE8AVABZAF4AYwBoAG0AcgB3AHwAgQCGAIsAkACV
+	AJoAnwCkAKkArgCyALcAvADBAMYAywDQANUA2wDgAOUA6wDwAPYA+wEBAQcBDQETARkB
+	HwElASsBMgE4AT4BRQFMAVIBWQFgAWcBbgF1AXwBgwGLAZIBmgGhAakBsQG5AcEByQHR
+	AdkB4QHpAfIB+gIDAgwCFAIdAiYCLwI4AkECSwJUAl0CZwJxAnoChAKOApgCogKsArYC
+	wQLLAtUC4ALrAvUDAAMLAxYDIQMtAzgDQwNPA1oDZgNyA34DigOWA6IDrgO6A8cD0wPg
+	A+wD+QQGBBMEIAQtBDsESARVBGMEcQR+BIwEmgSoBLYExATTBOEE8AT+BQ0FHAUrBToF
+	SQVYBWcFdwWGBZYFpgW1BcUF1QXlBfYGBgYWBicGNwZIBlkGagZ7BowGnQavBsAG0Qbj
+	BvUHBwcZBysHPQdPB2EHdAeGB5kHrAe/B9IH5Qf4CAsIHwgyCEYIWghuCIIIlgiqCL4I
+	0gjnCPsJEAklCToJTwlkCXkJjwmkCboJzwnlCfsKEQonCj0KVApqCoEKmAquCsUK3Arz
+	CwsLIgs5C1ELaQuAC5gLsAvIC+EL+QwSDCoMQwxcDHUMjgynDMAM2QzzDQ0NJg1ADVoN
+	dA2ODakNww3eDfgOEw4uDkkOZA5/DpsOtg7SDu4PCQ8lD0EPXg96D5YPsw/PD+wQCRAm
+	EEMQYRB+EJsQuRDXEPURExExEU8RbRGMEaoRyRHoEgcSJhJFEmQShBKjEsMS4xMDEyMT
+	QxNjE4MTpBPFE+UUBhQnFEkUahSLFK0UzhTwFRIVNBVWFXgVmxW9FeAWAxYmFkkWbBaP
+	FrIW1hb6Fx0XQRdlF4kXrhfSF/cYGxhAGGUYihivGNUY+hkgGUUZaxmRGbcZ3RoEGioa
+	URp3Gp4axRrsGxQbOxtjG4obshvaHAIcKhxSHHscoxzMHPUdHh1HHXAdmR3DHeweFh5A
+	HmoelB6+HukfEx8+H2kflB+/H+ogFSBBIGwgmCDEIPAhHCFIIXUhoSHOIfsiJyJVIoIi
+	ryLdIwojOCNmI5QjwiPwJB8kTSR8JKsk2iUJJTglaCWXJccl9yYnJlcmhya3JugnGCdJ
+	J3onqyfcKA0oPyhxKKIo1CkGKTgpaymdKdAqAio1KmgqmyrPKwIrNitpK50r0SwFLDks
+	biyiLNctDC1BLXYtqy3hLhYuTC6CLrcu7i8kL1ovkS/HL/4wNTBsMKQw2zESMUoxgjG6
+	MfIyKjJjMpsy1DMNM0YzfzO4M/E0KzRlNJ402DUTNU01hzXCNf02NzZyNq426TckN2A3
+	nDfXOBQ4UDiMOMg5BTlCOX85vDn5OjY6dDqyOu87LTtrO6o76DwnPGU8pDzjPSI9YT2h
+	PeA+ID5gPqA+4D8hP2E/oj/iQCNAZECmQOdBKUFqQaxB7kIwQnJCtUL3QzpDfUPARANE
+	R0SKRM5FEkVVRZpF3kYiRmdGq0bwRzVHe0fASAVIS0iRSNdJHUljSalJ8Eo3Sn1KxEsM
+	S1NLmkviTCpMcky6TQJNSk2TTdxOJU5uTrdPAE9JT5NP3VAnUHFQu1EGUVBRm1HmUjFS
+	fFLHUxNTX1OqU/ZUQlSPVNtVKFV1VcJWD1ZcVqlW91dEV5JX4FgvWH1Yy1kaWWlZuFoH
+	WlZaplr1W0VblVvlXDVchlzWXSddeF3JXhpebF69Xw9fYV+zYAVgV2CqYPxhT2GiYfVi
+	SWKcYvBjQ2OXY+tkQGSUZOllPWWSZedmPWaSZuhnPWeTZ+loP2iWaOxpQ2maafFqSGqf
+	avdrT2una/9sV2yvbQhtYG25bhJua27Ebx5veG/RcCtwhnDgcTpxlXHwcktypnMBc11z
+	uHQUdHB0zHUodYV14XY+dpt2+HdWd7N4EXhueMx5KnmJeed6RnqlewR7Y3vCfCF8gXzh
+	fUF9oX4BfmJ+wn8jf4R/5YBHgKiBCoFrgc2CMIKSgvSDV4O6hB2EgITjhUeFq4YOhnKG
+	14c7h5+IBIhpiM6JM4mZif6KZIrKizCLlov8jGOMyo0xjZiN/45mjs6PNo+ekAaQbpDW
+	kT+RqJIRknqS45NNk7aUIJSKlPSVX5XJljSWn5cKl3WX4JhMmLiZJJmQmfyaaJrVm0Kb
+	r5wcnImc951kndKeQJ6unx2fi5/6oGmg2KFHobaiJqKWowajdqPmpFakx6U4pammGqaL
+	pv2nbqfgqFKoxKk3qamqHKqPqwKrdavprFys0K1ErbiuLa6hrxavi7AAsHWw6rFgsday
+	S7LCszizrrQltJy1E7WKtgG2ebbwt2i34LhZuNG5SrnCuju6tbsuu6e8IbybvRW9j74K
+	voS+/796v/XAcMDswWfB48JfwtvDWMPUxFHEzsVLxcjGRsbDx0HHv8g9yLzJOsm5yjjK
+	t8s2y7bMNcy1zTXNtc42zrbPN8+40DnQutE80b7SP9LB00TTxtRJ1MvVTtXR1lXW2Ndc
+	1+DYZNjo2WzZ8dp22vvbgNwF3IrdEN2W3hzeot8p36/gNuC94UThzOJT4tvjY+Pr5HPk
+	/OWE5g3mlucf56noMui86Ubp0Opb6uXrcOv77IbtEe2c7ijutO9A78zwWPDl8XLx//KM
+	8xnzp/Q09ML1UPXe9m32+/eK+Bn4qPk4+cf6V/rn+3f8B/yY/Sn9uv5L/tz/bf//gATS
+	Hh8gIVokY2xhc3NuYW1lWCRjbGFzc2VzXU5TTXV0YWJsZURhdGGjICIjVk5TRGF0YVhO
+	U09iamVjdNIeHyUmXE5TQ29sb3JTcGFjZaInI1xOU0NvbG9yU3BhY2XSHh8pKldOU0Nv
+	bG9yoikjXxAPTlNLZXllZEFyY2hpdmVy0S0uVHJvb3SAAQAIABEAGgAjAC0AMgA3AD8A
+	RQBQAF0AYwBwAIUAjAC1AN8A4QDjAOUA7ADxAPcA+QD7AP0BAgEKDVYNWA1dDWgNcQ1/
+	DYMNig2TDZgNpQ2oDbUNug3CDcUN1w3aDd8AAAAAAAACAQAAAAAAAAAvAAAAAAAAAAAA
+	AAAAAAAN4Q==
+	</data>
+	<key>UseBoldFonts</key>
+	<false/>
+	<key>UseBrightBold</key>
+	<true/>
+	<key>VisualBell</key>
+	<false/>
+	<key>VisualBellOnlyWhenMuted</key>
+	<false/>
+	<key>WindowTitle</key>
+	<string>Terminal</string>
+	<key>name</key>
+	<string>Dark</string>
+	<key>noWarnProcesses</key>
+	<array>
+		<dict>
+			<key>ProcessName</key>
+			<string>screen</string>
+		</dict>
+		<dict>
+			<key>ProcessName</key>
+			<string>tmux</string>
+		</dict>
+		<dict>
+			<key>ProcessName</key>
+			<string>atch</string>
+		</dict>
+	</array>
+	<key>rowCount</key>
+	<integer>25</integer>
+	<key>shellExitAction</key>
+	<integer>1</integer>
+	<key>type</key>
+	<string>Window Settings</string>
+	<key>useOptionAsMetaKey</key>
+	<false/>
+</dict>
+</plist>
diff --git a/etc/Go-Mono-Bold-Italic.ttf b/etc/Go-Mono-Bold-Italic.ttf
new file mode 100644
index 00000000..c138a9e1
--- /dev/null
+++ b/etc/Go-Mono-Bold-Italic.ttf
Binary files differdiff --git a/etc/Go-Mono-Bold.ttf b/etc/Go-Mono-Bold.ttf
new file mode 100644
index 00000000..551da07f
--- /dev/null
+++ b/etc/Go-Mono-Bold.ttf
Binary files differdiff --git a/etc/Go-Mono-Italic.ttf b/etc/Go-Mono-Italic.ttf
new file mode 100644
index 00000000..22d4390e
--- /dev/null
+++ b/etc/Go-Mono-Italic.ttf
Binary files differdiff --git a/etc/Go-Mono.ttf b/etc/Go-Mono.ttf
new file mode 100644
index 00000000..71e30123
--- /dev/null
+++ b/etc/Go-Mono.ttf
Binary files differdiff --git a/etc/README.Go-Mono b/etc/README.Go-Mono
new file mode 100644
index 00000000..7043c362
--- /dev/null
+++ b/etc/README.Go-Mono
@@ -0,0 +1,36 @@
+These fonts were created by the Bigelow & Holmes foundry specifically for the
+Go project. See https://blog.golang.org/go-fonts for details.
+
+They are licensed under the same open source license as the rest of the Go
+project's software:
+
+Copyright (c) 2016 Bigelow & Holmes Inc.. All rights reserved.
+
+Distribution of this font is governed by the following license. If you do not
+agree to this license, including the disclaimer, do not distribute or modify
+this font.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+	* Redistributions of source code must retain the above copyright notice,
+	  this list of conditions and the following disclaimer.
+
+	* Redistributions in binary form must reproduce the above copyright notice,
+	  this list of conditions and the following disclaimer in the documentation
+	  and/or other materials provided with the distribution.
+
+	* Neither the name of Google Inc. nor the names of its contributors may be
+	  used to endorse or promote products derived from this software without
+	  specific prior written permission.
+
+DISCLAIMER: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/etc/agpl.c b/etc/agpl.c
new file mode 100644
index 00000000..4cc71115
--- /dev/null
+++ b/etc/agpl.c
@@ -0,0 +1,20 @@
+/* Copyright (C) 2020  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
diff --git a/etc/code.map b/etc/code.map
new file mode 100644
index 00000000..a3749b8d
--- /dev/null
+++ b/etc/code.map
@@ -0,0 +1,20 @@
+include "/usr/share/kbd/keymaps/i386/qwerty/us.map.gz"
+
+keycode 2 = exclam one
+keycode 3 = at two
+keycode 4 = numbersign three
+keycode 5 = dollar four
+keycode 6 = percent five
+keycode 7 = asciicircum six
+keycode 8 = ampersand seven
+keycode 9 = asterisk eight
+keycode 10 = parenleft nine
+keycode 11 = parenright zero
+keycode 12 = underscore minus
+keycode 26 = braceleft bracketleft
+keycode 27 = braceright bracketright
+keycode 43 = bar backslash
+keycode 58 = Escape
+
+keycode 100 = Compose
+keycode 125 = Escape
diff --git a/etc/gpl.c b/etc/gpl.c
new file mode 100644
index 00000000..bed24ff0
--- /dev/null
+++ b/etc/gpl.c
@@ -0,0 +1,20 @@
+/* Copyright (C) 2020  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysexits.h>
diff --git a/etc/psf/.gitignore b/etc/psf/.gitignore
new file mode 100644
index 00000000..446e6b46
--- /dev/null
+++ b/etc/psf/.gitignore
@@ -0,0 +1,2 @@
+*.png
+*.psfu
diff --git a/etc/psf/Makefile b/etc/psf/Makefile
new file mode 100644
index 00000000..e92178cd
--- /dev/null
+++ b/etc/psf/Makefile
@@ -0,0 +1,24 @@
+TABLE = default.u
+
+FONTS += sans6x8.psfu
+FONTS += sans6x10.psfu
+FONTS += sans6x12.psfu
+
+PNGS = $(FONTS:psfu=png)
+
+all: $(FONTS)
+
+png: $(PNGS)
+
+$(FONTS): $(TABLE)
+
+.SUFFIXES: .psf .psfu .png
+
+.psf.psfu:
+	psfaddtable $< $(TABLE) $@
+
+.psf.png:
+	psf2png $< > $@
+
+clean:
+	rm -f $(FONTS) $(PNGS)
diff --git a/etc/psf/default.u b/etc/psf/default.u
new file mode 100644
index 00000000..790ad92b
--- /dev/null
+++ b/etc/psf/default.u
@@ -0,0 +1,259 @@
+#
+# Character table extracted from font default8x16.psfu
+#
+0x000	U+2008
+0x001	U+263a
+0x002	U+263b
+0x003	U+2665
+0x004	U+2666
+0x005	U+2663
+0x006	U+2660
+0x007	U+2022
+0x008	U+25d8
+0x009	U+25cb
+0x00a	U+25d9
+0x00b	U+2642
+0x00c	U+2640
+0x00d	U+266a
+0x00e	U+266b U+266c
+0x00f	U+263c
+0x010	U+25b6 U+25ba
+0x011	U+25c0 U+25c4
+0x012	U+2195
+0x013	U+203c
+0x014	U+00b6
+0x015	U+00a7
+0x016	U+25ac
+0x017	U+21a8
+0x018	U+2191
+0x019	U+2193
+0x01a	U+2192
+0x01b	U+2190
+0x01c	U+221f U+2319
+0x01d	U+2194
+0x01e	U+25b2
+0x01f	U+25bc
+0x020	U+0020 U+00a0 U+2000 U+2001 U+2002 U+2003 U+2004 U+2005 U+2006 U+2007 U+2008 U+2009 U+200a U+202f
+0x021	U+0021
+0x022	U+0022
+0x023	U+0023
+0x024	U+0024
+0x025	U+0025
+0x026	U+0026
+0x027	U+0027
+0x028	U+0028
+0x029	U+0029
+0x02a	U+002a
+0x02b	U+002b
+0x02c	U+002c
+0x02d	U+002d
+0x02e	U+002e
+0x02f	U+002f
+0x030	U+0030
+0x031	U+0031
+0x032	U+0032
+0x033	U+0033
+0x034	U+0034
+0x035	U+0035
+0x036	U+0036
+0x037	U+0037
+0x038	U+0038
+0x039	U+0039
+0x03a	U+003a
+0x03b	U+003b
+0x03c	U+003c
+0x03d	U+003d
+0x03e	U+003e
+0x03f	U+003f
+0x040	U+0040
+0x041	U+0041
+0x042	U+0042
+0x043	U+0043
+0x044	U+0044
+0x045	U+0045
+0x046	U+0046
+0x047	U+0047
+0x048	U+0048
+0x049	U+0049
+0x04a	U+004a
+0x04b	U+004b
+0x04c	U+004c
+0x04d	U+004d
+0x04e	U+004e
+0x04f	U+004f
+0x050	U+0050
+0x051	U+0051
+0x052	U+0052
+0x053	U+0053
+0x054	U+0054
+0x055	U+0055
+0x056	U+0056
+0x057	U+0057
+0x058	U+0058
+0x059	U+0059
+0x05a	U+005a
+0x05b	U+005b
+0x05c	U+005c
+0x05d	U+005d
+0x05e	U+005e
+0x05f	U+005f
+0x060	U+0060
+0x061	U+0061
+0x062	U+0062
+0x063	U+0063
+0x064	U+0064
+0x065	U+0065
+0x066	U+0066
+0x067	U+0067
+0x068	U+0068
+0x069	U+0069
+0x06a	U+006a
+0x06b	U+006b
+0x06c	U+006c
+0x06d	U+006d
+0x06e	U+006e
+0x06f	U+006f
+0x070	U+0070
+0x071	U+0071
+0x072	U+0072
+0x073	U+0073
+0x074	U+0074
+0x075	U+0075
+0x076	U+0076
+0x077	U+0077
+0x078	U+0078
+0x079	U+0079
+0x07a	U+007a
+0x07b	U+007b
+0x07c	U+007c
+0x07d	U+007d
+0x07e	U+007e
+0x07f	U+2302
+0x080	U+00c7
+0x081	U+00fc
+0x082	U+00e9
+0x083	U+00e2
+0x084	U+00e4
+0x085	U+00e0
+0x086	U+00e5
+0x087	U+00e7
+0x088	U+00ea
+0x089	U+00eb
+0x08a	U+00e8
+0x08b	U+00ef
+0x08c	U+00ee
+0x08d	U+00ec
+0x08e	U+00c4
+0x08f	U+00c5 U+212b
+0x090	U+00c9
+0x091	U+00e6
+0x092	U+00c6
+0x093	U+00f4
+0x094	U+00f6
+0x095	U+00f2
+0x096	U+00fb
+0x097	U+00f9
+0x098	U+00ff
+0x099	U+00d6
+0x09a	U+00dc
+0x09b	U+00a2
+0x09c	U+00a3
+0x09d	U+00a5
+0x09e	U+20a7
+0x09f	U+0192
+0x0a0	U+00e1
+0x0a1	U+00ed
+0x0a2	U+00f3
+0x0a3	U+00fa
+0x0a4	U+00f1
+0x0a5	U+00d1
+0x0a6	U+00aa
+0x0a7	U+00ba
+0x0a8	U+00bf
+0x0a9	U+2310
+0x0aa	U+00ac
+0x0ab	U+00bd
+0x0ac	U+00bc
+0x0ad	U+00a1
+0x0ae	U+00ab
+0x0af	U+00bb
+0x0b0	U+2591
+0x0b1	U+2592
+0x0b2	U+2593
+0x0b3	U+2502
+0x0b4	U+2524
+0x0b5	U+2561
+0x0b6	U+2562
+0x0b7	U+2556
+0x0b8	U+2555
+0x0b9	U+2563
+0x0ba	U+2551
+0x0bb	U+2557
+0x0bc	U+255d
+0x0bd	U+255c
+0x0be	U+255b
+0x0bf	U+2510
+0x0c0	U+2514
+0x0c1	U+2534
+0x0c2	U+252c
+0x0c3	U+251c
+0x0c4	U+2500
+0x0c5	U+253c
+0x0c6	U+255e
+0x0c7	U+255f
+0x0c8	U+255a
+0x0c9	U+2554
+0x0ca	U+2569
+0x0cb	U+2566
+0x0cc	U+2560
+0x0cd	U+2550
+0x0ce	U+256c
+0x0cf	U+2567
+0x0d0	U+2568
+0x0d1	U+2564
+0x0d2	U+2565
+0x0d3	U+2559
+0x0d4	U+2558
+0x0d5	U+2552
+0x0d6	U+2553
+0x0d7	U+256b
+0x0d8	U+256a
+0x0d9	U+2518
+0x0da	U+250c
+0x0db	U+2588
+0x0dc	U+2584
+0x0dd	U+258c
+0x0de	U+2590
+0x0df	U+2580
+0x0e0	U+03b1
+0x0e1	U+00df U+03b2
+0x0e2	U+0393
+0x0e3	U+03c0
+0x0e4	U+03a3
+0x0e5	U+03c3
+0x0e6	U+00b5 U+03bc
+0x0e7	U+03c4
+0x0e8	U+03a6
+0x0e9	U+0398
+0x0ea	U+03a9 U+2126
+0x0eb	U+03b4
+0x0ec	U+221e
+0x0ed	U+03c6 U+2205 U+2300
+0x0ee	U+03b5 U+2208
+0x0ef	U+2229
+0x0f0	U+2261
+0x0f1	U+00b1
+0x0f2	U+2265
+0x0f3	U+2264
+0x0f4	U+2320
+0x0f5	U+2321
+0x0f6	U+00f7
+0x0f7	U+2248
+0x0f8	U+00b0
+0x0f9	U+2219 U+22c5
+0x0fa	U+00b7
+0x0fb	U+221a
+0x0fc	U+207f
+0x0fd	U+00b2
+0x0fe	U+220e U+25a0
+0x0ff	U+00a0
diff --git a/etc/psf/sans6x10.psf b/etc/psf/sans6x10.psf
new file mode 100644
index 00000000..09bb1af6
--- /dev/null
+++ b/etc/psf/sans6x10.psf
Binary files differdiff --git a/etc/psf/sans6x12.psf b/etc/psf/sans6x12.psf
new file mode 100644
index 00000000..75c1fd49
--- /dev/null
+++ b/etc/psf/sans6x12.psf
Binary files differdiff --git a/etc/psf/sans6x8.psf b/etc/psf/sans6x8.psf
new file mode 100644
index 00000000..fef671d8
--- /dev/null
+++ b/etc/psf/sans6x8.psf
Binary files differdiff --git a/etc/samba_mdns b/etc/samba_mdns
new file mode 100644
index 00000000..66b13af7
--- /dev/null
+++ b/etc/samba_mdns
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# PROVIDE: samba_mdns
+# REQUIRE: samba_server mdnsd
+# KEYWORD: shutdown
+
+. /etc/rc.subr
+
+name="samba_mdns"
+rcvar="samba_mdns_enable"
+
+load_rc_config $name
+
+: ${samba_mdns_enable:="NO"}
+: ${samba_mdns_name:="${hostname}"}
+: ${samba_mdns_port:="445"}
+: ${samba_mdns_text:=""}
+
+procname="/usr/local/sbin/mdnsctl"
+command="/usr/sbin/daemon"
+pidfile="/var/run/${name}.pid"
+
+publish_args="${samba_mdns_name} smb tcp ${samba_mdns_port} '${samba_mdns_text}'"
+command_args="-T mdnsctl -p ${pidfile} -- ${procname} publish ${publish_args}"
+
+run_rc_command "$1"
diff --git a/home/.config/git/config b/home/.config/git/config
new file mode 100644
index 00000000..71799784
--- /dev/null
+++ b/home/.config/git/config
@@ -0,0 +1,21 @@
+[user]
+	name = C. McEnroe
+	email = june@causal.agency
+
+[commit]
+	verbose = true
+
+[merge]
+	conflictStyle = diff3
+
+[pull]
+	rebase = true
+
+[rebase]
+	autosquash = true
+
+[pretty]
+	log = %Cred%h %Creset%s%C(yellow)%d %Cgreen(%ar) %Cblue<%aN>
+
+[include]
+	path = ./private
diff --git a/home/.config/git/ignore b/home/.config/git/ignore
new file mode 100644
index 00000000..fea54519
--- /dev/null
+++ b/home/.config/git/ignore
@@ -0,0 +1,2 @@
+*.DS_store
+*.dSYM/
diff --git a/home/.config/htop/htoprc b/home/.config/htop/htoprc
new file mode 100644
index 00000000..cdda268d
--- /dev/null
+++ b/home/.config/htop/htoprc
@@ -0,0 +1,29 @@
+# Beware! This file is rewritten by htop when settings are changed in the interface.
+# The parser is also very primitive, and not human-friendly.
+fields=0 48 39 2 46 49 1
+sort_key=47
+sort_direction=1
+hide_threads=0
+hide_kernel_threads=1
+hide_userland_threads=1
+shadow_other_users=0
+show_thread_names=0
+show_program_path=1
+highlight_base_name=1
+highlight_megabytes=1
+highlight_threads=1
+tree_view=1
+header_margin=0
+detailed_cpu_time=0
+cpu_count_from_zero=0
+show_cpu_usage=1
+show_cpu_frequency=0
+update_process_names=0
+account_guest_in_cpu_meter=0
+color_scheme=0
+enable_mouse=0
+delay=15
+left_meters=AllCPUs2
+left_meter_modes=1
+right_meters=Memory Swap
+right_meter_modes=1 1
diff --git a/home/.config/nvim/colors/trivial.vim b/home/.config/nvim/colors/trivial.vim
new file mode 100644
index 00000000..3ebe8d97
--- /dev/null
+++ b/home/.config/nvim/colors/trivial.vim
@@ -0,0 +1,65 @@
+hi clear
+syntax reset
+let colors_name = 'trivial'
+
+hi Normal ctermbg=NONE ctermfg=NONE
+
+hi ColorColumn ctermbg=0
+hi EndOfBuffer ctermfg=8
+hi VertSplit cterm=NONE ctermbg=NONE ctermfg=8
+hi LineNr ctermfg=8
+hi MatchParen ctermbg=NONE ctermfg=3
+hi ModeMsg ctermfg=8
+hi MoreMsg ctermfg=2
+hi! link Question MoreMsg
+hi WarningMsg ctermfg=1
+hi NonText ctermfg=8
+hi Search ctermbg=NONE ctermfg=11
+hi StatusLine cterm=NONE ctermbg=0 ctermfg=7
+hi StatusLineNC cterm=NONE ctermbg=0 ctermfg=8
+hi Folded ctermbg=0 ctermfg=8
+hi Visual cterm=inverse ctermbg=NONE
+hi Title ctermfg=5
+hi Directory ctermfg=4
+
+hi Comment ctermfg=4
+
+hi! link Constant Normal
+hi String ctermfg=6
+hi link Character String
+
+hi! link Identifier Normal
+
+hi Statement ctermfg=7
+hi link Operator Normal
+
+hi PreProc ctermfg=2
+
+hi! link Type Normal
+hi link StorageClass Statement
+hi link Structure StorageClass
+hi link Typedef Structure
+
+hi! link Special Normal
+hi SpecialComment ctermfg=12
+hi SpecialKey ctermfg=5
+
+hi Underlined ctermfg=NONE
+hi Error ctermbg=NONE ctermfg=9
+hi SpellBad ctermbg=NONE ctermfg=1
+hi! link Todo SpecialComment
+
+hi cFormat ctermfg=14
+
+hi diffAdded ctermfg=10
+hi diffRemoved ctermfg=9
+
+hi manUnderline cterm=italic
+
+hi link pythonInclude Statement
+
+hi link rubyDefine Structure
+hi link rubyStringDelimiter String
+hi link rubySymbol String
+
+hi link rustModPath Identifier
diff --git a/home/.config/nvim/ftdetect/mdoc.vim b/home/.config/nvim/ftdetect/mdoc.vim
new file mode 100644
index 00000000..b845fee6
--- /dev/null
+++ b/home/.config/nvim/ftdetect/mdoc.vim
@@ -0,0 +1 @@
+autocmd BufRead,BufNewFile *.[1-9] set filetype=mdoc
diff --git a/home/.config/nvim/init.vim b/home/.config/nvim/init.vim
new file mode 100644
index 00000000..9245ccf4
--- /dev/null
+++ b/home/.config/nvim/init.vim
@@ -0,0 +1,37 @@
+set hidden
+set undofile
+set shortmess=atI
+set wildmode=list:longest wildignore=*.o
+set splitbelow splitright
+command! W w
+command! Q q
+
+set tabstop=4 shiftwidth=4 shiftround
+set smartindent cinoptions=l1(sU1m1
+set ignorecase smartcase inccommand=nosplit
+nmap <leader><leader> :nohlsearch<CR>
+set foldmethod=syntax foldlevel=99 foldopen-=block
+let asmsyntax = "nasm"
+let c_syntax_for_h = 1
+let is_posix = 1
+let man_hardwrap = 1
+
+set title laststatus=1
+set scrolloff=1
+set noruler colorcolumn=80
+set list listchars=tab:\ \ ,trail:·
+colorscheme trivial
+
+autocmd TermOpen * setlocal statusline=%{b:term_title}
+autocmd BufEnter term://* startinsert
+tmap <C-w> <C-\><C-n><C-w>
+
+let g:clipboard = {'copy':{'+':'pbcopy'},'paste':{'+':'pbpaste'}}
+nmap gp `[v`]
+
+nmap <leader>s vip:sort<CR>
+nmap <leader>S $vi{:sort<CR>
+nmap <leader>a m':0/^#include <<CR>:nohlsearch<CR>O#include <
+nmap <leader>l :0read ~/src/etc/agpl.c<CR>''
+nmap <leader>L :0read ~/src/etc/gpl.c<CR>''
+nmap <leader>d :0delete<CR>:0read !date +'.Dd \%B \%e, \%Y'<CR>
diff --git a/home/.config/nvim/syntax/mdoc.vim b/home/.config/nvim/syntax/mdoc.vim
new file mode 100644
index 00000000..d9d587f5
--- /dev/null
+++ b/home/.config/nvim/syntax/mdoc.vim
@@ -0,0 +1,12 @@
+if exists("b:current_syntax")
+	finish
+endif
+
+runtime! syntax/nroff.vim
+unlet! b:current_syntax
+
+setlocal sections+=ShSs
+syntax match mdocBlank /^\.$/ conceal
+setlocal conceallevel=2
+
+let b:current_syntax = "mdoc"
diff --git a/home/.editrc b/home/.editrc
new file mode 100644
index 00000000..cf779a7d
--- /dev/null
+++ b/home/.editrc
@@ -0,0 +1 @@
+bind -v
diff --git a/home/.gdbinit b/home/.gdbinit
new file mode 100644
index 00000000..9422460c
--- /dev/null
+++ b/home/.gdbinit
@@ -0,0 +1 @@
+set disassembly-flavor intel
diff --git a/home/.hushlogin b/home/.hushlogin
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/home/.hushlogin
diff --git a/home/.inputrc b/home/.inputrc
new file mode 100644
index 00000000..b2cc9d61
--- /dev/null
+++ b/home/.inputrc
@@ -0,0 +1 @@
+set editing-mode vi
diff --git a/home/.lldbinit b/home/.lldbinit
new file mode 100644
index 00000000..73f3e676
--- /dev/null
+++ b/home/.lldbinit
@@ -0,0 +1 @@
+settings set target.x86-disassembly-flavor intel
diff --git a/home/.local/bin/aes b/home/.local/bin/aes
new file mode 100755
index 00000000..32b52637
--- /dev/null
+++ b/home/.local/bin/aes
@@ -0,0 +1,7 @@
+#!/bin/sh
+set -eu
+
+enwiden() {
+	exec tr ' -~' ' ï¼-~'
+}
+[ $# -gt 0 ] && echo "$*" | enwiden || enwiden
diff --git a/home/.local/bin/def b/home/.local/bin/def
new file mode 100755
index 00000000..6a1681d3
--- /dev/null
+++ b/home/.local/bin/def
@@ -0,0 +1,47 @@
+#!/bin/sh
+set -eu
+
+macro=$1
+headers='
+assert.h
+complex.h
+ctype.h
+errno.h
+fenv.h
+float.h
+inttypes.h
+iso646.h
+limits.h
+locale.h
+math.h
+setjmp.h
+signal.h
+stdalign.h
+stdarg.h
+stdatomic.h
+stdbool.h
+stddef.h
+stdint.h
+stdio.h
+stdlib.h
+stdnoreturn.h
+string.h
+tgmath.h
+threads.h
+time.h
+uchar.h
+wchar.h
+wctype.h
+'
+
+for header in $headers; do
+	defined=$(
+		echo "$macro" \
+		| cc -E -x c -include "$header" - \
+		2> /dev/null \
+		| tail -n 1
+	)
+	[ $? -ne 0 -o "$defined" = "$macro" ] && continue
+	echo "#include <${header}>"
+	echo "$defined"
+done
diff --git a/home/.local/bin/git-password b/home/.local/bin/git-password
new file mode 100755
index 00000000..41351e38
--- /dev/null
+++ b/home/.local/bin/git-password
@@ -0,0 +1,7 @@
+#!/bin/sh
+set -eu
+
+url=$1
+echo "url=${url}" \
+	| git credential fill \
+	| sed -En 's/^password=(.*)/\1/p'
diff --git a/home/.local/bin/nasd b/home/.local/bin/nasd
new file mode 100755
index 00000000..d64b2c3a
--- /dev/null
+++ b/home/.local/bin/nasd
@@ -0,0 +1,9 @@
+#!/bin/sh
+set -eu
+
+dir=$(mktemp -d)
+echo 'bits 64' > "${dir}/input"
+cat >> "${dir}/input"
+nasm -o "${dir}/output" "${dir}/input" || true
+ndisasm -b 64 "${dir}/output" || true
+rm -r "$dir"
diff --git a/home/.local/bin/notify-send b/home/.local/bin/notify-send
new file mode 100755
index 00000000..5630440d
--- /dev/null
+++ b/home/.local/bin/notify-send
@@ -0,0 +1,9 @@
+#!/usr/bin/osascript
+
+on run argv
+	if count of argv is 2 then
+		display notification (item 2 of argv) with title (item 1 of argv)
+	else
+		display notification (item 1 of argv)
+	end if
+end run
diff --git a/home/.local/bin/np b/home/.local/bin/np
new file mode 100755
index 00000000..7d54574c
--- /dev/null
+++ b/home/.local/bin/np
@@ -0,0 +1,7 @@
+#!/usr/bin/osascript
+
+tell application "iTunes"
+	tell current track
+		get "/me is listening to " & artist & " — " & name
+	end tell
+end tell
diff --git a/home/.local/bin/versions b/home/.local/bin/versions
new file mode 100755
index 00000000..25e5ff72
--- /dev/null
+++ b/home/.local/bin/versions
@@ -0,0 +1,9 @@
+#!/bin/sh
+set -u
+
+for repo in ~/src/git/*; do
+	version=$(git -C "${repo}" describe --dirty 2>/dev/null)
+	if [ $? -eq 0 ]; then
+		echo "${repo##*/}-${version#v}"
+	fi
+done | sort -nr -t '-' -k 3 | column -t -s '-'
diff --git a/home/.profile b/home/.profile
new file mode 100644
index 00000000..c0c9ce66
--- /dev/null
+++ b/home/.profile
@@ -0,0 +1,21 @@
+_PATH=$PATH PATH=
+path() { [ -d "$1" ] && PATH="${PATH}${PATH:+:}${1}"; }
+for prefix in '' /usr/local /opt/local /usr ~/.local; do
+	path "${prefix}/sbin"
+	path "${prefix}/bin"
+done
+path /usr/games
+
+export PAGER=less
+export LESS=FRXx4
+export EDITOR=nvim
+export MANPAGER='nvim +Man!'
+export MANSECT=2:3:1:8:6:5:7:4:9
+export CLICOLOR=1
+export GPG_TTY=$(tty)
+export NETHACKOPTIONS='pickup_types:$!?+/=, color, DECgraphics'
+
+[ -e /usr/share/mk/sys.mk ] || export CFLAGS=-O
+[ -d /usr/home ] && cd
+
+export ENV=~/.shrc
diff --git a/home/.shrc b/home/.shrc
new file mode 100644
index 00000000..bf3e0dfe
--- /dev/null
+++ b/home/.shrc
@@ -0,0 +1,49 @@
+set -o noclobber -o nounset -o vi
+
+CDPATH=:~
+
+alias vim=$EDITOR
+alias ls='LC_COLLATE=C ls -p'
+alias ll='ls -hl'
+alias bc='bc -l'
+alias gs='git status --short --branch || ls' gd='git diff'
+alias gsh='git show' gl='git log --graph --pretty=log'
+alias gco='git checkout' gb='git branch' gm='git merge' gst='git stash'
+alias ga='git add' gmv='git mv' grm='git rm'
+alias gc='git commit' gca='gc --amend' gt='git tag'
+alias gp='git push' gu='git pull' gf='git fetch' gr='git rebase'
+alias rand='openssl rand -base64 33'
+alias private='eval "$(gpg -d ~/.private)"'
+type doas >/dev/null 2>&1 && alias sudo=doas
+
+cd() {
+	local path
+	if [ $# -eq 0 ]; then
+		command cd
+	elif [ "${1%%:*}" != "$1" ]; then
+		path=${1#*:}
+		[ -n "${path}" ] || path=${PWD#${HOME}/}
+		SSH_CD=$path ssh -o SendEnv=SSH_CD "${1%%:*}"
+	elif [ -e "$1" -a ! -d "$1" ]; then
+		command cd "${1%/*}" && $EDITOR "${1##*/}"
+	else
+		command cd "$@"
+	fi
+}
+if [ -n "${SSH_CD:-}" ]; then
+	cd "${SSH_CD}"
+	unset SSH_CD
+fi
+
+hostname=$(hostname -s)
+rprompt() {
+	local pwd
+	pwd=${PWD#${HOME}}
+	[ "${pwd}" != "${PWD}" ] && pwd="~${pwd}"
+	[ "${TERM%-*}" = 'xterm' ] \
+		&& printf '\33]0;%s\a' "${SSH_CLIENT:+${hostname}:}${pwd##*/}" >&2
+	printf '%s' "${SSH_CLIENT:+${hostname}:}${pwd}"
+}
+PS1='
+$ '
+RPS1='${?#0} $(rprompt)'
diff --git a/home/.ssh/config b/home/.ssh/config
new file mode 100644
index 00000000..3fc6a8db
--- /dev/null
+++ b/home/.ssh/config
@@ -0,0 +1,18 @@
+IgnoreUnknown Include
+Include config_private
+
+HashKnownHosts yes
+
+SendEnv LANG LC_*
+
+Host monday beastie puffy toaster tux progynova
+	HostName %h.local
+	ForwardAgent yes
+	RemoteForward 7062 127.0.0.1:7062
+
+Host june july
+	HostName %h.nyc3.do.causal.agency
+	Port 2222
+
+Host git.causal.agency temp.causal.agency
+	Port 2222
diff --git a/install.sh b/install.sh
new file mode 100644
index 00000000..6051edaf
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+set -eu
+
+packages='curl htop neovim sl the_silver_searcher tree'
+
+FreeBSD() {
+	sudo pkg install ddate $packages
+}
+
+OpenBSD() {
+	doas pkg_add $packages
+}
+
+Linux() {
+	sudo pacman -Sy --needed bc ctags gdb openbssh $packages
+}
+
+installMacPorts() {
+	xcode-select --install
+	xcodebuild -license
+	dir=MacPorts-2.6.3
+	tar=${dir}.tar.bz2
+	curl -O "https://distfiles.macports.org/MacPorts/${tar}"
+	tar -x -f $tar
+	(cd $dir && ./configure)
+	make -C $dir
+	sudo make -C $dir install
+	rm -fr $tar $dir
+}
+
+Darwin() {
+	[ -d /opt/local ] || installMacPorts
+	sudo /opt/local/bin/port selfupdate
+	sudo /opt/local/bin/port -N install git mandoc pkgconfig $packages
+	sudo mkdir -p /opt/local/etc/select/man
+	printf 'bin/man\nshare/man/man1/man.1\nshare/man/man1/man.1.gz\n' \
+		| sudo tee /opt/local/etc/select/man/base >/dev/null
+	printf '/usr/bin/man\n/usr/share/man/man1/man.1\n-\n' \
+		| sudo tee /opt/local/etc/select/man/system >/dev/null
+	sudo port select --set man system
+}
+
+$(uname)
diff --git a/link.sh b/link.sh
new file mode 100644
index 00000000..6763f2e0
--- /dev/null
+++ b/link.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+set -eu
+
+die() {
+	echo "$*"
+	exit 1
+}
+
+if [ $# -eq 1 ]; then
+	link=$1
+	file="${PWD}/home/${link#${HOME}/}"
+	[ ! -f "$file" ] || die "${file} exists"
+	mkdir -p "${file%/*}"
+	mv "$link" "$file"
+fi
+
+find home -type f | while read -r find; do
+	file="${PWD}/${find}"
+	link="${HOME}/${find#home/}"
+	mkdir -p "${link%/*}"
+	[ \( -f "$link" -a -L "$link" \) -o ! -f "$link" ] || die "${link} exists"
+	ln -fs "$file" "$link"
+done
diff --git a/port/caesar/.gitignore b/port/caesar/.gitignore
new file mode 100644
index 00000000..e2c3034b
--- /dev/null
+++ b/port/caesar/.gitignore
@@ -0,0 +1,2 @@
+caesar
+rot13
diff --git a/port/caesar/Makefile b/port/caesar/Makefile
new file mode 100644
index 00000000..01205b16
--- /dev/null
+++ b/port/caesar/Makefile
@@ -0,0 +1,19 @@
+PREFIX = ~/.local
+MANDIR = ${PREFIX}/share/man
+
+LDLIBS = -lm
+
+all: caesar rot13
+
+clean:
+	rm -f caesar rot13
+
+install: caesar rot13 caesar.6
+	install -d ${PREFIX}/bin ${MANDIR}/man6
+	install caesar rot13 ${PREFIX}/bin
+	install -m 644 caesar.6 ${MANDIR}/man6/caesar.6
+	install -m 644 caesar.6 ${MANDIR}/man6/rot13.6
+
+uninstall:
+	rm -f ${PREFIX}/bin/caesar ${PREFIX}/bin/rot13
+	rm -f ${MANDIR}/man6/caesar.6 ${MANDIR}/man6/rot13.6
diff --git a/port/caesar/caesar.6 b/port/caesar/caesar.6
new file mode 100644
index 00000000..4c4bbfb4
--- /dev/null
+++ b/port/caesar/caesar.6
@@ -0,0 +1,73 @@
+.\" Copyright (c) 1989, 1991, 1993
+.\"	The Regents of the University of California.  All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\"	@(#)caesar.6	8.2 (Berkeley) 11/16/93
+.\" $FreeBSD: releng/11.2/usr.bin/caesar/caesar.6 216239 2010-12-06 19:12:51Z uqs $
+.\"
+.Dd November 16, 1993
+.Dt CAESAR 6
+.Os
+.Sh NAME
+.Nm caesar , rot13
+.Nd decrypt caesar ciphers
+.Sh SYNOPSIS
+.Nm
+.Op Ar rotation
+.Nm rot13
+.Sh DESCRIPTION
+The
+.Nm
+utility attempts to decrypt caesar ciphers using English letter frequency
+statistics.
+.Nm Caesar
+reads from the standard input and writes to the standard output.
+.Pp
+The optional numerical argument
+.Ar rotation
+may be used to specify a specific rotation value.
+If invoked as
+.Nm rot13 ,
+a rotation value of 13 will be used.
+.Pp
+The frequency (from most common to least) of English letters is as follows:
+.Bd -ragged -offset indent
+ETAONRISHDLFCMUGPYWBVKXJQZ
+.Ed
+.Pp
+Their frequencies as a percentage are as follows:
+.Bd -ragged -offset indent
+E(13), T(10.5), A(8.1), O(7.9), N(7.1), R(6.8), I(6.3), S(6.1), H(5.2),
+D(3.8), L(3.4), F(2.9), C(2.7), M(2.5), U(2.4), G(2),
+P(1.9), Y(1.9),
+W(1.5), B(1.4), V(.9), K(.4), X(.15), J(.13), Q(.11), Z(.07).
+.Ed
+.Pp
+Rotated postings to
+.Tn USENET
+and some of the databases used by the
+.Xr fortune 6
+program are rotated by 13 characters.
diff --git a/port/caesar/caesar.c b/port/caesar/caesar.c
new file mode 100644
index 00000000..cd6cd579
--- /dev/null
+++ b/port/caesar/caesar.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 1989, 1993
+ *	The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Rick Adams.
+ *
+ * Authors:
+ *	Stan King, John Eldridge, based on algorithm suggested by
+ *	Bob Morris
+ * 29-Sep-82
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if 0
+#ifndef lint
+static const char copyright[] =
+"@(#) Copyright (c) 1989, 1993\n\
+	The Regents of the University of California.  All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static const char sccsid[] = "@(#)caesar.c    8.1 (Berkeley) 5/31/93";
+#endif /* not lint */
+#endif
+#include <sys/cdefs.h>
+//__FBSDID("$FreeBSD: releng/11.2/usr.bin/caesar/caesar.c 241846 2012-10-22 03:06:53Z eadler $");
+
+#include <errno.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+
+#define	LINELENGTH	2048
+#define	ROTATE(ch, perm) \
+     isascii(ch) ? ( \
+	isupper(ch) ? ('A' + (ch - 'A' + perm) % 26) : \
+	    islower(ch) ? ('a' + (ch - 'a' + perm) % 26) : ch) : ch
+
+/*
+ * letter frequencies (taken from some unix(tm) documentation)
+ * (unix is a trademark of Bell Laboratories)
+ */
+static double stdf[26] = {
+	7.97, 1.35, 3.61, 4.78, 12.37, 2.01, 1.46, 4.49, 6.39, 0.04,
+	0.42, 3.81, 2.69, 5.92,  6.96, 2.91, 0.08, 6.63, 8.77, 9.68,
+	2.62, 0.81, 1.88, 0.23,  2.07, 0.06,
+};
+
+static void printit(char *);
+
+int
+main(int argc, char **argv)
+{
+	int ch, dot, i, nread, winnerdot = 0;
+	char *inbuf;
+	int obs[26], try, winner;
+
+	if (argc > 1)
+		printit(argv[1]);
+
+	if (!(inbuf = malloc((size_t)LINELENGTH))) {
+		(void)fprintf(stderr, "caesar: out of memory.\n");
+		exit(1);
+	}
+
+	/* adjust frequency table to weight low probs REAL low */
+	for (i = 0; i < 26; ++i)
+		stdf[i] = log(stdf[i]) + log(26.0 / 100.0);
+
+	/* zero out observation table */
+	bzero(obs, 26 * sizeof(int));
+
+	if ((nread = read(STDIN_FILENO, inbuf, (size_t)LINELENGTH)) < 0) {
+		(void)fprintf(stderr, "caesar: %s\n", strerror(errno));
+		exit(1);
+	}
+	for (i = nread; i--;) {
+		ch = (unsigned char) inbuf[i];
+		if (isascii(ch)) {
+			if (islower(ch))
+				++obs[ch - 'a'];
+			else if (isupper(ch))
+				++obs[ch - 'A'];
+		}
+	}
+
+	/*
+	 * now "dot" the freqs with the observed letter freqs
+	 * and keep track of best fit
+	 */
+	for (try = winner = 0; try < 26; ++try) { /* += 13) { */
+		dot = 0;
+		for (i = 0; i < 26; i++)
+			dot += obs[i] * stdf[(i + try) % 26];
+		/* initialize winning score */
+		if (try == 0)
+			winnerdot = dot;
+		if (dot > winnerdot) {
+			/* got a new winner! */
+			winner = try;
+			winnerdot = dot;
+		}
+	}
+
+	for (;;) {
+		for (i = 0; i < nread; ++i) {
+			ch = (unsigned char) inbuf[i];
+			putchar(ROTATE(ch, winner));
+		}
+		if (nread < LINELENGTH)
+			break;
+		if ((nread = read(STDIN_FILENO, inbuf, (size_t)LINELENGTH)) < 0) {
+			(void)fprintf(stderr, "caesar: %s\n", strerror(errno));
+			exit(1);
+		}
+	}
+	exit(0);
+}
+
+static void
+printit(char *arg)
+{
+	int ch, rot;
+
+	if ((rot = atoi(arg)) < 0) {
+		(void)fprintf(stderr, "caesar: bad rotation value.\n");
+		exit(1);
+	}
+	while ((ch = getchar()) != EOF)
+		putchar(ROTATE(ch, rot));
+	exit(0);
+}
diff --git a/port/caesar/rot13.sh b/port/caesar/rot13.sh
new file mode 100644
index 00000000..8ce4b94e
--- /dev/null
+++ b/port/caesar/rot13.sh
@@ -0,0 +1,33 @@
+#!/bin/sh -
+#
+# Copyright (c) 1992, 1993
+#	The Regents of the University of California.  All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+# 3. Neither the name of the University nor the names of its contributors
+#    may be used to endorse or promote products derived from this software
+#    without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+#	@(#)rot13.sh	8.1 (Berkeley) 5/31/93
+# $FreeBSD: releng/11.2/usr.bin/caesar/rot13.sh 278616 2015-02-12 05:35:00Z cperciva $
+
+exec caesar 13 "$@"
diff --git a/port/cgram/.gitignore b/port/cgram/.gitignore
new file mode 100644
index 00000000..d4f2ec10
--- /dev/null
+++ b/port/cgram/.gitignore
@@ -0,0 +1 @@
+cgram
diff --git a/port/cgram/Makefile b/port/cgram/Makefile
new file mode 100644
index 00000000..02f11eec
--- /dev/null
+++ b/port/cgram/Makefile
@@ -0,0 +1,17 @@
+PREFIX = ~/.local
+MANDIR = ${PREFIX}/share/man
+
+LDLIBS = -lcurses
+
+cgram:
+
+clean:
+	rm -f cgram
+
+install: cgram cgram.6
+	install -d ${PREFIX}/bin ${MANDIR}/man6
+	install cgram ${PREFIX}/bin
+	install -m 644 cgram.6 ${MANDIR}/man6
+
+uninstall:
+	rm -f ${PREFIX}/bin/cgram ${MANDIR}/man6/cgram.6
diff --git a/port/cgram/cgram.6 b/port/cgram/cgram.6
new file mode 100644
index 00000000..9f315804
--- /dev/null
+++ b/port/cgram/cgram.6
@@ -0,0 +1,65 @@
+.\" $NetBSD: cgram.6,v 1.2 2013/08/04 07:55:09 wiz Exp $
+.\"
+.\" Copyright (c) 2004, 2013 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by David A. Holland.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd August 3, 2013
+.Dt CGRAM 6
+.Os
+.Sh NAME
+.Nm cgram
+.Nd solve Sunday-paper cryptograms
+.Sh SYNOPSIS
+.Nm
+.Sh DESCRIPTION
+.Nm
+is a curses-based widget for solving Sunday-paper-type cryptograms
+based on substitution ciphers.
+A random cleartext is chosen using
+.Xr fortune 6
+and a random substitution key is generated.
+.Pp
+The ciphertext is displayed.
+Typing a letter changes the key so that the letter under the cursor
+maps to the newly typed letter, and updates the display accordingly.
+Use Emacs-type cursor commands to move around.
+Enter a tilde
+.Pq ~
+to quit.
+Press asterisk
+.Pq *
+to enter an easier mode where correct letters are displayed in
+boldface.
+.Sh SEE ALSO
+.Xr caesar 6
+.Sh HISTORY
+.Nm
+was written circa 2004.
+It was imported into
+.Nx
+in 2013 and first appeared in
+.Nx 7.0 .
diff --git a/port/cgram/cgram.c b/port/cgram/cgram.c
new file mode 100644
index 00000000..76ea55fb
--- /dev/null
+++ b/port/cgram/cgram.c
@@ -0,0 +1,344 @@
+/*-
+ * Copyright (c) 2013 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <time.h>
+#include <err.h>
+#include <assert.h>
+#include <curses.h>
+#include "pathnames.h"
+
+////////////////////////////////////////////////////////////
+
+static char *xstrdup(const char *s) {
+   char *ret;
+
+   ret = malloc(strlen(s) + 1);
+   if (ret == NULL) {
+      errx(1, "Out of memory");
+   }
+   strcpy(ret, s);
+   return ret;
+}
+
+////////////////////////////////////////////////////////////
+
+struct stringarray {
+   char **v;
+   int num;
+};
+
+static void stringarray_init(struct stringarray *a) {
+   a->v = NULL;
+   a->num = 0;
+}
+
+static void stringarray_cleanup(struct stringarray *a) {
+   free(a->v);
+}
+
+static void stringarray_add(struct stringarray *a, const char *s) {
+   a->v = realloc(a->v, (a->num + 1) * sizeof(a->v[0]));
+   if (a->v == NULL) {
+      errx(1, "Out of memory");
+   }
+   a->v[a->num] = xstrdup(s);
+   a->num++;
+}
+
+////////////////////////////////////////////////////////////
+
+static struct stringarray lines;
+static struct stringarray sollines;
+static bool hinting;
+static int scrolldown;
+static unsigned curx;
+static int cury;
+
+static void readquote(void) {
+   FILE *f = popen(_PATH_FORTUNE, "r");
+   if (!f) {
+      err(1, "%s", _PATH_FORTUNE);
+   }
+
+   char buf[128], buf2[8*sizeof(buf)];
+   while (fgets(buf, sizeof(buf), f)) {
+      char *s = strrchr(buf, '\n');
+      assert(s);
+      assert(strlen(s)==1);
+      *s = 0;
+
+      int i,j;
+      for (i=j=0; buf[i]; i++) {
+	 if (buf[i]=='\t') {
+	    buf2[j++] = ' ';
+	    while (j%8) buf2[j++] = ' ';
+	 }
+	 else if (buf[i]=='\b') {
+	    if (j>0) j--;
+	 }
+	 else {
+	    buf2[j++] = buf[i];
+	 }
+      }
+      buf2[j] = 0;
+
+      stringarray_add(&lines, buf2);
+      stringarray_add(&sollines, buf2);
+   }
+
+   pclose(f);
+}
+
+static void encode(void) {
+   int used[26];
+   for (int i=0; i<26; i++) used[i] = 0;
+
+   int key[26];
+   int keypos=0;
+   while (keypos < 26) {
+      int c = random()%26;
+      if (used[c]) continue;
+      key[keypos++] = c;
+      used[c] = 1;
+   }
+
+   for (int y=0; y<lines.num; y++) {
+      for (unsigned x=0; lines.v[y][x]; x++) {
+	 if (islower((unsigned char)lines.v[y][x])) {
+	    int q = lines.v[y][x]-'a';
+	    lines.v[y][x] = 'a'+key[q];
+	 }
+	 if (isupper((unsigned char)lines.v[y][x])) {
+	    int q = lines.v[y][x]-'A';
+	    lines.v[y][x] = 'A'+key[q];
+	 }
+      }
+   }
+}
+
+static int substitute(int ch) {
+   assert(cury>=0 && cury<lines.num);
+   if (curx >= strlen(lines.v[cury])) {
+      beep();
+      return -1;
+   }
+
+   int och = lines.v[cury][curx];
+   if (!isalpha((unsigned char)och)) {
+      beep();
+      return -1;
+   }
+
+   int loch = tolower((unsigned char)och);
+   int uoch = toupper((unsigned char)och);
+   int lch = tolower((unsigned char)ch);
+   int uch = toupper((unsigned char)ch);
+
+   for (int y=0; y<lines.num; y++) {
+      for (unsigned x=0; lines.v[y][x]; x++) {
+	 if (lines.v[y][x]==loch) {
+	    lines.v[y][x] = lch;
+	 }
+	 else if (lines.v[y][x]==uoch) {
+	    lines.v[y][x] = uch;
+	 }
+	 else if (lines.v[y][x]==lch) {
+	    lines.v[y][x] = loch;
+	 }
+	 else if (lines.v[y][x]==uch) {
+	    lines.v[y][x] = uoch;
+	 }
+      }
+   }
+   return 0;
+}
+
+////////////////////////////////////////////////////////////
+
+static void redraw(void) {
+   erase();
+   bool won = true;
+   for (int i=0; i<LINES-1; i++) {
+      move(i, 0);
+      int ln = i+scrolldown;
+      if (ln < lines.num) {
+	 for (unsigned j=0; lines.v[i][j]; j++) {
+	    int ch = lines.v[i][j];
+	    if (ch != sollines.v[i][j] && isalpha((unsigned char)ch)) {
+	       won = false;
+	    }
+	    bool bold=false;
+	    if (hinting && ch==sollines.v[i][j] &&
+		isalpha((unsigned char)ch)) {
+	       bold = true;
+	       attron(A_BOLD);
+	    }
+	    addch(lines.v[i][j]);
+	    if (bold) {
+	       attroff(A_BOLD);
+	    }
+	 }
+      }
+      clrtoeol();
+   }
+
+   move(LINES-1, 0);
+   if (won) {
+      addstr("*solved* ");
+   }
+   addstr("~ to quit, * to cheat, ^pnfb to move");
+
+   move(LINES-1, 0);
+
+   move(cury-scrolldown, curx);
+
+   refresh();
+}
+
+static void opencurses(void) {
+    initscr();
+    cbreak();
+    noecho();
+}
+
+static void closecurses(void) {
+   endwin();
+}
+
+////////////////////////////////////////////////////////////
+
+static void loop(void) {
+   bool done=false;
+   while (!done) {
+      redraw();
+      int ch = getch();
+      switch (ch) {
+       case 1: /* ^A */
+	curx=0;
+	break;
+       case 2: /* ^B */
+	if (curx > 0) {
+	   curx--;
+	}
+	else if (cury > 0) {
+	   cury--;
+	   curx = strlen(lines.v[cury]);
+	}
+	break;
+       case 5: /* ^E */
+	curx = strlen(lines.v[cury]);
+	break;
+       case 6: /* ^F */
+	if (curx < strlen(lines.v[cury])) {
+	   curx++;
+	}
+	else if (cury < lines.num - 1) {
+	   cury++;
+	   curx = 0;
+	}
+	break;
+       case 12: /* ^L */
+	clear();
+	break;
+       case 14: /* ^N */
+	if (cury < lines.num-1) {
+	   cury++;
+	}
+	if (curx > strlen(lines.v[cury])) {
+	   curx =  strlen(lines.v[cury]);
+	}
+	if (scrolldown < cury - (LINES-2)) {
+	   scrolldown = cury - (LINES-2);
+	}
+	break;
+       case 16: /* ^P */
+	if (cury > 0) {
+	   cury--;
+	}
+	if (curx > strlen(lines.v[cury])) {
+	   curx = strlen(lines.v[cury]);
+	}
+	if (scrolldown > cury) {
+	   scrolldown = cury;
+	}
+	break;
+       case '*':
+	hinting = !hinting;
+	break;
+       case '~':
+	done = true;
+	break;
+       default:
+	if (isalpha(ch)) {
+	   if (!substitute(ch)) {
+	      if (curx < strlen(lines.v[cury])) {
+		 curx++;
+	      }
+	      if (curx==strlen(lines.v[cury]) && cury < lines.num-1) {
+		 curx=0;
+		 cury++;
+	      }
+	   }
+	}
+	else if (curx < strlen(lines.v[cury]) && ch==lines.v[cury][curx]) {
+	   curx++;
+	   if (curx==strlen(lines.v[cury]) && cury < lines.num-1) {
+	      curx=0;
+	      cury++;
+	   }
+	}
+	else {
+	   beep();
+	}
+	break;
+      }
+   }
+}
+
+////////////////////////////////////////////////////////////
+
+int main(void) {
+   stringarray_init(&lines);
+   stringarray_init(&sollines);
+   srandom(time(NULL));
+   readquote();
+   encode();
+   opencurses();
+
+   loop();
+
+   closecurses();
+   stringarray_cleanup(&sollines);
+   stringarray_cleanup(&lines);
+   return 0;
+}
diff --git a/port/cgram/pathnames.h b/port/cgram/pathnames.h
new file mode 100644
index 00000000..40db1eed
--- /dev/null
+++ b/port/cgram/pathnames.h
@@ -0,0 +1,30 @@
+/*-
+ * Copyright (c) 2013 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by David A. Holland.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _PATH_FORTUNE "fortune"
diff --git a/port/file2c/.gitignore b/port/file2c/.gitignore
new file mode 100644
index 00000000..aafb358f
--- /dev/null
+++ b/port/file2c/.gitignore
@@ -0,0 +1 @@
+file2c
diff --git a/port/file2c/Makefile b/port/file2c/Makefile
new file mode 100644
index 00000000..09f6b5d0
--- /dev/null
+++ b/port/file2c/Makefile
@@ -0,0 +1,15 @@
+PREFIX = ~/.local
+MANDIR = ${PREFIX}/share/man
+
+file2c:
+
+clean:
+	rm -f file2c
+
+install: file2c file2c.1
+	install -d ${PREFIX}/bin ${MANDIR}/man1
+	install file2c ${PREFIX}/bin
+	install -m 644 file2c.1 ${MANDIR}/man1
+
+uninstall:
+	rm -f ${PREFIX}/bin/file2c ${MANDIR}/man1/file2c.1
diff --git a/port/file2c/file2c.1 b/port/file2c/file2c.1
new file mode 100644
index 00000000..fe1fe5e7
--- /dev/null
+++ b/port/file2c/file2c.1
@@ -0,0 +1,75 @@
+.\"----------------------------------------------------------------------------
+.\" "THE BEER-WARE LICENSE" (Revision 42):
+.\" <phk@FreeBSD.org> wrote this file.  As long as you retain this notice, you
+.\" can do whatever you want with this file. If we meet some day, and you think
+.\" this stuff is worth it, you can buy me a beer in return.  Poul-Henning Kamp
+.\" ---------------------------------------------------------------------------
+.\"
+.\" $FreeBSD: releng/11.2/usr.bin/file2c/file2c.1 173197 2007-10-30 17:49:00Z ru $
+.\"
+.Dd March 22, 2007
+.Dt FILE2C 1
+.Os
+.Sh NAME
+.Nm file2c
+.Nd convert file to c-source
+.Sh SYNOPSIS
+.Nm
+.Op Fl sx
+.Op Fl n Ar count
+.Op Ar prefix Op Ar suffix
+.Sh DESCRIPTION
+The
+.Nm
+utility reads a file from stdin and writes it to stdout, converting each
+byte to its decimal or hexadecimal representation on the fly.
+The byte values are separated by a comma.
+This also means that the last byte value is not followed by a comma.
+By default the byte values are printed in decimal, but when the
+.Fl x
+option is given, the values will be printed in hexadecimal.
+When
+.Fl s
+option is given, each line is printed with a leading tab and each comma is
+followed by a space except for the last one on the line.
+.Pp
+If more than 70 characters are printed on the same line, that line is
+ended and the output continues on the next line.
+With the
+.Fl n
+option this can be made to happen after the specified number of
+byte values have been printed.
+The length of the line will not be considered anymore.
+To have all the byte values printed on the same line, give the
+.Fl n
+option a negative number.
+.Pp
+A prefix and suffix strings can be printed before and after the byte values
+(resp.)
+If a suffix is to be printed, a prefix must also be specified.
+The first non-option word is the prefix, which may optionally be followed
+by a word that is to be used as the suffix.
+.Pp
+This program is typically used to embed binary files into C source files.
+The prefix is used to define an array type and the suffix is used to end
+the C statement.
+The
+.Fl n , s
+and
+.Fl x
+options are useful when the binary data represents a bitmap and the output
+needs to remain readable and/or editable.
+Fonts, for example, are a good example of this.
+.Sh EXAMPLES
+The command:
+.Bd -literal -offset indent
+date | file2c 'const char date[] = {' ',0};'
+.Ed
+.Pp
+will produce:
+.Bd -literal -offset indent
+const char date[] = {
+83,97,116,32,74,97,110,32,50,56,32,49,54,58,50,56,58,48,53,
+32,80,83,84,32,49,57,57,53,10
+,0};
+.Ed
diff --git a/port/file2c/file2c.c b/port/file2c/file2c.c
new file mode 100644
index 00000000..cff7f602
--- /dev/null
+++ b/port/file2c/file2c.c
@@ -0,0 +1,92 @@
+/*
+ * ----------------------------------------------------------------------------
+ * "THE BEER-WARE LICENSE" (Revision 42):
+ * <phk@FreeBSD.org> wrote this file.  As long as you retain this notice you
+ * can do whatever you want with this stuff. If we meet some day, and you think
+ * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
+ * ----------------------------------------------------------------------------
+ */
+
+#include <sys/cdefs.h>
+//__FBSDID("$FreeBSD: releng/11.2/usr.bin/file2c/file2c.c 200462 2009-12-13 03:14:06Z delphij $");
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static void
+usage(void)
+{
+
+	fprintf(stderr, "usage: %s [-sx] [-n count] [prefix [suffix]]\n",
+	    "file2c");
+	exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+	int c, count, linepos, maxcount, pretty, radix;
+
+	maxcount = 0;
+	pretty = 0;
+	radix = 10;
+	while ((c = getopt(argc, argv, "n:sx")) != -1) {
+		switch (c) {
+		case 'n':	/* Max. number of bytes per line. */
+			maxcount = strtol(optarg, NULL, 10);
+			break;
+		case 's':	/* Be more style(9) comliant. */
+			pretty = 1;
+			break;
+		case 'x':	/* Print hexadecimal numbers. */
+			radix = 16;
+			break;
+		case '?':
+		default:
+			usage();
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (argc > 0)
+		printf("%s\n", argv[0]);
+	count = linepos = 0;
+	while((c = getchar()) != EOF) {
+		if (count) {
+			putchar(',');
+			linepos++;
+		}
+		if ((maxcount == 0 && linepos > 70) ||
+		    (maxcount > 0 && count >= maxcount)) {
+			putchar('\n');
+			count = linepos = 0;
+		}
+		if (pretty) {
+			if (count) {
+				putchar(' ');
+				linepos++;
+			} else {
+				putchar('\t');
+				linepos += 8;
+			}
+		}
+		switch (radix) {
+		case 10:
+			linepos += printf("%d", c);
+			break;
+		case 16:
+			linepos += printf("0x%02x", c);
+			break;
+		default:
+			abort();
+		}
+		count++;
+	}
+	putchar('\n');
+	if (argc > 1)
+		printf("%s\n", argv[1]);
+	return (0);
+}
diff --git a/port/wcwidth/.gitignore b/port/wcwidth/.gitignore
new file mode 100644
index 00000000..132e8098
--- /dev/null
+++ b/port/wcwidth/.gitignore
@@ -0,0 +1,3 @@
+*.o
+libwcwidth.dylib
+wcfix
diff --git a/port/wcwidth/COPYRIGHT b/port/wcwidth/COPYRIGHT
new file mode 100644
index 00000000..e6472371
--- /dev/null
+++ b/port/wcwidth/COPYRIGHT
@@ -0,0 +1,190 @@
+musl as a whole is licensed under the following standard MIT license:
+
+----------------------------------------------------------------------
+Copyright © 2005-2020 Rich Felker, et al.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+----------------------------------------------------------------------
+
+Authors/contributors include:
+
+A. Wilcox
+Ada Worcester
+Alex Dowad
+Alex Suykov
+Alexander Monakov
+Andre McCurdy
+Andrew Kelley
+Anthony G. Basile
+Aric Belsito
+Arvid Picciani
+Bartosz Brachaczek
+Benjamin Peterson
+Bobby Bingham
+Boris Brezillon
+Brent Cook
+Chris Spiegel
+Clément Vasseur
+Daniel Micay
+Daniel Sabogal
+Daurnimator
+David Carlier
+David Edelsohn
+Denys Vlasenko
+Dmitry Ivanov
+Dmitry V. Levin
+Drew DeVault
+Emil Renner Berthing
+Fangrui Song
+Felix Fietkau
+Felix Janda
+Gianluca Anzolin
+Hauke Mehrtens
+He X
+Hiltjo Posthuma
+Isaac Dunham
+Jaydeep Patil
+Jens Gustedt
+Jeremy Huntwork
+Jo-Philipp Wich
+Joakim Sindholt
+John Spencer
+Julien Ramseier
+Justin Cormack
+Kaarle Ritvanen
+Khem Raj
+Kylie McClain
+Leah Neukirchen
+Luca Barbato
+Luka Perkov
+M Farkas-Dyck (Strake)
+Mahesh Bodapati
+Markus Wichmann
+Masanori Ogino
+Michael Clark
+Michael Forney
+Mikhail Kremnyov
+Natanael Copa
+Nicholas J. Kain
+orc
+Pascal Cuoq
+Patrick Oppenlander
+Petr Hosek
+Petr Skocik
+Pierre Carrier
+Reini Urban
+Rich Felker
+Richard Pennington
+Ryan Fairfax
+Samuel Holland
+Segev Finer
+Shiz
+sin
+Solar Designer
+Stefan Kristiansson
+Stefan O'Rear
+Szabolcs Nagy
+Timo Teräs
+Trutz Behn
+Valentin Ochs
+Will Dietz
+William Haddon
+William Pitcock
+
+Portions of this software are derived from third-party works licensed
+under terms compatible with the above MIT license:
+
+The TRE regular expression implementation (src/regex/reg* and
+src/regex/tre*) is Copyright © 2001-2008 Ville Laurikari and licensed
+under a 2-clause BSD license (license text in the source files). The
+included version has been heavily modified by Rich Felker in 2012, in
+the interests of size, simplicity, and namespace cleanliness.
+
+Much of the math library code (src/math/* and src/complex/*) is
+Copyright © 1993,2004 Sun Microsystems or
+Copyright © 2003-2011 David Schultz or
+Copyright © 2003-2009 Steven G. Kargl or
+Copyright © 2003-2009 Bruce D. Evans or
+Copyright © 2008 Stephen L. Moshier or
+Copyright © 2017-2018 Arm Limited
+and labelled as such in comments in the individual source files. All
+have been licensed under extremely permissive terms.
+
+The ARM memcpy code (src/string/arm/memcpy_el.S) is Copyright © 2008
+The Android Open Source Project and is licensed under a two-clause BSD
+license. It was taken from Bionic libc, used on Android.
+
+The implementation of DES for crypt (src/crypt/crypt_des.c) is
+Copyright © 1994 David Burren. It is licensed under a BSD license.
+
+The implementation of blowfish crypt (src/crypt/crypt_blowfish.c) was
+originally written by Solar Designer and placed into the public
+domain. The code also comes with a fallback permissive license for use
+in jurisdictions that may not recognize the public domain.
+
+The smoothsort implementation (src/stdlib/qsort.c) is Copyright © 2011
+Valentin Ochs and is licensed under an MIT-style license.
+
+The x86_64 port was written by Nicholas J. Kain and is licensed under
+the standard MIT terms.
+
+The mips and microblaze ports were originally written by Richard
+Pennington for use in the ellcc project. The original code was adapted
+by Rich Felker for build system and code conventions during upstream
+integration. It is licensed under the standard MIT terms.
+
+The mips64 port was contributed by Imagination Technologies and is
+licensed under the standard MIT terms.
+
+The powerpc port was also originally written by Richard Pennington,
+and later supplemented and integrated by John Spencer. It is licensed
+under the standard MIT terms.
+
+All other files which have no copyright comments are original works
+produced specifically for use as part of this library, written either
+by Rich Felker, the main author of the library, or by one or more
+contibutors listed above. Details on authorship of individual files
+can be found in the git version control history of the project. The
+omission of copyright and license comments in each file is in the
+interest of source tree size.
+
+In addition, permission is hereby granted for all public header files
+(include/* and arch/*/bits/*) and crt files intended to be linked into
+applications (crt/*, ldso/dlstart.c, and arch/*/crt_arch.h) to omit
+the copyright notice and permission notice otherwise required by the
+license, and to use these files without any requirement of
+attribution. These files include substantial contributions from:
+
+Bobby Bingham
+John Spencer
+Nicholas J. Kain
+Rich Felker
+Richard Pennington
+Stefan Kristiansson
+Szabolcs Nagy
+
+all of whom have explicitly granted such permission.
+
+This file previously contained text expressing a belief that most of
+the files covered by the above exception were sufficiently trivial not
+to be subject to copyright, resulting in confusion over whether it
+negated the permissions granted in the license. In the spirit of
+permissive licensing, and of not having licensing issues being an
+obstacle to adoption, that text has been removed.
diff --git a/port/wcwidth/Makefile b/port/wcwidth/Makefile
new file mode 100644
index 00000000..50faa653
--- /dev/null
+++ b/port/wcwidth/Makefile
@@ -0,0 +1,27 @@
+PREFIX ?= ~/.local
+
+OBJS = wcwidth.o wcswidth.o
+
+all: libwcwidth.dylib wcfix
+
+libwcwidth.dylib: ${OBJS}
+	${CC} -dynamiclib ${LDFLAGS} ${OBJS} -o $@
+
+wcwidth.o: nonspacing.h wide.h
+
+.SUFFIXES: .in
+
+.in:
+	sed 's|%%PREFIX%%|${PREFIX}|g' $< > $@
+	chmod a+x $@
+
+clean:
+	rm -f libwcwidth.dylib wcfix ${OBJS}
+
+install: libwcwidth.dylib wcfix
+	install -d ${PREFIX}/lib ${PREFIX}/bin
+	install -m 644 libwcwidth.dylib ${PREFIX}/lib
+	install wcfix ${PREFIX}/bin
+
+uninstall:
+	rm -f ${PREFIX}/lib/libwcwidth.dylib ${PREFIX}/bin/wcfix
diff --git a/port/wcwidth/nonspacing.h b/port/wcwidth/nonspacing.h
new file mode 100644
index 00000000..5d05a3d1
--- /dev/null
+++ b/port/wcwidth/nonspacing.h
@@ -0,0 +1,89 @@
+16,16,16,18,19,20,21,22,23,24,25,26,27,28,29,30,31,16,16,32,16,16,16,33,34,35,
+36,37,38,39,16,16,40,16,16,16,16,16,16,16,16,16,16,16,41,42,16,16,43,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,44,16,45,46,47,48,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,49,16,16,50,
+51,16,52,53,54,16,16,16,16,16,16,55,16,16,56,16,57,58,59,60,61,62,63,64,65,66,
+67,68,16,69,70,71,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,72,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,73,74,16,16,16,75,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,76,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,77,78,16,16,16,16,16,16,16,79,16,16,16,16,16,80,81,82,16,16,16,16,16,83,
+84,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,248,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,254,255,255,255,255,191,182,0,0,0,0,0,0,0,63,0,255,23,0,0,0,0,0,248,255,
+255,0,0,1,0,0,0,0,0,0,0,0,0,0,0,192,191,159,61,0,0,0,128,2,0,0,0,255,255,255,
+7,0,0,0,0,0,0,0,0,0,0,192,255,1,0,0,0,0,0,0,248,15,32,0,0,192,251,239,62,0,0,
+0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,248,255,255,255,255,
+255,7,0,0,0,0,0,0,20,254,33,254,0,12,0,0,0,2,0,0,0,0,0,0,16,30,32,0,0,12,0,0,
+64,6,0,0,0,0,0,0,16,134,57,2,0,0,0,35,0,6,0,0,0,0,0,0,16,190,33,0,0,12,0,0,
+252,2,0,0,0,0,0,0,144,30,32,64,0,12,0,0,0,4,0,0,0,0,0,0,0,1,32,0,0,0,0,0,0,17,
+0,0,0,0,0,0,192,193,61,96,0,12,0,0,0,2,0,0,0,0,0,0,144,64,48,0,0,12,0,0,0,3,0,
+0,0,0,0,0,24,30,32,0,0,12,0,0,0,0,0,0,0,0,0,0,0,0,4,92,0,0,0,0,0,0,0,0,0,0,0,
+242,7,128,127,0,0,0,0,0,0,0,0,0,0,0,0,242,31,0,63,0,0,0,0,0,0,0,0,0,3,0,0,160,
+2,0,0,0,0,0,0,254,127,223,224,255,254,255,255,255,31,64,0,0,0,0,0,0,0,0,0,0,0,
+0,224,253,102,0,0,0,195,1,0,30,0,100,32,0,32,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,224,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,0,
+0,0,28,0,0,0,12,0,0,0,12,0,0,0,0,0,0,0,176,63,64,254,15,32,0,0,0,0,0,120,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,135,1,4,14,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,9,0,0,0,0,0,0,64,127,
+229,31,248,159,0,0,0,0,0,0,255,127,0,0,0,0,0,0,0,0,15,0,0,0,0,0,208,23,4,0,0,
+0,0,248,15,0,3,0,0,0,60,59,0,0,0,0,0,0,64,163,3,0,0,0,0,0,0,240,207,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,247,255,253,33,16,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,255,255,255,255,255,255,255,
+251,0,248,0,0,0,124,0,0,0,0,0,0,223,255,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,
+255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,3,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,0,0,0,0,
+0,60,0,0,0,0,0,0,0,0,0,0,0,0,0,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,128,247,63,0,0,0,192,0,0,0,0,0,0,0,0,0,0,3,0,68,8,0,0,96,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,48,0,0,0,255,255,3,128,0,0,0,0,192,63,0,0,128,255,3,0,
+0,0,0,0,7,0,0,0,0,0,200,51,0,0,0,0,32,0,0,0,0,0,0,0,0,126,102,0,8,16,0,0,0,0,
+0,16,0,0,0,0,0,0,157,193,2,0,0,0,0,48,64,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,32,33,0,0,0,0,0,64,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,0,0,255,255,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,14,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,32,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,192,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,110,240,0,0,0,0,0,135,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,
+0,0,0,0,0,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,192,255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,255,
+127,0,0,0,0,0,0,128,3,0,0,0,0,0,120,38,0,32,0,0,0,0,0,0,7,0,0,0,128,239,31,0,
+0,0,0,0,0,0,8,0,3,0,0,0,0,0,192,127,0,30,0,0,0,0,0,0,0,0,0,0,0,128,211,64,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,248,7,0,0,3,0,0,0,0,0,0,24,1,0,0,0,192,
+31,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,92,0,0,64,0,0,0,0,0,
+0,0,0,0,0,248,133,13,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,60,176,1,0,0,48,0,0,0,
+0,0,0,0,0,0,0,248,167,1,0,0,0,0,0,0,0,0,0,0,0,0,40,191,0,0,0,0,0,0,0,0,0,0,0,
+0,224,188,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+128,255,6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,12,1,0,0,0,254,7,0,0,0,0,248,121,128,0,
+126,14,0,0,0,0,0,252,127,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,191,0,0,0,
+0,0,0,0,0,0,0,252,255,255,252,109,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,126,180,191,0,
+0,0,0,0,0,0,0,0,163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,
+0,0,0,0,0,0,0,255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,31,0,0,0,0,0,0,0,127,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,
+0,128,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,15,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,3,248,255,231,15,0,0,0,60,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,28,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,255,255,255,255,255,255,127,248,255,255,255,255,255,31,32,0,16,0,0,248,
+254,255,0,0,0,0,0,0,0,0,0,
+0,127,255,255,249,219,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,240,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,127,0,0,0,0,0,0,0,0,0,0,0,0,0,240,7,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,
diff --git a/port/wcwidth/wcfix.in b/port/wcwidth/wcfix.in
new file mode 100644
index 00000000..832c83d6
--- /dev/null
+++ b/port/wcwidth/wcfix.in
@@ -0,0 +1,7 @@
+#!/bin/sh
+set -eu
+
+export DYLD_FORCE_FLAT_NAMESPACE=1
+export DYLD_INSERT_LIBRARIES=%%PREFIX%%/lib/libwcwidth.dylib
+
+exec "$@"
diff --git a/port/wcwidth/wcswidth.c b/port/wcwidth/wcswidth.c
new file mode 100644
index 00000000..5c8a5a4d
--- /dev/null
+++ b/port/wcwidth/wcswidth.c
@@ -0,0 +1,8 @@
+#include <wchar.h>
+
+int wcswidth(const wchar_t *wcs, size_t n)
+{
+	int l=0, k=0;
+	for (; n-- && *wcs && (k = wcwidth(*wcs)) >= 0; l+=k, wcs++);
+	return (k < 0) ? k : l;
+}
diff --git a/port/wcwidth/wcwidth.c b/port/wcwidth/wcwidth.c
new file mode 100644
index 00000000..36256a53
--- /dev/null
+++ b/port/wcwidth/wcwidth.c
@@ -0,0 +1,29 @@
+#include <wchar.h>
+
+static const unsigned char table[] = {
+#include "nonspacing.h"
+};
+
+static const unsigned char wtable[] = {
+#include "wide.h"
+};
+
+int wcwidth(wchar_t wc)
+{
+	if (wc < 0xffU)
+		return (wc+1 & 0x7f) >= 0x21 ? 1 : wc ? -1 : 0;
+	if ((wc & 0xfffeffffU) < 0xfffe) {
+		if ((table[table[wc>>8]*32+((wc&255)>>3)]>>(wc&7))&1)
+			return 0;
+		if ((wtable[wtable[wc>>8]*32+((wc&255)>>3)]>>(wc&7))&1)
+			return 2;
+		return 1;
+	}
+	if ((wc & 0xfffe) == 0xfffe)
+		return -1;
+	if (wc-0x20000U < 0x20000)
+		return 2;
+	if (wc == 0xe0001 || wc-0xe0020U < 0x5f || wc-0xe0100U < 0xef)
+		return 0;
+	return 1;
+}
diff --git a/port/wcwidth/wide.h b/port/wcwidth/wide.h
new file mode 100644
index 00000000..e403c9a5
--- /dev/null
+++ b/port/wcwidth/wide.h
@@ -0,0 +1,65 @@
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,18,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,19,16,20,21,22,16,16,16,23,16,16,24,25,26,27,28,17,
+17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,29,
+17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+17,17,17,17,17,17,17,17,30,16,16,16,16,31,16,16,17,17,17,17,17,17,17,17,17,17,
+17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+17,17,17,17,17,17,17,32,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,16,16,16,33,
+34,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,35,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+17,17,17,17,17,17,36,17,17,37,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,38,39,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+16,16,16,16,16,16,16,40,41,42,43,44,45,46,47,16,48,49,16,16,16,16,
+16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,12,0,6,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,30,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,0,0,48,0,0,0,0,0,0,255,15,0,0,0,0,128,0,0,8,
+0,2,12,0,96,48,64,16,0,0,4,44,36,32,12,0,0,0,1,0,0,0,80,184,0,0,0,0,0,0,0,224,
+0,0,0,1,128,0,0,0,0,0,0,0,0,0,0,0,24,0,0,0,0,0,0,33,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,251,255,255,255,255,255,255,255,
+255,255,255,15,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,63,0,0,0,255,15,255,255,255,255,
+255,255,255,127,254,255,255,255,255,255,255,255,255,255,127,254,255,255,255,
+255,255,255,255,255,255,255,255,255,224,255,255,255,255,255,254,255,255,255,
+255,255,255,255,255,255,255,127,255,255,255,255,255,7,255,255,255,255,15,0,
+255,255,255,255,255,127,255,255,255,255,255,0,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0,
+0,0,0,0,0,0,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,31,255,255,255,255,255,255,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,
+255,255,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,15,0,0,0,0,0,0,0,0,0,0,0,0,0,255,3,0,0,255,255,255,255,247,255,127,15,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,254,255,255,255,255,255,255,255,255,255,255,
+255,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,127,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,15,0,0,0,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,7,0,255,255,255,127,0,0,0,0,0,
+0,7,0,240,0,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+15,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,128,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,64,254,7,0,0,0,0,0,0,0,0,0,0,0,0,7,0,255,255,255,
+255,255,15,255,1,3,0,63,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,255,255,255,255,
+1,224,191,255,255,255,255,255,255,255,255,223,255,255,15,0,255,255,255,255,
+255,135,15,0,255,255,17,255,255,255,255,255,255,255,255,127,253,255,255,255,
+255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
+159,255,255,255,255,255,255,255,63,0,120,255,255,255,0,0,4,0,0,96,0,16,0,0,0,
+0,0,0,0,0,0,0,248,255,255,255,255,255,255,255,255,255,255,0,0,0,0,0,0,255,255,
+255,255,255,255,255,255,63,16,39,0,0,24,240,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,255,15,0,
+0,0,224,255,255,255,255,255,255,255,255,255,255,255,255,123,252,255,255,255,
+255,231,199,255,255,255,231,255,255,255,255,255,255,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,15,7,7,0,63,0,0,0,0,0,0,0,0,0,0,0,0,0,
diff --git a/prune.sh b/prune.sh
new file mode 100644
index 00000000..fabe865f
--- /dev/null
+++ b/prune.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+set -eu
+
+find -L ~/.config ~/.local -type l -lname "${PWD}/*" | while read -r link; do
+	echo "$link"
+	rm "$link"
+done
diff --git a/txt/.notemap b/txt/.notemap
new file mode 100644
index 00000000..acfe44ab
--- /dev/null
+++ b/txt/.notemap
@@ -0,0 +1,3 @@
+5347bbff-b124-432a-a67e-5d90fa34d0d4 books.txt
+9efa92ef-a8c0-4fa6-8a2a-13315992162b music.txt
+2d3c58f2-a193-4ff5-b79f-fd430f80eb64 shows.txt
diff --git a/txt/books.txt b/txt/books.txt
new file mode 100644
index 00000000..35657c91
--- /dev/null
+++ b/txt/books.txt
@@ -0,0 +1,114 @@
+[ 2020 ]
+
+ 19. ★★★ N. K. Jemisin, The Awakened Kingdom
+ 18. ★★☆ N. K. Jemisin, The Kingdom of Gods
+ 17. ★★☆ N. K. Jemisin, The Broken Kingdoms
+ 16. ★★☆ Ann Leckie, Ancillary Justice
+ 15. ★★☆ Madeline Miller, The Song of Achilles
+ 14. ★★☆ N. K. Jemisin, The Hundred Thousand Kingdoms
+ 13. ★★☆ Annalee Newitz, Autonomous
+ 12. ★☆☆ Tade Thompson, The Rosewater Insurrection
+ 11. ★★☆ Alix E. Harrow, The Ten Thousand Doors of January
+ 10. ★★☆ N. K. Jemisin, The Stone Sky
+  9. ★★★ Kai Cheng Thom, Fierce Femmes and Notorious Liars
+  8. ★☆☆ Amal El-Mohtar & Max Gladstone, This Is How You Lose the Time War
+  7. ★★★ N. K. Jemisin, The Obelisk Gate
+  6. ★★★ Becky Chambers, To Be Taught, If Fortunate
+  5. ★★★ Annalee Newitz, The Future of Another Timeline
+  4. ★★☆ Rivers Solomon, The Deep
+  3. ★★☆ ed. Hope Nicholson, Love Beyond Body, Space & Time
+  2. ★★☆ Tade Thompson, Rosewater
+  1. ★★★ Meg Elison, The Book of Flora
+
+[ 2019 ]
+
+ 42. ★★★ Meg Elison, The Book of Etta
+ 41. ★★☆ Martha Wells, Exit Strategy
+ 40. ★★☆ Martha Wells, Artificial Condition
+ 39. ★★☆ N. K. Jemisin, The Fifth Season
+ 38. ★★☆ Martha Wells, Rogue Protocol
+ 37. ★★☆ Yoon Ha Lee, Ninefox Gambit
+ 36. ★★★ Rebecca Makkai, The Great Believers
+ 35. ★★☆ Meg Elison, The Book of the Unnamed Midwife
+ 34. ★★☆ Martha Wells, All Systems Red
+ 33. ★★☆ Nnedi Okorafor, The Book of Phoenix
+ 32. ★★☆ JY Yang, The Red Threads of Fortune
+ 31. ★★☆ JY Yang, The Black Tides of Heaven
+ 30. ★☆☆ Rebecca Roanhorse, Trail of Lightning
+ 29. ★☆☆ Jo Walton, The Just City
+ 28. ★★★ Arkady Martine, A Memory Called Empire
+ 27. ★★☆ Mary Robinette Kowal, The Fated Sky
+ 26. ★★★ Becky Chambers, Record of a Spaceborn Few
+ 25. ★★☆ Mary Robinette Kowal, The Calculating Stars
+ 24. ★★☆ Octavia E. Butler, Imago
+ 23. ★★☆ Octavia E. Butler, Kindred
+ 22. ★★☆ Octavia E. Butler, Adulthood Rites
+ 21. ★★★ Octavia E. Butler, Wild Seed
+ 20. ★★☆ Octavia E. Butler, Parable of the Talents
+ 19. ★★☆ Jeff VanderMeer, Acceptance
+ 18. ★★★ Becky Chambers, A Closed and Common Orbit
+ 17. ★★☆ Octavia E. Butler, Dawn
+ 16. ★★☆ Jeff VanderMeer, Authority
+ 15. ★★☆ Octavia E. Butler, Parable of the Sower
+ 14. ★★☆ C. A. Higgins, Lightless
+ 13. ★☆☆ Alfred Bester, The Demolished Man
+ 12. ★★★ Karin Tidbeck, Amatka
+ 11. ★☆☆ Catherynne M. Valente, Space Opera
+ 10. ★★☆ Rivers Solomon, An Unkindness of Ghosts
+  9. ★★★ Becky Chambers, The Long Way to a Small Angry Planet
+  8. ★★☆ Emily St. John Mandel, Station Eleven
+  7. ★★★ Douglas Adams, Dirk Gently's Holistic Detective Agency
+  6. ★★★ Octavia E. Butler, Fledgling
+  5. ★★★ Jeff VanderMeer, Annihilation
+  4. ★★☆ Alfred Bester, The Stars My Destination
+  3. ★☆☆ Samuel R. Delany, The Einstein Intersection
+  2. ★★★ Octavia E. Butler, Mind of My Mind
+  1. ★☆☆ Robert A. Heinlein, Waldo & Magic, Inc.
+
+[ 2018 ]
+
+ 25. ★★★ Douglas Adams, The Long Dark Tea-Time of the Soul
+ 24. ★★☆ Khaled Hosseini, A Thousand Splendid Suns
+ 23. ★★☆ Nathan Altice, I Am Error
+ 22. ★★★ Ruth Ozeki, A Tale for the Time Being
+ 21. ★☆☆ Arthur C. Clarke, Earthlight
+ 20. ★★★ Terry Pratchett & Neil Gaiman, Good Omens
+ 19. ★☆☆ Robert A. Heinlein, Starship Troopers
+ 18. ★★☆ Liu Cixin, The Three-Body Problem
+ 17. ★★☆ Robert A. Heinlein, Revolt in 2100
+ 16. ★★☆ ed. Robert A. Heinlein, Tomorrow, the Stars
+ 15. ★★☆ Robert A. Heinlein, Assignment in Eternity
+ 14. ★☆☆ Robert A. Heinlein, The Rolling Stones
+ 13. ★★☆ Robert A. Heinlein, Starman Jones
+ 12. ★☆☆ Robert A. Heinlein, Space Cadet
+ 11. ★☆☆ Robert A. Heinlein, Farmer in the Sky
+ 10. ★★☆ Robert A. Heinlein, Between Planets
+  9. ★★☆ Robert A. Heinlein, Red Planet
+  8. ★☆☆ Robert A. Heinlein, Rocket Ship Galileo
+  7. ★☆☆ Robert A. Heinlein, Sixth Column
+  6. ★☆☆ Robert A. Heinlein, Beyond This Horizon
+  5. ★★☆ Robert A. Heinlein, Orphans of the Sky
+  4. ★★☆ Tracy Kidder, The Soul of a New Machine
+  3. ★★★ James Alan Gardner, Ascending
+  2. ★★★ Anne Frank, The Diary of a Young Girl
+  1. ★★★ Harper Lee, Go Set a Watchman
+
+[ 2017 ]
+
+  1. ★★★ James Alan Gardner, Expendable
+
+[ ... ]
+
+ • ★★★ Harper Lee, To Kill a Mockingbird
+ • ★★★ Douglas Adams, The Hitchhiker's Guide to the Galaxy
+ • ★★★ Douglas Adams, The Restaurant at the End of the Universe
+ • ★★☆ Douglas Adams, Life, the Universe and Everything
+ • ★★★ Douglas Adams, So Long, and Thanks for All the Fish
+ • ★★☆ Douglas Adams, Mostly Harmless
+ • ★☆☆ Eoin Colfer, And Another Thing...
+ • ★★★ Margaret Atwood, The Handmaid's Tale
+ • ★★☆ Kazuo Ishiguro, Never Let Me Go
+ • ★★★ Philip Pullman, The Goldan Compass
+ • ★★★ Philip Pullman, The Subtle Knife
+ • ★★★ Philip Pullman, The Amber Spyglass
+ • ★★★ William Goldman, The Princess Bride
diff --git a/txt/music.txt b/txt/music.txt
new file mode 100644
index 00000000..cd3705c4
--- /dev/null
+++ b/txt/music.txt
@@ -0,0 +1,285 @@
+Feist — I Feel It All
+<https://youtu.be/g-1Gb2rxzlk>
+
+The Blow — "Come On Petunia"
+<https://youtu.be/MO1HSfzK1Ns>
+
+Black Country, New Road — Sunglasses
+<https://youtu.be/UAZhzi9cpkc>
+
+Mitski, Xiu Xiu — Between the Breaths
+<https://youtu.be/HnhTkFl5Imw>
+
+Black Dresses — CREEP U
+<https://youtu.be/w9RSZmltcVI>
+
+BACKXWASH — DONT COME TO THE WOODS
+<https://youtu.be/7ZcJsWyGbOw>
+
+William Bonney — See Ya Later
+<https://youtu.be/-uCUlvUlr9s>
+
+Four Tet — Hands
+<https://youtu.be/ojZoQbRt1tc>
+
+Buffy Sainte-Marie — Darling Don't Cry
+
+LINGUA IGNOTA — DO YOU DOUBT ME TRAITOR
+<https://youtu.be/M1ZweG__q-w>
+
+Lizzo — Truth Hurts
+<https://youtu.be/P00HMxdsVZI>
+
+Hobo Johnson and The Lovemakers — Tiny Desk Concert
+<https://youtu.be/A8a2EosJIbM>
+
+Poppy — Concrete
+<https://youtu.be/WwoGhpYdebQ>
+
+Kim Petras — Do Me
+<https://youtu.be/LShK0Yhd964>
+
+Kim Petras — Heart to Break
+<https://youtu.be/5CPeHQHAQyo>
+
+Anomie — Avorter n'est pas tuer
+<https://youtu.be/W1iGXRDeZf4>
+
+Zhaoze — Birds Contending
+<https://zhaoze.bandcamp.com/album/birds-contending>
+
+Colin Stetson — Reborn
+<https://youtu.be/MVnSFj6XQZY>
+
+Holly Herndon — Frontier
+<https://youtu.be/rvNqNgHAEys>
+
+Bleachers — Tiny Desk Concert
+<https://youtu.be/QCtkkX2f18M>
+
+Girlpool — Tiny Desk Concert
+<https://youtu.be/VNM8Tg9pvDU>
+
+Daughters — You Won't Get What You Want
+<https://daughters.bandcamp.com/album/you-wont-get-what-you-want>
+
+La Dispute — FULTON STREET I
+<https://ladispute.bandcamp.com/track/fulton-street-i>
+
+KASHIWA Daisuke — Stella
+<https://youtu.be/ei7cdynwRMA>
+
+Jeff Wayne — The Eve of the War
+<https://youtu.be/6YwFvmnbj3E>
+
+Julia Holter — I Shall Love 2
+<https://youtu.be/k5uwPaCvbhA>
+
+Chromatics — Running Up That Hill
+<https://youtu.be/Mgv88ZLi6LY>
+
+Low — Double Negative
+<https://lowtheband.bandcamp.com/album/double-negative>
+
+Rival Consoles — Helios
+<https://youtu.be/T8n-XC_2a-k>
+
+Tiffany — I Think We're Alone Now
+<https://youtu.be/w6Q3mHyzn78>
+
+Pulp — Common People
+<https://youtu.be/yuTMWgOduFM>
+
+XTC — Making Plans for Nigel
+<https://youtu.be/gZjZBCZWxpg>
+
+New Order — Temptation
+<https://youtu.be/xxDv_RTdLQo>
+
+Street Sects — In for a World of Hurt
+<https://streetsects.bandcamp.com/track/in-for-a-world-of-hurt>
+
+New Order — Temptation
+<https://youtu.be/xxDv_RTdLQo>
+
+Blondie — Heart of Glass
+<https://youtu.be/fWPhhlKHM80>
+
+Suuns — Watch You, Watch Me
+<https://suuns.bandcamp.com/track/watch-you-watch-me>
+
+Neckbeard Deathcamp — White Nationalism is for Basement Dwelling Losers
+<https://neckbearddeathcamp.bandcamp.com/album/white-nationalism-is-for-basement-dwelling-losers>
+
+rook — shed blood
+<https://rooksfeather.bandcamp.com/album/shed-blood>
+
+SOPHIE — OIL OF EVERY PEARL'S UN-INSIDES
+<http://smarturl.it/SOPHIEALBUM>
+
+Rosetta — Utopioid
+<https://theanaesthete.bandcamp.com/album/utopioid>
+
+Desire — Under Your Spell
+<https://youtu.be/9K7rmxjk5RQ>
+
+Broken Social Scene — Anthems for a Seventeen-Year Old Girl
+<https://youtu.be/DDqNL0js0iU>
+
+Shinsei Kamattechan — Yuugure no tori
+<https://youtu.be/sUW4dDWiz-A>
+
+Petite Meller — The Flute
+<https://youtu.be/BLwgeV7dXOI>
+
+King Gizzard and the Lizard Wizard — Flying Microtonal Banana
+<https://youtu.be/D0BsgJxw208>
+
+rook&nomie — DAYDREAM
+<https://youtu.be/00TdaTffFeY>
+
+Spiritualized — Ladies and Gentlemen We Are Floating in Space
+<https://youtu.be/p47V3w4m1yg>
+
+Street Sects — End Position
+<https://streetsects.bandcamp.com/album/end-position-2>
+
+Converge — The Dusk in Us
+<https://convergecult.bandcamp.com/album/the-dusk-in-us>
+
+St. Vincent — MASSEDUCTION
+
+Godspeed You! Black Emperor — Undoing a Luciferian Towers
+<https://godspeedyoublackemperor.bandcamp.com/track/undoing-a-luciferian-towers>
+
+Florist — Tiny Desk Concert
+<https://youtu.be/WbyyxIZ02Zs>
+
+CHVRCHES — Tiny Desk Concert
+<https://youtu.be/haunJARHPm4>
+
+Aurora — Tiny Desk Concert
+<https://youtu.be/evBgLWQwAFA>
+
+Joy Division — Atmosphere
+<https://youtu.be/1EdUjlawLJM>
+
+Underworld — Born Slippy
+<https://youtu.be/iTFrCbQGyvM>
+
+Converge — I Can Tell You About Pain
+<https://convergecult.bandcamp.com/album/i-can-tell-you-about-pain>
+
+CocoRosie — Lost Girls
+<https://youtu.be/aRa-SlftLQo>
+
+FAUVE — Blizzard
+<https://youtu.be/HMpmedi_pH4>
+
+FAUVE — Nuits Fauves
+<https://youtu.be/cwaAppsy5yo>
+
+Sarin — House of Leaves
+<https://sarin.bandcamp.com/track/house-of-leaves-split-w-guiltfeeder>
+
+Chromatics — Shadow
+<https://youtu.be/IGUboLZx3Tk>
+
+Arcade Fire — Creature Comfort
+<https://youtu.be/xzwicesJQ7E>
+
+Arcade Fire — Everything Now
+<https://youtu.be/zC30BYR3CUk>
+
+Ed Schrader's Music Beat — Sermon
+
+Jessica Moss — Pools of Light
+
+Death Grips — Steroids (Crouching Tiger Hidden Gabber Megamix)
+<https://youtu.be/JUTKTk60aGk>
+
+Saltland — A Common Truth
+
+Colin Stetson — All This I Do for Glory
+<https://colinstetson.bandcamp.com/album/all-this-i-do-for-glory>
+
+Xiu Xiu — Forget
+<https://youtu.be/ywRzfwA75pY>
+
+Arca — Arca
+
+Joni Void — Dissociation (Kyla's Song)
+<http://cstrecords.com/cst125/>
+
+Do Make Say Think — Bound and Boundless
+<http://cstrecords.com/cst120/>
+
+The Body Lovers / The Body Haters
+
+Unwound — Leaves Turn Inside You
+
+Xiu Xiu — Fabulous Muscles
+
+Xiu Xiu — A Promise
+
+Saltland — Light of Mercy
+<http://cstrecords.com/saltland-releases-new-single-light-of-mercy/>
+
+Jessica Moss — Glaciers I (Pt I)
+<http://cstrecords.com/cst124/>
+
+BNNY RBBT — Big World
+<http://www.bnnyrbbt.fans>
+
+Woman is the Earth — Depths
+<https://womanistheearth.bandcamp.com/album/depths>
+
+Saltland — I Only Wish This For You
+<http://cstrecords.com/cst123/>
+
+Sun Kil Moon — Benji
+<https://youtu.be/UtndQzCUEY4>
+
+Neil Cicierega — Mouth Moods
+<http://www.neilcic.com/mouthmoods/>
+
+Those Who Walk Away — "First Degraded Rhythm" + "First Partially Recollected Conversation"
+<http://cstrecords.com/cst122/>
+
+Dan Smith — Some Tunes
+<https://thedancemyth.bandcamp.com/album/some-tunes>
+
+Avec le soleil sortant de sa bouche — Pas pire pop, I Love You So Much
+
+Arcade Fire — I Give You Power
+<https://youtu.be/f6jma9VQEls>
+
+Xiu Xiu — Jenny GoGo
+<https://youtu.be/WMT6MsA3ut8>
+
+Kero Kero Bonito — Fish Bowl
+<https://youtu.be/FY-CjOJCjJE>
+
+Avec le soleil sortant de sa bouche — Alizé et Margaret D. Midi moins le quart. Sur la plage, un palmier ensanglanté
+<http://cstrecords.com/cst121/>
+
+G.L.O.S.S. — Trans Day of Revenge
+<https://girlslivingoutsidesocietysshit.bandcamp.com/releases>
+
+Joy Division — Love Will Tear Us Apart
+<https://youtu.be/zuuObGsB0No>
+
+Xiu Xiu — Honeysuckle
+<https://youtu.be/hYKGR8Er4vM>
+
+Xiu Xiu — Ceremony
+<https://youtu.be/95ms8A2XJY0>
+
+Xiu Xiu — I Luv the Valley Oh
+<https://youtu.be/dztURk0_DOg>
+
+Porter Robinson & Madeon — Shelter
+<https://youtu.be/fzQ6gRAEoy0>
+
+Julien Baker — Tiny Desk Concert
+<https://youtu.be/tADWPTqR_4A>
diff --git a/txt/plan.7 b/txt/plan.7
new file mode 100644
index 00000000..5fb8eec1
--- /dev/null
+++ b/txt/plan.7
@@ -0,0 +1,45 @@
+.Dd November 26, 2020
+.Dt PLAN 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm plan
+.Nd possible future projects
+.
+.Sh DESCRIPTION
+.
+.Ss bubger -- mostly works
+.Bl -bullet -compact
+.It
+IMAP mailbox (mailing list) archive generator
+.It
+export HTML/Atom/mboxrd fragments by UID
+.It
+assemble fragments into threads using IMAP THREAD
+.El
+.
+.Ss sbubby
+.Bl -bullet -compact
+.It
+mailing list subscription manager for FastMail
+.It
+process messages from IMAP mailbox
+.It
+add/remove addresses from CardDAV address book
+.It
+send confirmations with SMTP
+.It
+it seems I can't be bothered
+implementing all the parts of this
+.El
+.
+.Ss getaddrinfo with SRV
+wrap
+.Xr getaddrinfo 3
+with
+.Xr res_query 3
+SRV lookup
+.
+.Ss code reuse
+discover some method of sharing code
+that isn't awful
diff --git a/txt/shows.txt b/txt/shows.txt
new file mode 100644
index 00000000..62e6a071
--- /dev/null
+++ b/txt/shows.txt
@@ -0,0 +1,32 @@
+2020-01-23 (La Sala Rossa) Secondsight, BIG|BRAVE
+2019-12-10 (Casa del Popolo) meth, Street Sects
+2019-06-22 (Casa del Popolo) Persons Unknown, Palissade, Police des Moeurs, Blu Anxiety
+2019-06-22 (Bar Le Ritz) Cloakroom, Pelican
+2019-06-21 (La Vitrola) Leash Aggression, CPU Rave, Vitex, Pinocchio
+2019-06-20 (Casa del Popolo) Beep Test, Liar//Lier, Wetware
+2019-06-18 (Casa del Popolo) Nomadic War Machine; it foot, it ears; Wendy Eisenberg
+2019-06-17 (La Sala Rossa) SaskPWR, Lubomyr Melnyk, Architek Percussion (Nicole Lizée)
+2019-06-08 (La Sotterenea) Eliza Kavtion, Beep Test, Dri Hiev
+2019-05-03 (Turbo Haüs) Issfenn
+2019-04-21 (Bar Le Ritz PDB) Claud, Hatchie, Girlpool
+2018-11-14 (Turbo Haus) Juss, Street Sects
+2018-07-26 (Theatre Fairmount) Uniform, Deafheaven
+2018-06-14 (Casa del Popolo) Markus Floats, Mark Lowe, Carodiaro, Eliza Kavtion
+2018-04-26 (Bar Le Ritz PDB) Strega, Show of Bedlam, Rosetta
+2018-04-13 (La Vitrola) Eliza Kavtion, Lungbutter, Wrekmeister Harmonies
+2018-03-08 (Brasserie Beaubien) Clayborne, Nightwitches, Aseethe, Vile Creature
+2018-02-08 (Bar Le Ritz PDB) KGD, Efrim Manuel Menuck
+2017-09-30 (La Sala Rossa) Truster, Lungbutter, Big Brave
+2017-09-19 (La Vitrola) Saccharine, Xiu Xiu
+2017-06-01 (Casa del Popolo) Genevieve Heistek, Saltland
+2017-05-27 (l’Escogriffe) Sough, Ikaray, C H R I S T
+2017-05-14 (Bar Le Ritz PDB) Mutter’d, Aim Low, Wrekmeister Harmonies
+2016-09-25 (La Sala Rossa) Cloud Rat, Wolves in the Throne Room
+2016-09-21 (Theatre Paradoxe) Godspeed You! Black Emperor
+2016-04-08 (La Sala Rossa) Caro Diaro, Wreckage With Stick, Ought
+2015-08-13 (La Vitrola) Gutser, Chemical Way, Saul Hittner, In the Name of Havoc
+2015-06-10 (La Sala Rossa) Ryan Sawyer, Colin Stetson & Sarah Neufeld
+2015-06-09 (La Sala Rossa) The Mile End Ladies String Auxiliary, Christof Migone
+2015-06-08 (Casa del Popolo) C H R I S T, Jessica Moss, Big Brave
+2014-01-19 (Metropolis) Godspeed You! Black Emperor
+2014-01-18 (l’Olympia) Elf Power, Neutral Milk Hotel
diff --git a/txt/trouble-at-jinx-hotel.txt b/txt/trouble-at-jinx-hotel.txt
new file mode 100644
index 00000000..4db28dd6
--- /dev/null
+++ b/txt/trouble-at-jinx-hotel.txt
@@ -0,0 +1,236 @@
+mollasses
+
+...à l'hôtel jinx...
+
+thierry amar; flüffy erskine; norsola johnson; kate lawrence;
+jennifer ménard; mike moya; sam shalabi; scott lregrande chernoff...
+chris brokaw; bruce cawdron; david michael curry; lisa gamble; efrim
+menuck; michel meunier; js truchy; thalia zedek... à l'hôtel jinx.
+
+lucky 13: siren's song. la la la, amerika. Saint Christopher's
+blues.  sign of judgment. Lynn Canyon wedding song. coda. You Can't
+Win. trouble in mind. las niñas. Miss Peach's Pawnshop. buffaloed
+at Wounded Knee/the weeping winds. no love lost. songs from the
+basement.
+
+trouble at jinx hotel. these cursed recordings began in late winter
+2003 at the hotel2tango in montréal. it was a bitter sunday in
+march. the world was on the brink of war in old mesopotamia, and
+we were all of us sick with the influenza. headaches. fevers. wrecked
+guts and water on the lungs. coughing like crazy. everyone with a
+base case of the shakes.  the hotel wass cold and its ghosts were
+unhappy. electricity was being weird. lights flickered and all the
+microphones crackled with live current. little blue sparks dancing
+through the studio rooms. amplifiers were picking up strange radio
+frequencies and the piano, which couldn't hold a tune to save its
+own life, was behaving like a cantankerous old man. our cats and
+dogs were nervous and excited, a-jitter the way animals get whenever
+catastrope looms. we started with a 16-track tape machine, three
+spools of 1/4" analog audio tape, and a lucky thirteen little songs
+hanging in our heads. but when the 16-track busted and broke down
+a few days later, we moved to 2" tape on a 24-track machine and
+began anew with our fingers crossed. then the 24-track showed the
+first symptoms of its own terrible illness. so we did what we could
+with digital audio tape while our trusty friend and technician,
+garfield lamb, nursed the equipment back to some semblance of health.
+then we transfered the digital recordings to 2" tape and went to
+work again...  waiting to see what the gods would do next. they
+toyed with us for a while before unleashing their full fury: the
+24-track, seizing mid-song one evening, ate a reel of tape and sent
+it spewing around the studio in little pieces, a scene as cruel as
+confetti at a funeral.  garfield somehow managed to splice the tape
+together again. but our nerves were now in tatters too, scattered
+all over the hotel floor. when we finally finished tracking this
+music in the summer of 2003, the cats and dogs didn't look any less
+worried than when we'd begun. of course, they sensed the disaster
+we would soon see for ourselves: as we started mixing the very next
+night, a faulty lamp at the studio short-circuited and the tape
+machine caught on fire. a freak electrical accident. there was an
+asterisk of light; then a dragon-tail of smoke coiling across the
+room; then there was a darkness; and then the 24-track quietly died.
+the animals, cowering in corners, whined, *why didn't you fucking
+fools jump ship at the first sign of the storm*? that's when we
+drifted away from the jinxed hotel... floating until we found safe
+harbour in a studio we could borrow for a few days - one whose own
+resident dogs, zoro and ciska, greeted us with idiot grins and the
+contented looks of drunken sailors on calm seas. mixing was finished
+in a kind of delirious distress at MixArt in notre-dame-de-grace,
+montréal, autumn 2003. these lucky thirteen. dieu merci. slgc. *new
+year's eve* 2003.
+
+siren's song. instrumental.
+
+la la la, amerika. amerika is crawling with cops tonight as we put
+our fists through the windows of the world i know you're broken and
+you're down at the mouth but you're pouting like a punished little
+girl when darkness calls night falls oh mother where are the children
+you've orphaned all over the Earth we're frightened and sickened
+and poverty-stricken and waiting for you to send word when darkness
+calls the sky falls we're tired mother and we're poor we're wretched
+at your teeming shore we're homeless and tossed by the storm looking
+for the light at your golden door when darkness calls night falls.
+
+flüffy: marching drum, percussion & choir; norsola: cello & voice;
+kate: violin; jennifer: voice; moya: Crumar organ; dmc: viola &
+choir; gamble: choir; efrim: choir; js: upright bass; thalia: voice;
+scott: acoustic guitars & vocals; scott recorded the air-raid siren
+and bombs on the nightly news when baghdad was attacked by the
+united states and great britain in march 2003; words & music by
+scott chernoff, as stolen from the inscription on the statue of
+liberty: *give me your tired your poor your huddled masses yearning
+to breathe the wretched refuse of your teeming shore send these the
+homeless tempest-tossed to me i lift my lamp beside the golden
+door*.
+
+Saint Christopher's blues. the locusts are in the jimson Shit Creek
+is on the rise and you're out wrecking eternity this town looks
+like a prison in September's crimson sky when i should be loving
+you perfectly you're such a pretty little mess with your Saint
+Christopher blues and that doomed little look in your eye the weight
+of the world rests all upon your tiny shoes but June why are you
+so terrified give me the locust and the flood i'm a Fool for Love
+but i'm kneeling in Shit Creek tonight.
+
+thierry: upright & electric bass; flüffy: marching drum; kate:
+violin; jennifer: voice; moya: piano & Crumar organ; bruce: drums;
+gramble: scrap metal; scott: electric guitar & vocals. words & music
+by scott chernoff.
+
+sign of judgment. yes sign of judgment yes sign of judgment yes
+sign of judgment time ain't long i don't like old Satan none of his
+tempting charms cheats you at your Jesus now and roll you in his
+arms yes sign of judgment yes sign of judgment yes sign of judgment
+time ain't long i don't like old Satan nothing he say or do tell
+one lie to hurt us all and two to make it true yes sign of judgment
+yes sign of judgment yes sign of judgment time ain't long.
+
+norsola: cello; jennifer: voice; efrim: electric guitar & harmonium;
+js: upright bass; thalia: voice; scott: acoustic guitar & vocals.
+words & music by kid prince moore (traditional).
+
+Lynn Canyon wedding song. well i never cared very much for the west
+except for you there who i loved the best now please take me back
+east i wrote your name in silver and green where Love and Hate Play
+with Fate and History now please take me back east oh please take
+me back east we came to the Canyon across the calm and the wild
+from Mountain to Mountain and Island to aisle i came for Abel you
+came for Cain and we danced on the tables between the cradle and
+the grave and i never cared very much for the west but i will be
+there in my Sunday best if you will wear your red wedding dress
+then please take me back east oh please take me back east.
+
+flüffy: saw; jennifer: voice; moya: piano & Crumar organ; dmc: viola
+& saw; js: upright bass & voice; scott: acoustic guitar & vocals.
+words & music by scott chernoff.
+
+coda. instrumental.
+
+flüffy: metal heating duct; michel: banjo; dog & voice drone recorded
+by julian evans on video tape at Lynn Canyon 2002; mixed by scott
+& harris newman in montréal 2003.
+
+You Can't Win. you with your railroad bones and you with your
+golden-spiked skin you with the wind in your clothes and your hat
+like a house caving in oh man You Can't Win oh man You Can't Win
+oh man You Can't Win when the Angel of Light is at the window with
+the Sanctimonious Kid when you're down and out from Sacramento on
+the New York City skids oh man You Can't Win oh man You Can't Win
+oh man You Can't Win when the East River is groaning all below the
+Williamsburg Bridge when airplanes are exploding in the skyline's
+falling limbs oh man You Can't Win oh man You Can't Win oh man You
+Can't Win when the Swans are at your throat with their busted burning
+wings when they squeal and scream and moan at every last ship that
+comes in oh man You Can't Win oh man You Can't Win oh man You Can't
+Win.
+
+flüffy: chor; norsola: choir; jennifer: voice; moya: electric guitars
+& Crumar organ; sam: oud; bruce: drums & marimba; dmc: choir;
+gramble: scrap metal & choir; efrim: electric guitar & choir; js:
+choir; thalia: choir; scott: electric & acoustic guitars, vocals.
+words & music by scott chernoff, on the road from sacramento to
+nyc. september 11 2001, while reading jack black's 1926 hobo memoir
+*You Can't Win* and listening to the music of michael gira..
+
+trouble in mind. trouble in mind i'm blue but i won't be blue all
+the way you know that sun is gonna shine on me someday i'm gonna
+lay my head on some lonesome railroad line and let that big
+motherfucking engine nullify my mind trouble in mind i'm blue but
+i won't be blue all the way you know that sun is gonna shine on me
+someday.
+
+jennifer: voice; sam: electric guitars; chris: electric guitar;
+scott: acoustic guitar & vocals. words & music traditional, as
+learned from a 1960s  recording by sam lightnin' hopkins.
+
+las niñas. instrumental.
+
+andrew mayrs recorded his baby daughter illimani crying while banging
+on a piano in kitsilano, vancouver 2003; screaming children recorded
+by scott at rue roy, montréal 2003. mixed by scott & harris newman
+in montréal 2003.
+
+Miss Peach's Pawnshop. i bought this cross on the island of St.
+Thomas because i wanted jesus on my chest i got this tattoo in New
+Orleans and this one in the Orient for my honeymoon and i got this
+scar somewhere far far away i was drunk as hell and i tried to walk
+home and my hands were broken and i found this prayer in the back
+of a magazine i know it's ripped but it came like this it came like
+this i bought this song at Miss Peach's Pawn with this old overcoat
+and a gold-plated wristwatch and i found this prayer in the back
+of a magazine i know it's ripped but it came like this it came like
+this it came like this it came like this.
+
+norsola: cello; kate: violin; jennifer: voice; moya: electric guitar,
+piano & Crumar organ; dmc: viola; js: upright bass; scott: acoustic
+guitar & vocals. words by hilary peach & scott chernoff; music by
+scott chernoff. this is a bastard adaptation of peach's poem *Tattoo*
+from her album, *Poems Only Dogs Can Hear*.
+
+buffaloed at Wounded Knee/the weeping winds. instrumental. (for
+l.p. & a.i.m. and a pox on mean-spirited sons of bitches everywhere).
+
+flüffy: saws; dmc: saws.
+
+no love lost. good-bye old friend it's time to go this is the end
+of all that we know when you worship in the house of burden i worship
+in the house of blame as you learn to let your spirit burn i yearn
+to be a flame to be a flame and there's no love no love lost as
+there is the righteous there is the wrong and there is the night
+which is betrayed by the dawn and there's no love no love lost as
+there is the sinner there is the god and there is the singer profaned
+by the song and there's no love no love lost there's no love no
+love lost.
+
+thierry: electric bass; flüffy: bowed piano & saws; jennifer: voice;
+moya: piano; michel: banjo; thalia: voice; scott: acoustic guitar,
+bowed piano & vocals. words & music by scott chernoff.
+
+songs from the basement. there's a song for hatred and a song for
+love a song for betrayal and a song for trust there's a song for
+what's fated a song for dumb luck a song for what's sacred and one
+for what's fucked there's a song which is prayed a song which is
+cussed these songs from the basement which play unto us.
+
+norsola: cello; kate: violin; jennifer: voice; sam: electric guitars;
+bruce: marimba & cymbals; dmc: viola; efrim: harmonium; garfield
+lamb: knife; scott: acoustic guitar & vocals. words & music by scott
+chernoff.  mixed by howard bilerman at the hotel... ...after the
+storm had all blown by...
+
+recorded by howard bilerman, efrim & scott at mom & pop sounds in
+the hotel2tango, mile end, montréal 2003. except where noted,
+everything was mixed by howard bilerman & molasses at MixArt in
+notre-dame-de-grace, montréal 2003. mastered by harris newman in
+little italy, montréal 2003 & 2004. all songs c&p SOCAN 2004, except
+*sign of judgment* & *trouble in mind* which are in the public
+domain. hilary peach's poem *Tattoo*, which inspired *Miss Peach's
+Pawnshop*, is c&p hilary peach 2003. all music arranged by molasses,
+design and hand-lettering by slgc ink. in tokyo. photography by dmc
+& norsola. layout by slgc ink. with assistance by sean o'hara. all
+love to those who helped: sohara & gary at alien8 recordings; howard
+bilerman; cada del popolo; don & ian at constellation; eric craven;
+julian evans; michael feuerstack; aidan girt; lea grahovac; taras
+grescoe; garfield lamb; tim mahony; andrew mayrs, vally mendoza &
+illimani mendoza-mayrs; boris & nicolas at MixArt; harris newman,
+*who is never wrong*; hilary peach; sugar & smokey; and all the
+cars and dogs... xoxo...
diff --git a/txt/tweets.txt b/txt/tweets.txt
new file mode 100644
index 00000000..7d8f7e71
--- /dev/null
+++ b/txt/tweets.txt
@@ -0,0 +1,29 @@
+triangle forum
+https://twitter.com/g0m/status/963890156477603841
+
+damn ya ass fat what's ya pronouns?
+https://twitter.com/msbigmilk/status/1291218055939448833
+
+afab
+https://twitter.com/ErisGael/status/1257524779046907904
+
+chaos emeralds
+https://twitter.com/ErisGael/status/1190147736244490240
+
+might as well salivate
+https://twitter.com/prawn_meat/status/857444039833944064
+
+notifications
+https://twitter.com/WTMMP/status/1259694226595610629
+
+all robot & computers
+https://twitter.com/cryptcrier/status/1238339418160680960
+
+the wet world
+https://twitter.com/TragicAllyHere/status/1137187876507140098
+
+a purchase?
+https://twitter.com/pant_leg/status/1027693563604230144
+
+influencer
+https://twitter.com/witchpuppy/status/974690828257054721
diff --git a/www/causal.agency/.gitignore b/www/causal.agency/.gitignore
new file mode 100644
index 00000000..7935a3c1
--- /dev/null
+++ b/www/causal.agency/.gitignore
@@ -0,0 +1,3 @@
+*.html
+scheme.css
+scheme.png
diff --git a/www/causal.agency/Makefile b/www/causal.agency/Makefile
new file mode 100644
index 00000000..fdb1748e
--- /dev/null
+++ b/www/causal.agency/Makefile
@@ -0,0 +1,31 @@
+WEBROOT = /usr/local/www/causal.agency
+
+FILES = index.html scheme.png
+
+all: ${FILES}
+
+install: ${FILES}
+	install -C -m 644 ${FILES} ${WEBROOT}
+
+INCLUDES = scheme.css torus.html play.html catgirl.html scheme.html
+
+index.html: index.html.in index.sed ${INCLUDES}
+	sed -f index.sed index.html.in > index.html
+
+FLAGS.torus.pty = -n -h 25
+FLAGS.scheme.pty = -n -h 10
+FLAGS.play.pty = -h 16
+
+.SUFFIXES: .html .pty
+
+.pty.html:
+	shotty ${FLAGS.$<} $< > $@
+
+scheme.css:
+	scheme -s > scheme.css
+
+scheme.png:
+	scheme -g > scheme.png
+
+clean:
+	rm -f ${FILES} ${INCLUDES}
diff --git a/www/causal.agency/catgirl.pty b/www/causal.agency/catgirl.pty
new file mode 100644
index 00000000..651e83db
--- /dev/null
+++ b/www/causal.agency/catgirl.pty
@@ -0,0 +1,97 @@
+[?1049h(B[?7h[?1h=[?1004h[?2004h(B(B 0 <network> 
(Bcatgirl(B is GPLv3 fwee softwawe ^w^  code is avaiwable fwomhttps://git.causal.agency/catgirl

+Traveling...

+]2;chat.freenode.net <network>
+-adams.freenode.net- *** Looking up your hostname...

+-adams.freenode.net- *** Checking Ident

+-adams.freenode.net- *** Couldn't look up your hostname

+(B
+-adams.freenode.net- *** No Ident response

+(B
+You arrive in freenode

+
+-(B Welcome to adams.freenode.net. Thanks to ATW Internet Kft

+-(B (http://www.atw.hu) for sponsoring this server!

+-
+-(B ADAMS, DOUGLAS (1952-2001).  Author of The Hitch Hikers Guide

+-(B to the Galaxy and many other witty and humourous books,

+-(B portrayed in his uniquely British irony. He is sorely missed

+
+-(B by many millions of devoted fans. "So long and thanks for all

+-(B the books!"

+-
+-(B Welcome to freenode - supporting the free and open source

+-(B software communities since 1998.

+-
+(B
+-(B By connecting to freenode you indicate that you have read and

+-(B accept our policies and guidelines as set out on https://freenode.net

+-
+(B
+-(B In the event that you observe behaviour that contravenes our policies,

+-(B please notify a volunteer staff member via private message, or send us an

+-(B e-mail to complaints@freenode.net -- we will do our best to address the

+
+-(B situation within a reasonable period of time, and we may request further

+-(B information or, as appropriate, involve other parties such as channel

+  operators

+-(B Group Contacts representing an on-topic group.

+
+-
+-(B freenode runs an open proxy scanner.

+-
+-(B If you are looking for assistance, you may be able to find a list of

+-(B volunteer staff on '/stats p' (shows only on-call staff) or by joining

+-(B #freenode and using the '/who freenode/staff/*' command. You may message

+
+-(B any of us at any time. Please note that freenode predominantly provides

+-(B assistance via private message, and while we have a network channel the

+-(B primary venue for support requests is via private message to a member

+-(B of the volunteer staff team.

+-
+(B
+-(B From time to time, volunteer staff may send server-wide notices relating to

+-(B the project, or the communities that we host. The majority of such notices

+-(B will be sent as wallops, and you can '/mode <yournick> +w' to ensure that you

+-(B do not miss them. Important messages relating to the freenode project,

+  including

+
+-(B notices of upcoming maintenance and other scheduled downtime will be issued

+  as

+-(B global notices.

+-
+-(B Representing an on-topic project? Don't forget to register, more information

+-(B can be found on the https://freenode.net website under "Group Registration".

+-
+(B
+-(B Thank you also to our server sponsors for the sustained support in keeping

+  the

+-(B network going for close to two decades.

+-
+-(B Thank you for using freenode!

+ 1 freenode-connect (2) 
(B]2;freenode <network> (+2!) 1 freenode-connect (2) (B 2 #ascii.town 

+(B
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+catgirl(B arrives in #ascii.town(B

+The sign in #ascii.town(B reads: https://ascii.town public SSH services and IRC

+things <3 AGPL

+In #ascii.town(B are catgirl, gjabell, danopia, larbob, ep, nonlinear, epilys,

+benharri, june, yourfate, josuah

+<catgirl> (B]2;freenode #ascii.town (+2!)
/close 1(B 1(B
<catgirl> (B]2;freenode #ascii.townhello, world!
+ 

+<catgirl> (B(B(1) 
(B* june(B waves
+(B]2;freenode #ascii.town (1)]2;freenode #ascii.town
\ No newline at end of file
diff --git a/www/causal.agency/index.html.in b/www/causal.agency/index.html.in
new file mode 100644
index 00000000..82f2457e
--- /dev/null
+++ b/www/causal.agency/index.html.in
@@ -0,0 +1,125 @@
+<!DOCTYPE html>
+<title>Causal Agency</title>
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<style>
+html {
+	font-family: monospace;
+	color: var(--ansi15);
+	background-color: var(--ansi0);
+}
+body {
+	max-width: 80ch;
+	margin: 2em auto;
+	padding: 0 1ch;
+}
+h1 {
+	font-size: inherit;
+	font-weight: inherit;
+	margin: 1em 0 0;
+}
+p {
+	margin: 0 0 1em 4ch;
+}
+a {
+	color: var(--ansi12);
+	text-decoration: none;
+}
+a:visited {
+	color: var(--ansi13);
+}
+pre {
+	border: 1px dashed var(--ansi8);
+}
+hr {
+	visibility: hidden;
+}
+/* include:scheme.css */
+</style>
+
+<p>
+Hi.
+I make mostly IRC software in C.
+I like FreeBSD and OpenBSD
+but also the GPL.
+You can find me in
+<a href="ircs://chat.freenode.net:6697/#ascii.town">#ascii.town</a>
+on freenode
+or send mail to june@.
+
+<p>
+<a href="https://git.causal.agency">code</a>
+--
+<a href="https://text.causal.agency">words</a>
+
+<h1><a href="https://git.causal.agency/pounce/about">pounce</a></h1>
+<p>
+multi-client IRC bouncer
+
+<h1><a href="https://git.causal.agency/catgirl/about">catgirl</a></h1>
+<p>
+artisanal IRC client
+<p>
+demo: <a href="ssh://chat@ascii.town">ssh chat@ascii.town</a>
+<p>
+<!-- include:catgirl.html -->
+
+<h1><a href="https://git.causal.agency/litterbox/about">litterbox</a></h1>
+<p>
+full-text search IRC logger
+
+<h1><a href="https://git.causal.agency/scooper/about">scooper</a></h1>
+<p>
+web interface for litterbox
+
+<h1><a href="https://git.causal.agency/catsit/about">catsit</a></h1>
+<p>
+process supervisor
+
+<hr>
+
+<h1><a href="https://git.causal.agency/imbox/about">imbox & git-fetch-email</a></h1>
+<p>
+IMAP to mbox
+
+<h1><a href="https://git.causal.agency/notemap/about">notemap</a></h1>
+<p>
+mirror notes to IMAP
+
+<hr>
+
+<h1><a href="https://ascii.town/explore.html">torus</a></h1>
+<p>
+collaborative ASCII art
+<p>
+<a href="ssh://torus@ascii.town">ssh torus@ascii.town</a>
+--
+<a href="https://git.causal.agency/torus">src</a>
+<p>
+<!-- include:torus.html -->
+
+<h1>play</h1>
+<p>
+2048 over SSH
+<p>
+<a href="ssh://play@ascii.town">ssh play@ascii.town</a>
+--
+<a href="https://git.causal.agency/play">src</a>
+<p>
+<!-- include:play.html -->
+
+<hr>
+
+<h1><a href="https://git.causal.agency/cards/about">cards</a></h1>
+<p>
+CARDS.DLL loader for SDL
+
+<h1><a href="bin/scheme.html">scheme</a></h1>
+<p>
+earthy terminal colours
+<p>
+<a href="scheme.png">palette</a>
+<!-- include:scheme.html -->
+
+<h1><a href="bin/">bin</a></h1>
+<p>
+other little tools
diff --git a/www/causal.agency/index.sed b/www/causal.agency/index.sed
new file mode 100644
index 00000000..ea32675f
--- /dev/null
+++ b/www/causal.agency/index.sed
@@ -0,0 +1,5 @@
+/include:scheme[.]css/r scheme.css
+/include:torus[.]html/r torus.html
+/include:play[.]html/r play.html
+/include:catgirl[.]html/r catgirl.html
+/include:scheme[.]html/r scheme.html
diff --git a/www/causal.agency/play.pty b/www/causal.agency/play.pty
new file mode 100644
index 00000000..3da44fb7
--- /dev/null
+++ b/www/causal.agency/play.pty
@@ -0,0 +1,23 @@
+[?12l[?25h[?1h=[?25l[?1h=0

+
+                              Use the arrow keys to

+     .      .      .      .   slide and merge tiles.

+                              Press q to quit.

+                              
+   .      .      .      .   
+                            
+                            
+   .      .      2      2   
+                            
+                            
+   .      .      .      .   
+                            4          2          
+                     
+   4      .      .   
+                               2                    .          
+       
+   .   
+       
+              
+   4      2   
+              
\ No newline at end of file
diff --git a/www/causal.agency/scheme.pty b/www/causal.agency/scheme.pty
new file mode 100644
index 00000000..74be2196
--- /dev/null
+++ b/www/causal.agency/scheme.pty
@@ -0,0 +1,10 @@
+                                                                                

+                                                                                

+                                                                                

+                                                                                

+                                                                                

+                                                                                

+                                                                                

+                                                                                

+                                                                                

+                                                                                
diff --git a/www/causal.agency/torus.pty b/www/causal.agency/torus.pty
new file mode 100644
index 00000000..1e147970
--- /dev/null
+++ b/www/causal.agency/torus.pty
@@ -0,0 +1,774 @@
+[?1h=[?25l┌──────────────────────────────────────────────────────────────────────────────â”│ Welcome to ascii.town!          q quit  Q teleport  m mini-map  ? help       ││                                                                              ││    k       0 1 2 3 4 5 6 7    esc navigation mode     s copy                 ││  y ↑ u                          i insert mode         p paste                ││ h â†âˆ™â†’ l    ) ! @ # $ % ^ &      a insert after        ~ paint color          ││  b ↓ n                          I insert direction    * paint bright         ││    j      8 bright  9 invert    R draw mode           ( paint invert         ││               ` pipette         . line mode         C-a increment            ││ \ fast                          r replace           C-x decrement            ││                                 x erase        HJKLYUBN swap cell            ││                                                                              ││ F1 @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno                          ││ F2  ☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→â†âˆŸâ†”▲▼                                          ││ F3 αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√â¿Â²â–                                            ││ F4 ░▒▓│┤╡╢╖╕╣║╗â•â•œâ•›â”└┴┬├─┼╞╟╚╔╩╦╠â•â•¬â•§â•¨â•¤â•¥â•™â•˜â•’╓╫╪┘┌█▄▌â–â–€                          ││ F5 ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿âŒÂ¬Â½Â¼Â¡Â«Â»                          ││                                                                              ││                                                                              ││ This is AGPLv3 Free Software!                                                ││ Code is available from <https://code.causal.agency/june/torus>.              ││                                                                              ││                                                                              ││ Press ? to open this help again. Press any key to continue...                │└──────────────────────────────────────────────────────────────────────────────┘─[?12l[?25hPress ? for help!│   this space now frees     \ y \ CP437 ☻ ♥♦♣♠ â–‘â–’â–“â–ˆ  (000,000)─────────────────┘   It's Free Real Estate     \ y \  â•’â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â••

+For a good   (0_0) | y | │ ascii.town guestbook │ 

+time, go in                   a                 | y | â•žâ•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•â•¡

+any direction.     RR w         | y | │ - june         

+Pls don't publicg@conferencRRRRuRRRRR.forsale   | y | │ - scott      

+vandal. ThereRRRRRRRRRR           |   | │ - cjm        

+are huuuuuge    helo woldr  RRRRRRRR    r a     | l?| │ - erik       

+tracts of       aaaac  RRRRRR     What's up?j/| │ - swgillespie

+unused land.RRRR       poop   | a▼| │ - dikaiosune

+if u do vandal                 RR       test    | o     - danopia    

+make it funny  but maybe dont   hey  000 sdddddd|   | │ - Connor_____

+PROTIP: Press ESC to escape input modes  q,,,,,,| a   │ - tokenrove          │

+2048 clone: ssh play@ascii.town  <3     aa      | y | │ - mykey      │

+    G O O D B Y E  W I T C H E S . T Osjjj :(   | y | │ - dmrd               │ 

+vim controls, eh?.__________________. Conjor    | y | │ - hiya               │

+ . . . . . . . . |   Out of space?  | Wasj whut?| y | │ - arke 

+. Welcome to. . .|Try typing 1Q, 2Q,| Herj      | y | │ - cmr

+ . . . the TOWN. |3Q or 4Q to travel|   wjoft   | y | │ - Grissess (:D)      │

+  . . . . . . . .|to far-away lands |   ajoo    | y | │ - eternaleye (ipv6)  │

+  .. . . . . . . '------------------'    j test | y | │ - meena      

+  sad   hi friends  hello  cybre! ^Yes?  j ni   | y | │ - b0rk      boy __   │

+ nethack    yo how do i select black??? aj sxD  | y | │ - fbernier     /  '  │

+   boi    clown townjj this is as wired 0j dasdksajklj│ - mguaypaq     |     │

+  *   hot tip: don't ruin shit    *    * j *  / yq/ * │ - rkallos      V     │
+
+
+
+
+
+
+
+
+
+
+
+
(The client tries to check if you'll     j    | y |///│ - fsj         │1

+be able to see all the colours now.)     j    | y |\\\│ - Sylvhem     └──────â”//                                         j    | y |///│ - iliana <3          │\\         ] gas pedal â” this is just \    j    | y |\\\│ - tjk                │//  .__    [ brakes    ┘ to toggle fast nowj    | y |///│ - duckinator   \\  |\     } curly gas pedal   :)          j  elo y |\\\│ (pup@mastodon.social)│//    \    { curly brakes                  j    | y |///│ - bug(@chitter.xyz)  │\\ Looking for something interesting?      j    | y |\\\│ - peidran    // Aside from scattered pages all over,    j    | y |///│ - KitRedgrave\\ there's some development left and up froj    | y |\\\│ - vimtingu  // here.  The bulletin is over to the rightj->  | y |///│ - thomas@touhey.org  │\\ somewhere.  And there's stuff all along j    | y |\\\│ - frewsxcv    \.fr   │// the torus if you follow it around.      j    | y |///│ - NecroTechno <3     │\\                                    \    j    | y |\\\│ - er1n       │// NEW: press m to open the mini-map  _\|       | y |///│ - quantified         │\\      and see what's nearby                   | y |\\\│   @cybre.space       │//                        yoooooo yw!           | y |///│ - nightpool (cybre)  │\\    aaa\]\\y wat           hey vanta          | y |\\\│ - ghosty//    UrFU !d               this is dope       | y |///│ - lynn (@chordbug)   │\\     RTF - chempion!       thx 4 sharing      | y |\\\│ - vantablack         │// This is actually amazing.                    | y |///│ //wxyzzyrd   \\Don't forget to grab the source:             | y |\\\│ //international <3   │//                              hi RC!         | y |///│ - maren              │\\ $ git clone \                                | y |\\\│ - rose               │//   https://code.causal.agency/june/torus.git  | y |///│   (@BLASTPROCESSlNG) │/\/
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| y |~~~│ - grainloom   2

+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| y |~~~│   @cybre.space~~~~~~~~~hi~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| y |~~~│   @slimelia  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| y |~~~│ - jfo~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~jh~~~~| y |~~~│   @Phairupegiont~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| y |~~~│ - chr@cybre.space <3 │~~~~~~~~~~~~ i want to marry lynn ~~~~~~~~~~~~~~| y |~~~│ - elomatreb :3       │~~~~~~~~~~~~~~~~~~~same~~~~~~~~~~~~~~~~~~~~~~~~~| y |~~~│ - minerobber~~~~~~~~~~~~~~~~~~~same tbh ~~~~~~~~~~~~~~~~   ~| y |~~~│   @tilde.town~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ I iiiiikhjk been ~| y |~~~│ - selfsame(~town)    │~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ moving around ~| y |~~~│ - Zhorken@awoo.space │~~~~~~~~~~ I am bad at VIM ~~~~~~ I didnt know ~| y |~~~│ - noiob@awoo.space   │~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ this is~vim~| y |~~~│ -               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~it's vim~| y |~~~│ - aeon~~~~~~~~~~vim is  bad hello lynn~~~~~~~~~ ~~~~~~| y |~~~│ - unascribed         │~~~~~~~~~~(lovely application, this)~~~~~~~~~~~~| y |~~~│ ~ nee (hidamari.blue)│~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| y |~~~│ ~ curiouser          │~~~~~~~~this is as much nethack               ~~| y |~~~│ - clouded  ~~~~~~~~as it is vim  ~~~~~~~~~ sry i cnt drw ~~| y |~~~│ - flacs (@f1ac5)     │~~~~~~~~~~~~~~~~~~~     ~~~~~~~               ~~| y |~~~│ - xenonnsmb (~town)  │~~~~~~~~~~~~~~~~~~~~emacs keys pls~+2~~~~~~~~~~~| y |~~~│ - Jakob              │~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| y |~~~│ - rose!              │~~~~~~~~oh no its vim~~~~~~~~~~whoops~~~~~~~~~~~| y |~~~│   (@mahoushoujorose) │~~~~~~~~help im trapped in a vim factory~~~~~~~~| y |~~~│ - jess (@dogs ~town) │~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~I messed| yq|~~~│ - rachel (@arjache)  │~~~
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  ^^ ^^^  | y |   │ - Falkreon 3

+            Roses are red.                 ^  | y |   │ - mitosis@manhater.io│

+          Violets are blue.                ^  | y |   │ - yrgfm    

+Some people don't think                    ^  | y |   │ - felix   

+                 ascii.town be like it is.    | y |   │ - ndiesslin     

+              But it do!                   ^  | y |   │ - matth             

+                                           ^  | y |   │ - timi        

+ .-.  .-. .-.  .-. .-..-. .----. .-.  .-.  ^  | y |   │ - @andwhatnot2 

+ }  \/  {  \ \/ /  | ' /  } |__}  \ \  /   ^  | y |   │ - Spocky     

+ | {  } |   `-\ }  | . \  } '__}   `-  }      | y |   │ - Kid Iccurus    

+ `-'  `-'     `-'  `-'`-` `----'      -'      | y |   │ - tom@slime.global :3

+                                              | y |   │ -                    │ 

+                                              | y |   │  lr            

+                                              | y |   │ - fennecs (helo)

+                     this poem sd             | y |   │ - @amsomniac (mastod

+                     is factually             | y |   │    on)               

+                   incorrect, but             | y |   │ - maxj     

+                   at least it rhymes         | y |   │   AdamThePhantump was

+                       i love this place      | y |   │   here. Boo! :)      â””â”

+                                              | y |   │   Tsuki "Hello guys !"│

+             now I can't sleep                | y |   │ - @dejawu_           ┌┘

+                                              | y |   │ - @eal  

+                             zzzz             | y |   │   @social.sakamoto.gq

+                                              | y |   │ - frenata            

+we are addicts. we need an intervention       | yq|   │ - banjo            
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
What is this big fucking line of 'y'?        -Ethan    4

+  What is this big fucking line of 'y', you - Profpatsch  └──────â”

+  may ask yourself? It's the torus ouroboros,viv@cybre.space

+  ayying its own lmao.                        | y - dom96 ;)                             | cloin                - dbucklin i went all the way around the       - hiljusti  

+                         ros                   │ - @ercts_mxms  

+             leave some notes on the           │ -f@r4ch0

+           way for your fellow travellersr     │ ffffff               

+                                 ~chr         │ -Harper              
+     this is pretty sweet    │ -@discordalert

+           -meena                             | y |   │ - von   ShadowRZ      world wide web!            (@ShadowRZ@mastodoninternet                    .xyz

+ Your journey begins now.                 |   │ - QBFreak

+                                        y |   │ - @scanlime  +Tuco <3wow this is very exciting       | y |   │   @diode.zone        │-b0rk - zer00              │        O         |   │ - kokakoda│

+  ifdfd\|/(!)  - m3tax         
+o_O|       (!!!!!)    ~~~zgebiit~~~     

+ / \hey b0rk!! - gauntlet

+                           a                            - lastrik
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
                                     - CX    5

+ ######################################     - tomjschwanke 

+#                                    #      - @aliasless     

+ #              Mastodon              #         @awoo.wolfgirl.

+ #  #    engineering

+ # Giving social networks back to you #  - cosine  

+ # Free, decentralized microblogging  #       - stevenleeg

+#            #- steampunc   

+ #      https://joinmastodon.org      #- @blinry

+######################################   - ben@tilde.team          - Alpatron#6158
+   #################################### Calamitous  
+#             #       
+#Witches Town#   │           
+#                                  #                   
+#      Mastodon instance for       #       

+       # queer   feministanarchists #         

+       #         #                     
+#      https://witches.town        #            
+####################################       

+<<<more advertising goes over there<<<          

+   ,-._.~~~^-v^v^v**=>                     

+  /life is better on/ <3                         

+  */a.weirder.earth/*                      

+   \______________/*       love you| yq|   │          │
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    6

+  someone.. feel free to reuse this space.              â””

+  it's uh... i wouldn't feel bad about                 

+  erasing whatever this is                            

+                                 

+            Tidied. The ASCII Janitor.           

+                                                         

+                                          

+      WHATS UP MY GLIB GLOBS?                   

+       W U B A L U B A D U B D U B                       *~~~~~~~~~~~~~~~~~~~~~~~*                   
+     < tilde.town <3s u      >                   
+    *~~~~~~~~~~~~~~~~~~~~~~~*         .
+ < shell in today >          
+           *~~~~~~~~~~~~~~~~*        
+                       < :3 >         i want to avoid
+                       *~~~~*       confusion for new
+                 │ people though
+ this thing is cool                  or make the guestbook

+too bad i   ssss u  u k k                  go AROUNDWAYS        │ 

+             s    u  u kk             to be continued...
+           ssss u  u kk │   │ we need to move the
+              s u  u k k     ├───┘ text below elsewhere └─â”           ssss uuuu k  k  at ascii art│ (Snek petting dude moved to│             -xenonnsmb, from ~town000,009.)  │ 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  7

+                                          

+                                           Shrine of the church 
+                                 empty lot             o o o o 
+          COME ON DOWN               o        oTO ASCII TOWN~~~ o     ..   o                             o   |----    o
+                         o   ||       o
+          uiwiidr                 o    ---     o  of                            o   |.       o                    o   ..       o                          o    \---    oI walked the               o     ,/    oourobouros             o   '     oand all I got was this   o o   o o   lousy t-shirt         o         
+_---.___,---_       | y |  ┌┘    GNU   Emacs â””─â”/             \        │=========================│     /|  |y|  |\          │ https://gnu.org/s/emacs │|       |           │=========================│|       |     <- amazing shirt|  └┠                     ┌─┘|_______|         would i buy it?|   │ Emacs faithfuls  |   │                            |   │                 \|/  │       maybe           │ signqbelow:v   │
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+      │ - [X] nee     8

+ i may prefer vim, but emacs has tetris   │ - [X] byxor

+which is a stellar feature     │ - [X  @amsomniac (   

+honestly makes me like emacs  │     ]mastodon )      │                           │ - [X] tokenrove 
+l  shame it doesn't have  │ - [X] rkallos Gj  a text editor             │ - [X  nephariuz 

+      fucking heretics   SHAME!!!       │ -      evoxel   

+emacs doesn't have :smile though :)  │ - :wq!<CR>           │M         │                      │       li  │                    │ - [M-x] zge     N            │                                |   │                

+o---------o o----o        y |   │              A

+|| |    |                       │              

+|| |    |                       U|         | |    |        │                │ 

+|         | o----o      stop here  │                      │ 

+|         |         │                      │C

+|         | 000129          go down until you  │                      │ 

+|         |                reach        ok  ││ 

+|         |     100              │                      │ 

+o---------o e|   │     q                │S

+ U:%*-  *Tetris*      Top (1,0)      (Tetris Projqctile[rms])
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+      │               9

+    +/////////////////+                   │            

+    //you@cybre.space//          │                   

+    +/////////////////+                     
+/\/\/\/\/\\\/\/\/\/\/\             │                

+   /                     /    │              

+   \ pleroma.soykaf.com  \   │                

+   /     see   the       /             │              

+   \   -   entire   -    \   I miss Karl...  │                   

+   /     fediverse /   

+   \ \            

+    \/\/\/\/\///\/\/\/\/\/   │               │          .      /---------------------\  

+                    |I'm petting the snek.|   

+           |                     |  

+           |        ^U^          |  

+                    \---------------------/  

+       [X] TODO: move this too            \     

+       [ ] TODO: write todo list            @/   

+       [X] TODO: hey, me too               /|   

+                   / \  

+       ##########################  

+       # TODO: advertise things #     |   │      

+       ##########################             | yq|   │     q                │
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+││10

+                       │└──────â”

+                      ││

+            #          ││
+   ##   #       ##    ││
+    ##   #       ##     ││
+          #             ││
+         ####    ││
+                 ##                     ││
+      #   ###   ##     │     a│
+      ## ## ## ##      │(hells         │
+       ###   ### Capouet│  guettbook     │
+lol this is awesome│    s retch│                       │      goals)│                        ││                       ││                       ││                       ││
+                                    ││
+                             │  │
+todo: write todo list    ││   ▓▓
+                          â–’â–’
+                          â–‘â–‘
+                           
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+   (000,011)   W 

+    E 
+         E 
+ 3333333333333333 E 333333333333   3 E 333 3333   3   3 E 3333333333333333 E              E             H                   E 

+                      E           . H  E 

+   vi for the vin! E   E   E  E  E 
+                      E             E             E                            
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  2  iiit  llgwfth                                                       Acme is best editor :P 
+ is that a plan9 reference    
+ is that a Jojo's referenc  .  is that a let's encryp          
+                    d s f            s            ffd hello there            o f    l   eGenre l? d           j
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  p3  x      m        p  p  
+i want to pet a soft girl      p
+and call her by her first nameu
+god, i'm so lonely. b                         l
+You can do it i                         c
+Go out there and talk to peopleg                      @
+Even if it doesnt go anywhere, don'tc
+see it as a loss, see it as practiceo     n
+I believe in you     f     e
+You will be succesful     r     e
+You just have to try...     n              c
+UwU     e
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+    .4y
+o
+u
+r
+d
+a
+                         t
+                              a
+                   .
+f
+             o
+r
+                              wheres area
+                              you   l
+                              going ethere
+                isnothing
+                     downhere
+                       i promise
+   (there is)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+      015 
+ 
+or is there? 
+wow
+  
+you all just 
+bbhello 
+       |   |           
+Rewrite this in Nim for better     
+performance and easier maintenance.
+https://nim-lang.org       
+Sorry, couldn't resist :P         Rewrite it in Idris to prove itscorrectness :P  
+Remove it :P       
+dasdasdasdsa          
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+6write.
+
+jjjjjjjjjjj
+     see,What's that creepingi told you  waaaaayyy over there?-------------------->                              
+                                   
+                    
+                                                                       
+            
+q           
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+7What is this ?:wqq      there istrust me        | yi wrapped                .          
+   
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+8                 

+Ancient legend tells of the treasure

+of the legendary pirate,                     

+Cap'n Jasmine Sharkbait!

+No one knows where its shiny and well shaded

+glory is hidden, and many have perished

+trying to find it.

+
+   stuff is here                     | 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+(000,019)MDG - boi

+
+                                    

+                                             

+                        

+                                            why though

+======================================D

+                  

+
+                (i legitput tape                     | on my       screen
+to do this)you are crazyyou will be seen asa visionaryhello tilde.town hereqweird flex but ok
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+     020         
+please                     stop
+this
+is
+silly

+                                       itllllllllllllll nois                  lovely
+"Root? Where we're going wet
+don't need root!"people havedied tryingdon't try :(
+   don't stop!there's nice artuwujust ahead   
+|||                     vvv 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
                                              | @ | comment space!!! <3(000,021)                                              | @

+                                              | @ |  __________________ 

+                                              > @ > |                  |   

+                                              > @ > | >>> MORE ART >>> | 

+                                              > @ > | (on (001, 021))  |      

+                 #                            > @ > |___________  _____|     

+   ############# #           ##############   | @     

+                                              | @ |       ooh what you drawing

+     /     /     /       /         /          | @ |   just a little landscape

+                                  /           | @ |  nice!! keep it up ^o^

+        /                     /               | @ |   thank you =v=

+                   /              /           | @ 

+           /                /                 | @oh wait hi rose

+                                              | @ |  this is wonderful

+                     /                        | @hi alice!!

+               #### ####                      | @ |                          

+                #######                       | @ |     art restored to its

+         /       ##|##      /                 | @ |     former glory         

+               ####|####                      | @ |  cmon, play nice yall

+                ## |  ##                      | @ |             yeah!

+||||||||||||||||||||\|||||||||||||||||||||||||| @

+#####################\########################| @ |  thanks for watching

+######################\#######################| @ |  i guess it's done now   

+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^| @q|  wonderful :)  might do morer
\ No newline at end of file
diff --git a/www/git.causal.agency/.gitignore b/www/git.causal.agency/.gitignore
new file mode 100644
index 00000000..8d20f25d
--- /dev/null
+++ b/www/git.causal.agency/.gitignore
@@ -0,0 +1,3 @@
+about-filter
+hi
+source-filter
diff --git a/www/git.causal.agency/Makefile b/www/git.causal.agency/Makefile
new file mode 100644
index 00000000..28e08ba5
--- /dev/null
+++ b/www/git.causal.agency/Makefile
@@ -0,0 +1,18 @@
+ETC = /usr/local/etc
+WWW = /usr/local/www/cgit
+LIBEXEC = /usr/local/libexec
+
+BIN = ../../bin
+BINS = about-filter source-filter hi
+
+install: cgitrc custom.css ${BINS}
+	install -m 644 cgitrc ${ETC}
+	install -m 644 custom.css ${WWW}
+	install ${BINS} ${LIBEXEC}
+
+hi: ${BIN}/hi.c
+	${MAKE} -C ${BIN} $@
+	cp ${BIN}/$@ $@
+
+clean:
+	rm -f ${BINS}
diff --git a/www/git.causal.agency/about-filter.sh b/www/git.causal.agency/about-filter.sh
new file mode 100644
index 00000000..d27d2d48
--- /dev/null
+++ b/www/git.causal.agency/about-filter.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+
+options=fragment,man=%N.%S,includes=../tree/%I
+
+case "$1" in
+	(README.[1-9])
+		exec /usr/bin/mandoc -T html -O $options
+		;;
+	(*.[1-9])
+		exec /usr/bin/mandoc -T html -O $options,toc
+		;;
+	(*)
+		exec /usr/local/libexec/hi -l text -f html
+		;;
+esac
diff --git a/www/git.causal.agency/cgit/.gitignore b/www/git.causal.agency/cgit/.gitignore
new file mode 100644
index 00000000..661df346
--- /dev/null
+++ b/www/git.causal.agency/cgit/.gitignore
@@ -0,0 +1,12 @@
+# Files I don't care to see in git-status/commit
+/cgit
+cgit.conf
+CGIT-CFLAGS
+VERSION
+cgitrc.5
+cgitrc.5.fo
+cgitrc.5.html
+cgitrc.5.pdf
+cgitrc.5.xml
+*.o
+*.d
diff --git a/www/git.causal.agency/cgit/.mailmap b/www/git.causal.agency/cgit/.mailmap
new file mode 100644
index 00000000..03b54796
--- /dev/null
+++ b/www/git.causal.agency/cgit/.mailmap
@@ -0,0 +1,10 @@
+Florian Pritz <bluewind@xinu.at> <bluewind@xssn.at>
+Harley Laue <losinggeneration@gmail.com> <losinggeneration@aim.com>
+John Keeping <john@keeping.me.uk> <john@metanate.com>
+Lars Hjemli <hjemli@gmail.com> <larsh@hal-2004.(none)>
+Lars Hjemli <hjemli@gmail.com> <larsh@hatman.(none)>
+Lars Hjemli <hjemli@gmail.com> <larsh@slackbox.hjemli.net>
+Lars Hjemli <hjemli@gmail.com> <larsh@slaptop.hjemli.net>
+Lukas Fleischer <lfleischer@lfos.de> <cgit@cryptocrack.de>
+Lukas Fleischer <lfleischer@lfos.de> <info@cryptocrack.de>
+Stefan Bühler <source@stbuehler.de> <lighttpd@stbuehler.de>
diff --git a/www/git.causal.agency/cgit/AUTHORS b/www/git.causal.agency/cgit/AUTHORS
new file mode 100644
index 00000000..031de338
--- /dev/null
+++ b/www/git.causal.agency/cgit/AUTHORS
@@ -0,0 +1,13 @@
+Maintainer:
+	Jason A. Donenfeld <Jason@zx2c4.com>
+
+Contributors:
+	Jason A. Donenfeld <Jason@zx2c4.com>
+	Lukas Fleischer <cgit@cryptocrack.de>
+	Johan Herland <johan@herland.net>
+	Lars Hjemli <hjemli@gmail.com>
+	Ferry Huberts <ferry.huberts@pelagic.nl>
+	John Keeping <john@keeping.me.uk>
+
+Previous Maintainer:
+	Lars Hjemli <hjemli@gmail.com>
diff --git a/www/git.causal.agency/cgit/COPYING b/www/git.causal.agency/cgit/COPYING
new file mode 100644
index 00000000..d159169d
--- /dev/null
+++ b/www/git.causal.agency/cgit/COPYING
@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/www/git.causal.agency/cgit/Makefile b/www/git.causal.agency/cgit/Makefile
new file mode 100644
index 00000000..49109adc
--- /dev/null
+++ b/www/git.causal.agency/cgit/Makefile
@@ -0,0 +1,170 @@
+all::
+
+CGIT_VERSION = v1.2.3
+CGIT_SCRIPT_NAME = cgit.cgi
+CGIT_SCRIPT_PATH = /var/www/htdocs/cgit
+CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
+CGIT_CONFIG = /etc/cgitrc
+CACHE_ROOT = /var/cache/cgit
+prefix = /usr/local
+libdir = $(prefix)/lib
+filterdir = $(libdir)/cgit/filters
+docdir = $(prefix)/share/doc/cgit
+htmldir = $(docdir)
+pdfdir = $(docdir)
+mandir = $(prefix)/share/man
+SHA1_HEADER = <openssl/sha.h>
+GIT_VER = 2.25.1
+GIT_URL = https://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.xz
+INSTALL = install
+COPYTREE = cp -r
+MAN5_TXT = $(wildcard *.5.txt)
+MAN_TXT  = $(MAN5_TXT)
+DOC_MAN5 = $(patsubst %.txt,%,$(MAN5_TXT))
+DOC_HTML = $(patsubst %.txt,%.html,$(MAN_TXT))
+DOC_PDF  = $(patsubst %.txt,%.pdf,$(MAN_TXT))
+
+ASCIIDOC = asciidoc
+ASCIIDOC_EXTRA =
+ASCIIDOC_HTML = xhtml11
+ASCIIDOC_COMMON = $(ASCIIDOC) $(ASCIIDOC_EXTRA)
+TXT_TO_HTML = $(ASCIIDOC_COMMON) -b $(ASCIIDOC_HTML)
+
+# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.)
+# do not support the 'size specifiers' introduced by C99, namely ll, hh,
+# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t).
+# some C compilers supported these specifiers prior to C99 as an extension.
+#
+# Define HAVE_LINUX_SENDFILE to use sendfile()
+
+#-include config.mak
+
+-include git/config.mak.uname
+#
+# Let the user override the above settings.
+#
+-include cgit.conf
+
+export CGIT_VERSION CGIT_SCRIPT_NAME CGIT_SCRIPT_PATH CGIT_DATA_PATH CGIT_CONFIG CACHE_ROOT
+
+#
+# Define a way to invoke make in subdirs quietly, shamelessly ripped
+# from git.git
+#
+QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
+QUIET_SUBDIR1  =
+
+ifneq ($(findstring w,$(MAKEFLAGS)),w)
+PRINT_DIR = --no-print-directory
+else # "make -w"
+NO_SUBDIR = :
+endif
+
+ifndef V
+	QUIET_SUBDIR0  = +@subdir=
+	QUIET_SUBDIR1  = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
+			 $(MAKE) $(PRINT_DIR) -C $$subdir
+	QUIET_TAGS     = @echo '   ' TAGS $@;
+	export V
+endif
+
+.SUFFIXES:
+
+all:: cgit
+
+cgit:
+	$(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) -f ../cgit.mk ../cgit $(EXTRA_GIT_TARGETS) NO_CURL=1
+
+sparse:
+	$(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) -f ../cgit.mk NO_CURL=1 cgit-sparse
+
+test:
+	@$(MAKE) --no-print-directory cgit EXTRA_GIT_TARGETS=all
+	$(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
+
+install: all
+	$(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
+	$(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
+	$(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
+	$(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
+	$(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
+	$(INSTALL) -m 0644 favicon.ico $(DESTDIR)$(CGIT_DATA_PATH)/favicon.ico
+	$(INSTALL) -m 0644 robots.txt $(DESTDIR)$(CGIT_DATA_PATH)/robots.txt
+	$(INSTALL) -m 0755 -d $(DESTDIR)$(filterdir)
+	$(COPYTREE) filters/* $(DESTDIR)$(filterdir)
+
+install-doc: install-man install-html install-pdf
+
+install-man: doc-man
+	$(INSTALL) -m 0755 -d $(DESTDIR)$(mandir)/man5
+	$(INSTALL) -m 0644 $(DOC_MAN5) $(DESTDIR)$(mandir)/man5
+
+install-html: doc-html
+	$(INSTALL) -m 0755 -d $(DESTDIR)$(htmldir)
+	$(INSTALL) -m 0644 $(DOC_HTML) $(DESTDIR)$(htmldir)
+
+install-pdf: doc-pdf
+	$(INSTALL) -m 0755 -d $(DESTDIR)$(pdfdir)
+	$(INSTALL) -m 0644 $(DOC_PDF) $(DESTDIR)$(pdfdir)
+
+uninstall:
+	rm -f $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
+	rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
+	rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
+	rm -f $(DESTDIR)$(CGIT_DATA_PATH)/favicon.ico
+
+uninstall-doc: uninstall-man uninstall-html uninstall-pdf
+
+uninstall-man:
+	@for i in $(DOC_MAN5); do \
+	    rm -fv $(DESTDIR)$(mandir)/man5/$$i; \
+	done
+
+uninstall-html:
+	@for i in $(DOC_HTML); do \
+	    rm -fv $(DESTDIR)$(htmldir)/$$i; \
+	done
+
+uninstall-pdf:
+	@for i in $(DOC_PDF); do \
+	    rm -fv $(DESTDIR)$(pdfdir)/$$i; \
+	done
+
+doc: doc-man doc-html doc-pdf
+doc-man: doc-man5
+doc-man5: $(DOC_MAN5)
+doc-html: $(DOC_HTML)
+doc-pdf: $(DOC_PDF)
+
+%.5 : %.5.txt
+	a2x -f manpage $<
+
+$(DOC_HTML): %.html : %.txt
+	$(TXT_TO_HTML) -o $@+ $< && \
+	mv $@+ $@
+
+$(DOC_PDF): %.pdf : %.txt
+	a2x -f pdf cgitrc.5.txt
+
+clean: clean-doc
+	$(RM) cgit VERSION CGIT-CFLAGS *.o tags
+	$(RM) -r .deps
+
+cleanall: clean
+	$(MAKE) -C git clean
+
+clean-doc:
+	$(RM) cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo
+
+get-git:
+	curl -L $(GIT_URL) | tar -xJf - && rm -rf git && mv git-$(GIT_VER) git
+
+tags:
+	$(QUIET_TAGS)find . -name '*.[ch]' | xargs ctags
+
+.PHONY: all cgit git get-git
+.PHONY: clean clean-doc cleanall
+.PHONY: doc doc-html doc-man doc-pdf
+.PHONY: install install-doc install-html install-man install-pdf
+.PHONY: tags test
+.PHONY: uninstall uninstall-doc uninstall-html uninstall-man uninstall-pdf
diff --git a/www/git.causal.agency/cgit/README b/www/git.causal.agency/cgit/README
new file mode 100644
index 00000000..7a6b4a40
--- /dev/null
+++ b/www/git.causal.agency/cgit/README
@@ -0,0 +1,99 @@
+cgit - CGI for Git
+==================
+
+This is an attempt to create a fast web interface for the Git SCM, using a
+built-in cache to decrease server I/O pressure.
+
+Installation
+------------
+
+Building cgit involves building a proper version of Git. How to do this
+depends on how you obtained the cgit sources:
+
+a) If you're working in a cloned cgit repository, you first need to
+initialize and update the Git submodule:
+
+    $ git submodule init     # register the Git submodule in .git/config
+    $ $EDITOR .git/config    # if you want to specify a different url for git
+    $ git submodule update   # clone/fetch and checkout correct git version
+
+b) If you're building from a cgit tarball, you can download a proper git
+version like this:
+
+    $ make get-git
+
+When either a) or b) has been performed, you can build and install cgit like
+this:
+
+    $ make
+    $ sudo make install
+
+This will install `cgit.cgi` and `cgit.css` into `/var/www/htdocs/cgit`. You
+can configure this location (and a few other things) by providing a `cgit.conf`
+file (see the Makefile for details).
+
+If you'd like to compile without Lua support, you may use:
+
+    $ make NO_LUA=1
+
+And if you'd like to specify a Lua implementation, you may use:
+
+    $ make LUA_PKGCONFIG=lua5.1
+
+If this is not specified, the Lua implementation will be auto-detected,
+preferring LuaJIT if many are present. Acceptable values are generally "lua",
+"luajit", "lua5.1", and "lua5.2".
+
+
+Dependencies
+------------
+
+* libzip
+* libcrypto (OpenSSL)
+* libssl (OpenSSL)
+* optional: luajit or lua, most reliably used when pkg-config is available
+
+Apache configuration
+--------------------
+
+A new `Directory` section must probably be added for cgit, possibly something
+like this:
+
+    <Directory "/var/www/htdocs/cgit/">
+        AllowOverride None
+        Options +ExecCGI
+        Order allow,deny
+        Allow from all
+    </Directory>
+
+
+Runtime configuration
+---------------------
+
+The file `/etc/cgitrc` is read by cgit before handling a request. In addition
+to runtime parameters, this file may also contain a list of repositories
+displayed by cgit (see `cgitrc.5.txt` for further details).
+
+The cache
+---------
+
+When cgit is invoked it looks for a cache file matching the request and
+returns it to the client. If no such cache file exists (or if it has expired),
+the content for the request is written into the proper cache file before the
+file is returned.
+
+If the cache file has expired but cgit is unable to obtain a lock for it, the
+stale cache file is returned to the client. This is done to favour page
+throughput over page freshness.
+
+The generated content contains the complete response to the client, including
+the HTTP headers `Modified` and `Expires`.
+
+Online presence
+---------------
+
+* The cgit homepage is hosted by cgit at <https://git.zx2c4.com/cgit/about/>
+
+* Patches, bug reports, discussions and support should go to the cgit
+  mailing list: <cgit@lists.zx2c4.com>. To sign up, visit
+  <https://lists.zx2c4.com/mailman/listinfo/cgit>
diff --git a/www/git.causal.agency/cgit/cache.c b/www/git.causal.agency/cgit/cache.c
new file mode 100644
index 00000000..2c70be78
--- /dev/null
+++ b/www/git.causal.agency/cgit/cache.c
@@ -0,0 +1,468 @@
+/* cache.c: cache management
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ *
+ *
+ * The cache is just a directory structure where each file is a cache slot,
+ * and each filename is based on the hash of some key (e.g. the cgit url).
+ * Each file contains the full key followed by the cached content for that
+ * key.
+ *
+ */
+
+#include "cgit.h"
+#include "cache.h"
+#include "html.h"
+#ifdef HAVE_LINUX_SENDFILE
+#include <sys/sendfile.h>
+#endif
+
+#define CACHE_BUFSIZE (1024 * 4)
+
+struct cache_slot {
+	const char *key;
+	size_t keylen;
+	int ttl;
+	cache_fill_fn fn;
+	int cache_fd;
+	int lock_fd;
+	int stdout_fd;
+	const char *cache_name;
+	const char *lock_name;
+	int match;
+	struct stat cache_st;
+	int bufsize;
+	char buf[CACHE_BUFSIZE];
+};
+
+/* Open an existing cache slot and fill the cache buffer with
+ * (part of) the content of the cache file. Return 0 on success
+ * and errno otherwise.
+ */
+static int open_slot(struct cache_slot *slot)
+{
+	char *bufz;
+	ssize_t bufkeylen = -1;
+
+	slot->cache_fd = open(slot->cache_name, O_RDONLY);
+	if (slot->cache_fd == -1)
+		return errno;
+
+	if (fstat(slot->cache_fd, &slot->cache_st))
+		return errno;
+
+	slot->bufsize = xread(slot->cache_fd, slot->buf, sizeof(slot->buf));
+	if (slot->bufsize < 0)
+		return errno;
+
+	bufz = memchr(slot->buf, 0, slot->bufsize);
+	if (bufz)
+		bufkeylen = bufz - slot->buf;
+
+	if (slot->key)
+		slot->match = bufkeylen == slot->keylen &&
+		    !memcmp(slot->key, slot->buf, bufkeylen + 1);
+
+	return 0;
+}
+
+/* Close the active cache slot */
+static int close_slot(struct cache_slot *slot)
+{
+	int err = 0;
+	if (slot->cache_fd > 0) {
+		if (close(slot->cache_fd))
+			err = errno;
+		else
+			slot->cache_fd = -1;
+	}
+	return err;
+}
+
+/* Print the content of the active cache slot (but skip the key). */
+static int print_slot(struct cache_slot *slot)
+{
+#ifdef HAVE_LINUX_SENDFILE
+	off_t start_off;
+	int ret;
+
+	start_off = slot->keylen + 1;
+
+	do {
+		ret = sendfile(STDOUT_FILENO, slot->cache_fd, &start_off,
+				slot->cache_st.st_size - start_off);
+		if (ret < 0) {
+			if (errno == EAGAIN || errno == EINTR)
+				continue;
+			return errno;
+		}
+		return 0;
+	} while (1);
+#else
+	ssize_t i, j;
+
+	i = lseek(slot->cache_fd, slot->keylen + 1, SEEK_SET);
+	if (i != slot->keylen + 1)
+		return errno;
+
+	do {
+		i = j = xread(slot->cache_fd, slot->buf, sizeof(slot->buf));
+		if (i > 0)
+			j = xwrite(STDOUT_FILENO, slot->buf, i);
+	} while (i > 0 && j == i);
+
+	if (i < 0 || j != i)
+		return errno;
+	else
+		return 0;
+#endif
+}
+
+/* Check if the slot has expired */
+static int is_expired(struct cache_slot *slot)
+{
+	if (slot->ttl < 0)
+		return 0;
+	else
+		return slot->cache_st.st_mtime + slot->ttl * 60 < time(NULL);
+}
+
+/* Check if the slot has been modified since we opened it.
+ * NB: If stat() fails, we pretend the file is modified.
+ */
+static int is_modified(struct cache_slot *slot)
+{
+	struct stat st;
+
+	if (stat(slot->cache_name, &st))
+		return 1;
+	return (st.st_ino != slot->cache_st.st_ino ||
+		st.st_mtime != slot->cache_st.st_mtime ||
+		st.st_size != slot->cache_st.st_size);
+}
+
+/* Close an open lockfile */
+static int close_lock(struct cache_slot *slot)
+{
+	int err = 0;
+	if (slot->lock_fd > 0) {
+		if (close(slot->lock_fd))
+			err = errno;
+		else
+			slot->lock_fd = -1;
+	}
+	return err;
+}
+
+/* Create a lockfile used to store the generated content for a cache
+ * slot, and write the slot key + \0 into it.
+ * Returns 0 on success and errno otherwise.
+ */
+static int lock_slot(struct cache_slot *slot)
+{
+	struct flock lock = {
+		.l_type = F_WRLCK,
+		.l_whence = SEEK_SET,
+		.l_start = 0,
+		.l_len = 0,
+	};
+
+	slot->lock_fd = open(slot->lock_name, O_RDWR | O_CREAT,
+			     S_IRUSR | S_IWUSR);
+	if (slot->lock_fd == -1)
+		return errno;
+	if (fcntl(slot->lock_fd, F_SETLK, &lock) < 0) {
+		int saved_errno = errno;
+		close(slot->lock_fd);
+		slot->lock_fd = -1;
+		return saved_errno;
+	}
+	if (xwrite(slot->lock_fd, slot->key, slot->keylen + 1) < 0)
+		return errno;
+	return 0;
+}
+
+/* Release the current lockfile. If `replace_old_slot` is set the
+ * lockfile replaces the old cache slot, otherwise the lockfile is
+ * just deleted.
+ */
+static int unlock_slot(struct cache_slot *slot, int replace_old_slot)
+{
+	int err;
+
+	if (replace_old_slot)
+		err = rename(slot->lock_name, slot->cache_name);
+	else
+		err = unlink(slot->lock_name);
+
+	/* Restore stdout and close the temporary FD. */
+	if (slot->stdout_fd >= 0) {
+		dup2(slot->stdout_fd, STDOUT_FILENO);
+		close(slot->stdout_fd);
+		slot->stdout_fd = -1;
+	}
+
+	if (err)
+		return errno;
+
+	return 0;
+}
+
+/* Generate the content for the current cache slot by redirecting
+ * stdout to the lock-fd and invoking the callback function
+ */
+static int fill_slot(struct cache_slot *slot)
+{
+	/* Preserve stdout */
+	slot->stdout_fd = dup(STDOUT_FILENO);
+	if (slot->stdout_fd == -1)
+		return errno;
+
+	/* Redirect stdout to lockfile */
+	if (dup2(slot->lock_fd, STDOUT_FILENO) == -1)
+		return errno;
+
+	/* Generate cache content */
+	slot->fn();
+
+	/* Make sure any buffered data is flushed to the file */
+	if (fflush(stdout))
+		return errno;
+
+	/* update stat info */
+	if (fstat(slot->lock_fd, &slot->cache_st))
+		return errno;
+
+	return 0;
+}
+
+/* Crude implementation of 32-bit FNV-1 hash algorithm,
+ * see http://www.isthe.com/chongo/tech/comp/fnv/ for details
+ * about the magic numbers.
+ */
+#define FNV_OFFSET 0x811c9dc5
+#define FNV_PRIME  0x01000193
+
+unsigned long hash_str(const char *str)
+{
+	unsigned long h = FNV_OFFSET;
+	unsigned char *s = (unsigned char *)str;
+
+	if (!s)
+		return h;
+
+	while (*s) {
+		h *= FNV_PRIME;
+		h ^= *s++;
+	}
+	return h;
+}
+
+static int process_slot(struct cache_slot *slot)
+{
+	int err;
+
+	err = open_slot(slot);
+	if (!err && slot->match) {
+		if (is_expired(slot)) {
+			if (!lock_slot(slot)) {
+				/* If the cachefile has been replaced between
+				 * `open_slot` and `lock_slot`, we'll just
+				 * serve the stale content from the original
+				 * cachefile. This way we avoid pruning the
+				 * newly generated slot. The same code-path
+				 * is chosen if fill_slot() fails for some
+				 * reason.
+				 *
+				 * TODO? check if the new slot contains the
+				 * same key as the old one, since we would
+				 * prefer to serve the newest content.
+				 * This will require us to open yet another
+				 * file-descriptor and read and compare the
+				 * key from the new file, so for now we're
+				 * lazy and just ignore the new file.
+				 */
+				if (is_modified(slot) || fill_slot(slot)) {
+					unlock_slot(slot, 0);
+					close_lock(slot);
+				} else {
+					close_slot(slot);
+					unlock_slot(slot, 1);
+					slot->cache_fd = slot->lock_fd;
+				}
+			}
+		}
+		if ((err = print_slot(slot)) != 0) {
+			cache_log("[cgit] error printing cache %s: %s (%d)\n",
+				  slot->cache_name,
+				  strerror(err),
+				  err);
+		}
+		close_slot(slot);
+		return err;
+	}
+
+	/* If the cache slot does not exist (or its key doesn't match the
+	 * current key), lets try to create a new cache slot for this
+	 * request. If this fails (for whatever reason), lets just generate
+	 * the content without caching it and fool the caller to believe
+	 * everything worked out (but print a warning on stdout).
+	 */
+
+	close_slot(slot);
+	if ((err = lock_slot(slot)) != 0) {
+		cache_log("[cgit] Unable to lock slot %s: %s (%d)\n",
+			  slot->lock_name, strerror(err), err);
+		slot->fn();
+		return 0;
+	}
+
+	if ((err = fill_slot(slot)) != 0) {
+		cache_log("[cgit] Unable to fill slot %s: %s (%d)\n",
+			  slot->lock_name, strerror(err), err);
+		unlock_slot(slot, 0);
+		close_lock(slot);
+		slot->fn();
+		return 0;
+	}
+	// We've got a valid cache slot in the lock file, which
+	// is about to replace the old cache slot. But if we
+	// release the lockfile and then try to open the new cache
+	// slot, we might get a race condition with a concurrent
+	// writer for the same cache slot (with a different key).
+	// Lets avoid such a race by just printing the content of
+	// the lock file.
+	slot->cache_fd = slot->lock_fd;
+	unlock_slot(slot, 1);
+	if ((err = print_slot(slot)) != 0) {
+		cache_log("[cgit] error printing cache %s: %s (%d)\n",
+			  slot->cache_name,
+			  strerror(err),
+			  err);
+	}
+	close_slot(slot);
+	return err;
+}
+
+/* Print cached content to stdout, generate the content if necessary. */
+int cache_process(int size, const char *path, const char *key, int ttl,
+		  cache_fill_fn fn)
+{
+	unsigned long hash;
+	int i;
+	struct strbuf filename = STRBUF_INIT;
+	struct strbuf lockname = STRBUF_INIT;
+	struct cache_slot slot;
+	int result;
+
+	/* If the cache is disabled, just generate the content */
+	if (size <= 0 || ttl == 0) {
+		fn();
+		return 0;
+	}
+
+	/* Verify input, calculate filenames */
+	if (!path) {
+		cache_log("[cgit] Cache path not specified, caching is disabled\n");
+		fn();
+		return 0;
+	}
+	if (!key)
+		key = "";
+	hash = hash_str(key) % size;
+	strbuf_addstr(&filename, path);
+	strbuf_ensure_end(&filename, '/');
+	for (i = 0; i < 8; i++) {
+		strbuf_addf(&filename, "%x", (unsigned char)(hash & 0xf));
+		hash >>= 4;
+	}
+	strbuf_addbuf(&lockname, &filename);
+	strbuf_addstr(&lockname, ".lock");
+	slot.fn = fn;
+	slot.ttl = ttl;
+	slot.stdout_fd = -1;
+	slot.cache_name = filename.buf;
+	slot.lock_name = lockname.buf;
+	slot.key = key;
+	slot.keylen = strlen(key);
+	result = process_slot(&slot);
+
+	strbuf_release(&filename);
+	strbuf_release(&lockname);
+	return result;
+}
+
+/* Return a strftime formatted date/time
+ * NB: the result from this function is to shared memory
+ */
+static char *sprintftime(const char *format, time_t time)
+{
+	static char buf[64];
+	struct tm *tm;
+
+	if (!time)
+		return NULL;
+	tm = gmtime(&time);
+	strftime(buf, sizeof(buf)-1, format, tm);
+	return buf;
+}
+
+int cache_ls(const char *path)
+{
+	DIR *dir;
+	struct dirent *ent;
+	int err = 0;
+	struct cache_slot slot = { NULL };
+	struct strbuf fullname = STRBUF_INIT;
+	size_t prefixlen;
+
+	if (!path) {
+		cache_log("[cgit] cache path not specified\n");
+		return -1;
+	}
+	dir = opendir(path);
+	if (!dir) {
+		err = errno;
+		cache_log("[cgit] unable to open path %s: %s (%d)\n",
+			  path, strerror(err), err);
+		return err;
+	}
+	strbuf_addstr(&fullname, path);
+	strbuf_ensure_end(&fullname, '/');
+	prefixlen = fullname.len;
+	while ((ent = readdir(dir)) != NULL) {
+		if (strlen(ent->d_name) != 8)
+			continue;
+		strbuf_setlen(&fullname, prefixlen);
+		strbuf_addstr(&fullname, ent->d_name);
+		slot.cache_name = fullname.buf;
+		if ((err = open_slot(&slot)) != 0) {
+			cache_log("[cgit] unable to open path %s: %s (%d)\n",
+				  fullname.buf, strerror(err), err);
+			continue;
+		}
+		htmlf("%s %s %10"PRIuMAX" %s\n",
+		      fullname.buf,
+		      sprintftime("%Y-%m-%d %H:%M:%S",
+				  slot.cache_st.st_mtime),
+		      (uintmax_t)slot.cache_st.st_size,
+		      slot.buf);
+		close_slot(&slot);
+	}
+	closedir(dir);
+	strbuf_release(&fullname);
+	return 0;
+}
+
+/* Print a message to stdout */
+void cache_log(const char *format, ...)
+{
+	va_list args;
+	va_start(args, format);
+	vfprintf(stderr, format, args);
+	va_end(args);
+}
+
diff --git a/www/git.causal.agency/cgit/cache.h b/www/git.causal.agency/cgit/cache.h
new file mode 100644
index 00000000..470da4fc
--- /dev/null
+++ b/www/git.causal.agency/cgit/cache.h
@@ -0,0 +1,37 @@
+/*
+ * Since git has it's own cache.h which we include,
+ * lets test on CGIT_CACHE_H to avoid confusion
+ */
+
+#ifndef CGIT_CACHE_H
+#define CGIT_CACHE_H
+
+typedef void (*cache_fill_fn)(void);
+
+
+/* Print cached content to stdout, generate the content if necessary.
+ *
+ * Parameters
+ *   size    max number of cache files
+ *   path    directory used to store cache files
+ *   key     the key used to lookup cache files
+ *   ttl     max cache time in seconds for this key
+ *   fn      content generator function for this key
+ *
+ * Return value
+ *   0 indicates success, everything else is an error
+ */
+extern int cache_process(int size, const char *path, const char *key, int ttl,
+			 cache_fill_fn fn);
+
+
+/* List info about all cache entries on stdout */
+extern int cache_ls(const char *path);
+
+/* Print a message to stdout */
+__attribute__((format (printf,1,2)))
+extern void cache_log(const char *format, ...);
+
+extern unsigned long hash_str(const char *str);
+
+#endif /* CGIT_CACHE_H */
diff --git a/www/git.causal.agency/cgit/cgit.c b/www/git.causal.agency/cgit/cgit.c
new file mode 100644
index 00000000..c4320f04
--- /dev/null
+++ b/www/git.causal.agency/cgit/cgit.c
@@ -0,0 +1,1112 @@
+/* cgit.c: cgi for the git scm
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "cache.h"
+#include "cmd.h"
+#include "configfile.h"
+#include "html.h"
+#include "ui-shared.h"
+#include "ui-stats.h"
+#include "ui-blob.h"
+#include "ui-summary.h"
+#include "scan-tree.h"
+
+const char *cgit_version = CGIT_VERSION;
+
+__attribute__((constructor))
+static void constructor_environment()
+{
+	/* Do not look in /etc/ for gitconfig and gitattributes. */
+	setenv("GIT_CONFIG_NOSYSTEM", "1", 1);
+	setenv("GIT_ATTR_NOSYSTEM", "1", 1);
+	unsetenv("HOME");
+	unsetenv("XDG_CONFIG_HOME");
+}
+
+static void add_mimetype(const char *name, const char *value)
+{
+	struct string_list_item *item;
+
+	item = string_list_insert(&ctx.cfg.mimetypes, name);
+	item->util = xstrdup(value);
+}
+
+static void process_cached_repolist(const char *path);
+
+static void repo_config(struct cgit_repo *repo, const char *name, const char *value)
+{
+	const char *path;
+	struct string_list_item *item;
+
+	if (!strcmp(name, "name"))
+		repo->name = xstrdup(value);
+	else if (!strcmp(name, "clone-url"))
+		repo->clone_url = xstrdup(value);
+	else if (!strcmp(name, "desc"))
+		repo->desc = xstrdup(value);
+	else if (!strcmp(name, "owner"))
+		repo->owner = xstrdup(value);
+	else if (!strcmp(name, "homepage"))
+		repo->homepage = xstrdup(value);
+	else if (!strcmp(name, "defbranch"))
+		repo->defbranch = xstrdup(value);
+	else if (!strcmp(name, "extra-head-content"))
+		repo->extra_head_content = xstrdup(value);
+	else if (!strcmp(name, "snapshots"))
+		repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
+	else if (!strcmp(name, "enable-blame"))
+		repo->enable_blame = atoi(value);
+	else if (!strcmp(name, "enable-commit-graph"))
+		repo->enable_commit_graph = atoi(value);
+	else if (!strcmp(name, "enable-log-filecount"))
+		repo->enable_log_filecount = atoi(value);
+	else if (!strcmp(name, "enable-log-linecount"))
+		repo->enable_log_linecount = atoi(value);
+	else if (!strcmp(name, "enable-remote-branches"))
+		repo->enable_remote_branches = atoi(value);
+	else if (!strcmp(name, "enable-subject-links"))
+		repo->enable_subject_links = atoi(value);
+	else if (!strcmp(name, "enable-html-serving"))
+		repo->enable_html_serving = atoi(value);
+	else if (!strcmp(name, "branch-sort")) {
+		if (!strcmp(value, "age"))
+			repo->branch_sort = 1;
+		if (!strcmp(value, "name"))
+			repo->branch_sort = 0;
+	} else if (!strcmp(name, "commit-sort")) {
+		if (!strcmp(value, "date"))
+			repo->commit_sort = 1;
+		if (!strcmp(value, "topo"))
+			repo->commit_sort = 2;
+	} else if (!strcmp(name, "max-stats"))
+		repo->max_stats = cgit_find_stats_period(value, NULL);
+	else if (!strcmp(name, "module-link"))
+		repo->module_link= xstrdup(value);
+	else if (skip_prefix(name, "module-link.", &path)) {
+		item = string_list_append(&repo->submodules, xstrdup(path));
+		item->util = xstrdup(value);
+	} else if (!strcmp(name, "section"))
+		repo->section = xstrdup(value);
+	else if (!strcmp(name, "snapshot-prefix"))
+		repo->snapshot_prefix = xstrdup(value);
+	else if (!strcmp(name, "readme") && value != NULL) {
+		if (repo->readme.items == ctx.cfg.readme.items)
+			memset(&repo->readme, 0, sizeof(repo->readme));
+		string_list_append(&repo->readme, xstrdup(value));
+	} else if (!strcmp(name, "logo") && value != NULL)
+		repo->logo = xstrdup(value);
+	else if (!strcmp(name, "logo-link") && value != NULL)
+		repo->logo_link = xstrdup(value);
+	else if (!strcmp(name, "hide"))
+		repo->hide = atoi(value);
+	else if (!strcmp(name, "ignore"))
+		repo->ignore = atoi(value);
+	else if (ctx.cfg.enable_filter_overrides) {
+		if (!strcmp(name, "about-filter"))
+			repo->about_filter = cgit_new_filter(value, ABOUT);
+		else if (!strcmp(name, "commit-filter"))
+			repo->commit_filter = cgit_new_filter(value, COMMIT);
+		else if (!strcmp(name, "source-filter"))
+			repo->source_filter = cgit_new_filter(value, SOURCE);
+		else if (!strcmp(name, "email-filter"))
+			repo->email_filter = cgit_new_filter(value, EMAIL);
+		else if (!strcmp(name, "owner-filter"))
+			repo->owner_filter = cgit_new_filter(value, OWNER);
+	}
+}
+
+static void config_cb(const char *name, const char *value)
+{
+	const char *arg;
+
+	if (!strcmp(name, "section"))
+		ctx.cfg.section = xstrdup(value);
+	else if (!strcmp(name, "repo.url"))
+		ctx.repo = cgit_add_repo(value);
+	else if (ctx.repo && !strcmp(name, "repo.path"))
+		ctx.repo->path = trim_end(value, '/');
+	else if (ctx.repo && skip_prefix(name, "repo.", &arg))
+		repo_config(ctx.repo, arg, value);
+	else if (!strcmp(name, "readme"))
+		string_list_append(&ctx.cfg.readme, xstrdup(value));
+	else if (!strcmp(name, "root-title"))
+		ctx.cfg.root_title = xstrdup(value);
+	else if (!strcmp(name, "root-desc"))
+		ctx.cfg.root_desc = xstrdup(value);
+	else if (!strcmp(name, "root-readme"))
+		ctx.cfg.root_readme = xstrdup(value);
+	else if (!strcmp(name, "css"))
+		ctx.cfg.css = xstrdup(value);
+	else if (!strcmp(name, "favicon"))
+		ctx.cfg.favicon = xstrdup(value);
+	else if (!strcmp(name, "footer"))
+		ctx.cfg.footer = xstrdup(value);
+	else if (!strcmp(name, "head-include"))
+		ctx.cfg.head_include = xstrdup(value);
+	else if (!strcmp(name, "header"))
+		ctx.cfg.header = xstrdup(value);
+	else if (!strcmp(name, "logo"))
+		ctx.cfg.logo = xstrdup(value);
+	else if (!strcmp(name, "logo-link"))
+		ctx.cfg.logo_link = xstrdup(value);
+	else if (!strcmp(name, "module-link"))
+		ctx.cfg.module_link = xstrdup(value);
+	else if (!strcmp(name, "strict-export"))
+		ctx.cfg.strict_export = xstrdup(value);
+	else if (!strcmp(name, "virtual-root"))
+		ctx.cfg.virtual_root = ensure_end(value, '/');
+	else if (!strcmp(name, "noplainemail"))
+		ctx.cfg.noplainemail = atoi(value);
+	else if (!strcmp(name, "noheader"))
+		ctx.cfg.noheader = atoi(value);
+	else if (!strcmp(name, "snapshots"))
+		ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
+	else if (!strcmp(name, "enable-filter-overrides"))
+		ctx.cfg.enable_filter_overrides = atoi(value);
+	else if (!strcmp(name, "enable-follow-links"))
+		ctx.cfg.enable_follow_links = atoi(value);
+	else if (!strcmp(name, "enable-http-clone"))
+		ctx.cfg.enable_http_clone = atoi(value);
+	else if (!strcmp(name, "enable-index-links"))
+		ctx.cfg.enable_index_links = atoi(value);
+	else if (!strcmp(name, "enable-index-owner"))
+		ctx.cfg.enable_index_owner = atoi(value);
+	else if (!strcmp(name, "enable-blame"))
+		ctx.cfg.enable_blame = atoi(value);
+	else if (!strcmp(name, "enable-commit-graph"))
+		ctx.cfg.enable_commit_graph = atoi(value);
+	else if (!strcmp(name, "enable-log-filecount"))
+		ctx.cfg.enable_log_filecount = atoi(value);
+	else if (!strcmp(name, "enable-log-linecount"))
+		ctx.cfg.enable_log_linecount = atoi(value);
+	else if (!strcmp(name, "enable-remote-branches"))
+		ctx.cfg.enable_remote_branches = atoi(value);
+	else if (!strcmp(name, "enable-subject-links"))
+		ctx.cfg.enable_subject_links = atoi(value);
+	else if (!strcmp(name, "enable-html-serving"))
+		ctx.cfg.enable_html_serving = atoi(value);
+	else if (!strcmp(name, "enable-tree-linenumbers"))
+		ctx.cfg.enable_tree_linenumbers = atoi(value);
+	else if (!strcmp(name, "enable-git-config"))
+		ctx.cfg.enable_git_config = atoi(value);
+	else if (!strcmp(name, "max-stats"))
+		ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
+	else if (!strcmp(name, "cache-size"))
+		ctx.cfg.cache_size = atoi(value);
+	else if (!strcmp(name, "cache-root"))
+		ctx.cfg.cache_root = xstrdup(expand_macros(value));
+	else if (!strcmp(name, "cache-root-ttl"))
+		ctx.cfg.cache_root_ttl = atoi(value);
+	else if (!strcmp(name, "cache-repo-ttl"))
+		ctx.cfg.cache_repo_ttl = atoi(value);
+	else if (!strcmp(name, "cache-scanrc-ttl"))
+		ctx.cfg.cache_scanrc_ttl = atoi(value);
+	else if (!strcmp(name, "cache-static-ttl"))
+		ctx.cfg.cache_static_ttl = atoi(value);
+	else if (!strcmp(name, "cache-dynamic-ttl"))
+		ctx.cfg.cache_dynamic_ttl = atoi(value);
+	else if (!strcmp(name, "cache-about-ttl"))
+		ctx.cfg.cache_about_ttl = atoi(value);
+	else if (!strcmp(name, "cache-snapshot-ttl"))
+		ctx.cfg.cache_snapshot_ttl = atoi(value);
+	else if (!strcmp(name, "case-sensitive-sort"))
+		ctx.cfg.case_sensitive_sort = atoi(value);
+	else if (!strcmp(name, "about-filter"))
+		ctx.cfg.about_filter = cgit_new_filter(value, ABOUT);
+	else if (!strcmp(name, "commit-filter"))
+		ctx.cfg.commit_filter = cgit_new_filter(value, COMMIT);
+	else if (!strcmp(name, "email-filter"))
+		ctx.cfg.email_filter = cgit_new_filter(value, EMAIL);
+	else if (!strcmp(name, "owner-filter"))
+		ctx.cfg.owner_filter = cgit_new_filter(value, OWNER);
+	else if (!strcmp(name, "auth-filter"))
+		ctx.cfg.auth_filter = cgit_new_filter(value, AUTH);
+	else if (!strcmp(name, "embedded"))
+		ctx.cfg.embedded = atoi(value);
+	else if (!strcmp(name, "max-atom-items"))
+		ctx.cfg.max_atom_items = atoi(value);
+	else if (!strcmp(name, "max-message-length"))
+		ctx.cfg.max_msg_len = atoi(value);
+	else if (!strcmp(name, "max-repodesc-length"))
+		ctx.cfg.max_repodesc_len = atoi(value);
+	else if (!strcmp(name, "max-blob-size"))
+		ctx.cfg.max_blob_size = atoi(value);
+	else if (!strcmp(name, "max-repo-count"))
+		ctx.cfg.max_repo_count = atoi(value);
+	else if (!strcmp(name, "max-commit-count"))
+		ctx.cfg.max_commit_count = atoi(value);
+	else if (!strcmp(name, "project-list"))
+		ctx.cfg.project_list = xstrdup(expand_macros(value));
+	else if (!strcmp(name, "scan-path"))
+		if (ctx.cfg.cache_size)
+			process_cached_repolist(expand_macros(value));
+		else if (ctx.cfg.project_list)
+			scan_projects(expand_macros(value),
+				      ctx.cfg.project_list, repo_config);
+		else
+			scan_tree(expand_macros(value), repo_config);
+	else if (!strcmp(name, "scan-hidden-path"))
+		ctx.cfg.scan_hidden_path = atoi(value);
+	else if (!strcmp(name, "section-from-path"))
+		ctx.cfg.section_from_path = atoi(value);
+	else if (!strcmp(name, "repository-sort"))
+		ctx.cfg.repository_sort = xstrdup(value);
+	else if (!strcmp(name, "section-sort"))
+		ctx.cfg.section_sort = atoi(value);
+	else if (!strcmp(name, "source-filter"))
+		ctx.cfg.source_filter = cgit_new_filter(value, SOURCE);
+	else if (!strcmp(name, "summary-log"))
+		ctx.cfg.summary_log = atoi(value);
+	else if (!strcmp(name, "summary-branches"))
+		ctx.cfg.summary_branches = atoi(value);
+	else if (!strcmp(name, "summary-tags"))
+		ctx.cfg.summary_tags = atoi(value);
+	else if (!strcmp(name, "side-by-side-diffs"))
+		ctx.cfg.difftype = atoi(value) ? DIFF_SSDIFF : DIFF_UNIFIED;
+	else if (!strcmp(name, "agefile"))
+		ctx.cfg.agefile = xstrdup(value);
+	else if (!strcmp(name, "mimetype-file"))
+		ctx.cfg.mimetype_file = xstrdup(value);
+	else if (!strcmp(name, "renamelimit"))
+		ctx.cfg.renamelimit = atoi(value);
+	else if (!strcmp(name, "remove-suffix"))
+		ctx.cfg.remove_suffix = atoi(value);
+	else if (!strcmp(name, "robots"))
+		ctx.cfg.robots = xstrdup(value);
+	else if (!strcmp(name, "clone-prefix"))
+		ctx.cfg.clone_prefix = xstrdup(value);
+	else if (!strcmp(name, "clone-url"))
+		ctx.cfg.clone_url = xstrdup(value);
+	else if (!strcmp(name, "local-time"))
+		ctx.cfg.local_time = atoi(value);
+	else if (!strcmp(name, "commit-sort")) {
+		if (!strcmp(value, "date"))
+			ctx.cfg.commit_sort = 1;
+		if (!strcmp(value, "topo"))
+			ctx.cfg.commit_sort = 2;
+	} else if (!strcmp(name, "branch-sort")) {
+		if (!strcmp(value, "age"))
+			ctx.cfg.branch_sort = 1;
+		if (!strcmp(value, "name"))
+			ctx.cfg.branch_sort = 0;
+	} else if (skip_prefix(name, "mimetype.", &arg))
+		add_mimetype(arg, value);
+	else if (!strcmp(name, "include"))
+		parse_configfile(expand_macros(value), config_cb);
+}
+
+static void querystring_cb(const char *name, const char *value)
+{
+	if (!value)
+		value = "";
+
+	if (!strcmp(name,"r")) {
+		ctx.qry.repo = xstrdup(value);
+		ctx.repo = cgit_get_repoinfo(value);
+	} else if (!strcmp(name, "p")) {
+		ctx.qry.page = xstrdup(value);
+	} else if (!strcmp(name, "url")) {
+		if (*value == '/')
+			value++;
+		ctx.qry.url = xstrdup(value);
+		cgit_parse_url(value);
+	} else if (!strcmp(name, "qt")) {
+		ctx.qry.grep = xstrdup(value);
+	} else if (!strcmp(name, "q")) {
+		ctx.qry.search = xstrdup(value);
+	} else if (!strcmp(name, "h")) {
+		ctx.qry.head = xstrdup(value);
+		ctx.qry.has_symref = 1;
+	} else if (!strcmp(name, "id")) {
+		ctx.qry.sha1 = xstrdup(value);
+		ctx.qry.has_sha1 = 1;
+	} else if (!strcmp(name, "id2")) {
+		ctx.qry.sha2 = xstrdup(value);
+		ctx.qry.has_sha1 = 1;
+	} else if (!strcmp(name, "ofs")) {
+		ctx.qry.ofs = atoi(value);
+	} else if (!strcmp(name, "path")) {
+		ctx.qry.path = trim_end(value, '/');
+	} else if (!strcmp(name, "name")) {
+		ctx.qry.name = xstrdup(value);
+	} else if (!strcmp(name, "s")) {
+		ctx.qry.sort = xstrdup(value);
+	} else if (!strcmp(name, "showmsg")) {
+		ctx.qry.showmsg = atoi(value);
+	} else if (!strcmp(name, "period")) {
+		ctx.qry.period = xstrdup(value);
+	} else if (!strcmp(name, "dt")) {
+		ctx.qry.difftype = atoi(value);
+		ctx.qry.has_difftype = 1;
+	} else if (!strcmp(name, "ss")) {
+		/* No longer generated, but there may be links out there. */
+		ctx.qry.difftype = atoi(value) ? DIFF_SSDIFF : DIFF_UNIFIED;
+		ctx.qry.has_difftype = 1;
+	} else if (!strcmp(name, "all")) {
+		ctx.qry.show_all = atoi(value);
+	} else if (!strcmp(name, "context")) {
+		ctx.qry.context = atoi(value);
+	} else if (!strcmp(name, "ignorews")) {
+		ctx.qry.ignorews = atoi(value);
+	} else if (!strcmp(name, "follow")) {
+		ctx.qry.follow = atoi(value);
+	}
+}
+
+static void prepare_context(void)
+{
+	memset(&ctx, 0, sizeof(ctx));
+	ctx.cfg.agefile = "info/web/last-modified";
+	ctx.cfg.cache_size = 0;
+	ctx.cfg.cache_max_create_time = 5;
+	ctx.cfg.cache_root = CGIT_CACHE_ROOT;
+	ctx.cfg.cache_about_ttl = 15;
+	ctx.cfg.cache_snapshot_ttl = 5;
+	ctx.cfg.cache_repo_ttl = 5;
+	ctx.cfg.cache_root_ttl = 5;
+	ctx.cfg.cache_scanrc_ttl = 15;
+	ctx.cfg.cache_dynamic_ttl = 5;
+	ctx.cfg.cache_static_ttl = -1;
+	ctx.cfg.case_sensitive_sort = 1;
+	ctx.cfg.branch_sort = 0;
+	ctx.cfg.commit_sort = 0;
+	ctx.cfg.css = "/cgit.css";
+	ctx.cfg.logo = "/cgit.png";
+	ctx.cfg.favicon = "/favicon.ico";
+	ctx.cfg.local_time = 0;
+	ctx.cfg.enable_http_clone = 1;
+	ctx.cfg.enable_index_owner = 1;
+	ctx.cfg.enable_tree_linenumbers = 1;
+	ctx.cfg.enable_git_config = 0;
+	ctx.cfg.max_repo_count = 50;
+	ctx.cfg.max_commit_count = 50;
+	ctx.cfg.max_lock_attempts = 5;
+	ctx.cfg.max_msg_len = 80;
+	ctx.cfg.max_repodesc_len = 80;
+	ctx.cfg.max_blob_size = 0;
+	ctx.cfg.max_stats = 0;
+	ctx.cfg.project_list = NULL;
+	ctx.cfg.renamelimit = -1;
+	ctx.cfg.remove_suffix = 0;
+	ctx.cfg.robots = "index, nofollow";
+	ctx.cfg.root_title = "Git repository browser";
+	ctx.cfg.root_desc = "a fast webinterface for the git dscm";
+	ctx.cfg.scan_hidden_path = 0;
+	ctx.cfg.script_name = CGIT_SCRIPT_NAME;
+	ctx.cfg.section = "";
+	ctx.cfg.repository_sort = "name";
+	ctx.cfg.section_sort = 1;
+	ctx.cfg.summary_branches = 10;
+	ctx.cfg.summary_log = 10;
+	ctx.cfg.summary_tags = 10;
+	ctx.cfg.max_atom_items = 10;
+	ctx.cfg.difftype = DIFF_UNIFIED;
+	ctx.env.cgit_config = getenv("CGIT_CONFIG");
+	ctx.env.http_host = getenv("HTTP_HOST");
+	ctx.env.https = getenv("HTTPS");
+	ctx.env.no_http = getenv("NO_HTTP");
+	ctx.env.path_info = getenv("PATH_INFO");
+	ctx.env.query_string = getenv("QUERY_STRING");
+	ctx.env.request_method = getenv("REQUEST_METHOD");
+	ctx.env.script_name = getenv("SCRIPT_NAME");
+	ctx.env.server_name = getenv("SERVER_NAME");
+	ctx.env.server_port = getenv("SERVER_PORT");
+	ctx.env.http_cookie = getenv("HTTP_COOKIE");
+	ctx.env.http_referer = getenv("HTTP_REFERER");
+	ctx.env.content_length = getenv("CONTENT_LENGTH") ? strtoul(getenv("CONTENT_LENGTH"), NULL, 10) : 0;
+	ctx.env.authenticated = 0;
+	ctx.page.mimetype = "text/html";
+	ctx.page.charset = PAGE_ENCODING;
+	ctx.page.filename = NULL;
+	ctx.page.size = 0;
+	ctx.page.modified = time(NULL);
+	ctx.page.expires = ctx.page.modified;
+	ctx.page.etag = NULL;
+	string_list_init(&ctx.cfg.mimetypes, 1);
+	if (ctx.env.script_name)
+		ctx.cfg.script_name = xstrdup(ctx.env.script_name);
+	if (ctx.env.query_string)
+		ctx.qry.raw = xstrdup(ctx.env.query_string);
+	if (!ctx.env.cgit_config)
+		ctx.env.cgit_config = CGIT_CONFIG;
+}
+
+struct refmatch {
+	char *req_ref;
+	char *first_ref;
+	int match;
+};
+
+static int find_current_ref(const char *refname, const struct object_id *oid,
+			    int flags, void *cb_data)
+{
+	struct refmatch *info;
+
+	info = (struct refmatch *)cb_data;
+	if (!strcmp(refname, info->req_ref))
+		info->match = 1;
+	if (!info->first_ref)
+		info->first_ref = xstrdup(refname);
+	return info->match;
+}
+
+static void free_refmatch_inner(struct refmatch *info)
+{
+	if (info->first_ref)
+		free(info->first_ref);
+}
+
+static char *find_default_branch(struct cgit_repo *repo)
+{
+	struct refmatch info;
+	char *ref;
+
+	info.req_ref = repo->defbranch;
+	info.first_ref = NULL;
+	info.match = 0;
+	for_each_branch_ref(find_current_ref, &info);
+	if (info.match)
+		ref = info.req_ref;
+	else
+		ref = info.first_ref;
+	if (ref)
+		ref = xstrdup(ref);
+	free_refmatch_inner(&info);
+
+	return ref;
+}
+
+static char *guess_defbranch(void)
+{
+	const char *ref, *refname;
+	struct object_id oid;
+
+	ref = resolve_ref_unsafe("HEAD", 0, &oid, NULL);
+	if (!ref || !skip_prefix(ref, "refs/heads/", &refname))
+		return "master";
+	return xstrdup(refname);
+}
+
+/* The caller must free filename and ref after calling this. */
+static inline void parse_readme(const char *readme, char **filename, char **ref, struct cgit_repo *repo)
+{
+	const char *colon;
+
+	*filename = NULL;
+	*ref = NULL;
+
+	if (!readme || !readme[0])
+		return;
+
+	/* Check if the readme is tracked in the git repo. */
+	colon = strchr(readme, ':');
+	if (colon && strlen(colon) > 1) {
+		/* If it starts with a colon, we want to use
+		 * the default branch */
+		if (colon == readme && repo->defbranch)
+			*ref = xstrdup(repo->defbranch);
+		else
+			*ref = xstrndup(readme, colon - readme);
+		readme = colon + 1;
+	}
+
+	/* Prepend repo path to relative readme path unless tracked. */
+	if (!(*ref) && readme[0] != '/')
+		*filename = fmtalloc("%s/%s", repo->path, readme);
+	else
+		*filename = xstrdup(readme);
+}
+static void choose_readme(struct cgit_repo *repo)
+{
+	int found;
+	char *filename, *ref;
+	struct string_list_item *entry;
+
+	if (!repo->readme.nr)
+		return;
+
+	found = 0;
+	for_each_string_list_item(entry, &repo->readme) {
+		parse_readme(entry->string, &filename, &ref, repo);
+		if (!filename) {
+			free(filename);
+			free(ref);
+			continue;
+		}
+		if (ref) {
+			if (cgit_ref_path_exists(filename, ref, 1)) {
+				found = 1;
+				break;
+			}
+		}
+		else if (!access(filename, R_OK)) {
+			found = 1;
+			break;
+		}
+		free(filename);
+		free(ref);
+	}
+	repo->readme.strdup_strings = 1;
+	string_list_clear(&repo->readme, 0);
+	repo->readme.strdup_strings = 0;
+	if (found)
+		string_list_append(&repo->readme, filename)->util = ref;
+}
+
+static void print_no_repo_clone_urls(const char *url)
+{
+        html("<tr><td><a rel='vcs-git' href='");
+        html_url_path(url);
+        html("' title='");
+        html_attr(ctx.repo->name);
+        html(" Git repository'>");
+        html_txt(url);
+        html("</a></td></tr>\n");
+}
+
+static void prepare_repo_env(int *nongit)
+{
+	/* The path to the git repository. */
+	setenv("GIT_DIR", ctx.repo->path, 1);
+
+	/* Setup the git directory and initialize the notes system. Both of these
+	 * load local configuration from the git repository, so we do them both while
+	 * the HOME variables are unset. */
+	setup_git_directory_gently(nongit);
+	load_display_notes(NULL);
+}
+
+static int prepare_repo_cmd(int nongit)
+{
+	struct object_id oid;
+	int rc;
+
+	if (nongit) {
+		const char *name = ctx.repo->name;
+		rc = errno;
+		ctx.page.title = fmtalloc("%s - %s", ctx.cfg.root_title,
+						"config error");
+		ctx.repo = NULL;
+		cgit_print_http_headers();
+		cgit_print_docstart();
+		cgit_print_pageheader();
+		cgit_print_error("Failed to open %s: %s", name,
+				 rc ? strerror(rc) : "Not a valid git repository");
+		cgit_print_docend();
+		return 1;
+	}
+	ctx.page.title = fmtalloc("%s - %s", ctx.repo->name, ctx.repo->desc);
+
+	if (!ctx.repo->defbranch)
+		ctx.repo->defbranch = guess_defbranch();
+
+	if (!ctx.qry.head) {
+		ctx.qry.nohead = 1;
+		ctx.qry.head = find_default_branch(ctx.repo);
+	}
+
+	if (!ctx.qry.head) {
+		cgit_print_http_headers();
+		cgit_print_docstart();
+		cgit_print_pageheader();
+		cgit_print_error("Repository seems to be empty");
+		if (!strcmp(ctx.qry.page, "summary")) {
+			html("<table class='list'><tr class='nohover'><td>&nbsp;</td></tr><tr class='nohover'><th class='left'>Clone</th></tr>\n");
+			cgit_prepare_repo_env(ctx.repo);
+			cgit_add_clone_urls(print_no_repo_clone_urls);
+			html("</table>\n");
+		}
+		cgit_print_docend();
+		return 1;
+	}
+
+	if (get_oid(ctx.qry.head, &oid)) {
+		char *old_head = ctx.qry.head;
+		ctx.qry.head = xstrdup(ctx.repo->defbranch);
+		cgit_print_error_page(404, "Not found",
+				"Invalid branch: %s", old_head);
+		free(old_head);
+		return 1;
+	}
+	string_list_sort(&ctx.repo->submodules);
+	cgit_prepare_repo_env(ctx.repo);
+	choose_readme(ctx.repo);
+	return 0;
+}
+
+static inline void open_auth_filter(const char *function)
+{
+	cgit_open_filter(ctx.cfg.auth_filter, function,
+		ctx.env.http_cookie ? ctx.env.http_cookie : "",
+		ctx.env.request_method ? ctx.env.request_method : "",
+		ctx.env.query_string ? ctx.env.query_string : "",
+		ctx.env.http_referer ? ctx.env.http_referer : "",
+		ctx.env.path_info ? ctx.env.path_info : "",
+		ctx.env.http_host ? ctx.env.http_host : "",
+		ctx.env.https ? ctx.env.https : "",
+		ctx.qry.repo ? ctx.qry.repo : "",
+		ctx.qry.page ? ctx.qry.page : "",
+		cgit_currentfullurl(),
+		cgit_loginurl());
+}
+
+/* We intentionally keep this rather small, instead of looping and
+ * feeding it to the filter a couple bytes at a time. This way, the
+ * filter itself does not need to handle any denial of service or
+ * buffer bloat issues. If this winds up being too small, people
+ * will complain on the mailing list, and we'll increase it as needed. */
+#define MAX_AUTHENTICATION_POST_BYTES 4096
+/* The filter is expected to spit out "Status: " and all headers. */
+static inline void authenticate_post(void)
+{
+	char buffer[MAX_AUTHENTICATION_POST_BYTES];
+	ssize_t len;
+
+	open_auth_filter("authenticate-post");
+	len = ctx.env.content_length;
+	if (len > MAX_AUTHENTICATION_POST_BYTES)
+		len = MAX_AUTHENTICATION_POST_BYTES;
+	if ((len = read(STDIN_FILENO, buffer, len)) < 0)
+		die_errno("Could not read POST from stdin");
+	if (write(STDOUT_FILENO, buffer, len) < 0)
+		die_errno("Could not write POST to stdout");
+	cgit_close_filter(ctx.cfg.auth_filter);
+	exit(0);
+}
+
+static inline void authenticate_cookie(void)
+{
+	/* If we don't have an auth_filter, consider all cookies valid, and thus return early. */
+	if (!ctx.cfg.auth_filter) {
+		ctx.env.authenticated = 1;
+		return;
+	}
+
+	/* If we're having something POST'd to /login, we're authenticating POST,
+	 * instead of the cookie, so call authenticate_post and bail out early.
+	 * This pattern here should match /?p=login with POST. */
+	if (ctx.env.request_method && ctx.qry.page && !ctx.repo && \
+	    !strcmp(ctx.env.request_method, "POST") && !strcmp(ctx.qry.page, "login")) {
+		authenticate_post();
+		return;
+	}
+
+	/* If we've made it this far, we're authenticating the cookie for real, so do that. */
+	open_auth_filter("authenticate-cookie");
+	ctx.env.authenticated = cgit_close_filter(ctx.cfg.auth_filter);
+}
+
+static void process_request(void)
+{
+	struct cgit_cmd *cmd;
+	int nongit = 0;
+
+	/* If we're not yet authenticated, no matter what page we're on,
+	 * display the authentication body from the auth_filter. This should
+	 * never be cached. */
+	if (!ctx.env.authenticated) {
+		ctx.page.title = "Authentication Required";
+		cgit_print_http_headers();
+		cgit_print_docstart();
+		cgit_print_pageheader();
+		open_auth_filter("body");
+		cgit_close_filter(ctx.cfg.auth_filter);
+		cgit_print_docend();
+		return;
+	}
+
+	if (ctx.repo)
+		prepare_repo_env(&nongit);
+
+	cmd = cgit_get_cmd();
+	if (!cmd) {
+		ctx.page.title = "cgit error";
+		cgit_print_error_page(404, "Not found", "Invalid request");
+		return;
+	}
+
+	if (!ctx.cfg.enable_http_clone && cmd->is_clone) {
+		ctx.page.title = "cgit error";
+		cgit_print_error_page(404, "Not found", "Invalid request");
+		return;
+	}
+
+	if (cmd->want_repo && !ctx.repo) {
+		cgit_print_error_page(400, "Bad request",
+				"No repository selected");
+		return;
+	}
+
+	/* If cmd->want_vpath is set, assume ctx.qry.path contains a "virtual"
+	 * in-project path limit to be made available at ctx.qry.vpath.
+	 * Otherwise, no path limit is in effect (ctx.qry.vpath = NULL).
+	 */
+	ctx.qry.vpath = cmd->want_vpath ? ctx.qry.path : NULL;
+
+	if (ctx.repo && prepare_repo_cmd(nongit))
+		return;
+
+	cmd->fn();
+}
+
+static int cmp_repos(const void *a, const void *b)
+{
+	const struct cgit_repo *ra = a, *rb = b;
+	return strcmp(ra->url, rb->url);
+}
+
+static char *build_snapshot_setting(int bitmap)
+{
+	const struct cgit_snapshot_format *f;
+	struct strbuf result = STRBUF_INIT;
+
+	for (f = cgit_snapshot_formats; f->suffix; f++) {
+		if (cgit_snapshot_format_bit(f) & bitmap) {
+			if (result.len)
+				strbuf_addch(&result, ' ');
+			strbuf_addstr(&result, f->suffix);
+		}
+	}
+	return strbuf_detach(&result, NULL);
+}
+
+static char *get_first_line(char *txt)
+{
+	char *t = xstrdup(txt);
+	char *p = strchr(t, '\n');
+	if (p)
+		*p = '\0';
+	return t;
+}
+
+static void print_repo(FILE *f, struct cgit_repo *repo)
+{
+	struct string_list_item *item;
+	fprintf(f, "repo.url=%s\n", repo->url);
+	fprintf(f, "repo.name=%s\n", repo->name);
+	fprintf(f, "repo.path=%s\n", repo->path);
+	if (repo->owner)
+		fprintf(f, "repo.owner=%s\n", repo->owner);
+	if (repo->desc) {
+		char *tmp = get_first_line(repo->desc);
+		fprintf(f, "repo.desc=%s\n", tmp);
+		free(tmp);
+	}
+	for_each_string_list_item(item, &repo->readme) {
+		if (item->util)
+			fprintf(f, "repo.readme=%s:%s\n", (char *)item->util, item->string);
+		else
+			fprintf(f, "repo.readme=%s\n", item->string);
+	}
+	if (repo->defbranch)
+		fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
+	if (repo->extra_head_content)
+		fprintf(f, "repo.extra-head-content=%s\n", repo->extra_head_content);
+	if (repo->module_link)
+		fprintf(f, "repo.module-link=%s\n", repo->module_link);
+	if (repo->section)
+		fprintf(f, "repo.section=%s\n", repo->section);
+	if (repo->homepage)
+		fprintf(f, "repo.homepage=%s\n", repo->homepage);
+	if (repo->clone_url)
+		fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
+	fprintf(f, "repo.enable-blame=%d\n",
+	        repo->enable_blame);
+	fprintf(f, "repo.enable-commit-graph=%d\n",
+	        repo->enable_commit_graph);
+	fprintf(f, "repo.enable-log-filecount=%d\n",
+	        repo->enable_log_filecount);
+	fprintf(f, "repo.enable-log-linecount=%d\n",
+	        repo->enable_log_linecount);
+	if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
+		cgit_fprintf_filter(repo->about_filter, f, "repo.about-filter=");
+	if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
+		cgit_fprintf_filter(repo->commit_filter, f, "repo.commit-filter=");
+	if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
+		cgit_fprintf_filter(repo->source_filter, f, "repo.source-filter=");
+	if (repo->email_filter && repo->email_filter != ctx.cfg.email_filter)
+		cgit_fprintf_filter(repo->email_filter, f, "repo.email-filter=");
+	if (repo->owner_filter && repo->owner_filter != ctx.cfg.owner_filter)
+		cgit_fprintf_filter(repo->owner_filter, f, "repo.owner-filter=");
+	if (repo->snapshots != ctx.cfg.snapshots) {
+		char *tmp = build_snapshot_setting(repo->snapshots);
+		fprintf(f, "repo.snapshots=%s\n", tmp ? tmp : "");
+		free(tmp);
+	}
+	if (repo->snapshot_prefix)
+		fprintf(f, "repo.snapshot-prefix=%s\n", repo->snapshot_prefix);
+	if (repo->max_stats != ctx.cfg.max_stats)
+		fprintf(f, "repo.max-stats=%s\n",
+		        cgit_find_stats_periodname(repo->max_stats));
+	if (repo->logo)
+		fprintf(f, "repo.logo=%s\n", repo->logo);
+	if (repo->logo_link)
+		fprintf(f, "repo.logo-link=%s\n", repo->logo_link);
+	fprintf(f, "repo.enable-remote-branches=%d\n", repo->enable_remote_branches);
+	fprintf(f, "repo.enable-subject-links=%d\n", repo->enable_subject_links);
+	fprintf(f, "repo.enable-html-serving=%d\n", repo->enable_html_serving);
+	if (repo->branch_sort == 1)
+		fprintf(f, "repo.branch-sort=age\n");
+	if (repo->commit_sort) {
+		if (repo->commit_sort == 1)
+			fprintf(f, "repo.commit-sort=date\n");
+		else if (repo->commit_sort == 2)
+			fprintf(f, "repo.commit-sort=topo\n");
+	}
+	fprintf(f, "repo.hide=%d\n", repo->hide);
+	fprintf(f, "repo.ignore=%d\n", repo->ignore);
+	fprintf(f, "\n");
+}
+
+static void print_repolist(FILE *f, struct cgit_repolist *list, int start)
+{
+	int i;
+
+	for (i = start; i < list->count; i++)
+		print_repo(f, &list->repos[i]);
+}
+
+/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
+ * and return 0 on success.
+ */
+static int generate_cached_repolist(const char *path, const char *cached_rc)
+{
+	struct strbuf locked_rc = STRBUF_INIT;
+	int result = 0;
+	int idx;
+	FILE *f;
+
+	strbuf_addf(&locked_rc, "%s.lock", cached_rc);
+	f = fopen(locked_rc.buf, "wx");
+	if (!f) {
+		/* Inform about the error unless the lockfile already existed,
+		 * since that only means we've got concurrent requests.
+		 */
+		result = errno;
+		if (result != EEXIST)
+			fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
+				locked_rc.buf, strerror(result), result);
+		goto out;
+	}
+	idx = cgit_repolist.count;
+	if (ctx.cfg.project_list)
+		scan_projects(path, ctx.cfg.project_list, repo_config);
+	else
+		scan_tree(path, repo_config);
+	print_repolist(f, &cgit_repolist, idx);
+	if (rename(locked_rc.buf, cached_rc))
+		fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
+			locked_rc.buf, cached_rc, strerror(errno), errno);
+	fclose(f);
+out:
+	strbuf_release(&locked_rc);
+	return result;
+}
+
+static void process_cached_repolist(const char *path)
+{
+	struct stat st;
+	struct strbuf cached_rc = STRBUF_INIT;
+	time_t age;
+	unsigned long hash;
+
+	hash = hash_str(path);
+	if (ctx.cfg.project_list)
+		hash += hash_str(ctx.cfg.project_list);
+	strbuf_addf(&cached_rc, "%s/rc-%8lx", ctx.cfg.cache_root, hash);
+
+	if (stat(cached_rc.buf, &st)) {
+		/* Nothing is cached, we need to scan without forking. And
+		 * if we fail to generate a cached repolist, we need to
+		 * invoke scan_tree manually.
+		 */
+		if (generate_cached_repolist(path, cached_rc.buf)) {
+			if (ctx.cfg.project_list)
+				scan_projects(path, ctx.cfg.project_list,
+					      repo_config);
+			else
+				scan_tree(path, repo_config);
+		}
+		goto out;
+	}
+
+	parse_configfile(cached_rc.buf, config_cb);
+
+	/* If the cached configfile hasn't expired, lets exit now */
+	age = time(NULL) - st.st_mtime;
+	if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
+		goto out;
+
+	/* The cached repolist has been parsed, but it was old. So lets
+	 * rescan the specified path and generate a new cached repolist
+	 * in a child-process to avoid latency for the current request.
+	 */
+	if (fork())
+		goto out;
+
+	exit(generate_cached_repolist(path, cached_rc.buf));
+out:
+	strbuf_release(&cached_rc);
+}
+
+static void cgit_parse_args(int argc, const char **argv)
+{
+	int i;
+	const char *arg;
+	int scan = 0;
+
+	for (i = 1; i < argc; i++) {
+		if (!strcmp(argv[i], "--version")) {
+			printf("CGit %s | https://git.zx2c4.com/cgit/\n\nCompiled in features:\n", CGIT_VERSION);
+#ifdef NO_LUA
+			printf("[-] ");
+#else
+			printf("[+] ");
+#endif
+			printf("Lua scripting\n");
+#ifndef HAVE_LINUX_SENDFILE
+			printf("[-] ");
+#else
+			printf("[+] ");
+#endif
+			printf("Linux sendfile() usage\n");
+
+			exit(0);
+		}
+		if (skip_prefix(argv[i], "--cache=", &arg)) {
+			ctx.cfg.cache_root = xstrdup(arg);
+		} else if (!strcmp(argv[i], "--nohttp")) {
+			ctx.env.no_http = "1";
+		} else if (skip_prefix(argv[i], "--query=", &arg)) {
+			ctx.qry.raw = xstrdup(arg);
+		} else if (skip_prefix(argv[i], "--repo=", &arg)) {
+			ctx.qry.repo = xstrdup(arg);
+		} else if (skip_prefix(argv[i], "--page=", &arg)) {
+			ctx.qry.page = xstrdup(arg);
+		} else if (skip_prefix(argv[i], "--head=", &arg)) {
+			ctx.qry.head = xstrdup(arg);
+			ctx.qry.has_symref = 1;
+		} else if (skip_prefix(argv[i], "--sha1=", &arg)) {
+			ctx.qry.sha1 = xstrdup(arg);
+			ctx.qry.has_sha1 = 1;
+		} else if (skip_prefix(argv[i], "--ofs=", &arg)) {
+			ctx.qry.ofs = atoi(arg);
+		} else if (skip_prefix(argv[i], "--scan-tree=", &arg) ||
+		           skip_prefix(argv[i], "--scan-path=", &arg)) {
+			/*
+			 * HACK: The global snapshot bit mask defines the set
+			 * of allowed snapshot formats, but the config file
+			 * hasn't been parsed yet so the mask is currently 0.
+			 * By setting all bits high before scanning we make
+			 * sure that any in-repo cgitrc snapshot setting is
+			 * respected by scan_tree().
+			 *
+			 * NOTE: We assume that there aren't more than 8
+			 * different snapshot formats supported by cgit...
+			 */
+			ctx.cfg.snapshots = 0xFF;
+			scan++;
+			scan_tree(arg, repo_config);
+		}
+	}
+	if (scan) {
+		qsort(cgit_repolist.repos, cgit_repolist.count,
+			sizeof(struct cgit_repo), cmp_repos);
+		print_repolist(stdout, &cgit_repolist, 0);
+		exit(0);
+	}
+}
+
+static int calc_ttl(void)
+{
+	if (!ctx.repo)
+		return ctx.cfg.cache_root_ttl;
+
+	if (!ctx.qry.page)
+		return ctx.cfg.cache_repo_ttl;
+
+	if (!strcmp(ctx.qry.page, "about"))
+		return ctx.cfg.cache_about_ttl;
+
+	if (!strcmp(ctx.qry.page, "snapshot"))
+		return ctx.cfg.cache_snapshot_ttl;
+
+	if (ctx.qry.has_sha1)
+		return ctx.cfg.cache_static_ttl;
+
+	if (ctx.qry.has_symref)
+		return ctx.cfg.cache_dynamic_ttl;
+
+	return ctx.cfg.cache_repo_ttl;
+}
+
+int cmd_main(int argc, const char **argv)
+{
+	const char *path;
+	int err, ttl;
+
+	cgit_init_filters();
+	atexit(cgit_cleanup_filters);
+
+	prepare_context();
+	cgit_repolist.length = 0;
+	cgit_repolist.count = 0;
+	cgit_repolist.repos = NULL;
+
+	cgit_parse_args(argc, argv);
+	parse_configfile(expand_macros(ctx.env.cgit_config), config_cb);
+	ctx.repo = NULL;
+	http_parse_querystring(ctx.qry.raw, querystring_cb);
+
+	/* If virtual-root isn't specified in cgitrc, lets pretend
+	 * that virtual-root equals SCRIPT_NAME, minus any possibly
+	 * trailing slashes.
+	 */
+	if (!ctx.cfg.virtual_root && ctx.cfg.script_name)
+		ctx.cfg.virtual_root = ensure_end(ctx.cfg.script_name, '/');
+
+	/* If no url parameter is specified on the querystring, lets
+	 * use PATH_INFO as url. This allows cgit to work with virtual
+	 * urls without the need for rewriterules in the webserver (as
+	 * long as PATH_INFO is included in the cache lookup key).
+	 */
+	path = ctx.env.path_info;
+	if (!ctx.qry.url && path) {
+		if (path[0] == '/')
+			path++;
+		ctx.qry.url = xstrdup(path);
+		if (ctx.qry.raw) {
+			char *newqry = fmtalloc("%s?%s", path, ctx.qry.raw);
+			free(ctx.qry.raw);
+			ctx.qry.raw = newqry;
+		} else
+			ctx.qry.raw = xstrdup(ctx.qry.url);
+		cgit_parse_url(ctx.qry.url);
+	}
+
+	/* Before we go any further, we set ctx.env.authenticated by checking to see
+	 * if the supplied cookie is valid. All cookies are valid if there is no
+	 * auth_filter. If there is an auth_filter, the filter decides. */
+	authenticate_cookie();
+
+	ttl = calc_ttl();
+	if (ttl < 0)
+		ctx.page.expires += 10 * 365 * 24 * 60 * 60; /* 10 years */
+	else
+		ctx.page.expires += ttl * 60;
+	if (!ctx.env.authenticated || (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")))
+		ctx.cfg.cache_size = 0;
+	err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
+			    ctx.qry.raw, ttl, process_request);
+	cgit_cleanup_filters();
+	if (err)
+		cgit_print_error("Error processing page: %s (%d)",
+				 strerror(err), err);
+	return err;
+}
diff --git a/www/git.causal.agency/cgit/cgit.css b/www/git.causal.agency/cgit/cgit.css
new file mode 100644
index 00000000..d4aadbfa
--- /dev/null
+++ b/www/git.causal.agency/cgit/cgit.css
@@ -0,0 +1,895 @@
+div#cgit {
+	padding: 0em;
+	margin: 0em;
+	font-family: sans-serif;
+	font-size: 10pt;
+	color: #333;
+	background: white;
+	padding: 4px;
+}
+
+div#cgit a {
+	color: blue;
+	text-decoration: none;
+}
+
+div#cgit a:hover {
+	text-decoration: underline;
+}
+
+div#cgit table {
+	border-collapse: collapse;
+}
+
+div#cgit table#header {
+	width: 100%;
+	margin-bottom: 1em;
+}
+
+div#cgit table#header td.logo {
+	width: 96px;
+	vertical-align: top;
+}
+
+div#cgit table#header td.main {
+	font-size: 250%;
+	padding-left: 10px;
+	white-space: nowrap;
+}
+
+div#cgit table#header td.main a {
+	color: #000;
+}
+
+div#cgit table#header td.form {
+	text-align: right;
+	vertical-align: bottom;
+	padding-right: 1em;
+	padding-bottom: 2px;
+	white-space: nowrap;
+}
+
+div#cgit table#header td.form form,
+div#cgit table#header td.form input,
+div#cgit table#header td.form select {
+	font-size: 90%;
+}
+
+div#cgit table#header td.sub {
+	color: #777;
+	border-top: solid 1px #ccc;
+	padding-left: 10px;
+}
+
+div#cgit table.tabs {
+	border-bottom: solid 3px #ccc;
+	border-collapse: collapse;
+	margin-top: 2em;
+	margin-bottom: 0px;
+	width: 100%;
+}
+
+div#cgit table.tabs td {
+	padding: 0px 1em;
+	vertical-align: bottom;
+}
+
+div#cgit table.tabs td a {
+	padding: 2px 0.75em;
+	color: #777;
+	font-size: 110%;
+}
+
+div#cgit table.tabs td a.active {
+	color: #000;
+	background-color: #ccc;
+}
+
+div#cgit table.tabs a[href^="http://"]:after, div#cgit table.tabs a[href^="https://"]:after {
+	content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAhcJDQY+gm2TAAAAHWlUWHRDb21tZW50AAAAAABDcmVhdGVkIHdpdGggR0lNUGQuZQcAAABbSURBVAhbY2BABs4MU4CwhYHBh2Erww4wrGFQZHjI8B8IgUIscJWyDHcggltQhI4zGDCcRwhChPggHIggP1QoAVmQkSETrGoHsiAEsACtBYN0oDAMbgU6EBcAAL2eHUt4XUU4AAAAAElFTkSuQmCC);
+	opacity: 0.5;
+	margin: 0 0 0 5px;
+}
+
+div#cgit table.tabs td.form {
+	text-align: right;
+}
+
+div#cgit table.tabs td.form form {
+	padding-bottom: 2px;
+	font-size: 90%;
+	white-space: nowrap;
+}
+
+div#cgit table.tabs td.form input,
+div#cgit table.tabs td.form select {
+	font-size: 90%;
+}
+
+div#cgit div.path {
+	margin: 0px;
+	padding: 5px 2em 2px 2em;
+	color: #000;
+	background-color: #eee;
+}
+
+div#cgit div.content {
+	margin: 0px;
+	padding: 2em;
+	border-bottom: solid 3px #ccc;
+}
+
+
+div#cgit table.list {
+	width: 100%;
+	border: none;
+	border-collapse: collapse;
+}
+
+div#cgit table.list tr {
+	background: white;
+}
+
+div#cgit table.list tr.logheader {
+	background: #eee;
+}
+
+div#cgit table.list tr:nth-child(even) {
+	background: #f7f7f7;
+}
+
+div#cgit table.list tr:nth-child(odd) {
+	background: white;
+}
+
+div#cgit table.list tr:hover {
+	background: #eee;
+}
+
+div#cgit table.list tr.nohover {
+	background: white;
+}
+
+div#cgit table.list tr.nohover:hover {
+	background: white;
+}
+
+div#cgit table.list tr.nohover-highlight:hover:nth-child(even) {
+	background: #f7f7f7;
+}
+
+div#cgit table.list tr.nohover-highlight:hover:nth-child(odd) {
+	background: white;
+}
+
+div#cgit table.list th {
+	font-weight: bold;
+	/* color: #888;
+	border-top: dashed 1px #888;
+	border-bottom: dashed 1px #888;
+	*/
+	padding: 0.1em 0.5em 0.05em 0.5em;
+	vertical-align: baseline;
+}
+
+div#cgit table.list td {
+	border: none;
+	padding: 0.1em 0.5em 0.1em 0.5em;
+}
+
+div#cgit table.list td.commitgraph {
+	font-family: monospace;
+	white-space: pre;
+}
+
+div#cgit table.list td.commitgraph .column1 {
+	color: #a00;
+}
+
+div#cgit table.list td.commitgraph .column2 {
+	color: #0a0;
+}
+
+div#cgit table.list td.commitgraph .column3 {
+	color: #aa0;
+}
+
+div#cgit table.list td.commitgraph .column4 {
+	color: #00a;
+}
+
+div#cgit table.list td.commitgraph .column5 {
+	color: #a0a;
+}
+
+div#cgit table.list td.commitgraph .column6 {
+	color: #0aa;
+}
+
+div#cgit table.list td.logsubject {
+	font-family: monospace;
+	font-weight: bold;
+}
+
+div#cgit table.list td.logmsg {
+	font-family: monospace;
+	white-space: pre;
+	padding: 0 0.5em;
+}
+
+div#cgit table.list td a {
+	color: black;
+}
+
+div#cgit table.list td a.ls-dir {
+	font-weight: bold;
+	color: #00f;
+}
+
+div#cgit table.list td a:hover {
+	color: #00f;
+}
+
+div#cgit img {
+	border: none;
+}
+
+div#cgit input#switch-btn {
+	margin: 2px 0px 0px 0px;
+}
+
+div#cgit td#sidebar input.txt {
+	width: 100%;
+	margin: 2px 0px 0px 0px;
+}
+
+div#cgit table#grid {
+	margin: 0px;
+}
+
+div#cgit td#content {
+	vertical-align: top;
+	padding: 1em 2em 1em 1em;
+	border: none;
+}
+
+div#cgit div#summary {
+	vertical-align: top;
+	margin-bottom: 1em;
+}
+
+div#cgit table#downloads {
+	float: right;
+	border-collapse: collapse;
+	border: solid 1px #777;
+	margin-left: 0.5em;
+	margin-bottom: 0.5em;
+}
+
+div#cgit table#downloads th {
+	background-color: #ccc;
+}
+
+div#cgit div#blob {
+	border: solid 1px black;
+}
+
+div#cgit div.error {
+	color: red;
+	font-weight: bold;
+	margin: 1em 2em;
+}
+
+div#cgit a.ls-blob, div#cgit a.ls-dir, div#cgit .ls-mod {
+	font-family: monospace;
+}
+
+div#cgit td.ls-size {
+	text-align: right;
+	font-family: monospace;
+	width: 10em;
+}
+
+div#cgit td.ls-mode {
+	font-family: monospace;
+	width: 10em;
+}
+
+div#cgit table.blob {
+	margin-top: 0.5em;
+	border-top: solid 1px black;
+}
+
+div#cgit table.blob td.hashes,
+div#cgit table.blob td.lines {
+	margin: 0; padding: 0 0 0 0.5em;
+	vertical-align: top;
+	color: black;
+}
+
+div#cgit table.blob td.linenumbers {
+	margin: 0; padding: 0 0.5em 0 0.5em;
+	vertical-align: top;
+	text-align: right;
+	border-right: 1px solid gray;
+}
+
+div#cgit table.blob pre {
+	padding: 0; margin: 0;
+}
+
+div#cgit table.blob td.linenumbers a,
+div#cgit table.ssdiff td.lineno a {
+	color: gray;
+	text-align: right;
+	text-decoration: none;
+}
+
+div#cgit table.blob td.linenumbers a:hover,
+div#cgit table.ssdiff td.lineno a:hover {
+	color: black;
+}
+
+div#cgit table.blame td.hashes,
+div#cgit table.blame td.lines,
+div#cgit table.blame td.linenumbers {
+	padding: 0;
+}
+
+div#cgit table.blame td.hashes div.alt,
+div#cgit table.blame td.lines div.alt {
+	padding: 0 0.5em 0 0.5em;
+}
+
+div#cgit table.blame td.linenumbers div.alt {
+	padding: 0 0.5em 0 0;
+}
+
+div#cgit table.blame div.alt:nth-child(even) {
+	background: #eee;
+}
+
+div#cgit table.blame div.alt:nth-child(odd) {
+	background: white;
+}
+
+div#cgit table.blame td.lines > div {
+	position: relative;
+}
+
+div#cgit table.blame td.lines > div > pre {
+	padding: 0 0 0 0.5em;
+	position: absolute;
+	top: 0;
+}
+
+div#cgit table.bin-blob {
+	margin-top: 0.5em;
+	border: solid 1px black;
+}
+
+div#cgit table.bin-blob th {
+	font-family: monospace;
+	white-space: pre;
+	border: solid 1px #777;
+	padding: 0.5em 1em;
+}
+
+div#cgit table.bin-blob td {
+	font-family: monospace;
+	white-space: pre;
+	border-left: solid 1px #777;
+	padding: 0em 1em;
+}
+
+div#cgit table.nowrap td {
+	white-space: nowrap;
+}
+
+div#cgit table.commit-info {
+	border-collapse: collapse;
+	margin-top: 1.5em;
+}
+
+div#cgit div.cgit-panel {
+	float: right;
+	margin-top: 1.5em;
+}
+
+div#cgit div.cgit-panel table {
+	border-collapse: collapse;
+	border: solid 1px #aaa;
+	background-color: #eee;
+}
+
+div#cgit div.cgit-panel th {
+	text-align: center;
+}
+
+div#cgit div.cgit-panel td {
+	padding: 0.25em 0.5em;
+}
+
+div#cgit div.cgit-panel td.label {
+	padding-right: 0.5em;
+}
+
+div#cgit div.cgit-panel td.ctrl {
+	padding-left: 0.5em;
+}
+
+div#cgit table.commit-info th {
+	text-align: left;
+	font-weight: normal;
+	padding: 0.1em 1em 0.1em 0.1em;
+	vertical-align: top;
+}
+
+div#cgit table.commit-info td {
+	font-weight: normal;
+	padding: 0.1em 1em 0.1em 0.1em;
+}
+
+div#cgit div.commit-subject {
+	font-weight: bold;
+	font-size: 125%;
+	margin: 1.5em 0em 0.5em 0em;
+	padding: 0em;
+}
+
+div#cgit div.commit-msg {
+	white-space: pre;
+	font-family: monospace;
+}
+
+div#cgit div.notes-header {
+	font-weight: bold;
+	padding-top: 1.5em;
+}
+
+div#cgit div.notes {
+	white-space: pre;
+	font-family: monospace;
+	border: solid 1px #ee9;
+	background-color: #ffd;
+	padding: 0.3em 2em 0.3em 1em;
+	float: left;
+}
+
+div#cgit div.notes-footer {
+	clear: left;
+}
+
+div#cgit div.diffstat-header {
+	font-weight: bold;
+	padding-top: 1.5em;
+}
+
+div#cgit table.diffstat {
+	border-collapse: collapse;
+	border: solid 1px #aaa;
+	background-color: #eee;
+}
+
+div#cgit table.diffstat th {
+	font-weight: normal;
+	text-align: left;
+	text-decoration: underline;
+	padding: 0.1em 1em 0.1em 0.1em;
+	font-size: 100%;
+}
+
+div#cgit table.diffstat td {
+	padding: 0.2em 0.2em 0.1em 0.1em;
+	font-size: 100%;
+	border: none;
+}
+
+div#cgit table.diffstat td.mode {
+	white-space: nowrap;
+}
+
+div#cgit table.diffstat td span.modechange {
+	padding-left: 1em;
+	color: red;
+}
+
+div#cgit table.diffstat td.add a {
+	color: green;
+}
+
+div#cgit table.diffstat td.del a {
+	color: red;
+}
+
+div#cgit table.diffstat td.upd a {
+	color: blue;
+}
+
+div#cgit table.diffstat td.graph {
+	width: 500px;
+	vertical-align: middle;
+}
+
+div#cgit table.diffstat td.graph table {
+	border: none;
+}
+
+div#cgit table.diffstat td.graph td {
+	padding: 0px;
+	border: 0px;
+	height: 7pt;
+}
+
+div#cgit table.diffstat td.graph td.add {
+	background-color: #5c5;
+}
+
+div#cgit table.diffstat td.graph td.rem {
+	background-color: #c55;
+}
+
+div#cgit div.diffstat-summary {
+	color: #888;
+	padding-top: 0.5em;
+}
+
+div#cgit table.diff {
+	width: 100%;
+}
+
+div#cgit table.diff td {
+	font-family: monospace;
+	white-space: pre;
+}
+
+div#cgit table.diff td div.head {
+	font-weight: bold;
+	margin-top: 1em;
+	color: black;
+}
+
+div#cgit table.diff td div.hunk {
+	color: #009;
+}
+
+div#cgit table.diff td div.add {
+	color: green;
+}
+
+div#cgit table.diff td div.del {
+	color: red;
+}
+
+div#cgit .sha1 {
+	font-family: monospace;
+	font-size: 90%;
+}
+
+div#cgit .left {
+	text-align: left;
+}
+
+div#cgit .right {
+	text-align: right;
+}
+
+div#cgit table.list td.reposection {
+	font-style: italic;
+	color: #888;
+}
+
+div#cgit a.button {
+	font-size: 80%;
+	padding: 0em 0.5em;
+}
+
+div#cgit a.primary {
+	font-size: 100%;
+}
+
+div#cgit a.secondary {
+	font-size: 90%;
+}
+
+div#cgit td.toplevel-repo {
+
+}
+
+div#cgit table.list td.sublevel-repo {
+	padding-left: 1.5em;
+}
+
+div#cgit ul.pager {
+	list-style-type: none;
+	text-align: center;
+	margin: 1em 0em 0em 0em;
+	padding: 0;
+}
+
+div#cgit ul.pager li {
+	display: inline-block;
+	margin: 0.25em 0.5em;
+}
+
+div#cgit ul.pager a {
+	color: #777;
+}
+
+div#cgit ul.pager .current {
+	font-weight: bold;
+}
+
+div#cgit span.age-mins {
+	font-weight: bold;
+	color: #080;
+}
+
+div#cgit span.age-hours {
+	color: #080;
+}
+
+div#cgit span.age-days {
+	color: #040;
+}
+
+div#cgit span.age-weeks {
+	color: #444;
+}
+
+div#cgit span.age-months {
+	color: #888;
+}
+
+div#cgit span.age-years {
+	color: #bbb;
+}
+
+div#cgit span.insertions {
+	color: #080;
+}
+
+div#cgit span.deletions {
+	color: #800;
+}
+
+div#cgit div.footer {
+	margin-top: 0.5em;
+	text-align: center;
+	font-size: 80%;
+	color: #ccc;
+}
+
+div#cgit div.footer a {
+	color: #ccc;
+	text-decoration: none;
+}
+
+div#cgit div.footer a:hover {
+	text-decoration: underline;
+}
+
+div#cgit a.branch-deco {
+	color: #000;
+	margin: 0px 0.5em;
+	padding: 0px 0.25em;
+	background-color: #88ff88;
+	border: solid 1px #007700;
+}
+
+div#cgit a.tag-deco {
+	color: #000;
+	margin: 0px 0.5em;
+	padding: 0px 0.25em;
+	background-color: #ffff88;
+	border: solid 1px #777700;
+}
+
+div#cgit a.tag-annotated-deco {
+	color: #000;
+	margin: 0px 0.5em;
+	padding: 0px 0.25em;
+	background-color: #ffcc88;
+	border: solid 1px #777700;
+}
+
+div#cgit a.remote-deco {
+	color: #000;
+	margin: 0px 0.5em;
+	padding: 0px 0.25em;
+	background-color: #ccccff;
+	border: solid 1px #000077;
+}
+
+div#cgit a.deco {
+	color: #000;
+	margin: 0px 0.5em;
+	padding: 0px 0.25em;
+	background-color: #ff8888;
+	border: solid 1px #770000;
+}
+
+div#cgit div.commit-subject a.branch-deco,
+div#cgit div.commit-subject a.tag-deco,
+div#cgit div.commit-subject a.tag-annotated-deco,
+div#cgit div.commit-subject a.remote-deco,
+div#cgit div.commit-subject a.deco {
+	margin-left: 1em;
+	font-size: 75%;
+}
+
+div#cgit table.stats {
+	border: solid 1px black;
+	border-collapse: collapse;
+}
+
+div#cgit table.stats th {
+	text-align: left;
+	padding: 1px 0.5em;
+	background-color: #eee;
+	border: solid 1px black;
+}
+
+div#cgit table.stats td {
+	text-align: right;
+	padding: 1px 0.5em;
+	border: solid 1px black;
+}
+
+div#cgit table.stats td.total {
+	font-weight: bold;
+	text-align: left;
+}
+
+div#cgit table.stats td.sum {
+	color: #c00;
+	font-weight: bold;
+/*	background-color: #eee; */
+}
+
+div#cgit table.stats td.left {
+	text-align: left;
+}
+
+div#cgit table.vgraph {
+	border-collapse: separate;
+	border: solid 1px black;
+	height: 200px;
+}
+
+div#cgit table.vgraph th {
+	background-color: #eee;
+	font-weight: bold;
+	border: solid 1px white;
+	padding: 1px 0.5em;
+}
+
+div#cgit table.vgraph td {
+	vertical-align: bottom;
+	padding: 0px 10px;
+}
+
+div#cgit table.vgraph div.bar {
+	background-color: #eee;
+}
+
+div#cgit table.hgraph {
+	border: solid 1px black;
+	width: 800px;
+}
+
+div#cgit table.hgraph th {
+	background-color: #eee;
+	font-weight: bold;
+	border: solid 1px black;
+	padding: 1px 0.5em;
+}
+
+div#cgit table.hgraph td {
+	vertical-align: middle;
+	padding: 2px 2px;
+}
+
+div#cgit table.hgraph div.bar {
+	background-color: #eee;
+	height: 1em;
+}
+
+div#cgit table.ssdiff {
+	width: 100%;
+}
+
+div#cgit table.ssdiff td {
+	font-size: 75%;
+	font-family: monospace;
+	white-space: pre;
+	padding: 1px 4px 1px 4px;
+	border-left: solid 1px #aaa;
+	border-right: solid 1px #aaa;
+}
+
+div#cgit table.ssdiff td.add {
+	color: black;
+	background: #cfc;
+	min-width: 50%;
+}
+
+div#cgit table.ssdiff td.add_dark {
+	color: black;
+	background: #aca;
+	min-width: 50%;
+}
+
+div#cgit table.ssdiff span.add {
+	background: #cfc;
+	font-weight: bold;
+}
+
+div#cgit table.ssdiff td.del {
+	color: black;
+	background: #fcc;
+	min-width: 50%;
+}
+
+div#cgit table.ssdiff td.del_dark {
+	color: black;
+	background: #caa;
+	min-width: 50%;
+}
+
+div#cgit table.ssdiff span.del {
+	background: #fcc;
+	font-weight: bold;
+}
+
+div#cgit table.ssdiff td.changed {
+	color: black;
+	background: #ffc;
+	min-width: 50%;
+}
+
+div#cgit table.ssdiff td.changed_dark {
+	color: black;
+	background: #cca;
+	min-width: 50%;
+}
+
+div#cgit table.ssdiff td.lineno {
+	color: black;
+	background: #eee;
+	text-align: right;
+	width: 3em;
+	min-width: 3em;
+}
+
+div#cgit table.ssdiff td.hunk {
+	color: black;
+	background: #ccf;
+	border-top: solid 1px #aaa;
+	border-bottom: solid 1px #aaa;
+}
+
+div#cgit table.ssdiff td.head {
+	border-top: solid 1px #aaa;
+	border-bottom: solid 1px #aaa;
+}
+
+div#cgit table.ssdiff td.head div.head {
+	font-weight: bold;
+	color: black;
+}
+
+div#cgit table.ssdiff td.foot {
+	border-top: solid 1px #aaa;
+	border-left: none;
+	border-right: none;
+	border-bottom: none;
+}
+
+div#cgit table.ssdiff td.space {
+	border: none;
+}
+
+div#cgit table.ssdiff td.space div {
+	min-height: 3em;
+}
diff --git a/www/git.causal.agency/cgit/cgit.h b/www/git.causal.agency/cgit/cgit.h
new file mode 100644
index 00000000..7ec46b48
--- /dev/null
+++ b/www/git.causal.agency/cgit/cgit.h
@@ -0,0 +1,398 @@
+#ifndef CGIT_H
+#define CGIT_H
+
+
+#include <git-compat-util.h>
+#include <stdbool.h>
+
+#include <cache.h>
+#include <grep.h>
+#include <object.h>
+#include <object-store.h>
+#include <tree.h>
+#include <commit.h>
+#include <tag.h>
+#include <diff.h>
+#include <diffcore.h>
+#include <argv-array.h>
+#include <refs.h>
+#include <revision.h>
+#include <log-tree.h>
+#include <archive.h>
+#include <string-list.h>
+#include <xdiff-interface.h>
+#include <xdiff/xdiff.h>
+#include <utf8.h>
+#include <notes.h>
+#include <graph.h>
+
+/* Add isgraph(x) to Git's sane ctype support (see git-compat-util.h) */
+#undef isgraph
+#define isgraph(x) (isprint((x)) && !isspace((x)))
+
+
+/*
+ * Limits used for relative dates
+ */
+#define TM_MIN    60
+#define TM_HOUR  (TM_MIN * 60)
+#define TM_DAY   (TM_HOUR * 24)
+#define TM_WEEK  (TM_DAY * 7)
+#define TM_YEAR  (TM_DAY * 365)
+#define TM_MONTH (TM_YEAR / 12.0)
+
+
+/*
+ * Default encoding
+ */
+#define PAGE_ENCODING "UTF-8"
+
+#define BIT(x)	(1U << (x))
+
+typedef void (*configfn)(const char *name, const char *value);
+typedef void (*filepair_fn)(struct diff_filepair *pair);
+typedef void (*linediff_fn)(char *line, int len);
+
+typedef enum {
+	DIFF_UNIFIED, DIFF_SSDIFF, DIFF_STATONLY
+} diff_type;
+
+typedef enum {
+	ABOUT, COMMIT, SOURCE, EMAIL, AUTH, OWNER
+} filter_type;
+
+struct cgit_filter {
+	int (*open)(struct cgit_filter *, va_list ap);
+	int (*close)(struct cgit_filter *);
+	void (*fprintf)(struct cgit_filter *, FILE *, const char *prefix);
+	void (*cleanup)(struct cgit_filter *);
+	int argument_count;
+};
+
+struct cgit_exec_filter {
+	struct cgit_filter base;
+	char *cmd;
+	char **argv;
+	int old_stdout;
+	int pid;
+};
+
+struct cgit_repo {
+	char *url;
+	char *name;
+	char *path;
+	char *desc;
+	char *extra_head_content;
+	char *owner;
+	char *homepage;
+	char *defbranch;
+	char *module_link;
+	struct string_list readme;
+	char *section;
+	char *clone_url;
+	char *logo;
+	char *logo_link;
+	char *snapshot_prefix;
+	int snapshots;
+	int enable_blame;
+	int enable_commit_graph;
+	int enable_log_filecount;
+	int enable_log_linecount;
+	int enable_remote_branches;
+	int enable_subject_links;
+	int enable_html_serving;
+	int max_stats;
+	int branch_sort;
+	int commit_sort;
+	time_t mtime;
+	struct cgit_filter *about_filter;
+	struct cgit_filter *commit_filter;
+	struct cgit_filter *source_filter;
+	struct cgit_filter *email_filter;
+	struct cgit_filter *owner_filter;
+	struct string_list submodules;
+	int hide;
+	int ignore;
+};
+
+typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
+	      const char *value);
+
+struct cgit_repolist {
+	int length;
+	int count;
+	struct cgit_repo *repos;
+};
+
+struct commitinfo {
+	struct commit *commit;
+	char *author;
+	char *author_email;
+	unsigned long author_date;
+	int author_tz;
+	char *committer;
+	char *committer_email;
+	unsigned long committer_date;
+	int committer_tz;
+	char *subject;
+	char *msg;
+	char *msg_encoding;
+};
+
+struct taginfo {
+	char *tagger;
+	char *tagger_email;
+	unsigned long tagger_date;
+	int tagger_tz;
+	char *msg;
+};
+
+struct refinfo {
+	const char *refname;
+	struct object *object;
+	union {
+		struct taginfo *tag;
+		struct commitinfo *commit;
+	};
+};
+
+struct reflist {
+	struct refinfo **refs;
+	int alloc;
+	int count;
+};
+
+struct cgit_query {
+	int has_symref;
+	int has_sha1;
+	int has_difftype;
+	char *raw;
+	char *repo;
+	char *page;
+	char *search;
+	char *grep;
+	char *head;
+	char *sha1;
+	char *sha2;
+	char *path;
+	char *name;
+	char *url;
+	char *period;
+	int   ofs;
+	int nohead;
+	char *sort;
+	int showmsg;
+	diff_type difftype;
+	int show_all;
+	int context;
+	int ignorews;
+	int follow;
+	char *vpath;
+};
+
+struct cgit_config {
+	char *agefile;
+	char *cache_root;
+	char *clone_prefix;
+	char *clone_url;
+	char *css;
+	char *favicon;
+	char *footer;
+	char *head_include;
+	char *header;
+	char *logo;
+	char *logo_link;
+	char *mimetype_file;
+	char *module_link;
+	char *project_list;
+	struct string_list readme;
+	char *robots;
+	char *root_title;
+	char *root_desc;
+	char *root_readme;
+	char *script_name;
+	char *section;
+	char *repository_sort;
+	char *virtual_root;	/* Always ends with '/'. */
+	char *strict_export;
+	int cache_size;
+	int cache_dynamic_ttl;
+	int cache_max_create_time;
+	int cache_repo_ttl;
+	int cache_root_ttl;
+	int cache_scanrc_ttl;
+	int cache_static_ttl;
+	int cache_about_ttl;
+	int cache_snapshot_ttl;
+	int case_sensitive_sort;
+	int embedded;
+	int enable_filter_overrides;
+	int enable_follow_links;
+	int enable_http_clone;
+	int enable_index_links;
+	int enable_index_owner;
+	int enable_blame;
+	int enable_commit_graph;
+	int enable_log_filecount;
+	int enable_log_linecount;
+	int enable_remote_branches;
+	int enable_subject_links;
+	int enable_html_serving;
+	int enable_tree_linenumbers;
+	int enable_git_config;
+	int local_time;
+	int max_atom_items;
+	int max_repo_count;
+	int max_commit_count;
+	int max_lock_attempts;
+	int max_msg_len;
+	int max_repodesc_len;
+	int max_blob_size;
+	int max_stats;
+	int noplainemail;
+	int noheader;
+	int renamelimit;
+	int remove_suffix;
+	int scan_hidden_path;
+	int section_from_path;
+	int snapshots;
+	int section_sort;
+	int summary_branches;
+	int summary_log;
+	int summary_tags;
+	diff_type difftype;
+	int branch_sort;
+	int commit_sort;
+	struct string_list mimetypes;
+	struct cgit_filter *about_filter;
+	struct cgit_filter *commit_filter;
+	struct cgit_filter *source_filter;
+	struct cgit_filter *email_filter;
+	struct cgit_filter *owner_filter;
+	struct cgit_filter *auth_filter;
+};
+
+struct cgit_page {
+	time_t modified;
+	time_t expires;
+	size_t size;
+	const char *mimetype;
+	const char *charset;
+	const char *filename;
+	const char *etag;
+	const char *title;
+	int status;
+	const char *statusmsg;
+};
+
+struct cgit_environment {
+	const char *cgit_config;
+	const char *http_host;
+	const char *https;
+	const char *no_http;
+	const char *path_info;
+	const char *query_string;
+	const char *request_method;
+	const char *script_name;
+	const char *server_name;
+	const char *server_port;
+	const char *http_cookie;
+	const char *http_referer;
+	unsigned int content_length;
+	int authenticated;
+};
+
+struct cgit_context {
+	struct cgit_environment env;
+	struct cgit_query qry;
+	struct cgit_config cfg;
+	struct cgit_repo *repo;
+	struct cgit_page page;
+};
+
+typedef int (*write_archive_fn_t)(const char *, const char *);
+
+struct cgit_snapshot_format {
+	const char *suffix;
+	const char *mimetype;
+	write_archive_fn_t write_func;
+};
+
+extern const char *cgit_version;
+
+extern struct cgit_repolist cgit_repolist;
+extern struct cgit_context ctx;
+extern const struct cgit_snapshot_format cgit_snapshot_formats[];
+
+extern char *cgit_default_repo_desc;
+extern struct cgit_repo *cgit_add_repo(const char *url);
+extern struct cgit_repo *cgit_get_repoinfo(const char *url);
+extern void cgit_repo_config_cb(const char *name, const char *value);
+
+extern int chk_zero(int result, char *msg);
+extern int chk_positive(int result, char *msg);
+extern int chk_non_negative(int result, char *msg);
+
+extern char *trim_end(const char *str, char c);
+extern char *ensure_end(const char *str, char c);
+
+extern void strbuf_ensure_end(struct strbuf *sb, char c);
+
+extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
+extern void cgit_free_reflist_inner(struct reflist *list);
+extern int cgit_refs_cb(const char *refname, const struct object_id *oid,
+			int flags, void *cb_data);
+
+extern void cgit_free_commitinfo(struct commitinfo *info);
+extern void cgit_free_taginfo(struct taginfo *info);
+
+void cgit_diff_tree_cb(struct diff_queue_struct *q,
+		       struct diff_options *options, void *data);
+
+extern int cgit_diff_files(const struct object_id *old_oid,
+			   const struct object_id *new_oid,
+			   unsigned long *old_size, unsigned long *new_size,
+			   int *binary, int context, int ignorews,
+			   linediff_fn fn);
+
+extern void cgit_diff_tree(const struct object_id *old_oid,
+			   const struct object_id *new_oid,
+			   filepair_fn fn, const char *prefix, int ignorews);
+
+extern void cgit_diff_commit(struct commit *commit, filepair_fn fn,
+			     const char *prefix);
+
+__attribute__((format (printf,1,2)))
+extern char *fmt(const char *format,...);
+
+__attribute__((format (printf,1,2)))
+extern char *fmtalloc(const char *format,...);
+
+extern struct commitinfo *cgit_parse_commit(struct commit *commit);
+extern struct taginfo *cgit_parse_tag(struct tag *tag);
+extern void cgit_parse_url(const char *url);
+
+extern const char *cgit_repobasename(const char *reponame);
+
+extern int cgit_parse_snapshots_mask(const char *str);
+extern const struct object_id *cgit_snapshot_get_sig(const char *ref,
+						     const struct cgit_snapshot_format *f);
+extern const unsigned cgit_snapshot_format_bit(const struct cgit_snapshot_format *f);
+
+extern int cgit_open_filter(struct cgit_filter *filter, ...);
+extern int cgit_close_filter(struct cgit_filter *filter);
+extern void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix);
+extern void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv);
+extern struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype);
+extern void cgit_cleanup_filters(void);
+extern void cgit_init_filters(void);
+
+extern void cgit_prepare_repo_env(struct cgit_repo * repo);
+
+extern int readfile(const char *path, char **buf, size_t *size);
+
+extern char *expand_macros(const char *txt);
+
+extern char *get_mimetype_for_filename(const char *filename);
+
+#endif /* CGIT_H */
diff --git a/www/git.causal.agency/cgit/cgit.mk b/www/git.causal.agency/cgit/cgit.mk
new file mode 100644
index 00000000..3fcc1ca3
--- /dev/null
+++ b/www/git.causal.agency/cgit/cgit.mk
@@ -0,0 +1,141 @@
+# This Makefile is run in the "git" directory in order to re-use Git's
+# build variables and operating system detection.  Hence all files in
+# CGit's directory must be prefixed with "../".
+include Makefile
+
+CGIT_PREFIX = ../
+
+-include $(CGIT_PREFIX)cgit.conf
+
+# The CGIT_* variables are inherited when this file is called from the
+# main Makefile - they are defined there.
+
+$(CGIT_PREFIX)VERSION: force-version
+	@cd $(CGIT_PREFIX) && '$(SHELL_PATH_SQ)' ./gen-version.sh "$(CGIT_VERSION)"
+-include $(CGIT_PREFIX)VERSION
+.PHONY: force-version
+
+# CGIT_CFLAGS is a separate variable so that we can track it separately
+# and avoid rebuilding all of Git when these variables change.
+CGIT_CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
+CGIT_CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
+CGIT_CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
+
+PKG_CONFIG ?= pkg-config
+
+ifdef NO_C99_FORMAT
+	CFLAGS += -DNO_C99_FORMAT
+endif
+
+ifdef NO_LUA
+	LUA_MESSAGE := linking without specified Lua support
+	CGIT_CFLAGS += -DNO_LUA
+else
+ifeq ($(LUA_PKGCONFIG),)
+	LUA_PKGCONFIG := $(shell for pc in luajit lua lua5.2 lua5.1; do \
+			$(PKG_CONFIG) --exists $$pc 2>/dev/null && echo $$pc && break; \
+			done)
+	LUA_MODE := autodetected
+else
+	LUA_MODE := specified
+endif
+ifneq ($(LUA_PKGCONFIG),)
+	LUA_MESSAGE := linking with $(LUA_MODE) $(LUA_PKGCONFIG)
+	LUA_LIBS := $(shell $(PKG_CONFIG) --libs $(LUA_PKGCONFIG) 2>/dev/null)
+	LUA_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(LUA_PKGCONFIG) 2>/dev/null)
+	CGIT_LIBS += $(LUA_LIBS)
+	CGIT_CFLAGS += $(LUA_CFLAGS)
+else
+	LUA_MESSAGE := linking without autodetected Lua support
+	NO_LUA := YesPlease
+	CGIT_CFLAGS += -DNO_LUA
+endif
+
+endif
+
+# Add -ldl to linker flags on systems that commonly use GNU libc.
+ifneq (,$(filter $(uname_S),Linux GNU GNU/kFreeBSD))
+	CGIT_LIBS += -ldl
+endif
+
+# glibc 2.1+ offers sendfile which the most common C library on Linux
+ifeq ($(uname_S),Linux)
+	HAVE_LINUX_SENDFILE = YesPlease
+endif
+
+ifdef HAVE_LINUX_SENDFILE
+	CGIT_CFLAGS += -DHAVE_LINUX_SENDFILE
+endif
+
+CGIT_OBJ_NAMES += cgit.o
+CGIT_OBJ_NAMES += cache.o
+CGIT_OBJ_NAMES += cmd.o
+CGIT_OBJ_NAMES += configfile.o
+CGIT_OBJ_NAMES += filter.o
+CGIT_OBJ_NAMES += html.o
+CGIT_OBJ_NAMES += parsing.o
+CGIT_OBJ_NAMES += scan-tree.o
+CGIT_OBJ_NAMES += shared.o
+CGIT_OBJ_NAMES += ui-atom.o
+CGIT_OBJ_NAMES += ui-blame.o
+CGIT_OBJ_NAMES += ui-blob.o
+CGIT_OBJ_NAMES += ui-clone.o
+CGIT_OBJ_NAMES += ui-commit.o
+CGIT_OBJ_NAMES += ui-diff.o
+CGIT_OBJ_NAMES += ui-log.o
+CGIT_OBJ_NAMES += ui-patch.o
+CGIT_OBJ_NAMES += ui-plain.o
+CGIT_OBJ_NAMES += ui-refs.o
+CGIT_OBJ_NAMES += ui-repolist.o
+CGIT_OBJ_NAMES += ui-shared.o
+CGIT_OBJ_NAMES += ui-snapshot.o
+CGIT_OBJ_NAMES += ui-ssdiff.o
+CGIT_OBJ_NAMES += ui-stats.o
+CGIT_OBJ_NAMES += ui-summary.o
+CGIT_OBJ_NAMES += ui-tag.o
+CGIT_OBJ_NAMES += ui-tree.o
+
+CGIT_OBJS := $(addprefix $(CGIT_PREFIX),$(CGIT_OBJ_NAMES))
+
+# Only cgit.c reference CGIT_VERSION so we only rebuild its objects when the
+# version changes.
+CGIT_VERSION_OBJS := $(addprefix $(CGIT_PREFIX),cgit.o cgit.sp)
+$(CGIT_VERSION_OBJS): $(CGIT_PREFIX)VERSION
+$(CGIT_VERSION_OBJS): EXTRA_CPPFLAGS = \
+	-DCGIT_VERSION='"$(CGIT_VERSION)"'
+
+# Git handles dependencies using ":=" so dependencies in CGIT_OBJ are not
+# handled by that and we must handle them ourselves.
+cgit_dep_files := $(foreach f,$(CGIT_OBJS),$(dir $f).depend/$(notdir $f).d)
+cgit_dep_files_present := $(wildcard $(cgit_dep_files))
+ifneq ($(cgit_dep_files_present),)
+include $(cgit_dep_files_present)
+endif
+
+ifeq ($(wildcard $(CGIT_PREFIX).depend),)
+missing_dep_dirs += $(CGIT_PREFIX).depend
+endif
+
+$(CGIT_PREFIX).depend:
+	@mkdir -p $@
+
+$(CGIT_PREFIX)CGIT-CFLAGS: FORCE
+	@FLAGS='$(subst ','\'',$(CGIT_CFLAGS))'; \
+	    if test x"$$FLAGS" != x"`cat ../CGIT-CFLAGS 2>/dev/null`" ; then \
+		echo 1>&2 "    * new CGit build flags"; \
+		echo "$$FLAGS" >$(CGIT_PREFIX)CGIT-CFLAGS; \
+            fi
+
+$(CGIT_OBJS): %.o: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS $(missing_dep_dirs)
+	$(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $<
+
+$(CGIT_PREFIX)cgit: $(CGIT_OBJS) GIT-LDFLAGS $(GITLIBS)
+	@echo 1>&1 "    * $(LUA_MESSAGE)"
+	$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) $(CGIT_LIBS)
+
+CGIT_SP_OBJS := $(patsubst %.o,%.sp,$(CGIT_OBJS))
+
+$(CGIT_SP_OBJS): %.sp: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS FORCE
+	$(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $(SPARSE_FLAGS) $<
+
+cgit-sparse: $(CGIT_SP_OBJS)
diff --git a/www/git.causal.agency/cgit/cgit.png b/www/git.causal.agency/cgit/cgit.png
new file mode 100644
index 00000000..425528ee
--- /dev/null
+++ b/www/git.causal.agency/cgit/cgit.png
Binary files differdiff --git a/www/git.causal.agency/cgit/cgitrc.5.txt b/www/git.causal.agency/cgit/cgitrc.5.txt
new file mode 100644
index 00000000..33a6a8c0
--- /dev/null
+++ b/www/git.causal.agency/cgit/cgitrc.5.txt
@@ -0,0 +1,1011 @@
+:man source:   cgit
+:man manual:   cgit
+
+CGITRC(5)
+========
+
+
+NAME
+----
+cgitrc - runtime configuration for cgit
+
+
+SYNOPSIS
+--------
+Cgitrc contains all runtime settings for cgit, including the list of git
+repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank
+lines, and lines starting with '#', are ignored.
+
+
+LOCATION
+--------
+The default location of cgitrc, defined at compile time, is /etc/cgitrc. At
+runtime, cgit will consult the environment variable CGIT_CONFIG and, if
+defined, use its value instead.
+
+
+GLOBAL SETTINGS
+---------------
+about-filter::
+	Specifies a command which will be invoked to format the content of
+	about pages (both top-level and for each repository). The command will
+	get the content of the about-file on its STDIN, the name of the file
+	as the first argument, and the STDOUT from the command will be
+	included verbatim on the about page. Default value: none. See
+	also: "FILTER API".
+
+agefile::
+	Specifies a path, relative to each repository path, which can be used
+	to specify the date and time of the youngest commit in the repository.
+	The first line in the file is used as input to the "parse_date"
+	function in libgit. Recommended timestamp-format is "yyyy-mm-dd
+	hh:mm:ss". You may want to generate this file from a post-receive
+	hook. Default value: "info/web/last-modified".
+
+auth-filter::
+	Specifies a command that will be invoked for authenticating repository
+	access. Receives quite a few arguments, and data on both stdin and
+	stdout for authentication processing. Details follow later in this
+	document. If no auth-filter is specified, no authentication is
+	performed. Default value: none. See also: "FILTER API".
+
+branch-sort::
+	Flag which, when set to "age", enables date ordering in the branch ref
+	list, and when set to "name" enables ordering by branch name. Default
+	value: "name".
+
+cache-about-ttl::
+	Number which specifies the time-to-live, in minutes, for the cached
+	version of the repository about page. See also: "CACHE". Default
+	value: "15".
+
+cache-dynamic-ttl::
+	Number which specifies the time-to-live, in minutes, for the cached
+	version of repository pages accessed without a fixed SHA1. See also:
+	"CACHE". Default value: "5".
+
+cache-repo-ttl::
+	Number which specifies the time-to-live, in minutes, for the cached
+	version of the repository summary page. See also: "CACHE". Default
+	value: "5".
+
+cache-root::
+	Path used to store the cgit cache entries. Default value:
+	"/var/cache/cgit". See also: "MACRO EXPANSION".
+
+cache-root-ttl::
+	Number which specifies the time-to-live, in minutes, for the cached
+	version of the repository index page. See also: "CACHE". Default
+	value: "5".
+
+cache-scanrc-ttl::
+	Number which specifies the time-to-live, in minutes, for the result
+	of scanning a path for git repositories. See also: "CACHE". Default
+	value: "15".
+
+case-sensitive-sort::
+	Sort items in the repo list case sensitively. Default value: "1".
+	See also: repository-sort, section-sort.
+
+cache-size::
+	The maximum number of entries in the cgit cache. When set to "0",
+	caching is disabled. See also: "CACHE". Default value: "0"
+
+cache-snapshot-ttl::
+	Number which specifies the time-to-live, in minutes, for the cached
+	version of snapshots. See also: "CACHE". Default value: "5".
+
+cache-static-ttl::
+	Number which specifies the time-to-live, in minutes, for the cached
+	version of repository pages accessed with a fixed SHA1. See also:
+	"CACHE". Default value: -1".
+
+clone-prefix::
+	Space-separated list of common prefixes which, when combined with a
+	repository url, generates valid clone urls for the repository. This
+	setting is only used if `repo.clone-url` is unspecified. Default value:
+	none.
+
+clone-url::
+	Space-separated list of clone-url templates. This setting is only
+	used if `repo.clone-url` is unspecified. Default value: none. See
+	also: "MACRO EXPANSION", "FILTER API".
+
+commit-filter::
+	Specifies a command which will be invoked to format commit messages.
+	The command will get the message on its STDIN, and the STDOUT from the
+	command will be included verbatim as the commit message, i.e. this can
+	be used to implement bugtracker integration. Default value: none.
+	See also: "FILTER API".
+
+commit-sort::
+	Flag which, when set to "date", enables strict date ordering in the
+	commit log, and when set to "topo" enables strict topological
+	ordering. If unset, the default ordering of "git log" is used. Default
+	value: unset.
+
+css::
+	Url which specifies the css document to include in all cgit pages.
+	Default value: "/cgit.css".
+
+email-filter::
+	Specifies a command which will be invoked to format names and email
+	address of committers, authors, and taggers, as represented in various
+	places throughout the cgit interface. This command will receive an
+	email address and an origin page string as its command line arguments,
+	and the text to format on STDIN. It is to write the formatted text back
+	out onto STDOUT. Default value: none. See also: "FILTER API".
+
+embedded::
+	Flag which, when set to "1", will make cgit generate a html fragment
+	suitable for embedding in other html pages. Default value: none. See
+	also: "noheader".
+
+enable-blame::
+	Flag which, when set to "1", will allow cgit to provide a "blame" page
+	for files, and will make it generate links to that page in appropriate
+	places. Default value: "0".
+
+enable-commit-graph::
+	Flag which, when set to "1", will make cgit print an ASCII-art commit
+	history graph to the left of the commit messages in the repository
+	log page. Default value: "0".
+
+enable-filter-overrides::
+	Flag which, when set to "1", allows all filter settings to be
+	overridden in repository-specific cgitrc files. Default value: none.
+
+enable-follow-links::
+	Flag which, when set to "1", allows users to follow a file in the log
+	view.  Default value: "0".
+
+enable-git-config::
+	Flag which, when set to "1", will allow cgit to use git config to set
+	any repo specific settings. This option is used in conjunction with
+	"scan-path", and must be defined prior, to augment repo-specific
+	settings. The keys gitweb.owner, gitweb.category, gitweb.description,
+	and gitweb.homepage will map to the cgit keys repo.owner, repo.section,
+	repo.desc, and repo.homepage respectively. All git config keys that begin
+	with "cgit." will be mapped to the corresponding "repo." key in cgit.
+	Default value: "0". See also: scan-path, section-from-path.
+
+enable-http-clone::
+	If set to "1", cgit will act as a dumb HTTP endpoint for git clones.
+	You can add "http://$HTTP_HOST$SCRIPT_NAME/$CGIT_REPO_URL" to clone-url
+	to expose this feature. If you use an alternate way of serving git
+	repositories, you may wish to disable this. Default value: "1".
+
+enable-html-serving::
+	Flag which, when set to "1", will allow the /plain handler to serve
+	mimetype headers that result in the file being treated as HTML by the
+	browser. When set to "0", such file types are returned instead as
+	text/plain or application/octet-stream. Default value: "0". See also:
+	"repo.enable-html-serving".
+
+enable-index-links::
+	Flag which, when set to "1", will make cgit generate extra links for
+	each repo in the repository index (specifically, to the "summary",
+	"commit" and "tree" pages). Default value: "0".
+
+enable-index-owner::
+	Flag which, when set to "1", will make cgit display the owner of
+	each repo in the repository index. Default value: "1".
+
+enable-log-filecount::
+	Flag which, when set to "1", will make cgit print the number of
+	modified files for each commit on the repository log page. Default
+	value: "0".
+
+enable-log-linecount::
+	Flag which, when set to "1", will make cgit print the number of added
+	and removed lines for each commit on the repository log page. Default
+	value: "0".
+
+enable-remote-branches::
+	Flag which, when set to "1", will make cgit display remote branches
+	in the summary and refs views. Default value: "0". See also:
+	"repo.enable-remote-branches".
+
+enable-subject-links::
+	Flag which, when set to "1", will make cgit use the subject of the
+	parent commit as link text when generating links to parent commits
+	in commit view. Default value: "0". See also:
+	"repo.enable-subject-links".
+
+enable-tree-linenumbers::
+	Flag which, when set to "1", will make cgit generate linenumber links
+	for plaintext blobs printed in the tree view. Default value: "1".
+
+favicon::
+	Url used as link to a shortcut icon for cgit. It is suggested to use
+	the value "/favicon.ico" since certain browsers will ignore other
+	values. Default value: "/favicon.ico".
+
+footer::
+	The content of the file specified with this option will be included
+	verbatim at the bottom of all pages (i.e. it replaces the standard
+	"generated by..." message. Default value: none.
+
+head-include::
+	The content of the file specified with this option will be included
+	verbatim in the html HEAD section on all pages. Default value: none.
+
+header::
+	The content of the file specified with this option will be included
+	verbatim at the top of all pages. Default value: none.
+
+include::
+	Name of a configfile to include before the rest of the current config-
+	file is parsed. Default value: none. See also: "MACRO EXPANSION".
+
+local-time::
+	Flag which, if set to "1", makes cgit print commit and tag times in the
+	servers timezone. Default value: "0".
+
+logo::
+	Url which specifies the source of an image which will be used as a logo
+	on all cgit pages. Default value: "/cgit.png".
+
+logo-link::
+	Url loaded when clicking on the cgit logo image. If unspecified the
+	calculated url of the repository index page will be used. Default
+	value: none.
+
+max-atom-items::
+	Specifies the number of items to display in atom feeds view. Default
+	value: "10".
+
+max-blob-size::
+	Specifies the maximum size of a blob to display HTML for in KBytes.
+	Default value: "0" (limit disabled).
+
+max-commit-count::
+	Specifies the number of entries to list per page in "log" view. Default
+	value: "50".
+
+max-message-length::
+	Specifies the maximum number of commit message characters to display in
+	"log" view. Default value: "80".
+
+max-repo-count::
+	Specifies the number of entries to list per page on the	repository
+	index page. Default value: "50".
+
+max-repodesc-length::
+	Specifies the maximum number of repo description characters to display
+	on the repository index page. Default value: "80".
+
+max-stats::
+	Set the default maximum statistics period. Valid values are "week",
+	"month", "quarter" and "year". If unspecified, statistics are
+	disabled. Default value: none. See also: "repo.max-stats".
+
+mimetype.<ext>::
+	Set the mimetype for the specified filename extension. This is used
+	by the `plain` command when returning blob content.
+
+mimetype-file::
+	Specifies the file to use for automatic mimetype lookup. If specified
+	then this field is used as a fallback when no "mimetype.<ext>" match is
+	found. If unspecified then no such lookup is performed. The typical file
+	to use on a Linux system is /etc/mime.types. The format of the file must
+	comply to:
+	- a comment line is an empty line or a line starting with a hash (#),
+	  optionally preceded by whitespace
+	- a non-comment line starts with the mimetype (like image/png), followed
+	  by one or more file extensions (like jpg), all separated by whitespace
+	Default value: none. See also: "mimetype.<ext>".
+
+module-link::
+	Text which will be used as the formatstring for a hyperlink when a
+	submodule is printed in a directory listing. The arguments for the
+	formatstring are the path and SHA1 of the submodule commit. Default
+	value: none.
+
+noplainemail::
+	If set to "1" showing full author email addresses will be disabled.
+	Default value: "0".
+
+noheader::
+	Flag which, when set to "1", will make cgit omit the standard header
+	on all pages. Default value: none. See also: "embedded".
+
+owner-filter::
+	Specifies a command which will be invoked to format the Owner
+	column of the main page.  The command will get the owner on STDIN,
+	and the STDOUT from the command will be included verbatim in the
+	table.  This can be used to link to additional context such as an
+	owners home page.  When active this filter is used instead of the
+	default owner query url.  Default value: none.
+	See also: "FILTER API".
+
+project-list::
+	A list of subdirectories inside of scan-path, relative to it, that
+	should loaded as git repositories. This must be defined prior to
+	scan-path. Default value: none. See also: scan-path, "MACRO
+	EXPANSION".
+
+readme::
+	Text which will be used as default value for "repo.readme". Multiple
+	config keys may be specified, and cgit will use the first found file
+	in this list. This is useful in conjunction with scan-path. Default
+	value: none. See also: scan-path, repo.readme.
+
+remove-suffix::
+	If set to "1" and scan-path is enabled, if any repositories are found
+	with a suffix of ".git", this suffix will be removed for the url and
+	name. This must be defined prior to scan-path. Default value: "0".
+	See also: scan-path.
+
+renamelimit::
+	Maximum number of files to consider when detecting renames. The value
+	 "-1" uses the compiletime value in git (for further info, look at
+	  `man git-diff`). Default value: "-1".
+
+repository-sort::
+	The way in which repositories in each section are sorted. Valid values
+	are "name" for sorting by the repo name or "age" for sorting by the
+	most recently updated repository. Default value: "name". See also:
+	section, case-sensitive-sort, section-sort.
+
+robots::
+	Text used as content for the "robots" meta-tag. Default value:
+	"index, nofollow".
+
+root-desc::
+	Text printed below the heading on the repository index page. Default
+	value: "a fast webinterface for the git dscm".
+
+root-readme::
+	The content of the file specified with this option will be included
+	verbatim below the "about" link on the repository index page. Default
+	value: none.
+
+root-title::
+	Text printed as heading on the repository index page. Default value:
+	"Git Repository Browser".
+
+scan-hidden-path::
+	If set to "1" and scan-path is enabled, scan-path will recurse into
+	directories whose name starts with a period ('.'). Otherwise,
+	scan-path will stay away from such directories (considered as
+	"hidden"). Note that this does not apply to the ".git" directory in
+	non-bare repos. This must be defined prior to scan-path.
+	Default value: 0. See also: scan-path.
+
+scan-path::
+	A path which will be scanned for repositories. If caching is enabled,
+	the result will be cached as a cgitrc include-file in the cache
+	directory. If project-list has been defined prior to scan-path,
+	scan-path loads only the directories listed in the file pointed to by
+	project-list. Be advised that only the global settings taken
+	before the scan-path directive will be applied to each repository.
+	Default value: none. See also: cache-scanrc-ttl, project-list,
+	"MACRO EXPANSION".
+
+section::
+	The name of the current repository section - all repositories defined
+	after this option will inherit the current section name. Default value:
+	none.
+
+section-sort::
+	Flag which, when set to "1", will sort the sections on the repository
+	listing by name. Set this flag to "0" if the order in the cgitrc file should
+	be preserved. Default value: "1". See also: section,
+	case-sensitive-sort, repository-sort.
+
+section-from-path::
+	A number which, if defined prior to scan-path, specifies how many
+	path elements from each repo path to use as a default section name.
+	If negative, cgit will discard the specified number of path elements
+	above the repo directory. Default value: "0".
+
+side-by-side-diffs::
+	If set to "1" shows side-by-side diffs instead of unidiffs per
+	default. Default value: "0".
+
+snapshots::
+	Text which specifies the default set of snapshot formats that cgit
+	generates links for. The value is a space-separated list of zero or
+	more of the values "tar", "tar.gz", "tar.bz2", "tar.lz", "tar.xz",
+	"tar.zst" and "zip". The special value "all" enables all snapshot
+	formats. Default value: none.
+	All compressors use default settings. Some settings can be influenced
+	with environment variables, for example set ZSTD_CLEVEL=10 in web
+	server environment for higher (but slower) zstd compression.
+
+source-filter::
+	Specifies a command which will be invoked to format plaintext blobs
+	in the tree view. The command will get the blob content on its STDIN
+	and the name of the blob as its only command line argument. The STDOUT
+	from the command will be included verbatim as the blob contents, i.e.
+	this can be used to implement e.g. syntax highlighting. Default value:
+	none. See also: "FILTER API".
+
+summary-branches::
+	Specifies the number of branches to display in the repository "summary"
+	view. Default value: "10".
+
+summary-log::
+	Specifies the number of log entries to display in the repository
+	"summary" view. Default value: "10".
+
+summary-tags::
+	Specifies the number of tags to display in the repository "summary"
+	view. Default value: "10".
+
+strict-export::
+	Filename which, if specified, needs to be present within the repository
+	for cgit to allow access to that repository. This can be used to emulate
+	gitweb's EXPORT_OK and STRICT_EXPORT functionality and limit cgit's
+	repositories to match those exported by git-daemon. This option must
+	be defined prior to scan-path.
+
+virtual-root::
+	Url which, if specified, will be used as root for all cgit links. It
+	will also cause cgit to generate 'virtual urls', i.e. urls like
+	'/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
+	value: none.
+	NOTE: cgit has recently learned how to use PATH_INFO to achieve the
+	same kind of virtual urls, so this option will probably be deprecated.
+
+
+REPOSITORY SETTINGS
+-------------------
+repo.about-filter::
+	Override the default about-filter. Default value: none. See also:
+	"enable-filter-overrides". See also: "FILTER API".
+
+repo.branch-sort::
+	Flag which, when set to "age", enables date ordering in the branch ref
+	list, and when set to "name" enables ordering by branch name. Default
+	value: "name".
+
+repo.clone-url::
+	A list of space-separated urls which can be used to clone this repo.
+	Default value: none. See also: "MACRO EXPANSION".
+
+repo.commit-filter::
+	Override the default commit-filter. Default value: none. See also:
+	"enable-filter-overrides". See also: "FILTER API".
+
+repo.commit-sort::
+	Flag which, when set to "date", enables strict date ordering in the
+	commit log, and when set to "topo" enables strict topological
+	ordering. If unset, the default ordering of "git log" is used. Default
+	value: unset.
+
+repo.defbranch::
+	The name of the default branch for this repository. If no such branch
+	exists in the repository, the first branch name (when sorted) is used
+	as default instead. Default value: branch pointed to by HEAD, or
+	"master" if there is no suitable HEAD.
+
+repo.desc::
+	The value to show as repository description. Default value: none.
+
+repo.email-filter::
+	Override the default email-filter. Default value: none. See also:
+	"enable-filter-overrides". See also: "FILTER API".
+
+repo.enable-blame::
+	A flag which can be used to disable the global setting
+	`enable-blame'. Default value: none.
+
+repo.enable-commit-graph::
+	A flag which can be used to disable the global setting
+	`enable-commit-graph'. Default value: none.
+
+repo.enable-html-serving::
+	A flag which can be used to override the global setting
+	`enable-html-serving`. Default value: none.
+
+repo.enable-log-filecount::
+	A flag which can be used to disable the global setting
+	`enable-log-filecount'. Default value: none.
+
+repo.enable-log-linecount::
+	A flag which can be used to disable the global setting
+	`enable-log-linecount'. Default value: none.
+
+repo.enable-remote-branches::
+	Flag which, when set to "1", will make cgit display remote branches
+	in the summary and refs views. Default value: <enable-remote-branches>.
+
+repo.enable-subject-links::
+	A flag which can be used to override the global setting
+	`enable-subject-links'. Default value: none.
+
+repo.extra-head-content::
+	This value will be added verbatim to the head section of each page
+	displayed for this repo. Default value: none.
+
+repo.hide::
+	Flag which, when set to "1", hides the repository from the repository
+	index. The repository can still be accessed by providing a direct path.
+	Default value: "0". See also: "repo.ignore".
+
+repo.homepage::
+	The value to show as repository homepage. Default value: none.
+
+repo.ignore::
+	Flag which, when set to "1", ignores the repository. The repository
+	is not shown in the index and cannot be accessed by providing a direct
+	path. Default value: "0". See also: "repo.hide".
+
+repo.logo::
+	Url which specifies the source of an image which will be used as a logo
+	on this repo's pages. Default value: global logo.
+
+repo.logo-link::
+	Url loaded when clicking on the cgit logo image. If unspecified the
+	calculated url of the repository index page will be used. Default
+	value: global logo-link.
+
+repo.module-link::
+	Text which will be used as the formatstring for a hyperlink when a
+	submodule is printed in a directory listing. The arguments for the
+	formatstring are the path and SHA1 of the submodule commit. Default
+	value: <module-link>
+
+repo.module-link.<path>::
+	Text which will be used as the formatstring for a hyperlink when a
+	submodule with the specified subdirectory path is printed in a
+	directory listing. The only argument for the formatstring is the SHA1
+	of the submodule commit. Default value: none.
+
+repo.max-stats::
+	Override the default maximum statistics period. Valid values are equal
+	to the values specified for the global "max-stats" setting. Default
+	value: none.
+
+repo.name::
+	The value to show as repository name. Default value: <repo.url>.
+
+repo.owner::
+	A value used to identify the owner of the repository. Default value:
+	none.
+
+repo.owner-filter::
+	Override the default owner-filter. Default value: none. See also:
+	"enable-filter-overrides". See also: "FILTER API".
+
+repo.path::
+	An absolute path to the repository directory. For non-bare repositories
+	this is the .git-directory. Default value: none.
+
+repo.readme::
+	A path (relative to <repo.path>) which specifies a file to include
+	verbatim as the "About" page for this repo. You may also specify a
+	git refspec by head or by hash by prepending the refspec followed by
+	a colon. For example, "master:docs/readme.mkd". If the value begins
+	with a colon, i.e. ":docs/readme.rst", the default branch of the
+	repository will be used. Sharing any file will expose that entire
+	directory tree to the "/about/PATH" endpoints, so be sure that there
+	are no non-public files located in the same directory as the readme
+	file. Default value: <readme>.
+
+repo.section::
+	Override the current section name for this repository. Default value:
+	none.
+
+repo.snapshots::
+	A mask of snapshot formats for this repo that cgit generates links for,
+	restricted by the global "snapshots" setting. Default value:
+	<snapshots>.
+
+repo.snapshot-prefix::
+	Prefix to use for snapshot links instead of the repository basename.
+	For example, the "linux-stable" repository may wish to set this to
+	"linux" so that snapshots are in the format "linux-3.15.4" instead
+	of "linux-stable-3.15.4".  Default value: <empty> meaning to use
+	the repository basename.
+
+repo.source-filter::
+	Override the default source-filter. Default value: none. See also:
+	"enable-filter-overrides". See also: "FILTER API".
+
+repo.url::
+	The relative url used to access the repository. This must be the first
+	setting specified for each repo. Default value: none.
+
+
+REPOSITORY-SPECIFIC CGITRC FILE
+-------------------------------
+When the option "scan-path" is used to auto-discover git repositories, cgit
+will try to parse the file "cgitrc" within any found repository. Such a
+repo-specific config file may contain any of the repo-specific options
+described above, except "repo.url" and "repo.path". Additionally, the "filter"
+options are only acknowledged in repo-specific config files when
+"enable-filter-overrides" is set to "1".
+
+Note: the "repo." prefix is dropped from the option names in repo-specific
+config files, e.g. "repo.desc" becomes "desc".
+
+
+FILTER API
+----------
+By default, filters are separate processes that are executed each time they
+are needed.  Alternative technologies may be used by prefixing the filter
+specification with the relevant string; available values are:
+
+'exec:'::
+	The default "one process per filter" mode.
+
+'lua:'::
+	Executes the script using a built-in Lua interpreter. The script is
+	loaded once per execution of cgit, and may be called multiple times
+	during cgit's lifetime, making it a good choice for repeated filters
+	such as the 'email filter'. It responds to three functions:
+
+	'filter_open(argument1, argument2, argument3, ...)'::
+		This is called upon activation of the filter for a particular
+		set of data.
+	'filter_write(buffer)'::
+		This is called whenever cgit writes data to the webpage.
+	'filter_close()'::
+		This is called when the current filtering operation is
+		completed. It must return an integer value. Usually 0
+		indicates success.
+
+	Additionally, cgit exposes to the Lua the following built-in functions:
+
+	'html(str)'::
+		Writes 'str' to the webpage.
+	'html_txt(str)'::
+		HTML escapes and writes 'str' to the webpage.
+	'html_attr(str)'::
+		HTML escapes for an attribute and writes "str' to the webpage.
+	'html_url_path(str)'::
+		URL escapes for a path and writes 'str' to the webpage.
+	'html_url_arg(str)'::
+		URL escapes for an argument and writes 'str' to the webpage.
+	'html_include(file)'::
+		Includes 'file' in webpage.
+
+
+Parameters are provided to filters as follows.
+
+about filter::
+	This filter is given a single parameter: the filename of the source
+	file to filter. The filter can use the filename to determine (for
+	example) the type of syntax to follow when formatting the readme file.
+	The about text that is to be filtered is available on standard input
+	and the filtered text is expected on standard output.
+
+auth filter::
+	The authentication filter receives 12 parameters:
+	  - filter action, explained below, which specifies which action the
+	    filter is called for
+	  - http cookie
+	  - http method
+	  - http referer
+	  - http path
+	  - http https flag
+	  - cgit repo
+	  - cgit page
+	  - cgit url
+	  - cgit login url
+	When the filter action is "body", this filter must write to output the
+	HTML for displaying the login form, which POSTs to the login url. When
+	the filter action is "authenticate-cookie", this filter must validate
+	the http cookie and return a 0 if it is invalid or 1 if it is invalid,
+	in the exit code / close function. If the filter action is
+	"authenticate-post", this filter receives POST'd parameters on
+	standard input, and should write a complete CGI response, preferably
+	with a 302 redirect, and write to output one or more "Set-Cookie"
+	HTTP headers, each followed by a newline.
+
+	Please see `filters/simple-authentication.lua` for a clear example
+	script that may be modified.
+
+commit filter::
+	This filter is given no arguments. The commit message text that is to
+	be filtered is available on standard input and the filtered text is
+	expected on standard output.
+
+email filter::
+	This filter is given two parameters: the email address of the relevant
+	author and a string indicating the originating page. The filter will
+	then receive the text string to format on standard input and is
+	expected to write to standard output the formatted text to be included
+	in the page.
+
+owner filter::
+	This filter is given no arguments.  The owner text is available on
+	standard input and the filter is expected to write to standard
+	output.  The output is included in the Owner column.
+
+source filter::
+	This filter is given a single parameter: the filename of the source
+	file to filter. The filter can use the filename to determine (for
+	example) the syntax highlighting mode. The contents of the source
+	file that is to be filtered is available on standard input and the
+	filtered contents is expected on standard output.
+
+
+All filters are handed the following environment variables:
+
+- CGIT_REPO_URL (from repo.url)
+- CGIT_REPO_NAME (from repo.name)
+- CGIT_REPO_PATH (from repo.path)
+- CGIT_REPO_OWNER (from repo.owner)
+- CGIT_REPO_DEFBRANCH (from repo.defbranch)
+- CGIT_REPO_SECTION (from repo.section)
+- CGIT_REPO_CLONE_URL (from repo.clone-url)
+
+If a setting is not defined for a repository and the corresponding global
+setting is also not defined (if applicable), then the corresponding
+environment variable will be unset.
+
+
+MACRO EXPANSION
+---------------
+The following cgitrc options support a simple macro expansion feature,
+where tokens prefixed with "$" are replaced with the value of a similarly
+named environment variable:
+
+- cache-root
+- include
+- project-list
+- scan-path
+
+Macro expansion will also happen on the content of $CGIT_CONFIG, if
+defined.
+
+One usage of this feature is virtual hosting, which in its simplest form
+can be accomplished by adding the following line to /etc/cgitrc:
+
+	include=/etc/cgitrc.d/$HTTP_HOST
+
+The following options are expanded during request processing, and support
+the environment variables defined in "FILTER API":
+
+- clone-url
+- repo.clone-url
+
+
+CACHE
+-----
+
+All cache ttl values are in minutes. Negative ttl values indicate that a page
+type will never expire, and thus the first time a URL is accessed, the result
+will be cached indefinitely, even if the underlying git repository changes.
+Conversely, when a ttl value is zero, the cache is disabled for that
+particular page type, and the page type is never cached.
+
+SIGNATURES
+----------
+
+Cgit can host .asc signatures corresponding to various snapshot formats,
+through use of git notes. For example, the following command may be used to
+add a signature to a .tar.xz archive:
+
+    git notes --ref=refs/notes/signatures/tar.xz add -C "$(
+	gpg --output - --armor --detach-sign cgit-1.1.tar.xz |
+	git hash-object -w --stdin
+    )" v1.1
+
+If it is instead desirable to attach a signature of the underlying .tar, this
+will be linked, as a special case, beside a .tar.* link that does not have its
+own signature. For example, a signature of a tarball of the latest tag might
+be added with a similar command:
+
+    tag="$(git describe --abbrev=0)"
+    git notes --ref=refs/notes/signatures/tar add -C "$(
+        git archive --format tar --prefix "cgit-${tag#v}/" "$tag" |
+        gpg --output - --armor --detach-sign |
+        git hash-object -w --stdin
+    )" "$tag"
+
+Since git-archive(1) is expected to produce stable output between versions,
+this allows one to generate a long-term signature of the contents of a given
+tag.
+
+EXAMPLE CGITRC FILE
+-------------------
+
+....
+# Enable caching of up to 1000 output entries
+cache-size=1000
+
+
+# Specify some default clone urls using macro expansion
+clone-url=git://foo.org/$CGIT_REPO_URL git@foo.org:$CGIT_REPO_URL
+
+# Specify the css url
+css=/css/cgit.css
+
+
+# Show owner on index page
+enable-index-owner=1
+
+
+# Allow http transport git clone
+enable-http-clone=1
+
+
+# Show extra links for each repository on the index page
+enable-index-links=1
+
+
+# Enable blame page and create links to it from tree page
+enable-blame=1
+
+
+# Enable ASCII art commit history graph on the log pages
+enable-commit-graph=1
+
+
+# Show number of affected files per commit on the log pages
+enable-log-filecount=1
+
+
+# Show number of added/removed lines per commit on the log pages
+enable-log-linecount=1
+
+
+# Sort branches by date
+branch-sort=age
+
+
+# Add a cgit favicon
+favicon=/favicon.ico
+
+
+# Use a custom logo
+logo=/img/mylogo.png
+
+
+# Enable statistics per week, month and quarter
+max-stats=quarter
+
+
+# Set the title and heading of the repository index page
+root-title=example.com git repositories
+
+
+# Set a subheading for the repository index page
+root-desc=tracking the foobar development
+
+
+# Include some more info about example.com on the index page
+root-readme=/var/www/htdocs/about.html
+
+
+# Allow download of tar.gz, tar.bz2 and zip-files
+snapshots=tar.gz tar.bz2 zip
+
+
+##
+## List of common mimetypes
+##
+
+mimetype.gif=image/gif
+mimetype.html=text/html
+mimetype.jpg=image/jpeg
+mimetype.jpeg=image/jpeg
+mimetype.pdf=application/pdf
+mimetype.png=image/png
+mimetype.svg=image/svg+xml
+
+
+# Highlight source code with python pygments-based highlighter
+source-filter=/var/www/cgit/filters/syntax-highlighting.py
+
+# Format markdown, restructuredtext, manpages, text files, and html files
+# through the right converters
+about-filter=/var/www/cgit/filters/about-formatting.sh
+
+##
+## Search for these files in the root of the default branch of repositories
+## for coming up with the about page:
+##
+readme=:README.md
+readme=:readme.md
+readme=:README.mkd
+readme=:readme.mkd
+readme=:README.rst
+readme=:readme.rst
+readme=:README.html
+readme=:readme.html
+readme=:README.htm
+readme=:readme.htm
+readme=:README.txt
+readme=:readme.txt
+readme=:README
+readme=:readme
+readme=:INSTALL.md
+readme=:install.md
+readme=:INSTALL.mkd
+readme=:install.mkd
+readme=:INSTALL.rst
+readme=:install.rst
+readme=:INSTALL.html
+readme=:install.html
+readme=:INSTALL.htm
+readme=:install.htm
+readme=:INSTALL.txt
+readme=:install.txt
+readme=:INSTALL
+readme=:install
+
+
+##
+## List of repositories.
+## PS: Any repositories listed when section is unset will not be
+##     displayed under a section heading
+## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
+##      and included like this:
+##        include=/etc/cgitrepos
+##
+
+
+repo.url=foo
+repo.path=/pub/git/foo.git
+repo.desc=the master foo repository
+repo.owner=fooman@example.com
+repo.readme=info/web/about.html
+
+
+repo.url=bar
+repo.path=/pub/git/bar.git
+repo.desc=the bars for your foo
+repo.owner=barman@example.com
+repo.readme=info/web/about.html
+
+
+# The next repositories will be displayed under the 'extras' heading
+section=extras
+
+
+repo.url=baz
+repo.path=/pub/git/baz.git
+repo.desc=a set of extensions for bar users
+
+repo.url=wiz
+repo.path=/pub/git/wiz.git
+repo.desc=the wizard of foo
+
+
+# Add some mirrored repositories
+section=mirrors
+
+
+repo.url=git
+repo.path=/pub/git/git.git
+repo.desc=the dscm
+
+
+repo.url=linux
+repo.path=/pub/git/linux.git
+repo.desc=the kernel
+
+# Disable adhoc downloads of this repo
+repo.snapshots=0
+
+# Disable line-counts for this repo
+repo.enable-log-linecount=0
+
+# Restrict the max statistics period for this repo
+repo.max-stats=month
+....
+
+
+BUGS
+----
+Comments currently cannot appear on the same line as a setting; the comment
+will be included as part of the value. E.g. this line:
+
+	robots=index  # allow indexing
+
+will generate the following html element:
+
+	<meta name='robots' content='index  # allow indexing'/>
+
+
+
+AUTHOR
+------
+Lars Hjemli <hjemli@gmail.com>
+Jason A. Donenfeld <Jason@zx2c4.com>
diff --git a/www/git.causal.agency/cgit/cmd.c b/www/git.causal.agency/cgit/cmd.c
new file mode 100644
index 00000000..bf6d8f51
--- /dev/null
+++ b/www/git.causal.agency/cgit/cmd.c
@@ -0,0 +1,208 @@
+/* cmd.c: the cgit command dispatcher
+ *
+ * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "cmd.h"
+#include "cache.h"
+#include "ui-shared.h"
+#include "ui-atom.h"
+#include "ui-blame.h"
+#include "ui-blob.h"
+#include "ui-clone.h"
+#include "ui-commit.h"
+#include "ui-diff.h"
+#include "ui-log.h"
+#include "ui-patch.h"
+#include "ui-plain.h"
+#include "ui-refs.h"
+#include "ui-repolist.h"
+#include "ui-snapshot.h"
+#include "ui-stats.h"
+#include "ui-summary.h"
+#include "ui-tag.h"
+#include "ui-tree.h"
+
+static void HEAD_fn(void)
+{
+	cgit_clone_head();
+}
+
+static void atom_fn(void)
+{
+	cgit_print_atom(ctx.qry.head, ctx.qry.path, ctx.cfg.max_atom_items);
+}
+
+static void about_fn(void)
+{
+	if (ctx.repo) {
+		size_t path_info_len = ctx.env.path_info ? strlen(ctx.env.path_info) : 0;
+		if (!ctx.qry.path &&
+		    ctx.qry.url[strlen(ctx.qry.url) - 1] != '/' &&
+		    (!path_info_len || ctx.env.path_info[path_info_len - 1] != '/')) {
+			char *currenturl = cgit_currenturl();
+			char *redirect = fmtalloc("%s/", currenturl);
+			cgit_redirect(redirect, true);
+			free(currenturl);
+			free(redirect);
+		} else if (ctx.repo->readme.nr)
+			cgit_print_repo_readme(ctx.qry.path);
+		else if (ctx.repo->homepage)
+			cgit_redirect(ctx.repo->homepage, false);
+		else {
+			char *currenturl = cgit_currenturl();
+			char *redirect = fmtalloc("%s../", currenturl);
+			cgit_redirect(redirect, false);
+			free(currenturl);
+			free(redirect);
+		}
+	} else
+		cgit_print_site_readme();
+}
+
+static void blame_fn(void)
+{
+	if (ctx.repo->enable_blame)
+		cgit_print_blame();
+	else
+		cgit_print_error_page(403, "Forbidden", "Blame is disabled");
+}
+
+static void blob_fn(void)
+{
+	cgit_print_blob(ctx.qry.sha1, ctx.qry.path, ctx.qry.head, 0);
+}
+
+static void commit_fn(void)
+{
+	cgit_print_commit(ctx.qry.sha1, ctx.qry.path);
+}
+
+static void diff_fn(void)
+{
+	cgit_print_diff(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1, 0);
+}
+
+static void rawdiff_fn(void)
+{
+	cgit_print_diff(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1, 1);
+}
+
+static void info_fn(void)
+{
+	cgit_clone_info();
+}
+
+static void log_fn(void)
+{
+	cgit_print_log(ctx.qry.sha1, ctx.qry.ofs, ctx.cfg.max_commit_count,
+		       ctx.qry.grep, ctx.qry.search, ctx.qry.path, 1,
+		       ctx.repo->enable_commit_graph,
+		       ctx.repo->commit_sort);
+}
+
+static void ls_cache_fn(void)
+{
+	ctx.page.mimetype = "text/plain";
+	ctx.page.filename = "ls-cache.txt";
+	cgit_print_http_headers();
+	cache_ls(ctx.cfg.cache_root);
+}
+
+static void objects_fn(void)
+{
+	cgit_clone_objects();
+}
+
+static void repolist_fn(void)
+{
+	cgit_print_repolist();
+}
+
+static void patch_fn(void)
+{
+	cgit_print_patch(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path);
+}
+
+static void plain_fn(void)
+{
+	cgit_print_plain();
+}
+
+static void refs_fn(void)
+{
+	cgit_print_refs();
+}
+
+static void snapshot_fn(void)
+{
+	cgit_print_snapshot(ctx.qry.head, ctx.qry.sha1, ctx.qry.path,
+			    ctx.qry.nohead);
+}
+
+static void stats_fn(void)
+{
+	cgit_show_stats();
+}
+
+static void summary_fn(void)
+{
+	cgit_print_summary();
+}
+
+static void tag_fn(void)
+{
+	cgit_print_tag(ctx.qry.sha1);
+}
+
+static void tree_fn(void)
+{
+	cgit_print_tree(ctx.qry.sha1, ctx.qry.path);
+}
+
+#define def_cmd(name, want_repo, want_vpath, is_clone) \
+	{#name, name##_fn, want_repo, want_vpath, is_clone}
+
+struct cgit_cmd *cgit_get_cmd(void)
+{
+	static struct cgit_cmd cmds[] = {
+		def_cmd(HEAD, 1, 0, 1),
+		def_cmd(atom, 1, 0, 0),
+		def_cmd(about, 0, 0, 0),
+		def_cmd(blame, 1, 1, 0),
+		def_cmd(blob, 1, 0, 0),
+		def_cmd(commit, 1, 1, 0),
+		def_cmd(diff, 1, 1, 0),
+		def_cmd(info, 1, 0, 1),
+		def_cmd(log, 1, 1, 0),
+		def_cmd(ls_cache, 0, 0, 0),
+		def_cmd(objects, 1, 0, 1),
+		def_cmd(patch, 1, 1, 0),
+		def_cmd(plain, 1, 0, 0),
+		def_cmd(rawdiff, 1, 1, 0),
+		def_cmd(refs, 1, 0, 0),
+		def_cmd(repolist, 0, 0, 0),
+		def_cmd(snapshot, 1, 0, 0),
+		def_cmd(stats, 1, 1, 0),
+		def_cmd(summary, 1, 0, 0),
+		def_cmd(tag, 1, 0, 0),
+		def_cmd(tree, 1, 1, 0),
+	};
+	int i;
+
+	if (ctx.qry.page == NULL) {
+		if (ctx.repo)
+			ctx.qry.page = "summary";
+		else
+			ctx.qry.page = "repolist";
+	}
+
+	for (i = 0; i < sizeof(cmds)/sizeof(*cmds); i++)
+		if (!strcmp(ctx.qry.page, cmds[i].name))
+			return &cmds[i];
+	return NULL;
+}
diff --git a/www/git.causal.agency/cgit/cmd.h b/www/git.causal.agency/cgit/cmd.h
new file mode 100644
index 00000000..6249b1d8
--- /dev/null
+++ b/www/git.causal.agency/cgit/cmd.h
@@ -0,0 +1,16 @@
+#ifndef CMD_H
+#define CMD_H
+
+typedef void (*cgit_cmd_fn)(void);
+
+struct cgit_cmd {
+	const char *name;
+	cgit_cmd_fn fn;
+	unsigned int want_repo:1,
+		want_vpath:1,
+		is_clone:1;
+};
+
+extern struct cgit_cmd *cgit_get_cmd(void);
+
+#endif /* CMD_H */
diff --git a/www/git.causal.agency/cgit/configfile.c b/www/git.causal.agency/cgit/configfile.c
new file mode 100644
index 00000000..e0391091
--- /dev/null
+++ b/www/git.causal.agency/cgit/configfile.c
@@ -0,0 +1,90 @@
+/* configfile.c: parsing of config files
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include <git-compat-util.h>
+#include "configfile.h"
+
+static int next_char(FILE *f)
+{
+	int c = fgetc(f);
+	if (c == '\r') {
+		c = fgetc(f);
+		if (c != '\n') {
+			ungetc(c, f);
+			c = '\r';
+		}
+	}
+	return c;
+}
+
+static void skip_line(FILE *f)
+{
+	int c;
+
+	while ((c = next_char(f)) && c != '\n' && c != EOF)
+		;
+}
+
+static int read_config_line(FILE *f, struct strbuf *name, struct strbuf *value)
+{
+	int c = next_char(f);
+
+	strbuf_reset(name);
+	strbuf_reset(value);
+
+	/* Skip comments and preceding spaces. */
+	for(;;) {
+		if (c == EOF)
+			return 0;
+		else if (c == '#' || c == ';')
+			skip_line(f);
+		else if (!isspace(c))
+			break;
+		c = next_char(f);
+	}
+
+	/* Read variable name. */
+	while (c != '=') {
+		if (c == '\n' || c == EOF)
+			return 0;
+		strbuf_addch(name, c);
+		c = next_char(f);
+	}
+
+	/* Read variable value. */
+	c = next_char(f);
+	while (c != '\n' && c != EOF) {
+		strbuf_addch(value, c);
+		c = next_char(f);
+	}
+
+	return 1;
+}
+
+int parse_configfile(const char *filename, configfile_value_fn fn)
+{
+	static int nesting;
+	struct strbuf name = STRBUF_INIT;
+	struct strbuf value = STRBUF_INIT;
+	FILE *f;
+
+	/* cancel deeply nested include-commands */
+	if (nesting > 8)
+		return -1;
+	if (!(f = fopen(filename, "r")))
+		return -1;
+	nesting++;
+	while (read_config_line(f, &name, &value))
+		fn(name.buf, value.buf);
+	nesting--;
+	fclose(f);
+	strbuf_release(&name);
+	strbuf_release(&value);
+	return 0;
+}
+
diff --git a/www/git.causal.agency/cgit/configfile.h b/www/git.causal.agency/cgit/configfile.h
new file mode 100644
index 00000000..af7ca197
--- /dev/null
+++ b/www/git.causal.agency/cgit/configfile.h
@@ -0,0 +1,10 @@
+#ifndef CONFIGFILE_H
+#define CONFIGFILE_H
+
+#include "cgit.h"
+
+typedef void (*configfile_value_fn)(const char *name, const char *value);
+
+extern int parse_configfile(const char *filename, configfile_value_fn fn);
+
+#endif /* CONFIGFILE_H */
diff --git a/www/git.causal.agency/cgit/contrib/hooks/post-receive.agefile b/www/git.causal.agency/cgit/contrib/hooks/post-receive.agefile
new file mode 100755
index 00000000..2f72ae9c
--- /dev/null
+++ b/www/git.causal.agency/cgit/contrib/hooks/post-receive.agefile
@@ -0,0 +1,19 @@
+#!/bin/sh
+#
+# An example hook to update the "agefile" for CGit's idle time calculation.
+#
+# This hook assumes that you are using the default agefile location of
+# "info/web/last-modified".  If you change the value in your cgitrc then you
+# must also change it here.
+#
+# To install the hook, copy (or link) it to the file "hooks/post-receive" in
+# each of your repositories.
+#
+
+agefile="$(git rev-parse --git-dir)"/info/web/last-modified
+
+mkdir -p "$(dirname "$agefile")" &&
+git for-each-ref \
+	--sort=-authordate --count=1 \
+	--format='%(authordate:iso8601)' \
+	>"$agefile"
diff --git a/www/git.causal.agency/cgit/favicon.ico b/www/git.causal.agency/cgit/favicon.ico
new file mode 100644
index 00000000..56ff5938
--- /dev/null
+++ b/www/git.causal.agency/cgit/favicon.ico
Binary files differdiff --git a/www/git.causal.agency/cgit/filter.c b/www/git.causal.agency/cgit/filter.c
new file mode 100644
index 00000000..70f5b749
--- /dev/null
+++ b/www/git.causal.agency/cgit/filter.c
@@ -0,0 +1,457 @@
+/* filter.c: filter framework functions
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "html.h"
+#ifndef NO_LUA
+#include <dlfcn.h>
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+#endif
+
+static inline void reap_filter(struct cgit_filter *filter)
+{
+	if (filter && filter->cleanup)
+		filter->cleanup(filter);
+}
+
+void cgit_cleanup_filters(void)
+{
+	int i;
+	reap_filter(ctx.cfg.about_filter);
+	reap_filter(ctx.cfg.commit_filter);
+	reap_filter(ctx.cfg.source_filter);
+	reap_filter(ctx.cfg.email_filter);
+	reap_filter(ctx.cfg.owner_filter);
+	reap_filter(ctx.cfg.auth_filter);
+	for (i = 0; i < cgit_repolist.count; ++i) {
+		reap_filter(cgit_repolist.repos[i].about_filter);
+		reap_filter(cgit_repolist.repos[i].commit_filter);
+		reap_filter(cgit_repolist.repos[i].source_filter);
+		reap_filter(cgit_repolist.repos[i].email_filter);
+		reap_filter(cgit_repolist.repos[i].owner_filter);
+	}
+}
+
+static int open_exec_filter(struct cgit_filter *base, va_list ap)
+{
+	struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
+	int pipe_fh[2];
+	int i;
+
+	for (i = 0; i < filter->base.argument_count; i++)
+		filter->argv[i + 1] = va_arg(ap, char *);
+
+	filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
+		"Unable to duplicate STDOUT");
+	chk_zero(pipe(pipe_fh), "Unable to create pipe to subprocess");
+	filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
+	if (filter->pid == 0) {
+		close(pipe_fh[1]);
+		chk_non_negative(dup2(pipe_fh[0], STDIN_FILENO),
+			"Unable to use pipe as STDIN");
+		execvp(filter->cmd, filter->argv);
+		die_errno("Unable to exec subprocess %s", filter->cmd);
+	}
+	close(pipe_fh[0]);
+	chk_non_negative(dup2(pipe_fh[1], STDOUT_FILENO),
+		"Unable to use pipe as STDOUT");
+	close(pipe_fh[1]);
+	return 0;
+}
+
+static int close_exec_filter(struct cgit_filter *base)
+{
+	struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
+	int i, exit_status = 0;
+
+	chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
+		"Unable to restore STDOUT");
+	close(filter->old_stdout);
+	if (filter->pid < 0)
+		goto done;
+	waitpid(filter->pid, &exit_status, 0);
+	if (WIFEXITED(exit_status))
+		goto done;
+	die("Subprocess %s exited abnormally", filter->cmd);
+
+done:
+	for (i = 0; i < filter->base.argument_count; i++)
+		filter->argv[i + 1] = NULL;
+	return WEXITSTATUS(exit_status);
+
+}
+
+static void fprintf_exec_filter(struct cgit_filter *base, FILE *f, const char *prefix)
+{
+	struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
+	fprintf(f, "%sexec:%s\n", prefix, filter->cmd);
+}
+
+static void cleanup_exec_filter(struct cgit_filter *base)
+{
+	struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
+	if (filter->argv) {
+		free(filter->argv);
+		filter->argv = NULL;
+	}
+	if (filter->cmd) {
+		free(filter->cmd);
+		filter->cmd = NULL;
+	}
+}
+
+static struct cgit_filter *new_exec_filter(const char *cmd, int argument_count)
+{
+	struct cgit_exec_filter *f;
+	int args_size = 0;
+
+	f = xmalloc(sizeof(*f));
+	/* We leave argv for now and assign it below. */
+	cgit_exec_filter_init(f, xstrdup(cmd), NULL);
+	f->base.argument_count = argument_count;
+	args_size = (2 + argument_count) * sizeof(char *);
+	f->argv = xmalloc(args_size);
+	memset(f->argv, 0, args_size);
+	f->argv[0] = f->cmd;
+	return &f->base;
+}
+
+void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv)
+{
+	memset(filter, 0, sizeof(*filter));
+	filter->base.open = open_exec_filter;
+	filter->base.close = close_exec_filter;
+	filter->base.fprintf = fprintf_exec_filter;
+	filter->base.cleanup = cleanup_exec_filter;
+	filter->cmd = cmd;
+	filter->argv = argv;
+	/* The argument count for open_filter is zero by default, unless called from new_filter, above. */
+	filter->base.argument_count = 0;
+}
+
+#ifdef NO_LUA
+void cgit_init_filters(void)
+{
+}
+#endif
+
+#ifndef NO_LUA
+static ssize_t (*libc_write)(int fd, const void *buf, size_t count);
+static ssize_t (*filter_write)(struct cgit_filter *base, const void *buf, size_t count) = NULL;
+static struct cgit_filter *current_write_filter = NULL;
+
+void cgit_init_filters(void)
+{
+	libc_write = dlsym(RTLD_NEXT, "write");
+	if (!libc_write)
+		die("Could not locate libc's write function");
+}
+
+ssize_t write(int fd, const void *buf, size_t count)
+{
+	if (fd != STDOUT_FILENO || !filter_write)
+		return libc_write(fd, buf, count);
+	return filter_write(current_write_filter, buf, count);
+}
+
+static inline void hook_write(struct cgit_filter *filter, ssize_t (*new_write)(struct cgit_filter *base, const void *buf, size_t count))
+{
+	/* We want to avoid buggy nested patterns. */
+	assert(filter_write == NULL);
+	assert(current_write_filter == NULL);
+	current_write_filter = filter;
+	filter_write = new_write;
+}
+
+static inline void unhook_write(void)
+{
+	assert(filter_write != NULL);
+	assert(current_write_filter != NULL);
+	filter_write = NULL;
+	current_write_filter = NULL;
+}
+
+struct lua_filter {
+	struct cgit_filter base;
+	char *script_file;
+	lua_State *lua_state;
+};
+
+static void error_lua_filter(struct lua_filter *filter)
+{
+	die("Lua error in %s: %s", filter->script_file, lua_tostring(filter->lua_state, -1));
+	lua_pop(filter->lua_state, 1);
+}
+
+static ssize_t write_lua_filter(struct cgit_filter *base, const void *buf, size_t count)
+{
+	struct lua_filter *filter = (struct lua_filter *)base;
+
+	lua_getglobal(filter->lua_state, "filter_write");
+	lua_pushlstring(filter->lua_state, buf, count);
+	if (lua_pcall(filter->lua_state, 1, 0, 0)) {
+		error_lua_filter(filter);
+		errno = EIO;
+		return -1;
+	}
+	return count;
+}
+
+static inline int hook_lua_filter(lua_State *lua_state, void (*fn)(const char *txt))
+{
+	const char *str;
+	ssize_t (*save_filter_write)(struct cgit_filter *base, const void *buf, size_t count);
+	struct cgit_filter *save_filter;
+
+	str = lua_tostring(lua_state, 1);
+	if (!str)
+		return 0;
+
+	save_filter_write = filter_write;
+	save_filter = current_write_filter;
+	unhook_write();
+	fn(str);
+	hook_write(save_filter, save_filter_write);
+
+	return 0;
+}
+
+static int html_lua_filter(lua_State *lua_state)
+{
+	return hook_lua_filter(lua_state, html);
+}
+
+static int html_txt_lua_filter(lua_State *lua_state)
+{
+	return hook_lua_filter(lua_state, html_txt);
+}
+
+static int html_attr_lua_filter(lua_State *lua_state)
+{
+	return hook_lua_filter(lua_state, html_attr);
+}
+
+static int html_url_path_lua_filter(lua_State *lua_state)
+{
+	return hook_lua_filter(lua_state, html_url_path);
+}
+
+static int html_url_arg_lua_filter(lua_State *lua_state)
+{
+	return hook_lua_filter(lua_state, html_url_arg);
+}
+
+static int html_include_lua_filter(lua_State *lua_state)
+{
+	return hook_lua_filter(lua_state, (void (*)(const char *))html_include);
+}
+
+static void cleanup_lua_filter(struct cgit_filter *base)
+{
+	struct lua_filter *filter = (struct lua_filter *)base;
+
+	if (!filter->lua_state)
+		return;
+
+	lua_close(filter->lua_state);
+	filter->lua_state = NULL;
+	if (filter->script_file) {
+		free(filter->script_file);
+		filter->script_file = NULL;
+	}
+}
+
+static int init_lua_filter(struct lua_filter *filter)
+{
+	if (filter->lua_state)
+		return 0;
+
+	if (!(filter->lua_state = luaL_newstate()))
+		return 1;
+
+	luaL_openlibs(filter->lua_state);
+
+	lua_pushcfunction(filter->lua_state, html_lua_filter);
+	lua_setglobal(filter->lua_state, "html");
+	lua_pushcfunction(filter->lua_state, html_txt_lua_filter);
+	lua_setglobal(filter->lua_state, "html_txt");
+	lua_pushcfunction(filter->lua_state, html_attr_lua_filter);
+	lua_setglobal(filter->lua_state, "html_attr");
+	lua_pushcfunction(filter->lua_state, html_url_path_lua_filter);
+	lua_setglobal(filter->lua_state, "html_url_path");
+	lua_pushcfunction(filter->lua_state, html_url_arg_lua_filter);
+	lua_setglobal(filter->lua_state, "html_url_arg");
+	lua_pushcfunction(filter->lua_state, html_include_lua_filter);
+	lua_setglobal(filter->lua_state, "html_include");
+
+	if (luaL_dofile(filter->lua_state, filter->script_file)) {
+		error_lua_filter(filter);
+		lua_close(filter->lua_state);
+		filter->lua_state = NULL;
+		return 1;
+	}
+	return 0;
+}
+
+static int open_lua_filter(struct cgit_filter *base, va_list ap)
+{
+	struct lua_filter *filter = (struct lua_filter *)base;
+	int i;
+
+	if (init_lua_filter(filter))
+		return 1;
+
+	hook_write(base, write_lua_filter);
+
+	lua_getglobal(filter->lua_state, "filter_open");
+	for (i = 0; i < filter->base.argument_count; ++i)
+		lua_pushstring(filter->lua_state, va_arg(ap, char *));
+	if (lua_pcall(filter->lua_state, filter->base.argument_count, 0, 0)) {
+		error_lua_filter(filter);
+		return 1;
+	}
+	return 0;
+}
+
+static int close_lua_filter(struct cgit_filter *base)
+{
+	struct lua_filter *filter = (struct lua_filter *)base;
+	int ret = 0;
+
+	lua_getglobal(filter->lua_state, "filter_close");
+	if (lua_pcall(filter->lua_state, 0, 1, 0)) {
+		error_lua_filter(filter);
+		ret = -1;
+	} else {
+		ret = lua_tonumber(filter->lua_state, -1);
+		lua_pop(filter->lua_state, 1);
+	}
+
+	unhook_write();
+	return ret;
+}
+
+static void fprintf_lua_filter(struct cgit_filter *base, FILE *f, const char *prefix)
+{
+	struct lua_filter *filter = (struct lua_filter *)base;
+	fprintf(f, "%slua:%s\n", prefix, filter->script_file);
+}
+
+
+static struct cgit_filter *new_lua_filter(const char *cmd, int argument_count)
+{
+	struct lua_filter *filter;
+
+	filter = xmalloc(sizeof(*filter));
+	memset(filter, 0, sizeof(*filter));
+	filter->base.open = open_lua_filter;
+	filter->base.close = close_lua_filter;
+	filter->base.fprintf = fprintf_lua_filter;
+	filter->base.cleanup = cleanup_lua_filter;
+	filter->base.argument_count = argument_count;
+	filter->script_file = xstrdup(cmd);
+
+	return &filter->base;
+}
+
+#endif
+
+
+int cgit_open_filter(struct cgit_filter *filter, ...)
+{
+	int result;
+	va_list ap;
+	if (!filter)
+		return 0;
+	va_start(ap, filter);
+	result = filter->open(filter, ap);
+	va_end(ap);
+	return result;
+}
+
+int cgit_close_filter(struct cgit_filter *filter)
+{
+	if (!filter)
+		return 0;
+	return filter->close(filter);
+}
+
+void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix)
+{
+	filter->fprintf(filter, f, prefix);
+}
+
+
+
+static const struct {
+	const char *prefix;
+	struct cgit_filter *(*ctor)(const char *cmd, int argument_count);
+} filter_specs[] = {
+	{ "exec", new_exec_filter },
+#ifndef NO_LUA
+	{ "lua", new_lua_filter },
+#endif
+};
+
+struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
+{
+	char *colon;
+	int i;
+	size_t len;
+	int argument_count;
+
+	if (!cmd || !cmd[0])
+		return NULL;
+
+	colon = strchr(cmd, ':');
+	len = colon - cmd;
+	/*
+	 * In case we're running on Windows, don't allow a single letter before
+	 * the colon.
+	 */
+	if (len == 1)
+		colon = NULL;
+
+	switch (filtertype) {
+		case AUTH:
+			argument_count = 12;
+			break;
+
+		case EMAIL:
+			argument_count = 2;
+			break;
+
+		case OWNER:
+			argument_count = 0;
+			break;
+
+		case SOURCE:
+		case ABOUT:
+			argument_count = 1;
+			break;
+
+		case COMMIT:
+		default:
+			argument_count = 0;
+			break;
+	}
+
+	/* If no prefix is given, exec filter is the default. */
+	if (!colon)
+		return new_exec_filter(cmd, argument_count);
+
+	for (i = 0; i < ARRAY_SIZE(filter_specs); i++) {
+		if (len == strlen(filter_specs[i].prefix) &&
+		    !strncmp(filter_specs[i].prefix, cmd, len))
+			return filter_specs[i].ctor(colon + 1, argument_count);
+	}
+
+	die("Invalid filter type: %.*s", (int) len, cmd);
+}
diff --git a/www/git.causal.agency/cgit/filters/about-formatting.sh b/www/git.causal.agency/cgit/filters/about-formatting.sh
new file mode 100755
index 00000000..85daf9c2
--- /dev/null
+++ b/www/git.causal.agency/cgit/filters/about-formatting.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# This may be used with the about-filter or repo.about-filter setting in cgitrc.
+# It passes formatting of about pages to differing programs, depending on the usage.
+
+# Markdown support requires python and markdown-python.
+# RestructuredText support requires python and docutils.
+# Man page support requires groff.
+
+# The following environment variables can be used to retrieve the configuration
+# of the repository for which this script is called:
+# CGIT_REPO_URL        ( = repo.url       setting )
+# CGIT_REPO_NAME       ( = repo.name      setting )
+# CGIT_REPO_PATH       ( = repo.path      setting )
+# CGIT_REPO_OWNER      ( = repo.owner     setting )
+# CGIT_REPO_DEFBRANCH  ( = repo.defbranch setting )
+# CGIT_REPO_SECTION    ( = section        setting )
+# CGIT_REPO_CLONE_URL  ( = repo.clone-url setting )
+
+cd "$(dirname $0)/html-converters/"
+case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
+	*.markdown|*.mdown|*.md|*.mkd) exec ./md2html; ;;
+	*.rst) exec ./rst2html; ;;
+	*.[1-9]) exec ./man2html; ;;
+	*.htm|*.html) exec cat; ;;
+	*.txt|*) exec ./txt2html; ;;
+esac
diff --git a/www/git.causal.agency/cgit/filters/commit-links.sh b/www/git.causal.agency/cgit/filters/commit-links.sh
new file mode 100755
index 00000000..58819524
--- /dev/null
+++ b/www/git.causal.agency/cgit/filters/commit-links.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# This script can be used to generate links in commit messages.
+#
+# To use this script, refer to this file with either the commit-filter or the
+# repo.commit-filter options in cgitrc.
+#
+# The following environment variables can be used to retrieve the configuration
+# of the repository for which this script is called:
+# CGIT_REPO_URL        ( = repo.url       setting )
+# CGIT_REPO_NAME       ( = repo.name      setting )
+# CGIT_REPO_PATH       ( = repo.path      setting )
+# CGIT_REPO_OWNER      ( = repo.owner     setting )
+# CGIT_REPO_DEFBRANCH  ( = repo.defbranch setting )
+# CGIT_REPO_SECTION    ( = section        setting )
+# CGIT_REPO_CLONE_URL  ( = repo.clone-url setting )
+#
+
+regex=''
+
+# This expression generates links to commits referenced by their SHA1.
+regex=$regex'
+s|\b([0-9a-fA-F]{7,40})\b|<a href="./?id=\1">\1</a>|g'
+
+# This expression generates links to a fictional bugtracker.
+regex=$regex'
+s|#([0-9]+)\b|<a href="http://bugs.example.com/?bug=\1">#\1</a>|g'
+
+sed -re "$regex"
diff --git a/www/git.causal.agency/cgit/filters/email-gravatar.lua b/www/git.causal.agency/cgit/filters/email-gravatar.lua
new file mode 100644
index 00000000..c39b490d
--- /dev/null
+++ b/www/git.causal.agency/cgit/filters/email-gravatar.lua
@@ -0,0 +1,35 @@
+-- This script may be used with the email-filter or repo.email-filter settings in cgitrc.
+-- It adds gravatar icons to author names. It is designed to be used with the lua:
+-- prefix in filters. It is much faster than the corresponding python script.
+--
+-- Requirements:
+-- 	luaossl
+-- 	<http://25thandclement.com/~william/projects/luaossl.html>
+--
+
+local digest = require("openssl.digest")
+
+function md5_hex(input)
+	local b = digest.new("md5"):final(input)
+	local x = ""
+	for i = 1, #b do
+		x = x .. string.format("%.2x", string.byte(b, i))
+	end
+	return x
+end
+
+function filter_open(email, page)
+	buffer = ""
+	md5 = md5_hex(email:sub(2, -2):lower())
+end
+
+function filter_close()
+	html("<img src='//www.gravatar.com/avatar/" .. md5 .. "?s=13&amp;d=retro' width='13' height='13' alt='Gravatar' /> " .. buffer)
+	return 0
+end
+
+function filter_write(str)
+	buffer = buffer .. str
+end
+
+
diff --git a/www/git.causal.agency/cgit/filters/email-gravatar.py b/www/git.causal.agency/cgit/filters/email-gravatar.py
new file mode 100755
index 00000000..d70440ea
--- /dev/null
+++ b/www/git.causal.agency/cgit/filters/email-gravatar.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python3
+
+# Please prefer the email-gravatar.lua using lua: as a prefix over this script. This
+# script is very slow, in comparison.
+#
+# This script may be used with the email-filter or repo.email-filter settings in cgitrc.
+#
+# The following environment variables can be used to retrieve the configuration
+# of the repository for which this script is called:
+# CGIT_REPO_URL        ( = repo.url       setting )
+# CGIT_REPO_NAME       ( = repo.name      setting )
+# CGIT_REPO_PATH       ( = repo.path      setting )
+# CGIT_REPO_OWNER      ( = repo.owner     setting )
+# CGIT_REPO_DEFBRANCH  ( = repo.defbranch setting )
+# CGIT_REPO_SECTION    ( = section        setting )
+# CGIT_REPO_CLONE_URL  ( = repo.clone-url setting )
+#
+# It receives an email address on argv[1] and text on stdin. It prints
+# to stdout that text prepended by a gravatar at 10pt.
+
+import sys
+import hashlib
+import codecs
+
+email = sys.argv[1].lower().strip()
+if email[0] == '<':
+        email = email[1:]
+if email[-1] == '>':
+        email = email[0:-1]
+
+page = sys.argv[2]
+
+sys.stdin = codecs.getreader("utf-8")(sys.stdin.detach())
+sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())
+
+md5 = hashlib.md5(email.encode()).hexdigest()
+text = sys.stdin.read().strip()
+
+print("<img src='//www.gravatar.com/avatar/" + md5 + "?s=13&amp;d=retro' width='13' height='13' alt='Gravatar' /> " + text)
diff --git a/www/git.causal.agency/cgit/filters/email-libravatar.lua b/www/git.causal.agency/cgit/filters/email-libravatar.lua
new file mode 100644
index 00000000..7336baf8
--- /dev/null
+++ b/www/git.causal.agency/cgit/filters/email-libravatar.lua
@@ -0,0 +1,36 @@
+-- This script may be used with the email-filter or repo.email-filter settings in cgitrc.
+-- It adds libravatar icons to author names. It is designed to be used with the lua:
+-- prefix in filters.
+--
+-- Requirements:
+-- 	luaossl
+-- 	<http://25thandclement.com/~william/projects/luaossl.html>
+--
+
+local digest = require("openssl.digest")
+
+function md5_hex(input)
+	local b = digest.new("md5"):final(input)
+	local x = ""
+	for i = 1, #b do
+		x = x .. string.format("%.2x", string.byte(b, i))
+	end
+	return x
+end
+
+function filter_open(email, page)
+	buffer = ""
+	md5 = md5_hex(email:sub(2, -2):lower())
+end
+
+function filter_close()
+	baseurl = os.getenv("HTTPS") and "https://seccdn.libravatar.org/" or "http://cdn.libravatar.org/"
+	html("<img src='" .. baseurl .. "avatar/" .. md5 .. "?s=13&amp;d=retro' width='13' height='13' alt='Libravatar' /> " .. buffer)
+	return 0
+end
+
+function filter_write(str)
+	buffer = buffer .. str
+end
+
+
diff --git a/www/git.causal.agency/cgit/filters/file-authentication.lua b/www/git.causal.agency/cgit/filters/file-authentication.lua
new file mode 100644
index 00000000..02488046
--- /dev/null
+++ b/www/git.causal.agency/cgit/filters/file-authentication.lua
@@ -0,0 +1,359 @@
+-- This script may be used with the auth-filter.
+--
+-- Requirements:
+-- 	luaossl
+-- 	<http://25thandclement.com/~william/projects/luaossl.html>
+-- 	luaposix
+-- 	<https://github.com/luaposix/luaposix>
+--
+local sysstat = require("posix.sys.stat")
+local unistd = require("posix.unistd")
+local rand = require("openssl.rand")
+local hmac = require("openssl.hmac")
+
+-- This file should contain a series of lines in the form of:
+--	username1:hash1
+--	username2:hash2
+--	username3:hash3
+--	...
+-- Hashes can be generated using something like `mkpasswd -m sha-512 -R 300000`.
+-- This file should not be world-readable.
+local users_filename = "/etc/cgit-auth/users"
+
+-- This file should contain a series of lines in the form of:
+-- 	groupname1:username1,username2,username3,...
+--	...
+local groups_filename = "/etc/cgit-auth/groups"
+
+-- This file should contain a series of lines in the form of:
+-- 	reponame1:groupname1,groupname2,groupname3,...
+--	...
+local repos_filename = "/etc/cgit-auth/repos"
+
+-- Set this to a path this script can write to for storing a persistent
+-- cookie secret, which should not be world-readable.
+local secret_filename = "/var/cache/cgit/auth-secret"
+
+--
+--
+-- Authentication functions follow below. Swap these out if you want different authentication semantics.
+--
+--
+
+-- Looks up a hash for a given user.
+function lookup_hash(user)
+	local line
+	for line in io.lines(users_filename) do
+		local u, h = string.match(line, "(.-):(.+)")
+		if u:lower() == user:lower() then
+			return h
+		end
+	end
+	return nil
+end
+
+-- Looks up users for a given repo.
+function lookup_users(repo)
+	local users = nil
+	local groups = nil
+	local line, group, user
+	for line in io.lines(repos_filename) do
+		local r, g = string.match(line, "(.-):(.+)")
+		if r == repo then
+			groups = { }
+			for group in string.gmatch(g, "([^,]+)") do
+				groups[group:lower()] = true
+			end
+			break
+		end
+	end
+	if groups == nil then
+		return nil
+	end
+	for line in io.lines(groups_filename) do
+		local g, u = string.match(line, "(.-):(.+)")
+		if groups[g:lower()] then
+			if users == nil then
+				users = { }
+			end
+			for user in string.gmatch(u, "([^,]+)") do
+				users[user:lower()] = true
+			end
+		end
+	end
+	return users
+end
+
+
+-- Sets HTTP cookie headers based on post and sets up redirection.
+function authenticate_post()
+	local hash = lookup_hash(post["username"])
+	local redirect = validate_value("redirect", post["redirect"])
+
+	if redirect == nil then
+		not_found()
+		return 0
+	end
+
+	redirect_to(redirect)
+
+	if hash == nil or hash ~= unistd.crypt(post["password"], hash) then
+		set_cookie("cgitauth", "")
+	else
+		-- One week expiration time
+		local username = secure_value("username", post["username"], os.time() + 604800)
+		set_cookie("cgitauth", username)
+	end
+
+	html("\n")
+	return 0
+end
+
+
+-- Returns 1 if the cookie is valid and 0 if it is not.
+function authenticate_cookie()
+	accepted_users = lookup_users(cgit["repo"])
+	if accepted_users == nil then
+		-- We return as valid if the repo is not protected.
+		return 1
+	end
+
+	local username = validate_value("username", get_cookie(http["cookie"], "cgitauth"))
+	if username == nil or not accepted_users[username:lower()] then
+		return 0
+	else
+		return 1
+	end
+end
+
+-- Prints the html for the login form.
+function body()
+	html("<h2>Authentication Required</h2>")
+	html("<form method='post' action='")
+	html_attr(cgit["login"])
+	html("'>")
+	html("<input type='hidden' name='redirect' value='")
+	html_attr(secure_value("redirect", cgit["url"], 0))
+	html("' />")
+	html("<table>")
+	html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
+	html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
+	html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
+	html("</table></form>")
+
+	return 0
+end
+
+
+
+--
+--
+-- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.
+--
+--
+
+local actions = {}
+actions["authenticate-post"] = authenticate_post
+actions["authenticate-cookie"] = authenticate_cookie
+actions["body"] = body
+
+function filter_open(...)
+	action = actions[select(1, ...)]
+
+	http = {}
+	http["cookie"] = select(2, ...)
+	http["method"] = select(3, ...)
+	http["query"] = select(4, ...)
+	http["referer"] = select(5, ...)
+	http["path"] = select(6, ...)
+	http["host"] = select(7, ...)
+	http["https"] = select(8, ...)
+
+	cgit = {}
+	cgit["repo"] = select(9, ...)
+	cgit["page"] = select(10, ...)
+	cgit["url"] = select(11, ...)
+	cgit["login"] = select(12, ...)
+
+end
+
+function filter_close()
+	return action()
+end
+
+function filter_write(str)
+	post = parse_qs(str)
+end
+
+
+--
+--
+-- Utility functions based on keplerproject/wsapi.
+--
+--
+
+function url_decode(str)
+	if not str then
+		return ""
+	end
+	str = string.gsub(str, "+", " ")
+	str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
+	str = string.gsub(str, "\r\n", "\n")
+	return str
+end
+
+function url_encode(str)
+	if not str then
+		return ""
+	end
+	str = string.gsub(str, "\n", "\r\n")
+	str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
+	str = string.gsub(str, " ", "+")
+	return str
+end
+
+function parse_qs(qs)
+	local tab = {}
+	for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
+		tab[url_decode(key)] = url_decode(val)
+	end
+	return tab
+end
+
+function get_cookie(cookies, name)
+	cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
+	return url_decode(string.match(cookies, ";" .. name .. "=(.-);"))
+end
+
+function tohex(b)
+	local x = ""
+	for i = 1, #b do
+		x = x .. string.format("%.2x", string.byte(b, i))
+	end
+	return x
+end
+
+--
+--
+-- Cookie construction and validation helpers.
+--
+--
+
+local secret = nil
+
+-- Loads a secret from a file, creates a secret, or returns one from memory.
+function get_secret()
+	if secret ~= nil then
+		return secret
+	end
+	local secret_file = io.open(secret_filename, "r")
+	if secret_file == nil then
+		local old_umask = sysstat.umask(63)
+		local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
+		local temporary_file = io.open(temporary_filename, "w")
+		if temporary_file == nil then
+			os.exit(177)
+		end
+		temporary_file:write(tohex(rand.bytes(32)))
+		temporary_file:close()
+		unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
+		unistd.unlink(temporary_filename)
+		sysstat.umask(old_umask)
+		secret_file = io.open(secret_filename, "r")
+	end
+	if secret_file == nil then
+		os.exit(177)
+	end
+	secret = secret_file:read()
+	secret_file:close()
+	if secret:len() ~= 64 then
+		os.exit(177)
+	end
+	return secret
+end
+
+-- Returns value of cookie if cookie is valid. Otherwise returns nil.
+function validate_value(expected_field, cookie)
+	local i = 0
+	local value = ""
+	local field = ""
+	local expiration = 0
+	local salt = ""
+	local chmac = ""
+
+	if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
+		return nil
+	end
+
+	for component in string.gmatch(cookie, "[^|]+") do
+		if i == 0 then
+			field = component
+		elseif i == 1 then
+			value = component
+		elseif i == 2 then
+			expiration = tonumber(component)
+			if expiration == nil then
+				expiration = -1
+			end
+		elseif i == 3 then
+			salt = component
+		elseif i == 4 then
+			chmac = component
+		else
+			break
+		end
+		i = i + 1
+	end
+
+	if chmac == nil or chmac:len() == 0 then
+		return nil
+	end
+
+	-- Lua hashes strings, so these comparisons are time invariant.
+	if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
+		return nil
+	end
+
+	if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
+		return nil
+	end
+
+	if url_decode(field) ~= expected_field then
+		return nil
+	end
+
+	return url_decode(value)
+end
+
+function secure_value(field, value, expiration)
+	if value == nil or value:len() <= 0 then
+		return ""
+	end
+
+	local authstr = ""
+	local salt = tohex(rand.bytes(16))
+	value = url_encode(value)
+	field = url_encode(field)
+	authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
+	authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
+	return authstr
+end
+
+function set_cookie(cookie, value)
+	html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
+	if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
+		html("; secure")
+	end
+	html("\n")
+end
+
+function redirect_to(url)
+	html("Status: 302 Redirect\n")
+	html("Cache-Control: no-cache, no-store\n")
+	html("Location: " .. url .. "\n")
+end
+
+function not_found()
+	html("Status: 404 Not Found\n")
+	html("Cache-Control: no-cache, no-store\n\n")
+end
diff --git a/www/git.causal.agency/cgit/filters/gentoo-ldap-authentication.lua b/www/git.causal.agency/cgit/filters/gentoo-ldap-authentication.lua
new file mode 100644
index 00000000..673c88d1
--- /dev/null
+++ b/www/git.causal.agency/cgit/filters/gentoo-ldap-authentication.lua
@@ -0,0 +1,360 @@
+-- This script may be used with the auth-filter. Be sure to configure it as you wish.
+--
+-- Requirements:
+-- 	luaossl
+-- 	<http://25thandclement.com/~william/projects/luaossl.html>
+-- 	lualdap >= 1.2
+-- 	<https://git.zx2c4.com/lualdap/about/>
+-- 	luaposix
+-- 	<https://github.com/luaposix/luaposix>
+--
+local sysstat = require("posix.sys.stat")
+local unistd = require("posix.unistd")
+local lualdap = require("lualdap")
+local rand = require("openssl.rand")
+local hmac = require("openssl.hmac")
+
+--
+--
+-- Configure these variables for your settings.
+--
+--
+
+-- A list of password protected repositories, with which gentooAccess
+-- group is allowed to access each one.
+local protected_repos = {
+	glouglou = "infra",
+	portage = "dev"
+}
+
+-- Set this to a path this script can write to for storing a persistent
+-- cookie secret, which should be guarded.
+local secret_filename = "/var/cache/cgit/auth-secret"
+
+
+--
+--
+-- Authentication functions follow below. Swap these out if you want different authentication semantics.
+--
+--
+
+-- Sets HTTP cookie headers based on post and sets up redirection.
+function authenticate_post()
+	local redirect = validate_value("redirect", post["redirect"])
+
+	if redirect == nil then
+		not_found()
+		return 0
+	end
+
+	redirect_to(redirect)
+	
+	local groups = gentoo_ldap_user_groups(post["username"], post["password"])
+	if groups == nil then
+		set_cookie("cgitauth", "")
+	else
+		-- One week expiration time
+		set_cookie("cgitauth", secure_value("gentoogroups", table.concat(groups, ","), os.time() + 604800))
+	end
+
+	html("\n")
+	return 0
+end
+
+
+-- Returns 1 if the cookie is valid and 0 if it is not.
+function authenticate_cookie()
+	local required_group = protected_repos[cgit["repo"]]
+	if required_group == nil then
+		-- We return as valid if the repo is not protected.
+		return 1
+	end
+	
+	local user_groups = validate_value("gentoogroups", get_cookie(http["cookie"], "cgitauth"))
+	if user_groups == nil or user_groups == "" then
+		return 0
+	end
+	for group in string.gmatch(user_groups, "[^,]+") do
+		if group == required_group then
+			return 1
+		end
+	end
+	return 0
+end
+
+-- Prints the html for the login form.
+function body()
+	html("<h2>Gentoo LDAP Authentication Required</h2>")
+	html("<form method='post' action='")
+	html_attr(cgit["login"])
+	html("'>")
+	html("<input type='hidden' name='redirect' value='")
+	html_attr(secure_value("redirect", cgit["url"], 0))
+	html("' />")
+	html("<table>")
+	html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
+	html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
+	html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
+	html("</table></form>")
+
+	return 0
+end
+
+--
+--
+-- Gentoo LDAP support.
+--
+--
+
+function gentoo_ldap_user_groups(username, password)
+	-- Ensure the user is alphanumeric
+	if username == nil or username:match("%W") then
+		return nil
+	end
+
+	local who = "uid=" .. username .. ",ou=devs,dc=gentoo,dc=org"
+
+	local ldap, err = lualdap.open_simple {
+		uri = "ldap://ldap1.gentoo.org",
+		who = who,
+		password = password,
+		starttls = true,
+		certfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.crt",
+		keyfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.key",
+		cacertfile = "/var/www/uwsgi/cgit/gentoo-ldap/ca.pem"
+	}
+	if ldap == nil then
+		return nil
+	end
+
+	local group_suffix = ".group"
+	local group_suffix_len = group_suffix:len()
+	local groups = {}
+	for dn, attribs in ldap:search { base = who, scope = "subtree" } do
+		local access = attribs["gentooAccess"]
+		if dn == who and access ~= nil then
+			for i, v in ipairs(access) do
+				local vlen = v:len()
+				if vlen > group_suffix_len and v:sub(-group_suffix_len) == group_suffix then
+					table.insert(groups, v:sub(1, vlen - group_suffix_len))
+				end
+			end
+		end
+	end
+
+	ldap:close()
+
+	return groups
+end
+
+--
+--
+-- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.
+--
+--
+
+local actions = {}
+actions["authenticate-post"] = authenticate_post
+actions["authenticate-cookie"] = authenticate_cookie
+actions["body"] = body
+
+function filter_open(...)
+	action = actions[select(1, ...)]
+
+	http = {}
+	http["cookie"] = select(2, ...)
+	http["method"] = select(3, ...)
+	http["query"] = select(4, ...)
+	http["referer"] = select(5, ...)
+	http["path"] = select(6, ...)
+	http["host"] = select(7, ...)
+	http["https"] = select(8, ...)
+
+	cgit = {}
+	cgit["repo"] = select(9, ...)
+	cgit["page"] = select(10, ...)
+	cgit["url"] = select(11, ...)
+	cgit["login"] = select(12, ...)
+
+end
+
+function filter_close()
+	return action()
+end
+
+function filter_write(str)
+	post = parse_qs(str)
+end
+
+
+--
+--
+-- Utility functions based on keplerproject/wsapi.
+--
+--
+
+function url_decode(str)
+	if not str then
+		return ""
+	end
+	str = string.gsub(str, "+", " ")
+	str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
+	str = string.gsub(str, "\r\n", "\n")
+	return str
+end
+
+function url_encode(str)
+	if not str then
+		return ""
+	end
+	str = string.gsub(str, "\n", "\r\n")
+	str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
+	str = string.gsub(str, " ", "+")
+	return str
+end
+
+function parse_qs(qs)
+	local tab = {}
+	for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
+		tab[url_decode(key)] = url_decode(val)
+	end
+	return tab
+end
+
+function get_cookie(cookies, name)
+	cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
+	return string.match(cookies, ";" .. name .. "=(.-);")
+end
+
+function tohex(b)
+	local x = ""
+	for i = 1, #b do
+		x = x .. string.format("%.2x", string.byte(b, i))
+	end
+	return x
+end
+
+--
+--
+-- Cookie construction and validation helpers.
+--
+--
+
+local secret = nil
+
+-- Loads a secret from a file, creates a secret, or returns one from memory.
+function get_secret()
+	if secret ~= nil then
+		return secret
+	end
+	local secret_file = io.open(secret_filename, "r")
+	if secret_file == nil then
+		local old_umask = sysstat.umask(63)
+		local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
+		local temporary_file = io.open(temporary_filename, "w")
+		if temporary_file == nil then
+			os.exit(177)
+		end
+		temporary_file:write(tohex(rand.bytes(32)))
+		temporary_file:close()
+		unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
+		unistd.unlink(temporary_filename)
+		sysstat.umask(old_umask)
+		secret_file = io.open(secret_filename, "r")
+	end
+	if secret_file == nil then
+		os.exit(177)
+	end
+	secret = secret_file:read()
+	secret_file:close()
+	if secret:len() ~= 64 then
+		os.exit(177)
+	end
+	return secret
+end
+
+-- Returns value of cookie if cookie is valid. Otherwise returns nil.
+function validate_value(expected_field, cookie)
+	local i = 0
+	local value = ""
+	local field = ""
+	local expiration = 0
+	local salt = ""
+	local chmac = ""
+
+	if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
+		return nil
+	end
+
+	for component in string.gmatch(cookie, "[^|]+") do
+		if i == 0 then
+			field = component
+		elseif i == 1 then
+			value = component
+		elseif i == 2 then
+			expiration = tonumber(component)
+			if expiration == nil then
+				expiration = -1
+			end
+		elseif i == 3 then
+			salt = component
+		elseif i == 4 then
+			chmac = component
+		else
+			break
+		end
+		i = i + 1
+	end
+
+	if chmac == nil or chmac:len() == 0 then
+		return nil
+	end
+
+	-- Lua hashes strings, so these comparisons are time invariant.
+	if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
+		return nil
+	end
+
+	if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
+		return nil
+	end
+
+	if url_decode(field) ~= expected_field then
+		return nil
+	end
+
+	return url_decode(value)
+end
+
+function secure_value(field, value, expiration)
+	if value == nil or value:len() <= 0 then
+		return ""
+	end
+
+	local authstr = ""
+	local salt = tohex(rand.bytes(16))
+	value = url_encode(value)
+	field = url_encode(field)
+	authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
+	authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
+	return authstr
+end
+
+function set_cookie(cookie, value)
+	html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
+	if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
+		html("; secure")
+	end
+	html("\n")
+end
+
+function redirect_to(url)
+	html("Status: 302 Redirect\n")
+	html("Cache-Control: no-cache, no-store\n")
+	html("Location: " .. url .. "\n")
+end
+
+function not_found()
+	html("Status: 404 Not Found\n")
+	html("Cache-Control: no-cache, no-store\n\n")
+end
diff --git a/www/git.causal.agency/cgit/filters/html-converters/man2html b/www/git.causal.agency/cgit/filters/html-converters/man2html
new file mode 100755
index 00000000..0ef78841
--- /dev/null
+++ b/www/git.causal.agency/cgit/filters/html-converters/man2html
@@ -0,0 +1,4 @@
+#!/bin/sh
+echo "<div style=\"font-family: monospace\">"
+groff -mandoc -T html -P -r -P -l | egrep -v '(<html>|<head>|<meta|<title>|</title>|</head>|<body>|</body>|</html>|<!DOCTYPE|"http://www.w3.org)'
+echo "</div>"
diff --git a/www/git.causal.agency/cgit/filters/html-converters/md2html b/www/git.causal.agency/cgit/filters/html-converters/md2html
new file mode 100755
index 00000000..dc20f42a
--- /dev/null
+++ b/www/git.causal.agency/cgit/filters/html-converters/md2html
@@ -0,0 +1,307 @@
+#!/usr/bin/env python3
+import markdown
+import sys
+import io
+from pygments.formatters import HtmlFormatter
+from markdown.extensions.toc import TocExtension
+sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8')
+sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
+sys.stdout.write('''
+<style>
+.markdown-body {
+    font-size: 14px;
+    line-height: 1.6;
+    overflow: hidden;
+}
+.markdown-body>*:first-child {
+    margin-top: 0 !important;
+}
+.markdown-body>*:last-child {
+    margin-bottom: 0 !important;
+}
+.markdown-body a.absent {
+    color: #c00;
+}
+.markdown-body a.anchor {
+    display: block;
+    padding-left: 30px;
+    margin-left: -30px;
+    cursor: pointer;
+    position: absolute;
+    top: 0;
+    left: 0;
+    bottom: 0;
+}
+.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
+    margin: 20px 0 10px;
+    padding: 0;
+    font-weight: bold;
+    -webkit-font-smoothing: antialiased;
+    cursor: text;
+    position: relative;
+}
+.markdown-body h1 .mini-icon-link, .markdown-body h2 .mini-icon-link, .markdown-body h3 .mini-icon-link, .markdown-body h4 .mini-icon-link, .markdown-body h5 .mini-icon-link, .markdown-body h6 .mini-icon-link {
+    display: none;
+    color: #000;
+}
+.markdown-body h1:hover a.anchor, .markdown-body h2:hover a.anchor, .markdown-body h3:hover a.anchor, .markdown-body h4:hover a.anchor, .markdown-body h5:hover a.anchor, .markdown-body h6:hover a.anchor {
+    text-decoration: none;
+    line-height: 1;
+    padding-left: 0;
+    margin-left: -22px;
+    top: 15%;
+}
+.markdown-body h1:hover a.anchor .mini-icon-link, .markdown-body h2:hover a.anchor .mini-icon-link, .markdown-body h3:hover a.anchor .mini-icon-link, .markdown-body h4:hover a.anchor .mini-icon-link, .markdown-body h5:hover a.anchor .mini-icon-link, .markdown-body h6:hover a.anchor .mini-icon-link {
+    display: inline-block;
+}
+div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div#cgit .markdown-body h3 a.toclink, div#cgit .markdown-body h4 a.toclink, div#cgit .markdown-body h5 a.toclink, div#cgit .markdown-body h6 a.toclink {
+    color: black;
+}
+.markdown-body h1 tt, .markdown-body h1 code, .markdown-body h2 tt, .markdown-body h2 code, .markdown-body h3 tt, .markdown-body h3 code, .markdown-body h4 tt, .markdown-body h4 code, .markdown-body h5 tt, .markdown-body h5 code, .markdown-body h6 tt, .markdown-body h6 code {
+    font-size: inherit;
+}
+.markdown-body h1 {
+    font-size: 28px;
+    color: #000;
+}
+.markdown-body h2 {
+    font-size: 24px;
+    border-bottom: 1px solid #ccc;
+    color: #000;
+}
+.markdown-body h3 {
+    font-size: 18px;
+}
+.markdown-body h4 {
+    font-size: 16px;
+}
+.markdown-body h5 {
+    font-size: 14px;
+}
+.markdown-body h6 {
+    color: #777;
+    font-size: 14px;
+}
+.markdown-body p, .markdown-body blockquote, .markdown-body ul, .markdown-body ol, .markdown-body dl, .markdown-body table, .markdown-body pre {
+    margin: 15px 0;
+}
+.markdown-body hr {
+    background: transparent url("/dirty-shade.png") repeat-x 0 0;
+    border: 0 none;
+    color: #ccc;
+    height: 4px;
+    padding: 0;
+}
+.markdown-body>h2:first-child, .markdown-body>h1:first-child, .markdown-body>h1:first-child+h2, .markdown-body>h3:first-child, .markdown-body>h4:first-child, .markdown-body>h5:first-child, .markdown-body>h6:first-child {
+    margin-top: 0;
+    padding-top: 0;
+}
+.markdown-body a:first-child h1, .markdown-body a:first-child h2, .markdown-body a:first-child h3, .markdown-body a:first-child h4, .markdown-body a:first-child h5, .markdown-body a:first-child h6 {
+    margin-top: 0;
+    padding-top: 0;
+}
+.markdown-body h1+p, .markdown-body h2+p, .markdown-body h3+p, .markdown-body h4+p, .markdown-body h5+p, .markdown-body h6+p {
+    margin-top: 0;
+}
+.markdown-body li p.first {
+    display: inline-block;
+}
+.markdown-body ul, .markdown-body ol {
+    padding-left: 30px;
+}
+.markdown-body ul.no-list, .markdown-body ol.no-list {
+    list-style-type: none;
+    padding: 0;
+}
+.markdown-body ul li>:first-child, .markdown-body ul li ul:first-of-type, .markdown-body ul li ol:first-of-type, .markdown-body ol li>:first-child, .markdown-body ol li ul:first-of-type, .markdown-body ol li ol:first-of-type {
+    margin-top: 0px;
+}
+.markdown-body ul li p:last-of-type, .markdown-body ol li p:last-of-type {
+    margin-bottom: 0;
+}
+.markdown-body ul ul, .markdown-body ul ol, .markdown-body ol ol, .markdown-body ol ul {
+    margin-bottom: 0;
+}
+.markdown-body dl {
+    padding: 0;
+}
+.markdown-body dl dt {
+    font-size: 14px;
+    font-weight: bold;
+    font-style: italic;
+    padding: 0;
+    margin: 15px 0 5px;
+}
+.markdown-body dl dt:first-child {
+    padding: 0;
+}
+.markdown-body dl dt>:first-child {
+    margin-top: 0px;
+}
+.markdown-body dl dt>:last-child {
+    margin-bottom: 0px;
+}
+.markdown-body dl dd {
+    margin: 0 0 15px;
+    padding: 0 15px;
+}
+.markdown-body dl dd>:first-child {
+    margin-top: 0px;
+}
+.markdown-body dl dd>:last-child {
+    margin-bottom: 0px;
+}
+.markdown-body blockquote {
+    border-left: 4px solid #DDD;
+    padding: 0 15px;
+    color: #777;
+}
+.markdown-body blockquote>:first-child {
+    margin-top: 0px;
+}
+.markdown-body blockquote>:last-child {
+    margin-bottom: 0px;
+}
+.markdown-body table th {
+    font-weight: bold;
+}
+.markdown-body table th, .markdown-body table td {
+    border: 1px solid #ccc;
+    padding: 6px 13px;
+}
+.markdown-body table tr {
+    border-top: 1px solid #ccc;
+    background-color: #fff;
+}
+.markdown-body table tr:nth-child(2n) {
+    background-color: #f8f8f8;
+}
+.markdown-body img {
+    max-width: 100%;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+}
+.markdown-body span.frame {
+    display: block;
+    overflow: hidden;
+}
+.markdown-body span.frame>span {
+    border: 1px solid #ddd;
+    display: block;
+    float: left;
+    overflow: hidden;
+    margin: 13px 0 0;
+    padding: 7px;
+    width: auto;
+}
+.markdown-body span.frame span img {
+    display: block;
+    float: left;
+}
+.markdown-body span.frame span span {
+    clear: both;
+    color: #333;
+    display: block;
+    padding: 5px 0 0;
+}
+.markdown-body span.align-center {
+    display: block;
+    overflow: hidden;
+    clear: both;
+}
+.markdown-body span.align-center>span {
+    display: block;
+    overflow: hidden;
+    margin: 13px auto 0;
+    text-align: center;
+}
+.markdown-body span.align-center span img {
+    margin: 0 auto;
+    text-align: center;
+}
+.markdown-body span.align-right {
+    display: block;
+    overflow: hidden;
+    clear: both;
+}
+.markdown-body span.align-right>span {
+    display: block;
+    overflow: hidden;
+    margin: 13px 0 0;
+    text-align: right;
+}
+.markdown-body span.align-right span img {
+    margin: 0;
+    text-align: right;
+}
+.markdown-body span.float-left {
+    display: block;
+    margin-right: 13px;
+    overflow: hidden;
+    float: left;
+}
+.markdown-body span.float-left span {
+    margin: 13px 0 0;
+}
+.markdown-body span.float-right {
+    display: block;
+    margin-left: 13px;
+    overflow: hidden;
+    float: right;
+}
+.markdown-body span.float-right>span {
+    display: block;
+    overflow: hidden;
+    margin: 13px auto 0;
+    text-align: right;
+}
+.markdown-body code, .markdown-body tt {
+    margin: 0 2px;
+    padding: 0px 5px;
+    border: 1px solid #eaeaea;
+    background-color: #f8f8f8;
+    border-radius: 3px;
+}
+.markdown-body code {
+    white-space: nowrap;
+}
+.markdown-body pre>code {
+    margin: 0;
+    padding: 0;
+    white-space: pre;
+    border: none;
+    background: transparent;
+}
+.markdown-body .highlight pre, .markdown-body pre {
+    background-color: #f8f8f8;
+    border: 1px solid #ccc;
+    font-size: 13px;
+    line-height: 19px;
+    overflow: auto;
+    padding: 6px 10px;
+    border-radius: 3px;
+}
+.markdown-body pre code, .markdown-body pre tt {
+    margin: 0;
+    padding: 0;
+    background-color: transparent;
+    border: none;
+}
+''')
+sys.stdout.write(HtmlFormatter(style='pastie').get_style_defs('.highlight'))
+sys.stdout.write('''
+</style>   
+''')
+sys.stdout.write("<div class='markdown-body'>")
+sys.stdout.flush()
+# Note: you may want to run this through bleach for sanitization
+markdown.markdownFromFile(
+	output_format="html5",
+	extensions=[
+		"markdown.extensions.fenced_code",
+		"markdown.extensions.codehilite",
+		"markdown.extensions.tables",
+		TocExtension(anchorlink=True)],
+	extension_configs={
+		"markdown.extensions.codehilite":{"css_class":"highlight"}})
+sys.stdout.write("</div>")
diff --git a/www/git.causal.agency/cgit/filters/html-converters/rst2html b/www/git.causal.agency/cgit/filters/html-converters/rst2html
new file mode 100755
index 00000000..02d90f81
--- /dev/null
+++ b/www/git.causal.agency/cgit/filters/html-converters/rst2html
@@ -0,0 +1,2 @@
+#!/bin/bash
+exec rst2html.py --template <(echo -e "%(stylesheet)s\n%(body_pre_docinfo)s\n%(docinfo)s\n%(body)s")
diff --git a/www/git.causal.agency/cgit/filters/html-converters/txt2html b/www/git.causal.agency/cgit/filters/html-converters/txt2html
new file mode 100755
index 00000000..495eeceb
--- /dev/null
+++ b/www/git.causal.agency/cgit/filters/html-converters/txt2html
@@ -0,0 +1,4 @@
+#!/bin/sh
+echo "<pre>"
+sed "s|&|\\&amp;|g;s|'|\\&apos;|g;s|\"|\\&quot;|g;s|<|\\&lt;|g;s|>|\\&gt;|g"
+echo "</pre>"
diff --git a/www/git.causal.agency/cgit/filters/owner-example.lua b/www/git.causal.agency/cgit/filters/owner-example.lua
new file mode 100644
index 00000000..50fc25a8
--- /dev/null
+++ b/www/git.causal.agency/cgit/filters/owner-example.lua
@@ -0,0 +1,17 @@
+-- This script is an example of an owner-filter.  It replaces the
+-- usual query link with one to a fictional homepage.  This script may
+-- be used with the owner-filter or repo.owner-filter settings in
+-- cgitrc with the `lua:` prefix.
+
+function filter_open()
+	buffer = ""
+end
+
+function filter_close()
+	html(string.format("<a href=\"%s\">%s</a>", "http://wiki.example.com/about/" .. buffer, buffer))
+	return 0
+end
+
+function filter_write(str)
+	buffer = buffer .. str
+end
diff --git a/www/git.causal.agency/cgit/filters/simple-authentication.lua b/www/git.causal.agency/cgit/filters/simple-authentication.lua
new file mode 100644
index 00000000..23d34576
--- /dev/null
+++ b/www/git.causal.agency/cgit/filters/simple-authentication.lua
@@ -0,0 +1,314 @@
+-- This script may be used with the auth-filter. Be sure to configure it as you wish.
+--
+-- Requirements:
+-- 	luaossl
+-- 	<http://25thandclement.com/~william/projects/luaossl.html>
+-- 	luaposix
+-- 	<https://github.com/luaposix/luaposix>
+--
+local sysstat = require("posix.sys.stat")
+local unistd = require("posix.unistd")
+local rand = require("openssl.rand")
+local hmac = require("openssl.hmac")
+
+--
+--
+-- Configure these variables for your settings.
+--
+--
+
+-- A list of password protected repositories along with the users who can access them.
+local protected_repos = {
+	glouglou	= { laurent = true, jason = true },
+	qt		= { jason = true, bob = true }
+}
+
+-- A list of users and hashes, generated with `mkpasswd -m sha-512 -R 300000`.
+local users = {
+	jason		= "$6$rounds=300000$YYJct3n/o.ruYK$HhpSeuCuW1fJkpvMZOZzVizeLsBKcGA/aF2UPuV5v60JyH2MVSG6P511UMTj2F3H75.IT2HIlnvXzNb60FcZH1",
+	laurent		= "$6$rounds=300000$dP0KNHwYb3JKigT$pN/LG7rWxQ4HniFtx5wKyJXBJUKP7R01zTNZ0qSK/aivw8ywGAOdfYiIQFqFhZFtVGvr11/7an.nesvm8iJUi.",
+	bob		= "$6$rounds=300000$jCLCCt6LUpTz$PI1vvd1yaVYcCzqH8QAJFcJ60b6W/6sjcOsU7mAkNo7IE8FRGW1vkjF8I/T5jt/auv5ODLb1L4S2s.CAyZyUC"
+}
+
+-- Set this to a path this script can write to for storing a persistent
+-- cookie secret, which should be guarded.
+local secret_filename = "/var/cache/cgit/auth-secret"
+
+--
+--
+-- Authentication functions follow below. Swap these out if you want different authentication semantics.
+--
+--
+
+-- Sets HTTP cookie headers based on post and sets up redirection.
+function authenticate_post()
+	local hash = users[post["username"]]
+	local redirect = validate_value("redirect", post["redirect"])
+
+	if redirect == nil then
+		not_found()
+		return 0
+	end
+
+	redirect_to(redirect)
+
+	if hash == nil or hash ~= unistd.crypt(post["password"], hash) then
+		set_cookie("cgitauth", "")
+	else
+		-- One week expiration time
+		local username = secure_value("username", post["username"], os.time() + 604800)
+		set_cookie("cgitauth", username)
+	end
+
+	html("\n")
+	return 0
+end
+
+
+-- Returns 1 if the cookie is valid and 0 if it is not.
+function authenticate_cookie()
+	accepted_users = protected_repos[cgit["repo"]]
+	if accepted_users == nil then
+		-- We return as valid if the repo is not protected.
+		return 1
+	end
+
+	local username = validate_value("username", get_cookie(http["cookie"], "cgitauth"))
+	if username == nil or not accepted_users[username:lower()] then
+		return 0
+	else
+		return 1
+	end
+end
+
+-- Prints the html for the login form.
+function body()
+	html("<h2>Authentication Required</h2>")
+	html("<form method='post' action='")
+	html_attr(cgit["login"])
+	html("'>")
+	html("<input type='hidden' name='redirect' value='")
+	html_attr(secure_value("redirect", cgit["url"], 0))
+	html("' />")
+	html("<table>")
+	html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
+	html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
+	html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
+	html("</table></form>")
+
+	return 0
+end
+
+
+
+--
+--
+-- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.
+--
+--
+
+local actions = {}
+actions["authenticate-post"] = authenticate_post
+actions["authenticate-cookie"] = authenticate_cookie
+actions["body"] = body
+
+function filter_open(...)
+	action = actions[select(1, ...)]
+
+	http = {}
+	http["cookie"] = select(2, ...)
+	http["method"] = select(3, ...)
+	http["query"] = select(4, ...)
+	http["referer"] = select(5, ...)
+	http["path"] = select(6, ...)
+	http["host"] = select(7, ...)
+	http["https"] = select(8, ...)
+
+	cgit = {}
+	cgit["repo"] = select(9, ...)
+	cgit["page"] = select(10, ...)
+	cgit["url"] = select(11, ...)
+	cgit["login"] = select(12, ...)
+
+end
+
+function filter_close()
+	return action()
+end
+
+function filter_write(str)
+	post = parse_qs(str)
+end
+
+
+--
+--
+-- Utility functions based on keplerproject/wsapi.
+--
+--
+
+function url_decode(str)
+	if not str then
+		return ""
+	end
+	str = string.gsub(str, "+", " ")
+	str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
+	str = string.gsub(str, "\r\n", "\n")
+	return str
+end
+
+function url_encode(str)
+	if not str then
+		return ""
+	end
+	str = string.gsub(str, "\n", "\r\n")
+	str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
+	str = string.gsub(str, " ", "+")
+	return str
+end
+
+function parse_qs(qs)
+	local tab = {}
+	for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
+		tab[url_decode(key)] = url_decode(val)
+	end
+	return tab
+end
+
+function get_cookie(cookies, name)
+	cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
+	return url_decode(string.match(cookies, ";" .. name .. "=(.-);"))
+end
+
+function tohex(b)
+	local x = ""
+	for i = 1, #b do
+		x = x .. string.format("%.2x", string.byte(b, i))
+	end
+	return x
+end
+
+--
+--
+-- Cookie construction and validation helpers.
+--
+--
+
+local secret = nil
+
+-- Loads a secret from a file, creates a secret, or returns one from memory.
+function get_secret()
+	if secret ~= nil then
+		return secret
+	end
+	local secret_file = io.open(secret_filename, "r")
+	if secret_file == nil then
+		local old_umask = sysstat.umask(63)
+		local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
+		local temporary_file = io.open(temporary_filename, "w")
+		if temporary_file == nil then
+			os.exit(177)
+		end
+		temporary_file:write(tohex(rand.bytes(32)))
+		temporary_file:close()
+		unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
+		unistd.unlink(temporary_filename)
+		sysstat.umask(old_umask)
+		secret_file = io.open(secret_filename, "r")
+	end
+	if secret_file == nil then
+		os.exit(177)
+	end
+	secret = secret_file:read()
+	secret_file:close()
+	if secret:len() ~= 64 then
+		os.exit(177)
+	end
+	return secret
+end
+
+-- Returns value of cookie if cookie is valid. Otherwise returns nil.
+function validate_value(expected_field, cookie)
+	local i = 0
+	local value = ""
+	local field = ""
+	local expiration = 0
+	local salt = ""
+	local chmac = ""
+
+	if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
+		return nil
+	end
+
+	for component in string.gmatch(cookie, "[^|]+") do
+		if i == 0 then
+			field = component
+		elseif i == 1 then
+			value = component
+		elseif i == 2 then
+			expiration = tonumber(component)
+			if expiration == nil then
+				expiration = -1
+			end
+		elseif i == 3 then
+			salt = component
+		elseif i == 4 then
+			chmac = component
+		else
+			break
+		end
+		i = i + 1
+	end
+
+	if chmac == nil or chmac:len() == 0 then
+		return nil
+	end
+
+	-- Lua hashes strings, so these comparisons are time invariant.
+	if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
+		return nil
+	end
+
+	if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
+		return nil
+	end
+
+	if url_decode(field) ~= expected_field then
+		return nil
+	end
+
+	return url_decode(value)
+end
+
+function secure_value(field, value, expiration)
+	if value == nil or value:len() <= 0 then
+		return ""
+	end
+
+	local authstr = ""
+	local salt = tohex(rand.bytes(16))
+	value = url_encode(value)
+	field = url_encode(field)
+	authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
+	authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
+	return authstr
+end
+
+function set_cookie(cookie, value)
+	html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
+	if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
+		html("; secure")
+	end
+	html("\n")
+end
+
+function redirect_to(url)
+	html("Status: 302 Redirect\n")
+	html("Cache-Control: no-cache, no-store\n")
+	html("Location: " .. url .. "\n")
+end
+
+function not_found()
+	html("Status: 404 Not Found\n")
+	html("Cache-Control: no-cache, no-store\n\n")
+end
diff --git a/www/git.causal.agency/cgit/filters/syntax-highlighting.py b/www/git.causal.agency/cgit/filters/syntax-highlighting.py
new file mode 100755
index 00000000..e912594c
--- /dev/null
+++ b/www/git.causal.agency/cgit/filters/syntax-highlighting.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+
+# This script uses Pygments and Python3. You must have both installed
+# for this to work.
+#
+# http://pygments.org/
+# http://python.org/
+#
+# It may be used with the source-filter or repo.source-filter settings
+# in cgitrc.
+#
+# The following environment variables can be used to retrieve the
+# configuration of the repository for which this script is called:
+# CGIT_REPO_URL        ( = repo.url       setting )
+# CGIT_REPO_NAME       ( = repo.name      setting )
+# CGIT_REPO_PATH       ( = repo.path      setting )
+# CGIT_REPO_OWNER      ( = repo.owner     setting )
+# CGIT_REPO_DEFBRANCH  ( = repo.defbranch setting )
+# CGIT_REPO_SECTION    ( = section        setting )
+# CGIT_REPO_CLONE_URL  ( = repo.clone-url setting )
+
+
+import sys
+import io
+from pygments import highlight
+from pygments.util import ClassNotFound
+from pygments.lexers import TextLexer
+from pygments.lexers import guess_lexer
+from pygments.lexers import guess_lexer_for_filename
+from pygments.formatters import HtmlFormatter
+
+
+sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8', errors='replace')
+sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
+data = sys.stdin.read()
+filename = sys.argv[1]
+formatter = HtmlFormatter(style='pastie', nobackground=True)
+
+try:
+	lexer = guess_lexer_for_filename(filename, data)
+except ClassNotFound:
+	# check if there is any shebang
+	if data[0:2] == '#!':
+		lexer = guess_lexer(data)
+	else:
+		lexer = TextLexer()
+except TypeError:
+	lexer = TextLexer()
+
+# highlight! :-)
+# printout pygments' css definitions as well
+sys.stdout.write('<style>')
+sys.stdout.write(formatter.get_style_defs('.highlight'))
+sys.stdout.write('</style>')
+sys.stdout.write(highlight(data, lexer, formatter, outfile=None))
diff --git a/www/git.causal.agency/cgit/filters/syntax-highlighting.sh b/www/git.causal.agency/cgit/filters/syntax-highlighting.sh
new file mode 100755
index 00000000..840bc34f
--- /dev/null
+++ b/www/git.causal.agency/cgit/filters/syntax-highlighting.sh
@@ -0,0 +1,121 @@
+#!/bin/sh
+# This script can be used to implement syntax highlighting in the cgit
+# tree-view by referring to this file with the source-filter or repo.source-
+# filter options in cgitrc.
+#
+# This script requires a shell supporting the ${var##pattern} syntax.
+# It is supported by at least dash and bash, however busybox environments
+# might have to use an external call to sed instead.
+#
+# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax
+# highlighting, so you'll probably want something like the following included
+# in your css file:
+#
+# Style definition file generated by highlight 2.4.8, http://www.andre-simon.de/
+#
+# table.blob .num  { color:#2928ff; }
+# table.blob .esc  { color:#ff00ff; }
+# table.blob .str  { color:#ff0000; }
+# table.blob .dstr { color:#818100; }
+# table.blob .slc  { color:#838183; font-style:italic; }
+# table.blob .com  { color:#838183; font-style:italic; }
+# table.blob .dir  { color:#008200; }
+# table.blob .sym  { color:#000000; }
+# table.blob .kwa  { color:#000000; font-weight:bold; }
+# table.blob .kwb  { color:#830000; }
+# table.blob .kwc  { color:#000000; font-weight:bold; }
+# table.blob .kwd  { color:#010181; }
+#
+#
+# Style definition file generated by highlight 2.6.14, http://www.andre-simon.de/
+#
+# body.hl  { background-color:#ffffff; }
+# pre.hl   { color:#000000; background-color:#ffffff; font-size:10pt; font-family:'Courier New';}
+# .hl.num  { color:#2928ff; }
+# .hl.esc  { color:#ff00ff; }
+# .hl.str  { color:#ff0000; }
+# .hl.dstr { color:#818100; }
+# .hl.slc  { color:#838183; font-style:italic; }
+# .hl.com  { color:#838183; font-style:italic; }
+# .hl.dir  { color:#008200; }
+# .hl.sym  { color:#000000; }
+# .hl.line { color:#555555; }
+# .hl.mark { background-color:#ffffbb;}
+# .hl.kwa  { color:#000000; font-weight:bold; }
+# .hl.kwb  { color:#830000; }
+# .hl.kwc  { color:#000000; font-weight:bold; }
+# .hl.kwd  { color:#010181; }
+#
+#
+# Style definition file generated by highlight 3.8, http://www.andre-simon.de/
+#
+# body.hl { background-color:#e0eaee; }
+# pre.hl  { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New';}
+# .hl.num { color:#b07e00; }
+# .hl.esc { color:#ff00ff; }
+# .hl.str { color:#bf0303; }
+# .hl.pps { color:#818100; }
+# .hl.slc { color:#838183; font-style:italic; }
+# .hl.com { color:#838183; font-style:italic; }
+# .hl.ppc { color:#008200; }
+# .hl.opt { color:#000000; }
+# .hl.lin { color:#555555; }
+# .hl.kwa { color:#000000; font-weight:bold; }
+# .hl.kwb { color:#0057ae; }
+# .hl.kwc { color:#000000; font-weight:bold; }
+# .hl.kwd { color:#010181; }
+#
+#
+# Style definition file generated by highlight 3.13, http://www.andre-simon.de/
+#
+# body.hl { background-color:#e0eaee; }
+# pre.hl  { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New',monospace;}
+# .hl.num { color:#b07e00; }
+# .hl.esc { color:#ff00ff; }
+# .hl.str { color:#bf0303; }
+# .hl.pps { color:#818100; }
+# .hl.slc { color:#838183; font-style:italic; }
+# .hl.com { color:#838183; font-style:italic; }
+# .hl.ppc { color:#008200; }
+# .hl.opt { color:#000000; }
+# .hl.ipl { color:#0057ae; }
+# .hl.lin { color:#555555; }
+# .hl.kwa { color:#000000; font-weight:bold; }
+# .hl.kwb { color:#0057ae; }
+# .hl.kwc { color:#000000; font-weight:bold; }
+# .hl.kwd { color:#010181; }
+#
+#
+# The following environment variables can be used to retrieve the configuration
+# of the repository for which this script is called:
+# CGIT_REPO_URL        ( = repo.url       setting )
+# CGIT_REPO_NAME       ( = repo.name      setting )
+# CGIT_REPO_PATH       ( = repo.path      setting )
+# CGIT_REPO_OWNER      ( = repo.owner     setting )
+# CGIT_REPO_DEFBRANCH  ( = repo.defbranch setting )
+# CGIT_REPO_SECTION    ( = section        setting )
+# CGIT_REPO_CLONE_URL  ( = repo.clone-url setting )
+#
+
+# store filename and extension in local vars
+BASENAME="$1"
+EXTENSION="${BASENAME##*.}"
+
+[ "${BASENAME}" = "${EXTENSION}" ] && EXTENSION=txt
+[ -z "${EXTENSION}" ] && EXTENSION=txt
+
+# map Makefile and Makefile.* to .mk
+[ "${BASENAME%%.*}" = "Makefile" ] && EXTENSION=mk
+
+# highlight versions 2 and 3 have different commandline options. Specifically,
+# the -X option that is used for version 2 is replaced by the -O xhtml option
+# for version 3.
+#
+# Version 2 can be found (for example) on EPEL 5, while version 3 can be
+# found (for example) on EPEL 6.
+#
+# This is for version 2
+exec highlight --force -f -I -X -S "$EXTENSION" 2>/dev/null
+
+# This is for version 3
+#exec highlight --force -f -I -O xhtml -S "$EXTENSION" 2>/dev/null
diff --git a/www/git.causal.agency/cgit/gen-version.sh b/www/git.causal.agency/cgit/gen-version.sh
new file mode 100755
index 00000000..80cf49af
--- /dev/null
+++ b/www/git.causal.agency/cgit/gen-version.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+# Get version-info specified in Makefile
+V=$1
+
+# Use `git describe` to get current version if we're inside a git repo
+if test "$(git rev-parse --git-dir 2>/dev/null)" = '.git'
+then
+	V=$(git describe --abbrev=4 HEAD 2>/dev/null)
+fi
+
+new="CGIT_VERSION = $V"
+old=$(cat VERSION 2>/dev/null)
+
+# Exit if VERSION is uptodate
+test "$old" = "$new" && exit 0
+
+# Update VERSION with new version-info
+echo "$new" > VERSION
+cat VERSION
diff --git a/www/git.causal.agency/cgit/html.c b/www/git.causal.agency/cgit/html.c
new file mode 100644
index 00000000..7f81965f
--- /dev/null
+++ b/www/git.causal.agency/cgit/html.c
@@ -0,0 +1,344 @@
+/* html.c: helper functions for html output
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "html.h"
+#include "url.h"
+
+/* Percent-encoding of each character, except: a-zA-Z0-9!$()*,./:;@- */
+static const char* url_escape_table[256] = {
+	"%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
+	"%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
+	"%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
+	"%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
+	"%20", NULL,  "%22", "%23", NULL,  "%25", "%26", "%27",
+	NULL,  NULL,  NULL,  "%2b", NULL,  NULL,  NULL,  NULL,
+	NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
+	NULL,  NULL,  NULL,  NULL,  "%3c", "%3d", "%3e", "%3f",
+	NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
+	NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
+	NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
+	NULL,  NULL,  NULL,  NULL,  "%5c", NULL,  "%5e", NULL,
+	"%60", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
+	NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
+	NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
+	NULL,  NULL,  NULL,  "%7b", "%7c", "%7d", NULL,  "%7f",
+	"%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
+	"%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
+	"%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
+	"%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
+	"%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
+	"%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
+	"%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
+	"%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
+	"%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
+	"%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
+	"%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
+	"%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
+	"%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
+	"%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
+	"%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
+	"%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
+};
+
+char *fmt(const char *format, ...)
+{
+	static char buf[8][1024];
+	static int bufidx;
+	int len;
+	va_list args;
+
+	bufidx++;
+	bufidx &= 7;
+
+	va_start(args, format);
+	len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args);
+	va_end(args);
+	if (len > sizeof(buf[bufidx])) {
+		fprintf(stderr, "[html.c] string truncated: %s\n", format);
+		exit(1);
+	}
+	return buf[bufidx];
+}
+
+char *fmtalloc(const char *format, ...)
+{
+	struct strbuf sb = STRBUF_INIT;
+	va_list args;
+
+	va_start(args, format);
+	strbuf_vaddf(&sb, format, args);
+	va_end(args);
+
+	return strbuf_detach(&sb, NULL);
+}
+
+void html_raw(const char *data, size_t size)
+{
+	if (write(STDOUT_FILENO, data, size) != size)
+		die_errno("write error on html output");
+}
+
+void html(const char *txt)
+{
+	html_raw(txt, strlen(txt));
+}
+
+void htmlf(const char *format, ...)
+{
+	va_list args;
+	struct strbuf buf = STRBUF_INIT;
+
+	va_start(args, format);
+	strbuf_vaddf(&buf, format, args);
+	va_end(args);
+	html(buf.buf);
+	strbuf_release(&buf);
+}
+
+void html_txtf(const char *format, ...)
+{
+	va_list args;
+
+	va_start(args, format);
+	html_vtxtf(format, args);
+	va_end(args);
+}
+
+void html_vtxtf(const char *format, va_list ap)
+{
+	va_list cp;
+	struct strbuf buf = STRBUF_INIT;
+
+	va_copy(cp, ap);
+	strbuf_vaddf(&buf, format, cp);
+	va_end(cp);
+	html_txt(buf.buf);
+	strbuf_release(&buf);
+}
+
+void html_txt(const char *txt)
+{
+	if (txt)
+		html_ntxt(txt, strlen(txt));
+}
+
+ssize_t html_ntxt(const char *txt, size_t len)
+{
+	const char *t = txt;
+	ssize_t slen;
+
+	if (len > SSIZE_MAX)
+		return -1;
+
+	slen = (ssize_t) len;
+	while (t && *t && slen--) {
+		int c = *t;
+		if (c == '<' || c == '>' || c == '&') {
+			html_raw(txt, t - txt);
+			if (c == '>')
+				html("&gt;");
+			else if (c == '<')
+				html("&lt;");
+			else if (c == '&')
+				html("&amp;");
+			txt = t + 1;
+		}
+		t++;
+	}
+	if (t != txt)
+		html_raw(txt, t - txt);
+	return slen;
+}
+
+void html_attrf(const char *fmt, ...)
+{
+	va_list ap;
+	struct strbuf sb = STRBUF_INIT;
+
+	va_start(ap, fmt);
+	strbuf_vaddf(&sb, fmt, ap);
+	va_end(ap);
+
+	html_attr(sb.buf);
+	strbuf_release(&sb);
+}
+
+void html_attr(const char *txt)
+{
+	const char *t = txt;
+	while (t && *t) {
+		int c = *t;
+		if (c == '<' || c == '>' || c == '\'' || c == '\"' || c == '&') {
+			html_raw(txt, t - txt);
+			if (c == '>')
+				html("&gt;");
+			else if (c == '<')
+				html("&lt;");
+			else if (c == '\'')
+				html("&#x27;");
+			else if (c == '"')
+				html("&quot;");
+			else if (c == '&')
+				html("&amp;");
+			txt = t + 1;
+		}
+		t++;
+	}
+	if (t != txt)
+		html(txt);
+}
+
+void html_url_path(const char *txt)
+{
+	const char *t = txt;
+	while (t && *t) {
+		unsigned char c = *t;
+		const char *e = url_escape_table[c];
+		if (e && c != '+' && c != '&') {
+			html_raw(txt, t - txt);
+			html(e);
+			txt = t + 1;
+		}
+		t++;
+	}
+	if (t != txt)
+		html(txt);
+}
+
+void html_url_arg(const char *txt)
+{
+	const char *t = txt;
+	while (t && *t) {
+		unsigned char c = *t;
+		const char *e = url_escape_table[c];
+		if (c == ' ')
+			e = "+";
+		if (e) {
+			html_raw(txt, t - txt);
+			html(e);
+			txt = t + 1;
+		}
+		t++;
+	}
+	if (t != txt)
+		html(txt);
+}
+
+void html_header_arg_in_quotes(const char *txt)
+{
+	const char *t = txt;
+	while (t && *t) {
+		unsigned char c = *t;
+		const char *e = NULL;
+		if (c == '\\')
+			e = "\\\\";
+		else if (c == '\r')
+			e = "\\r";
+		else if (c == '\n')
+			e = "\\n";
+		else if (c == '"')
+			e = "\\\"";
+		if (e) {
+			html_raw(txt, t - txt);
+			html(e);
+			txt = t + 1;
+		}
+		t++;
+	}
+	if (t != txt)
+		html(txt);
+
+}
+
+void html_hidden(const char *name, const char *value)
+{
+	html("<input type='hidden' name='");
+	html_attr(name);
+	html("' value='");
+	html_attr(value);
+	html("'/>");
+}
+
+void html_option(const char *value, const char *text, const char *selected_value)
+{
+	html("<option value='");
+	html_attr(value);
+	html("'");
+	if (selected_value && !strcmp(selected_value, value))
+		html(" selected='selected'");
+	html(">");
+	html_txt(text);
+	html("</option>\n");
+}
+
+void html_intoption(int value, const char *text, int selected_value)
+{
+	htmlf("<option value='%d'%s>", value,
+	      value == selected_value ? " selected='selected'" : "");
+	html_txt(text);
+	html("</option>");
+}
+
+void html_link_open(const char *url, const char *title, const char *class)
+{
+	html("<a href='");
+	html_attr(url);
+	if (title) {
+		html("' title='");
+		html_attr(title);
+	}
+	if (class) {
+		html("' class='");
+		html_attr(class);
+	}
+	html("'>");
+}
+
+void html_link_close(void)
+{
+	html("</a>");
+}
+
+void html_fileperm(unsigned short mode)
+{
+	htmlf("%c%c%c", (mode & 4 ? 'r' : '-'),
+	      (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-'));
+}
+
+int html_include(const char *filename)
+{
+	FILE *f;
+	char buf[4096];
+	size_t len;
+
+	if (!(f = fopen(filename, "r"))) {
+		fprintf(stderr, "[cgit] Failed to include file %s: %s (%d).\n",
+			filename, strerror(errno), errno);
+		return -1;
+	}
+	while ((len = fread(buf, 1, 4096, f)) > 0)
+		html_raw(buf, len);
+	fclose(f);
+	return 0;
+}
+
+void http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value))
+{
+	const char *t = txt;
+
+	while (t && *t) {
+		char *name = url_decode_parameter_name(&t);
+		if (*name) {
+			char *value = url_decode_parameter_value(&t);
+			fn(name, value);
+			free(value);
+		}
+		free(name);
+	}
+}
diff --git a/www/git.causal.agency/cgit/html.h b/www/git.causal.agency/cgit/html.h
new file mode 100644
index 00000000..fa4de775
--- /dev/null
+++ b/www/git.causal.agency/cgit/html.h
@@ -0,0 +1,37 @@
+#ifndef HTML_H
+#define HTML_H
+
+#include "cgit.h"
+
+extern void html_raw(const char *txt, size_t size);
+extern void html(const char *txt);
+
+__attribute__((format (printf,1,2)))
+extern void htmlf(const char *format,...);
+
+__attribute__((format (printf,1,2)))
+extern void html_txtf(const char *format,...);
+
+__attribute__((format (printf,1,0)))
+extern void html_vtxtf(const char *format, va_list ap);
+
+__attribute__((format (printf,1,2)))
+extern void html_attrf(const char *format,...);
+
+extern void html_txt(const char *txt);
+extern ssize_t html_ntxt(const char *txt, size_t len);
+extern void html_attr(const char *txt);
+extern void html_url_path(const char *txt);
+extern void html_url_arg(const char *txt);
+extern void html_header_arg_in_quotes(const char *txt);
+extern void html_hidden(const char *name, const char *value);
+extern void html_option(const char *value, const char *text, const char *selected_value);
+extern void html_intoption(int value, const char *text, int selected_value);
+extern void html_link_open(const char *url, const char *title, const char *class);
+extern void html_link_close(void);
+extern void html_fileperm(unsigned short mode);
+extern int html_include(const char *filename);
+
+extern void http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value));
+
+#endif /* HTML_H */
diff --git a/www/git.causal.agency/cgit/parsing.c b/www/git.causal.agency/cgit/parsing.c
new file mode 100644
index 00000000..93b4767e
--- /dev/null
+++ b/www/git.causal.agency/cgit/parsing.c
@@ -0,0 +1,224 @@
+/* parsing.c: parsing of config files
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+
+/*
+ * url syntax: [repo ['/' cmd [ '/' path]]]
+ *   repo: any valid repo url, may contain '/'
+ *   cmd:  log | commit | diff | tree | view | blob | snapshot
+ *   path: any valid path, may contain '/'
+ *
+ */
+void cgit_parse_url(const char *url)
+{
+	char *c, *cmd, *p;
+	struct cgit_repo *repo;
+
+	if (!url || url[0] == '\0')
+		return;
+
+	ctx.qry.page = NULL;
+	ctx.repo = cgit_get_repoinfo(url);
+	if (ctx.repo) {
+		ctx.qry.repo = ctx.repo->url;
+		return;
+	}
+
+	cmd = NULL;
+	c = strchr(url, '/');
+	while (c) {
+		c[0] = '\0';
+		repo = cgit_get_repoinfo(url);
+		if (repo) {
+			ctx.repo = repo;
+			cmd = c;
+		}
+		c[0] = '/';
+		c = strchr(c + 1, '/');
+	}
+
+	if (ctx.repo) {
+		ctx.qry.repo = ctx.repo->url;
+		p = strchr(cmd + 1, '/');
+		if (p) {
+			p[0] = '\0';
+			if (p[1])
+				ctx.qry.path = trim_end(p + 1, '/');
+		}
+		if (cmd[1])
+			ctx.qry.page = xstrdup(cmd + 1);
+	}
+}
+
+static char *substr(const char *head, const char *tail)
+{
+	char *buf;
+
+	if (tail < head)
+		return xstrdup("");
+	buf = xmalloc(tail - head + 1);
+	strlcpy(buf, head, tail - head + 1);
+	return buf;
+}
+
+static void parse_user(const char *t, char **name, char **email, unsigned long *date, int *tz)
+{
+	struct ident_split ident;
+	unsigned email_len;
+
+	if (!split_ident_line(&ident, t, strchrnul(t, '\n') - t)) {
+		*name = substr(ident.name_begin, ident.name_end);
+
+		email_len = ident.mail_end - ident.mail_begin;
+		*email = xmalloc(strlen("<") + email_len + strlen(">") + 1);
+		xsnprintf(*email, email_len + 3, "<%.*s>", email_len, ident.mail_begin);
+
+		if (ident.date_begin)
+			*date = strtoul(ident.date_begin, NULL, 10);
+		if (ident.tz_begin)
+			*tz = atoi(ident.tz_begin);
+	}
+}
+
+#ifdef NO_ICONV
+#define reencode(a, b, c)
+#else
+static const char *reencode(char **txt, const char *src_enc, const char *dst_enc)
+{
+	char *tmp;
+
+	if (!txt)
+		return NULL;
+
+	if (!*txt || !src_enc || !dst_enc)
+		return *txt;
+
+	/* no encoding needed if src_enc equals dst_enc */
+	if (!strcasecmp(src_enc, dst_enc))
+		return *txt;
+
+	tmp = reencode_string(*txt, dst_enc, src_enc);
+	if (tmp) {
+		free(*txt);
+		*txt = tmp;
+	}
+	return *txt;
+}
+#endif
+
+static const char *next_header_line(const char *p)
+{
+	p = strchr(p, '\n');
+	if (!p)
+		return NULL;
+	return p + 1;
+}
+
+static int end_of_header(const char *p)
+{
+	return !p || (*p == '\n');
+}
+
+struct commitinfo *cgit_parse_commit(struct commit *commit)
+{
+	const int sha1hex_len = 40;
+	struct commitinfo *ret;
+	const char *p = repo_get_commit_buffer(the_repository, commit, NULL);
+	const char *t;
+
+	ret = xcalloc(1, sizeof(struct commitinfo));
+	ret->commit = commit;
+
+	if (!p)
+		return ret;
+
+	if (!skip_prefix(p, "tree ", &p))
+		die("Bad commit: %s", oid_to_hex(&commit->object.oid));
+	p += sha1hex_len + 1;
+
+	while (skip_prefix(p, "parent ", &p))
+		p += sha1hex_len + 1;
+
+	if (p && skip_prefix(p, "author ", &p)) {
+		parse_user(p, &ret->author, &ret->author_email,
+			&ret->author_date, &ret->author_tz);
+		p = next_header_line(p);
+	}
+
+	if (p && skip_prefix(p, "committer ", &p)) {
+		parse_user(p, &ret->committer, &ret->committer_email,
+			&ret->committer_date, &ret->committer_tz);
+		p = next_header_line(p);
+	}
+
+	if (p && skip_prefix(p, "encoding ", &p)) {
+		t = strchr(p, '\n');
+		if (t) {
+			ret->msg_encoding = substr(p, t + 1);
+			p = t + 1;
+		}
+	}
+
+	if (!ret->msg_encoding)
+		ret->msg_encoding = xstrdup("UTF-8");
+
+	while (!end_of_header(p))
+		p = next_header_line(p);
+	while (p && *p == '\n')
+		p++;
+	if (!p)
+		return ret;
+
+	t = strchrnul(p, '\n');
+	ret->subject = substr(p, t);
+	while (*t == '\n')
+		t++;
+	ret->msg = xstrdup(t);
+
+	reencode(&ret->author, ret->msg_encoding, PAGE_ENCODING);
+	reencode(&ret->author_email, ret->msg_encoding, PAGE_ENCODING);
+	reencode(&ret->committer, ret->msg_encoding, PAGE_ENCODING);
+	reencode(&ret->committer_email, ret->msg_encoding, PAGE_ENCODING);
+	reencode(&ret->subject, ret->msg_encoding, PAGE_ENCODING);
+	reencode(&ret->msg, ret->msg_encoding, PAGE_ENCODING);
+
+	return ret;
+}
+
+struct taginfo *cgit_parse_tag(struct tag *tag)
+{
+	void *data;
+	enum object_type type;
+	unsigned long size;
+	const char *p;
+	struct taginfo *ret = NULL;
+
+	data = read_object_file(&tag->object.oid, &type, &size);
+	if (!data || type != OBJ_TAG)
+		goto cleanup;
+
+	ret = xcalloc(1, sizeof(struct taginfo));
+
+	for (p = data; !end_of_header(p); p = next_header_line(p)) {
+		if (skip_prefix(p, "tagger ", &p)) {
+			parse_user(p, &ret->tagger, &ret->tagger_email,
+				&ret->tagger_date, &ret->tagger_tz);
+		}
+	}
+
+	while (p && *p == '\n')
+		p++;
+
+	if (p && *p)
+		ret->msg = xstrdup(p);
+
+cleanup:
+	free(data);
+	return ret;
+}
diff --git a/www/git.causal.agency/cgit/robots.txt b/www/git.causal.agency/cgit/robots.txt
new file mode 100644
index 00000000..1b33266d
--- /dev/null
+++ b/www/git.causal.agency/cgit/robots.txt
@@ -0,0 +1,4 @@
+User-agent: *
+Disallow: /*/snapshot/*
+Disallow: /*/blame/*
+Allow: /
diff --git a/www/git.causal.agency/cgit/scan-tree.c b/www/git.causal.agency/cgit/scan-tree.c
new file mode 100644
index 00000000..6a2f65a8
--- /dev/null
+++ b/www/git.causal.agency/cgit/scan-tree.c
@@ -0,0 +1,270 @@
+/* scan-tree.c
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "scan-tree.h"
+#include "configfile.h"
+#include "html.h"
+#include <config.h>
+
+/* return 1 if path contains a objects/ directory and a HEAD file */
+static int is_git_dir(const char *path)
+{
+	struct stat st;
+	struct strbuf pathbuf = STRBUF_INIT;
+	int result = 0;
+
+	strbuf_addf(&pathbuf, "%s/objects", path);
+	if (stat(pathbuf.buf, &st)) {
+		if (errno != ENOENT)
+			fprintf(stderr, "Error checking path %s: %s (%d)\n",
+				path, strerror(errno), errno);
+		goto out;
+	}
+	if (!S_ISDIR(st.st_mode))
+		goto out;
+
+	strbuf_reset(&pathbuf);
+	strbuf_addf(&pathbuf, "%s/HEAD", path);
+	if (stat(pathbuf.buf, &st)) {
+		if (errno != ENOENT)
+			fprintf(stderr, "Error checking path %s: %s (%d)\n",
+				path, strerror(errno), errno);
+		goto out;
+	}
+	if (!S_ISREG(st.st_mode))
+		goto out;
+
+	result = 1;
+out:
+	strbuf_release(&pathbuf);
+	return result;
+}
+
+static struct cgit_repo *repo;
+static repo_config_fn config_fn;
+
+static void scan_tree_repo_config(const char *name, const char *value)
+{
+	config_fn(repo, name, value);
+}
+
+static int gitconfig_config(const char *key, const char *value, void *cb)
+{
+	const char *name;
+
+	if (!strcmp(key, "gitweb.owner"))
+		config_fn(repo, "owner", value);
+	else if (!strcmp(key, "gitweb.description"))
+		config_fn(repo, "desc", value);
+	else if (!strcmp(key, "gitweb.category"))
+		config_fn(repo, "section", value);
+	else if (!strcmp(key, "gitweb.homepage"))
+		config_fn(repo, "homepage", value);
+	else if (skip_prefix(key, "cgit.", &name))
+		config_fn(repo, name, value);
+
+	return 0;
+}
+
+static char *xstrrchr(char *s, char *from, int c)
+{
+	while (from >= s && *from != c)
+		from--;
+	return from < s ? NULL : from;
+}
+
+static void add_repo(const char *base, struct strbuf *path, repo_config_fn fn)
+{
+	struct stat st;
+	struct passwd *pwd;
+	size_t pathlen;
+	struct strbuf rel = STRBUF_INIT;
+	char *p, *slash;
+	int n;
+	size_t size;
+
+	if (stat(path->buf, &st)) {
+		fprintf(stderr, "Error accessing %s: %s (%d)\n",
+			path->buf, strerror(errno), errno);
+		return;
+	}
+
+	strbuf_addch(path, '/');
+	pathlen = path->len;
+
+	if (ctx.cfg.strict_export) {
+		strbuf_addstr(path, ctx.cfg.strict_export);
+		if(stat(path->buf, &st))
+			return;
+		strbuf_setlen(path, pathlen);
+	}
+
+	strbuf_addstr(path, "noweb");
+	if (!stat(path->buf, &st))
+		return;
+	strbuf_setlen(path, pathlen);
+
+	if (!starts_with(path->buf, base))
+		strbuf_addbuf(&rel, path);
+	else
+		strbuf_addstr(&rel, path->buf + strlen(base) + 1);
+
+	if (!strcmp(rel.buf + rel.len - 5, "/.git"))
+		strbuf_setlen(&rel, rel.len - 5);
+	else if (rel.len && rel.buf[rel.len - 1] == '/')
+		strbuf_setlen(&rel, rel.len - 1);
+
+	repo = cgit_add_repo(rel.buf);
+	config_fn = fn;
+	if (ctx.cfg.enable_git_config) {
+		strbuf_addstr(path, "config");
+		git_config_from_file(gitconfig_config, path->buf, NULL);
+		strbuf_setlen(path, pathlen);
+	}
+
+	if (ctx.cfg.remove_suffix) {
+		size_t urllen;
+		strip_suffix(repo->url, ".git", &urllen);
+		strip_suffix_mem(repo->url, &urllen, "/");
+		repo->url[urllen] = '\0';
+	}
+	repo->path = xstrdup(path->buf);
+	while (!repo->owner) {
+		if ((pwd = getpwuid(st.st_uid)) == NULL) {
+			fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
+				path->buf, strerror(errno), errno);
+			break;
+		}
+		if (pwd->pw_gecos)
+			if ((p = strchr(pwd->pw_gecos, ',')))
+				*p = '\0';
+		repo->owner = xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name);
+	}
+
+	if (repo->desc == cgit_default_repo_desc || !repo->desc) {
+		strbuf_addstr(path, "description");
+		if (!stat(path->buf, &st))
+			readfile(path->buf, &repo->desc, &size);
+		strbuf_setlen(path, pathlen);
+	}
+
+	if (ctx.cfg.section_from_path) {
+		n = ctx.cfg.section_from_path;
+		if (n > 0) {
+			slash = rel.buf - 1;
+			while (slash && n && (slash = strchr(slash + 1, '/')))
+				n--;
+		} else {
+			slash = rel.buf + rel.len;
+			while (slash && n && (slash = xstrrchr(rel.buf, slash - 1, '/')))
+				n++;
+		}
+		if (slash && !n) {
+			*slash = '\0';
+			repo->section = xstrdup(rel.buf);
+			*slash = '/';
+			if (starts_with(repo->name, repo->section)) {
+				repo->name += strlen(repo->section);
+				if (*repo->name == '/')
+					repo->name++;
+			}
+		}
+	}
+
+	strbuf_addstr(path, "cgitrc");
+	if (!stat(path->buf, &st))
+		parse_configfile(path->buf, &scan_tree_repo_config);
+
+	strbuf_release(&rel);
+}
+
+static void scan_path(const char *base, const char *path, repo_config_fn fn)
+{
+	DIR *dir = opendir(path);
+	struct dirent *ent;
+	struct strbuf pathbuf = STRBUF_INIT;
+	size_t pathlen = strlen(path);
+	struct stat st;
+
+	if (!dir) {
+		fprintf(stderr, "Error opening directory %s: %s (%d)\n",
+			path, strerror(errno), errno);
+		return;
+	}
+
+	strbuf_add(&pathbuf, path, strlen(path));
+	if (is_git_dir(pathbuf.buf)) {
+		add_repo(base, &pathbuf, fn);
+		goto end;
+	}
+	strbuf_addstr(&pathbuf, "/.git");
+	if (is_git_dir(pathbuf.buf)) {
+		add_repo(base, &pathbuf, fn);
+		goto end;
+	}
+	/*
+	 * Add one because we don't want to lose the trailing '/' when we
+	 * reset the length of pathbuf in the loop below.
+	 */
+	pathlen++;
+	while ((ent = readdir(dir)) != NULL) {
+		if (ent->d_name[0] == '.') {
+			if (ent->d_name[1] == '\0')
+				continue;
+			if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
+				continue;
+			if (!ctx.cfg.scan_hidden_path)
+				continue;
+		}
+		strbuf_setlen(&pathbuf, pathlen);
+		strbuf_addstr(&pathbuf, ent->d_name);
+		if (stat(pathbuf.buf, &st)) {
+			fprintf(stderr, "Error checking path %s: %s (%d)\n",
+				pathbuf.buf, strerror(errno), errno);
+			continue;
+		}
+		if (S_ISDIR(st.st_mode))
+			scan_path(base, pathbuf.buf, fn);
+	}
+end:
+	strbuf_release(&pathbuf);
+	closedir(dir);
+}
+
+void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn)
+{
+	struct strbuf line = STRBUF_INIT;
+	FILE *projects;
+	int err;
+
+	projects = fopen(projectsfile, "r");
+	if (!projects) {
+		fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n",
+			projectsfile, strerror(errno), errno);
+		return;
+	}
+	while (strbuf_getline(&line, projects) != EOF) {
+		if (!line.len)
+			continue;
+		strbuf_insert(&line, 0, "/", 1);
+		strbuf_insert(&line, 0, path, strlen(path));
+		scan_path(path, line.buf, fn);
+	}
+	if ((err = ferror(projects))) {
+		fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n",
+			projectsfile, strerror(err), err);
+	}
+	fclose(projects);
+	strbuf_release(&line);
+}
+
+void scan_tree(const char *path, repo_config_fn fn)
+{
+	scan_path(path, path, fn);
+}
diff --git a/www/git.causal.agency/cgit/scan-tree.h b/www/git.causal.agency/cgit/scan-tree.h
new file mode 100644
index 00000000..1afbd4bb
--- /dev/null
+++ b/www/git.causal.agency/cgit/scan-tree.h
@@ -0,0 +1,2 @@
+extern void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn);
+extern void scan_tree(const char *path, repo_config_fn fn);
diff --git a/www/git.causal.agency/cgit/shared.c b/www/git.causal.agency/cgit/shared.c
new file mode 100644
index 00000000..8115469a
--- /dev/null
+++ b/www/git.causal.agency/cgit/shared.c
@@ -0,0 +1,579 @@
+/* shared.c: global vars + some callback functions
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+
+struct cgit_repolist cgit_repolist;
+struct cgit_context ctx;
+
+int chk_zero(int result, char *msg)
+{
+	if (result != 0)
+		die_errno("%s", msg);
+	return result;
+}
+
+int chk_positive(int result, char *msg)
+{
+	if (result <= 0)
+		die_errno("%s", msg);
+	return result;
+}
+
+int chk_non_negative(int result, char *msg)
+{
+	if (result < 0)
+		die_errno("%s", msg);
+	return result;
+}
+
+char *cgit_default_repo_desc = "[no description]";
+struct cgit_repo *cgit_add_repo(const char *url)
+{
+	struct cgit_repo *ret;
+
+	if (++cgit_repolist.count > cgit_repolist.length) {
+		if (cgit_repolist.length == 0)
+			cgit_repolist.length = 8;
+		else
+			cgit_repolist.length *= 2;
+		cgit_repolist.repos = xrealloc(cgit_repolist.repos,
+					       cgit_repolist.length *
+					       sizeof(struct cgit_repo));
+	}
+
+	ret = &cgit_repolist.repos[cgit_repolist.count-1];
+	memset(ret, 0, sizeof(struct cgit_repo));
+	ret->url = trim_end(url, '/');
+	ret->name = ret->url;
+	ret->path = NULL;
+	ret->desc = cgit_default_repo_desc;
+	ret->extra_head_content = NULL;
+	ret->owner = NULL;
+	ret->homepage = NULL;
+	ret->section = ctx.cfg.section;
+	ret->snapshots = ctx.cfg.snapshots;
+	ret->enable_blame = ctx.cfg.enable_blame;
+	ret->enable_commit_graph = ctx.cfg.enable_commit_graph;
+	ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
+	ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
+	ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
+	ret->enable_subject_links = ctx.cfg.enable_subject_links;
+	ret->enable_html_serving = ctx.cfg.enable_html_serving;
+	ret->max_stats = ctx.cfg.max_stats;
+	ret->branch_sort = ctx.cfg.branch_sort;
+	ret->commit_sort = ctx.cfg.commit_sort;
+	ret->module_link = ctx.cfg.module_link;
+	ret->readme = ctx.cfg.readme;
+	ret->mtime = -1;
+	ret->about_filter = ctx.cfg.about_filter;
+	ret->commit_filter = ctx.cfg.commit_filter;
+	ret->source_filter = ctx.cfg.source_filter;
+	ret->email_filter = ctx.cfg.email_filter;
+	ret->owner_filter = ctx.cfg.owner_filter;
+	ret->clone_url = ctx.cfg.clone_url;
+	ret->submodules.strdup_strings = 1;
+	ret->hide = ret->ignore = 0;
+	return ret;
+}
+
+struct cgit_repo *cgit_get_repoinfo(const char *url)
+{
+	int i;
+	struct cgit_repo *repo;
+
+	for (i = 0; i < cgit_repolist.count; i++) {
+		repo = &cgit_repolist.repos[i];
+		if (repo->ignore)
+			continue;
+		if (!strcmp(repo->url, url))
+			return repo;
+	}
+	return NULL;
+}
+
+void cgit_free_commitinfo(struct commitinfo *info)
+{
+	free(info->author);
+	free(info->author_email);
+	free(info->committer);
+	free(info->committer_email);
+	free(info->subject);
+	free(info->msg);
+	free(info->msg_encoding);
+	free(info);
+}
+
+char *trim_end(const char *str, char c)
+{
+	int len;
+
+	if (str == NULL)
+		return NULL;
+	len = strlen(str);
+	while (len > 0 && str[len - 1] == c)
+		len--;
+	if (len == 0)
+		return NULL;
+	return xstrndup(str, len);
+}
+
+char *ensure_end(const char *str, char c)
+{
+	size_t len = strlen(str);
+	char *result;
+
+	if (len && str[len - 1] == c)
+		return xstrndup(str, len);
+
+	result = xmalloc(len + 2);
+	memcpy(result, str, len);
+	result[len] = '/';
+	result[len + 1] = '\0';
+	return result;
+}
+
+void strbuf_ensure_end(struct strbuf *sb, char c)
+{
+	if (!sb->len || sb->buf[sb->len - 1] != c)
+		strbuf_addch(sb, c);
+}
+
+void cgit_add_ref(struct reflist *list, struct refinfo *ref)
+{
+	size_t size;
+
+	if (list->count >= list->alloc) {
+		list->alloc += (list->alloc ? list->alloc : 4);
+		size = list->alloc * sizeof(struct refinfo *);
+		list->refs = xrealloc(list->refs, size);
+	}
+	list->refs[list->count++] = ref;
+}
+
+static struct refinfo *cgit_mk_refinfo(const char *refname, const struct object_id *oid)
+{
+	struct refinfo *ref;
+
+	ref = xmalloc(sizeof (struct refinfo));
+	ref->refname = xstrdup(refname);
+	ref->object = parse_object(the_repository, oid);
+	switch (ref->object->type) {
+	case OBJ_TAG:
+		ref->tag = cgit_parse_tag((struct tag *)ref->object);
+		break;
+	case OBJ_COMMIT:
+		ref->commit = cgit_parse_commit((struct commit *)ref->object);
+		break;
+	}
+	return ref;
+}
+
+void cgit_free_taginfo(struct taginfo *tag)
+{
+	if (tag->tagger)
+		free(tag->tagger);
+	if (tag->tagger_email)
+		free(tag->tagger_email);
+	if (tag->msg)
+		free(tag->msg);
+	free(tag);
+}
+
+static void cgit_free_refinfo(struct refinfo *ref)
+{
+	if (ref->refname)
+		free((char *)ref->refname);
+	switch (ref->object->type) {
+	case OBJ_TAG:
+		cgit_free_taginfo(ref->tag);
+		break;
+	case OBJ_COMMIT:
+		cgit_free_commitinfo(ref->commit);
+		break;
+	}
+	free(ref);
+}
+
+void cgit_free_reflist_inner(struct reflist *list)
+{
+	int i;
+
+	for (i = 0; i < list->count; i++) {
+		cgit_free_refinfo(list->refs[i]);
+	}
+	free(list->refs);
+}
+
+int cgit_refs_cb(const char *refname, const struct object_id *oid, int flags,
+		  void *cb_data)
+{
+	struct reflist *list = (struct reflist *)cb_data;
+	struct refinfo *info = cgit_mk_refinfo(refname, oid);
+
+	if (info)
+		cgit_add_ref(list, info);
+	return 0;
+}
+
+void cgit_diff_tree_cb(struct diff_queue_struct *q,
+		       struct diff_options *options, void *data)
+{
+	int i;
+
+	for (i = 0; i < q->nr; i++) {
+		if (q->queue[i]->status == 'U')
+			continue;
+		((filepair_fn)data)(q->queue[i]);
+	}
+}
+
+static int load_mmfile(mmfile_t *file, const struct object_id *oid)
+{
+	enum object_type type;
+
+	if (is_null_oid(oid)) {
+		file->ptr = (char *)"";
+		file->size = 0;
+	} else {
+		file->ptr = read_object_file(oid, &type,
+		                           (unsigned long *)&file->size);
+	}
+	return 1;
+}
+
+/*
+ * Receive diff-buffers from xdiff and concatenate them as
+ * needed across multiple callbacks.
+ *
+ * This is basically a copy of xdiff-interface.c/xdiff_outf(),
+ * ripped from git and modified to use globals instead of
+ * a special callback-struct.
+ */
+static char *diffbuf = NULL;
+static int buflen = 0;
+
+static int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
+{
+	int i;
+
+	for (i = 0; i < nbuf; i++) {
+		if (mb[i].ptr[mb[i].size-1] != '\n') {
+			/* Incomplete line */
+			diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
+			memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
+			buflen += mb[i].size;
+			continue;
+		}
+
+		/* we have a complete line */
+		if (!diffbuf) {
+			((linediff_fn)priv)(mb[i].ptr, mb[i].size);
+			continue;
+		}
+		diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
+		memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
+		((linediff_fn)priv)(diffbuf, buflen + mb[i].size);
+		free(diffbuf);
+		diffbuf = NULL;
+		buflen = 0;
+	}
+	if (diffbuf) {
+		((linediff_fn)priv)(diffbuf, buflen);
+		free(diffbuf);
+		diffbuf = NULL;
+		buflen = 0;
+	}
+	return 0;
+}
+
+int cgit_diff_files(const struct object_id *old_oid,
+		    const struct object_id *new_oid, unsigned long *old_size,
+		    unsigned long *new_size, int *binary, int context,
+		    int ignorews, linediff_fn fn)
+{
+	mmfile_t file1, file2;
+	xpparam_t diff_params;
+	xdemitconf_t emit_params;
+	xdemitcb_t emit_cb;
+
+	if (!load_mmfile(&file1, old_oid) || !load_mmfile(&file2, new_oid))
+		return 1;
+
+	*old_size = file1.size;
+	*new_size = file2.size;
+
+	if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) ||
+	    (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) {
+		*binary = 1;
+		if (file1.size)
+			free(file1.ptr);
+		if (file2.size)
+			free(file2.ptr);
+		return 0;
+	}
+
+	memset(&diff_params, 0, sizeof(diff_params));
+	memset(&emit_params, 0, sizeof(emit_params));
+	memset(&emit_cb, 0, sizeof(emit_cb));
+	diff_params.flags = XDF_NEED_MINIMAL;
+	if (ignorews)
+		diff_params.flags |= XDF_IGNORE_WHITESPACE;
+	emit_params.ctxlen = context > 0 ? context : 3;
+	emit_params.flags = XDL_EMIT_FUNCNAMES;
+	emit_cb.out_line = filediff_cb;
+	emit_cb.priv = fn;
+	xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
+	if (file1.size)
+		free(file1.ptr);
+	if (file2.size)
+		free(file2.ptr);
+	return 0;
+}
+
+void cgit_diff_tree(const struct object_id *old_oid,
+		    const struct object_id *new_oid,
+		    filepair_fn fn, const char *prefix, int ignorews)
+{
+	struct diff_options opt;
+	struct pathspec_item item;
+
+	memset(&item, 0, sizeof(item));
+	diff_setup(&opt);
+	opt.output_format = DIFF_FORMAT_CALLBACK;
+	opt.detect_rename = 1;
+	opt.rename_limit = ctx.cfg.renamelimit;
+	opt.flags.recursive = 1;
+	if (ignorews)
+		DIFF_XDL_SET(&opt, IGNORE_WHITESPACE);
+	opt.format_callback = cgit_diff_tree_cb;
+	opt.format_callback_data = fn;
+	if (prefix) {
+		item.match = xstrdup(prefix);
+		item.len = strlen(prefix);
+		opt.pathspec.nr = 1;
+		opt.pathspec.items = &item;
+	}
+	diff_setup_done(&opt);
+
+	if (old_oid && !is_null_oid(old_oid))
+		diff_tree_oid(old_oid, new_oid, "", &opt);
+	else
+		diff_root_tree_oid(new_oid, "", &opt);
+	diffcore_std(&opt);
+	diff_flush(&opt);
+
+	free(item.match);
+}
+
+void cgit_diff_commit(struct commit *commit, filepair_fn fn, const char *prefix)
+{
+	const struct object_id *old_oid = NULL;
+
+	if (commit->parents)
+		old_oid = &commit->parents->item->object.oid;
+	cgit_diff_tree(old_oid, &commit->object.oid, fn, prefix,
+		       ctx.qry.ignorews);
+}
+
+int cgit_parse_snapshots_mask(const char *str)
+{
+	struct string_list tokens = STRING_LIST_INIT_DUP;
+	struct string_list_item *item;
+	const struct cgit_snapshot_format *f;
+	int rv = 0;
+
+	/* favor legacy setting */
+	if (atoi(str))
+		return 1;
+
+	if (strcmp(str, "all") == 0)
+		return INT_MAX;
+
+	string_list_split(&tokens, str, ' ', -1);
+	string_list_remove_empty_items(&tokens, 0);
+
+	for_each_string_list_item(item, &tokens) {
+		for (f = cgit_snapshot_formats; f->suffix; f++) {
+			if (!strcmp(item->string, f->suffix) ||
+			    !strcmp(item->string, f->suffix + 1)) {
+				rv |= cgit_snapshot_format_bit(f);
+				break;
+			}
+		}
+	}
+
+	string_list_clear(&tokens, 0);
+	return rv;
+}
+
+typedef struct {
+	char * name;
+	char * value;
+} cgit_env_var;
+
+void cgit_prepare_repo_env(struct cgit_repo * repo)
+{
+	cgit_env_var env_vars[] = {
+		{ .name = "CGIT_REPO_URL", .value = repo->url },
+		{ .name = "CGIT_REPO_NAME", .value = repo->name },
+		{ .name = "CGIT_REPO_PATH", .value = repo->path },
+		{ .name = "CGIT_REPO_OWNER", .value = repo->owner },
+		{ .name = "CGIT_REPO_DEFBRANCH", .value = repo->defbranch },
+		{ .name = "CGIT_REPO_SECTION", .value = repo->section },
+		{ .name = "CGIT_REPO_CLONE_URL", .value = repo->clone_url }
+	};
+	int env_var_count = ARRAY_SIZE(env_vars);
+	cgit_env_var *p, *q;
+	static char *warn = "cgit warning: failed to set env: %s=%s\n";
+
+	p = env_vars;
+	q = p + env_var_count;
+	for (; p < q; p++)
+		if (p->value && setenv(p->name, p->value, 1))
+			fprintf(stderr, warn, p->name, p->value);
+}
+
+/* Read the content of the specified file into a newly allocated buffer,
+ * zeroterminate the buffer and return 0 on success, errno otherwise.
+ */
+int readfile(const char *path, char **buf, size_t *size)
+{
+	int fd, e;
+	struct stat st;
+
+	fd = open(path, O_RDONLY);
+	if (fd == -1)
+		return errno;
+	if (fstat(fd, &st)) {
+		e = errno;
+		close(fd);
+		return e;
+	}
+	if (!S_ISREG(st.st_mode)) {
+		close(fd);
+		return EISDIR;
+	}
+	*buf = xmalloc(st.st_size + 1);
+	*size = read_in_full(fd, *buf, st.st_size);
+	e = errno;
+	(*buf)[*size] = '\0';
+	close(fd);
+	return (*size == st.st_size ? 0 : e);
+}
+
+static int is_token_char(char c)
+{
+	return isalnum(c) || c == '_';
+}
+
+/* Replace name with getenv(name), return pointer to zero-terminating char
+ */
+static char *expand_macro(char *name, int maxlength)
+{
+	char *value;
+	size_t len;
+
+	len = 0;
+	value = getenv(name);
+	if (value) {
+		len = strlen(value) + 1;
+		if (len > maxlength)
+			len = maxlength;
+		strlcpy(name, value, len);
+		--len;
+	}
+	return name + len;
+}
+
+#define EXPBUFSIZE (1024 * 8)
+
+/* Replace all tokens prefixed by '$' in the specified text with the
+ * value of the named environment variable.
+ * NB: the return value is a static buffer, i.e. it must be strdup'd
+ * by the caller.
+ */
+char *expand_macros(const char *txt)
+{
+	static char result[EXPBUFSIZE];
+	char *p, *start;
+	int len;
+
+	p = result;
+	start = NULL;
+	while (p < result + EXPBUFSIZE - 1 && txt && *txt) {
+		*p = *txt;
+		if (start) {
+			if (!is_token_char(*txt)) {
+				if (p - start > 0) {
+					*p = '\0';
+					len = result + EXPBUFSIZE - start - 1;
+					p = expand_macro(start, len) - 1;
+				}
+				start = NULL;
+				txt--;
+			}
+			p++;
+			txt++;
+			continue;
+		}
+		if (*txt == '$') {
+			start = p;
+			txt++;
+			continue;
+		}
+		p++;
+		txt++;
+	}
+	*p = '\0';
+	if (start && p - start > 0) {
+		len = result + EXPBUFSIZE - start - 1;
+		p = expand_macro(start, len);
+		*p = '\0';
+	}
+	return result;
+}
+
+char *get_mimetype_for_filename(const char *filename)
+{
+	char *ext, *mimetype, *token, line[1024], *saveptr;
+	FILE *file;
+	struct string_list_item *mime;
+
+	if (!filename)
+		return NULL;
+
+	ext = strrchr(filename, '.');
+	if (!ext)
+		return NULL;
+	++ext;
+	if (!ext[0])
+		return NULL;
+	mime = string_list_lookup(&ctx.cfg.mimetypes, ext);
+	if (mime)
+		return xstrdup(mime->util);
+
+	if (!ctx.cfg.mimetype_file)
+		return NULL;
+	file = fopen(ctx.cfg.mimetype_file, "r");
+	if (!file)
+		return NULL;
+	while (fgets(line, sizeof(line), file)) {
+		if (!line[0] || line[0] == '#')
+			continue;
+		mimetype = strtok_r(line, " \t\r\n", &saveptr);
+		while ((token = strtok_r(NULL, " \t\r\n", &saveptr))) {
+			if (!strcasecmp(ext, token)) {
+				fclose(file);
+				return xstrdup(mimetype);
+			}
+		}
+	}
+	fclose(file);
+	return NULL;
+}
diff --git a/www/git.causal.agency/cgit/tests/.gitignore b/www/git.causal.agency/cgit/tests/.gitignore
new file mode 100644
index 00000000..3fd2e965
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/.gitignore
@@ -0,0 +1,2 @@
+trash\ directory.t*
+test-results
diff --git a/www/git.causal.agency/cgit/tests/Makefile b/www/git.causal.agency/cgit/tests/Makefile
new file mode 100644
index 00000000..65e11173
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/Makefile
@@ -0,0 +1,17 @@
+include ../git/config.mak.uname
+-include ../cgit.conf
+
+SHELL_PATH ?= $(SHELL)
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+
+T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
+
+all: $(T)
+
+$(T):
+	@'$(SHELL_PATH_SQ)' $@ $(CGIT_TEST_OPTS)
+
+clean:
+	$(RM) -rf trash
+
+.PHONY: $(T) clean
diff --git a/www/git.causal.agency/cgit/tests/filters/dump.lua b/www/git.causal.agency/cgit/tests/filters/dump.lua
new file mode 100644
index 00000000..1f15c931
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/filters/dump.lua
@@ -0,0 +1,17 @@
+function filter_open(...)
+	buffer = ""
+	for i = 1, select("#", ...) do
+		buffer = buffer .. select(i, ...) .. " "
+	end
+end
+
+function filter_close()
+	html(buffer)
+	return 0
+end
+
+function filter_write(str)
+	buffer = buffer .. string.upper(str)
+end
+
+
diff --git a/www/git.causal.agency/cgit/tests/filters/dump.sh b/www/git.causal.agency/cgit/tests/filters/dump.sh
new file mode 100755
index 00000000..da6f7a1b
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/filters/dump.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+[ "$#" -gt 0 ] && printf "%s " "$*"
+tr '[:lower:]' '[:upper:]'
diff --git a/www/git.causal.agency/cgit/tests/setup.sh b/www/git.causal.agency/cgit/tests/setup.sh
new file mode 100755
index 00000000..5879348e
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/setup.sh
@@ -0,0 +1,176 @@
+# This file should be sourced by all test-scripts
+#
+# Main functions:
+#   prepare_tests(description) - setup for testing, i.e. create repos+config
+#   run_test(description, script) - run one test, i.e. eval script
+#
+# Helper functions
+#   cgit_query(querystring) - call cgit with the specified querystring
+#   cgit_url(url) - call cgit with the specified virtual url
+#
+# Example script:
+#
+# . setup.sh
+# prepare_tests "html validation"
+# run_test 'repo index' 'cgit_url "/" | tidy -e'
+# run_test 'repo summary' 'cgit_url "/foo" | tidy -e'
+
+# We don't want to run Git commands through Valgrind, so we filter out the
+# --valgrind option here and handle it ourselves.  We copy the arguments
+# assuming that none contain a newline, although other whitespace is
+# preserved.
+LF='
+'
+test_argv=
+
+while test $# != 0
+do
+	case "$1" in
+	--va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind)
+		cgit_valgrind=t
+		test_argv="$test_argv${LF}--verbose"
+		;;
+	*)
+		test_argv="$test_argv$LF$1"
+		;;
+	esac
+	shift
+done
+
+OLDIFS=$IFS
+IFS=$LF
+set -- $test_argv
+IFS=$OLDIFS
+
+: ${TEST_DIRECTORY=$(pwd)/../git/t}
+: ${TEST_OUTPUT_DIRECTORY=$(pwd)}
+TEST_NO_CREATE_REPO=YesPlease
+. "$TEST_DIRECTORY"/test-lib.sh
+
+# Prepend the directory containing cgit to PATH.
+if test -n "$cgit_valgrind"
+then
+	GIT_VALGRIND="$TEST_DIRECTORY/valgrind"
+	CGIT_VALGRIND=$(cd ../valgrind && pwd)
+	PATH="$CGIT_VALGRIND/bin:$PATH"
+	export GIT_VALGRIND CGIT_VALGRIND
+else
+	PATH="$(pwd)/../..:$PATH"
+fi
+
+FILTER_DIRECTORY=$(cd ../filters && pwd)
+
+if cgit --version | grep -F -q "[+] Lua scripting"; then
+	export CGIT_HAS_LUA=1
+else
+	export CGIT_HAS_LUA=0
+fi
+
+mkrepo() {
+	name=$1
+	count=$2
+	test_create_repo "$name"
+	(
+		cd "$name"
+		n=1
+		while test $n -le $count
+		do
+			echo $n >file-$n
+			git add file-$n
+			git commit -m "commit $n"
+			n=$(expr $n + 1)
+		done
+		if test "$3" = "testplus"
+		then
+			echo "hello" >a+b
+			git add a+b
+			git commit -m "add a+b"
+			git branch "1+2"
+		fi
+	)
+}
+
+setup_repos()
+{
+	rm -rf cache
+	mkdir -p cache
+	mkrepo repos/foo 5 >/dev/null
+	mkrepo repos/bar 50 >/dev/null
+	mkrepo repos/foo+bar 10 testplus >/dev/null
+	mkrepo "repos/with space" 2 >/dev/null
+	mkrepo repos/filter 5 testplus >/dev/null
+	cat >cgitrc <<EOF
+virtual-root=/
+cache-root=$PWD/cache
+
+cache-size=1021
+snapshots=tar.gz tar.bz tar.lz tar.xz tar.zst zip
+enable-log-filecount=1
+enable-log-linecount=1
+summary-log=5
+summary-branches=5
+summary-tags=5
+clone-url=git://example.org/\$CGIT_REPO_URL.git
+enable-filter-overrides=1
+
+repo.url=foo
+repo.path=$PWD/repos/foo/.git
+# Do not specify a description for this repo, as it then will be assigned
+# the constant value "[no description]" (which actually used to cause a
+# segfault).
+
+repo.url=bar
+repo.path=$PWD/repos/bar/.git
+repo.desc=the bar repo
+
+repo.url=foo+bar
+repo.path=$PWD/repos/foo+bar/.git
+repo.desc=the foo+bar repo
+
+repo.url=with space
+repo.path=$PWD/repos/with space/.git
+repo.desc=spaced repo
+
+repo.url=filter-exec
+repo.path=$PWD/repos/filter/.git
+repo.desc=filtered repo
+repo.about-filter=exec:$FILTER_DIRECTORY/dump.sh
+repo.commit-filter=exec:$FILTER_DIRECTORY/dump.sh
+repo.email-filter=exec:$FILTER_DIRECTORY/dump.sh
+repo.source-filter=exec:$FILTER_DIRECTORY/dump.sh
+repo.readme=master:a+b
+EOF
+
+	if [ $CGIT_HAS_LUA -eq 1 ]; then
+		cat >>cgitrc <<EOF
+repo.url=filter-lua
+repo.path=$PWD/repos/filter/.git
+repo.desc=filtered repo
+repo.about-filter=lua:$FILTER_DIRECTORY/dump.lua
+repo.commit-filter=lua:$FILTER_DIRECTORY/dump.lua
+repo.email-filter=lua:$FILTER_DIRECTORY/dump.lua
+repo.source-filter=lua:$FILTER_DIRECTORY/dump.lua
+repo.readme=master:a+b
+EOF
+	fi
+}
+
+cgit_query()
+{
+	CGIT_CONFIG="$PWD/cgitrc" QUERY_STRING="$1" cgit
+}
+
+cgit_url()
+{
+	CGIT_CONFIG="$PWD/cgitrc" QUERY_STRING="url=$1" cgit
+}
+
+strip_headers() {
+	while read -r line
+	do
+		test -z "$line" && break
+	done
+	cat
+}
+
+test -z "$CGIT_TEST_NO_CREATE_REPOS" && setup_repos
diff --git a/www/git.causal.agency/cgit/tests/t0001-validate-git-versions.sh b/www/git.causal.agency/cgit/tests/t0001-validate-git-versions.sh
new file mode 100755
index 00000000..73bd32f5
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/t0001-validate-git-versions.sh
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+if [ "${CGIT_TEST_NO_GIT_VERSION}" = "YesPlease" ]; then
+	exit 0
+fi
+
+test_description='Check Git version is correct'
+CGIT_TEST_NO_CREATE_REPOS=YesPlease
+. ./setup.sh
+
+test_expect_success 'extract Git version from Makefile' '
+	sed -n -e "/^GIT_VER[ 	]*=/ {
+		s/^GIT_VER[ 	]*=[ 	]*//
+		p
+	}" ../../Makefile >makefile_version
+'
+
+# Note that Git's GIT-VERSION-GEN script applies "s/-/./g" to the version
+# string to produce the internal version in the GIT-VERSION-FILE, so we
+# must apply the same transformation to the version in the Makefile before
+# comparing them.
+test_expect_success 'test Git version matches Makefile' '
+	( cat ../../git/GIT-VERSION-FILE || echo "No GIT-VERSION-FILE" ) |
+	sed -e "s/GIT_VERSION[ 	]*=[ 	]*//" -e "s/\\.dirty$//" >git_version &&
+	sed -e "s/-/./g" makefile_version >makefile_git_version &&
+	test_cmp git_version makefile_git_version
+'
+
+test_expect_success 'test submodule version matches Makefile' '
+	if ! test -e ../../git/.git
+	then
+		echo "git/ is not a Git repository" >&2
+	else
+		(
+			cd ../.. &&
+			sm_sha1=$(git ls-files --stage -- git |
+				sed -e "s/^[0-9]* \\([0-9a-f]*\\) [0-9]	.*$/\\1/") &&
+			cd git &&
+			git describe --match "v[0-9]*" $sm_sha1
+		) | sed -e "s/^v//" -e "s/-/./" >sm_version &&
+		test_cmp sm_version makefile_version
+	fi
+'
+
+test_done
diff --git a/www/git.causal.agency/cgit/tests/t0010-validate-html.sh b/www/git.causal.agency/cgit/tests/t0010-validate-html.sh
new file mode 100755
index 00000000..ca08d69d
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/t0010-validate-html.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+test_description='Validate html with tidy'
+. ./setup.sh
+
+
+test_url()
+{
+	tidy_opt="-eq"
+	test -z "$NO_TIDY_WARNINGS" || tidy_opt+=" --show-warnings no"
+	cgit_url "$1" >tidy-$test_count.tmp || return
+	sed -e "1,4d" tidy-$test_count.tmp >tidy-$test_count || return
+	"$tidy" $tidy_opt tidy-$test_count
+	rc=$?
+
+	# tidy returns with exitcode 1 on warnings, 2 on error
+	if test $rc = 2
+	then
+		false
+	else
+		:
+	fi
+}
+
+tidy=`which tidy 2>/dev/null`
+test -n "$tidy" || {
+	skip_all='Skipping html validation tests: tidy not found'
+	test_done
+	exit
+}
+
+test_expect_success 'index page' 'test_url ""'
+test_expect_success 'foo' 'test_url "foo"'
+test_expect_success 'foo/log' 'test_url "foo/log"'
+test_expect_success 'foo/tree' 'test_url "foo/tree"'
+test_expect_success 'foo/tree/file-1' 'test_url "foo/tree/file-1"'
+test_expect_success 'foo/commit' 'test_url "foo/commit"'
+test_expect_success 'foo/diff' 'test_url "foo/diff"'
+
+test_done
diff --git a/www/git.causal.agency/cgit/tests/t0020-validate-cache.sh b/www/git.causal.agency/cgit/tests/t0020-validate-cache.sh
new file mode 100755
index 00000000..657765d8
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/t0020-validate-cache.sh
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+test_description='Validate cache'
+. ./setup.sh
+
+test_expect_success 'verify cache-size=0' '
+
+	rm -f cache/* &&
+	sed -e "s/cache-size=1021$/cache-size=0/" cgitrc >cgitrc.tmp &&
+	mv -f cgitrc.tmp cgitrc &&
+	cgit_url "" &&
+	cgit_url "foo" &&
+	cgit_url "foo/refs" &&
+	cgit_url "foo/tree" &&
+	cgit_url "foo/log" &&
+	cgit_url "foo/diff" &&
+	cgit_url "foo/patch" &&
+	cgit_url "bar" &&
+	cgit_url "bar/refs" &&
+	cgit_url "bar/tree" &&
+	cgit_url "bar/log" &&
+	cgit_url "bar/diff" &&
+	cgit_url "bar/patch" &&
+	ls cache >output &&
+	test_line_count = 0 output
+'
+
+test_expect_success 'verify cache-size=1' '
+
+	rm -f cache/* &&
+	sed -e "s/cache-size=0$/cache-size=1/" cgitrc >cgitrc.tmp &&
+	mv -f cgitrc.tmp cgitrc &&
+	cgit_url "" &&
+	cgit_url "foo" &&
+	cgit_url "foo/refs" &&
+	cgit_url "foo/tree" &&
+	cgit_url "foo/log" &&
+	cgit_url "foo/diff" &&
+	cgit_url "foo/patch" &&
+	cgit_url "bar" &&
+	cgit_url "bar/refs" &&
+	cgit_url "bar/tree" &&
+	cgit_url "bar/log" &&
+	cgit_url "bar/diff" &&
+	cgit_url "bar/patch" &&
+	ls cache >output &&
+	test_line_count = 1 output
+'
+
+test_expect_success 'verify cache-size=1021' '
+
+	rm -f cache/* &&
+	sed -e "s/cache-size=1$/cache-size=1021/" cgitrc >cgitrc.tmp &&
+	mv -f cgitrc.tmp cgitrc &&
+	cgit_url "" &&
+	cgit_url "foo" &&
+	cgit_url "foo/refs" &&
+	cgit_url "foo/tree" &&
+	cgit_url "foo/log" &&
+	cgit_url "foo/diff" &&
+	cgit_url "foo/patch" &&
+	cgit_url "bar" &&
+	cgit_url "bar/refs" &&
+	cgit_url "bar/tree" &&
+	cgit_url "bar/log" &&
+	cgit_url "bar/diff" &&
+	cgit_url "bar/patch" &&
+	ls cache >output &&
+	test_line_count = 13 output &&
+	cgit_url "foo/ls_cache" >output.full &&
+	strip_headers <output.full >output &&
+	test_line_count = 13 output &&
+	# Check that ls_cache output is cached correctly
+	cgit_url "foo/ls_cache" >output.second &&
+	test_cmp output.full output.second
+'
+
+test_done
diff --git a/www/git.causal.agency/cgit/tests/t0101-index.sh b/www/git.causal.agency/cgit/tests/t0101-index.sh
new file mode 100755
index 00000000..82ef9b04
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/t0101-index.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+test_description='Check content on index page'
+. ./setup.sh
+
+test_expect_success 'generate index page' 'cgit_url "" >tmp'
+test_expect_success 'find foo repo' 'grep "foo" tmp'
+test_expect_success 'find foo description' 'grep "\[no description\]" tmp'
+test_expect_success 'find bar repo' 'grep "bar" tmp'
+test_expect_success 'find bar description' 'grep "the bar repo" tmp'
+test_expect_success 'find foo+bar repo' 'grep ">foo+bar<" tmp'
+test_expect_success 'verify foo+bar link' 'grep "/foo+bar/" tmp'
+test_expect_success 'verify "with%20space" link' 'grep "/with%20space/" tmp'
+test_expect_success 'no tree-link' '! grep "foo/tree" tmp'
+test_expect_success 'no log-link' '! grep "foo/log" tmp'
+
+test_done
diff --git a/www/git.causal.agency/cgit/tests/t0102-summary.sh b/www/git.causal.agency/cgit/tests/t0102-summary.sh
new file mode 100755
index 00000000..b8864cb1
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/t0102-summary.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+
+test_description='Check content on summary page'
+. ./setup.sh
+
+test_expect_success 'generate foo summary' 'cgit_url "foo" >tmp'
+test_expect_success 'find commit 1' 'grep "commit 1" tmp'
+test_expect_success 'find commit 5' 'grep "commit 5" tmp'
+test_expect_success 'find branch master' 'grep "master" tmp'
+test_expect_success 'no tags' '! grep "tags" tmp'
+test_expect_success 'clone-url expanded correctly' '
+	grep "git://example.org/foo.git" tmp
+'
+
+test_expect_success 'generate bar summary' 'cgit_url "bar" >tmp'
+test_expect_success 'no commit 45' '! grep "commit 45" tmp'
+test_expect_success 'find commit 46' 'grep "commit 46" tmp'
+test_expect_success 'find commit 50' 'grep "commit 50" tmp'
+test_expect_success 'find branch master' 'grep "master" tmp'
+test_expect_success 'no tags' '! grep "tags" tmp'
+test_expect_success 'clone-url expanded correctly' '
+	grep "git://example.org/bar.git" tmp
+'
+
+test_done
diff --git a/www/git.causal.agency/cgit/tests/t0103-log.sh b/www/git.causal.agency/cgit/tests/t0103-log.sh
new file mode 100755
index 00000000..bdf1435a
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/t0103-log.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+test_description='Check content on log page'
+. ./setup.sh
+
+test_expect_success 'generate foo/log' 'cgit_url "foo/log" >tmp'
+test_expect_success 'find commit 1' 'grep "commit 1" tmp'
+test_expect_success 'find commit 5' 'grep "commit 5" tmp'
+
+test_expect_success 'generate bar/log' 'cgit_url "bar/log" >tmp'
+test_expect_success 'find commit 1' 'grep "commit 1" tmp'
+test_expect_success 'find commit 50' 'grep "commit 50" tmp'
+
+test_expect_success 'generate "with%20space/log?qt=grep&q=commit+1"' '
+	cgit_url "with+space/log&qt=grep&q=commit+1" >tmp
+'
+test_expect_success 'find commit 1' 'grep "commit 1" tmp'
+test_expect_success 'find link with %20 in path' 'grep "/with%20space/log/?qt=grep" tmp'
+test_expect_success 'find link with + in arg' 'grep "/log/?qt=grep&amp;q=commit+1" tmp'
+test_expect_success 'no links with space in path' '! grep "href=./with space/" tmp'
+test_expect_success 'no links with space in arg' '! grep "q=commit 1" tmp'
+test_expect_success 'commit 2 is not visible' '! grep "commit 2" tmp'
+
+test_done
diff --git a/www/git.causal.agency/cgit/tests/t0104-tree.sh b/www/git.causal.agency/cgit/tests/t0104-tree.sh
new file mode 100755
index 00000000..2e140f59
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/t0104-tree.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test_description='Check content on tree page'
+. ./setup.sh
+
+test_expect_success 'generate bar/tree' 'cgit_url "bar/tree" >tmp'
+test_expect_success 'find file-1' 'grep "file-1" tmp'
+test_expect_success 'find file-50' 'grep "file-50" tmp'
+
+test_expect_success 'generate bar/tree/file-50' 'cgit_url "bar/tree/file-50" >tmp'
+
+test_expect_success 'find line 1' '
+	grep "<a id=.n1. href=.#n1.>1</a>" tmp
+'
+
+test_expect_success 'no line 2' '
+	! grep "<a id=.n2. href=.#n2.>2</a>" tmp
+'
+
+test_expect_success 'generate foo+bar/tree' 'cgit_url "foo%2bbar/tree" >tmp'
+
+test_expect_success 'verify a+b link' '
+	grep "/foo+bar/tree/a+b" tmp
+'
+
+test_expect_success 'generate foo+bar/tree?h=1+2' 'cgit_url "foo%2bbar/tree&h=1%2b2" >tmp'
+
+test_expect_success 'verify a+b?h=1+2 link' '
+	grep "/foo+bar/tree/a+b?h=1%2b2" tmp
+'
+
+test_done
diff --git a/www/git.causal.agency/cgit/tests/t0105-commit.sh b/www/git.causal.agency/cgit/tests/t0105-commit.sh
new file mode 100755
index 00000000..9cdf55c0
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/t0105-commit.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+test_description='Check content on commit page'
+. ./setup.sh
+
+test_expect_success 'generate foo/commit' 'cgit_url "foo/commit" >tmp'
+test_expect_success 'find tree link' 'grep "<a href=./foo/tree/.>" tmp'
+test_expect_success 'find parent link' 'grep -E "<a href=./foo/commit/\?id=.+>" tmp'
+
+test_expect_success 'find commit subject' '
+	grep "<div class=.commit-subject.>commit 5<" tmp
+'
+
+test_expect_success 'find commit msg' 'grep "<div class=.commit-msg.></div>" tmp'
+test_expect_success 'find diffstat' 'grep "<table summary=.diffstat. class=.diffstat.>" tmp'
+
+test_expect_success 'find diff summary' '
+	grep "1 files changed, 1 insertions, 0 deletions" tmp
+'
+
+test_expect_success 'get root commit' '
+	root=$(cd repos/foo && git rev-list --reverse HEAD | head -1) &&
+	cgit_url "foo/commit&id=$root" >tmp &&
+	grep "</html>" tmp
+'
+
+test_expect_success 'root commit contains diffstat' '
+	grep "<a href=./foo/diff/file-1.id=[0-9a-f]\{40\}.>file-1</a>" tmp
+'
+
+test_expect_success 'root commit contains diff' '
+	grep ">diff --git a/file-1 b/file-1<" tmp &&
+	grep "<div class=.add.>+1</div>" tmp
+'
+
+test_done
diff --git a/www/git.causal.agency/cgit/tests/t0106-diff.sh b/www/git.causal.agency/cgit/tests/t0106-diff.sh
new file mode 100755
index 00000000..82b645ec
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/t0106-diff.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+test_description='Check content on diff page'
+. ./setup.sh
+
+test_expect_success 'generate foo/diff' 'cgit_url "foo/diff" >tmp'
+test_expect_success 'find diff header' 'grep "a/file-5 b/file-5" tmp'
+test_expect_success 'find blob link' 'grep "<a href=./foo/tree/file-5?id=" tmp'
+test_expect_success 'find added file' 'grep "new file mode 100644" tmp'
+
+test_expect_success 'find hunk header' '
+	grep "<div class=.hunk.>@@ -0,0 +1 @@</div>" tmp
+'
+
+test_expect_success 'find added line' '
+	grep "<div class=.add.>+5</div>" tmp
+'
+
+test_done
diff --git a/www/git.causal.agency/cgit/tests/t0107-snapshot.sh b/www/git.causal.agency/cgit/tests/t0107-snapshot.sh
new file mode 100755
index 00000000..c164d3e2
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/t0107-snapshot.sh
@@ -0,0 +1,208 @@
+#!/bin/sh
+
+test_description='Verify snapshot'
+. ./setup.sh
+
+test_expect_success 'get foo/snapshot/master.tar.gz' '
+	cgit_url "foo/snapshot/master.tar.gz" >tmp
+'
+
+test_expect_success 'check html headers' '
+	head -n 1 tmp |
+	grep "Content-Type: application/x-gzip" &&
+
+	head -n 2 tmp |
+	grep "Content-Disposition: inline; filename=.master.tar.gz."
+'
+
+test_expect_success 'strip off the header lines' '
+	strip_headers <tmp >master.tar.gz
+'
+
+test_expect_success 'verify gzip format' '
+	gunzip --test master.tar.gz
+'
+
+test_expect_success 'untar' '
+	rm -rf master &&
+	tar -xzf master.tar.gz
+'
+
+test_expect_success 'count files' '
+	ls master/ >output &&
+	test_line_count = 5 output
+'
+
+test_expect_success 'verify untarred file-5' '
+	grep "^5$" master/file-5 &&
+	test_line_count = 1 master/file-5
+'
+
+if test -n "$(which lzip 2>/dev/null)"; then
+	test_set_prereq LZIP
+else
+	say 'Skipping LZIP validation tests: lzip not found'
+fi
+
+test_expect_success LZIP 'get foo/snapshot/master.tar.lz' '
+	cgit_url "foo/snapshot/master.tar.lz" >tmp
+'
+
+test_expect_success LZIP 'check html headers' '
+	head -n 1 tmp |
+	grep "Content-Type: application/x-lzip" &&
+
+	head -n 2 tmp |
+	grep "Content-Disposition: inline; filename=.master.tar.lz."
+'
+
+test_expect_success LZIP 'strip off the header lines' '
+	strip_headers <tmp >master.tar.lz
+'
+
+test_expect_success LZIP 'verify lzip format' '
+	lzip --test master.tar.lz &&
+	cp master.tar.lz /tmp/.
+'
+
+test_expect_success LZIP 'untar' '
+	rm -rf master &&
+	tar --lzip -xf master.tar.lz
+'
+
+test_expect_success LZIP 'count files' '
+	ls master/ >output &&
+	test_line_count = 5 output
+'
+
+test_expect_success LZIP 'verify untarred file-5' '
+	grep "^5$" master/file-5 &&
+	test_line_count = 1 master/file-5
+'
+
+if test -n "$(which xz 2>/dev/null)"; then
+	test_set_prereq XZ
+else
+	say 'Skipping XZ validation tests: xz not found'
+fi
+
+test_expect_success XZ 'get foo/snapshot/master.tar.xz' '
+	cgit_url "foo/snapshot/master.tar.xz" >tmp
+'
+
+test_expect_success XZ 'check html headers' '
+	head -n 1 tmp |
+	grep "Content-Type: application/x-xz" &&
+
+	head -n 2 tmp |
+	grep "Content-Disposition: inline; filename=.master.tar.xz."
+'
+
+test_expect_success XZ 'strip off the header lines' '
+	strip_headers <tmp >master.tar.xz
+'
+
+test_expect_success XZ 'verify xz format' '
+	xz --test master.tar.xz &&
+	cp master.tar.xz /tmp/.
+'
+
+test_expect_success XZ 'untar' '
+	rm -rf master &&
+	tar --xz -xf master.tar.xz
+'
+
+test_expect_success XZ 'count files' '
+	ls master/ >output &&
+	test_line_count = 5 output
+'
+
+test_expect_success XZ 'verify untarred file-5' '
+	grep "^5$" master/file-5 &&
+	test_line_count = 1 master/file-5
+'
+
+if test -n "$(which zstd 2>/dev/null)"; then
+	test_set_prereq ZSTD
+else
+	say 'Skipping ZSTD validation tests: zstd not found'
+fi
+
+test_expect_success ZSTD 'get foo/snapshot/master.tar.zst' '
+	cgit_url "foo/snapshot/master.tar.zst" >tmp
+'
+
+test_expect_success ZSTD 'check html headers' '
+	head -n 1 tmp |
+	grep "Content-Type: application/x-zstd" &&
+
+	head -n 2 tmp |
+	grep "Content-Disposition: inline; filename=.master.tar.zst."
+'
+
+test_expect_success ZSTD 'strip off the header lines' '
+	strip_headers <tmp >master.tar.zst
+'
+
+test_expect_success ZSTD 'verify zstd format' '
+	zstd --test master.tar.zst &&
+	cp master.tar.zst /tmp/.
+'
+
+test_expect_success ZSTD 'untar' '
+	rm -rf master &&
+	tar --zstd -xf master.tar.zst
+'
+
+test_expect_success ZSTD 'count files' '
+	ls master/ >output &&
+	test_line_count = 5 output
+'
+
+test_expect_success ZSTD 'verify untarred file-5' '
+	grep "^5$" master/file-5 &&
+	test_line_count = 1 master/file-5
+'
+
+test_expect_success 'get foo/snapshot/master.zip' '
+	cgit_url "foo/snapshot/master.zip" >tmp
+'
+
+test_expect_success 'check HTML headers (zip)' '
+	head -n 1 tmp |
+	grep "Content-Type: application/x-zip" &&
+
+	head -n 2 tmp |
+	grep "Content-Disposition: inline; filename=.master.zip."
+'
+
+test_expect_success 'strip off the header lines (zip)' '
+	strip_headers <tmp >master.zip
+'
+
+if test -n "$(which unzip 2>/dev/null)"; then
+	test_set_prereq UNZIP
+else
+	say 'Skipping ZIP validation tests: unzip not found'
+fi
+
+test_expect_success UNZIP 'verify zip format' '
+	unzip -t master.zip
+'
+
+test_expect_success UNZIP 'unzip' '
+	rm -rf master &&
+	unzip master.zip
+'
+
+test_expect_success UNZIP 'count files (zip)' '
+	ls master/ >output &&
+	test_line_count = 5 output
+'
+
+test_expect_success UNZIP 'verify unzipped file-5' '
+	grep "^5$" master/file-5 &&
+	test_line_count = 1 master/file-5
+'
+
+test_done
diff --git a/www/git.causal.agency/cgit/tests/t0108-patch.sh b/www/git.causal.agency/cgit/tests/t0108-patch.sh
new file mode 100755
index 00000000..013d6802
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/t0108-patch.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+test_description='Check content on patch page'
+. ./setup.sh
+
+test_expect_success 'generate foo/patch' '
+	cgit_query "url=foo/patch" >tmp
+'
+
+test_expect_success 'find `From:` line' '
+	grep "^From: " tmp
+'
+
+test_expect_success 'find `Date:` line' '
+	grep "^Date: " tmp
+'
+
+test_expect_success 'find `Subject:` line' '
+	grep "^Subject: commit 5" tmp
+'
+
+test_expect_success 'find `cgit` signature' '
+	tail -2 tmp | head -1 | grep "^cgit"
+'
+
+test_expect_success 'compare with output of git-format-patch(1)' '
+	CGIT_VERSION=$(sed -n "s/CGIT_VERSION = //p" ../../VERSION) &&
+	git --git-dir="$PWD/repos/foo/.git" format-patch --subject-prefix="" --signature="cgit $CGIT_VERSION" --stdout HEAD^ >tmp2 &&
+	strip_headers <tmp >tmp_ &&
+	test_cmp tmp_ tmp2
+'
+
+test_expect_success 'find initial commit' '
+	root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD)
+'
+
+test_expect_success 'generate patch for initial commit' '
+	cgit_query "url=foo/patch&id=$root" >tmp
+'
+
+test_expect_success 'find `cgit` signature' '
+	tail -2 tmp | head -1 | grep "^cgit"
+'
+
+test_expect_success 'generate patches for multiple commits' '
+	id=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD) &&
+	id2=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD~3) &&
+	cgit_query "url=foo/patch&id=$id&id2=$id2" >tmp
+'
+
+test_expect_success 'find `cgit` signature' '
+	tail -2 tmp | head -1 | grep "^cgit"
+'
+
+test_expect_success 'compare with output of git-format-patch(1)' '
+	CGIT_VERSION=$(sed -n "s/CGIT_VERSION = //p" ../../VERSION) &&
+	git --git-dir="$PWD/repos/foo/.git" format-patch -N --subject-prefix="" --signature="cgit $CGIT_VERSION" --stdout HEAD~3..HEAD >tmp2 &&
+	strip_headers <tmp >tmp_ &&
+	test_cmp tmp_ tmp2
+'
+
+test_done
diff --git a/www/git.causal.agency/cgit/tests/t0109-gitconfig.sh b/www/git.causal.agency/cgit/tests/t0109-gitconfig.sh
new file mode 100755
index 00000000..8cee75cd
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/t0109-gitconfig.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+test_description='Ensure that git does not access $HOME'
+. ./setup.sh
+
+test -n "$(which strace 2>/dev/null)" || {
+	skip_all='Skipping access validation tests: strace not found'
+	test_done
+	exit
+}
+
+strace true 2>/dev/null || {
+	skip_all='Skipping access validation tests: strace not functional'
+	test_done
+	exit
+}
+
+test_no_home_access () {
+	non_existent_path="/path/to/some/place/that/does/not/possibly/exist"
+	while test -d "$non_existent_path"; do
+		non_existent_path="$non_existent_path/$(date +%N)"
+	done &&
+	strace \
+		-E HOME="$non_existent_path" \
+		-E CGIT_CONFIG="$PWD/cgitrc" \
+		-E QUERY_STRING="url=$1" \
+		-e access -f -o strace.out cgit &&
+	test_must_fail grep "$non_existent_path" strace.out
+}
+
+test_no_home_access_success() {
+	test_expect_success "do not access \$HOME: $1" "
+		test_no_home_access '$1'
+	"
+}
+
+test_no_home_access_success
+test_no_home_access_success foo
+test_no_home_access_success foo/refs
+test_no_home_access_success foo/log
+test_no_home_access_success foo/tree
+test_no_home_access_success foo/tree/file-1
+test_no_home_access_success foo/commit
+test_no_home_access_success foo/diff
+test_no_home_access_success foo/patch
+test_no_home_access_success foo/snapshot/master.tar.gz
+
+test_done
diff --git a/www/git.causal.agency/cgit/tests/t0110-rawdiff.sh b/www/git.causal.agency/cgit/tests/t0110-rawdiff.sh
new file mode 100755
index 00000000..66fa7d5d
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/t0110-rawdiff.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='Check content on rawdiff page'
+. ./setup.sh
+
+test_expect_success 'generate foo/rawdiff' '
+	cgit_query "url=foo/rawdiff" >tmp
+'
+
+test_expect_success 'compare with output of git-diff(1)' '
+	git --git-dir="$PWD/repos/foo/.git" diff HEAD^.. >tmp2 &&
+	sed "1,4d" tmp >tmp_ &&
+	cmp tmp_ tmp2
+'
+
+test_expect_success 'find initial commit' '
+	root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD)
+'
+
+test_expect_success 'generate diff for initial commit' '
+	cgit_query "url=foo/rawdiff&id=$root" >tmp
+'
+
+test_expect_success 'compare with output of git-diff-tree(1)' '
+	git --git-dir="$PWD/repos/foo/.git" diff-tree -p --no-commit-id --root "$root" >tmp2 &&
+	sed "1,4d" tmp >tmp_ &&
+	cmp tmp_ tmp2
+'
+
+test_expect_success 'generate diff for multiple commits' '
+	id=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD) &&
+	id2=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD~3) &&
+	cgit_query "url=foo/rawdiff&id=$id&id2=$id2" >tmp
+'
+
+test_expect_success 'compare with output of git-diff(1)' '
+	git --git-dir="$PWD/repos/foo/.git" diff HEAD~3..HEAD >tmp2 &&
+	sed "1,4d" tmp >tmp_ &&
+	cmp tmp_ tmp2
+'
+
+test_done
diff --git a/www/git.causal.agency/cgit/tests/t0111-filter.sh b/www/git.causal.agency/cgit/tests/t0111-filter.sh
new file mode 100755
index 00000000..2fdc3669
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/t0111-filter.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+test_description='Check filtered content'
+. ./setup.sh
+
+prefixes="exec"
+if [ $CGIT_HAS_LUA -eq 1 ]; then
+	prefixes="$prefixes lua"
+fi
+
+for prefix in $prefixes
+do
+	test_expect_success "generate filter-$prefix/tree/a%2bb" "
+		cgit_url 'filter-$prefix/tree/a%2bb' >tmp
+	"
+
+	test_expect_success "check whether the $prefix source filter works" '
+		grep "<code>a+b HELLO$" tmp
+	'
+
+	test_expect_success "generate filter-$prefix/about/" "
+		cgit_url 'filter-$prefix/about/' >tmp
+	"
+
+	test_expect_success "check whether the $prefix about filter works" '
+		grep "<div id='"'"'summary'"'"'>a+b HELLO$" tmp
+	'
+
+	test_expect_success "generate filter-$prefix/commit/" "
+		cgit_url 'filter-$prefix/commit/' >tmp
+	"
+
+	test_expect_success "check whether the $prefix commit filter works" '
+		grep "<div class='"'"'commit-subject'"'"'>ADD A+B" tmp
+	'
+
+	test_expect_success "check whether the $prefix email filter works for authors" '
+		grep "<author@example.com> commit A U THOR &LT;AUTHOR@EXAMPLE.COM&GT;" tmp
+	'
+
+	test_expect_success "check whether the $prefix email filter works for committers" '
+		grep "<committer@example.com> commit C O MITTER &LT;COMMITTER@EXAMPLE.COM&GT;" tmp
+	'
+done
+
+test_done
diff --git a/www/git.causal.agency/cgit/tests/valgrind/bin/cgit b/www/git.causal.agency/cgit/tests/valgrind/bin/cgit
new file mode 100755
index 00000000..dcdfbe53
--- /dev/null
+++ b/www/git.causal.agency/cgit/tests/valgrind/bin/cgit
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# Note that we currently use Git's suppression file and there are variables
+# $GIT_VALGRIND and $CGIT_VALGRIND which point to different places.
+exec valgrind -q --error-exitcode=126 \
+	--suppressions="$GIT_VALGRIND/default.supp" \
+	--gen-suppressions=all \
+	--leak-check=no \
+	--track-origins=yes \
+	--log-fd=4 \
+	--input-fd=4 \
+	"$CGIT_VALGRIND/../../cgit" "$@"
diff --git a/www/git.causal.agency/cgit/ui-atom.c b/www/git.causal.agency/cgit/ui-atom.c
new file mode 100644
index 00000000..1056f363
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-atom.c
@@ -0,0 +1,149 @@
+/* ui-atom.c: functions for atom feeds
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-atom.h"
+#include "html.h"
+#include "ui-shared.h"
+
+static void add_entry(struct commit *commit, const char *host)
+{
+	char delim = '&';
+	char *hex;
+	char *mail, *t, *t2;
+	struct commitinfo *info;
+
+	info = cgit_parse_commit(commit);
+	hex = oid_to_hex(&commit->object.oid);
+	html("<entry>\n");
+	html("<title>");
+	html_txt(info->subject);
+	html("</title>\n");
+	html("<updated>");
+	html_txt(show_date(info->committer_date, 0,
+                    date_mode_from_type(DATE_ISO8601_STRICT)));
+	html("</updated>\n");
+	html("<author>\n");
+	if (info->author) {
+		html("<name>");
+		html_txt(info->author);
+		html("</name>\n");
+	}
+	if (info->author_email && !ctx.cfg.noplainemail) {
+		mail = xstrdup(info->author_email);
+		t = strchr(mail, '<');
+		if (t)
+			t++;
+		else
+			t = mail;
+		t2 = strchr(t, '>');
+		if (t2)
+			*t2 = '\0';
+		html("<email>");
+		html_txt(t);
+		html("</email>\n");
+		free(mail);
+	}
+	html("</author>\n");
+	html("<published>");
+	html_txt(show_date(info->author_date, 0,
+                    date_mode_from_type(DATE_ISO8601_STRICT)));
+	html("</published>\n");
+	if (host) {
+		char *pageurl;
+		html("<link rel='alternate' type='text/html' href='");
+		html(cgit_httpscheme());
+		html_attr(host);
+		pageurl = cgit_pageurl(ctx.repo->url, "commit", NULL);
+		html_attr(pageurl);
+		if (ctx.cfg.virtual_root)
+			delim = '?';
+		html_attrf("%cid=%s", delim, hex);
+		html("'/>\n");
+		free(pageurl);
+	}
+	htmlf("<id>%s</id>\n", hex);
+	html("<content type='text'>\n");
+	html_txt(info->msg);
+	html("</content>\n");
+	html("<content type='xhtml'>\n");
+	html("<div xmlns='http://www.w3.org/1999/xhtml'>\n");
+	html("<pre>\n");
+	html_txt(info->msg);
+	html("</pre>\n");
+	html("</div>\n");
+	html("</content>\n");
+	html("</entry>\n");
+	cgit_free_commitinfo(info);
+}
+
+
+void cgit_print_atom(char *tip, const char *path, int max_count)
+{
+	char *host;
+	const char *argv[] = {NULL, tip, NULL, NULL, NULL};
+	struct commit *commit;
+	struct rev_info rev;
+	int argc = 2;
+
+	if (ctx.qry.show_all)
+		argv[1] = "--all";
+	else if (!tip)
+		argv[1] = ctx.qry.head;
+
+	if (path) {
+		argv[argc++] = "--";
+		argv[argc++] = path;
+	}
+
+	init_revisions(&rev, NULL);
+	rev.abbrev = DEFAULT_ABBREV;
+	rev.commit_format = CMIT_FMT_DEFAULT;
+	rev.verbose_header = 1;
+	rev.show_root_diff = 0;
+	rev.max_count = max_count;
+	setup_revisions(argc, argv, &rev, NULL);
+	prepare_revision_walk(&rev);
+
+	host = cgit_hosturl();
+	ctx.page.mimetype = "text/xml";
+	ctx.page.charset = "utf-8";
+	cgit_print_http_headers();
+	html("<feed xmlns='http://www.w3.org/2005/Atom'>\n");
+	html("<title>");
+	html_txt(ctx.repo->name);
+	if (path) {
+		html("/");
+		html_txt(path);
+	}
+	if (tip && !ctx.qry.show_all) {
+		html(", branch ");
+		html_txt(tip);
+	}
+	html("</title>\n");
+	html("<subtitle>");
+	html_txt(ctx.repo->desc);
+	html("</subtitle>\n");
+	if (host) {
+		char *repourl = cgit_repourl(ctx.repo->url);
+		html("<link rel='alternate' type='text/html' href='");
+		html(cgit_httpscheme());
+		html_attr(host);
+		html_attr(repourl);
+		html("'/>\n");
+		free(repourl);
+	}
+	while ((commit = get_revision(&rev)) != NULL) {
+		add_entry(commit, host);
+		free_commit_buffer(the_repository->parsed_objects, commit);
+		free_commit_list(commit->parents);
+		commit->parents = NULL;
+	}
+	html("</feed>\n");
+	free(host);
+}
diff --git a/www/git.causal.agency/cgit/ui-atom.h b/www/git.causal.agency/cgit/ui-atom.h
new file mode 100644
index 00000000..dda953bb
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-atom.h
@@ -0,0 +1,6 @@
+#ifndef UI_ATOM_H
+#define UI_ATOM_H
+
+extern void cgit_print_atom(char *tip, const char *path, int max_count);
+
+#endif
diff --git a/www/git.causal.agency/cgit/ui-blame.c b/www/git.causal.agency/cgit/ui-blame.c
new file mode 100644
index 00000000..08832cd0
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-blame.c
@@ -0,0 +1,308 @@
+/* ui-blame.c: functions for blame output
+ *
+ * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-blame.h"
+#include "html.h"
+#include "ui-shared.h"
+#include "argv-array.h"
+#include "blame.h"
+
+
+static char *emit_suspect_detail(struct blame_origin *suspect)
+{
+	struct commitinfo *info;
+	struct strbuf detail = STRBUF_INIT;
+
+	info = cgit_parse_commit(suspect->commit);
+
+	strbuf_addf(&detail, "author  %s", info->author);
+	if (!ctx.cfg.noplainemail)
+		strbuf_addf(&detail, " %s", info->author_email);
+	strbuf_addf(&detail, "  %s\n",
+		    show_date(info->author_date, info->author_tz,
+				    cgit_date_mode(DATE_ISO8601)));
+
+	strbuf_addf(&detail, "committer  %s", info->committer);
+	if (!ctx.cfg.noplainemail)
+		strbuf_addf(&detail, " %s", info->committer_email);
+	strbuf_addf(&detail, "  %s\n\n",
+		    show_date(info->committer_date, info->committer_tz,
+				    cgit_date_mode(DATE_ISO8601)));
+
+	strbuf_addstr(&detail, info->subject);
+
+	cgit_free_commitinfo(info);
+	return strbuf_detach(&detail, NULL);
+}
+
+static void emit_blame_entry_hash(struct blame_entry *ent)
+{
+	struct blame_origin *suspect = ent->suspect;
+	struct object_id *oid = &suspect->commit->object.oid;
+	unsigned long line = 0;
+
+	char *detail = emit_suspect_detail(suspect);
+	html("<span class='sha1'>");
+	cgit_commit_link(find_unique_abbrev(oid, DEFAULT_ABBREV), detail,
+			 NULL, ctx.qry.head, oid_to_hex(oid), suspect->path);
+	html("</span>");
+	free(detail);
+
+	while (line++ < ent->num_lines)
+		html("\n");
+}
+
+static void emit_blame_entry_linenumber(struct blame_entry *ent)
+{
+	const char *numberfmt = "<a id='n%1$d' href='#n%1$d'>%1$d</a>\n";
+
+	unsigned long lineno = ent->lno;
+	while (lineno < ent->lno + ent->num_lines)
+		htmlf(numberfmt, ++lineno);
+}
+
+static void emit_blame_entry_line_background(struct blame_scoreboard *sb,
+					     struct blame_entry *ent)
+{
+	unsigned long line;
+	size_t len, maxlen = 2;
+	const char* pos, *endpos;
+
+	for (line = ent->lno; line < ent->lno + ent->num_lines; line++) {
+		html("\n");
+		pos = blame_nth_line(sb, line);
+		endpos = blame_nth_line(sb, line + 1);
+		len = 0;
+		while (pos < endpos) {
+			len++;
+			if (*pos++ == '\t')
+				len = (len + 7) & ~7;
+		}
+		if (len > maxlen)
+			maxlen = len;
+	}
+
+	for (len = 0; len < maxlen - 1; len++)
+		html(" ");
+}
+
+struct walk_tree_context {
+	char *curr_rev;
+	int match_baselen;
+	int state;
+};
+
+static void print_object(const struct object_id *oid, const char *path,
+			 const char *basename, const char *rev)
+{
+	enum object_type type;
+	char *buf;
+	unsigned long size;
+	struct argv_array rev_argv = ARGV_ARRAY_INIT;
+	struct rev_info revs;
+	struct blame_scoreboard sb;
+	struct blame_origin *o;
+	struct blame_entry *ent = NULL;
+
+	type = oid_object_info(the_repository, oid, &size);
+	if (type == OBJ_BAD) {
+		cgit_print_error_page(404, "Not found", "Bad object name: %s",
+				      oid_to_hex(oid));
+		return;
+	}
+
+	buf = read_object_file(oid, &type, &size);
+	if (!buf) {
+		cgit_print_error_page(500, "Internal server error",
+			"Error reading object %s", oid_to_hex(oid));
+		return;
+	}
+
+	argv_array_push(&rev_argv, "blame");
+	argv_array_push(&rev_argv, rev);
+	init_revisions(&revs, NULL);
+	revs.diffopt.flags.allow_textconv = 1;
+	setup_revisions(rev_argv.argc, rev_argv.argv, &revs, NULL);
+	init_scoreboard(&sb);
+	sb.revs = &revs;
+	sb.repo = the_repository;
+	setup_scoreboard(&sb, path, &o);
+	o->suspects = blame_entry_prepend(NULL, 0, sb.num_lines, o);
+	prio_queue_put(&sb.commits, o->commit);
+	blame_origin_decref(o);
+	sb.ent = NULL;
+	sb.path = path;
+	assign_blame(&sb, 0);
+	blame_sort_final(&sb);
+	blame_coalesce(&sb);
+
+	cgit_set_title_from_path(path);
+
+	cgit_print_layout_start();
+	htmlf("blob: %s (", oid_to_hex(oid));
+	cgit_plain_link("plain", NULL, NULL, ctx.qry.head, rev, path);
+	html(") (");
+	cgit_tree_link("tree", NULL, NULL, ctx.qry.head, rev, path);
+	html(")\n");
+
+	if (buffer_is_binary(buf, size)) {
+		html("<div class='error'>blob is binary.</div>");
+		goto cleanup;
+	}
+	if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
+		htmlf("<div class='error'>blob size (%ldKB)"
+		      " exceeds display size limit (%dKB).</div>",
+		      size / 1024, ctx.cfg.max_blob_size);
+		goto cleanup;
+	}
+
+	html("<table class='blame blob'>\n<tr>\n");
+
+	/* Commit hashes */
+	html("<td class='hashes'>");
+	for (ent = sb.ent; ent; ent = ent->next) {
+		html("<div class='alt'><pre>");
+		emit_blame_entry_hash(ent);
+		html("</pre></div>");
+	}
+	html("</td>\n");
+
+	/* Line numbers */
+	if (ctx.cfg.enable_tree_linenumbers) {
+		html("<td class='linenumbers'>");
+		for (ent = sb.ent; ent; ent = ent->next) {
+			html("<div class='alt'><pre>");
+			emit_blame_entry_linenumber(ent);
+			html("</pre></div>");
+		}
+		html("</td>\n");
+	}
+
+	html("<td class='lines'><div>");
+
+	/* Colored bars behind lines */
+	html("<div>");
+	for (ent = sb.ent; ent; ) {
+		struct blame_entry *e = ent->next;
+		html("<div class='alt'><pre>");
+		emit_blame_entry_line_background(&sb, ent);
+		html("</pre></div>");
+		free(ent);
+		ent = e;
+	}
+	html("</div>");
+
+	free((void *)sb.final_buf);
+
+	/* Lines */
+	html("<pre><code>");
+	if (ctx.repo->source_filter) {
+		char *filter_arg = xstrdup(basename);
+		cgit_open_filter(ctx.repo->source_filter, filter_arg);
+		html_raw(buf, size);
+		cgit_close_filter(ctx.repo->source_filter);
+		free(filter_arg);
+	} else {
+		html_txt(buf);
+	}
+	html("</code></pre>");
+
+	html("</div></td>\n");
+
+	html("</tr>\n</table>\n");
+
+	cgit_print_layout_end();
+
+cleanup:
+	free(buf);
+}
+
+static int walk_tree(const struct object_id *oid, struct strbuf *base,
+		     const char *pathname, unsigned mode, int stage,
+		     void *cbdata)
+{
+	struct walk_tree_context *walk_tree_ctx = cbdata;
+
+	if (base->len == walk_tree_ctx->match_baselen) {
+		if (S_ISREG(mode)) {
+			struct strbuf buffer = STRBUF_INIT;
+			strbuf_addbuf(&buffer, base);
+			strbuf_addstr(&buffer, pathname);
+			print_object(oid, buffer.buf, pathname,
+				     walk_tree_ctx->curr_rev);
+			strbuf_release(&buffer);
+			walk_tree_ctx->state = 1;
+		} else if (S_ISDIR(mode)) {
+			walk_tree_ctx->state = 2;
+		}
+	} else if (base->len < INT_MAX
+			&& (int)base->len > walk_tree_ctx->match_baselen) {
+		walk_tree_ctx->state = 2;
+	} else if (S_ISDIR(mode)) {
+		return READ_TREE_RECURSIVE;
+	}
+	return 0;
+}
+
+static int basedir_len(const char *path)
+{
+	char *p = strrchr(path, '/');
+	if (p)
+		return p - path + 1;
+	return 0;
+}
+
+void cgit_print_blame(void)
+{
+	const char *rev = ctx.qry.sha1;
+	struct object_id oid;
+	struct commit *commit;
+	struct pathspec_item path_items = {
+		.match = ctx.qry.path,
+		.len = ctx.qry.path ? strlen(ctx.qry.path) : 0
+	};
+	struct pathspec paths = {
+		.nr = 1,
+		.items = &path_items
+	};
+	struct walk_tree_context walk_tree_ctx = {
+		.state = 0
+	};
+
+	if (!rev)
+		rev = ctx.qry.head;
+
+	if (get_oid(rev, &oid)) {
+		cgit_print_error_page(404, "Not found",
+			"Invalid revision name: %s", rev);
+		return;
+	}
+	commit = lookup_commit_reference(the_repository, &oid);
+	if (!commit || parse_commit(commit)) {
+		cgit_print_error_page(404, "Not found",
+			"Invalid commit reference: %s", rev);
+		return;
+	}
+
+	walk_tree_ctx.curr_rev = xstrdup(rev);
+	walk_tree_ctx.match_baselen = (path_items.match) ?
+				       basedir_len(path_items.match) : -1;
+
+	read_tree_recursive(the_repository,
+			    repo_get_commit_tree(the_repository, commit),
+			    "", 0, 0,
+			    &paths, walk_tree, &walk_tree_ctx);
+	if (!walk_tree_ctx.state)
+		cgit_print_error_page(404, "Not found", "Not found");
+	else if (walk_tree_ctx.state == 2)
+		cgit_print_error_page(404, "No blame for folders",
+			"Blame is not available for folders.");
+
+	free(walk_tree_ctx.curr_rev);
+}
diff --git a/www/git.causal.agency/cgit/ui-blame.h b/www/git.causal.agency/cgit/ui-blame.h
new file mode 100644
index 00000000..5b97e035
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-blame.h
@@ -0,0 +1,6 @@
+#ifndef UI_BLAME_H
+#define UI_BLAME_H
+
+extern void cgit_print_blame(void);
+
+#endif /* UI_BLAME_H */
diff --git a/www/git.causal.agency/cgit/ui-blob.c b/www/git.causal.agency/cgit/ui-blob.c
new file mode 100644
index 00000000..f76c641e
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-blob.c
@@ -0,0 +1,186 @@
+/* ui-blob.c: show blob content
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-blob.h"
+#include "html.h"
+#include "ui-shared.h"
+
+struct walk_tree_context {
+	const char *match_path;
+	struct object_id *matched_oid;
+	unsigned int found_path:1;
+	unsigned int file_only:1;
+};
+
+static int walk_tree(const struct object_id *oid, struct strbuf *base,
+		const char *pathname, unsigned mode, int stage, void *cbdata)
+{
+	struct walk_tree_context *walk_tree_ctx = cbdata;
+
+	if (walk_tree_ctx->file_only && !S_ISREG(mode))
+		return READ_TREE_RECURSIVE;
+	if (strncmp(base->buf, walk_tree_ctx->match_path, base->len)
+		|| strcmp(walk_tree_ctx->match_path + base->len, pathname))
+		return READ_TREE_RECURSIVE;
+	oidcpy(walk_tree_ctx->matched_oid, oid);
+	walk_tree_ctx->found_path = 1;
+	return 0;
+}
+
+int cgit_ref_path_exists(const char *path, const char *ref, int file_only)
+{
+	struct object_id oid;
+	unsigned long size;
+	struct pathspec_item path_items = {
+		.match = xstrdup(path),
+		.len = strlen(path)
+	};
+	struct pathspec paths = {
+		.nr = 1,
+		.items = &path_items
+	};
+	struct walk_tree_context walk_tree_ctx = {
+		.match_path = path,
+		.matched_oid = &oid,
+		.found_path = 0,
+		.file_only = file_only
+	};
+
+	if (get_oid(ref, &oid))
+		goto done;
+	if (oid_object_info(the_repository, &oid, &size) != OBJ_COMMIT)
+		goto done;
+	read_tree_recursive(the_repository,
+			    repo_get_commit_tree(the_repository, lookup_commit_reference(the_repository, &oid)),
+			    "", 0, 0, &paths, walk_tree, &walk_tree_ctx);
+
+done:
+	free(path_items.match);
+	return walk_tree_ctx.found_path;
+}
+
+int cgit_print_file(char *path, const char *head, int file_only)
+{
+	struct object_id oid;
+	enum object_type type;
+	char *buf;
+	unsigned long size;
+	struct commit *commit;
+	struct pathspec_item path_items = {
+		.match = path,
+		.len = strlen(path)
+	};
+	struct pathspec paths = {
+		.nr = 1,
+		.items = &path_items
+	};
+	struct walk_tree_context walk_tree_ctx = {
+		.match_path = path,
+		.matched_oid = &oid,
+		.found_path = 0,
+		.file_only = file_only
+	};
+
+	if (get_oid(head, &oid))
+		return -1;
+	type = oid_object_info(the_repository, &oid, &size);
+	if (type == OBJ_COMMIT) {
+		commit = lookup_commit_reference(the_repository, &oid);
+		read_tree_recursive(the_repository,
+				    repo_get_commit_tree(the_repository, commit),
+				    "", 0, 0, &paths, walk_tree,
+				    &walk_tree_ctx);
+		if (!walk_tree_ctx.found_path)
+			return -1;
+		type = oid_object_info(the_repository, &oid, &size);
+	}
+	if (type == OBJ_BAD)
+		return -1;
+	buf = read_object_file(&oid, &type, &size);
+	if (!buf)
+		return -1;
+	buf[size] = '\0';
+	html_raw(buf, size);
+	free(buf);
+	return 0;
+}
+
+void cgit_print_blob(const char *hex, char *path, const char *head, int file_only)
+{
+	struct object_id oid;
+	enum object_type type;
+	char *buf;
+	unsigned long size;
+	struct commit *commit;
+	struct pathspec_item path_items = {
+		.match = path,
+		.len = path ? strlen(path) : 0
+	};
+	struct pathspec paths = {
+		.nr = 1,
+		.items = &path_items
+	};
+	struct walk_tree_context walk_tree_ctx = {
+		.match_path = path,
+		.matched_oid = &oid,
+		.found_path = 0,
+		.file_only = file_only
+	};
+
+	if (hex) {
+		if (get_oid_hex(hex, &oid)) {
+			cgit_print_error_page(400, "Bad request",
+					"Bad hex value: %s", hex);
+			return;
+		}
+	} else {
+		if (get_oid(head, &oid)) {
+			cgit_print_error_page(404, "Not found",
+					"Bad ref: %s", head);
+			return;
+		}
+	}
+
+	type = oid_object_info(the_repository, &oid, &size);
+
+	if ((!hex) && type == OBJ_COMMIT && path) {
+		commit = lookup_commit_reference(the_repository, &oid);
+		read_tree_recursive(the_repository,
+				    repo_get_commit_tree(the_repository, commit),
+				    "", 0, 0, &paths, walk_tree,
+				    &walk_tree_ctx);
+		type = oid_object_info(the_repository, &oid, &size);
+	}
+
+	if (type == OBJ_BAD) {
+		cgit_print_error_page(404, "Not found",
+				"Bad object name: %s", hex);
+		return;
+	}
+
+	buf = read_object_file(&oid, &type, &size);
+	if (!buf) {
+		cgit_print_error_page(500, "Internal server error",
+				"Error reading object %s", hex);
+		return;
+	}
+
+	buf[size] = '\0';
+	if (buffer_is_binary(buf, size))
+		ctx.page.mimetype = "application/octet-stream";
+	else
+		ctx.page.mimetype = "text/plain";
+	ctx.page.filename = path;
+
+	html("X-Content-Type-Options: nosniff\n");
+	html("Content-Security-Policy: default-src 'none'\n");
+	cgit_print_http_headers();
+	html_raw(buf, size);
+	free(buf);
+}
diff --git a/www/git.causal.agency/cgit/ui-blob.h b/www/git.causal.agency/cgit/ui-blob.h
new file mode 100644
index 00000000..16847b20
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-blob.h
@@ -0,0 +1,8 @@
+#ifndef UI_BLOB_H
+#define UI_BLOB_H
+
+extern int cgit_ref_path_exists(const char *path, const char *ref, int file_only);
+extern int cgit_print_file(char *path, const char *head, int file_only);
+extern void cgit_print_blob(const char *hex, char *path, const char *head, int file_only);
+
+#endif /* UI_BLOB_H */
diff --git a/www/git.causal.agency/cgit/ui-clone.c b/www/git.causal.agency/cgit/ui-clone.c
new file mode 100644
index 00000000..5dccb639
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-clone.c
@@ -0,0 +1,126 @@
+/* ui-clone.c: functions for http cloning, based on
+ * git's http-backend.c by Shawn O. Pearce
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-clone.h"
+#include "html.h"
+#include "ui-shared.h"
+#include "packfile.h"
+#include "object-store.h"
+
+static int print_ref_info(const char *refname, const struct object_id *oid,
+                          int flags, void *cb_data)
+{
+	struct object *obj;
+
+	if (!(obj = parse_object(the_repository, oid)))
+		return 0;
+
+	htmlf("%s\t%s\n", oid_to_hex(oid), refname);
+	if (obj->type == OBJ_TAG) {
+		if (!(obj = deref_tag(the_repository, obj, refname, 0)))
+			return 0;
+		htmlf("%s\t%s^{}\n", oid_to_hex(&obj->oid), refname);
+	}
+	return 0;
+}
+
+static void print_pack_info(void)
+{
+	struct packed_git *pack;
+	char *offset;
+
+	ctx.page.mimetype = "text/plain";
+	ctx.page.filename = "objects/info/packs";
+	cgit_print_http_headers();
+	reprepare_packed_git(the_repository);
+	for (pack = get_packed_git(the_repository); pack; pack = pack->next) {
+		if (pack->pack_local) {
+			offset = strrchr(pack->pack_name, '/');
+			if (offset && offset[1] != '\0')
+				++offset;
+			else
+				offset = pack->pack_name;
+			htmlf("P %s\n", offset);
+		}
+	}
+}
+
+static void send_file(const char *path)
+{
+	struct stat st;
+
+	if (stat(path, &st)) {
+		switch (errno) {
+		case ENOENT:
+			cgit_print_error_page(404, "Not found", "Not found");
+			break;
+		case EACCES:
+			cgit_print_error_page(403, "Forbidden", "Forbidden");
+			break;
+		default:
+			cgit_print_error_page(400, "Bad request", "Bad request");
+		}
+		return;
+	}
+	ctx.page.mimetype = "application/octet-stream";
+	ctx.page.filename = path;
+	skip_prefix(path, ctx.repo->path, &ctx.page.filename);
+	skip_prefix(ctx.page.filename, "/", &ctx.page.filename);
+	cgit_print_http_headers();
+	html_include(path);
+}
+
+void cgit_clone_info(void)
+{
+	if (!ctx.qry.path || strcmp(ctx.qry.path, "refs")) {
+		cgit_print_error_page(400, "Bad request", "Bad request");
+		return;
+	}
+
+	ctx.page.mimetype = "text/plain";
+	ctx.page.filename = "info/refs";
+	cgit_print_http_headers();
+	for_each_ref(print_ref_info, NULL);
+}
+
+void cgit_clone_objects(void)
+{
+	char *p;
+
+	if (!ctx.qry.path)
+		goto err;
+
+	if (!strcmp(ctx.qry.path, "info/packs")) {
+		print_pack_info();
+		return;
+	}
+
+	/* Avoid directory traversal by forbidding "..", but also work around
+	 * other funny business by just specifying a fairly strict format. For
+	 * example, now we don't have to stress out about the Cygwin port.
+	 */
+	for (p = ctx.qry.path; *p; ++p) {
+		if (*p == '.' && *(p + 1) == '.')
+			goto err;
+		if (!isalnum(*p) && *p != '/' && *p != '.' && *p != '-')
+			goto err;
+	}
+
+	send_file(git_path("objects/%s", ctx.qry.path));
+	return;
+
+err:
+	cgit_print_error_page(400, "Bad request", "Bad request");
+}
+
+void cgit_clone_head(void)
+{
+	send_file(git_path("%s", "HEAD"));
+}
diff --git a/www/git.causal.agency/cgit/ui-clone.h b/www/git.causal.agency/cgit/ui-clone.h
new file mode 100644
index 00000000..3e460a3d
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-clone.h
@@ -0,0 +1,8 @@
+#ifndef UI_CLONE_H
+#define UI_CLONE_H
+
+void cgit_clone_info(void);
+void cgit_clone_objects(void);
+void cgit_clone_head(void);
+
+#endif /* UI_CLONE_H */
diff --git a/www/git.causal.agency/cgit/ui-commit.c b/www/git.causal.agency/cgit/ui-commit.c
new file mode 100644
index 00000000..7e6b5a06
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-commit.c
@@ -0,0 +1,148 @@
+/* ui-commit.c: generate commit view
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-commit.h"
+#include "html.h"
+#include "ui-shared.h"
+#include "ui-diff.h"
+#include "ui-log.h"
+
+void cgit_print_commit(char *hex, const char *prefix)
+{
+	struct commit *commit, *parent;
+	struct commitinfo *info, *parent_info;
+	struct commit_list *p;
+	struct strbuf notes = STRBUF_INIT;
+	struct object_id oid;
+	char *tmp, *tmp2;
+	int parents = 0;
+
+	if (!hex)
+		hex = ctx.qry.head;
+
+	if (get_oid(hex, &oid)) {
+		cgit_print_error_page(400, "Bad request",
+				"Bad object id: %s", hex);
+		return;
+	}
+	commit = lookup_commit_reference(the_repository, &oid);
+	if (!commit) {
+		cgit_print_error_page(404, "Not found",
+				"Bad commit reference: %s", hex);
+		return;
+	}
+	info = cgit_parse_commit(commit);
+
+	format_display_notes(&oid, &notes, PAGE_ENCODING, 0);
+
+	load_ref_decorations(NULL, DECORATE_FULL_REFS);
+
+	ctx.page.title = fmtalloc("%s - %s", info->subject, ctx.page.title);
+	cgit_print_layout_start();
+	cgit_print_diff_ctrls();
+	html("<table summary='commit info' class='commit-info'>\n");
+	html("<tr><th>author</th><td>");
+	cgit_open_filter(ctx.repo->email_filter, info->author_email, "commit");
+	html_txt(info->author);
+	if (!ctx.cfg.noplainemail) {
+		html(" ");
+		html_txt(info->author_email);
+	}
+	cgit_close_filter(ctx.repo->email_filter);
+	html("</td><td class='right'>");
+	html_txt(show_date(info->author_date, info->author_tz,
+				cgit_date_mode(DATE_ISO8601)));
+	html("</td></tr>\n");
+	html("<tr><th>committer</th><td>");
+	cgit_open_filter(ctx.repo->email_filter, info->committer_email, "commit");
+	html_txt(info->committer);
+	if (!ctx.cfg.noplainemail) {
+		html(" ");
+		html_txt(info->committer_email);
+	}
+	cgit_close_filter(ctx.repo->email_filter);
+	html("</td><td class='right'>");
+	html_txt(show_date(info->committer_date, info->committer_tz,
+				cgit_date_mode(DATE_ISO8601)));
+	html("</td></tr>\n");
+	html("<tr><th>commit</th><td colspan='2' class='sha1'>");
+	tmp = oid_to_hex(&commit->object.oid);
+	cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, prefix);
+	html(" (");
+	cgit_patch_link("patch", NULL, NULL, NULL, tmp, prefix);
+	html(")</td></tr>\n");
+	html("<tr><th>tree</th><td colspan='2' class='sha1'>");
+	tmp = xstrdup(hex);
+	cgit_tree_link(oid_to_hex(get_commit_tree_oid(commit)), NULL, NULL,
+		       ctx.qry.head, tmp, NULL);
+	if (prefix) {
+		html(" /");
+		cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix);
+	}
+	free(tmp);
+	html("</td></tr>\n");
+	for (p = commit->parents; p; p = p->next) {
+		parent = lookup_commit_reference(the_repository, &p->item->object.oid);
+		if (!parent) {
+			html("<tr><td colspan='3'>");
+			cgit_print_error("Error reading parent commit");
+			html("</td></tr>");
+			continue;
+		}
+		html("<tr><th>parent</th>"
+		     "<td colspan='2' class='sha1'>");
+		tmp = tmp2 = oid_to_hex(&p->item->object.oid);
+		if (ctx.repo->enable_subject_links) {
+			parent_info = cgit_parse_commit(parent);
+			tmp2 = parent_info->subject;
+		}
+		cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, prefix);
+		html(" (");
+		cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
+			       oid_to_hex(&p->item->object.oid), prefix);
+		html(")</td></tr>");
+		parents++;
+	}
+	if (ctx.repo->snapshots) {
+		html("<tr><th>download</th><td colspan='2' class='sha1'>");
+		cgit_print_snapshot_links(ctx.repo, hex, "<br/>");
+		html("</td></tr>");
+	}
+	html("</table>\n");
+	html("<div class='commit-subject'>");
+	cgit_open_filter(ctx.repo->commit_filter);
+	html_txt(info->subject);
+	cgit_close_filter(ctx.repo->commit_filter);
+	show_commit_decorations(commit);
+	html("</div>");
+	html("<div class='commit-msg'>");
+	cgit_open_filter(ctx.repo->commit_filter);
+	html_txt(info->msg);
+	cgit_close_filter(ctx.repo->commit_filter);
+	html("</div>");
+	if (notes.len != 0) {
+		html("<div class='notes-header'>Notes</div>");
+		html("<div class='notes'>");
+		cgit_open_filter(ctx.repo->commit_filter);
+		html_txt(notes.buf);
+		cgit_close_filter(ctx.repo->commit_filter);
+		html("</div>");
+		html("<div class='notes-footer'></div>");
+	}
+	if (parents < 3) {
+		if (parents)
+			tmp = oid_to_hex(&commit->parents->item->object.oid);
+		else
+			tmp = NULL;
+		cgit_print_diff(ctx.qry.sha1, tmp, prefix, 0, 0);
+	}
+	strbuf_release(&notes);
+	cgit_free_commitinfo(info);
+	cgit_print_layout_end();
+}
diff --git a/www/git.causal.agency/cgit/ui-commit.h b/www/git.causal.agency/cgit/ui-commit.h
new file mode 100644
index 00000000..8198b4ba
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-commit.h
@@ -0,0 +1,6 @@
+#ifndef UI_COMMIT_H
+#define UI_COMMIT_H
+
+extern void cgit_print_commit(char *hex, const char *prefix);
+
+#endif /* UI_COMMIT_H */
diff --git a/www/git.causal.agency/cgit/ui-diff.c b/www/git.causal.agency/cgit/ui-diff.c
new file mode 100644
index 00000000..329c3506
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-diff.c
@@ -0,0 +1,501 @@
+/* ui-diff.c: show diff between two blobs
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-diff.h"
+#include "html.h"
+#include "ui-shared.h"
+#include "ui-ssdiff.h"
+
+struct object_id old_rev_oid[1];
+struct object_id new_rev_oid[1];
+
+static int files, slots;
+static int total_adds, total_rems, max_changes;
+static int lines_added, lines_removed;
+
+static struct fileinfo {
+	char status;
+	struct object_id old_oid[1];
+	struct object_id new_oid[1];
+	unsigned short old_mode;
+	unsigned short new_mode;
+	char *old_path;
+	char *new_path;
+	unsigned int added;
+	unsigned int removed;
+	unsigned long old_size;
+	unsigned long new_size;
+	unsigned int binary:1;
+} *items;
+
+static int use_ssdiff = 0;
+static struct diff_filepair *current_filepair;
+static const char *current_prefix;
+
+struct diff_filespec *cgit_get_current_old_file(void)
+{
+	return current_filepair->one;
+}
+
+struct diff_filespec *cgit_get_current_new_file(void)
+{
+	return current_filepair->two;
+}
+
+static void print_fileinfo(struct fileinfo *info)
+{
+	char *class;
+
+	switch (info->status) {
+	case DIFF_STATUS_ADDED:
+		class = "add";
+		break;
+	case DIFF_STATUS_COPIED:
+		class = "cpy";
+		break;
+	case DIFF_STATUS_DELETED:
+		class = "del";
+		break;
+	case DIFF_STATUS_MODIFIED:
+		class = "upd";
+		break;
+	case DIFF_STATUS_RENAMED:
+		class = "mov";
+		break;
+	case DIFF_STATUS_TYPE_CHANGED:
+		class = "typ";
+		break;
+	case DIFF_STATUS_UNKNOWN:
+		class = "unk";
+		break;
+	case DIFF_STATUS_UNMERGED:
+		class = "stg";
+		break;
+	default:
+		die("bug: unhandled diff status %c", info->status);
+	}
+
+	html("<tr>");
+	html("<td class='mode'>");
+	if (is_null_oid(info->new_oid)) {
+		cgit_print_filemode(info->old_mode);
+	} else {
+		cgit_print_filemode(info->new_mode);
+	}
+
+	if (info->old_mode != info->new_mode &&
+	    !is_null_oid(info->old_oid) &&
+	    !is_null_oid(info->new_oid)) {
+		html("<span class='modechange'>[");
+		cgit_print_filemode(info->old_mode);
+		html("]</span>");
+	}
+	htmlf("</td><td class='%s'>", class);
+	cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
+		       ctx.qry.sha2, info->new_path);
+	if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) {
+		htmlf(" (%s from ",
+		      info->status == DIFF_STATUS_COPIED ? "copied" : "renamed");
+		html_txt(info->old_path);
+		html(")");
+	}
+	html("</td><td class='right'>");
+	if (info->binary) {
+		htmlf("bin</td><td class='graph'>%ld -> %ld bytes",
+		      info->old_size, info->new_size);
+		return;
+	}
+	htmlf("%d", info->added + info->removed);
+	html("</td><td class='graph'>");
+	htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
+	htmlf("<td class='add' style='width: %.1f%%;'/>",
+	      info->added * 100.0 / max_changes);
+	htmlf("<td class='rem' style='width: %.1f%%;'/>",
+	      info->removed * 100.0 / max_changes);
+	htmlf("<td class='none' style='width: %.1f%%;'/>",
+	      (max_changes - info->removed - info->added) * 100.0 / max_changes);
+	html("</tr></table></td></tr>\n");
+}
+
+static void count_diff_lines(char *line, int len)
+{
+	if (line && (len > 0)) {
+		if (line[0] == '+')
+			lines_added++;
+		else if (line[0] == '-')
+			lines_removed++;
+	}
+}
+
+static int show_filepair(struct diff_filepair *pair)
+{
+	/* Always show if we have no limiting prefix. */
+	if (!current_prefix)
+		return 1;
+
+	/* Show if either path in the pair begins with the prefix. */
+	if (starts_with(pair->one->path, current_prefix) ||
+	    starts_with(pair->two->path, current_prefix))
+		return 1;
+
+	/* Otherwise we don't want to show this filepair. */
+	return 0;
+}
+
+static void inspect_filepair(struct diff_filepair *pair)
+{
+	int binary = 0;
+	unsigned long old_size = 0;
+	unsigned long new_size = 0;
+
+	if (!show_filepair(pair))
+		return;
+
+	files++;
+	lines_added = 0;
+	lines_removed = 0;
+	cgit_diff_files(&pair->one->oid, &pair->two->oid, &old_size, &new_size,
+			&binary, 0, ctx.qry.ignorews, count_diff_lines);
+	if (files >= slots) {
+		if (slots == 0)
+			slots = 4;
+		else
+			slots = slots * 2;
+		items = xrealloc(items, slots * sizeof(struct fileinfo));
+	}
+	items[files-1].status = pair->status;
+	oidcpy(items[files-1].old_oid, &pair->one->oid);
+	oidcpy(items[files-1].new_oid, &pair->two->oid);
+	items[files-1].old_mode = pair->one->mode;
+	items[files-1].new_mode = pair->two->mode;
+	items[files-1].old_path = xstrdup(pair->one->path);
+	items[files-1].new_path = xstrdup(pair->two->path);
+	items[files-1].added = lines_added;
+	items[files-1].removed = lines_removed;
+	items[files-1].old_size = old_size;
+	items[files-1].new_size = new_size;
+	items[files-1].binary = binary;
+	if (lines_added + lines_removed > max_changes)
+		max_changes = lines_added + lines_removed;
+	total_adds += lines_added;
+	total_rems += lines_removed;
+}
+
+static void cgit_print_diffstat(const struct object_id *old_oid,
+				const struct object_id *new_oid,
+				const char *prefix)
+{
+	int i;
+
+	html("<div class='diffstat-header'>");
+	cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
+		       ctx.qry.sha2, NULL);
+	if (prefix) {
+		html(" (limited to '");
+		html_txt(prefix);
+		html("')");
+	}
+	html("</div>");
+	html("<table summary='diffstat' class='diffstat'>");
+	max_changes = 0;
+	cgit_diff_tree(old_oid, new_oid, inspect_filepair, prefix,
+		       ctx.qry.ignorews);
+	for (i = 0; i<files; i++)
+		print_fileinfo(&items[i]);
+	html("</table>");
+	html("<div class='diffstat-summary'>");
+	htmlf("%d files changed, %d insertions, %d deletions",
+	      files, total_adds, total_rems);
+	html("</div>");
+}
+
+
+/*
+ * print a single line returned from xdiff
+ */
+static void print_line(char *line, int len)
+{
+	char *class = "ctx";
+	char c = line[len-1];
+
+	if (line[0] == '+')
+		class = "add";
+	else if (line[0] == '-')
+		class = "del";
+	else if (line[0] == '@')
+		class = "hunk";
+
+	htmlf("<div class='%s'>", class);
+	line[len-1] = '\0';
+	html_txt(line);
+	html("</div>");
+	line[len-1] = c;
+}
+
+static void header(const struct object_id *oid1, char *path1, int mode1,
+		   const struct object_id *oid2, char *path2, int mode2)
+{
+	char *abbrev1, *abbrev2;
+	int subproject;
+
+	subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
+	html("<div class='head'>");
+	html("diff --git a/");
+	html_txt(path1);
+	html(" b/");
+	html_txt(path2);
+
+	if (mode1 == 0)
+		htmlf("<br/>new file mode %.6o", mode2);
+
+	if (mode2 == 0)
+		htmlf("<br/>deleted file mode %.6o", mode1);
+
+	if (!subproject) {
+		abbrev1 = xstrdup(find_unique_abbrev(oid1, DEFAULT_ABBREV));
+		abbrev2 = xstrdup(find_unique_abbrev(oid2, DEFAULT_ABBREV));
+		htmlf("<br/>index %s..%s", abbrev1, abbrev2);
+		free(abbrev1);
+		free(abbrev2);
+		if (mode1 != 0 && mode2 != 0) {
+			htmlf(" %.6o", mode1);
+			if (mode2 != mode1)
+				htmlf("..%.6o", mode2);
+		}
+		if (is_null_oid(oid1)) {
+			path1 = "dev/null";
+			html("<br/>--- /");
+		} else
+			html("<br/>--- a/");
+		if (mode1 != 0)
+			cgit_tree_link(path1, NULL, NULL, ctx.qry.head,
+				       oid_to_hex(old_rev_oid), path1);
+		else
+			html_txt(path1);
+		if (is_null_oid(oid2)) {
+			path2 = "dev/null";
+			html("<br/>+++ /");
+		} else
+			html("<br/>+++ b/");
+		if (mode2 != 0)
+			cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
+				       oid_to_hex(new_rev_oid), path2);
+		else
+			html_txt(path2);
+	}
+	html("</div>");
+}
+
+static void filepair_cb(struct diff_filepair *pair)
+{
+	unsigned long old_size = 0;
+	unsigned long new_size = 0;
+	int binary = 0;
+	linediff_fn print_line_fn = print_line;
+
+	if (!show_filepair(pair))
+		return;
+
+	current_filepair = pair;
+	if (use_ssdiff) {
+		cgit_ssdiff_header_begin();
+		print_line_fn = cgit_ssdiff_line_cb;
+	}
+	header(&pair->one->oid, pair->one->path, pair->one->mode,
+	       &pair->two->oid, pair->two->path, pair->two->mode);
+	if (use_ssdiff)
+		cgit_ssdiff_header_end();
+	if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
+		if (S_ISGITLINK(pair->one->mode))
+			print_line_fn(fmt("-Subproject %s", oid_to_hex(&pair->one->oid)), 52);
+		if (S_ISGITLINK(pair->two->mode))
+			print_line_fn(fmt("+Subproject %s", oid_to_hex(&pair->two->oid)), 52);
+		if (use_ssdiff)
+			cgit_ssdiff_footer();
+		return;
+	}
+	if (cgit_diff_files(&pair->one->oid, &pair->two->oid, &old_size,
+			    &new_size, &binary, ctx.qry.context,
+			    ctx.qry.ignorews, print_line_fn))
+		cgit_print_error("Error running diff");
+	if (binary) {
+		if (use_ssdiff)
+			html("<tr><td colspan='4'>Binary files differ</td></tr>");
+		else
+			html("Binary files differ");
+	}
+	if (use_ssdiff)
+		cgit_ssdiff_footer();
+}
+
+void cgit_print_diff_ctrls(void)
+{
+	int i, curr;
+
+	html("<div class='cgit-panel'>");
+	html("<b>diff options</b>");
+	html("<form method='get'>");
+	cgit_add_hidden_formfields(1, 0, ctx.qry.page);
+	html("<table>");
+	html("<tr><td colspan='2'/></tr>");
+	html("<tr>");
+	html("<td class='label'>context:</td>");
+	html("<td class='ctrl'>");
+	html("<select name='context' onchange='this.form.submit();'>");
+	curr = ctx.qry.context;
+	if (!curr)
+		curr = 3;
+	for (i = 1; i <= 10; i++)
+		html_intoption(i, fmt("%d", i), curr);
+	for (i = 15; i <= 40; i += 5)
+		html_intoption(i, fmt("%d", i), curr);
+	html("</select>");
+	html("</td>");
+	html("</tr><tr>");
+	html("<td class='label'>space:</td>");
+	html("<td class='ctrl'>");
+	html("<select name='ignorews' onchange='this.form.submit();'>");
+	html_intoption(0, "include", ctx.qry.ignorews);
+	html_intoption(1, "ignore", ctx.qry.ignorews);
+	html("</select>");
+	html("</td>");
+	html("</tr><tr>");
+	html("<td class='label'>mode:</td>");
+	html("<td class='ctrl'>");
+	html("<select name='dt' onchange='this.form.submit();'>");
+	curr = ctx.qry.has_difftype ? ctx.qry.difftype : ctx.cfg.difftype;
+	html_intoption(0, "unified", curr);
+	html_intoption(1, "ssdiff", curr);
+	html_intoption(2, "stat only", curr);
+	html("</select></td></tr>");
+	html("<tr><td/><td class='ctrl'>");
+	html("<noscript><input type='submit' value='reload'/></noscript>");
+	html("</td></tr></table>");
+	html("</form>");
+	html("</div>");
+}
+
+void cgit_print_diff(const char *new_rev, const char *old_rev,
+		     const char *prefix, int show_ctrls, int raw)
+{
+	struct commit *commit, *commit2;
+	const struct object_id *old_tree_oid, *new_tree_oid;
+	diff_type difftype;
+
+	/*
+	 * If "follow" is set then the diff machinery needs to examine the
+	 * entire commit to detect renames so we must limit the paths in our
+	 * own callbacks and not pass the prefix to the diff machinery.
+	 */
+	if (ctx.qry.follow && ctx.cfg.enable_follow_links) {
+		current_prefix = prefix;
+		prefix = "";
+	} else {
+		current_prefix = NULL;
+	}
+
+	if (!new_rev)
+		new_rev = ctx.qry.head;
+	if (get_oid(new_rev, new_rev_oid)) {
+		cgit_print_error_page(404, "Not found",
+			"Bad object name: %s", new_rev);
+		return;
+	}
+	commit = lookup_commit_reference(the_repository, new_rev_oid);
+	if (!commit || parse_commit(commit)) {
+		cgit_print_error_page(404, "Not found",
+			"Bad commit: %s", oid_to_hex(new_rev_oid));
+		return;
+	}
+	new_tree_oid = get_commit_tree_oid(commit);
+
+	if (old_rev) {
+		if (get_oid(old_rev, old_rev_oid)) {
+			cgit_print_error_page(404, "Not found",
+				"Bad object name: %s", old_rev);
+			return;
+		}
+	} else if (commit->parents && commit->parents->item) {
+		oidcpy(old_rev_oid, &commit->parents->item->object.oid);
+	} else {
+		oidclr(old_rev_oid);
+	}
+
+	if (!is_null_oid(old_rev_oid)) {
+		commit2 = lookup_commit_reference(the_repository, old_rev_oid);
+		if (!commit2 || parse_commit(commit2)) {
+			cgit_print_error_page(404, "Not found",
+				"Bad commit: %s", oid_to_hex(old_rev_oid));
+			return;
+		}
+		old_tree_oid = get_commit_tree_oid(commit2);
+	} else {
+		old_tree_oid = NULL;
+	}
+
+	if (raw) {
+		struct diff_options diffopt;
+
+		diff_setup(&diffopt);
+		diffopt.output_format = DIFF_FORMAT_PATCH;
+		diffopt.flags.recursive = 1;
+		diff_setup_done(&diffopt);
+
+		ctx.page.mimetype = "text/plain";
+		cgit_print_http_headers();
+		if (old_tree_oid) {
+			diff_tree_oid(old_tree_oid, new_tree_oid, "",
+				       &diffopt);
+		} else {
+			diff_root_tree_oid(new_tree_oid, "", &diffopt);
+		}
+		diffcore_std(&diffopt);
+		diff_flush(&diffopt);
+
+		return;
+	}
+
+	difftype = ctx.qry.has_difftype ? ctx.qry.difftype : ctx.cfg.difftype;
+	use_ssdiff = difftype == DIFF_SSDIFF;
+
+	if (show_ctrls) {
+		cgit_print_layout_start();
+		cgit_print_diff_ctrls();
+	}
+
+	/*
+	 * Clicking on a link to a file in the diff stat should show a diff
+	 * of the file, showing the diff stat limited to a single file is
+	 * pretty useless.  All links from this point on will be to
+	 * individual files, so we simply reset the difftype in the query
+	 * here to avoid propagating DIFF_STATONLY to the individual files.
+	 */
+	if (difftype == DIFF_STATONLY)
+		ctx.qry.difftype = ctx.cfg.difftype;
+
+	cgit_print_diffstat(old_rev_oid, new_rev_oid, prefix);
+
+	if (difftype == DIFF_STATONLY)
+		return;
+
+	if (use_ssdiff) {
+		html("<table summary='ssdiff' class='ssdiff'>");
+	} else {
+		html("<table summary='diff' class='diff'>");
+		html("<tr><td>");
+	}
+	cgit_diff_tree(old_rev_oid, new_rev_oid, filepair_cb, prefix,
+		       ctx.qry.ignorews);
+	if (!use_ssdiff)
+		html("</td></tr>");
+	html("</table>");
+
+	if (show_ctrls)
+		cgit_print_layout_end();
+}
diff --git a/www/git.causal.agency/cgit/ui-diff.h b/www/git.causal.agency/cgit/ui-diff.h
new file mode 100644
index 00000000..39264a16
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-diff.h
@@ -0,0 +1,15 @@
+#ifndef UI_DIFF_H
+#define UI_DIFF_H
+
+extern void cgit_print_diff_ctrls(void);
+
+extern void cgit_print_diff(const char *new_hex, const char *old_hex,
+			    const char *prefix, int show_ctrls, int raw);
+
+extern struct diff_filespec *cgit_get_current_old_file(void);
+extern struct diff_filespec *cgit_get_current_new_file(void);
+
+extern struct object_id old_rev_oid[1];
+extern struct object_id new_rev_oid[1];
+
+#endif /* UI_DIFF_H */
diff --git a/www/git.causal.agency/cgit/ui-log.c b/www/git.causal.agency/cgit/ui-log.c
new file mode 100644
index 00000000..2939c016
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-log.c
@@ -0,0 +1,550 @@
+/* ui-log.c: functions for log output
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-log.h"
+#include "html.h"
+#include "ui-shared.h"
+#include "argv-array.h"
+
+static int files, add_lines, rem_lines, lines_counted;
+
+/*
+ * The list of available column colors in the commit graph.
+ */
+static const char *column_colors_html[] = {
+	"<span class='column1'>",
+	"<span class='column2'>",
+	"<span class='column3'>",
+	"<span class='column4'>",
+	"<span class='column5'>",
+	"<span class='column6'>",
+	"</span>",
+};
+
+#define COLUMN_COLORS_HTML_MAX (ARRAY_SIZE(column_colors_html) - 1)
+
+static void count_lines(char *line, int size)
+{
+	if (size <= 0)
+		return;
+
+	if (line[0] == '+')
+		add_lines++;
+
+	else if (line[0] == '-')
+		rem_lines++;
+}
+
+static void inspect_files(struct diff_filepair *pair)
+{
+	unsigned long old_size = 0;
+	unsigned long new_size = 0;
+	int binary = 0;
+
+	files++;
+	if (ctx.repo->enable_log_linecount)
+		cgit_diff_files(&pair->one->oid, &pair->two->oid, &old_size,
+				&new_size, &binary, 0, ctx.qry.ignorews,
+				count_lines);
+}
+
+void show_commit_decorations(struct commit *commit)
+{
+	const struct name_decoration *deco;
+	static char buf[1024];
+
+	buf[sizeof(buf) - 1] = 0;
+	deco = get_name_decoration(&commit->object);
+	if (!deco)
+		return;
+	html("<span class='decoration'>");
+	while (deco) {
+		struct object_id peeled;
+		int is_annotated = 0;
+		strlcpy(buf, prettify_refname(deco->name), sizeof(buf));
+		switch(deco->type) {
+		case DECORATION_NONE:
+			/* If the git-core doesn't recognize it,
+			 * don't display anything. */
+			break;
+		case DECORATION_REF_LOCAL:
+			cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
+				ctx.qry.vpath, 0, NULL, NULL,
+				ctx.qry.showmsg, 0);
+			break;
+		case DECORATION_REF_TAG:
+			if (!peel_ref(deco->name, &peeled))
+				is_annotated = !oidcmp(&commit->object.oid, &peeled);
+			cgit_tag_link(buf, NULL, is_annotated ? "tag-annotated-deco" : "tag-deco", buf);
+			break;
+		case DECORATION_REF_REMOTE:
+			if (!ctx.repo->enable_remote_branches)
+				break;
+			cgit_log_link(buf, NULL, "remote-deco", NULL,
+				oid_to_hex(&commit->object.oid),
+				ctx.qry.vpath, 0, NULL, NULL,
+				ctx.qry.showmsg, 0);
+			break;
+		default:
+			cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
+					oid_to_hex(&commit->object.oid),
+					ctx.qry.vpath);
+			break;
+		}
+		deco = deco->next;
+	}
+	html("</span>");
+}
+
+static void handle_rename(struct diff_filepair *pair)
+{
+	/*
+	 * After we have seen a rename, we generate links to the previous
+	 * name of the file so that commit & diff views get fed the path
+	 * that is correct for the commit they are showing, avoiding the
+	 * need to walk the entire history leading back to every commit we
+	 * show in order detect renames.
+	 */
+	if (0 != strcmp(ctx.qry.vpath, pair->two->path)) {
+		free(ctx.qry.vpath);
+		ctx.qry.vpath = xstrdup(pair->two->path);
+	}
+	inspect_files(pair);
+}
+
+static int show_commit(struct commit *commit, struct rev_info *revs)
+{
+	struct commit_list *parents = commit->parents;
+	struct commit *parent;
+	int found = 0, saved_fmt;
+	struct diff_flags saved_flags = revs->diffopt.flags;
+
+	/* Always show if we're not in "follow" mode with a single file. */
+	if (!ctx.qry.follow)
+		return 1;
+
+	/*
+	 * In "follow" mode, we don't show merges.  This is consistent with
+	 * "git log --follow -- <file>".
+	 */
+	if (parents && parents->next)
+		return 0;
+
+	/*
+	 * If this is the root commit, do what rev_info tells us.
+	 */
+	if (!parents)
+		return revs->show_root_diff;
+
+	/* When we get here we have precisely one parent. */
+	parent = parents->item;
+	/* If we can't parse the commit, let print_commit() report an error. */
+	if (parse_commit(parent))
+		return 1;
+
+	files = 0;
+	add_lines = 0;
+	rem_lines = 0;
+
+	revs->diffopt.flags.recursive = 1;
+	diff_tree_oid(get_commit_tree_oid(parent),
+		      get_commit_tree_oid(commit),
+		      "", &revs->diffopt);
+	diffcore_std(&revs->diffopt);
+
+	found = !diff_queue_is_empty();
+	saved_fmt = revs->diffopt.output_format;
+	revs->diffopt.output_format = DIFF_FORMAT_CALLBACK;
+	revs->diffopt.format_callback = cgit_diff_tree_cb;
+	revs->diffopt.format_callback_data = handle_rename;
+	diff_flush(&revs->diffopt);
+	revs->diffopt.output_format = saved_fmt;
+	revs->diffopt.flags = saved_flags;
+
+	lines_counted = 1;
+	return found;
+}
+
+static void print_commit(struct commit *commit, struct rev_info *revs)
+{
+	struct commitinfo *info;
+	int columns = revs->graph ? 4 : 3;
+	struct strbuf graphbuf = STRBUF_INIT;
+	struct strbuf msgbuf = STRBUF_INIT;
+
+	if (ctx.repo->enable_log_filecount)
+		columns++;
+	if (ctx.repo->enable_log_linecount)
+		columns++;
+
+	if (revs->graph) {
+		/* Advance graph until current commit */
+		while (!graph_next_line(revs->graph, &graphbuf)) {
+			/* Print graph segment in otherwise empty table row */
+			html("<tr class='nohover'><td class='commitgraph'>");
+			html(graphbuf.buf);
+			htmlf("</td><td colspan='%d' /></tr>\n", columns);
+			strbuf_setlen(&graphbuf, 0);
+		}
+		/* Current commit's graph segment is now ready in graphbuf */
+	}
+
+	info = cgit_parse_commit(commit);
+	htmlf("<tr%s>", ctx.qry.showmsg ? " class='logheader'" : "");
+
+	if (revs->graph) {
+		/* Print graph segment for current commit */
+		html("<td class='commitgraph'>");
+		html(graphbuf.buf);
+		html("</td>");
+		strbuf_setlen(&graphbuf, 0);
+	}
+	else {
+		html("<td>");
+		cgit_print_age(info->committer_date, info->committer_tz, TM_WEEK * 2);
+		html("</td>");
+	}
+
+	htmlf("<td%s>", ctx.qry.showmsg ? " class='logsubject'" : "");
+	if (ctx.qry.showmsg) {
+		/* line-wrap long commit subjects instead of truncating them */
+		size_t subject_len = strlen(info->subject);
+
+		if (subject_len > ctx.cfg.max_msg_len &&
+		    ctx.cfg.max_msg_len >= 15) {
+			/* symbol for signaling line-wrap (in PAGE_ENCODING) */
+			const char wrap_symbol[] = { ' ', 0xE2, 0x86, 0xB5, 0 };
+			int i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
+
+			/* Rewind i to preceding space character */
+			while (i > 0 && !isspace(info->subject[i]))
+				--i;
+			if (!i) /* Oops, zero spaces. Reset i */
+				i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
+
+			/* add remainder starting at i to msgbuf */
+			strbuf_add(&msgbuf, info->subject + i, subject_len - i);
+			strbuf_trim(&msgbuf);
+			strbuf_add(&msgbuf, "\n\n", 2);
+
+			/* Place wrap_symbol at position i in info->subject */
+			strlcpy(info->subject + i, wrap_symbol, subject_len - i + 1);
+		}
+	}
+	cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
+			 oid_to_hex(&commit->object.oid), ctx.qry.vpath);
+	show_commit_decorations(commit);
+	html("</td><td>");
+	cgit_open_filter(ctx.repo->email_filter, info->author_email, "log");
+	html_txt(info->author);
+	cgit_close_filter(ctx.repo->email_filter);
+
+	if (revs->graph) {
+		html("</td><td>");
+		cgit_print_age(info->committer_date, info->committer_tz, TM_WEEK * 2);
+	}
+
+	if (!lines_counted && (ctx.repo->enable_log_filecount ||
+			       ctx.repo->enable_log_linecount)) {
+		files = 0;
+		add_lines = 0;
+		rem_lines = 0;
+		cgit_diff_commit(commit, inspect_files, ctx.qry.vpath);
+	}
+
+	if (ctx.repo->enable_log_filecount)
+		htmlf("</td><td>%d", files);
+	if (ctx.repo->enable_log_linecount)
+		htmlf("</td><td><span class='deletions'>-%d</span>/"
+			"<span class='insertions'>+%d</span>", rem_lines, add_lines);
+
+	html("</td></tr>\n");
+
+	if ((revs->graph && !graph_is_commit_finished(revs->graph))
+			|| ctx.qry.showmsg) { /* Print a second table row */
+		html("<tr class='nohover-highlight'>");
+
+		if (ctx.qry.showmsg) {
+			/* Concatenate commit message + notes in msgbuf */
+			if (info->msg && *(info->msg)) {
+				strbuf_addstr(&msgbuf, info->msg);
+				strbuf_addch(&msgbuf, '\n');
+			}
+			format_display_notes(&commit->object.oid,
+					     &msgbuf, PAGE_ENCODING, 0);
+			strbuf_addch(&msgbuf, '\n');
+			strbuf_ltrim(&msgbuf);
+		}
+
+		if (revs->graph) {
+			int lines = 0;
+
+			/* Calculate graph padding */
+			if (ctx.qry.showmsg) {
+				/* Count #lines in commit message + notes */
+				const char *p = msgbuf.buf;
+				lines = 1;
+				while ((p = strchr(p, '\n'))) {
+					p++;
+					lines++;
+				}
+			}
+
+			/* Print graph padding */
+			html("<td class='commitgraph'>");
+			while (lines > 0 || !graph_is_commit_finished(revs->graph)) {
+				if (graphbuf.len)
+					html("\n");
+				strbuf_setlen(&graphbuf, 0);
+				graph_next_line(revs->graph, &graphbuf);
+				html(graphbuf.buf);
+				lines--;
+			}
+			html("</td>\n");
+		}
+		else
+			html("<td/>"); /* Empty 'Age' column */
+
+		/* Print msgbuf into remainder of table row */
+		htmlf("<td colspan='%d'%s>\n", columns - (revs->graph ? 1 : 0),
+			ctx.qry.showmsg ? " class='logmsg'" : "");
+		html_txt(msgbuf.buf);
+		html("</td></tr>\n");
+	}
+
+	strbuf_release(&msgbuf);
+	strbuf_release(&graphbuf);
+	cgit_free_commitinfo(info);
+}
+
+static const char *disambiguate_ref(const char *ref, int *must_free_result)
+{
+	struct object_id oid;
+	struct strbuf longref = STRBUF_INIT;
+
+	strbuf_addf(&longref, "refs/heads/%s", ref);
+	if (get_oid(longref.buf, &oid) == 0) {
+		*must_free_result = 1;
+		return strbuf_detach(&longref, NULL);
+	}
+
+	*must_free_result = 0;
+	strbuf_release(&longref);
+	return ref;
+}
+
+static char *next_token(char **src)
+{
+	char *result;
+
+	if (!src || !*src)
+		return NULL;
+	while (isspace(**src))
+		(*src)++;
+	if (!**src)
+		return NULL;
+	result = *src;
+	while (**src) {
+		if (isspace(**src)) {
+			**src = '\0';
+			(*src)++;
+			break;
+		}
+		(*src)++;
+	}
+	return result;
+}
+
+void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
+		    const char *path, int pager, int commit_graph, int commit_sort)
+{
+	struct rev_info rev;
+	struct commit *commit;
+	struct argv_array rev_argv = ARGV_ARRAY_INIT;
+	int i, columns = commit_graph ? 4 : 3;
+	int must_free_tip = 0;
+
+	/* rev_argv.argv[0] will be ignored by setup_revisions */
+	argv_array_push(&rev_argv, "log_rev_setup");
+
+	if (!tip)
+		tip = ctx.qry.head;
+	tip = disambiguate_ref(tip, &must_free_tip);
+	argv_array_push(&rev_argv, tip);
+
+	if (grep && pattern && *pattern) {
+		pattern = xstrdup(pattern);
+		if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
+		    !strcmp(grep, "committer")) {
+			argv_array_pushf(&rev_argv, "--%s=%s", grep, pattern);
+		} else if (!strcmp(grep, "range")) {
+			char *arg;
+			/* Split the pattern at whitespace and add each token
+			 * as a revision expression. Do not accept other
+			 * rev-list options. Also, replace the previously
+			 * pushed tip (it's no longer relevant).
+			 */
+			argv_array_pop(&rev_argv);
+			while ((arg = next_token(&pattern))) {
+				if (*arg == '-') {
+					fprintf(stderr, "Bad range expr: %s\n",
+						arg);
+					break;
+				}
+				argv_array_push(&rev_argv, arg);
+			}
+		}
+	}
+
+	if (!path || !ctx.cfg.enable_follow_links) {
+		/*
+		 * If we don't have a path, "follow" is a no-op so make sure
+		 * the variable is set to false to avoid needing to check
+		 * both this and whether we have a path everywhere.
+		 */
+		ctx.qry.follow = 0;
+	}
+
+	if (commit_graph && !ctx.qry.follow) {
+		argv_array_push(&rev_argv, "--graph");
+		argv_array_push(&rev_argv, "--color");
+		graph_set_column_colors(column_colors_html,
+					COLUMN_COLORS_HTML_MAX);
+	}
+
+	if (commit_sort == 1)
+		argv_array_push(&rev_argv, "--date-order");
+	else if (commit_sort == 2)
+		argv_array_push(&rev_argv, "--topo-order");
+
+	if (path && ctx.qry.follow)
+		argv_array_push(&rev_argv, "--follow");
+	argv_array_push(&rev_argv, "--");
+	if (path)
+		argv_array_push(&rev_argv, path);
+
+	init_revisions(&rev, NULL);
+	rev.abbrev = DEFAULT_ABBREV;
+	rev.commit_format = CMIT_FMT_DEFAULT;
+	rev.verbose_header = 1;
+	rev.show_root_diff = 0;
+	rev.ignore_missing = 1;
+	rev.simplify_history = 1;
+	setup_revisions(rev_argv.argc, rev_argv.argv, &rev, NULL);
+	load_ref_decorations(NULL, DECORATE_FULL_REFS);
+	rev.show_decorations = 1;
+	rev.grep_filter.ignore_case = 1;
+
+	rev.diffopt.detect_rename = 1;
+	rev.diffopt.rename_limit = ctx.cfg.renamelimit;
+	if (ctx.qry.ignorews)
+		DIFF_XDL_SET(&rev.diffopt, IGNORE_WHITESPACE);
+
+	compile_grep_patterns(&rev.grep_filter);
+	prepare_revision_walk(&rev);
+
+	if (pager) {
+		cgit_print_layout_start();
+		html("<table class='list nowrap'>");
+	}
+
+	html("<tr class='nohover'>");
+	if (commit_graph)
+		html("<th></th>");
+	else
+		html("<th class='left'>Age</th>");
+	html("<th class='left'>Commit message");
+	if (pager) {
+		html(" (");
+		cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
+			      NULL, ctx.qry.head, ctx.qry.sha1,
+			      ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
+			      ctx.qry.search, ctx.qry.showmsg ? 0 : 1,
+			      ctx.qry.follow);
+		html(")");
+	}
+	html("</th><th class='left'>Author</th>");
+	if (rev.graph)
+		html("<th class='left'>Age</th>");
+	if (ctx.repo->enable_log_filecount) {
+		html("<th class='left'>Files</th>");
+		columns++;
+	}
+	if (ctx.repo->enable_log_linecount) {
+		html("<th class='left'>Lines</th>");
+		columns++;
+	}
+	html("</tr>\n");
+
+	if (ofs<0)
+		ofs = 0;
+
+	for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; /* nop */) {
+		if (show_commit(commit, &rev))
+			i++;
+		free_commit_buffer(the_repository->parsed_objects, commit);
+		free_commit_list(commit->parents);
+		commit->parents = NULL;
+	}
+
+	for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; /* nop */) {
+		/*
+		 * In "follow" mode, we must count the files and lines the
+		 * first time we invoke diff on a given commit, and we need
+		 * to do that to see if the commit touches the path we care
+		 * about, so we do it in show_commit.  Hence we must clear
+		 * lines_counted here.
+		 *
+		 * This has the side effect of avoiding running diff twice
+		 * when we are both following renames and showing file
+		 * and/or line counts.
+		 */
+		lines_counted = 0;
+		if (show_commit(commit, &rev)) {
+			i++;
+			print_commit(commit, &rev);
+		}
+		free_commit_buffer(the_repository->parsed_objects, commit);
+		free_commit_list(commit->parents);
+		commit->parents = NULL;
+	}
+	if (pager) {
+		html("</table><ul class='pager'>");
+		if (ofs > 0) {
+			html("<li>");
+			cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
+				      ctx.qry.sha1, ctx.qry.vpath,
+				      ofs - cnt, ctx.qry.grep,
+				      ctx.qry.search, ctx.qry.showmsg,
+				      ctx.qry.follow);
+			html("</li>");
+		}
+		if ((commit = get_revision(&rev)) != NULL) {
+			html("<li>");
+			cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
+				      ctx.qry.sha1, ctx.qry.vpath,
+				      ofs + cnt, ctx.qry.grep,
+				      ctx.qry.search, ctx.qry.showmsg,
+				      ctx.qry.follow);
+			html("</li>");
+		}
+		html("</ul>");
+		cgit_print_layout_end();
+	} else if ((commit = get_revision(&rev)) != NULL) {
+		htmlf("<tr class='nohover'><td colspan='%d'>", columns);
+		cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
+			      ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg,
+			      ctx.qry.follow);
+		html("</td></tr>\n");
+	}
+
+	/* If we allocated tip then it is safe to cast away const. */
+	if (must_free_tip)
+		free((char*) tip);
+}
diff --git a/www/git.causal.agency/cgit/ui-log.h b/www/git.causal.agency/cgit/ui-log.h
new file mode 100644
index 00000000..325607cd
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-log.h
@@ -0,0 +1,9 @@
+#ifndef UI_LOG_H
+#define UI_LOG_H
+
+extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep,
+			   char *pattern, const char *path, int pager,
+			   int commit_graph, int commit_sort);
+extern void show_commit_decorations(struct commit *commit);
+
+#endif /* UI_LOG_H */
diff --git a/www/git.causal.agency/cgit/ui-patch.c b/www/git.causal.agency/cgit/ui-patch.c
new file mode 100644
index 00000000..5a964108
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-patch.c
@@ -0,0 +1,98 @@
+/* ui-patch.c: generate patch view
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-patch.h"
+#include "html.h"
+#include "ui-shared.h"
+
+/* two commit hashes with two dots in between and termination */
+#define REV_RANGE_LEN 2 * GIT_MAX_HEXSZ + 3
+
+void cgit_print_patch(const char *new_rev, const char *old_rev,
+		      const char *prefix)
+{
+	struct rev_info rev;
+	struct commit *commit;
+	struct object_id new_rev_oid, old_rev_oid;
+	char rev_range[REV_RANGE_LEN];
+	const char *rev_argv[] = { NULL, "--reverse", "--format=email", rev_range, "--", prefix, NULL };
+	int rev_argc = ARRAY_SIZE(rev_argv) - 1;
+	char *patchname;
+
+	if (!prefix)
+		rev_argc--;
+
+	if (!new_rev)
+		new_rev = ctx.qry.head;
+
+	if (get_oid(new_rev, &new_rev_oid)) {
+		cgit_print_error_page(404, "Not found",
+				"Bad object id: %s", new_rev);
+		return;
+	}
+	commit = lookup_commit_reference(the_repository, &new_rev_oid);
+	if (!commit) {
+		cgit_print_error_page(404, "Not found",
+				"Bad commit reference: %s", new_rev);
+		return;
+	}
+
+	if (old_rev) {
+		if (get_oid(old_rev, &old_rev_oid)) {
+			cgit_print_error_page(404, "Not found",
+					"Bad object id: %s", old_rev);
+			return;
+		}
+		if (!lookup_commit_reference(the_repository, &old_rev_oid)) {
+			cgit_print_error_page(404, "Not found",
+					"Bad commit reference: %s", old_rev);
+			return;
+		}
+	} else if (commit->parents && commit->parents->item) {
+		oidcpy(&old_rev_oid, &commit->parents->item->object.oid);
+	} else {
+		oidclr(&old_rev_oid);
+	}
+
+	if (is_null_oid(&old_rev_oid)) {
+		memcpy(rev_range, oid_to_hex(&new_rev_oid), GIT_SHA1_HEXSZ + 1);
+	} else {
+		xsnprintf(rev_range, REV_RANGE_LEN, "%s..%s", oid_to_hex(&old_rev_oid),
+			oid_to_hex(&new_rev_oid));
+	}
+
+	patchname = fmt("%s.patch", rev_range);
+	ctx.page.mimetype = "text/plain";
+	ctx.page.filename = patchname;
+	cgit_print_http_headers();
+
+	if (ctx.cfg.noplainemail) {
+		rev_argv[2] = "--format=format:From %H Mon Sep 17 00:00:00 "
+			      "2001%nFrom: %an%nDate: %aD%n%w(78,0,1)Subject: "
+			      "%s%n%n%w(0)%b";
+	}
+
+	init_revisions(&rev, NULL);
+	rev.abbrev = DEFAULT_ABBREV;
+	rev.verbose_header = 1;
+	rev.diff = 1;
+	rev.show_root_diff = 1;
+	rev.max_parents = 1;
+	rev.diffopt.output_format |= DIFF_FORMAT_DIFFSTAT |
+			DIFF_FORMAT_PATCH | DIFF_FORMAT_SUMMARY;
+	if (prefix)
+		rev.diffopt.stat_sep = fmt("(limited to '%s')\n\n", prefix);
+	setup_revisions(rev_argc, rev_argv, &rev, NULL);
+	prepare_revision_walk(&rev);
+
+	while ((commit = get_revision(&rev)) != NULL) {
+		log_tree_commit(&rev, commit);
+		printf("-- \ncgit %s\n\n", cgit_version);
+	}
+}
diff --git a/www/git.causal.agency/cgit/ui-patch.h b/www/git.causal.agency/cgit/ui-patch.h
new file mode 100644
index 00000000..7a6cacd5
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-patch.h
@@ -0,0 +1,7 @@
+#ifndef UI_PATCH_H
+#define UI_PATCH_H
+
+extern void cgit_print_patch(const char *new_rev, const char *old_rev,
+			     const char *prefix);
+
+#endif /* UI_PATCH_H */
diff --git a/www/git.causal.agency/cgit/ui-plain.c b/www/git.causal.agency/cgit/ui-plain.c
new file mode 100644
index 00000000..2a7b18cc
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-plain.c
@@ -0,0 +1,208 @@
+/* ui-plain.c: functions for output of plain blobs by path
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-plain.h"
+#include "html.h"
+#include "ui-shared.h"
+
+struct walk_tree_context {
+	int match_baselen;
+	int match;
+};
+
+static int print_object(const struct object_id *oid, const char *path)
+{
+	enum object_type type;
+	char *buf, *mimetype;
+	unsigned long size;
+
+	type = oid_object_info(the_repository, oid, &size);
+	if (type == OBJ_BAD) {
+		cgit_print_error_page(404, "Not found", "Not found");
+		return 0;
+	}
+
+	buf = read_object_file(oid, &type, &size);
+	if (!buf) {
+		cgit_print_error_page(404, "Not found", "Not found");
+		return 0;
+	}
+
+	mimetype = get_mimetype_for_filename(path);
+	ctx.page.mimetype = mimetype;
+
+	if (!ctx.repo->enable_html_serving) {
+		html("X-Content-Type-Options: nosniff\n");
+		html("Content-Security-Policy: default-src 'none'\n");
+		if (mimetype) {
+			/* Built-in white list allows PDF and everything that isn't text/ and application/ */
+			if ((!strncmp(mimetype, "text/", 5) || !strncmp(mimetype, "application/", 12)) && strcmp(mimetype, "application/pdf"))
+				ctx.page.mimetype = NULL;
+		}
+	}
+
+	if (!ctx.page.mimetype) {
+		if (buffer_is_binary(buf, size)) {
+			ctx.page.mimetype = "application/octet-stream";
+			ctx.page.charset = NULL;
+		} else {
+			ctx.page.mimetype = "text/plain";
+		}
+	}
+	ctx.page.filename = path;
+	ctx.page.size = size;
+	ctx.page.etag = oid_to_hex(oid);
+	cgit_print_http_headers();
+	html_raw(buf, size);
+	free(mimetype);
+	free(buf);
+	return 1;
+}
+
+static char *buildpath(const char *base, int baselen, const char *path)
+{
+	if (path[0])
+		return fmtalloc("%.*s%s/", baselen, base, path);
+	else
+		return fmtalloc("%.*s/", baselen, base);
+}
+
+static void print_dir(const struct object_id *oid, const char *base,
+		      int baselen, const char *path)
+{
+	char *fullpath, *slash;
+	size_t len;
+
+	fullpath = buildpath(base, baselen, path);
+	slash = (fullpath[0] == '/' ? "" : "/");
+	ctx.page.etag = oid_to_hex(oid);
+	cgit_print_http_headers();
+	htmlf("<html><head><title>%s", slash);
+	html_txt(fullpath);
+	htmlf("</title></head>\n<body>\n<h2>%s", slash);
+	html_txt(fullpath);
+	html("</h2>\n<ul>\n");
+	len = strlen(fullpath);
+	if (len > 1) {
+		fullpath[len - 1] = 0;
+		slash = strrchr(fullpath, '/');
+		if (slash)
+			*(slash + 1) = 0;
+		else {
+			free(fullpath);
+			fullpath = NULL;
+		}
+		html("<li>");
+		cgit_plain_link("../", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
+				fullpath);
+		html("</li>\n");
+	}
+	free(fullpath);
+}
+
+static void print_dir_entry(const struct object_id *oid, const char *base,
+			    int baselen, const char *path, unsigned mode)
+{
+	char *fullpath;
+
+	fullpath = buildpath(base, baselen, path);
+	if (!S_ISDIR(mode) && !S_ISGITLINK(mode))
+		fullpath[strlen(fullpath) - 1] = 0;
+	html("  <li>");
+	if (S_ISGITLINK(mode)) {
+		cgit_submodule_link(NULL, fullpath, oid_to_hex(oid));
+	} else
+		cgit_plain_link(path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
+				fullpath);
+	html("</li>\n");
+	free(fullpath);
+}
+
+static void print_dir_tail(void)
+{
+	html(" </ul>\n</body></html>\n");
+}
+
+static int walk_tree(const struct object_id *oid, struct strbuf *base,
+		const char *pathname, unsigned mode, int stage, void *cbdata)
+{
+	struct walk_tree_context *walk_tree_ctx = cbdata;
+
+	if (base->len == walk_tree_ctx->match_baselen) {
+		if (S_ISREG(mode) || S_ISLNK(mode)) {
+			if (print_object(oid, pathname))
+				walk_tree_ctx->match = 1;
+		} else if (S_ISDIR(mode)) {
+			print_dir(oid, base->buf, base->len, pathname);
+			walk_tree_ctx->match = 2;
+			return READ_TREE_RECURSIVE;
+		}
+	} else if (base->len < INT_MAX && (int)base->len > walk_tree_ctx->match_baselen) {
+		print_dir_entry(oid, base->buf, base->len, pathname, mode);
+		walk_tree_ctx->match = 2;
+	} else if (S_ISDIR(mode)) {
+		return READ_TREE_RECURSIVE;
+	}
+
+	return 0;
+}
+
+static int basedir_len(const char *path)
+{
+	char *p = strrchr(path, '/');
+	if (p)
+		return p - path + 1;
+	return 0;
+}
+
+void cgit_print_plain(void)
+{
+	const char *rev = ctx.qry.sha1;
+	struct object_id oid;
+	struct commit *commit;
+	struct pathspec_item path_items = {
+		.match = ctx.qry.path,
+		.len = ctx.qry.path ? strlen(ctx.qry.path) : 0
+	};
+	struct pathspec paths = {
+		.nr = 1,
+		.items = &path_items
+	};
+	struct walk_tree_context walk_tree_ctx = {
+		.match = 0
+	};
+
+	if (!rev)
+		rev = ctx.qry.head;
+
+	if (get_oid(rev, &oid)) {
+		cgit_print_error_page(404, "Not found", "Not found");
+		return;
+	}
+	commit = lookup_commit_reference(the_repository, &oid);
+	if (!commit || parse_commit(commit)) {
+		cgit_print_error_page(404, "Not found", "Not found");
+		return;
+	}
+	if (!path_items.match) {
+		path_items.match = "";
+		walk_tree_ctx.match_baselen = -1;
+		print_dir(get_commit_tree_oid(commit), "", 0, "");
+		walk_tree_ctx.match = 2;
+	}
+	else
+		walk_tree_ctx.match_baselen = basedir_len(path_items.match);
+	read_tree_recursive(the_repository,
+		            repo_get_commit_tree(the_repository, commit),
+		            "", 0, 0, &paths, walk_tree, &walk_tree_ctx);
+	if (!walk_tree_ctx.match)
+		cgit_print_error_page(404, "Not found", "Not found");
+	else if (walk_tree_ctx.match == 2)
+		print_dir_tail();
+}
diff --git a/www/git.causal.agency/cgit/ui-plain.h b/www/git.causal.agency/cgit/ui-plain.h
new file mode 100644
index 00000000..5bff07b8
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-plain.h
@@ -0,0 +1,6 @@
+#ifndef UI_PLAIN_H
+#define UI_PLAIN_H
+
+extern void cgit_print_plain(void);
+
+#endif /* UI_PLAIN_H */
diff --git a/www/git.causal.agency/cgit/ui-refs.c b/www/git.causal.agency/cgit/ui-refs.c
new file mode 100644
index 00000000..456f610d
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-refs.c
@@ -0,0 +1,219 @@
+/* ui-refs.c: browse symbolic refs
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-refs.h"
+#include "html.h"
+#include "ui-shared.h"
+
+static inline int cmp_age(int age1, int age2)
+{
+	/* age1 and age2 are assumed to be non-negative */
+	return age2 - age1;
+}
+
+static int cmp_ref_name(const void *a, const void *b)
+{
+	struct refinfo *r1 = *(struct refinfo **)a;
+	struct refinfo *r2 = *(struct refinfo **)b;
+
+	return strcmp(r1->refname, r2->refname);
+}
+
+static int cmp_branch_age(const void *a, const void *b)
+{
+	struct refinfo *r1 = *(struct refinfo **)a;
+	struct refinfo *r2 = *(struct refinfo **)b;
+
+	return cmp_age(r1->commit->committer_date, r2->commit->committer_date);
+}
+
+static int get_ref_age(struct refinfo *ref)
+{
+	if (!ref->object)
+		return 0;
+	switch (ref->object->type) {
+	case OBJ_TAG:
+		return ref->tag ? ref->tag->tagger_date : 0;
+	case OBJ_COMMIT:
+		return ref->commit ? ref->commit->committer_date : 0;
+	}
+	return 0;
+}
+
+static int cmp_tag_age(const void *a, const void *b)
+{
+	struct refinfo *r1 = *(struct refinfo **)a;
+	struct refinfo *r2 = *(struct refinfo **)b;
+
+	return cmp_age(get_ref_age(r1), get_ref_age(r2));
+}
+
+static int print_branch(struct refinfo *ref)
+{
+	struct commitinfo *info = ref->commit;
+	char *name = (char *)ref->refname;
+
+	if (!info)
+		return 1;
+	html("<tr><td>");
+	cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
+		      ctx.qry.showmsg, 0);
+	html("</td><td>");
+
+	if (ref->object->type == OBJ_COMMIT) {
+		cgit_commit_link(info->subject, NULL, NULL, name, NULL, NULL);
+		html("</td><td>");
+		cgit_open_filter(ctx.repo->email_filter, info->author_email, "refs");
+		html_txt(info->author);
+		cgit_close_filter(ctx.repo->email_filter);
+		html("</td><td colspan='2'>");
+		cgit_print_age(info->committer_date, info->committer_tz, -1);
+	} else {
+		html("</td><td></td><td>");
+		cgit_object_link(ref->object);
+	}
+	html("</td></tr>\n");
+	return 0;
+}
+
+static void print_tag_header(void)
+{
+	html("<tr class='nohover'><th class='left'>Tag</th>"
+	     "<th class='left'>Download</th>"
+	     "<th class='left'>Author</th>"
+	     "<th class='left' colspan='2'>Age</th></tr>\n");
+}
+
+static int print_tag(struct refinfo *ref)
+{
+	struct tag *tag = NULL;
+	struct taginfo *info = NULL;
+	char *name = (char *)ref->refname;
+	struct object *obj = ref->object;
+
+	if (obj->type == OBJ_TAG) {
+		tag = (struct tag *)obj;
+		obj = tag->tagged;
+		info = ref->tag;
+		if (!info)
+			return 1;
+	}
+
+	html("<tr><td>");
+	cgit_tag_link(name, NULL, NULL, name);
+	html("</td><td>");
+	if (ctx.repo->snapshots && (obj->type == OBJ_COMMIT))
+		cgit_print_snapshot_links(ctx.repo, name, "&nbsp;&nbsp;");
+	else
+		cgit_object_link(obj);
+	html("</td><td>");
+	if (info) {
+		if (info->tagger) {
+			cgit_open_filter(ctx.repo->email_filter, info->tagger_email, "refs");
+			html_txt(info->tagger);
+			cgit_close_filter(ctx.repo->email_filter);
+		}
+	} else if (ref->object->type == OBJ_COMMIT) {
+		cgit_open_filter(ctx.repo->email_filter, ref->commit->author_email, "refs");
+		html_txt(ref->commit->author);
+		cgit_close_filter(ctx.repo->email_filter);
+	}
+	html("</td><td colspan='2'>");
+	if (info) {
+		if (info->tagger_date > 0)
+			cgit_print_age(info->tagger_date, info->tagger_tz, -1);
+	} else if (ref->object->type == OBJ_COMMIT) {
+		cgit_print_age(ref->commit->commit->date, 0, -1);
+	}
+	html("</td></tr>\n");
+
+	return 0;
+}
+
+static void print_refs_link(const char *path)
+{
+	html("<tr class='nohover'><td colspan='5'>");
+	cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
+	html("</td></tr>");
+}
+
+void cgit_print_branches(int maxcount)
+{
+	struct reflist list;
+	int i;
+
+	html("<tr class='nohover'><th class='left'>Branch</th>"
+	     "<th class='left'>Commit message</th>"
+	     "<th class='left'>Author</th>"
+	     "<th class='left' colspan='2'>Age</th></tr>\n");
+
+	list.refs = NULL;
+	list.alloc = list.count = 0;
+	for_each_branch_ref(cgit_refs_cb, &list);
+	if (ctx.repo->enable_remote_branches)
+		for_each_remote_ref(cgit_refs_cb, &list);
+
+	if (maxcount == 0 || maxcount > list.count)
+		maxcount = list.count;
+
+	qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
+	if (ctx.repo->branch_sort == 0)
+		qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name);
+
+	for (i = 0; i < maxcount; i++)
+		print_branch(list.refs[i]);
+
+	if (maxcount < list.count)
+		print_refs_link("heads");
+
+	cgit_free_reflist_inner(&list);
+}
+
+void cgit_print_tags(int maxcount)
+{
+	struct reflist list;
+	int i;
+
+	list.refs = NULL;
+	list.alloc = list.count = 0;
+	for_each_tag_ref(cgit_refs_cb, &list);
+	if (list.count == 0)
+		return;
+	qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age);
+	if (!maxcount)
+		maxcount = list.count;
+	else if (maxcount > list.count)
+		maxcount = list.count;
+	print_tag_header();
+	for (i = 0; i < maxcount; i++)
+		print_tag(list.refs[i]);
+
+	if (maxcount < list.count)
+		print_refs_link("tags");
+
+	cgit_free_reflist_inner(&list);
+}
+
+void cgit_print_refs(void)
+{
+	cgit_print_layout_start();
+	html("<table class='list nowrap'>");
+
+	if (ctx.qry.path && starts_with(ctx.qry.path, "heads"))
+		cgit_print_branches(0);
+	else if (ctx.qry.path && starts_with(ctx.qry.path, "tags"))
+		cgit_print_tags(0);
+	else {
+		cgit_print_branches(0);
+		html("<tr class='nohover'><td colspan='5'>&nbsp;</td></tr>");
+		cgit_print_tags(0);
+	}
+	html("</table>");
+	cgit_print_layout_end();
+}
diff --git a/www/git.causal.agency/cgit/ui-refs.h b/www/git.causal.agency/cgit/ui-refs.h
new file mode 100644
index 00000000..1d4a54a2
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-refs.h
@@ -0,0 +1,8 @@
+#ifndef UI_REFS_H
+#define UI_REFS_H
+
+extern void cgit_print_branches(int maxcount);
+extern void cgit_print_tags(int maxcount);
+extern void cgit_print_refs(void);
+
+#endif /* UI_REFS_H */
diff --git a/www/git.causal.agency/cgit/ui-repolist.c b/www/git.causal.agency/cgit/ui-repolist.c
new file mode 100644
index 00000000..529a2038
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-repolist.c
@@ -0,0 +1,379 @@
+/* ui-repolist.c: functions for generating the repolist page
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-repolist.h"
+#include "html.h"
+#include "ui-shared.h"
+
+static time_t read_agefile(const char *path)
+{
+	time_t result;
+	size_t size;
+	char *buf = NULL;
+	struct strbuf date_buf = STRBUF_INIT;
+
+	if (readfile(path, &buf, &size)) {
+		free(buf);
+		return 0;
+	}
+
+	if (parse_date(buf, &date_buf) == 0)
+		result = strtoul(date_buf.buf, NULL, 10);
+	else
+		result = 0;
+	free(buf);
+	strbuf_release(&date_buf);
+	return result;
+}
+
+static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime)
+{
+	struct strbuf path = STRBUF_INIT;
+	struct stat s;
+	struct cgit_repo *r = (struct cgit_repo *)repo;
+
+	if (repo->mtime != -1) {
+		*mtime = repo->mtime;
+		return 1;
+	}
+	strbuf_addf(&path, "%s/%s", repo->path, ctx.cfg.agefile);
+	if (stat(path.buf, &s) == 0) {
+		*mtime = read_agefile(path.buf);
+		if (*mtime) {
+			r->mtime = *mtime;
+			goto end;
+		}
+	}
+
+	strbuf_reset(&path);
+	strbuf_addf(&path, "%s/refs/heads/%s", repo->path,
+		    repo->defbranch ? repo->defbranch : "master");
+	if (stat(path.buf, &s) == 0) {
+		*mtime = s.st_mtime;
+		r->mtime = *mtime;
+		goto end;
+	}
+
+	strbuf_reset(&path);
+	strbuf_addf(&path, "%s/%s", repo->path, "packed-refs");
+	if (stat(path.buf, &s) == 0) {
+		*mtime = s.st_mtime;
+		r->mtime = *mtime;
+		goto end;
+	}
+
+	*mtime = 0;
+	r->mtime = *mtime;
+end:
+	strbuf_release(&path);
+	return (r->mtime != 0);
+}
+
+static void print_modtime(struct cgit_repo *repo)
+{
+	time_t t;
+	if (get_repo_modtime(repo, &t))
+		cgit_print_age(t, 0, -1);
+}
+
+static int is_match(struct cgit_repo *repo)
+{
+	if (!ctx.qry.search)
+		return 1;
+	if (repo->url && strcasestr(repo->url, ctx.qry.search))
+		return 1;
+	if (repo->name && strcasestr(repo->name, ctx.qry.search))
+		return 1;
+	if (repo->desc && strcasestr(repo->desc, ctx.qry.search))
+		return 1;
+	if (repo->owner && strcasestr(repo->owner, ctx.qry.search))
+		return 1;
+	return 0;
+}
+
+static int is_in_url(struct cgit_repo *repo)
+{
+	if (!ctx.qry.url)
+		return 1;
+	if (repo->url && starts_with(repo->url, ctx.qry.url))
+		return 1;
+	return 0;
+}
+
+static int is_visible(struct cgit_repo *repo)
+{
+	if (repo->hide || repo->ignore)
+		return 0;
+	if (!(is_match(repo) && is_in_url(repo)))
+		return 0;
+	return 1;
+}
+
+static int any_repos_visible(void)
+{
+	int i;
+
+	for (i = 0; i < cgit_repolist.count; i++) {
+		if (is_visible(&cgit_repolist.repos[i]))
+			return 1;
+	}
+	return 0;
+}
+
+static void print_sort_header(const char *title, const char *sort)
+{
+	char *currenturl = cgit_currenturl();
+	html("<th class='left'><a href='");
+	html_attr(currenturl);
+	htmlf("?s=%s", sort);
+	if (ctx.qry.search) {
+		html("&amp;q=");
+		html_url_arg(ctx.qry.search);
+	}
+	htmlf("'>%s</a></th>", title);
+	free(currenturl);
+}
+
+static void print_header(void)
+{
+	html("<tr class='nohover'>");
+	print_sort_header("Name", "name");
+	print_sort_header("Description", "desc");
+	if (ctx.cfg.enable_index_owner)
+		print_sort_header("Owner", "owner");
+	print_sort_header("Idle", "idle");
+	if (ctx.cfg.enable_index_links)
+		html("<th class='left'>Links</th>");
+	html("</tr>\n");
+}
+
+
+static void print_pager(int items, int pagelen, char *search, char *sort)
+{
+	int i, ofs;
+	char *class = NULL;
+	html("<ul class='pager'>");
+	for (i = 0, ofs = 0; ofs < items; i++, ofs = i * pagelen) {
+		class = (ctx.qry.ofs == ofs) ? "current" : NULL;
+		html("<li>");
+		cgit_index_link(fmt("[%d]", i + 1), fmt("Page %d", i + 1),
+				class, search, sort, ofs, 0);
+		html("</li>");
+	}
+	html("</ul>");
+}
+
+static int cmp(const char *s1, const char *s2)
+{
+	if (s1 && s2) {
+		if (ctx.cfg.case_sensitive_sort)
+			return strcmp(s1, s2);
+		else
+			return strcasecmp(s1, s2);
+	}
+	if (s1 && !s2)
+		return -1;
+	if (s2 && !s1)
+		return 1;
+	return 0;
+}
+
+static int sort_name(const void *a, const void *b)
+{
+	const struct cgit_repo *r1 = a;
+	const struct cgit_repo *r2 = b;
+
+	return cmp(r1->name, r2->name);
+}
+
+static int sort_desc(const void *a, const void *b)
+{
+	const struct cgit_repo *r1 = a;
+	const struct cgit_repo *r2 = b;
+
+	return cmp(r1->desc, r2->desc);
+}
+
+static int sort_owner(const void *a, const void *b)
+{
+	const struct cgit_repo *r1 = a;
+	const struct cgit_repo *r2 = b;
+
+	return cmp(r1->owner, r2->owner);
+}
+
+static int sort_idle(const void *a, const void *b)
+{
+	const struct cgit_repo *r1 = a;
+	const struct cgit_repo *r2 = b;
+	time_t t1, t2;
+
+	t1 = t2 = 0;
+	get_repo_modtime(r1, &t1);
+	get_repo_modtime(r2, &t2);
+	return t2 - t1;
+}
+
+static int sort_section(const void *a, const void *b)
+{
+	const struct cgit_repo *r1 = a;
+	const struct cgit_repo *r2 = b;
+	int result;
+
+	result = cmp(r1->section, r2->section);
+	if (!result) {
+		if (!strcmp(ctx.cfg.repository_sort, "age"))
+			result = sort_idle(r1, r2);
+		if (!result)
+			result = cmp(r1->name, r2->name);
+	}
+	return result;
+}
+
+struct sortcolumn {
+	const char *name;
+	int (*fn)(const void *a, const void *b);
+};
+
+static const struct sortcolumn sortcolumn[] = {
+	{"section", sort_section},
+	{"name", sort_name},
+	{"desc", sort_desc},
+	{"owner", sort_owner},
+	{"idle", sort_idle},
+	{NULL, NULL}
+};
+
+static int sort_repolist(char *field)
+{
+	const struct sortcolumn *column;
+
+	for (column = &sortcolumn[0]; column->name; column++) {
+		if (strcmp(field, column->name))
+			continue;
+		qsort(cgit_repolist.repos, cgit_repolist.count,
+			sizeof(struct cgit_repo), column->fn);
+		return 1;
+	}
+	return 0;
+}
+
+
+void cgit_print_repolist(void)
+{
+	int i, columns = 3, hits = 0, header = 0;
+	char *last_section = NULL;
+	char *section;
+	char *repourl;
+	int sorted = 0;
+
+	if (!any_repos_visible()) {
+		cgit_print_error_page(404, "Not found", "No repositories found");
+		return;
+	}
+
+	if (ctx.cfg.enable_index_links)
+		++columns;
+	if (ctx.cfg.enable_index_owner)
+		++columns;
+
+	ctx.page.title = ctx.cfg.root_title;
+	cgit_print_http_headers();
+	cgit_print_docstart();
+	cgit_print_pageheader();
+
+	if (ctx.qry.sort)
+		sorted = sort_repolist(ctx.qry.sort);
+	else if (ctx.cfg.section_sort)
+		sort_repolist("section");
+
+	html("<table summary='repository list' class='list nowrap'>");
+	for (i = 0; i < cgit_repolist.count; i++) {
+		ctx.repo = &cgit_repolist.repos[i];
+		if (!is_visible(ctx.repo))
+			continue;
+		hits++;
+		if (hits <= ctx.qry.ofs)
+			continue;
+		if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count)
+			continue;
+		if (!header++)
+			print_header();
+		section = ctx.repo->section;
+		if (section && !strcmp(section, ""))
+			section = NULL;
+		if (!sorted &&
+		    ((last_section == NULL && section != NULL) ||
+		    (last_section != NULL && section == NULL) ||
+		    (last_section != NULL && section != NULL &&
+		     strcmp(section, last_section)))) {
+			htmlf("<tr class='nohover-highlight'><td colspan='%d' class='reposection'>",
+			      columns);
+			html_txt(section);
+			html("</td></tr>");
+			last_section = section;
+		}
+		htmlf("<tr><td class='%s'>",
+		      !sorted && section ? "sublevel-repo" : "toplevel-repo");
+		cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
+		html("</td><td>");
+		repourl = cgit_repourl(ctx.repo->url);
+		html_link_open(repourl, NULL, NULL);
+		free(repourl);
+		if (html_ntxt(ctx.repo->desc, ctx.cfg.max_repodesc_len) < 0)
+			html("...");
+		html_link_close();
+		html("</td><td>");
+		if (ctx.cfg.enable_index_owner) {
+			if (ctx.repo->owner_filter) {
+				cgit_open_filter(ctx.repo->owner_filter);
+				html_txt(ctx.repo->owner);
+				cgit_close_filter(ctx.repo->owner_filter);
+			} else {
+				char *currenturl = cgit_currenturl();
+				html("<a href='");
+				html_attr(currenturl);
+				html("?q=");
+				html_url_arg(ctx.repo->owner);
+				html("'>");
+				html_txt(ctx.repo->owner);
+				html("</a>");
+				free(currenturl);
+			}
+			html("</td><td>");
+		}
+		print_modtime(ctx.repo);
+		html("</td>");
+		if (ctx.cfg.enable_index_links) {
+			html("<td>");
+			cgit_summary_link("summary", NULL, "button", NULL);
+			cgit_log_link("log", NULL, "button", NULL, NULL, NULL,
+				      0, NULL, NULL, ctx.qry.showmsg, 0);
+			cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
+			html("</td>");
+		}
+		html("</tr>\n");
+	}
+	html("</table>");
+	if (hits > ctx.cfg.max_repo_count)
+		print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search, ctx.qry.sort);
+	cgit_print_docend();
+}
+
+void cgit_print_site_readme(void)
+{
+	cgit_print_layout_start();
+	if (!ctx.cfg.root_readme)
+		goto done;
+	cgit_open_filter(ctx.cfg.about_filter, ctx.cfg.root_readme);
+	html_include(ctx.cfg.root_readme);
+	cgit_close_filter(ctx.cfg.about_filter);
+done:
+	cgit_print_layout_end();
+}
diff --git a/www/git.causal.agency/cgit/ui-repolist.h b/www/git.causal.agency/cgit/ui-repolist.h
new file mode 100644
index 00000000..1b6b3227
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-repolist.h
@@ -0,0 +1,7 @@
+#ifndef UI_REPOLIST_H
+#define UI_REPOLIST_H
+
+extern void cgit_print_repolist(void);
+extern void cgit_print_site_readme(void);
+
+#endif /* UI_REPOLIST_H */
diff --git a/www/git.causal.agency/cgit/ui-shared.c b/www/git.causal.agency/cgit/ui-shared.c
new file mode 100644
index 00000000..dd312bb0
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-shared.c
@@ -0,0 +1,1225 @@
+/* ui-shared.c: common web output functions
+ *
+ * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-shared.h"
+#include "cmd.h"
+#include "html.h"
+#include "version.h"
+
+static const char cgit_doctype[] =
+"<!DOCTYPE html>\n";
+
+static char *http_date(time_t t)
+{
+	static char day[][4] =
+		{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
+	static char month[][4] =
+		{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+		 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+	struct tm *tm = gmtime(&t);
+	return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
+		   tm->tm_mday, month[tm->tm_mon], 1900 + tm->tm_year,
+		   tm->tm_hour, tm->tm_min, tm->tm_sec);
+}
+
+void cgit_print_error(const char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+	cgit_vprint_error(fmt, ap);
+	va_end(ap);
+}
+
+void cgit_vprint_error(const char *fmt, va_list ap)
+{
+	va_list cp;
+	html("<div class='error'>");
+	va_copy(cp, ap);
+	html_vtxtf(fmt, cp);
+	va_end(cp);
+	html("</div>\n");
+}
+
+const char *cgit_httpscheme(void)
+{
+	if (ctx.env.https && !strcmp(ctx.env.https, "on"))
+		return "https://";
+	else
+		return "http://";
+}
+
+char *cgit_hosturl(void)
+{
+	if (ctx.env.http_host)
+		return xstrdup(ctx.env.http_host);
+	if (!ctx.env.server_name)
+		return NULL;
+	if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80)
+		return xstrdup(ctx.env.server_name);
+	return fmtalloc("%s:%s", ctx.env.server_name, ctx.env.server_port);
+}
+
+char *cgit_currenturl(void)
+{
+	const char *root = cgit_rooturl();
+
+	if (!ctx.qry.url)
+		return xstrdup(root);
+	if (root[0] && root[strlen(root) - 1] == '/')
+		return fmtalloc("%s%s", root, ctx.qry.url);
+	return fmtalloc("%s/%s", root, ctx.qry.url);
+}
+
+char *cgit_currentfullurl(void)
+{
+	const char *root = cgit_rooturl();
+	const char *orig_query = ctx.env.query_string ? ctx.env.query_string : "";
+	size_t len = strlen(orig_query);
+	char *query = xmalloc(len + 2), *start_url, *ret;
+
+	/* Remove all url=... parts from query string */
+	memcpy(query + 1, orig_query, len + 1);
+	query[0] = '?';
+	start_url = query;
+	while ((start_url = strstr(start_url, "url=")) != NULL) {
+		if (start_url[-1] == '?' || start_url[-1] == '&') {
+			const char *end_url = strchr(start_url, '&');
+			if (end_url)
+				memmove(start_url, end_url + 1, strlen(end_url));
+			else
+				start_url[0] = '\0';
+		} else
+			++start_url;
+	}
+	if (!query[1])
+		query[0] = '\0';
+
+	if (!ctx.qry.url)
+		ret = fmtalloc("%s%s", root, query);
+	else if (root[0] && root[strlen(root) - 1] == '/')
+		ret = fmtalloc("%s%s%s", root, ctx.qry.url, query);
+	else
+		ret = fmtalloc("%s/%s%s", root, ctx.qry.url, query);
+	free(query);
+	return ret;
+}
+
+const char *cgit_rooturl(void)
+{
+	if (ctx.cfg.virtual_root)
+		return ctx.cfg.virtual_root;
+	else
+		return ctx.cfg.script_name;
+}
+
+const char *cgit_loginurl(void)
+{
+	static const char *login_url;
+	if (!login_url)
+		login_url = fmtalloc("%s?p=login", cgit_rooturl());
+	return login_url;
+}
+
+char *cgit_repourl(const char *reponame)
+{
+	if (ctx.cfg.virtual_root)
+		return fmtalloc("%s%s/", ctx.cfg.virtual_root, reponame);
+	else
+		return fmtalloc("?r=%s", reponame);
+}
+
+char *cgit_fileurl(const char *reponame, const char *pagename,
+		   const char *filename, const char *query)
+{
+	struct strbuf sb = STRBUF_INIT;
+	char *delim;
+
+	if (ctx.cfg.virtual_root) {
+		strbuf_addf(&sb, "%s%s/%s/%s", ctx.cfg.virtual_root, reponame,
+			    pagename, (filename ? filename:""));
+		delim = "?";
+	} else {
+		strbuf_addf(&sb, "?url=%s/%s/%s", reponame, pagename,
+			    (filename ? filename : ""));
+		delim = "&amp;";
+	}
+	if (query)
+		strbuf_addf(&sb, "%s%s", delim, query);
+	return strbuf_detach(&sb, NULL);
+}
+
+char *cgit_pageurl(const char *reponame, const char *pagename,
+		   const char *query)
+{
+	return cgit_fileurl(reponame, pagename, NULL, query);
+}
+
+const char *cgit_repobasename(const char *reponame)
+{
+	/* I assume we don't need to store more than one repo basename */
+	static char rvbuf[1024];
+	int p;
+	const char *rv;
+	size_t len;
+
+	len = strlcpy(rvbuf, reponame, sizeof(rvbuf));
+	if (len >= sizeof(rvbuf))
+		die("cgit_repobasename: truncated repository name '%s'", reponame);
+	p = len - 1;
+	/* strip trailing slashes */
+	while (p && rvbuf[p] == '/')
+		rvbuf[p--] = '\0';
+	/* strip trailing .git */
+	if (p >= 3 && starts_with(&rvbuf[p-3], ".git")) {
+		p -= 3;
+		rvbuf[p--] = '\0';
+	}
+	/* strip more trailing slashes if any */
+	while (p && rvbuf[p] == '/')
+		rvbuf[p--] = '\0';
+	/* find last slash in the remaining string */
+	rv = strrchr(rvbuf, '/');
+	if (rv)
+		return ++rv;
+	return rvbuf;
+}
+
+const char *cgit_snapshot_prefix(const struct cgit_repo *repo)
+{
+	if (repo->snapshot_prefix)
+		return repo->snapshot_prefix;
+
+	return cgit_repobasename(repo->url);
+}
+
+static void site_url(const char *page, const char *search, const char *sort, int ofs, int always_root)
+{
+	char *delim = "?";
+
+	if (always_root || page)
+		html_attr(cgit_rooturl());
+	else {
+		char *currenturl = cgit_currenturl();
+		html_attr(currenturl);
+		free(currenturl);
+	}
+
+	if (page) {
+		htmlf("?p=%s", page);
+		delim = "&amp;";
+	}
+	if (search) {
+		html(delim);
+		html("q=");
+		html_attr(search);
+		delim = "&amp;";
+	}
+	if (sort) {
+		html(delim);
+		html("s=");
+		html_attr(sort);
+		delim = "&amp;";
+	}
+	if (ofs) {
+		html(delim);
+		htmlf("ofs=%d", ofs);
+	}
+}
+
+static void site_link(const char *page, const char *name, const char *title,
+		      const char *class, const char *search, const char *sort, int ofs, int always_root)
+{
+	html("<a");
+	if (title) {
+		html(" title='");
+		html_attr(title);
+		html("'");
+	}
+	if (class) {
+		html(" class='");
+		html_attr(class);
+		html("'");
+	}
+	html(" href='");
+	site_url(page, search, sort, ofs, always_root);
+	html("'>");
+	html_txt(name);
+	html("</a>");
+}
+
+void cgit_index_link(const char *name, const char *title, const char *class,
+		     const char *pattern, const char *sort, int ofs, int always_root)
+{
+	site_link(NULL, name, title, class, pattern, sort, ofs, always_root);
+}
+
+static char *repolink(const char *title, const char *class, const char *page,
+		      const char *head, const char *path)
+{
+	char *delim = "?";
+
+	html("<a");
+	if (title) {
+		html(" title='");
+		html_attr(title);
+		html("'");
+	}
+	if (class) {
+		html(" class='");
+		html_attr(class);
+		html("'");
+	}
+	html(" href='");
+	if (ctx.cfg.virtual_root) {
+		html_url_path(ctx.cfg.virtual_root);
+		html_url_path(ctx.repo->url);
+		if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
+			html("/");
+		if (page) {
+			html_url_path(page);
+			html("/");
+			if (path)
+				html_url_path(path);
+		}
+	} else {
+		html_url_path(ctx.cfg.script_name);
+		html("?url=");
+		html_url_arg(ctx.repo->url);
+		if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
+			html("/");
+		if (page) {
+			html_url_arg(page);
+			html("/");
+			if (path)
+				html_url_arg(path);
+		}
+		delim = "&amp;";
+	}
+	if (head && ctx.repo->defbranch && strcmp(head, ctx.repo->defbranch)) {
+		html(delim);
+		html("h=");
+		html_url_arg(head);
+		delim = "&amp;";
+	}
+	return fmt("%s", delim);
+}
+
+static void reporevlink(const char *page, const char *name, const char *title,
+			const char *class, const char *head, const char *rev,
+			const char *path)
+{
+	char *delim;
+
+	delim = repolink(title, class, page, head, path);
+	if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) {
+		html(delim);
+		html("id=");
+		html_url_arg(rev);
+	}
+	html("'>");
+	html_txt(name);
+	html("</a>");
+}
+
+void cgit_summary_link(const char *name, const char *title, const char *class,
+		       const char *head)
+{
+	reporevlink(NULL, name, title, class, head, NULL, NULL);
+}
+
+void cgit_tag_link(const char *name, const char *title, const char *class,
+		   const char *tag)
+{
+	reporevlink("tag", name, title, class, tag, NULL, NULL);
+}
+
+void cgit_tree_link(const char *name, const char *title, const char *class,
+		    const char *head, const char *rev, const char *path)
+{
+	reporevlink("tree", name, title, class, head, rev, path);
+}
+
+void cgit_plain_link(const char *name, const char *title, const char *class,
+		     const char *head, const char *rev, const char *path)
+{
+	reporevlink("plain", name, title, class, head, rev, path);
+}
+
+void cgit_blame_link(const char *name, const char *title, const char *class,
+		     const char *head, const char *rev, const char *path)
+{
+	reporevlink("blame", name, title, class, head, rev, path);
+}
+
+void cgit_log_link(const char *name, const char *title, const char *class,
+		   const char *head, const char *rev, const char *path,
+		   int ofs, const char *grep, const char *pattern, int showmsg,
+		   int follow)
+{
+	char *delim;
+
+	delim = repolink(title, class, "log", head, path);
+	if (rev && ctx.qry.head && strcmp(rev, ctx.qry.head)) {
+		html(delim);
+		html("id=");
+		html_url_arg(rev);
+		delim = "&amp;";
+	}
+	if (grep && pattern) {
+		html(delim);
+		html("qt=");
+		html_url_arg(grep);
+		delim = "&amp;";
+		html(delim);
+		html("q=");
+		html_url_arg(pattern);
+	}
+	if (ofs > 0) {
+		html(delim);
+		html("ofs=");
+		htmlf("%d", ofs);
+		delim = "&amp;";
+	}
+	if (showmsg) {
+		html(delim);
+		html("showmsg=1");
+		delim = "&amp;";
+	}
+	if (follow) {
+		html(delim);
+		html("follow=1");
+	}
+	html("'>");
+	html_txt(name);
+	html("</a>");
+}
+
+void cgit_commit_link(const char *name, const char *title, const char *class,
+		      const char *head, const char *rev, const char *path)
+{
+	char *delim;
+
+	delim = repolink(title, class, "commit", head, path);
+	if (rev && ctx.qry.head && strcmp(rev, ctx.qry.head)) {
+		html(delim);
+		html("id=");
+		html_url_arg(rev);
+		delim = "&amp;";
+	}
+	if (ctx.qry.difftype) {
+		html(delim);
+		htmlf("dt=%d", ctx.qry.difftype);
+		delim = "&amp;";
+	}
+	if (ctx.qry.context > 0 && ctx.qry.context != 3) {
+		html(delim);
+		html("context=");
+		htmlf("%d", ctx.qry.context);
+		delim = "&amp;";
+	}
+	if (ctx.qry.ignorews) {
+		html(delim);
+		html("ignorews=1");
+		delim = "&amp;";
+	}
+	if (ctx.qry.follow) {
+		html(delim);
+		html("follow=1");
+	}
+	html("'>");
+	if (name[0] != '\0') {
+		if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
+			html_ntxt(name, ctx.cfg.max_msg_len - 3);
+			html("...");
+		} else
+			html_txt(name);
+	} else
+		html_txt("(no commit message)");
+	html("</a>");
+}
+
+void cgit_refs_link(const char *name, const char *title, const char *class,
+		    const char *head, const char *rev, const char *path)
+{
+	reporevlink("refs", name, title, class, head, rev, path);
+}
+
+void cgit_snapshot_link(const char *name, const char *title, const char *class,
+			const char *head, const char *rev,
+			const char *archivename)
+{
+	reporevlink("snapshot", name, title, class, head, rev, archivename);
+}
+
+void cgit_diff_link(const char *name, const char *title, const char *class,
+		    const char *head, const char *new_rev, const char *old_rev,
+		    const char *path)
+{
+	char *delim;
+
+	delim = repolink(title, class, "diff", head, path);
+	if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) {
+		html(delim);
+		html("id=");
+		html_url_arg(new_rev);
+		delim = "&amp;";
+	}
+	if (old_rev) {
+		html(delim);
+		html("id2=");
+		html_url_arg(old_rev);
+		delim = "&amp;";
+	}
+	if (ctx.qry.difftype) {
+		html(delim);
+		htmlf("dt=%d", ctx.qry.difftype);
+		delim = "&amp;";
+	}
+	if (ctx.qry.context > 0 && ctx.qry.context != 3) {
+		html(delim);
+		html("context=");
+		htmlf("%d", ctx.qry.context);
+		delim = "&amp;";
+	}
+	if (ctx.qry.ignorews) {
+		html(delim);
+		html("ignorews=1");
+		delim = "&amp;";
+	}
+	if (ctx.qry.follow) {
+		html(delim);
+		html("follow=1");
+	}
+	html("'>");
+	html_txt(name);
+	html("</a>");
+}
+
+void cgit_patch_link(const char *name, const char *title, const char *class,
+		     const char *head, const char *rev, const char *path)
+{
+	reporevlink("patch", name, title, class, head, rev, path);
+}
+
+void cgit_stats_link(const char *name, const char *title, const char *class,
+		     const char *head, const char *path)
+{
+	reporevlink("stats", name, title, class, head, NULL, path);
+}
+
+static void cgit_self_link(char *name, const char *title, const char *class)
+{
+	if (!strcmp(ctx.qry.page, "repolist"))
+		cgit_index_link(name, title, class, ctx.qry.search, ctx.qry.sort,
+				ctx.qry.ofs, 1);
+	else if (!strcmp(ctx.qry.page, "summary"))
+		cgit_summary_link(name, title, class, ctx.qry.head);
+	else if (!strcmp(ctx.qry.page, "tag"))
+		cgit_tag_link(name, title, class, ctx.qry.has_sha1 ?
+			       ctx.qry.sha1 : ctx.qry.head);
+	else if (!strcmp(ctx.qry.page, "tree"))
+		cgit_tree_link(name, title, class, ctx.qry.head,
+			       ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
+			       ctx.qry.path);
+	else if (!strcmp(ctx.qry.page, "plain"))
+		cgit_plain_link(name, title, class, ctx.qry.head,
+				ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
+				ctx.qry.path);
+	else if (!strcmp(ctx.qry.page, "blame"))
+		cgit_blame_link(name, title, class, ctx.qry.head,
+				ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
+				ctx.qry.path);
+	else if (!strcmp(ctx.qry.page, "log"))
+		cgit_log_link(name, title, class, ctx.qry.head,
+			      ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
+			      ctx.qry.path, ctx.qry.ofs,
+			      ctx.qry.grep, ctx.qry.search,
+			      ctx.qry.showmsg, ctx.qry.follow);
+	else if (!strcmp(ctx.qry.page, "commit"))
+		cgit_commit_link(name, title, class, ctx.qry.head,
+				 ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
+				 ctx.qry.path);
+	else if (!strcmp(ctx.qry.page, "patch"))
+		cgit_patch_link(name, title, class, ctx.qry.head,
+				ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
+				ctx.qry.path);
+	else if (!strcmp(ctx.qry.page, "refs"))
+		cgit_refs_link(name, title, class, ctx.qry.head,
+			       ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
+			       ctx.qry.path);
+	else if (!strcmp(ctx.qry.page, "snapshot"))
+		cgit_snapshot_link(name, title, class, ctx.qry.head,
+				   ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
+				   ctx.qry.path);
+	else if (!strcmp(ctx.qry.page, "diff"))
+		cgit_diff_link(name, title, class, ctx.qry.head,
+			       ctx.qry.sha1, ctx.qry.sha2,
+			       ctx.qry.path);
+	else if (!strcmp(ctx.qry.page, "stats"))
+		cgit_stats_link(name, title, class, ctx.qry.head,
+				ctx.qry.path);
+	else {
+		/* Don't known how to make link for this page */
+		repolink(title, class, ctx.qry.page, ctx.qry.head, ctx.qry.path);
+		html("><!-- cgit_self_link() doesn't know how to make link for page '");
+		html_txt(ctx.qry.page);
+		html("' -->");
+		html_txt(name);
+		html("</a>");
+	}
+}
+
+void cgit_object_link(struct object *obj)
+{
+	char *page, *shortrev, *fullrev, *name;
+
+	fullrev = oid_to_hex(&obj->oid);
+	shortrev = xstrdup(fullrev);
+	shortrev[10] = '\0';
+	if (obj->type == OBJ_COMMIT) {
+		cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
+				 ctx.qry.head, fullrev, NULL);
+		return;
+	} else if (obj->type == OBJ_TREE)
+		page = "tree";
+	else if (obj->type == OBJ_TAG)
+		page = "tag";
+	else
+		page = "blob";
+	name = fmt("%s %s...", type_name(obj->type), shortrev);
+	reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
+}
+
+static struct string_list_item *lookup_path(struct string_list *list,
+					    const char *path)
+{
+	struct string_list_item *item;
+
+	while (path && path[0]) {
+		if ((item = string_list_lookup(list, path)))
+			return item;
+		if (!(path = strchr(path, '/')))
+			break;
+		path++;
+	}
+	return NULL;
+}
+
+void cgit_submodule_link(const char *class, char *path, const char *rev)
+{
+	struct string_list *list;
+	struct string_list_item *item;
+	char tail, *dir;
+	size_t len;
+
+	len = 0;
+	tail = 0;
+	list = &ctx.repo->submodules;
+	item = lookup_path(list, path);
+	if (!item) {
+		len = strlen(path);
+		tail = path[len - 1];
+		if (tail == '/') {
+			path[len - 1] = 0;
+			item = lookup_path(list, path);
+		}
+	}
+	if (item || ctx.repo->module_link) {
+		html("<a ");
+		if (class)
+			htmlf("class='%s' ", class);
+		html("href='");
+		if (item) {
+			html_attrf(item->util, rev);
+		} else {
+			dir = strrchr(path, '/');
+			if (dir)
+				dir++;
+			else
+				dir = path;
+			html_attrf(ctx.repo->module_link, dir, rev);
+		}
+		html("'>");
+		html_txt(path);
+		html("</a>");
+	} else {
+		html("<span");
+		if (class)
+			htmlf(" class='%s'", class);
+		html(">");
+		html_txt(path);
+		html("</span>");
+	}
+	html_txtf(" @ %.7s", rev);
+	if (item && tail)
+		path[len - 1] = tail;
+}
+
+const struct date_mode *cgit_date_mode(enum date_mode_type type)
+{
+	static struct date_mode mode;
+	mode.type = type;
+	mode.local = ctx.cfg.local_time;
+	return &mode;
+}
+
+static void print_rel_date(time_t t, int tz, double value,
+	const char *class, const char *suffix)
+{
+	htmlf("<span class='%s' title='", class);
+	html_attr(show_date(t, tz, cgit_date_mode(DATE_ISO8601)));
+	htmlf("'>%.0f %s</span>", value, suffix);
+}
+
+void cgit_print_age(time_t t, int tz, time_t max_relative)
+{
+	time_t now, secs;
+
+	if (!t)
+		return;
+	time(&now);
+	secs = now - t;
+	if (secs < 0)
+		secs = 0;
+
+	if (secs > max_relative && max_relative >= 0) {
+		html("<span title='");
+		html_attr(show_date(t, tz, cgit_date_mode(DATE_ISO8601)));
+		html("'>");
+		html_txt(show_date(t, tz, cgit_date_mode(DATE_SHORT)));
+		html("</span>");
+		return;
+	}
+
+	if (secs < TM_HOUR * 2) {
+		print_rel_date(t, tz, secs * 1.0 / TM_MIN, "age-mins", "min.");
+		return;
+	}
+	if (secs < TM_DAY * 2) {
+		print_rel_date(t, tz, secs * 1.0 / TM_HOUR, "age-hours", "hours");
+		return;
+	}
+	if (secs < TM_WEEK * 2) {
+		print_rel_date(t, tz, secs * 1.0 / TM_DAY, "age-days", "days");
+		return;
+	}
+	if (secs < TM_MONTH * 2) {
+		print_rel_date(t, tz, secs * 1.0 / TM_WEEK, "age-weeks", "weeks");
+		return;
+	}
+	if (secs < TM_YEAR * 2) {
+		print_rel_date(t, tz, secs * 1.0 / TM_MONTH, "age-months", "months");
+		return;
+	}
+	print_rel_date(t, tz, secs * 1.0 / TM_YEAR, "age-years", "years");
+}
+
+void cgit_print_http_headers(void)
+{
+	if (ctx.env.no_http && !strcmp(ctx.env.no_http, "1"))
+		return;
+
+	if (ctx.page.status)
+		htmlf("Status: %d %s\n", ctx.page.status, ctx.page.statusmsg);
+	if (ctx.page.mimetype && ctx.page.charset)
+		htmlf("Content-Type: %s; charset=%s\n", ctx.page.mimetype,
+		      ctx.page.charset);
+	else if (ctx.page.mimetype)
+		htmlf("Content-Type: %s\n", ctx.page.mimetype);
+	if (ctx.page.size)
+		htmlf("Content-Length: %zd\n", ctx.page.size);
+	if (ctx.page.filename) {
+		html("Content-Disposition: inline; filename=\"");
+		html_header_arg_in_quotes(ctx.page.filename);
+		html("\"\n");
+	}
+	if (!ctx.env.authenticated)
+		html("Cache-Control: no-cache, no-store\n");
+	htmlf("Last-Modified: %s\n", http_date(ctx.page.modified));
+	htmlf("Expires: %s\n", http_date(ctx.page.expires));
+	if (ctx.page.etag)
+		htmlf("ETag: \"%s\"\n", ctx.page.etag);
+	html("\n");
+	if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
+		exit(0);
+}
+
+void cgit_redirect(const char *url, bool permanent)
+{
+	htmlf("Status: %d %s\n", permanent ? 301 : 302, permanent ? "Moved" : "Found");
+	html("Location: ");
+	html_url_path(url);
+	html("\n\n");
+}
+
+static void print_rel_vcs_link(const char *url)
+{
+	html("<link rel='vcs-git' href='");
+	html_attr(url);
+	html("' title='");
+	html_attr(ctx.repo->name);
+	html(" Git repository'/>\n");
+}
+
+void cgit_print_docstart(void)
+{
+	char *host = cgit_hosturl();
+
+	if (ctx.cfg.embedded) {
+		if (ctx.cfg.header)
+			html_include(ctx.cfg.header);
+		return;
+	}
+
+	html(cgit_doctype);
+	html("<html lang='en'>\n");
+	html("<head>\n");
+	html("<title>");
+	html_txt(ctx.page.title);
+	html("</title>\n");
+	htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
+	if (ctx.cfg.robots && *ctx.cfg.robots)
+		htmlf("<meta name='robots' content='%s'/>\n", ctx.cfg.robots);
+	html("<link rel='stylesheet' type='text/css' href='");
+	html_attr(ctx.cfg.css);
+	html("'/>\n");
+	if (ctx.cfg.favicon) {
+		html("<link rel='shortcut icon' href='");
+		html_attr(ctx.cfg.favicon);
+		html("'/>\n");
+	}
+	if (host && ctx.repo && ctx.qry.head) {
+		char *fileurl;
+		struct strbuf sb = STRBUF_INIT;
+		strbuf_addf(&sb, "h=%s", ctx.qry.head);
+
+		html("<link rel='alternate' title='Atom feed' href='");
+		html(cgit_httpscheme());
+		html_attr(host);
+		fileurl = cgit_fileurl(ctx.repo->url, "atom", ctx.qry.vpath,
+				       sb.buf);
+		html_attr(fileurl);
+		html("' type='application/atom+xml'/>\n");
+		strbuf_release(&sb);
+		free(fileurl);
+	}
+	if (ctx.repo)
+		cgit_add_clone_urls(print_rel_vcs_link);
+	if (ctx.cfg.head_include)
+		html_include(ctx.cfg.head_include);
+	if (ctx.repo && ctx.repo->extra_head_content)
+		html(ctx.repo->extra_head_content);
+	html("</head>\n");
+	html("<body>\n");
+	if (ctx.cfg.header)
+		html_include(ctx.cfg.header);
+	free(host);
+}
+
+void cgit_print_docend(void)
+{
+	html("</div> <!-- class=content -->\n");
+	if (ctx.cfg.embedded) {
+		html("</div> <!-- id=cgit -->\n");
+		if (ctx.cfg.footer)
+			html_include(ctx.cfg.footer);
+		return;
+	}
+	if (ctx.cfg.footer)
+		html_include(ctx.cfg.footer);
+	else {
+		htmlf("<div class='footer'>generated by <a href='https://git.zx2c4.com/cgit/about/'>cgit %s</a> "
+			"(<a href='https://git-scm.com/'>git %s</a>) at ", cgit_version, git_version_string);
+		html_txt(show_date(time(NULL), 0, cgit_date_mode(DATE_ISO8601)));
+		html("</div>\n");
+	}
+	html("</div> <!-- id=cgit -->\n");
+	html("</body>\n</html>\n");
+}
+
+void cgit_print_error_page(int code, const char *msg, const char *fmt, ...)
+{
+	va_list ap;
+	ctx.page.expires = ctx.cfg.cache_dynamic_ttl;
+	ctx.page.status = code;
+	ctx.page.statusmsg = msg;
+	cgit_print_layout_start();
+	va_start(ap, fmt);
+	cgit_vprint_error(fmt, ap);
+	va_end(ap);
+	cgit_print_layout_end();
+}
+
+void cgit_print_layout_start(void)
+{
+	cgit_print_http_headers();
+	cgit_print_docstart();
+	cgit_print_pageheader();
+}
+
+void cgit_print_layout_end(void)
+{
+	cgit_print_docend();
+}
+
+static void add_clone_urls(void (*fn)(const char *), char *txt, char *suffix)
+{
+	struct strbuf **url_list = strbuf_split_str(txt, ' ', 0);
+	int i;
+
+	for (i = 0; url_list[i]; i++) {
+		strbuf_rtrim(url_list[i]);
+		if (url_list[i]->len == 0)
+			continue;
+		if (suffix && *suffix)
+			strbuf_addf(url_list[i], "/%s", suffix);
+		fn(url_list[i]->buf);
+	}
+
+	strbuf_list_free(url_list);
+}
+
+void cgit_add_clone_urls(void (*fn)(const char *))
+{
+	if (ctx.repo->clone_url)
+		add_clone_urls(fn, expand_macros(ctx.repo->clone_url), NULL);
+	else if (ctx.cfg.clone_prefix)
+		add_clone_urls(fn, ctx.cfg.clone_prefix, ctx.repo->url);
+}
+
+static int print_this_commit_option(void)
+{
+	struct object_id oid;
+	if (get_oid(ctx.qry.head, &oid))
+		return 1;
+	html_option(oid_to_hex(&oid), "this commit", ctx.qry.head);
+	return 0;
+}
+
+static int print_branch_option(const char *refname, const struct object_id *oid,
+			       int flags, void *cb_data)
+{
+	char *name = (char *)refname;
+	html_option(name, name, ctx.qry.head);
+	return 0;
+}
+
+void cgit_add_hidden_formfields(int incl_head, int incl_search,
+				const char *page)
+{
+	if (!ctx.cfg.virtual_root) {
+		struct strbuf url = STRBUF_INIT;
+
+		strbuf_addf(&url, "%s/%s", ctx.qry.repo, page);
+		if (ctx.qry.vpath)
+			strbuf_addf(&url, "/%s", ctx.qry.vpath);
+		html_hidden("url", url.buf);
+		strbuf_release(&url);
+	}
+
+	if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
+	    strcmp(ctx.qry.head, ctx.repo->defbranch))
+		html_hidden("h", ctx.qry.head);
+
+	if (ctx.qry.sha1)
+		html_hidden("id", ctx.qry.sha1);
+	if (ctx.qry.sha2)
+		html_hidden("id2", ctx.qry.sha2);
+	if (ctx.qry.showmsg)
+		html_hidden("showmsg", "1");
+
+	if (incl_search) {
+		if (ctx.qry.grep)
+			html_hidden("qt", ctx.qry.grep);
+		if (ctx.qry.search)
+			html_hidden("q", ctx.qry.search);
+	}
+}
+
+static const char *hc(const char *page)
+{
+	if (!ctx.qry.page)
+		return NULL;
+
+	return strcmp(ctx.qry.page, page) ? NULL : "active";
+}
+
+static void cgit_print_path_crumbs(char *path)
+{
+	char *old_path = ctx.qry.path;
+	char *p = path, *q, *end = path + strlen(path);
+	int levels = 0;
+
+	ctx.qry.path = NULL;
+	cgit_self_link("root", NULL, NULL);
+	ctx.qry.path = p = path;
+	while (p < end) {
+		if (!(q = strchr(p, '/')) || levels > 15)
+			q = end;
+		*q = '\0';
+		html_txt("/");
+		cgit_self_link(p, NULL, NULL);
+		if (q < end)
+			*q = '/';
+		p = q + 1;
+		++levels;
+	}
+	ctx.qry.path = old_path;
+}
+
+static void print_header(void)
+{
+	char *logo = NULL, *logo_link = NULL;
+
+	html("<table id='header'>\n");
+	html("<tr>\n");
+
+	if (ctx.repo && ctx.repo->logo && *ctx.repo->logo)
+		logo = ctx.repo->logo;
+	else
+		logo = ctx.cfg.logo;
+	if (ctx.repo && ctx.repo->logo_link && *ctx.repo->logo_link)
+		logo_link = ctx.repo->logo_link;
+	else
+		logo_link = ctx.cfg.logo_link;
+	if (logo && *logo) {
+		html("<td class='logo' rowspan='2'><a href='");
+		if (logo_link && *logo_link)
+			html_attr(logo_link);
+		else
+			html_attr(cgit_rooturl());
+		html("'><img src='");
+		html_attr(logo);
+		html("' alt='cgit logo'/></a></td>\n");
+	}
+
+	html("<td class='main'>");
+	if (ctx.repo) {
+		cgit_index_link("index", NULL, NULL, NULL, NULL, 0, 1);
+		html(" : ");
+		cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
+		if (ctx.env.authenticated) {
+			html("</td><td class='form'>");
+			html("<form method='get'>\n");
+			cgit_add_hidden_formfields(0, 1, ctx.qry.page);
+			html("<select name='h' onchange='this.form.submit();'>\n");
+			print_this_commit_option();
+			html("<optgroup label='branches'>");
+			for_each_branch_ref(print_branch_option, ctx.qry.head);
+			if (ctx.repo->enable_remote_branches)
+				for_each_remote_ref(print_branch_option, ctx.qry.head);
+			html("</optgroup>");
+			html("</select> ");
+			html("<input type='submit' value='switch'/>");
+			html("</form>");
+		}
+	} else
+		html_txt(ctx.cfg.root_title);
+	html("</td></tr>\n");
+
+	html("<tr><td class='sub'>");
+	if (ctx.repo) {
+		html_txt(ctx.repo->desc);
+		html("</td><td class='sub right'>");
+		html_txt(ctx.repo->owner);
+	} else {
+		if (ctx.cfg.root_desc)
+			html_txt(ctx.cfg.root_desc);
+	}
+	html("</td></tr></table>\n");
+}
+
+void cgit_print_pageheader(void)
+{
+	html("<div id='cgit'>");
+	if (!ctx.env.authenticated || !ctx.cfg.noheader)
+		print_header();
+
+	html("<table class='tabs'><tr><td>\n");
+	if (ctx.env.authenticated && ctx.repo) {
+		if (ctx.repo->readme.nr)
+			reporevlink("about", "about", NULL,
+				    hc("about"), ctx.qry.head, NULL,
+				    NULL);
+		cgit_summary_link("summary", NULL, hc("summary"),
+				  ctx.qry.head);
+		cgit_refs_link("refs", NULL, hc("refs"), ctx.qry.head,
+			       ctx.qry.sha1, NULL);
+		cgit_log_link("log", NULL, hc("log"), ctx.qry.head,
+			      NULL, ctx.qry.vpath, 0, NULL, NULL,
+			      ctx.qry.showmsg, ctx.qry.follow);
+		if (ctx.qry.page && !strcmp(ctx.qry.page, "blame"))
+			cgit_blame_link("blame", NULL, hc("blame"), ctx.qry.head,
+				        ctx.qry.sha1, ctx.qry.vpath);
+		else
+			cgit_tree_link("tree", NULL, hc("tree"), ctx.qry.head,
+				       ctx.qry.sha1, ctx.qry.vpath);
+		cgit_commit_link("commit", NULL, hc("commit"),
+				 ctx.qry.head, ctx.qry.sha1, ctx.qry.vpath);
+		cgit_diff_link("diff", NULL, hc("diff"), ctx.qry.head,
+			       ctx.qry.sha1, ctx.qry.sha2, ctx.qry.vpath);
+		if (ctx.repo->max_stats)
+			cgit_stats_link("stats", NULL, hc("stats"),
+					ctx.qry.head, ctx.qry.vpath);
+		if (ctx.repo->homepage) {
+			html("<a href='");
+			html_attr(ctx.repo->homepage);
+			html("'>homepage</a>");
+		}
+		html("</td><td class='form'>");
+		html("<form class='right' method='get' action='");
+		if (ctx.cfg.virtual_root) {
+			char *fileurl = cgit_fileurl(ctx.qry.repo, "log",
+						   ctx.qry.vpath, NULL);
+			html_url_path(fileurl);
+			free(fileurl);
+		}
+		html("'>\n");
+		cgit_add_hidden_formfields(1, 0, "log");
+		html("<select name='qt'>\n");
+		html_option("grep", "log msg", ctx.qry.grep);
+		html_option("author", "author", ctx.qry.grep);
+		html_option("committer", "committer", ctx.qry.grep);
+		html_option("range", "range", ctx.qry.grep);
+		html("</select>\n");
+		html("<input class='txt' type='search' size='10' name='q' value='");
+		html_attr(ctx.qry.search);
+		html("'/>\n");
+		html("<input type='submit' value='search'/>\n");
+		html("</form>\n");
+	} else if (ctx.env.authenticated) {
+		char *currenturl = cgit_currenturl();
+		site_link(NULL, "index", NULL, hc("repolist"), NULL, NULL, 0, 1);
+		if (ctx.cfg.root_readme)
+			site_link("about", "about", NULL, hc("about"),
+				  NULL, NULL, 0, 1);
+		html("</td><td class='form'>");
+		html("<form method='get' action='");
+		html_attr(currenturl);
+		html("'>\n");
+		html("<input type='search' name='q' size='10' value='");
+		html_attr(ctx.qry.search);
+		html("'/>\n");
+		html("<input type='submit' value='search'/>\n");
+		html("</form>");
+		free(currenturl);
+	}
+	html("</td></tr></table>\n");
+	if (ctx.env.authenticated && ctx.repo && ctx.qry.vpath) {
+		html("<div class='path'>");
+		html("path: ");
+		cgit_print_path_crumbs(ctx.qry.vpath);
+		if (ctx.cfg.enable_follow_links && !strcmp(ctx.qry.page, "log")) {
+			html(" (");
+			ctx.qry.follow = !ctx.qry.follow;
+			cgit_self_link(ctx.qry.follow ? "follow" : "unfollow",
+					NULL, NULL);
+			ctx.qry.follow = !ctx.qry.follow;
+			html(")");
+		}
+		html("</div>");
+	}
+	html("<div class='content'>");
+}
+
+void cgit_print_filemode(unsigned short mode)
+{
+	if (S_ISDIR(mode))
+		html("d");
+	else if (S_ISLNK(mode))
+		html("l");
+	else if (S_ISGITLINK(mode))
+		html("m");
+	else
+		html("-");
+	html_fileperm(mode >> 6);
+	html_fileperm(mode >> 3);
+	html_fileperm(mode);
+}
+
+void cgit_compose_snapshot_prefix(struct strbuf *filename, const char *base,
+				  const char *ref)
+{
+	struct object_id oid;
+
+	/*
+	 * Prettify snapshot names by stripping leading "v" or "V" if the tag
+	 * name starts with {v,V}[0-9] and the prettify mapping is injective,
+	 * i.e. each stripped tag can be inverted without ambiguities.
+	 */
+	if (get_oid(fmt("refs/tags/%s", ref), &oid) == 0 &&
+	    (ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]) &&
+	    ((get_oid(fmt("refs/tags/%s", ref + 1), &oid) == 0) +
+	     (get_oid(fmt("refs/tags/v%s", ref + 1), &oid) == 0) +
+	     (get_oid(fmt("refs/tags/V%s", ref + 1), &oid) == 0) == 1))
+		ref++;
+
+	strbuf_addf(filename, "%s-%s", base, ref);
+}
+
+void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref,
+			       const char *separator)
+{
+	const struct cgit_snapshot_format *f;
+	struct strbuf filename = STRBUF_INIT;
+	const char *basename;
+	size_t prefixlen;
+
+	basename = cgit_snapshot_prefix(repo);
+	if (starts_with(ref, basename))
+		strbuf_addstr(&filename, ref);
+	else
+		cgit_compose_snapshot_prefix(&filename, basename, ref);
+
+	prefixlen = filename.len;
+	for (f = cgit_snapshot_formats; f->suffix; f++) {
+		if (!(repo->snapshots & cgit_snapshot_format_bit(f)))
+			continue;
+		strbuf_setlen(&filename, prefixlen);
+		strbuf_addstr(&filename, f->suffix);
+		cgit_snapshot_link(filename.buf, NULL, NULL, NULL, NULL,
+				   filename.buf);
+		if (cgit_snapshot_get_sig(ref, f)) {
+			strbuf_addstr(&filename, ".asc");
+			html(" (");
+			cgit_snapshot_link("sig", NULL, NULL, NULL, NULL,
+					   filename.buf);
+			html(")");
+		} else if (starts_with(f->suffix, ".tar") && cgit_snapshot_get_sig(ref, &cgit_snapshot_formats[0])) {
+			strbuf_setlen(&filename, strlen(filename.buf) - strlen(f->suffix));
+			strbuf_addstr(&filename, ".tar.asc");
+			html(" (");
+			cgit_snapshot_link("sig", NULL, NULL, NULL, NULL,
+					   filename.buf);
+			html(")");
+		}
+		html(separator);
+	}
+	strbuf_release(&filename);
+}
+
+void cgit_set_title_from_path(const char *path)
+{
+	struct strbuf sb = STRBUF_INIT;
+	const char *slash, *last_slash;
+
+	if (!path)
+		return;
+
+	last_slash = path + strlen(path);
+	for (slash = last_slash; slash > path; --slash) {
+		if (*slash != '/') continue;
+		strbuf_add(&sb, slash + 1, last_slash - slash - 1);
+		strbuf_addstr(&sb, " \xc2\xab ");
+		last_slash = slash;
+	}
+	strbuf_add(&sb, path, last_slash - path);
+	strbuf_addf(&sb, " - %s", ctx.page.title);
+	ctx.page.title = strbuf_detach(&sb, NULL);
+}
diff --git a/www/git.causal.agency/cgit/ui-shared.h b/www/git.causal.agency/cgit/ui-shared.h
new file mode 100644
index 00000000..6964873a
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-shared.h
@@ -0,0 +1,87 @@
+#ifndef UI_SHARED_H
+#define UI_SHARED_H
+
+extern const char *cgit_httpscheme(void);
+extern char *cgit_hosturl(void);
+extern const char *cgit_rooturl(void);
+extern char *cgit_currenturl(void);
+extern char *cgit_currentfullurl(void);
+extern const char *cgit_loginurl(void);
+extern char *cgit_repourl(const char *reponame);
+extern char *cgit_fileurl(const char *reponame, const char *pagename,
+			  const char *filename, const char *query);
+extern char *cgit_pageurl(const char *reponame, const char *pagename,
+			  const char *query);
+
+extern void cgit_add_clone_urls(void (*fn)(const char *));
+
+extern void cgit_index_link(const char *name, const char *title,
+			    const char *class, const char *pattern, const char *sort, int ofs, int always_root);
+extern void cgit_summary_link(const char *name, const char *title,
+			      const char *class, const char *head);
+extern void cgit_tag_link(const char *name, const char *title,
+			  const char *class, const char *tag);
+extern void cgit_tree_link(const char *name, const char *title,
+			   const char *class, const char *head,
+			   const char *rev, const char *path);
+extern void cgit_plain_link(const char *name, const char *title,
+			    const char *class, const char *head,
+			    const char *rev, const char *path);
+extern void cgit_blame_link(const char *name, const char *title,
+			    const char *class, const char *head,
+			    const char *rev, const char *path);
+extern void cgit_log_link(const char *name, const char *title,
+			  const char *class, const char *head, const char *rev,
+			  const char *path, int ofs, const char *grep,
+			  const char *pattern, int showmsg, int follow);
+extern void cgit_commit_link(const char *name, const char *title,
+			     const char *class, const char *head,
+			     const char *rev, const char *path);
+extern void cgit_patch_link(const char *name, const char *title,
+			    const char *class, const char *head,
+			    const char *rev, const char *path);
+extern void cgit_refs_link(const char *name, const char *title,
+			   const char *class, const char *head,
+			   const char *rev, const char *path);
+extern void cgit_snapshot_link(const char *name, const char *title,
+			       const char *class, const char *head,
+			       const char *rev, const char *archivename);
+extern void cgit_diff_link(const char *name, const char *title,
+			   const char *class, const char *head,
+			   const char *new_rev, const char *old_rev,
+			   const char *path);
+extern void cgit_stats_link(const char *name, const char *title,
+			    const char *class, const char *head,
+			    const char *path);
+extern void cgit_object_link(struct object *obj);
+
+extern void cgit_submodule_link(const char *class, char *path,
+				const char *rev);
+
+extern void cgit_print_layout_start(void);
+extern void cgit_print_layout_end(void);
+
+__attribute__((format (printf,1,2)))
+extern void cgit_print_error(const char *fmt, ...);
+__attribute__((format (printf,1,0)))
+extern void cgit_vprint_error(const char *fmt, va_list ap);
+extern const struct date_mode *cgit_date_mode(enum date_mode_type type);
+extern void cgit_print_age(time_t t, int tz, time_t max_relative);
+extern void cgit_print_http_headers(void);
+extern void cgit_redirect(const char *url, bool permanent);
+extern void cgit_print_docstart(void);
+extern void cgit_print_docend(void);
+__attribute__((format (printf,3,4)))
+extern void cgit_print_error_page(int code, const char *msg, const char *fmt, ...);
+extern void cgit_print_pageheader(void);
+extern void cgit_print_filemode(unsigned short mode);
+extern void cgit_compose_snapshot_prefix(struct strbuf *filename,
+					 const char *base, const char *ref);
+extern void cgit_print_snapshot_links(const struct cgit_repo *repo,
+				      const char *ref, const char *separator);
+extern const char *cgit_snapshot_prefix(const struct cgit_repo *repo);
+extern void cgit_add_hidden_formfields(int incl_head, int incl_search,
+				       const char *page);
+
+extern void cgit_set_title_from_path(const char *path);
+#endif /* UI_SHARED_H */
diff --git a/www/git.causal.agency/cgit/ui-snapshot.c b/www/git.causal.agency/cgit/ui-snapshot.c
new file mode 100644
index 00000000..556d3ed4
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-snapshot.c
@@ -0,0 +1,316 @@
+/* ui-snapshot.c: generate snapshot of a commit
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-snapshot.h"
+#include "html.h"
+#include "ui-shared.h"
+
+static int write_archive_type(const char *format, const char *hex, const char *prefix)
+{
+	struct argv_array argv = ARGV_ARRAY_INIT;
+	const char **nargv;
+	int result;
+	argv_array_push(&argv, "snapshot");
+	argv_array_push(&argv, format);
+	if (prefix) {
+		struct strbuf buf = STRBUF_INIT;
+		strbuf_addstr(&buf, prefix);
+		strbuf_addch(&buf, '/');
+		argv_array_push(&argv, "--prefix");
+		argv_array_push(&argv, buf.buf);
+		strbuf_release(&buf);
+	}
+	argv_array_push(&argv, hex);
+	/*
+	 * Now we need to copy the pointers to arguments into a new
+	 * structure because write_archive will rearrange its arguments
+	 * which may result in duplicated/missing entries causing leaks
+	 * or double-frees in argv_array_clear.
+	 */
+	nargv = xmalloc(sizeof(char *) * (argv.argc + 1));
+	/* argv_array guarantees a trailing NULL entry. */
+	memcpy(nargv, argv.argv, sizeof(char *) * (argv.argc + 1));
+
+	result = write_archive(argv.argc, nargv, NULL, the_repository, NULL, 0);
+	argv_array_clear(&argv);
+	free(nargv);
+	return result;
+}
+
+static int write_tar_archive(const char *hex, const char *prefix)
+{
+	return write_archive_type("--format=tar", hex, prefix);
+}
+
+static int write_zip_archive(const char *hex, const char *prefix)
+{
+	return write_archive_type("--format=zip", hex, prefix);
+}
+
+static int write_compressed_tar_archive(const char *hex,
+					const char *prefix,
+					char *filter_argv[])
+{
+	int rv;
+	struct cgit_exec_filter f;
+	cgit_exec_filter_init(&f, filter_argv[0], filter_argv);
+
+	cgit_open_filter(&f.base);
+	rv = write_tar_archive(hex, prefix);
+	cgit_close_filter(&f.base);
+	return rv;
+}
+
+static int write_tar_gzip_archive(const char *hex, const char *prefix)
+{
+	char *argv[] = { "gzip", "-n", NULL };
+	return write_compressed_tar_archive(hex, prefix, argv);
+}
+
+static int write_tar_bzip2_archive(const char *hex, const char *prefix)
+{
+	char *argv[] = { "bzip2", NULL };
+	return write_compressed_tar_archive(hex, prefix, argv);
+}
+
+static int write_tar_lzip_archive(const char *hex, const char *prefix)
+{
+	char *argv[] = { "lzip", NULL };
+	return write_compressed_tar_archive(hex, prefix, argv);
+}
+
+static int write_tar_xz_archive(const char *hex, const char *prefix)
+{
+	char *argv[] = { "xz", NULL };
+	return write_compressed_tar_archive(hex, prefix, argv);
+}
+
+static int write_tar_zstd_archive(const char *hex, const char *prefix)
+{
+	char *argv[] = { "zstd", "-T0", NULL };
+	return write_compressed_tar_archive(hex, prefix, argv);
+}
+
+const struct cgit_snapshot_format cgit_snapshot_formats[] = {
+	/* .tar must remain the 0 index */
+	{ ".tar",	"application/x-tar",	write_tar_archive	},
+	{ ".tar.gz",	"application/x-gzip",	write_tar_gzip_archive	},
+	{ ".tar.bz2",	"application/x-bzip2",	write_tar_bzip2_archive	},
+	{ ".tar.lz",	"application/x-lzip",	write_tar_lzip_archive	},
+	{ ".tar.xz",	"application/x-xz",	write_tar_xz_archive	},
+	{ ".tar.zst",	"application/x-zstd",	write_tar_zstd_archive	},
+	{ ".zip",	"application/x-zip",	write_zip_archive	},
+	{ NULL }
+};
+
+static struct notes_tree snapshot_sig_notes[ARRAY_SIZE(cgit_snapshot_formats)];
+
+const struct object_id *cgit_snapshot_get_sig(const char *ref,
+					      const struct cgit_snapshot_format *f)
+{
+	struct notes_tree *tree;
+	struct object_id oid;
+
+	if (get_oid(ref, &oid))
+		return NULL;
+
+	tree = &snapshot_sig_notes[f - &cgit_snapshot_formats[0]];
+	if (!tree->initialized) {
+		struct strbuf notes_ref = STRBUF_INIT;
+
+		strbuf_addf(&notes_ref, "refs/notes/signatures/%s",
+			    f->suffix + 1);
+
+		init_notes(tree, notes_ref.buf, combine_notes_ignore, 0);
+		strbuf_release(&notes_ref);
+	}
+
+	return get_note(tree, &oid);
+}
+
+static const struct cgit_snapshot_format *get_format(const char *filename)
+{
+	const struct cgit_snapshot_format *fmt;
+
+	for (fmt = cgit_snapshot_formats; fmt->suffix; fmt++) {
+		if (ends_with(filename, fmt->suffix))
+			return fmt;
+	}
+	return NULL;
+}
+
+const unsigned cgit_snapshot_format_bit(const struct cgit_snapshot_format *f)
+{
+	return BIT(f - &cgit_snapshot_formats[0]);
+}
+
+static int make_snapshot(const struct cgit_snapshot_format *format,
+			 const char *hex, const char *prefix,
+			 const char *filename)
+{
+	struct object_id oid;
+
+	if (get_oid(hex, &oid)) {
+		cgit_print_error_page(404, "Not found",
+				"Bad object id: %s", hex);
+		return 1;
+	}
+	if (!lookup_commit_reference(the_repository, &oid)) {
+		cgit_print_error_page(400, "Bad request",
+				"Not a commit reference: %s", hex);
+		return 1;
+	}
+	ctx.page.etag = oid_to_hex(&oid);
+	ctx.page.mimetype = xstrdup(format->mimetype);
+	ctx.page.filename = xstrdup(filename);
+	cgit_print_http_headers();
+	init_archivers();
+	format->write_func(hex, prefix);
+	return 0;
+}
+
+static int write_sig(const struct cgit_snapshot_format *format,
+		     const char *hex, const char *archive,
+		     const char *filename)
+{
+	const struct object_id *note = cgit_snapshot_get_sig(hex, format);
+	enum object_type type;
+	unsigned long size;
+	char *buf;
+
+	if (!note) {
+		cgit_print_error_page(404, "Not found",
+				"No signature for %s", archive);
+		return 0;
+	}
+
+	buf = read_object_file(note, &type, &size);
+	if (!buf) {
+		cgit_print_error_page(404, "Not found", "Not found");
+		return 0;
+	}
+
+	html("X-Content-Type-Options: nosniff\n");
+	html("Content-Security-Policy: default-src 'none'\n");
+	ctx.page.etag = oid_to_hex(note);
+	ctx.page.mimetype = xstrdup("application/pgp-signature");
+	ctx.page.filename = xstrdup(filename);
+	cgit_print_http_headers();
+
+	html_raw(buf, size);
+	free(buf);
+	return 0;
+}
+
+/* Try to guess the requested revision from the requested snapshot name.
+ * First the format extension is stripped, e.g. "cgit-0.7.2.tar.gz" become
+ * "cgit-0.7.2". If this is a valid commit object name we've got a winner.
+ * Otherwise, if the snapshot name has a prefix matching the result from
+ * repo_basename(), we strip the basename and any following '-' and '_'
+ * characters ("cgit-0.7.2" -> "0.7.2") and check the resulting name once
+ * more. If this still isn't a valid commit object name, we check if pre-
+ * pending a 'v' or a 'V' to the remaining snapshot name ("0.7.2" ->
+ * "v0.7.2") gives us something valid.
+ */
+static const char *get_ref_from_filename(const struct cgit_repo *repo,
+					 const char *filename,
+					 const struct cgit_snapshot_format *format)
+{
+	const char *reponame;
+	struct object_id oid;
+	struct strbuf snapshot = STRBUF_INIT;
+	int result = 1;
+
+	strbuf_addstr(&snapshot, filename);
+	strbuf_setlen(&snapshot, snapshot.len - strlen(format->suffix));
+
+	if (get_oid(snapshot.buf, &oid) == 0)
+		goto out;
+
+	reponame = cgit_snapshot_prefix(repo);
+	if (starts_with(snapshot.buf, reponame)) {
+		const char *new_start = snapshot.buf;
+		new_start += strlen(reponame);
+		while (new_start && (*new_start == '-' || *new_start == '_'))
+			new_start++;
+		strbuf_splice(&snapshot, 0, new_start - snapshot.buf, "", 0);
+	}
+
+	if (get_oid(snapshot.buf, &oid) == 0)
+		goto out;
+
+	strbuf_insert(&snapshot, 0, "v", 1);
+	if (get_oid(snapshot.buf, &oid) == 0)
+		goto out;
+
+	strbuf_splice(&snapshot, 0, 1, "V", 1);
+	if (get_oid(snapshot.buf, &oid) == 0)
+		goto out;
+
+	result = 0;
+	strbuf_release(&snapshot);
+
+out:
+	return result ? strbuf_detach(&snapshot, NULL) : NULL;
+}
+
+void cgit_print_snapshot(const char *head, const char *hex,
+			 const char *filename, int dwim)
+{
+	const struct cgit_snapshot_format* f;
+	const char *sig_filename = NULL;
+	char *adj_filename = NULL;
+	char *prefix = NULL;
+
+	if (!filename) {
+		cgit_print_error_page(400, "Bad request",
+				"No snapshot name specified");
+		return;
+	}
+
+	if (ends_with(filename, ".asc")) {
+		sig_filename = filename;
+
+		/* Strip ".asc" from filename for common format processing */
+		adj_filename = xstrdup(filename);
+		adj_filename[strlen(adj_filename) - 4] = '\0';
+		filename = adj_filename;
+	}
+
+	f = get_format(filename);
+	if (!f || (!sig_filename && !(ctx.repo->snapshots & cgit_snapshot_format_bit(f)))) {
+		cgit_print_error_page(400, "Bad request",
+				"Unsupported snapshot format: %s", filename);
+		return;
+	}
+
+	if (!hex && dwim) {
+		hex = get_ref_from_filename(ctx.repo, filename, f);
+		if (hex == NULL) {
+			cgit_print_error_page(404, "Not found", "Not found");
+			return;
+		}
+		prefix = xstrdup(filename);
+		prefix[strlen(filename) - strlen(f->suffix)] = '\0';
+	}
+
+	if (!hex)
+		hex = head;
+
+	if (!prefix)
+		prefix = xstrdup(cgit_snapshot_prefix(ctx.repo));
+
+	if (sig_filename)
+		write_sig(f, hex, filename, sig_filename);
+	else
+		make_snapshot(f, hex, prefix, filename);
+
+	free(prefix);
+	free(adj_filename);
+}
diff --git a/www/git.causal.agency/cgit/ui-snapshot.h b/www/git.causal.agency/cgit/ui-snapshot.h
new file mode 100644
index 00000000..a8deec36
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-snapshot.h
@@ -0,0 +1,7 @@
+#ifndef UI_SNAPSHOT_H
+#define UI_SNAPSHOT_H
+
+extern void cgit_print_snapshot(const char *head, const char *hex,
+				const char *filename, int dwim);
+
+#endif /* UI_SNAPSHOT_H */
diff --git a/www/git.causal.agency/cgit/ui-ssdiff.c b/www/git.causal.agency/cgit/ui-ssdiff.c
new file mode 100644
index 00000000..af8bc9e0
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-ssdiff.c
@@ -0,0 +1,420 @@
+#include "cgit.h"
+#include "ui-ssdiff.h"
+#include "html.h"
+#include "ui-shared.h"
+#include "ui-diff.h"
+
+extern int use_ssdiff;
+
+static int current_old_line, current_new_line;
+static int **L = NULL;
+
+struct deferred_lines {
+	int line_no;
+	char *line;
+	struct deferred_lines *next;
+};
+
+static struct deferred_lines *deferred_old, *deferred_old_last;
+static struct deferred_lines *deferred_new, *deferred_new_last;
+
+static void create_or_reset_lcs_table(void)
+{
+	int i;
+
+	if (L != NULL) {
+		memset(*L, 0, sizeof(int) * MAX_SSDIFF_SIZE);
+		return;
+	}
+
+	// xcalloc will die if we ran out of memory;
+	// not very helpful for debugging
+	L = (int**)xcalloc(MAX_SSDIFF_M, sizeof(int *));
+	*L = (int*)xcalloc(MAX_SSDIFF_SIZE, sizeof(int));
+
+	for (i = 1; i < MAX_SSDIFF_M; i++) {
+		L[i] = *L + i * MAX_SSDIFF_N;
+	}
+}
+
+static char *longest_common_subsequence(char *A, char *B)
+{
+	int i, j, ri;
+	int m = strlen(A);
+	int n = strlen(B);
+	int tmp1, tmp2;
+	int lcs_length;
+	char *result;
+
+	// We bail if the lines are too long
+	if (m >= MAX_SSDIFF_M || n >= MAX_SSDIFF_N)
+		return NULL;
+
+	create_or_reset_lcs_table();
+
+	for (i = m; i >= 0; i--) {
+		for (j = n; j >= 0; j--) {
+			if (A[i] == '\0' || B[j] == '\0') {
+				L[i][j] = 0;
+			} else if (A[i] == B[j]) {
+				L[i][j] = 1 + L[i + 1][j + 1];
+			} else {
+				tmp1 = L[i + 1][j];
+				tmp2 = L[i][j + 1];
+				L[i][j] = (tmp1 > tmp2 ? tmp1 : tmp2);
+			}
+		}
+	}
+
+	lcs_length = L[0][0];
+	result = xmalloc(lcs_length + 2);
+	memset(result, 0, sizeof(*result) * (lcs_length + 2));
+
+	ri = 0;
+	i = 0;
+	j = 0;
+	while (i < m && j < n) {
+		if (A[i] == B[j]) {
+			result[ri] = A[i];
+			ri += 1;
+			i += 1;
+			j += 1;
+		} else if (L[i + 1][j] >= L[i][j + 1]) {
+			i += 1;
+		} else {
+			j += 1;
+		}
+	}
+
+	return result;
+}
+
+static int line_from_hunk(char *line, char type)
+{
+	char *buf1, *buf2;
+	int len, res;
+
+	buf1 = strchr(line, type);
+	if (buf1 == NULL)
+		return 0;
+	buf1 += 1;
+	buf2 = strchr(buf1, ',');
+	if (buf2 == NULL)
+		return 0;
+	len = buf2 - buf1;
+	buf2 = xmalloc(len + 1);
+	strlcpy(buf2, buf1, len + 1);
+	res = atoi(buf2);
+	free(buf2);
+	return res;
+}
+
+static char *replace_tabs(char *line)
+{
+	char *prev_buf = line;
+	char *cur_buf;
+	size_t linelen = strlen(line);
+	int n_tabs = 0;
+	int i;
+	char *result;
+	size_t result_len;
+
+	if (linelen == 0) {
+		result = xmalloc(1);
+		result[0] = '\0';
+		return result;
+	}
+
+	for (i = 0; i < linelen; i++) {
+		if (line[i] == '\t')
+			n_tabs += 1;
+	}
+	result_len = linelen + n_tabs * 8;
+	result = xmalloc(result_len + 1);
+	result[0] = '\0';
+
+	for (;;) {
+		cur_buf = strchr(prev_buf, '\t');
+		if (!cur_buf) {
+			linelen = strlen(result);
+			strlcpy(&result[linelen], prev_buf, result_len - linelen + 1);
+			break;
+		} else {
+			linelen = strlen(result);
+			strlcpy(&result[linelen], prev_buf, cur_buf - prev_buf + 1);
+			linelen = strlen(result);
+			memset(&result[linelen], ' ', 8 - (linelen % 8));
+			result[linelen + 8 - (linelen % 8)] = '\0';
+		}
+		prev_buf = cur_buf + 1;
+	}
+	return result;
+}
+
+static int calc_deferred_lines(struct deferred_lines *start)
+{
+	struct deferred_lines *item = start;
+	int result = 0;
+	while (item) {
+		result += 1;
+		item = item->next;
+	}
+	return result;
+}
+
+static void deferred_old_add(char *line, int line_no)
+{
+	struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
+	item->line = xstrdup(line);
+	item->line_no = line_no;
+	item->next = NULL;
+	if (deferred_old) {
+		deferred_old_last->next = item;
+		deferred_old_last = item;
+	} else {
+		deferred_old = deferred_old_last = item;
+	}
+}
+
+static void deferred_new_add(char *line, int line_no)
+{
+	struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
+	item->line = xstrdup(line);
+	item->line_no = line_no;
+	item->next = NULL;
+	if (deferred_new) {
+		deferred_new_last->next = item;
+		deferred_new_last = item;
+	} else {
+		deferred_new = deferred_new_last = item;
+	}
+}
+
+static void print_part_with_lcs(char *class, char *line, char *lcs)
+{
+	int line_len = strlen(line);
+	int i, j;
+	char c[2] = " ";
+	int same = 1;
+
+	j = 0;
+	for (i = 0; i < line_len; i++) {
+		c[0] = line[i];
+		if (same) {
+			if (line[i] == lcs[j])
+				j += 1;
+			else {
+				same = 0;
+				htmlf("<span class='%s'>", class);
+			}
+		} else if (line[i] == lcs[j]) {
+			same = 1;
+			html("</span>");
+			j += 1;
+		}
+		html_txt(c);
+	}
+	if (!same)
+		html("</span>");
+}
+
+static void print_ssdiff_line(char *class,
+			      int old_line_no,
+			      char *old_line,
+			      int new_line_no,
+			      char *new_line, int individual_chars)
+{
+	char *lcs = NULL;
+
+	if (old_line)
+		old_line = replace_tabs(old_line + 1);
+	if (new_line)
+		new_line = replace_tabs(new_line + 1);
+	if (individual_chars && old_line && new_line)
+		lcs = longest_common_subsequence(old_line, new_line);
+	html("<tr>\n");
+	if (old_line_no > 0) {
+		struct diff_filespec *old_file = cgit_get_current_old_file();
+		char *lineno_str = fmt("n%d", old_line_no);
+		char *id_str = fmt("id=%s#%s", is_null_oid(&old_file->oid)?"HEAD":oid_to_hex(old_rev_oid), lineno_str);
+		char *fileurl = cgit_fileurl(ctx.repo->url, "tree", old_file->path, id_str);
+		html("<td class='lineno'><a href='");
+		html(fileurl);
+		htmlf("'>%s</a>", lineno_str + 1);
+		html("</td>");
+		htmlf("<td class='%s'>", class);
+		free(fileurl);
+	} else if (old_line)
+		htmlf("<td class='lineno'></td><td class='%s'>", class);
+	else
+		htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
+	if (old_line) {
+		if (lcs)
+			print_part_with_lcs("del", old_line, lcs);
+		else
+			html_txt(old_line);
+	}
+
+	html("</td>\n");
+	if (new_line_no > 0) {
+		struct diff_filespec *new_file = cgit_get_current_new_file();
+		char *lineno_str = fmt("n%d", new_line_no);
+		char *id_str = fmt("id=%s#%s", is_null_oid(&new_file->oid)?"HEAD":oid_to_hex(new_rev_oid), lineno_str);
+		char *fileurl = cgit_fileurl(ctx.repo->url, "tree", new_file->path, id_str);
+		html("<td class='lineno'><a href='");
+		html(fileurl);
+		htmlf("'>%s</a>", lineno_str + 1);
+		html("</td>");
+		htmlf("<td class='%s'>", class);
+		free(fileurl);
+	} else if (new_line)
+		htmlf("<td class='lineno'></td><td class='%s'>", class);
+	else
+		htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
+	if (new_line) {
+		if (lcs)
+			print_part_with_lcs("add", new_line, lcs);
+		else
+			html_txt(new_line);
+	}
+
+	html("</td></tr>");
+	if (lcs)
+		free(lcs);
+	if (new_line)
+		free(new_line);
+	if (old_line)
+		free(old_line);
+}
+
+static void print_deferred_old_lines(void)
+{
+	struct deferred_lines *iter_old, *tmp;
+	iter_old = deferred_old;
+	while (iter_old) {
+		print_ssdiff_line("del", iter_old->line_no,
+				  iter_old->line, -1, NULL, 0);
+		tmp = iter_old->next;
+		free(iter_old);
+		iter_old = tmp;
+	}
+}
+
+static void print_deferred_new_lines(void)
+{
+	struct deferred_lines *iter_new, *tmp;
+	iter_new = deferred_new;
+	while (iter_new) {
+		print_ssdiff_line("add", -1, NULL,
+				  iter_new->line_no, iter_new->line, 0);
+		tmp = iter_new->next;
+		free(iter_new);
+		iter_new = tmp;
+	}
+}
+
+static void print_deferred_changed_lines(void)
+{
+	struct deferred_lines *iter_old, *iter_new, *tmp;
+	int n_old_lines = calc_deferred_lines(deferred_old);
+	int n_new_lines = calc_deferred_lines(deferred_new);
+	int individual_chars = (n_old_lines == n_new_lines ? 1 : 0);
+
+	iter_old = deferred_old;
+	iter_new = deferred_new;
+	while (iter_old || iter_new) {
+		if (iter_old && iter_new)
+			print_ssdiff_line("changed", iter_old->line_no,
+					  iter_old->line,
+					  iter_new->line_no, iter_new->line,
+					  individual_chars);
+		else if (iter_old)
+			print_ssdiff_line("changed", iter_old->line_no,
+					  iter_old->line, -1, NULL, 0);
+		else if (iter_new)
+			print_ssdiff_line("changed", -1, NULL,
+					  iter_new->line_no, iter_new->line, 0);
+		if (iter_old) {
+			tmp = iter_old->next;
+			free(iter_old);
+			iter_old = tmp;
+		}
+
+		if (iter_new) {
+			tmp = iter_new->next;
+			free(iter_new);
+			iter_new = tmp;
+		}
+	}
+}
+
+void cgit_ssdiff_print_deferred_lines(void)
+{
+	if (!deferred_old && !deferred_new)
+		return;
+	if (deferred_old && !deferred_new)
+		print_deferred_old_lines();
+	else if (!deferred_old && deferred_new)
+		print_deferred_new_lines();
+	else
+		print_deferred_changed_lines();
+	deferred_old = deferred_old_last = NULL;
+	deferred_new = deferred_new_last = NULL;
+}
+
+/*
+ * print a single line returned from xdiff
+ */
+void cgit_ssdiff_line_cb(char *line, int len)
+{
+	char c = line[len - 1];
+	line[len - 1] = '\0';
+	if (line[0] == '@') {
+		current_old_line = line_from_hunk(line, '-');
+		current_new_line = line_from_hunk(line, '+');
+	}
+
+	if (line[0] == ' ') {
+		if (deferred_old || deferred_new)
+			cgit_ssdiff_print_deferred_lines();
+		print_ssdiff_line("ctx", current_old_line, line,
+				  current_new_line, line, 0);
+		current_old_line += 1;
+		current_new_line += 1;
+	} else if (line[0] == '+') {
+		deferred_new_add(line, current_new_line);
+		current_new_line += 1;
+	} else if (line[0] == '-') {
+		deferred_old_add(line, current_old_line);
+		current_old_line += 1;
+	} else if (line[0] == '@') {
+		html("<tr><td colspan='4' class='hunk'>");
+		html_txt(line);
+		html("</td></tr>");
+	} else {
+		html("<tr><td colspan='4' class='ctx'>");
+		html_txt(line);
+		html("</td></tr>");
+	}
+	line[len - 1] = c;
+}
+
+void cgit_ssdiff_header_begin(void)
+{
+	current_old_line = -1;
+	current_new_line = -1;
+	html("<tr><td class='space' colspan='4'><div></div></td></tr>");
+	html("<tr><td class='head' colspan='4'>");
+}
+
+void cgit_ssdiff_header_end(void)
+{
+	html("</td></tr>");
+}
+
+void cgit_ssdiff_footer(void)
+{
+	if (deferred_old || deferred_new)
+		cgit_ssdiff_print_deferred_lines();
+	html("<tr><td class='foot' colspan='4'></td></tr>");
+}
diff --git a/www/git.causal.agency/cgit/ui-ssdiff.h b/www/git.causal.agency/cgit/ui-ssdiff.h
new file mode 100644
index 00000000..11f27144
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-ssdiff.h
@@ -0,0 +1,25 @@
+#ifndef UI_SSDIFF_H
+#define UI_SSDIFF_H
+
+/*
+ * ssdiff line limits
+ */
+#ifndef MAX_SSDIFF_M
+#define MAX_SSDIFF_M 128
+#endif
+
+#ifndef MAX_SSDIFF_N
+#define MAX_SSDIFF_N 128
+#endif
+#define MAX_SSDIFF_SIZE ((MAX_SSDIFF_M) * (MAX_SSDIFF_N))
+
+extern void cgit_ssdiff_print_deferred_lines(void);
+
+extern void cgit_ssdiff_line_cb(char *line, int len);
+
+extern void cgit_ssdiff_header_begin(void);
+extern void cgit_ssdiff_header_end(void);
+
+extern void cgit_ssdiff_footer(void);
+
+#endif /* UI_SSDIFF_H */
diff --git a/www/git.causal.agency/cgit/ui-stats.c b/www/git.causal.agency/cgit/ui-stats.c
new file mode 100644
index 00000000..7272a61a
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-stats.c
@@ -0,0 +1,426 @@
+#include "cgit.h"
+#include "ui-stats.h"
+#include "html.h"
+#include "ui-shared.h"
+
+struct authorstat {
+	long total;
+	struct string_list list;
+};
+
+#define DAY_SECS (60 * 60 * 24)
+#define WEEK_SECS (DAY_SECS * 7)
+
+static void trunc_week(struct tm *tm)
+{
+	time_t t = timegm(tm);
+	t -= ((tm->tm_wday + 6) % 7) * DAY_SECS;
+	gmtime_r(&t, tm);
+}
+
+static void dec_week(struct tm *tm)
+{
+	time_t t = timegm(tm);
+	t -= WEEK_SECS;
+	gmtime_r(&t, tm);
+}
+
+static void inc_week(struct tm *tm)
+{
+	time_t t = timegm(tm);
+	t += WEEK_SECS;
+	gmtime_r(&t, tm);
+}
+
+static char *pretty_week(struct tm *tm)
+{
+	static char buf[10];
+
+	strftime(buf, sizeof(buf), "W%V %G", tm);
+	return buf;
+}
+
+static void trunc_month(struct tm *tm)
+{
+	tm->tm_mday = 1;
+}
+
+static void dec_month(struct tm *tm)
+{
+	tm->tm_mon--;
+	if (tm->tm_mon < 0) {
+		tm->tm_year--;
+		tm->tm_mon = 11;
+	}
+}
+
+static void inc_month(struct tm *tm)
+{
+	tm->tm_mon++;
+	if (tm->tm_mon > 11) {
+		tm->tm_year++;
+		tm->tm_mon = 0;
+	}
+}
+
+static char *pretty_month(struct tm *tm)
+{
+	static const char *months[] = {
+		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+	};
+	return fmt("%s %d", months[tm->tm_mon], tm->tm_year + 1900);
+}
+
+static void trunc_quarter(struct tm *tm)
+{
+	trunc_month(tm);
+	while (tm->tm_mon % 3 != 0)
+		dec_month(tm);
+}
+
+static void dec_quarter(struct tm *tm)
+{
+	dec_month(tm);
+	dec_month(tm);
+	dec_month(tm);
+}
+
+static void inc_quarter(struct tm *tm)
+{
+	inc_month(tm);
+	inc_month(tm);
+	inc_month(tm);
+}
+
+static char *pretty_quarter(struct tm *tm)
+{
+	return fmt("Q%d %d", tm->tm_mon / 3 + 1, tm->tm_year + 1900);
+}
+
+static void trunc_year(struct tm *tm)
+{
+	trunc_month(tm);
+	tm->tm_mon = 0;
+}
+
+static void dec_year(struct tm *tm)
+{
+	tm->tm_year--;
+}
+
+static void inc_year(struct tm *tm)
+{
+	tm->tm_year++;
+}
+
+static char *pretty_year(struct tm *tm)
+{
+	return fmt("%d", tm->tm_year + 1900);
+}
+
+static const struct cgit_period periods[] = {
+	{'w', "week", 12, 4, trunc_week, dec_week, inc_week, pretty_week},
+	{'m', "month", 12, 4, trunc_month, dec_month, inc_month, pretty_month},
+	{'q', "quarter", 12, 4, trunc_quarter, dec_quarter, inc_quarter, pretty_quarter},
+	{'y', "year", 12, 4, trunc_year, dec_year, inc_year, pretty_year},
+};
+
+/* Given a period code or name, return a period index (1, 2, 3 or 4)
+ * and update the period pointer to the correcsponding struct.
+ * If no matching code is found, return 0.
+ */
+int cgit_find_stats_period(const char *expr, const struct cgit_period **period)
+{
+	int i;
+	char code = '\0';
+
+	if (!expr)
+		return 0;
+
+	if (strlen(expr) == 1)
+		code = expr[0];
+
+	for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++)
+		if (periods[i].code == code || !strcmp(periods[i].name, expr)) {
+			if (period)
+				*period = &periods[i];
+			return i + 1;
+		}
+	return 0;
+}
+
+const char *cgit_find_stats_periodname(int idx)
+{
+	if (idx > 0 && idx < 4)
+		return periods[idx - 1].name;
+	else
+		return "";
+}
+
+static void add_commit(struct string_list *authors, struct commit *commit,
+	const struct cgit_period *period)
+{
+	struct commitinfo *info;
+	struct string_list_item *author, *item;
+	struct authorstat *authorstat;
+	struct string_list *items;
+	char *tmp;
+	struct tm *date;
+	time_t t;
+	uintptr_t *counter;
+
+	info = cgit_parse_commit(commit);
+	tmp = xstrdup(info->author);
+	author = string_list_insert(authors, tmp);
+	if (!author->util)
+		author->util = xcalloc(1, sizeof(struct authorstat));
+	else
+		free(tmp);
+	authorstat = author->util;
+	items = &authorstat->list;
+	t = info->committer_date;
+	date = gmtime(&t);
+	period->trunc(date);
+	tmp = xstrdup(period->pretty(date));
+	item = string_list_insert(items, tmp);
+	counter = (uintptr_t *)&item->util;
+	if (*counter)
+		free(tmp);
+	(*counter)++;
+
+	authorstat->total++;
+	cgit_free_commitinfo(info);
+}
+
+static int cmp_total_commits(const void *a1, const void *a2)
+{
+	const struct string_list_item *i1 = a1;
+	const struct string_list_item *i2 = a2;
+	const struct authorstat *auth1 = i1->util;
+	const struct authorstat *auth2 = i2->util;
+
+	return auth2->total - auth1->total;
+}
+
+/* Walk the commit DAG and collect number of commits per author per
+ * timeperiod into a nested string_list collection.
+ */
+static struct string_list collect_stats(const struct cgit_period *period)
+{
+	struct string_list authors;
+	struct rev_info rev;
+	struct commit *commit;
+	const char *argv[] = {NULL, ctx.qry.head, NULL, NULL, NULL, NULL};
+	int argc = 3;
+	time_t now;
+	long i;
+	struct tm *tm;
+	char tmp[11];
+
+	time(&now);
+	tm = gmtime(&now);
+	period->trunc(tm);
+	for (i = 1; i < period->count; i++)
+		period->dec(tm);
+	strftime(tmp, sizeof(tmp), "%Y-%m-%d", tm);
+	argv[2] = xstrdup(fmt("--since=%s", tmp));
+	if (ctx.qry.path) {
+		argv[3] = "--";
+		argv[4] = ctx.qry.path;
+		argc += 2;
+	}
+	init_revisions(&rev, NULL);
+	rev.abbrev = DEFAULT_ABBREV;
+	rev.commit_format = CMIT_FMT_DEFAULT;
+	rev.max_parents = 1;
+	rev.verbose_header = 1;
+	rev.show_root_diff = 0;
+	setup_revisions(argc, argv, &rev, NULL);
+	prepare_revision_walk(&rev);
+	memset(&authors, 0, sizeof(authors));
+	while ((commit = get_revision(&rev)) != NULL) {
+		add_commit(&authors, commit, period);
+		free_commit_buffer(the_repository->parsed_objects, commit);
+		free_commit_list(commit->parents);
+		commit->parents = NULL;
+	}
+	return authors;
+}
+
+static void print_combined_authorrow(struct string_list *authors, int from,
+				     int to, const char *name,
+				     const char *leftclass,
+				     const char *centerclass,
+				     const char *rightclass,
+				     const struct cgit_period *period)
+{
+	struct string_list_item *author;
+	struct authorstat *authorstat;
+	struct string_list *items;
+	struct string_list_item *date;
+	time_t now;
+	long i, j, total, subtotal;
+	struct tm *tm;
+	char *tmp;
+
+	time(&now);
+	tm = gmtime(&now);
+	period->trunc(tm);
+	for (i = 1; i < period->count; i++)
+		period->dec(tm);
+
+	total = 0;
+	htmlf("<tr><td class='%s'>%s</td>", leftclass,
+		fmt(name, to - from + 1));
+	for (j = 0; j < period->count; j++) {
+		tmp = period->pretty(tm);
+		period->inc(tm);
+		subtotal = 0;
+		for (i = from; i <= to; i++) {
+			author = &authors->items[i];
+			authorstat = author->util;
+			items = &authorstat->list;
+			date = string_list_lookup(items, tmp);
+			if (date)
+				subtotal += (uintptr_t)date->util;
+		}
+		htmlf("<td class='%s'>%ld</td>", centerclass, subtotal);
+		total += subtotal;
+	}
+	htmlf("<td class='%s'>%ld</td></tr>", rightclass, total);
+}
+
+static void print_authors(struct string_list *authors, int top,
+			  const struct cgit_period *period)
+{
+	struct string_list_item *author;
+	struct authorstat *authorstat;
+	struct string_list *items;
+	struct string_list_item *date;
+	time_t now;
+	long i, j, total;
+	struct tm *tm;
+	char *tmp;
+
+	time(&now);
+	tm = gmtime(&now);
+	period->trunc(tm);
+	for (i = 1; i < period->count; i++)
+		period->dec(tm);
+
+	html("<table class='stats'><tr><th>Author</th>");
+	for (j = 0; j < period->count; j++) {
+		tmp = period->pretty(tm);
+		htmlf("<th>%s</th>", tmp);
+		period->inc(tm);
+	}
+	html("<th>Total</th></tr>\n");
+
+	if (top <= 0 || top > authors->nr)
+		top = authors->nr;
+
+	for (i = 0; i < top; i++) {
+		author = &authors->items[i];
+		html("<tr><td class='left'>");
+		html_txt(author->string);
+		html("</td>");
+		authorstat = author->util;
+		items = &authorstat->list;
+		total = 0;
+		for (j = 0; j < period->count; j++)
+			period->dec(tm);
+		for (j = 0; j < period->count; j++) {
+			tmp = period->pretty(tm);
+			period->inc(tm);
+			date = string_list_lookup(items, tmp);
+			if (!date)
+				html("<td>0</td>");
+			else {
+				htmlf("<td>%lu</td>", (uintptr_t)date->util);
+				total += (uintptr_t)date->util;
+			}
+		}
+		htmlf("<td class='sum'>%ld</td></tr>", total);
+	}
+
+	if (top < authors->nr)
+		print_combined_authorrow(authors, top, authors->nr - 1,
+			"Others (%ld)", "left", "", "sum", period);
+
+	print_combined_authorrow(authors, 0, authors->nr - 1, "Total",
+		"total", "sum", "sum", period);
+	html("</table>");
+}
+
+/* Create a sorted string_list with one entry per author. The util-field
+ * for each author is another string_list which is used to calculate the
+ * number of commits per time-interval.
+ */
+void cgit_show_stats(void)
+{
+	struct string_list authors;
+	const struct cgit_period *period;
+	int top, i;
+	const char *code = "w";
+
+	if (ctx.qry.period)
+		code = ctx.qry.period;
+
+	i = cgit_find_stats_period(code, &period);
+	if (!i) {
+		cgit_print_error_page(404, "Not found",
+			"Unknown statistics type: %c", code[0]);
+		return;
+	}
+	if (i > ctx.repo->max_stats) {
+		cgit_print_error_page(400, "Bad request",
+			"Statistics type disabled: %s", period->name);
+		return;
+	}
+	authors = collect_stats(period);
+	qsort(authors.items, authors.nr, sizeof(struct string_list_item),
+		cmp_total_commits);
+
+	top = ctx.qry.ofs;
+	if (!top)
+		top = 10;
+
+	cgit_print_layout_start();
+	html("<div class='cgit-panel'>");
+	html("<b>stat options</b>");
+	html("<form method='get'>");
+	cgit_add_hidden_formfields(1, 0, "stats");
+	html("<table><tr><td colspan='2'/></tr>");
+	if (ctx.repo->max_stats > 1) {
+		html("<tr><td class='label'>Period:</td>");
+		html("<td class='ctrl'><select name='period' onchange='this.form.submit();'>");
+		for (i = 0; i < ctx.repo->max_stats; i++)
+			html_option(fmt("%c", periods[i].code),
+				    periods[i].name, fmt("%c", period->code));
+		html("</select></td></tr>");
+	}
+	html("<tr><td class='label'>Authors:</td>");
+	html("<td class='ctrl'><select name='ofs' onchange='this.form.submit();'>");
+	html_intoption(10, "10", top);
+	html_intoption(25, "25", top);
+	html_intoption(50, "50", top);
+	html_intoption(100, "100", top);
+	html_intoption(-1, "all", top);
+	html("</select></td></tr>");
+	html("<tr><td/><td class='ctrl'>");
+	html("<noscript><input type='submit' value='Reload'/></noscript>");
+	html("</td></tr></table>");
+	html("</form>");
+	html("</div>");
+	htmlf("<h2>Commits per author per %s", period->name);
+	if (ctx.qry.path) {
+		html(" (path '");
+		html_txt(ctx.qry.path);
+		html("')");
+	}
+	html("</h2>");
+	print_authors(&authors, top, period);
+	cgit_print_layout_end();
+}
+
diff --git a/www/git.causal.agency/cgit/ui-stats.h b/www/git.causal.agency/cgit/ui-stats.h
new file mode 100644
index 00000000..0e61b03d
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-stats.h
@@ -0,0 +1,28 @@
+#ifndef UI_STATS_H
+#define UI_STATS_H
+
+#include "cgit.h"
+
+struct cgit_period {
+	const char code;
+	const char *name;
+	int max_periods;
+	int count;
+
+	/* Convert a tm value to the first day in the period */
+	void (*trunc)(struct tm *tm);
+
+	/* Update tm value to start of next/previous period */
+	void (*dec)(struct tm *tm);
+	void (*inc)(struct tm *tm);
+
+	/* Pretty-print a tm value */
+	char *(*pretty)(struct tm *tm);
+};
+
+extern int cgit_find_stats_period(const char *expr, const struct cgit_period **period);
+extern const char *cgit_find_stats_periodname(int idx);
+
+extern void cgit_show_stats(void);
+
+#endif /* UI_STATS_H */
diff --git a/www/git.causal.agency/cgit/ui-summary.c b/www/git.causal.agency/cgit/ui-summary.c
new file mode 100644
index 00000000..947812a8
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-summary.c
@@ -0,0 +1,148 @@
+/* ui-summary.c: functions for generating repo summary page
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-summary.h"
+#include "html.h"
+#include "ui-blob.h"
+#include "ui-log.h"
+#include "ui-plain.h"
+#include "ui-refs.h"
+#include "ui-shared.h"
+
+static int urls;
+
+static void print_url(const char *url)
+{
+	int columns = 3;
+
+	if (ctx.repo->enable_log_filecount)
+		columns++;
+	if (ctx.repo->enable_log_linecount)
+		columns++;
+
+	if (urls++ == 0) {
+		htmlf("<tr class='nohover'><td colspan='%d'>&nbsp;</td></tr>", columns);
+		htmlf("<tr class='nohover'><th class='left' colspan='%d'>Clone</th></tr>\n", columns);
+	}
+
+	htmlf("<tr><td colspan='%d'><a rel='vcs-git' href='", columns);
+	html_url_path(url);
+	html("' title='");
+	html_attr(ctx.repo->name);
+	html(" Git repository'>");
+	html_txt(url);
+	html("</a></td></tr>\n");
+}
+
+void cgit_print_summary(void)
+{
+	int columns = 3;
+
+	if (ctx.repo->enable_log_filecount)
+		columns++;
+	if (ctx.repo->enable_log_linecount)
+		columns++;
+
+	cgit_print_layout_start();
+	html("<table summary='repository info' class='list nowrap'>");
+	cgit_print_branches(ctx.cfg.summary_branches);
+	htmlf("<tr class='nohover'><td colspan='%d'>&nbsp;</td></tr>", columns);
+	cgit_print_tags(ctx.cfg.summary_tags);
+	if (ctx.cfg.summary_log > 0) {
+		htmlf("<tr class='nohover'><td colspan='%d'>&nbsp;</td></tr>", columns);
+		cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL,
+			       NULL, NULL, 0, 0, 0);
+	}
+	urls = 0;
+	cgit_add_clone_urls(print_url);
+	html("</table>");
+	cgit_print_layout_end();
+}
+
+/* The caller must free the return value. */
+static char* append_readme_path(const char *filename, const char *ref, const char *path)
+{
+	char *file, *base_dir, *full_path, *resolved_base = NULL, *resolved_full = NULL;
+	/* If a subpath is specified for the about page, make it relative
+	 * to the directory containing the configured readme. */
+
+	file = xstrdup(filename);
+	base_dir = dirname(file);
+	if (!strcmp(base_dir, ".") || !strcmp(base_dir, "..")) {
+		if (!ref) {
+			free(file);
+			return NULL;
+		}
+		full_path = xstrdup(path);
+	} else
+		full_path = fmtalloc("%s/%s", base_dir, path);
+
+	if (!ref) {
+		resolved_base = realpath(base_dir, NULL);
+		resolved_full = realpath(full_path, NULL);
+		if (!resolved_base || !resolved_full || !starts_with(resolved_full, resolved_base)) {
+			free(full_path);
+			full_path = NULL;
+		}
+	}
+
+	free(file);
+	free(resolved_base);
+	free(resolved_full);
+
+	return full_path;
+}
+
+void cgit_print_repo_readme(const char *path)
+{
+	char *filename, *ref, *mimetype;
+	int free_filename = 0;
+
+	mimetype = get_mimetype_for_filename(path);
+	if (mimetype && (!strncmp(mimetype, "image/", 6) || !strncmp(mimetype, "video/", 6))) {
+		ctx.page.mimetype = mimetype;
+		ctx.page.charset = NULL;
+		cgit_print_plain();
+		free(mimetype);
+		return;
+	}
+	free(mimetype);
+
+	cgit_print_layout_start();
+	if (ctx.repo->readme.nr == 0)
+		goto done;
+
+	filename = ctx.repo->readme.items[0].string;
+	ref = ctx.repo->readme.items[0].util;
+
+	if (path) {
+		free_filename = 1;
+		filename = append_readme_path(filename, ref, path);
+		if (!filename)
+			goto done;
+	}
+
+	/* Print the calculated readme, either from the git repo or from the
+	 * filesystem, while applying the about-filter.
+	 */
+	html("<div id='summary'>");
+	cgit_open_filter(ctx.repo->about_filter, filename);
+	if (ref)
+		cgit_print_file(filename, ref, 1);
+	else
+		html_include(filename);
+	cgit_close_filter(ctx.repo->about_filter);
+
+	html("</div>");
+	if (free_filename)
+		free(filename);
+
+done:
+	cgit_print_layout_end();
+}
diff --git a/www/git.causal.agency/cgit/ui-summary.h b/www/git.causal.agency/cgit/ui-summary.h
new file mode 100644
index 00000000..cba696af
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-summary.h
@@ -0,0 +1,7 @@
+#ifndef UI_SUMMARY_H
+#define UI_SUMMARY_H
+
+extern void cgit_print_summary(void);
+extern void cgit_print_repo_readme(const char *path);
+
+#endif /* UI_SUMMARY_H */
diff --git a/www/git.causal.agency/cgit/ui-tag.c b/www/git.causal.agency/cgit/ui-tag.c
new file mode 100644
index 00000000..846d5b14
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-tag.c
@@ -0,0 +1,120 @@
+/* ui-tag.c: display a tag
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-tag.h"
+#include "html.h"
+#include "ui-shared.h"
+
+static void print_tag_content(char *buf)
+{
+	char *p;
+
+	if (!buf)
+		return;
+
+	html("<div class='commit-subject'>");
+	p = strchr(buf, '\n');
+	if (p)
+		*p = '\0';
+	html_txt(buf);
+	html("</div>");
+	if (p) {
+		html("<div class='commit-msg'>");
+		html_txt(++p);
+		html("</div>");
+	}
+}
+
+static void print_download_links(char *revname)
+{
+	html("<tr><th>download</th><td class='sha1'>");
+	cgit_print_snapshot_links(ctx.repo, revname, "<br/>");
+	html("</td></tr>");
+}
+
+void cgit_print_tag(char *revname)
+{
+	struct strbuf fullref = STRBUF_INIT;
+	struct object_id oid;
+	struct object *obj;
+
+	if (!revname)
+		revname = ctx.qry.head;
+
+	strbuf_addf(&fullref, "refs/tags/%s", revname);
+	if (get_oid(fullref.buf, &oid)) {
+		cgit_print_error_page(404, "Not found",
+			"Bad tag reference: %s", revname);
+		goto cleanup;
+	}
+	obj = parse_object(the_repository, &oid);
+	if (!obj) {
+		cgit_print_error_page(500, "Internal server error",
+			"Bad object id: %s", oid_to_hex(&oid));
+		goto cleanup;
+	}
+	if (obj->type == OBJ_TAG) {
+		struct tag *tag;
+		struct taginfo *info;
+
+		tag = lookup_tag(the_repository, &oid);
+		if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) {
+			cgit_print_error_page(500, "Internal server error",
+				"Bad tag object: %s", revname);
+			goto cleanup;
+		}
+		cgit_print_layout_start();
+		html("<table class='commit-info'>\n");
+		html("<tr><td>tag name</td><td>");
+		html_txt(revname);
+		htmlf(" (%s)</td></tr>\n", oid_to_hex(&oid));
+		if (info->tagger_date > 0) {
+			html("<tr><td>tag date</td><td>");
+			html_txt(show_date(info->tagger_date, info->tagger_tz,
+						cgit_date_mode(DATE_ISO8601)));
+			html("</td></tr>\n");
+		}
+		if (info->tagger) {
+			html("<tr><td>tagged by</td><td>");
+			cgit_open_filter(ctx.repo->email_filter, info->tagger_email, "tag");
+			html_txt(info->tagger);
+			if (info->tagger_email && !ctx.cfg.noplainemail) {
+				html(" ");
+				html_txt(info->tagger_email);
+			}
+			cgit_close_filter(ctx.repo->email_filter);
+			html("</td></tr>\n");
+		}
+		html("<tr><td>tagged object</td><td class='sha1'>");
+		cgit_object_link(tag->tagged);
+		html("</td></tr>\n");
+		if (ctx.repo->snapshots)
+			print_download_links(revname);
+		html("</table>\n");
+		print_tag_content(info->msg);
+		cgit_print_layout_end();
+		cgit_free_taginfo(info);
+	} else {
+		cgit_print_layout_start();
+		html("<table class='commit-info'>\n");
+		html("<tr><td>tag name</td><td>");
+		html_txt(revname);
+		html("</td></tr>\n");
+		html("<tr><td>tagged object</td><td class='sha1'>");
+		cgit_object_link(obj);
+		html("</td></tr>\n");
+		if (ctx.repo->snapshots)
+			print_download_links(revname);
+		html("</table>\n");
+		cgit_print_layout_end();
+	}
+
+cleanup:
+	strbuf_release(&fullref);
+}
diff --git a/www/git.causal.agency/cgit/ui-tag.h b/www/git.causal.agency/cgit/ui-tag.h
new file mode 100644
index 00000000..d295cdcd
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-tag.h
@@ -0,0 +1,6 @@
+#ifndef UI_TAG_H
+#define UI_TAG_H
+
+extern void cgit_print_tag(char *revname);
+
+#endif /* UI_TAG_H */
diff --git a/www/git.causal.agency/cgit/ui-tree.c b/www/git.causal.agency/cgit/ui-tree.c
new file mode 100644
index 00000000..438b0011
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-tree.c
@@ -0,0 +1,410 @@
+/* ui-tree.c: functions for tree output
+ *
+ * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ *   (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+#include "ui-tree.h"
+#include "html.h"
+#include "ui-shared.h"
+
+struct walk_tree_context {
+	char *curr_rev;
+	char *match_path;
+	int state;
+};
+
+static void print_text_buffer(const char *name, char *buf, unsigned long size)
+{
+	unsigned long lineno, idx;
+	const char *numberfmt = "<a id='n%1$d' href='#n%1$d'>%1$d</a>\n";
+
+	html("<table summary='blob content' class='blob'>\n");
+
+	if (ctx.cfg.enable_tree_linenumbers) {
+		html("<tr><td class='linenumbers'><pre>");
+		idx = 0;
+		lineno = 0;
+
+		if (size) {
+			htmlf(numberfmt, ++lineno);
+			while (idx < size - 1) { // skip absolute last newline
+				if (buf[idx] == '\n')
+					htmlf(numberfmt, ++lineno);
+				idx++;
+			}
+		}
+		html("</pre></td>\n");
+	}
+	else {
+		html("<tr>\n");
+	}
+
+	if (ctx.repo->source_filter) {
+		char *filter_arg = xstrdup(name);
+		html("<td class='lines'><pre><code>");
+		cgit_open_filter(ctx.repo->source_filter, filter_arg);
+		html_raw(buf, size);
+		cgit_close_filter(ctx.repo->source_filter);
+		free(filter_arg);
+		html("</code></pre></td></tr></table>\n");
+		return;
+	}
+
+	html("<td class='lines'><pre><code>");
+	html_txt(buf);
+	html("</code></pre></td></tr></table>\n");
+}
+
+#define ROWLEN 32
+
+static void print_binary_buffer(char *buf, unsigned long size)
+{
+	unsigned long ofs, idx;
+	static char ascii[ROWLEN + 1];
+
+	html("<table summary='blob content' class='bin-blob'>\n");
+	html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>");
+	for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
+		htmlf("<tr><td class='right'>%04lx</td><td class='hex'>", ofs);
+		for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
+			htmlf("%*s%02x",
+			      idx == 16 ? 4 : 1, "",
+			      buf[idx] & 0xff);
+		html(" </td><td class='hex'>");
+		for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
+			ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
+		ascii[idx] = '\0';
+		html_txt(ascii);
+		html("</td></tr>\n");
+	}
+	html("</table>\n");
+}
+
+static void print_object(const struct object_id *oid, const char *path, const char *basename, const char *rev)
+{
+	enum object_type type;
+	char *buf;
+	unsigned long size;
+	int is_binary;
+
+	type = oid_object_info(the_repository, oid, &size);
+	if (type == OBJ_BAD) {
+		cgit_print_error_page(404, "Not found",
+			"Bad object name: %s", oid_to_hex(oid));
+		return;
+	}
+
+	buf = read_object_file(oid, &type, &size);
+	if (!buf) {
+		cgit_print_error_page(500, "Internal server error",
+			"Error reading object %s", oid_to_hex(oid));
+		return;
+	}
+	is_binary = buffer_is_binary(buf, size);
+
+	cgit_set_title_from_path(path);
+
+	cgit_print_layout_start();
+	htmlf("blob: %s (", oid_to_hex(oid));
+	cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
+		        rev, path);
+	if (ctx.repo->enable_blame && !is_binary) {
+		html(") (");
+		cgit_blame_link("blame", NULL, NULL, ctx.qry.head,
+			        rev, path);
+	}
+	html(")\n");
+
+	if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
+		htmlf("<div class='error'>blob size (%ldKB) exceeds display size limit (%dKB).</div>",
+				size / 1024, ctx.cfg.max_blob_size);
+		return;
+	}
+
+	if (is_binary)
+		print_binary_buffer(buf, size);
+	else
+		print_text_buffer(basename, buf, size);
+
+	free(buf);
+}
+
+struct single_tree_ctx {
+	struct strbuf *path;
+	struct object_id oid;
+	char *name;
+	size_t count;
+};
+
+static int single_tree_cb(const struct object_id *oid, struct strbuf *base,
+			  const char *pathname, unsigned mode, int stage,
+			  void *cbdata)
+{
+	struct single_tree_ctx *ctx = cbdata;
+
+	if (++ctx->count > 1)
+		return -1;
+
+	if (!S_ISDIR(mode)) {
+		ctx->count = 2;
+		return -1;
+	}
+
+	ctx->name = xstrdup(pathname);
+	oidcpy(&ctx->oid, oid);
+	strbuf_addf(ctx->path, "/%s", pathname);
+	return 0;
+}
+
+static void write_tree_link(const struct object_id *oid, char *name,
+			    char *rev, struct strbuf *fullpath)
+{
+	size_t initial_length = fullpath->len;
+	struct tree *tree;
+	struct single_tree_ctx tree_ctx = {
+		.path = fullpath,
+		.count = 1,
+	};
+	struct pathspec paths = {
+		.nr = 0
+	};
+
+	oidcpy(&tree_ctx.oid, oid);
+
+	while (tree_ctx.count == 1) {
+		cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, rev,
+			       fullpath->buf);
+
+		tree = lookup_tree(the_repository, &tree_ctx.oid);
+		if (!tree)
+			return;
+
+		free(tree_ctx.name);
+		tree_ctx.name = NULL;
+		tree_ctx.count = 0;
+
+		read_tree_recursive(the_repository, tree, "", 0, 1,
+			&paths, single_tree_cb, &tree_ctx);
+
+		if (tree_ctx.count != 1)
+			break;
+
+		html(" / ");
+		name = tree_ctx.name;
+	}
+
+	strbuf_setlen(fullpath, initial_length);
+}
+
+static int ls_item(const struct object_id *oid, struct strbuf *base,
+		const char *pathname, unsigned mode, int stage, void *cbdata)
+{
+	struct walk_tree_context *walk_tree_ctx = cbdata;
+	char *name;
+	struct strbuf fullpath = STRBUF_INIT;
+	struct strbuf linkpath = STRBUF_INIT;
+	struct strbuf class = STRBUF_INIT;
+	enum object_type type;
+	unsigned long size = 0;
+	char *buf;
+
+	name = xstrdup(pathname);
+	strbuf_addf(&fullpath, "%s%s%s", ctx.qry.path ? ctx.qry.path : "",
+		    ctx.qry.path ? "/" : "", name);
+
+	if (!S_ISGITLINK(mode)) {
+		type = oid_object_info(the_repository, oid, &size);
+		if (type == OBJ_BAD) {
+			htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
+			      name,
+			      oid_to_hex(oid));
+			goto cleanup;
+		}
+	}
+
+	html("<tr><td class='ls-mode'>");
+	cgit_print_filemode(mode);
+	html("</td><td>");
+	if (S_ISGITLINK(mode)) {
+		cgit_submodule_link("ls-mod", fullpath.buf, oid_to_hex(oid));
+	} else if (S_ISDIR(mode)) {
+		write_tree_link(oid, name, walk_tree_ctx->curr_rev,
+				&fullpath);
+	} else {
+		char *ext = strrchr(name, '.');
+		strbuf_addstr(&class, "ls-blob");
+		if (ext)
+			strbuf_addf(&class, " %s", ext + 1);
+		cgit_tree_link(name, NULL, class.buf, ctx.qry.head,
+			       walk_tree_ctx->curr_rev, fullpath.buf);
+	}
+	if (S_ISLNK(mode)) {
+		html(" -> ");
+		buf = read_object_file(oid, &type, &size);
+		if (!buf) {
+			htmlf("Error reading object: %s", oid_to_hex(oid));
+			goto cleanup;
+		}
+		strbuf_addbuf(&linkpath, &fullpath);
+		strbuf_addf(&linkpath, "/../%s", buf);
+		strbuf_normalize_path(&linkpath);
+		cgit_tree_link(buf, NULL, class.buf, ctx.qry.head,
+			walk_tree_ctx->curr_rev, linkpath.buf);
+		free(buf);
+		strbuf_release(&linkpath);
+	}
+	htmlf("</td><td class='ls-size'>%li</td>", size);
+
+	html("<td>");
+	cgit_log_link("log", NULL, "button", ctx.qry.head,
+		      walk_tree_ctx->curr_rev, fullpath.buf, 0, NULL, NULL,
+		      ctx.qry.showmsg, 0);
+	if (ctx.repo->max_stats)
+		cgit_stats_link("stats", NULL, "button", ctx.qry.head,
+				fullpath.buf);
+	if (!S_ISGITLINK(mode))
+		cgit_plain_link("plain", NULL, "button", ctx.qry.head,
+				walk_tree_ctx->curr_rev, fullpath.buf);
+	if (!S_ISDIR(mode) && ctx.repo->enable_blame)
+		cgit_blame_link("blame", NULL, "button", ctx.qry.head,
+				walk_tree_ctx->curr_rev, fullpath.buf);
+	html("</td></tr>\n");
+
+cleanup:
+	free(name);
+	strbuf_release(&fullpath);
+	strbuf_release(&class);
+	return 0;
+}
+
+static void ls_head(void)
+{
+	cgit_print_layout_start();
+	html("<table summary='tree listing' class='list'>\n");
+	html("<tr class='nohover'>");
+	html("<th class='left'>Mode</th>");
+	html("<th class='left'>Name</th>");
+	html("<th class='right'>Size</th>");
+	html("<th/>");
+	html("</tr>\n");
+}
+
+static void ls_tail(void)
+{
+	html("</table>\n");
+	cgit_print_layout_end();
+}
+
+static void ls_tree(const struct object_id *oid, const char *path, struct walk_tree_context *walk_tree_ctx)
+{
+	struct tree *tree;
+	struct pathspec paths = {
+		.nr = 0
+	};
+
+	tree = parse_tree_indirect(oid);
+	if (!tree) {
+		cgit_print_error_page(404, "Not found",
+			"Not a tree object: %s", oid_to_hex(oid));
+		return;
+	}
+
+	ls_head();
+	read_tree_recursive(the_repository, tree, "", 0, 1,
+		&paths, ls_item, walk_tree_ctx);
+	ls_tail();
+}
+
+
+static int walk_tree(const struct object_id *oid, struct strbuf *base,
+		const char *pathname, unsigned mode, int stage, void *cbdata)
+{
+	struct walk_tree_context *walk_tree_ctx = cbdata;
+
+	if (walk_tree_ctx->state == 0) {
+		struct strbuf buffer = STRBUF_INIT;
+
+		strbuf_addbuf(&buffer, base);
+		strbuf_addstr(&buffer, pathname);
+		if (strcmp(walk_tree_ctx->match_path, buffer.buf))
+			return READ_TREE_RECURSIVE;
+
+		if (S_ISDIR(mode)) {
+			walk_tree_ctx->state = 1;
+			cgit_set_title_from_path(buffer.buf);
+			strbuf_release(&buffer);
+			ls_head();
+			return READ_TREE_RECURSIVE;
+		} else {
+			walk_tree_ctx->state = 2;
+			print_object(oid, buffer.buf, pathname, walk_tree_ctx->curr_rev);
+			strbuf_release(&buffer);
+			return 0;
+		}
+	}
+	ls_item(oid, base, pathname, mode, stage, walk_tree_ctx);
+	return 0;
+}
+
+/*
+ * Show a tree or a blob
+ *   rev:  the commit pointing at the root tree object
+ *   path: path to tree or blob
+ */
+void cgit_print_tree(const char *rev, char *path)
+{
+	struct object_id oid;
+	struct commit *commit;
+	struct pathspec_item path_items = {
+		.match = path,
+		.len = path ? strlen(path) : 0
+	};
+	struct pathspec paths = {
+		.nr = path ? 1 : 0,
+		.items = &path_items
+	};
+	struct walk_tree_context walk_tree_ctx = {
+		.match_path = path,
+		.state = 0
+	};
+
+	if (!rev)
+		rev = ctx.qry.head;
+
+	if (get_oid(rev, &oid)) {
+		cgit_print_error_page(404, "Not found",
+			"Invalid revision name: %s", rev);
+		return;
+	}
+	commit = lookup_commit_reference(the_repository, &oid);
+	if (!commit || parse_commit(commit)) {
+		cgit_print_error_page(404, "Not found",
+			"Invalid commit reference: %s", rev);
+		return;
+	}
+
+	walk_tree_ctx.curr_rev = xstrdup(rev);
+
+	if (path == NULL) {
+		ls_tree(get_commit_tree_oid(commit), NULL, &walk_tree_ctx);
+		goto cleanup;
+	}
+
+	read_tree_recursive(the_repository,
+			    repo_get_commit_tree(the_repository, commit),
+			    "", 0, 0,
+			    &paths, walk_tree, &walk_tree_ctx);
+	if (walk_tree_ctx.state == 1)
+		ls_tail();
+	else if (walk_tree_ctx.state == 2)
+		cgit_print_layout_end();
+	else
+		cgit_print_error_page(404, "Not found", "Path not found");
+
+cleanup:
+	free(walk_tree_ctx.curr_rev);
+}
diff --git a/www/git.causal.agency/cgit/ui-tree.h b/www/git.causal.agency/cgit/ui-tree.h
new file mode 100644
index 00000000..bbd34e35
--- /dev/null
+++ b/www/git.causal.agency/cgit/ui-tree.h
@@ -0,0 +1,6 @@
+#ifndef UI_TREE_H
+#define UI_TREE_H
+
+extern void cgit_print_tree(const char *rev, char *path);
+
+#endif /* UI_TREE_H */
diff --git a/www/git.causal.agency/cgitrc b/www/git.causal.agency/cgitrc
new file mode 100644
index 00000000..c187e1ee
--- /dev/null
+++ b/www/git.causal.agency/cgitrc
@@ -0,0 +1,27 @@
+root-title=causal agency
+root-desc=“then I'm sorry, no offence, but you write toy programs.â€
+
+clone-url=https://$HTTP_HOST/$CGIT_REPO_URL
+snapshots=tar.gz zip
+
+enable-blame=1
+enable-commit-graph=1
+enable-subject-links=1
+enable-follow-links=1
+enable-index-owner=0
+repository-sort=age
+branch-sort=age
+
+css=/custom.css
+email-filter=/usr/local/libexec/cgit-email
+about-filter=/usr/local/libexec/about-filter
+source-filter=/usr/local/libexec/source-filter
+
+readme=:README.7
+readme=:README
+
+remove-suffix=1
+enable-git-config=1
+scan-path=/home/june/pub
+
+cache-size=1024
diff --git a/www/git.causal.agency/custom.css b/www/git.causal.agency/custom.css
new file mode 100644
index 00000000..802882d2
--- /dev/null
+++ b/www/git.causal.agency/custom.css
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+@import url("cgit.css");
+
+* { line-height: 1.25em; }
+
+div#cgit {
+	max-width: 117ch;
+	margin: auto;
+	font-family: monospace;
+	-moz-tab-size: 4;
+	tab-size: 4;
+}
+
+div#cgit table#header td.logo {
+	display: none;
+}
+div#cgit table#header td.sub {
+	border-top: none;
+}
+div#cgit table.tabs {
+	border-bottom: none;
+}
+div#cgit div.content {
+	border-bottom: none;
+}
+div#cgit table.list th a {
+	color: inherit;
+}
+div#cgit table.list tr:nth-child(even) {
+	background: inherit;
+}
+div#cgit table.list tr:hover {
+	background: inherit;
+}
+div#cgit table.list tr.nohover-highlight:hover:nth-child(even) {
+	background: inherit;
+}
+
+div#cgit table.blob td.linenumbers a:target {
+	color: goldenrod;
+	text-decoration: underline;
+	outline: none;
+}
+
+div#cgit div#summary {
+	max-width: 80ch;
+}
+
+/* from hi(1) */
+div#cgit .hi.Keyword { color: dimgray; }
+div#cgit .hi.Macro { color: green; }
+div#cgit .hi.Tag { color: inherit; text-decoration: underline; }
+div#cgit .hi.String { color: teal; }
+div#cgit .hi.Format { color: teal; font-weight: bold; }
+div#cgit .hi.Interp { color: olive; }
+div#cgit .hi.Comment { color: navy; }
+div#cgit .hi.Todo { color: navy; font-weight: bold; }
+div#cgit .hi.DiffOld { color: red; }
+div#cgit .hi.DiffNew { color: green; }
+div#cgit .hi.Tag:target { color: goldenrod; outline: none; }
+
+/* from mandoc(1) */
+table.head, table.foot { width: 100%; }
+td.head-rtitle, td.foot-os { text-align: right; }
+td.head-vol { text-align: center; }
+div.Pp { margin: 1ex 0ex; }
+div.Nd, div.Bf, div.Op { display: inline; }
+span.Pa, span.Ad { font-style: italic; }
+span.Ms { font-weight: bold; }
+dl.Bl-diag > dt { font-weight: bold; }
+code.Nm, code.Fl, code.Cm, code.Ic, code.In, code.Fd, code.Fn,
+code.Cd { font-weight: bold; font-family: inherit; }
+
+h1.Sh { font-size: 1.5em; }
+table.Nm td:first-child { padding-right: 1ch; }
+code.Fl { white-space: nowrap; }
+span.RsT { font-style: italic; }
+dl.Bl-tag:not(.Bl-compact) dt { margin-top: 1em; }
+ul.Bl-bullet:not(.Bl-compact) li { margin-top: 1em; }
+div.Bd-indent { margin-left: 4ch; }
+table.Bl-column { width: 100%; }
+table.foot { margin-top: 1em; }
+
+div#cgit a.permalink { color: inherit; }
diff --git a/www/git.causal.agency/source-filter.sh b/www/git.causal.agency/source-filter.sh
new file mode 100644
index 00000000..4febc2e0
--- /dev/null
+++ b/www/git.causal.agency/source-filter.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+exec /usr/local/libexec/hi -t -n "$1" -f html -o anchor
diff --git a/www/temp.causal.agency/.gitignore b/www/temp.causal.agency/.gitignore
new file mode 100644
index 00000000..e31ee94e
--- /dev/null
+++ b/www/temp.causal.agency/.gitignore
@@ -0,0 +1 @@
+up
diff --git a/www/temp.causal.agency/Makefile b/www/temp.causal.agency/Makefile
new file mode 100644
index 00000000..3e908305
--- /dev/null
+++ b/www/temp.causal.agency/Makefile
@@ -0,0 +1,16 @@
+WEBROOT = /usr/local/www/temp.causal.agency
+
+CFLAGS += -std=c11 -Wall -Wextra -Wpedantic -I/usr/local/include
+LDFLAGS += -static -L/usr/local/lib
+LDLIBS = -lkcgihtml -lkcgi -lz -lmd
+
+up:
+
+clean:
+	rm -f up
+
+install: up
+	install -m 700 up ${WEBROOT}/up
+
+uninstall:
+	rm -f ${WEBROOT}/up
diff --git a/www/temp.causal.agency/up.c b/www/temp.causal.agency/up.c
new file mode 100644
index 00000000..885b8acd
--- /dev/null
+++ b/www/temp.causal.agency/up.c
@@ -0,0 +1,156 @@
+/* Copyright (C) 2020  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/capsicum.h>
+#include <sys/types.h>
+#include <sysexits.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <kcgi.h>
+#include <kcgihtml.h>
+
+static int cwd = -1;
+
+static const struct kvalid Key = { NULL, "file" };
+
+static enum kcgi_err head(struct kreq *req, enum khttp http, enum kmime mime) {
+	return khttp_head(req, kresps[KRESP_STATUS], "%s", khttps[http])
+		|| khttp_head(req, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[mime]);
+}
+
+static enum kcgi_err fail(struct kreq *req, enum khttp http) {
+	return head(req, http, KMIME_TEXT_PLAIN)
+		|| khttp_body(req)
+		|| khttp_printf(req, "%s\n", khttps[http]);
+}
+
+static enum kcgi_err handle(struct kreq *req) {
+	if (req->page) return fail(req, KHTTP_404);
+
+	if (req->method == KMETHOD_GET) {
+		struct khtmlreq html;
+		struct khtmlreq *h = &html;
+		return head(req, KHTTP_200, KMIME_TEXT_HTML)
+			|| khttp_body(req)
+			|| khtml_open(h, req, 0)
+			|| khtml_elem(h, KELEM_DOCTYPE)
+			|| khtml_elem(h, KELEM_TITLE)
+			|| khtml_puts(h, "Upload")
+			|| khtml_closeelem(h, 1)
+			|| khtml_attr(
+				h, KELEM_FORM,
+				KATTR_METHOD, "post",
+				KATTR_ACTION, "",
+				KATTR_ENCTYPE, "multipart/form-data",
+				KATTR__MAX
+			)
+			|| khtml_attr(
+				h, KELEM_INPUT,
+				KATTR_TYPE, "file",
+				KATTR_NAME, Key.name,
+				KATTR__MAX
+			)
+			|| khtml_attr(
+				h, KELEM_INPUT,
+				KATTR_TYPE, "submit",
+				KATTR_VALUE, "Upload",
+				KATTR__MAX
+			)
+			|| khtml_close(h);
+
+	} else if (req->method == KMETHOD_POST) {
+		struct kpair *field = req->fieldmap[0];
+		if (!field || !field->valsz) return fail(req, KHTTP_400);
+
+		char name[256];
+		const char *ext = strrchr(field->file, '.');
+		if (!ext) ext = "";
+		snprintf(
+			name, sizeof(name), "%jx%08x%s",
+			(intmax_t)time(NULL), arc4random(), ext
+		);
+
+		int fd = openat(cwd, name, O_CREAT | O_EXCL | O_WRONLY, 0644);
+		if (fd < 0) {
+			warn("openat");
+			return fail(req, KHTTP_507);
+		}
+		ssize_t len = write(fd, field->val, field->valsz);
+		int error = close(fd);
+		if (len < 0 || error) {
+			warn("write");
+			return fail(req, KHTTP_507);
+		}
+
+		return head(req, KHTTP_303, KMIME_TEXT_PLAIN)
+			|| khttp_head(req, kresps[KRESP_LOCATION], "/%s", name)
+			|| khttp_body(req)
+			|| khttp_puts(req, name);
+
+	} else {
+		return fail(req, KHTTP_405);
+	}
+}
+
+static void sandbox(void) {
+	cwd = open(".", O_DIRECTORY);
+	if (cwd < 0) err(EX_CONFIG, ".");
+
+	int error = cap_enter();
+	if (error) err(EX_OSERR, "cap_enter");
+
+	cap_rights_t rights;
+	cap_rights_init(&rights, CAP_LOOKUP, CAP_CREATE, CAP_PWRITE);
+	error = cap_rights_limit(cwd, &rights);
+	if (error) err(EX_OSERR, "cap_rights_limit");
+}
+
+int main(void) {
+	const char *page = "up";
+	if (khttp_fcgi_test()) {
+		struct kfcgi *fcgi;
+		enum kcgi_err error = khttp_fcgi_init(&fcgi, &Key, 1, &page, 1, 0);
+		if (error) errx(EX_CONFIG, "khttp_fcgi_init: %s", kcgi_strerror(error));
+		sandbox();
+		for (
+			struct kreq req;
+			KCGI_OK == (error = khttp_fcgi_parse(fcgi, &req));
+			khttp_free(&req)
+		) {
+			error = handle(&req);
+			if (error && error != KCGI_HUP) break;
+		}
+		if (error != KCGI_EXIT) {
+			errx(EX_PROTOCOL, "khttp_fcgi_parse: %s", kcgi_strerror(error));
+		}
+		khttp_fcgi_free(fcgi);
+	} else {
+		struct kreq req;
+		enum kcgi_err error = khttp_parse(&req, &Key, 1, &page, 1, 0);
+		if (error) errx(EX_PROTOCOL, "khttp_parse: %s", kcgi_strerror(error));
+		error = handle(&req);
+		if (error) errx(EX_PROTOCOL, "%s", kcgi_strerror(error));
+		khttp_free(&req);
+	}
+}
diff --git a/www/text.causal.agency/.gitignore b/www/text.causal.agency/.gitignore
new file mode 100644
index 00000000..37dd51ef
--- /dev/null
+++ b/www/text.causal.agency/.gitignore
@@ -0,0 +1,2 @@
+*.txt
+feed.atom
diff --git a/www/text.causal.agency/001-make.7 b/www/text.causal.agency/001-make.7
new file mode 100644
index 00000000..b4805729
--- /dev/null
+++ b/www/text.causal.agency/001-make.7
@@ -0,0 +1,159 @@
+.Dd September 17, 2018
+.Dt MAKE 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Using Make
+.Nd writing less Makefile
+.
+.Sh DESCRIPTION
+Let's talk about
+.Xr make 1 .
+I think an important thing to know about
+.Xr make 1
+is that you don't need to write a
+.Pa Makefile
+to use it.
+There are default rules
+for C, C++ and probably Fortran.
+To build
+.Pa foo
+from
+.Pa foo.c ,
+just run:
+.
+.Pp
+.Dl make foo
+.
+.Pp
+The default rule for C files uses the
+.Ev CFLAGS
+variable,
+so you can set that in the environment
+to pass flags to the C compiler:
+.
+.Pp
+.Dl CFLAGS=-Wall make foo
+.
+.Pp
+It also uses
+.Ev LDLIBS
+for linking,
+so you can add libraries with:
+.
+.Pp
+.Dl LDLIBS=-lcurses make foo
+.
+.Pp
+Obviously writing this every time
+would become tedious,
+so it might be time to write a
+.Pa Makefile .
+But it really doesn't need much:
+.
+.Bd -literal -offset indent
+CFLAGS += -Wall -Wextra
+LDLIBS = -lcurses
+
+foo:
+.Ed
+.
+.Pp
+Assigning
+.Ev CFLAGS
+with
+.Ql +=
+preserves the system default
+or anything passed in the environment.
+Declaring
+.Pa foo
+as the first rule
+makes it the default when
+.Ql make
+is run without a target.
+Note that the rule doesn't need a definition;
+the default will still be used.
+.
+.Pp
+If
+.Pa foo
+is built from serveral source files,
+unfortunately a rule definition is required:
+.
+.Bd -literal -offset indent
+OBJS = foo.o bar.o baz.o
+
+foo: $(OBJS)
+	$(CC) $(LDFLAGS) $(OBJS) $(LDLIBS) -o $@
+.Ed
+.
+.Pp
+This rule uses
+.Ev LDFLAGS
+for passing linker flags,
+which is what the default rule does.
+The
+.Ql $@
+variable here expands to
+.Ql foo ,
+so this rule can be copied easily
+for other binary targets.
+.
+.Pp
+If some sources depend on a header file,
+they can be automatically rebuilt
+when the header changes
+by declaring a dependency rule:
+.
+.Pp
+.Dl foo.o bar.o: foo.h
+.
+.Pp
+Note that several files can appear
+either side of the
+.Ql ":" .
+.
+.Pp
+Lastly,
+it's always nice to add a
+.Cm clean
+target:
+.
+.Bd -literal -offset indent
+clean:
+	rm -f $(OBJS) foo
+.Ed
+.
+.Pp
+I hope this helps getting started with
+.Xr make 1
+without writing too much
+.Pa Makefile !
+.
+.Sh EXAMPLES
+The example
+.Pa Makefile
+in its entirety:
+.
+.Bd -literal -offset indent
+CFLAGS += -Wall -Wextra
+LDLIBS = -lcurses
+OBJS = foo.o bar.o baz.o
+
+foo: $(OBJS)
+	$(CC) $(LDFLAGS) $(OBJS) $(LDLIBS) -o $@
+
+foo.o bar.o: foo.h
+
+clean:
+	rm -f $(OBJS) foo
+.Ed
+.
+.Sh AUTHORS
+.An Mt june@causal.agency
+.
+.Pp
+This document is produced from
+.Xr mdoc 7
+source available from
+.Lk https://git.causal.agency/src/tree/www/text.causal.agency
diff --git a/www/text.causal.agency/002-writing-mdoc.7 b/www/text.causal.agency/002-writing-mdoc.7
new file mode 100644
index 00000000..b377d364
--- /dev/null
+++ b/www/text.causal.agency/002-writing-mdoc.7
@@ -0,0 +1,138 @@
+.Dd September 27, 2018
+.Dt WRITING-MDOC 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Writing mdoc
+.Nd semantic markup
+.
+.Sh DESCRIPTION
+I recently learned how to write man pages
+so that I could document
+a bunch of little programs I've written.
+Modern man pages are written in
+.Xr mdoc 7 ,
+whose documentation is also available from
+.Lk http://mandoc.bsd.lv .
+.
+.Pp
+.Xr mdoc 7
+differs from many other markup languages
+by providing
+.Dq semantic markup
+rather than just
+.Dq physical markup.
+What this means is that
+the markup indicates what something is,
+not how to format it.
+For example,
+the
+.Ql \&Ar
+macro is used to indicate
+command-line arguments
+rather than one of the macros
+for bold, italic or underline.
+This frees each author of having to choose
+and enables consistent presentation
+across different man pages.
+.
+.Pp
+Another advantage of semantic markup
+is that information can be extracted from it.
+For example,
+.Xr makewhatis 8
+can easily extract the name and short description
+from each man page
+thanks to the
+.Ql \&Nm
+and
+.Ql \&Nd
+macros.
+I use the same information
+to generate an Atom feed for these documents,
+though in admittedly a much less robust way than
+.Xr mandoc 1 .
+.
+.Pp
+When it comes to actually writing
+.Xr mdoc 7 ,
+it can take some getting used to.
+The language is of
+.Xr roff 7
+lineage
+so its syntax is very particular.
+Macros cannot appear inline,
+but must start on new lines
+beginning with
+.Ql \&. .
+Sentences should likewise
+always start on a new line.
+Since I'm in the habit of writing with
+semantic line breaks,
+I actually find these requirements
+fit in well.
+.
+.Pp
+The more frustrating syntax limitation to me
+is the rule against empty lines.
+Without them,
+it can be quite difficult to edit a lengthy document.
+Thankfully,
+lines with only a
+.Ql \&.
+on them are allowed,
+but this still causes visual noise.
+To alleviate that,
+I have a
+.Xr vim 1
+syntax file for
+.Xr mdoc 7
+which conceals the lone dots:
+.
+.Bd -literal -offset indent
+if exists("b:current_syntax")
+	finish
+endif
+
+runtime! syntax/nroff.vim
+unlet! b:current_syntax
+
+setlocal sections+=ShSs
+syntax match mdocBlank /^\\.$/ conceal
+setlocal conceallevel=2
+
+let b:current_syntax = "mdoc"
+.Ed
+.
+.Pp
+It also adds the
+.Xr mdoc 7
+section header and subsection header macros to the
+.Cm sections
+option to make
+.Xr vim 1 Ap s
+.Ic {
+and
+.Ic }
+motions
+aware of them.
+.
+.Pp
+With that,
+I've found writing man pages pleasant and rewarding.
+I've started writing other documents with
+.Xr mdoc 7
+as well,
+as you can see here.
+.
+.Sh SEE ALSO
+.Lk http://rhodesmill.org/brandon/2012/one-sentence-per-line/ "Semantic Linefeeds"
+.
+.Sh AUTHORS
+.An Mt june@causal.agency
+.
+.Pp
+This document is produced from
+.Xr mdoc 7
+source available from
+.Lk https://git.causal.agency/src/tree/www/text.causal.agency
diff --git a/www/text.causal.agency/003-pleasant-c.7 b/www/text.causal.agency/003-pleasant-c.7
new file mode 100644
index 00000000..16030b7e
--- /dev/null
+++ b/www/text.causal.agency/003-pleasant-c.7
@@ -0,0 +1,120 @@
+.Dd September 30, 2018
+.Dt PLEASANT-C 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Pleasant C
+.Nd it's good, actually
+.
+.Sh DESCRIPTION
+I've been writing a lot of C lately
+and actually find it very pleasant.
+I want to talk about some of its ergonomic features.
+These are C99 features unless otherwise noted.
+.
+.Ss Initializer syntax
+Struct and union initializer syntax
+is well generalized.
+Designators can be chained,
+making initializing nested structs easy,
+and all uninitialized fields are zeroed.
+.
+.Bd -literal -offset indent
+struct {
+	struct pollfd fds[2];
+} loop = {
+	.fds[0].fd = STDIN_FILENO,
+	.fds[1].fd = STDOUT_FILENO,
+	.fds[0].events = POLLIN,
+	.fds[1].events = POLLOUT,
+};
+.Ed
+.
+.Ss Variable-length arrays
+VLAs can be multi-dimensional,
+which can avoid manual stride multiplications
+needed to index a flat
+.Xr malloc 3 Ap d
+array.
+.
+.Bd -literal -offset indent
+uint8_t glyphs[len][height][width];
+fread(glyphs, height * width, len, stdin);
+.Ed
+.
+.Ss Incomplete array types
+The last field of a struct can be an
+.Dq incomplete
+array type,
+which means it doesn't have a length.
+A variable amount of space for the struct can be
+.Xr malloc 3 Ap d ,
+or the struct can be used as
+a sort of pointer with fields.
+.
+.Bd -literal -offset indent
+struct Line {
+	enum Filter type;
+	uint8_t data[];
+} *line = &png.data[1 + lineSize()];
+.Ed
+.
+.Ss Anonymous struct and union fields (C11)
+Members of structs or unions
+which are themselves structs or unions
+can be unnamed.
+In that case,
+each of the inner fields
+is treated as a member of the outer struct or union.
+This makes working with tagged unions nicer.
+.
+.Bd -literal -offset indent
+struct Message {
+	enum { Foo, Bar } type;
+	union {
+		uint8_t foo;
+		uint32_t bar;
+	};
+} msg = { .type = Foo, .foo = 0xFF };
+.Ed
+.
+.Ss Static assert (C11)
+Assertions can be made at compile time.
+Most useful for checking sizes of structs.
+.
+.Bd -literal -offset indent
+static_assert(13 == sizeof(struct PNGHeader), "PNG IHDR size");
+.Ed
+.
+.Ss Leading-break switch
+This one is just an odd style choice
+I came across that C happens to allow.
+To prevent accidental fall-through
+in switch statements,
+you can put breaks before the case labels.
+.
+.Bd -literal -offset indent
+while (0 < (opt = getopt(argc, argv, "h:w:"))) {
+	switch (opt) {
+		break; case 'h': height = optarg;
+		break; case 'w': width = optarg;
+		break; default:  return EX_USAGE;
+	}
+}
+.Ed
+.
+.Sh AUTHORS
+.An Mt june@causal.agency
+.
+.Pp
+This document is produced from
+.Xr mdoc 7
+source available from
+.Lk https://git.causal.agency/src/tree/www/text.causal.agency
+.
+.Sh CAVEATS
+This isn't meant to be advice.
+It's just how I like to write C,
+and I don't
+.Dq ship
+software in C.
diff --git a/www/text.causal.agency/004-uloc.7 b/www/text.causal.agency/004-uloc.7
new file mode 100644
index 00000000..edd78d80
--- /dev/null
+++ b/www/text.causal.agency/004-uloc.7
@@ -0,0 +1,64 @@
+.Dd December 14, 2018
+.Dt ULOC 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm ULOC
+.Nd unique lines of code
+.
+.Sh DESCRIPTION
+There are many tools available
+which measure SLOC: source lines of code.
+These tools are strangely complex
+for what they intend to do,
+which is to estimate the relative sizes of projects.
+They perform some amount of parsing
+in order to discount comments in various languages,
+and for reasons unknown each format their ouput
+in some oddly encumbered way.
+.
+.Pp
+I propose a much simpler method
+of estimating relative sizes of projects:
+unique lines of code.
+ULOC can be calculated with standard tools as follows:
+.
+.Bd -literal -offset indent
+sort -u *.h *.c | wc -l
+.Ed
+.
+.Pp
+In my opinion,
+the number this produces
+should be a better estimate of
+the complexity of a project.
+Compared to SLOC,
+not only are blank lines discounted,
+but so are close-brace lines
+and other repetitive code
+such as common includes.
+On the other hand,
+ULOC counts comments,
+which require just as much maintenance
+as the code around them does,
+while avoiding inflating the result
+with license headers which appear in every file,
+for example.
+.
+.Pp
+It can also be amusing
+to read all of your code sorted alphabetically.
+.
+.Sh AUTHORS
+.An Mt june@causal.agency
+.
+.Pp
+This document is produced from
+.Xr mdoc 7
+source available from
+.Lk https://git.causal.agency/src/tree/www/text.causal.agency
+.
+.Sh CAVEATS
+Estimates such as these
+should not be used for decision making
+as if they were data.
diff --git a/www/text.causal.agency/005-testing-c.7 b/www/text.causal.agency/005-testing-c.7
new file mode 100644
index 00000000..d0c636ff
--- /dev/null
+++ b/www/text.causal.agency/005-testing-c.7
@@ -0,0 +1,73 @@
+.Dd December 21, 2018
+.Dt TESTING-C 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Testing C
+.Nd a simple unit testing setup
+.
+.Sh DESCRIPTION
+This is a simple approach
+to unit testing in C
+that I've used in a couple projects.
+At the bottom of a C file
+with some code I want to test,
+I add:
+.
+.Bd -literal -offset indent
+#ifdef TEST
+#include <assert.h>
+
+int main(void) {
+	assert(...);
+	assert(...);
+}
+
+#endif
+.Ed
+.
+.Pp
+This file normally produces a
+.Pa .o
+to be linked into the main binary.
+For testing,
+I produce separate binaries
+and run them with
+.Xr make 1 :
+.
+.Bd -literal -offset indent
+TESTS = foo.t bar.t
+
+\&.SUFFIXES: .t
+
+\&.c.t:
+	$(CC) $(CFLAGS) -DTEST $(LDFLAGS) $< $(LDLIBS) -o $@
+
+test: $(TESTS)
+	set -e; $(TESTS:%=./%;)
+.Ed
+.
+.Pp
+Note that the test binaries
+aren't linked with the rest of the code,
+so there is potential for simple stubbing or mocking.
+.
+.Pp
+To get the best output
+from C's simple
+.Xr assert 3 ,
+it's best to assert the result
+of a helper function
+which takes the expected output
+and the test input,
+rather than calling
+.Xr assert 3
+inside the helper function.
+This way,
+the message printed by the assert failure
+contains a useful line number
+and the expected output
+rather than just variable names.
+.
+.Sh AUTHORS
+.An Mt june@causal.agency
diff --git a/www/text.causal.agency/006-some-libs.7 b/www/text.causal.agency/006-some-libs.7
new file mode 100644
index 00000000..5af65404
--- /dev/null
+++ b/www/text.causal.agency/006-some-libs.7
@@ -0,0 +1,96 @@
+.Dd December 11, 2019
+.Dt SOME-LIBS 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Some Libraries
+.Nd good ones
+.
+.Sh DESCRIPTION
+This is a little list of C libraries
+I've had good experiences using.
+.
+.Bl -tag -width Ds
+.It Fl lcurl
+The library behind the
+.Xr curl 1
+command.
+It downloads or uploads things on the internet
+through a number of protocols,
+not just HTTP.
+It has an easy-to-use library API,
+appropriately named
+.Xr libcurl-easy 3 .
+I've used it to implement a
+.Lk https://causal.agency/bin/title.html "page title fetcher" .
+.
+.It Fl lcurses
+Okay so this one really isn't great.
+Its interfaces can seem archaic
+and its documentation is often poor.
+However, it gets the job done
+and is commonly available pretty much everywhere.
+Interesting to note that
+.Nx
+uses its own implementation of curses
+that is not GNU ncurses,
+unlike
+.Fx .
+.
+.It Fl ledit
+This is a BSD line editing library,
+similar to GNU readline.
+It supports right-aligned prompts,
+which I prefer for variable-length
+information in shells.
+.
+.It Fl lkcgi
+A CGI and FastCGI library
+for web applications in C.
+Don't worry,
+it isolates HTTP parsing and input validation
+from application logic
+in sandboxed processes.
+I think it's an excellent example
+of how to design an API for C.
+I used it to implement the
+.Lk https://ascii.town/explore.html "torus web viewer" .
+.
+.It Fl lsqlite3
+An embedded relational database engine.
+It's amazing what you can do with this,
+and it's super easy to use!
+My one gripe with it is that the library and SQL documentation
+are not available as
+.Xr man 1
+pages.
+I'm currently working on a project using SQLite,
+but it hasn't gotten very far yet.
+.
+.It Fl ltls
+This is a new library in LibreSSL
+which provides a much simpler interface for TLS sockets
+compared to
+.Fl lssl .
+It's much more like what you'd expect
+from other TLS socket wrappers,
+with calls like
+.Xr tls_connect 3 ,
+.Xr tls_read 3
+and
+.Xr tls_write 3 .
+I've used this for IRC clients, bouncers and bots.
+.
+.It Fl lz
+An implementation of the DEFLATE compression algorithm
+and gzip format.
+It's all documented in comments in
+.In zlib.h ,
+which isn't bad,
+but for my own use I copied the docs into
+.Lk https://code.causal.agency/june/zlib-man-pages "manual pages" .
+I've used this for decoding and encoding PNG images.
+.El
+.
+.Sh AUTHORS
+.An June Bug Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/007-cgit-setup.7 b/www/text.causal.agency/007-cgit-setup.7
new file mode 100644
index 00000000..44fb436a
--- /dev/null
+++ b/www/text.causal.agency/007-cgit-setup.7
@@ -0,0 +1,271 @@
+.Dd December 15, 2019
+.Dt CGIT-SETUP 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm cgit setup
+.Nd configuration notes
+.
+.Sh DESCRIPTION
+I just set up cgit on
+.Lk https://git.causal.agency
+to replace an instance of gitea.
+After 30 days of uptime,
+gitea had accumulated over 11 hours of CPU time
+and was using hundreds of megabytes of memory.
+cgit is much more lightweight
+and much more in line with my aesthetic.
+I'm documenting how I set it up here
+mostly to remind myself in the future.
+.
+.Ss slowcgi
+cgit is CGI software,
+but
+.Xr nginx 8
+only supports FastCGI.
+I used
+.Xr slowcgi 8
+as a compatibility layer
+by adding the following to
+.Pa /etc/rc.conf :
+.Bd -literal -offset indent
+slowcgi_enable="YES"
+slowcgi_flags="-p / -s /var/run/slowcgi.sock"
+.Ed
+.
+.Ss nginx
+I added the following in a new
+.Cm server
+block to
+.Pa /usr/local/etc/nginx/nginx.conf :
+.Bd -literal -offset indent
+root /usr/local/www/cgit;
+location / {
+	try_files $uri @cgit;
+}
+location @cgit {
+	fastcgi_pass unix:/var/run/slowcgi.sock;
+	fastcgi_param SCRIPT_FILENAME $document_root/cgit.cgi;
+	fastcgi_param SCRIPT_NAME /;
+	fastcgi_param PATH_INFO $uri;
+	fastcgi_param QUERY_STRING $query_string;
+	fastcgi_param REQUEST_METHOD $request_method;
+	fastcgi_param CONTENT_TYPE $content_type;
+	fastcgi_param CONTENT_LENGTH $content_length;
+	fastcgi_param HTTPS $https if_not_empty;
+	fastcgi_param SERVER_PORT $server_port;
+	fastcgi_param SERVER_NAME $server_name;
+}
+.Ed
+.
+.Pp
+The
+.Cm try_files
+directive causes
+.Xr nginx 8
+to first try to serve static files from
+.Pa /usr/local/www/cgit
+before passing anything else on to FastCGI.
+.
+.Pp
+The
+.Va SCRIPT_FILENAME
+parameter tells
+.Xr slowcgi 8
+the path of the CGI binary to run.
+Setting
+.Va SCRIPT_NAME
+to
+.Pa /
+tells cgit its root URL
+and avoids it using query strings for everything.
+.
+.Ss cgit
+cgit doesn't provide any configuration to start from,
+so you have to just read
+.Xr cgitrc 5 .
+I added the following to
+.Pa /usr/local/etc/cgitrc :
+.Bd -literal -offset indent
+cache-size=1024
+clone-url=https://$HTTP_HOST/$CGIT_REPO_URL
+snapshots=tar.gz zip
+remove-suffix=1
+enable-git-config=1
+scan-path=/home/june/pub
+.Ed
+.
+.Pp
+The
+.Cm cache-size
+option enables caching,
+which by default is stored in
+.Pa /var/cache/cgit ,
+so I made sure that directory exists
+and is writable by the
+.Sy www
+user.
+The
+.Cm clone-url
+option sets the clone URL to advertise.
+cgit will automatically serve git over HTTP.
+The
+.Cm snapshots
+option makes tarballs available for tags and commits.
+.
+.Pp
+The
+.Cm scan-path
+option causes cgit to scan the given path
+for git repositories.
+I'm putting mine in
+.Pa ~/pub .
+The
+.Cm remove-suffix
+option causes cgit to remove the
+.Pa .git
+suffix from the URLs it uses
+for the repositories it finds,
+so that
+.Pa ~/pub/pounce.git
+is served at
+.Pa /pounce .
+The
+.Cm enable-git-config
+option allows controlling some cgit options
+from the
+.Xr git-config 1
+of each repository.
+See
+.Sx git
+below.
+.
+.Pp
+I also set up a filter to render
+.Xr mdoc 7
+files
+and do syntax highlighting
+by adding the following to
+.Pa cgitrc :
+.Bd -literal -offset indent
+readme=:README.7
+readme=:README
+about-filter=/usr/local/libexec/cgit-filter
+source-filter=/usr/local/libexec/cgit-filter
+.Ed
+.
+.Pp
+The
+.Cm readme
+options tell cgit which files to look for
+to render the
+.Dq about
+page.
+The colon prefix causes it to look for them
+in the git tree.
+The
+.Pa /usr/local/libexec/cgit-filter
+script contains the following:
+.Bd -literal -offset indent
+#!/bin/sh
+case "$1" in
+	(*.[1-9])
+		/usr/bin/mandoc -T utf8 | /usr/local/libexec/ttpre
+		;;
+	(*)
+		exec /usr/local/libexec/hi -t -n "$1" -f html -o anchor
+		;;
+esac
+.Ed
+.
+.Pp
+Filter scripts are run with the filename as their first argument
+and the contents of the file on standard input.
+The
+.Xr ttpre 1
+command is my own utility to convert
+.Xr man 1
+output to HTML.
+The
+.Xr hi 1
+command is my own
+.Lk https://causal.agency/bin/hi.html "syntax highlighter" .
+.
+.Ss git
+I create my repositories in
+.Pa ~/pub
+with
+.Ql git init --bare
+and use
+.Pa git.causal.agency:pub/example.git
+locally as the remote.
+Descriptions are set by editing the
+.Pa description
+file in each repository.
+The section and homepage can be set with
+.Xr git-config 1
+through the keys
+.Cm cgit.section
+and
+.Cm cgit.homepage ,
+respectively,
+thanks to the
+.Cm enable-git-config
+option above.
+.
+.Ss Redirects
+I added the following to the
+.Cm server
+block that used to serve gitea in
+.Pa nginx.conf :
+.Bd -literal -offset indent
+location ~* /june/([^.]+)[.]git(.*) {
+	return 301 https://git.causal.agency/$1$2?$query_string;
+}
+location ~* /june/([^/]+) {
+	return 301 https://git.causal.agency/$1;
+}
+location / {
+	return 301 https://git.causal.agency;
+}
+.Ed
+.
+.Pp
+This redirects any links to my gitea repos
+to the corresponding repo in cgit.
+The first
+.Sy location
+block also redirects gitea HTTP clone URLs to cgit
+so that
+.Xr git-pull 1
+continues to work on existing clones.
+.
+.Ss Update: fast HTTPS clones
+Someone pointed out that cloning my repos
+over HTTPS was incredibly slow,
+and this is because cgit only implements the
+.Dq dumb
+HTTP git transport.
+To speed up cloning,
+I send the URLs used by the
+.Dq smart
+HTTP transport to
+.Xr git-http-backend 1
+instead:
+.Bd -literal -offset indent
+location ~ /.+/(info/refs|git-upload-pack) {
+	fastcgi_pass unix:/var/run/slowcgi.sock;
+	fastcgi_param SCRIPT_NAME /usr/local/libexec/git-core/git-http-backend;
+	fastcgi_param GIT_HTTP_EXPORT_ALL 1;
+	fastcgi_param GIT_PROJECT_ROOT /home/june/pub;
+	include fastcgi_params;
+}
+.Ed
+.
+.Pp
+I factored out the FastCGI parameters
+I'm using with cgit
+to be included here as well.
+.
+.Sh AUTHORS
+.An June Bug Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/008-how-irc.7 b/www/text.causal.agency/008-how-irc.7
new file mode 100644
index 00000000..aba1bbf9
--- /dev/null
+++ b/www/text.causal.agency/008-how-irc.7
@@ -0,0 +1,193 @@
+.Dd March  8, 2020
+.Dt HOW-IRC 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm How I Relay Chat
+.Nd in code
+.
+.Sh DESCRIPTION
+I've been writing a lot of IRC software lately
+.Pq Sx SEE ALSO ,
+and developed some nice code patterns
+that I've been reusing.
+Here they are.
+.
+.Ss Parsing
+I use fixed size buffers almost everywhere,
+so it's necessary to know IRC's size limits.
+A traditional IRC message is a maximum of 512 bytes,
+but the IRCv3 message-tags spec adds
+(unreasonably, in my opinion)
+8191 bytes for tags.
+IRC messages also have a maximum of 15 command parameters.
+.Bd -literal -offset indent
+enum { MessageCap = 8191 + 512 };
+enum { ParamCap = 15 };
+.Ed
+.
+.Pp
+If I'm using tags,
+I'll use X macros
+to declare the set I care about.
+X macros are a way of maintaining parallel arrays,
+or in this case an enum and an array.
+.Bd -literal -offset indent
+#define ENUM_TAG \e
+	X("msgid", TagMsgid) \e
+	X("time", TagTime)
+
+enum Tag {
+#define X(name, id) id,
+	ENUM_TAG
+#undef X
+	TagCap,
+};
+
+static const char *TagNames[TagCap] = {
+#define X(name, id) [id] = name,
+	ENUM_TAG
+#undef X
+};
+.Ed
+.
+.Pp
+The TagNames array is used by the parsing function
+to assign tag values into the message structure,
+which looks like this:
+.Bd -literal -offset indent
+struct Message {
+	char *tags[TagCap];
+	char *nick;
+	char *user;
+	char *host;
+	char *cmd;
+	char *params[ParamCap];
+};
+.Ed
+.
+.Pp
+I'm a fan of using
+.Xr strsep 3
+for simple parsing.
+Although it modifies its input
+(replacing delimiters with NUL terminators),
+since the raw message is in a static buffer,
+it is ideal for so-called zero-copy parsing.
+I'm not going to include the whole parsing function here,
+but I will at least include the part that many get wrong,
+which is dealing with the colon-prefixed trailing parameter:
+.Bd -literal -offset indent
+msg.cmd = strsep(&line, " ");
+for (int i = 0; line && i < ParamCap; ++i) {
+	if (line[0] == ':') {
+		msg.params[i] = &line[1];
+		break;
+	}
+	msg.params[i] = strsep(&line, " ");
+}
+.Ed
+.
+.Ss Handling
+To handle IRC commands and replies
+I add handler functions to a big array.
+I usually have some form of helper as well
+to check the number of expected parameters.
+.Bd -literal -offset indent
+typedef void HandlerFn(struct Message *msg);
+
+static const struct Handler {
+	const char *cmd;
+	HandlerFn *fn;
+} Handlers[] = {
+	{ "001", handleReplyWelcome },
+	{ "PING", handlePing },
+	{ "PRIVMSG", handlePrivmsg },
+};
+.Ed
+.
+.Pp
+Since I keep these arrays sorted anyway,
+I started using the standard
+.Xr bsearch 3
+function,
+but a basic for loop probably works just as well.
+I do wish I could compile-time assert
+that the array really is sorted, though.
+.Bd -literal -offset indent
+static int compar(const void *cmd, const void *_handler) {
+	const struct Handler *handler = _handler;
+	return strcmp(cmd, handler->cmd);
+}
+
+void handle(struct Message msg) {
+	if (!msg.cmd) return;
+	const struct Handler *handler = bsearch(
+		msg.cmd,
+		Handlers, ARRAY_LEN(Handlers),
+		sizeof(*handler), compar
+	);
+	if (handler) handler->fn(&msg);
+}
+.Ed
+.
+.Ss Capabilities
+For IRCv3 capabilties
+I use X macros again,
+this time with another handy macro
+for declaring bit flag enums.
+.Bd -literal -offset indent
+#define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit
+
+#define ENUM_CAP \e
+	X("message-tags", CapMessageTags) \e
+	X("sasl", CapSASL) \e
+	X("server-time", CapServerTime)
+
+enum Cap {
+#define X(name, id) BIT(id),
+	ENUM_CAP
+#undef X
+};
+
+static const char *CapNames[] = {
+#define X(name, id) [id##Bit] = name,
+	ENUM_CAP
+#undef X
+};
+.Ed
+.
+.Pp
+The
+.Fn BIT
+macro declares, for example,
+.Dv CapSASL
+as the bit flag and
+.Dv CapSASLBit
+as the corresponding index.
+The
+.Vt "enum Cap"
+is used as a set,
+for example checking if SASL is enabled with
+.Ql caps & CapSASL .
+.
+.Pp
+These patterns are serving my IRC software well,
+and my IRC projects are serving me well.
+It is immensely satisfying
+to be (near) constantly using software
+that I wrote myself and am happy with,
+regardless of how niche it may be.
+.
+.Sh SEE ALSO
+.Bl -item -compact
+.It
+.Lk https://git.causal.agency/pounce/about "IRC bouncer"
+.It
+.Lk https://git.causal.agency/litterbox/about "IRC logger"
+.It
+.Lk https://git.causal.agency/catgirl/about "IRC client"
+.El
+.
+.Sh AUTHORS
+.An June Bug Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/009-casual-update.7 b/www/text.causal.agency/009-casual-update.7
new file mode 100644
index 00000000..0548436a
--- /dev/null
+++ b/www/text.causal.agency/009-casual-update.7
@@ -0,0 +1,127 @@
+.Dd May  6, 2020
+.Dt CASUAL-UPDATE 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm casual update
+.Nd software developments
+.
+.Sh DESCRIPTION
+I've been figuring out more of IMAP
+and Internet Messages in general
+while working on a new project
+so I've revisited some older ones.
+I've copied my somewhat more proper
+IMAP parsing code into them,
+so they should be more robust.
+.
+.Pp
+.Xr imbox 1
+is my tool to export messages
+in mboxrd format directly from IMAP.
+It's mostly for applying patches sent by email
+without having any kind of local mail setup.
+For that,
+it includes the
+.Xr git-fetch-email 1
+wrapper which works very similarly to
+.Xr git-send-email 1 .
+I learned by reading the source of
+.Xr git-subtree 1
+that
+.Xr git-rev-parse 1
+can be used by shell scripts
+to parse long options,
+so I added those.
+I also added the
+.Fl Fl apply
+flag to automatically pipe to
+.Xr git-am 1
+with the right flags for mboxrd.
+.
+.Pp
+.Xr notemap 1
+is a tool for mirroring text files
+to an IMAP Notes mailbox,
+which is used by FastMail's web UI
+and the macOS/iOS Notes app.
+Its original parsing code
+was particularly ad-hoc.
+Since I've now learned
+how UTF-8 headers are encoded,
+I updated it to properly encode
+the file name in the Subject line.
+.
+.Pp
+I also got distracted by
+a conversation about UNIX-domain sockets
+where I was comparing the macOS and FreeBSD
+.Xr unix 4
+pages and the Linux
+.Xr unix 7
+page.
+This lead me to make
+.Xr exman 1 ,
+a tool to locally install and read
+manual pages for Linux, POSIX,
+.Fx ,
+.Nx
+and
+.Ox .
+I've already gotten quite a bit of use out of it.
+.
+.Pp
+In yet another IRC distraction,
+I was talking about some further plans for my IRC software,
+and realized it might be time to write
+my future projects list down.
+I opened a
+.Pa .plan
+file,
+immediately wondered how anyone can write plain text,
+then switched to a
+.Pa plan.7
+file.
+There's nothing I won't use
+.Xr mdoc 7
+for.
+After a little setup,
+I can now be fingered,
+and make jokes about this silly little protocol
+from the days of old.
+.Xr finger 1 Ap s
+default output fills me with joy:
+.Bd -unfilled -offset indent
+No Mail.
+No Plan.
+.Ed
+.
+.Pp
+And speaking of IRC and plans,
+I've been meaning to tag
+.Xr catgirl 1
+version 1.0 for a while now.
+I've been using it as my main client
+and my commits to it have really slowed down.
+When I do tag it,
+I'm planning on writing another post
+about my whole
+.Dq suite
+of IRC software
+and how the parts work together.
+Watch this space.
+.
+.Sh SEE ALSO
+.Bl -item -compact
+.It
+.Lk https://git.causal.agency/imbox "imbox"
+.It
+.Lk https://git.causal.agency/notemap "notemap"
+.It
+.Lk https://git.causal.agency/exman "exman"
+.It
+.Lk https://git.causal.agency/catgirl "catgirl"
+.El
+.
+.Sh AUTHORS
+.An June Bug Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/010-irc-suite.7 b/www/text.causal.agency/010-irc-suite.7
new file mode 100644
index 00000000..b54adf3d
--- /dev/null
+++ b/www/text.causal.agency/010-irc-suite.7
@@ -0,0 +1,409 @@
+.Dd June 19, 2020
+.Dt IRC-SUITE 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm IRC suite
+.Nd my own IRC software
+.
+.Sh DESCRIPTION
+Over the past months
+.Po
+eight of them, according to
+.Xr git-log 1
+.Pc
+I developed a new
+.Dq suite
+of IRC software
+that I now use full-time,
+consisting of a bouncer,
+a new logging and search solution,
+and a terminal client.
+These new programs share some characteristics:
+they are all TLS-only
+and use the libtls API from LibreSSL,
+they can all be entirely configured from the command line
+or with equivalent configuration files,
+they are all designed as
+a one process to one IRC connection mapping,
+and they all take advantage of IRCv3 features.
+.
+.Pp
+For context,
+I was previously running
+the znc IRC bouncer
+and using the Textual IRC client
+with its plain text logs.
+I also continue to use
+the Palaver IRC client for iOS.
+.
+.Ss Background
+A bouncer is a piece of server software
+that stays connected to IRC at all times
+and acts as a relay
+between your client and the IRC server.
+When the client is disconnected,
+the bouncer buffers incoming messages
+to send to the client when it reconnects.
+.
+.Pp
+Aside from this,
+bouncers have another advantage:
+client multiplexing.
+Several clients,
+for instance on different computers
+or a phone,
+should be able to connect to the same bouncer,
+and send and receive messages under the same nick.
+Unfortunately,
+znc does not handle this use-case well at all.
+Out of the box it offers two options:
+either any client connection totally clears the buffer,
+causing other clients to miss chat history;
+or the buffer is never cleared,
+causing every client connection
+to be repeatedly spammed with redundant history.
+There is also a znc wiki page
+that suggests one way to solve this issue
+is to connect znc to itself multiple times over.
+Yikes.
+.
+.Ss pounce
+My dissatisfaction with
+connecting multiple clients to znc
+directly motivated me to start working
+on a new multi-client-focused IRC bouncer.
+The result is
+.Xr pounce 1 ,
+based on a rather straightforward
+single-producer (the IRC server)
+multiple-consumer (each IRC client)
+ring buffer.
+Each client has its own
+independent position in the buffer
+so can properly be brought up to date
+whenever it connects.
+.
+.Pp
+Additionally,
+by assuming support for the IRCv3
+.Sy server-time
+extension,
+all IRC events can be accurately
+relayed to clients at any time,
+and the internals of
+.Xr pounce 1
+can be kept very simple.
+In fact,
+it completely avoids parsing most IRC messages,
+simply pushing them into the buffer
+with an associated timestamp.
+.
+.Pp
+The usernames sent by clients during registration
+are used as opaque identifiers for buffer consumers.
+This was chosen since most clients
+can be configured to send an arbitrary username,
+and those that don't often default
+to the name of the client itself,
+making it an appropriate identifier.
+.
+.Pp
+Later,
+I added a way for clients
+to be informed of their own buffer positions
+using a vendor-specific IRCv3 capability.
+This means a client
+can save the position
+of the last message it actually received,
+and request to set its position
+when it reconnects,
+ensuring no messages are lost
+to network issues
+or software crashes.
+.
+.Ss calico
+Due to the simple design of mapping
+one process to one IRC (server) connection,
+it is necessary to run several instances of
+.Xr pounce 1 .
+Initially I simply used different ports for each,
+but as I connected to more networks
+and even ran some instances for friends,
+it became less feasible.
+.
+.Pp
+The solution I came up with
+was to dispatch incoming connections
+using Server Name Indication, or SNI.
+This way,
+multiple domains pointing to the same host
+could be used with only one port
+to connect to different instances of
+.Xr pounce 1 .
+For example,
+I use a
+.Li *.irc.causal.agency
+wildcard DNS entry
+and a subdomain for each IRC network,
+all on port 6697.
+.
+.Pp
+The
+.Xr calico 1
+daemon included with pounce
+accomplishes this dispatch
+using the
+.Dv MSG_PEEK
+flag of
+.Xr recvmsg(2)
+on incoming connections.
+Since SNI is immediately sent by TLS clients
+as part of the ClientHello message in clear-text,
+it can be processed
+without doing any actual TLS.
+The connection itself is then
+sent to the corresponding
+.Xr pounce 1
+instance
+over UNIX-domain socket,
+which handles TLS as normal.
+This means that
+.Xr calico 1
+and
+.Xr pounce 1
+operate entirely independently of each other.
+.
+.Ss litterbox
+Based on the multiple-consumer ring buffer design,
+I realized it would be easy
+to implement additional functionality
+as independent purpose-built clients
+which connect to
+.Xr pounce 1
+alongside regular clients.
+This could allow dedicated OTR or DCC software
+to operate in parallel with a basic client,
+or for more passive software
+to provide notifications
+or dedicated logging.
+.
+.Pp
+For the latter,
+I wanted to do better than
+plain text log files.
+.Xr grep 1
+over files works fine,
+but search could be faster and smarter,
+and the text format is
+more lossy and less structured
+than I'd like it to be.
+Conveniently,
+SQLite provides an extension
+(actually two)
+for full-text search.
+.
+.Pp
+The litterbox project
+is my dedicated logging solution
+using SQLite FTS5.
+It consists of three tools:
+the
+.Xr litterbox 1
+daemon itself which connects to pounce
+and logs messages to SQLite,
+the
+.Xr scoop 1
+command line query tool,
+and the
+.Xr unscoop 1
+plain text import tool.
+The
+.Xr scoop 1
+tool constructs SQL queries
+and formats the results for viewing,
+with coloured nicks
+and piped to a pager
+by default.
+.
+.Pp
+The
+.Xr litterbox 1
+daemon
+can also provide a simple
+.Dq online
+.Pq over IRC
+search query interface
+to other connected clients.
+The simplest way to allow different
+.Xr pounce 1
+clients to talk to each other
+was to route private messages to self
+internally without sending them to the IRC server.
+So from any client
+I can simply message myself
+a full-text search query
+and
+.Xr litterbox 1
+responds with the results.
+.
+.Pp
+Along with routing self-messages,
+.Xr pounce 1
+also provides a vendor-specific IRCv3 capability
+for passive clients such as
+.Xr litterbox 1
+to indicate that they should not influence
+the automatic away status,
+which is normally only set
+when no clients are connected.
+.
+.Pp
+An advantage of this architecture
+of dedicated clients
+rather than bouncer modules
+is that they need not run
+on the same host.
+I run my bouncers on a VPS,
+but I'd rather not store my private logs there,
+so
+.Xr litterbox 1
+runs instead on a Raspberry Pi
+in my apartment.
+Also,
+since it is essentially
+just a regular IRC bot,
+it could be used independently
+for keeping public logs for a channel.
+.
+.Ss catgirl
+There's not really that much to say
+about the client,
+.Xr catgirl 1 .
+Of the three projects
+it contains the most code
+but is also the least interesting,
+in my opinion.
+It just does what I want a client to do,
+and gets the details right.
+.
+.Pp
+Tab complete is ordered by most recently seen or used,
+and completing several nicks
+inserts commas between them
+as well as the colon following the final nick.
+In the input line,
+the prompt is updated
+to reflect whether the input
+will be interpreted as a command or as a message.
+Messages are automatically scanned for URLs,
+which can be opened or copied with commands
+specifying the nick or a substring of the URL.
+.
+.Pp
+Scrolling in a window creates a split view,
+keeping the latest messages visible.
+Nick colours are based instead on usernames,
+keeping them more stable across renames,
+and mentions in messages are coloured
+to make the conversation easier to follow.
+The visibility of ignored messages
+can be toggled at any time.
+Channels can be muted
+so their activity is hidden
+from the status line
+unless you are pinged.
+.
+.Pp
+.Xr catgirl 1
+is configured entirely on the command line
+or in equivalent simple configuration files.
+There's no dynamic manipulation of state
+using complex
+.Ql /
+commands like in some other clients.
+.
+.Pp
+The major caveat is that
+.Xr catgirl 1
+connects to only one network at a time.
+This keeps the configuration, the interface
+and the code much simpler.
+.Xr tmux 1 ,
+.Xr screen 1
+or a tabbed terminal emulator
+are good options to run several instances.
+.
+.Pp
+If you're interested in giving
+.Xr catgirl 1
+a quick (and necessarily limited) try,
+you can
+.Li ssh chat@ascii.town .
+.
+.Ss Future
+I think I'm done with IRC software for now.
+As mentioned above,
+there are a few more pieces
+that could fit in to this setup,
+but I don't really want or need them right now.
+One thing I definitely want to try
+at some point
+is adding a litterbox component
+to index the contents of URLs
+to make finding previously shared links easier.
+.
+.Pp
+If you try any of this software
+and have feedback,
+let me know in
+.Li #ascii.town
+on freenode
+or by email.
+And of course,
+patches are always welcome.
+.
+.Ss Update: scooper
+Somehow I had the motivation
+to create a web interface for litterbox:
+.Xr scooper 1 .
+It can be used either as CGI
+or as a FastCGI worker,
+and I used the excellent
+.Xr kcgi 3
+library for it.
+.
+.Pp
+The main advantage of this interface
+is that you can click on a search result
+to be brought to its context in the log viewer.
+I also added an option to
+.Xr litterbox 1
+to provide a corresponding scooper link
+in response to its query interface.
+.
+.Pp
+A small demo of scooper is hosted at
+.Aq Lk "https://causal.agency/scooper/" .
+It publicly logs the
+.Li #litterbox
+channel on freenode.
+.
+.Sh SEE ALSO
+.Bl -item -compact
+.It
+.Lk "https://git.causal.agency/pounce" pounce
+.It
+.Lk "https://git.causal.agency/litterbox" litterbox
+.It
+.Lk "https://git.causal.agency/catgirl" catgirl
+.It
+.Lk "https://www.sqlite.org/fts5.html" "SQLite FTS5 Extension"
+.It
+.Lk "https://git.causal.agency/scooper" scooper
+.It
+.Lk "https://kristaps.bsd.lv/kcgi/" kcgi
+.El
+.
+.Sh AUTHORS
+.An June Bug Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/011-libretls.7 b/www/text.causal.agency/011-libretls.7
new file mode 100644
index 00000000..c29c325e
--- /dev/null
+++ b/www/text.causal.agency/011-libretls.7
@@ -0,0 +1,220 @@
+.Dd August  9, 2020
+.Dt LIBRETLS 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm LibreTLS
+.Nd libtls for OpenSSL
+.
+.Sh DESCRIPTION
+This is a sort of announcement post about LibreTLS,
+my port of libtls from LibreSSL to OpenSSL.
+If you've wanted to try any of my software
+but have been unable to because of LibreSSL,
+LibreTLS is an option that will likely work for you.
+I'm including instructions
+for building it and my IRC software
+on Debian as an example,
+since manually installing libraries
+is less straightforward than it could be.
+.
+.Pp
+libtls is
+.Do
+a new TLS library,
+designed to make it easier to write foolproof applications
+.Dc .
+It was developed as part of LibreSSL,
+.Ox Ap s
+fork of OpenSSL,
+and is implemented against their version of libssl.
+It provides a nice high-level API
+for TLS sockets,
+with functions like
+.Xr tls_connect 3 ,
+.Xr tls_read 3
+and
+.Xr tls_write 3 .
+This is a vast improvement over libssl's
+confusing mess of an API!
+Its relative obscurity is a real shame
+for C programmers.
+.
+.Pp
+An obvious cause of its obscurity
+is that it is tied to LibreSSL.
+Although LibreSSL is available
+for platforms other than
+.Ox ,
+it conflicts with OpenSSL
+so is difficult to install alongside it
+and is often not packaged at all.
+Additionally,
+even if a user manually installs LibreSSL,
+libtls is likely not to work on some distros
+due to its hardcoded CA bundle file path.
+.
+.Pp
+Since libtls is implemented against libssl,
+which originates in OpenSSL,
+it should be possible to use libtls with it.
+This is what I set out to do in LibreTLS.
+I started by importing the sources
+from a LibreSSL-portable release,
+then worked on porting the portions
+that were incompatible with OpenSSL.
+.
+.Pp
+The simpler changes just involved
+replacing internal struct field accesses
+with public APIs.
+libtls accesses libssl internals
+using a hack to get the header files
+to declare private struct fields,
+and for basically no reason.
+The bigger changes involved
+reimplementing some functions
+which only exist in LibreSSL,
+but these were still quite small.
+I also imported the necessary compatibility functions
+from LibreSSL's libcrypto
+and adapated the autotools build files
+to produce only a libtls
+which depends on OpenSSL.
+.
+.Pp
+Along the way
+I decided to make one small behavioural change
+in order for LibreTLS to be more likely
+to work for everyone.
+I removed the hardcoded CA file path
+and changed the default configuration
+to use OpenSSL's default CA paths,
+which include a CA directory.
+This seems to be the preferred CA source
+on systems such as Debian,
+where the default CA file path doesn't exist.
+.
+.Pp
+I think the reason LibreSSL
+wants to avoid using a CA directory
+is so that it can fully load the CA file
+once before being sandboxed.
+However,
+using OpenSSL's default configuration,
+the CA file will still be loaded immediately
+if it exists.
+If it doesn't exist,
+sandboxed applications
+will fail when trying to
+load certificates from the directory,
+but unsandboxed applications
+will work just fine.
+Since LibreSSL's libtls
+would fail either way,
+I think the new behaviour
+is an improvement.
+.
+.Pp
+Another advantage of separating libtls from LibreSSL
+is that it is unencumbered by OpenSSL's
+awkward double-license,
+both of which are incompatible with the GPL.
+libtls is all new ISC-licensed code,
+and future versions of OpenSSL (3.0)
+will be released under the Apache 2.0 license,
+which is compatible with GPLv3.
+In the future,
+GPL software will be able to link with
+libtls and OpenSSL without additional permissions.
+.
+.Pp
+It's also worth noting that LibreSSL
+likely will not be able to import any code
+from future versions of OpenSSL,
+since Apache 2.0 is on
+.Ox Ap s
+license shitlist.
+LLVM is also slowly changing their license
+to Apache 2.0,
+so it'll be interesting to see what
+.Ox
+does.
+.
+.Ss Installing Manually
+To install LibreTLS on Debian,
+for example,
+fetch a release tarball from
+.Lk https://causal.agency/libretls/
+and install the build dependencies:
+.Bd -literal -offset indent
+sudo apt-get install build-essential libssl-dev pkgconf
+.Ed
+.
+.Pp
+.Xr pkgconf 1
+isn't a dependency of LibreTLS itself,
+but it's how my software
+configures its build
+for a dependency on libtls.
+The usual build steps
+will install the library:
+.Bd -literal -offset indent
+\&./configure
+make all
+sudo make install
+.Ed
+.
+.Pp
+The library will be installed in
+.Pa /usr/local/lib
+by default,
+and you need to make sure
+the dynamic linker
+will be able to find it there.
+On Debian,
+.Pa /usr/local/lib
+already appears in
+.Pa /etc/ld.so.conf.d/libc.conf ,
+but on other systems
+you'll probably need to add it to either
+.Pa /etc/ld.so.conf
+or a new file such as
+.Pa /etc/ld.so.conf.d/local.conf .
+Once the library is installed
+and the path is configured,
+the linker cache needs to be refreshed:
+.Bd -literal -offset indent
+sudo ldconfig
+.Ed
+.
+.Pp
+You'll probably also need to set
+.Ev PKG_CONFIG_PATH
+for the configure scripts
+of my software:
+.Bd -literal -offset indent
+PKG_CONFIG_PATH=/usr/local/lib/pkgconfig ./configure
+.Ed
+.
+.Pp
+On
+.Fx ,
+LibreTLS and some of my IRC software
+can be installed from my own
+.Lk https://git.causal.agency/ports/ "ports tree"
+.
+.Sh SEE ALSO
+.Bl -item -compact
+.It
+.Lk https://git.causal.agency/libretls/about LibreTLS
+.It
+.Lk https://man.openbsd.org/tls_init.3 "libtls API documentation"
+.El
+.
+.Pp
+Another alternative libtls implementation,
+.Lk https://sr.ht/~mcf/libtls-bearssl/ "libtls-bearssl"
+.
+.Sh AUTHORS
+.An June Bug Aq Mt june@causal.agency
diff --git a/www/text.causal.agency/012-inability.7 b/www/text.causal.agency/012-inability.7
new file mode 100644
index 00000000..d352143b
--- /dev/null
+++ b/www/text.causal.agency/012-inability.7
@@ -0,0 +1,39 @@
+.Dd November 26, 2020
+.Dt INABILITY 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm Inability
+.Nd losing the ability to create
+.
+.Sh DESCRIPTION
+For often weeks, sometimes months at a time,
+I lose the ability to write new code.
+I can still make fixes
+and little cleanups
+in my existing projects,
+but if I try to work on something new,
+nothing happens.
+I can't get anything done.
+.
+.Pp
+I think it's now been
+over 3 months
+since I've created anything.
+I don't know what to do about it.
+In the past I've eventually
+regained the ability to code,
+but it's unclear to me how or why.
+I also don't know what
+I should be doing instead.
+Writing code is the only hobby
+I've ever really developed,
+so without it I basically
+don't do anything.
+.
+.Pp
+Does this happen to anyone else?
+How do you cope?
+.
+.Sh AUTHORS
+.Mt june@causal.agency
diff --git a/www/text.causal.agency/013-hot-tips.7 b/www/text.causal.agency/013-hot-tips.7
new file mode 100644
index 00000000..63b6e353
--- /dev/null
+++ b/www/text.causal.agency/013-hot-tips.7
@@ -0,0 +1,156 @@
+.Dd December  2, 2020
+.Dt HOT-TIPS 7
+.Os "Causal Agency"
+.
+.Sh NAME
+.Nm hot tips
+.Nd from my files
+.
+.Sh DESCRIPTION
+This is a short list of tips
+from my configuration files and code
+that might be useful.
+.
+.Ss Shell
+.Bl -tag -width Ds
+.It CDPATH=:~
+This is useful if you sometimes type,
+for example,
+.Ql cd src/bin
+wanting to go to
+.Pa ~/src/bin
+but you aren't in
+.Pa ~ .
+If the path doesn't exist
+in the current directory,
+.Ic cd
+will try it in
+.Pa ~
+as well.
+.
+.It alias ls='LC_COLLATE=C ls'
+This makes it so that
+.Xr ls 1
+lists files in ASCIIbetical order,
+which puts capitalized names like
+.Pa README
+and
+.Pa Makefile
+first.
+.
+.It git config --global commit.verbose true
+Not shell but close enough.
+This makes it so the entire diff is shown
+below the usual summary
+in the editor for a
+.Xr git-commit(1)
+message.
+Useful for doing a quick review
+of what you're committing.
+.El
+.
+.Ss (neo)vim
+.Bl -tag -width Ds
+.It set inccommand=nosplit
+This is the only
+.Xr nvim 1
+feature I really care about
+aside from the improved defaults.
+This provides a live preview of what a
+.Ic :s
+substitution command will do.
+It makes it much easier to
+write complex substitutions.
+.
+.It nmap <leader>s vip:sort<CR>
+This mapping sorts the lines of a paragraph,
+or block of text separated by blank lines.
+I use this a lot to sort
+#include directives.
+.
+.It nmap <leader>S $vi{:sort<CR>
+Similar to the last mapping,
+this one sorts lines inside braces.
+I use this to sort
+switch statement cases
+or array initializers.
+.
+.It nmap <leader>a m':0/^#include <<CR>:nohlsearch<CR>O#include <
+I use this mapping to add new
+#include directives,
+usually followed by
+.Ic <leader>s
+and
+.Ic ''
+to sort them
+and return to where I was.
+.
+.It nmap <leader>d :0delete<CR>:0read !date +'.Dd \e%B \e%e, \e%Y'<CR>
+I use this to replace the first line of
+.Xr mdoc 7
+files with the current date.
+.El
+.
+.Ss C
+.Bl -tag -width Ds
+.It #define Q(...) #__VA_ARGS__
+This is what I've started using
+to quote things like SQL statements
+or HTML fragments in C.
+Anything that happens to be valid C tokens,
+which is most code,
+can be quoted this way.
+Macros are not expanded
+inside the quoted part.
+You can embed (matched) quotes
+without having to escape them.
+Whitespace gets collapsed,
+so you can write nicely formatted multi-line SQL
+that doesn't mess up your debug logging,
+for example.
+.Bd -literal -offset indent
+const char *sql = Q(
+	INSERT OR IGNORE INTO names (nick, user, host)
+	VALUES (:nick, :user, :host);
+);
+.Ed
+.
+.It #define BIT(x) x##Bit, x = 1 << x##Bit, x##Bit_ = x##Bit
+I use this macro to declare bitflag enums.
+It takes advantage of
+auto-incrementing enum items
+so you don't need to set the values manually.
+You also get constants
+for both the bit index
+and the flag value
+for each item.
+.Bd -literal -offset indent
+enum Attr {
+	BIT(Bold),
+	BIT(Reverse),
+	BIT(Italic),
+	BIT(Underline),
+};
+.Ed
+.Pp
+For example,
+defines
+.Sy ItalicBit = 2
+and
+.Sy Italic = 1 << 2 .
+Ignore the extraneous constants.
+.
+.It typedef int FnType(const char *str, size_t len);
+You can just typedef function types!
+It annoys me more than it probably should
+that everyone writes ugly
+function pointer typedefs.
+Just stick
+.Sy typedef
+on the front of a function declaration
+and use
+.Vt FnType * .
+.El
+.
+.Sh AUTHORS
+.Mt june@causal.agency
diff --git a/www/text.causal.agency/Makefile b/www/text.causal.agency/Makefile
new file mode 100644
index 00000000..6a8fcc87
--- /dev/null
+++ b/www/text.causal.agency/Makefile
@@ -0,0 +1,31 @@
+WEBROOT = /usr/local/www/text.causal.agency
+
+TXTS += 001-make.txt
+TXTS += 002-writing-mdoc.txt
+TXTS += 003-pleasant-c.txt
+TXTS += 004-uloc.txt
+TXTS += 005-testing-c.txt
+TXTS += 006-some-libs.txt
+TXTS += 007-cgit-setup.txt
+TXTS += 008-how-irc.txt
+TXTS += 009-casual-update.txt
+TXTS += 010-irc-suite.txt
+TXTS += 011-libretls.txt
+TXTS += 012-inability.txt
+TXTS += 013-hot-tips.txt
+
+all: ${TXTS}
+
+.SUFFIXES: .7 .txt
+
+.7.txt:
+	mandoc -T utf8 $< | col -bx > $@
+
+feed.atom: feed.sh ${TXTS}
+	sh feed.sh > feed.atom
+
+clean:
+	rm -f ${TXTS} feed.atom
+
+install: ${TXTS} feed.atom
+	install -p -m 644 ${TXTS} feed.atom ${WEBROOT}
diff --git a/www/text.causal.agency/feed.sh b/www/text.causal.agency/feed.sh
new file mode 100644
index 00000000..fe028621
--- /dev/null
+++ b/www/text.causal.agency/feed.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+set -eu
+
+readonly Root='https://text.causal.agency'
+
+updated=$(date -u '+%FT%TZ')
+cat <<-EOF
+	<?xml version="1.0" encoding="utf-8"?>
+	<feed xmlns="http://www.w3.org/2005/Atom">
+	<title>Causal Agency</title>
+	<author><name>June</name><email>june@causal.agency</email></author>
+	<link href="${Root}"/>
+	<link rel="self" href="${Root}/feed.atom"/>
+	<id>${Root}/</id>
+	<updated>${updated}</updated>
+EOF
+
+encode() {
+	sed '
+		s/&/\&amp;/g
+		s/</\&lt;/g
+		s/"/\&quot;/g
+	' "$@"
+}
+
+for entry in *.7; do
+	txt="${entry%.7}.txt"
+	date=$(grep '^[.]Dd' "$entry" | cut -c 5-)
+	title=$(grep '^[.]Nm' "$entry" | cut -c 5- | encode)
+	summary=$(grep '^[.]Nd' "$entry" | cut -c 5- | encode)
+	published=$(date -ju -f '%B %d, %Y %T' "${date} 00:00:00" '+%FT%TZ')
+	mtime=$(stat -f '%m' "$entry")
+	updated=$(date -ju -f '%s' "$mtime" '+%FT%TZ')
+	cat <<-EOF
+		<entry>
+		<title>${title}</title>
+		<summary>${summary}</summary>
+		<link href="${Root}/${txt}"/>
+		<id>${Root}/${txt}</id>
+		<published>${published}</published>
+		<updated>${updated}</updated>
+		<content type="xhtml">
+		<div xmlns="http://www.w3.org/1999/xhtml">
+	EOF
+	printf '<pre>'
+	encode "$txt"
+	cat <<-EOF
+		</pre>
+		</div>
+		</content>
+		</entry>
+	EOF
+done
+
+echo '</feed>'