diff options
-rw-r--r-- | .gitignore | 46 | ||||
-rw-r--r-- | bin/.gitignore | 36 | ||||
-rw-r--r-- | bin/1sh/.gitignore | 13 | ||||
-rw-r--r-- | bin/1sh/1sh-kill.1 | 157 | ||||
-rw-r--r-- | bin/1sh/1sh-printf.1 | 385 | ||||
-rw-r--r-- | bin/1sh/1sh-test.1 | 395 | ||||
-rw-r--r-- | bin/1sh/1sh.1 | 2917 | ||||
-rw-r--r-- | bin/1sh/Makefile | 84 | ||||
-rw-r--r-- | bin/1sh/TOUR | 301 | ||||
-rw-r--r-- | bin/1sh/alias.c | 256 | ||||
-rw-r--r-- | bin/1sh/alias.h | 45 | ||||
-rw-r--r-- | bin/1sh/arith.h | 37 | ||||
-rw-r--r-- | bin/1sh/arith_yacc.c | 381 | ||||
-rw-r--r-- | bin/1sh/arith_yacc.h | 94 | ||||
-rw-r--r-- | bin/1sh/arith_yylex.c | 276 | ||||
-rw-r--r-- | bin/1sh/bltin.h | 81 | ||||
-rw-r--r-- | bin/1sh/builtins.def | 96 | ||||
-rw-r--r-- | bin/1sh/cd.c | 430 | ||||
-rw-r--r-- | bin/1sh/cd.h | 32 | ||||
-rw-r--r-- | bin/1sh/echo.c | 109 | ||||
-rw-r--r-- | bin/1sh/error.c | 199 | ||||
-rw-r--r-- | bin/1sh/error.h | 95 | ||||
-rw-r--r-- | bin/1sh/eval.c | 1381 | ||||
-rw-r--r-- | bin/1sh/eval.h | 70 | ||||
-rw-r--r-- | bin/1sh/exec.c | 784 | ||||
-rw-r--r-- | bin/1sh/exec.h | 76 | ||||
-rw-r--r-- | bin/1sh/expand.c | 1553 | ||||
-rw-r--r-- | bin/1sh/expand.h | 62 | ||||
-rw-r--r-- | bin/1sh/histedit.c | 558 | ||||
-rw-r--r-- | bin/1sh/input.c | 520 | ||||
-rw-r--r-- | bin/1sh/input.h | 65 | ||||
-rw-r--r-- | bin/1sh/jobs.c | 1569 | ||||
-rw-r--r-- | bin/1sh/jobs.h | 67 | ||||
-rw-r--r-- | bin/1sh/kill.c | 215 | ||||
-rw-r--r-- | bin/1sh/mail.c | 120 | ||||
-rw-r--r-- | bin/1sh/mail.h | 38 | ||||
-rw-r--r-- | bin/1sh/main.c | 356 | ||||
-rw-r--r-- | bin/1sh/main.h | 42 | ||||
-rw-r--r-- | bin/1sh/memalloc.c | 344 | ||||
-rw-r--r-- | bin/1sh/memalloc.h | 88 | ||||
-rw-r--r-- | bin/1sh/miscbltin.c | 534 | ||||
-rwxr-xr-x | bin/1sh/mkbuiltins | 137 | ||||
-rw-r--r-- | bin/1sh/mknodes.c | 461 | ||||
-rw-r--r-- | bin/1sh/mksyntax.c | 332 | ||||
-rw-r--r-- | bin/1sh/mktokens | 93 | ||||
-rw-r--r-- | bin/1sh/myhistedit.h | 45 | ||||
-rw-r--r-- | bin/1sh/mystring.c | 100 | ||||
-rw-r--r-- | bin/1sh/mystring.h | 43 | ||||
-rw-r--r-- | bin/1sh/nodes.c.pat | 193 | ||||
-rw-r--r-- | bin/1sh/nodetypes | 145 | ||||
-rw-r--r-- | bin/1sh/options.c | 594 | ||||
-rw-r--r-- | bin/1sh/options.h | 117 | ||||
-rw-r--r-- | bin/1sh/output.c | 376 | ||||
-rw-r--r-- | bin/1sh/output.h | 87 | ||||
-rw-r--r-- | bin/1sh/parser.c | 2182 | ||||
-rw-r--r-- | bin/1sh/parser.h | 88 | ||||
-rw-r--r-- | bin/1sh/printf.c | 686 | ||||
-rw-r--r-- | bin/1sh/redir.c | 365 | ||||
-rw-r--r-- | bin/1sh/redir.h | 47 | ||||
-rw-r--r-- | bin/1sh/shell.h | 77 | ||||
-rw-r--r-- | bin/1sh/show.c | 410 | ||||
-rw-r--r-- | bin/1sh/show.h | 42 | ||||
-rw-r--r-- | bin/1sh/test.c | 630 | ||||
-rw-r--r-- | bin/1sh/trap.c | 553 | ||||
-rw-r--r-- | bin/1sh/trap.h | 50 | ||||
-rw-r--r-- | bin/1sh/var.c | 983 | ||||
-rw-r--r-- | bin/1sh/var.h | 140 | ||||
-rw-r--r-- | bin/LICENSE | 661 | ||||
-rw-r--r-- | bin/Makefile | 153 | ||||
-rw-r--r-- | bin/README.7 | 83 | ||||
-rw-r--r-- | bin/beef.c | 141 | ||||
-rw-r--r-- | bin/bibsort.pl | 66 | ||||
-rw-r--r-- | bin/bit.y | 194 | ||||
-rw-r--r-- | bin/bri.c | 85 | ||||
-rw-r--r-- | bin/c.sh | 87 | ||||
-rw-r--r-- | bin/dash/.gitignore | 42 | ||||
-rw-r--r-- | bin/dash/COPYING (renamed from COPYING) | 0 | ||||
-rw-r--r-- | bin/dash/ChangeLog (renamed from ChangeLog) | 0 | ||||
-rw-r--r-- | bin/dash/ChangeLog.O (renamed from ChangeLog.O) | 0 | ||||
-rw-r--r-- | bin/dash/Makefile.am (renamed from Makefile.am) | 0 | ||||
-rwxr-xr-x | bin/dash/autogen.sh (renamed from autogen.sh) | 0 | ||||
-rw-r--r-- | bin/dash/configure.ac (renamed from configure.ac) | 0 | ||||
-rw-r--r-- | bin/dash/src/.gitignore (renamed from src/.gitignore) | 0 | ||||
-rw-r--r-- | bin/dash/src/Makefile.am (renamed from src/Makefile.am) | 0 | ||||
-rw-r--r-- | bin/dash/src/TOUR (renamed from src/TOUR) | 0 | ||||
-rw-r--r-- | bin/dash/src/alias.c (renamed from src/alias.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/alias.h (renamed from src/alias.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/arith_yacc.c (renamed from src/arith_yacc.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/arith_yacc.h (renamed from src/arith_yacc.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/arith_yylex.c (renamed from src/arith_yylex.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/bltin/bltin.h (renamed from src/bltin/bltin.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/bltin/echo.1 (renamed from src/bltin/echo.1) | 0 | ||||
-rw-r--r-- | bin/dash/src/bltin/printf.1 (renamed from src/bltin/printf.1) | 0 | ||||
-rw-r--r-- | bin/dash/src/bltin/printf.c (renamed from src/bltin/printf.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/bltin/test.1 (renamed from src/bltin/test.1) | 0 | ||||
-rw-r--r-- | bin/dash/src/bltin/test.c (renamed from src/bltin/test.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/bltin/times.c (renamed from src/bltin/times.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/builtins.def.in (renamed from src/builtins.def.in) | 0 | ||||
-rw-r--r-- | bin/dash/src/cd.c (renamed from src/cd.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/cd.h (renamed from src/cd.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/dash.1 (renamed from src/dash.1) | 0 | ||||
-rw-r--r-- | bin/dash/src/error.c (renamed from src/error.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/error.h (renamed from src/error.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/eval.c (renamed from src/eval.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/eval.h (renamed from src/eval.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/exec.c (renamed from src/exec.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/exec.h (renamed from src/exec.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/expand.c (renamed from src/expand.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/expand.h (renamed from src/expand.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/cmv (renamed from src/funcs/cmv) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/dirs (renamed from src/funcs/dirs) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/kill (renamed from src/funcs/kill) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/login (renamed from src/funcs/login) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/newgrp (renamed from src/funcs/newgrp) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/popd (renamed from src/funcs/popd) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/pushd (renamed from src/funcs/pushd) | 0 | ||||
-rw-r--r-- | bin/dash/src/funcs/suspend (renamed from src/funcs/suspend) | 0 | ||||
-rw-r--r-- | bin/dash/src/histedit.c (renamed from src/histedit.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/init.h (renamed from src/init.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/input.c (renamed from src/input.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/input.h (renamed from src/input.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/jobs.c (renamed from src/jobs.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/jobs.h (renamed from src/jobs.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/machdep.h (renamed from src/machdep.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/mail.c (renamed from src/mail.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/mail.h (renamed from src/mail.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/main.c (renamed from src/main.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/main.h (renamed from src/main.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/memalloc.c (renamed from src/memalloc.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/memalloc.h (renamed from src/memalloc.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/miscbltin.c (renamed from src/miscbltin.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/miscbltin.h (renamed from src/miscbltin.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/mkbuiltins (renamed from src/mkbuiltins) | 0 | ||||
-rw-r--r-- | bin/dash/src/mkinit.c (renamed from src/mkinit.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/mknodes.c (renamed from src/mknodes.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/mksignames.c (renamed from src/mksignames.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/mksyntax.c (renamed from src/mksyntax.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/mktokens (renamed from src/mktokens) | 0 | ||||
-rw-r--r-- | bin/dash/src/myhistedit.h (renamed from src/myhistedit.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/mystring.c (renamed from src/mystring.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/mystring.h (renamed from src/mystring.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/nodes.c.pat (renamed from src/nodes.c.pat) | 0 | ||||
-rw-r--r-- | bin/dash/src/nodetypes (renamed from src/nodetypes) | 0 | ||||
-rw-r--r-- | bin/dash/src/options.c (renamed from src/options.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/options.h (renamed from src/options.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/output.c (renamed from src/output.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/output.h (renamed from src/output.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/parser.c (renamed from src/parser.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/parser.h (renamed from src/parser.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/redir.c (renamed from src/redir.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/redir.h (renamed from src/redir.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/shell.h (renamed from src/shell.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/show.c (renamed from src/show.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/show.h (renamed from src/show.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/system.c (renamed from src/system.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/system.h (renamed from src/system.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/trap.c (renamed from src/trap.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/trap.h (renamed from src/trap.h) | 0 | ||||
-rw-r--r-- | bin/dash/src/var.c (renamed from src/var.c) | 0 | ||||
-rw-r--r-- | bin/dash/src/var.h (renamed from src/var.h) | 0 | ||||
-rw-r--r-- | bin/dtch.c | 271 | ||||
-rw-r--r-- | bin/ever.c | 116 | ||||
-rw-r--r-- | bin/fbatt.c | 123 | ||||
-rw-r--r-- | bin/fbclock.c | 132 | ||||
-rw-r--r-- | bin/glitch.c | 538 | ||||
-rw-r--r-- | bin/hi.c | 766 | ||||
-rw-r--r-- | bin/hnel.c | 120 | ||||
-rw-r--r-- | bin/html.sh | 41 | ||||
-rw-r--r-- | bin/man1/beef.1 | 91 | ||||
-rw-r--r-- | bin/man1/bibsort.1 | 37 | ||||
-rw-r--r-- | bin/man1/bit.1 | 45 | ||||
-rw-r--r-- | bin/man1/bri.1 | 44 | ||||
-rw-r--r-- | bin/man1/c.1 | 40 | ||||
-rw-r--r-- | bin/man1/dtch.1 | 67 | ||||
-rw-r--r-- | bin/man1/ever.1 | 49 | ||||
-rw-r--r-- | bin/man1/fbatt.1 | 34 | ||||
-rw-r--r-- | bin/man1/fbclock.1 | 36 | ||||
-rw-r--r-- | bin/man1/glitch.1 | 77 | ||||
-rw-r--r-- | bin/man1/hi.1 | 173 | ||||
-rw-r--r-- | bin/man1/hnel.1 | 36 | ||||
-rw-r--r-- | bin/man1/modem.1 | 31 | ||||
-rw-r--r-- | bin/man1/nudge.1 | 44 | ||||
-rw-r--r-- | bin/man1/order.1 | 38 | ||||
-rw-r--r-- | bin/man1/pbd.1 | 59 | ||||
-rw-r--r-- | bin/man1/pngo.1 | 56 | ||||
-rw-r--r-- | bin/man1/psf2png.1 | 53 | ||||
-rw-r--r-- | bin/man1/psfed.1 | 166 | ||||
-rw-r--r-- | bin/man1/ptee.1 | 40 | ||||
-rw-r--r-- | bin/man1/relay.1 | 48 | ||||
-rw-r--r-- | bin/man1/scheme.1 | 56 | ||||
-rw-r--r-- | bin/man1/shotty.1 | 92 | ||||
-rw-r--r-- | bin/man1/title.1 | 51 | ||||
-rw-r--r-- | bin/man1/ttpre.1 | 32 | ||||
-rw-r--r-- | bin/man1/up.1 | 76 | ||||
-rw-r--r-- | bin/man1/when.1 | 76 | ||||
-rw-r--r-- | bin/man1/xx.1 | 68 | ||||
-rw-r--r-- | bin/man3/png.3 | 90 | ||||
-rw-r--r-- | bin/modem.c | 102 | ||||
-rw-r--r-- | bin/nudge.c | 78 | ||||
-rw-r--r-- | bin/order.y | 193 | ||||
-rw-r--r-- | bin/pbd.c | 152 | ||||
-rw-r--r-- | bin/png.h | 108 | ||||
-rw-r--r-- | bin/pngo.c | 812 | ||||
-rw-r--r-- | bin/psf2png.c | 107 | ||||
-rw-r--r-- | bin/psfed.c | 577 | ||||
-rw-r--r-- | bin/ptee.c | 116 | ||||
-rw-r--r-- | bin/relay.c | 218 | ||||
-rw-r--r-- | bin/scheme.c | 256 | ||||
-rw-r--r-- | bin/shotty.c | 648 | ||||
-rw-r--r-- | bin/title.c | 211 | ||||
-rw-r--r-- | bin/ttpre.c | 66 | ||||
-rw-r--r-- | bin/up.sh | 76 | ||||
-rw-r--r-- | bin/when.y | 250 | ||||
-rw-r--r-- | bin/xx.c | 142 | ||||
-rw-r--r-- | doc/pdf/.gitignore | 1 | ||||
-rw-r--r-- | doc/pdf/Makefile | 31 | ||||
-rw-r--r-- | doc/rfc/.gitignore | 3 | ||||
-rw-r--r-- | doc/rfc/Makefile | 21 | ||||
-rw-r--r-- | doc/rfc/rfc.vim | 30 | ||||
-rw-r--r-- | doc/rfc/rfctags.pl | 28 | ||||
-rw-r--r-- | etc/CodeQWERTY.bundle/Contents/Info.plist | 19 | ||||
-rw-r--r-- | etc/CodeQWERTY.bundle/Contents/Resources/CodeQWERTY.keylayout | 1178 | ||||
-rw-r--r-- | etc/Dark.terminal | 1684 | ||||
-rw-r--r-- | etc/Go-Mono-Bold-Italic.ttf | bin | 0 -> 176832 bytes | |||
-rw-r--r-- | etc/Go-Mono-Bold.ttf | bin | 0 -> 168340 bytes | |||
-rw-r--r-- | etc/Go-Mono-Italic.ttf | bin | 0 -> 173548 bytes | |||
-rw-r--r-- | etc/Go-Mono.ttf | bin | 0 -> 164200 bytes | |||
-rw-r--r-- | etc/README.Go-Mono | 36 | ||||
-rw-r--r-- | etc/agpl.c | 20 | ||||
-rw-r--r-- | etc/code.map | 20 | ||||
-rw-r--r-- | etc/gpl.c | 20 | ||||
-rw-r--r-- | etc/psf/.gitignore | 2 | ||||
-rw-r--r-- | etc/psf/Makefile | 24 | ||||
-rw-r--r-- | etc/psf/default.u | 259 | ||||
-rw-r--r-- | etc/psf/sans6x10.psf | bin | 0 -> 2592 bytes | |||
-rw-r--r-- | etc/psf/sans6x12.psf | bin | 0 -> 3104 bytes | |||
-rw-r--r-- | etc/psf/sans6x8.psf | bin | 0 -> 2080 bytes | |||
-rw-r--r-- | etc/samba_mdns | 26 | ||||
-rw-r--r-- | home/.bash_profile | 2 | ||||
-rw-r--r-- | home/.bashrc | 13 | ||||
-rw-r--r-- | home/.config/git/config | 21 | ||||
-rw-r--r-- | home/.config/git/ignore | 2 | ||||
-rw-r--r-- | home/.config/htop/htoprc | 29 | ||||
-rw-r--r-- | home/.config/nvim/colors/trivial.vim | 65 | ||||
-rw-r--r-- | home/.config/nvim/ftdetect/mdoc.vim | 1 | ||||
-rw-r--r-- | home/.config/nvim/init.vim | 37 | ||||
-rw-r--r-- | home/.config/nvim/syntax/mdoc.vim | 12 | ||||
-rw-r--r-- | home/.editrc | 1 | ||||
-rw-r--r-- | home/.gdbinit | 1 | ||||
-rw-r--r-- | home/.hushlogin | 0 | ||||
-rw-r--r-- | home/.inputrc | 1 | ||||
-rw-r--r-- | home/.lldbinit | 1 | ||||
-rwxr-xr-x | home/.local/bin/aes | 7 | ||||
-rwxr-xr-x | home/.local/bin/def | 47 | ||||
-rwxr-xr-x | home/.local/bin/git-password | 7 | ||||
-rwxr-xr-x | home/.local/bin/nasd | 9 | ||||
-rwxr-xr-x | home/.local/bin/notify-send | 9 | ||||
-rwxr-xr-x | home/.local/bin/np | 7 | ||||
-rwxr-xr-x | home/.local/bin/versions | 9 | ||||
-rw-r--r-- | home/.profile | 21 | ||||
-rw-r--r-- | home/.shrc | 47 | ||||
-rw-r--r-- | home/.ssh/config | 18 | ||||
-rw-r--r-- | install.sh | 43 | ||||
-rw-r--r-- | link.sh | 23 | ||||
-rw-r--r-- | port/caesar/.gitignore | 2 | ||||
-rw-r--r-- | port/caesar/Makefile | 19 | ||||
-rw-r--r-- | port/caesar/caesar.6 | 73 | ||||
-rw-r--r-- | port/caesar/caesar.c | 159 | ||||
-rw-r--r-- | port/caesar/rot13.sh | 33 | ||||
-rw-r--r-- | port/cgram/.gitignore | 1 | ||||
-rw-r--r-- | port/cgram/Makefile | 17 | ||||
-rw-r--r-- | port/cgram/cgram.6 | 65 | ||||
-rw-r--r-- | port/cgram/cgram.c | 344 | ||||
-rw-r--r-- | port/cgram/pathnames.h | 30 | ||||
-rw-r--r-- | port/file2c/.gitignore | 1 | ||||
-rw-r--r-- | port/file2c/Makefile | 15 | ||||
-rw-r--r-- | port/file2c/file2c.1 | 75 | ||||
-rw-r--r-- | port/file2c/file2c.c | 92 | ||||
-rw-r--r-- | port/wcwidth/.gitignore | 3 | ||||
-rw-r--r-- | port/wcwidth/COPYRIGHT | 190 | ||||
-rw-r--r-- | port/wcwidth/Makefile | 27 | ||||
-rw-r--r-- | port/wcwidth/nonspacing.h | 89 | ||||
-rw-r--r-- | port/wcwidth/wcfix.in | 7 | ||||
-rw-r--r-- | port/wcwidth/wcswidth.c | 8 | ||||
-rw-r--r-- | port/wcwidth/wcwidth.c | 29 | ||||
-rw-r--r-- | port/wcwidth/wide.h | 65 | ||||
-rw-r--r-- | prune.sh | 7 | ||||
-rw-r--r-- | txt/.notemap | 3 | ||||
-rw-r--r-- | txt/books.txt | 114 | ||||
-rw-r--r-- | txt/music.txt | 285 | ||||
-rw-r--r-- | txt/plan.7 | 45 | ||||
-rw-r--r-- | txt/shows.txt | 32 | ||||
-rw-r--r-- | txt/trouble-at-jinx-hotel.txt | 236 | ||||
-rw-r--r-- | txt/tweets.txt | 29 | ||||
-rw-r--r-- | www/causal.agency/.gitignore | 3 | ||||
-rw-r--r-- | www/causal.agency/Makefile | 31 | ||||
-rw-r--r-- | www/causal.agency/catgirl.pty | 97 | ||||
-rw-r--r-- | www/causal.agency/index.html.in | 125 | ||||
-rw-r--r-- | www/causal.agency/index.sed | 5 | ||||
-rw-r--r-- | www/causal.agency/play.pty | 23 | ||||
-rw-r--r-- | www/causal.agency/scheme.pty | 10 | ||||
-rw-r--r-- | www/causal.agency/torus.pty | 774 | ||||
-rw-r--r-- | www/git.causal.agency/.gitignore | 3 | ||||
-rw-r--r-- | www/git.causal.agency/Makefile | 18 | ||||
-rw-r--r-- | www/git.causal.agency/about-filter.sh | 15 | ||||
-rw-r--r-- | www/git.causal.agency/cgitrc | 27 | ||||
-rw-r--r-- | www/git.causal.agency/custom.css | 87 | ||||
-rw-r--r-- | www/git.causal.agency/source-filter.sh | 3 | ||||
-rw-r--r-- | www/temp.causal.agency/.gitignore | 1 | ||||
-rw-r--r-- | www/temp.causal.agency/Makefile | 16 | ||||
-rw-r--r-- | www/temp.causal.agency/up.c | 156 | ||||
-rw-r--r-- | www/text.causal.agency/.gitignore | 2 | ||||
-rw-r--r-- | www/text.causal.agency/001-make.7 | 159 | ||||
-rw-r--r-- | www/text.causal.agency/002-writing-mdoc.7 | 138 | ||||
-rw-r--r-- | www/text.causal.agency/003-pleasant-c.7 | 120 | ||||
-rw-r--r-- | www/text.causal.agency/004-uloc.7 | 64 | ||||
-rw-r--r-- | www/text.causal.agency/005-testing-c.7 | 73 | ||||
-rw-r--r-- | www/text.causal.agency/006-some-libs.7 | 96 | ||||
-rw-r--r-- | www/text.causal.agency/007-cgit-setup.7 | 271 | ||||
-rw-r--r-- | www/text.causal.agency/008-how-irc.7 | 193 | ||||
-rw-r--r-- | www/text.causal.agency/009-casual-update.7 | 127 | ||||
-rw-r--r-- | www/text.causal.agency/010-irc-suite.7 | 409 | ||||
-rw-r--r-- | www/text.causal.agency/011-libretls.7 | 220 | ||||
-rw-r--r-- | www/text.causal.agency/012-inability.7 | 39 | ||||
-rw-r--r-- | www/text.causal.agency/013-hot-tips.7 | 156 | ||||
-rw-r--r-- | www/text.causal.agency/Makefile | 31 | ||||
-rw-r--r-- | www/text.causal.agency/feed.sh | 55 |
327 files changed, 42854 insertions, 42 deletions
diff --git a/.gitignore b/.gitignore index e349901a..3bcf7b3f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,42 +1,4 @@ -# .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 +/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/1sh/.gitignore b/bin/1sh/.gitignore new file mode 100644 index 00000000..54db1cf1 --- /dev/null +++ b/bin/1sh/.gitignore @@ -0,0 +1,13 @@ +*.o +.depend +1sh +builtins.c +builtins.h +config.mk +mknodes +mksyntax +nodes.c +nodes.h +syntax.c +syntax.h +token.h diff --git a/bin/1sh/1sh-kill.1 b/bin/1sh/1sh-kill.1 new file mode 100644 index 00000000..110fab7d --- /dev/null +++ b/bin/1sh/1sh-kill.1 @@ -0,0 +1,157 @@ +.\"- +.\" Copyright (c) 1980, 1990, 1993 +.\" The Regents of the University of California. 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. +.\" +.\" @(#)kill.1 8.2 (Berkeley) 4/28/95 +.\" $FreeBSD: releng/12.1/bin/kill/kill.1 314436 2017-02-28 23:42:47Z imp $ +.\" +.Dd October 3, 2016 +.Dt 1SH-KILL 1 +.Os +.Sh NAME +.Nm kill +.Nd terminate or signal a process +.Sh SYNOPSIS +.Nm +.Op Fl s Ar signal_name +.Ar pid ... +.Nm +.Fl l +.Op Ar exit_status +.Nm +.Fl Ar signal_name +.Ar pid ... +.Nm +.Fl Ar signal_number +.Ar pid ... +.Sh DESCRIPTION +The +.Nm +utility sends a signal to the processes specified by the +.Ar pid +operands. +.Pp +Only the super-user may send signals to other users' processes. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl s Ar signal_name +A symbolic signal name specifying the signal to be sent instead of the +default +.Dv TERM . +.It Fl l Op Ar exit_status +If no operand is given, list the signal names; otherwise, write +the signal name corresponding to +.Ar exit_status . +.It Fl Ar signal_name +A symbolic signal name specifying the signal to be sent instead of the +default +.Dv TERM . +.It Fl Ar signal_number +A non-negative decimal integer, specifying the signal to be sent instead +of the default +.Dv TERM . +.El +.Pp +The following PIDs have special meanings: +.Bl -tag -width indent +.It -1 +If superuser, broadcast the signal to all processes; otherwise broadcast +to all processes belonging to the user. +.El +.Pp +Some of the more commonly used signals: +.Pp +.Bl -tag -width indent -compact +.It 1 +HUP (hang up) +.It 2 +INT (interrupt) +.It 3 +QUIT (quit) +.It 6 +ABRT (abort) +.It 9 +KILL (non-catchable, non-ignorable kill) +.It 14 +ALRM (alarm clock) +.It 15 +TERM (software termination signal) +.El +.Pp +Some shells may provide a builtin +.Nm +command which is similar or identical to this utility. +Consult the +.Xr builtin 1 +manual page. +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Terminate +the processes with PIDs 142 and 157: +.Pp +.Dl "kill 142 157" +.Pp +Send the hangup signal +.Pq Dv SIGHUP +to the process with PID 507: +.Pp +.Dl "kill -s HUP 507" +.Pp +Terminate the process group with PGID 117: +.Pp +.Dl "kill -- -117" +.Sh SEE ALSO +.Xr builtin 1 , +.Xr csh 1 , +.Xr killall 1 , +.Xr ps 1 , +.Xr sh 1 , +.Xr kill 2 , +.Xr sigaction 2 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. +.Sh HISTORY +A +.Nm +command appeared in +.At v3 +in section 8 of the manual. +.Sh BUGS +A replacement for the command +.Dq Li kill 0 +for +.Xr csh 1 +users should be provided. diff --git a/bin/1sh/1sh-printf.1 b/bin/1sh/1sh-printf.1 new file mode 100644 index 00000000..33c4b9f5 --- /dev/null +++ b/bin/1sh/1sh-printf.1 @@ -0,0 +1,385 @@ +.\" Copyright (c) 1989, 1990, 1993 +.\" The Regents of the University of California. 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. +.\" +.\" @(#)printf.1 8.1 (Berkeley) 6/6/93 +.\" $FreeBSD: releng/12.1/usr.bin/printf/printf.1 350613 2019-08-05 20:19:38Z jilles $ +.\" +.Dd July 29, 2019 +.Dt 1SH-PRINTF 1 +.Os +.Sh NAME +.Nm printf +.Nd formatted output +.Sh SYNOPSIS +.Nm +.Ar format Op Ar arguments ... +.Sh DESCRIPTION +The +.Nm +utility 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 c , b +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 +character 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 the +.St -ansiC , +with extensions. +The characters and their meanings +are as follows: +.Pp +.Bl -tag -width Ds -offset indent -compact +.It Cm \ea +Write a <bell> character. +.It Cm \eb +Write a <backspace> character. +.It Cm \ef +Write a <form-feed> character. +.It Cm \en +Write a <new-line> character. +.It Cm \er +Write a <carriage return> character. +.It Cm \et +Write a <tab> character. +.It Cm \ev +Write a <vertical tab> character. +.It Cm \e\' +Write a <single quote> character. +.It Cm \e\e +Write a backslash character. +.It Cm \e Ns Ar num +Write a byte whose +value is the 1-, 2-, or 3-digit +octal number +.Ar num . +Multibyte characters can be constructed using multiple +.Cm \e Ns Ar num +sequences. +.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 ``alternate form''. +For +.Cm b , c , d , s +and +.Cm u +formats, this option has no effect. +For the +.Cm o +formats 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 a , A , e , E , f , F , 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 bytes 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; 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 diouxXfFeEgGaAcsb ) . +The uppercase formats differ from their lowercase counterparts only in +that the output of the former is entirely in uppercase. +The floating-point format specifiers +.Pq Cm fFeEgGaA +may be prefixed by an +.Cm L +to request that additional precision be used, if available. +.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 fF +The +.Ar argument +is printed in the style `[\-]ddd.ddd' 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. +The values \*[If] and \*[Na] are printed as +.Ql inf +and +.Ql nan , +respectively. +.It Cm eE +The +.Ar argument +is printed in the style +.Cm e +.Sm off +.Sq Op - Ar d.ddd No \(+- Ar dd +.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. +The values \*[If] and \*[Na] are printed as +.Ql inf +and +.Ql nan , +respectively. +.It Cm gG +The +.Ar argument +is printed in style +.Cm f +.Pq Cm F +or in style +.Cm e +.Pq Cm E +whichever gives full precision in minimum space. +.It Cm aA +The +.Ar argument +is printed in style +.Sm off +.Sq Op - Ar h.hhh No \(+- Li p Ar d +.Sm on +where there is one digit before the hexadecimal point and the number +after is equal to the precision specification for the argument; +when the precision is missing, enough digits are produced to convey +the argument's exact double-precision floating-point representation. +The values \*[If] and \*[Na] are printed as +.Ql inf +and +.Ql nan , +respectively. +.It Cm c +The first byte of +.Ar argument +is printed. +.It Cm s +Bytes 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; however if the +precision is 0 or missing, the string is printed entirely. +.It Cm b +As for +.Cm s , +but interpret character escapes in backslash notation in the string +.Ar argument . +The permitted escape sequences are slightly different in that +octal escapes are +.Cm \e0 Ns Ar num +instead of +.Cm \e Ns Ar num +and that an additional escape sequence +.Cm \ec +stops further output from this +.Nm +invocation. +.It Cm n$ +Allows reordering of the output according to +.Ar argument . +.It Cm \&% +Print a `%'; no argument is used. +.El +.Pp +The decimal point +character is defined in the program's locale (category +.Dv LC_NUMERIC ) . +.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. +.Pp +Some shells may provide a builtin +.Nm +command which is similar or identical to this utility. +Consult the +.Xr builtin 1 +manual page. +.Sh EXIT STATUS +.Ex -std +.Sh COMPATIBILITY +The traditional +.Bx +behavior of converting arguments of numeric formats not beginning +with a digit to the +.Tn ASCII +code of the first character is not supported. +.Sh SEE ALSO +.Xr builtin 1 , +.Xr echo 1 , +.Xr sh 1 , +.Xr printf 3 +.Sh STANDARDS +The +.Nm +command is expected to be compatible with the +.St -p1003.2 +specification. +.Sh HISTORY +The +.Nm +command appeared in +.Bx 4.3 Reno . +It is modeled +after the standard library function, +.Xr printf 3 . +.Sh CAVEATS +.Tn ANSI +hexadecimal character constants were deliberately not provided. +.Pp +Trying to print a dash ("-") as the first character causes +.Nm +to interpret the dash as a program argument. +.Nm -- +must be used before +.Ar format . +.Pp +If the locale contains multibyte characters +(such as UTF-8), +the +.Cm c +format and +.Cm b +and +.Cm s +formats with a precision +may not operate as expected. +.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. +(By default, the number is translated to an IEEE-754 double-precision +value before being printed. +The +.Cm L +modifier may produce additional precision, depending on the hardware platform.) +.Pp +The escape sequence \e000 is the string terminator. +When present in the argument for the +.Cm b +format, the argument will be truncated at the \e000 character. +.Pp +Multibyte characters are not recognized in format strings (this is only +a problem if +.Ql % +can appear inside a multibyte character). diff --git a/bin/1sh/1sh-test.1 b/bin/1sh/1sh-test.1 new file mode 100644 index 00000000..8ad8d0f4 --- /dev/null +++ b/bin/1sh/1sh-test.1 @@ -0,0 +1,395 @@ +.\"- +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. 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 +.\" $FreeBSD: releng/12.1/bin/test/test.1 314436 2017-02-28 23:42:47Z imp $ +.\" +.Dd October 5, 2016 +.Dt 1SH-TEST 1 +.Os +.Sh NAME +.Nm test , +.Nm \&[ +.Nd condition evaluation utility +.Sh SYNOPSIS +.Nm +.Ar expression +.Nm \&[ +.Ar expression Cm \&] +.Sh DESCRIPTION +The +.Nm +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, +.Nm +also +returns 1 (false). +.Pp +All operators and flags are separate arguments to the +.Nm +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. +This operator is retained for compatibility with previous versions of +this program. +Do not rely on its existence; use +.Fl L +instead. +.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 +.Pq Tn FIFO . +.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. +.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 s1 Cm = Ar s2 +True if the strings +.Ar s1 +and +.Ar s2 +are identical. +.It Ar s1 Cm != Ar s2 +True if the strings +.Ar s1 +and +.Ar s2 +are not identical. +.It Ar s1 Cm < Ar s2 +True if string +.Ar s1 +comes before +.Ar s2 +based on the binary value of their characters. +.It Ar s1 Cm > Ar s2 +True if string +.Ar s1 +comes after +.Ar s2 +based on the binary value of their characters. +.It Ar n1 Fl eq Ar n2 +True if the integers +.Ar n1 +and +.Ar n2 +are algebraically +equal. +.It Ar n1 Fl ne Ar n2 +True if the integers +.Ar n1 +and +.Ar n2 +are not +algebraically equal. +.It Ar n1 Fl gt Ar n2 +True if the integer +.Ar n1 +is algebraically +greater than the integer +.Ar n2 . +.It Ar n1 Fl ge Ar n2 +True if the integer +.Ar n1 +is algebraically +greater than or equal to the integer +.Ar n2 . +.It Ar n1 Fl lt Ar n2 +True if the integer +.Ar n1 +is algebraically less +than the integer +.Ar n2 . +.It Ar n1 Fl le Ar n2 +True if the integer +.Ar n1 +is algebraically less +than or equal to the integer +.Ar n2 . +.El +.Pp +If +.Ar file +is a symbolic link, +.Nm +will fully dereference it and then evaluate the expression +against the file referenced, except for the +.Fl h +and +.Fl L +primaries. +.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 \&( Ar expression Cm \&) +True if expression is true. +.El +.Pp +The +.Fl a +operator has higher precedence than the +.Fl o +operator. +.Pp +Some shells may provide a builtin +.Nm +command which is similar or identical to this utility. +Consult the +.Xr builtin 1 +manual page. +.Sh GRAMMAR AMBIGUITY +The +.Nm +grammar is inherently ambiguous. +In order to assure a degree of consistency, +the cases described in the +.St -p1003.2 , +section D11.2/4.62.4, standard +are evaluated consistently according to the rules specified in the +standards document. +All other cases are subject to the ambiguity in the +command semantics. +.Pp +In particular, only expressions containing +.Fl a , +.Fl o , +.Cm \&( +or +.Cm \&) +can be ambiguous. +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Bl -tag -width indent +.It 0 +expression evaluated to true. +.It 1 +expression evaluated to false or expression was +missing. +.It >1 +An error occurred. +.El +.Sh EXAMPLES +Implement +.Li test FILE1 -nt FILE2 +using only +.Tn POSIX +functionality: +.Pp +.Dl test -n \&"$(find -L -- FILE1 -prune -newer FILE2 2>/dev/null)\&" +.Pp +This can be modified using non-standard +.Xr find 1 +primaries like +.Cm -newerca +to compare other timestamps. +.Sh COMPATIBILITY +For compatibility with some other implementations, +the +.Cm = +primary can be substituted with +.Cm == +with the same meaning. +.Sh SEE ALSO +.Xr builtin 1 , +.Xr expr 1 , +.Xr find 1 , +.Xr sh 1 , +.Xr stat 1 , +.Xr symlink 7 +.Sh STANDARDS +The +.Nm +utility implements a superset of the +.St -p1003.2 +specification. +The primaries +.Cm < , +.Cm == , +.Cm > , +.Fl ef , +.Fl nt , +.Fl ot , +.Fl G , +and +.Fl O +are extensions. +.Sh HISTORY +A +.Nm +utility appeared in +.At v7 . +.Sh BUGS +Both sides are always evaluated in +.Fl a +and +.Fl o . +For instance, the writable status of +.Pa file +will be tested by the following command even though the former expression +indicated false, which results in a gratuitous access to the file system: +.Dl "[ -z abc -a -w file ]" +To avoid this, write +.Dl "[ -z abc ] && [ -w file ]" diff --git a/bin/1sh/1sh.1 b/bin/1sh/1sh.1 new file mode 100644 index 00000000..aa906bca --- /dev/null +++ b/bin/1sh/1sh.1 @@ -0,0 +1,2917 @@ +.\"- +.\" Copyright (c) 1991, 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. +.\" +.\" from: @(#)sh.1 8.6 (Berkeley) 5/4/95 +.\" $FreeBSD: releng/12.1/bin/sh/sh.1 345487 2019-03-24 22:10:26Z jilles $ +.\" +.Dd March 9, 2020 +.Dt 1SH 1 +.Os +.Sh NAME +.Nm 1sh +.Nd command interpreter (shell) +.Sh SYNOPSIS +.Nm +.Op Fl /+abCEefhIimnPpTuVvx +.Op Fl /+o Ar longname +.Oo +.Ar script +.Op Ar arg ... +.Oc +.Nm +.Op Fl /+abCEefhIimnPpTuVvx +.Op Fl /+o Ar longname +.Fl c Ar string +.Oo +.Ar name +.Op Ar arg ... +.Oc +.Nm +.Op Fl /+abCEefhIimnPpTuVvx +.Op Fl /+o Ar longname +.Fl s +.Op Ar arg ... +.Sh DESCRIPTION +The +.Nm +utility is a command interpreter. +The current version of +.Nm +is close to the +.St -p1003.1 +specification for the shell. +It only supports features +designated by POSIX, +plus a few Berkeley extensions. +This man page is not intended to be a tutorial nor 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 started 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, +which can be executed directly by the shell. +.Ss Invocation +.\" +.\" XXX This next sentence is incredibly confusing. +.\" +If no arguments are present and if the standard input of the shell +is connected to a terminal +(or if the +.Fl i +option is set), +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 +.Pq Ql - , +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 then +.Pa .profile +in a user's home directory, +if they exist. +If the environment variable +.Ev ENV +is set on entry to a shell, or is set in the +.Pa .profile +of a login shell, the shell then subjects its value to parameter expansion +and arithmetic expansion and reads commands from the named file. +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 shell inside the +.Ev ENV +file. +The user can set the +.Ev ENV +variable to some file by placing the following line in the file +.Pa .profile +in the home directory, +substituting for +.Pa .shrc +the filename desired: +.Pp +.Dl "ENV=$HOME/.shrc; export ENV" +.Pp +The first non-option argument specified on the command line +will be treated 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 +.Li ( $1 , $2 , +etc.). +Otherwise, the shell reads commands +from its standard input. +.Pp +Unlike older versions of +.Nm +the +.Ev ENV +script is only sourced on invocation of interactive shells. +This +closes a well-known, and sometimes easily exploitable security +hole related to poorly thought out +.Ev ENV +scripts. +.Ss Argument List Processing +All of the single letter options to +.Nm +have a corresponding long name, +with the exception of +.Fl c +and +.Fl /+o . +These long names are provided next to the single letter options +in the descriptions below. +The long name for an option may be specified as an argument to the +.Fl /+o +option of +.Nm . +Once the shell is running, +the long name for an option may be specified as an argument to the +.Fl /+o +option of the +.Ic set +built-in command +(described later in the section called +.Sx Built-in Commands ) . +Introducing an option with a dash +.Pq Ql - +enables the option, +while using a plus +.Pq Ql + +disables the option. +A +.Dq Li -- +or plain +.Ql - +will stop option processing and will force the remaining +words on the command line to be treated as arguments. +The +.Fl /+o +and +.Fl c +options do not have long names. +They take arguments and are described after the single letter options. +.Bl -tag -width indent +.It Fl a Li allexport +Flag variables for export when assignments are made to them. +.It Fl b Li notify +Enable asynchronous notification of background job +completion. +(UNIMPLEMENTED) +.It Fl C Li noclobber +Do not overwrite existing files with +.Ql > . +.It Fl E Li emacs +Enable the built-in +.Xr emacs 1 +command line editor (disables the +.Fl V +option if it has been set; +set automatically when interactive on terminals). +.It Fl e Li errexit +Exit immediately if any untested command fails in non-interactive mode. +The exit status of a command is considered to be +explicitly tested if the command is part of the list used to control +an +.Ic if , elif , while , +or +.Ic until ; +if the command is the left +hand operand of an +.Dq Li && +or +.Dq Li || +operator; or if the command is a pipeline preceded by the +.Ic !\& +keyword. +If a shell function is executed and its exit status is explicitly +tested, all commands of the function are considered to be tested as +well. +.Pp +It is recommended to check for failures explicitly +instead of relying on +.Fl e +because it tends to behave in unexpected ways, +particularly in larger scripts. +.It Fl f Li noglob +Disable pathname expansion. +.It Fl h Li trackall +A do-nothing option for POSIX compliance. +.It Fl I Li ignoreeof +Ignore +.Dv EOF Ap s +from input when in interactive mode. +.It Fl i Li interactive +Force the shell to behave interactively. +.It Fl m Li monitor +Turn on job control (set automatically when interactive). +A new process group is created for each pipeline (called a job). +It is possible to suspend jobs or to have them run in the foreground or +in the background. +In a non-interactive shell, +this option can be set even if no terminal is available +and is useful to place processes in separate process groups. +.It Fl n Li noexec +If not interactive, read commands but do not +execute them. +This is useful for checking the +syntax of shell scripts. +.It Fl P Li physical +Change the default for the +.Ic cd +and +.Ic pwd +commands from +.Fl L +(logical directory layout) +to +.Fl P +(physical directory layout). +.It Fl p Li privileged +Turn on privileged mode. +This mode is enabled on startup +if either the effective user or group ID is not equal to the +real user or group ID. +Turning this mode off sets the +effective user and group IDs to the real user and group IDs. +When this mode is enabled for interactive shells, the file +.Pa /etc/suid_profile +is sourced instead of +.Pa ~/.profile +after +.Pa /etc/profile +is sourced, and the contents of the +.Ev ENV +variable are ignored. +.It Fl s Li 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., when set with the +.Ic set +command). +.It Fl T Li trapsasync +When waiting for a child, execute traps immediately. +If this option is not set, +traps are executed after the child exits, +as specified in +.St -p1003.2 . +This nonstandard option is useful for putting guarding shells around +children that block signals. +The surrounding shell may kill the child +or it may just return control to the tty and leave the child alone, +like this: +.Bd -literal -offset indent +1sh -T -c "trap 'exit 1' 2 ; some-blocking-program" +.Ed +.It Fl u Li nounset +Write a message to standard error when attempting +to expand a variable, a positional parameter or +the special parameter +.Va \&! +that is not set, and if the +shell is not interactive, exit immediately. +.It Fl V Li vi +Enable the built-in +.Xr vi 1 +command line editor (disables +.Fl E +if it has been set). +.It Fl v Li verbose +The shell writes its input to standard error +as it is read. +Useful for debugging. +.It Fl x Li xtrace +Write each command +(preceded by the value of the +.Va PS4 +variable subjected to parameter expansion and arithmetic expansion) +to standard error before it is executed. +Useful for debugging. +.It Li nolog +Another do-nothing option for POSIX compliance. +It only has a long name. +.It Li pipefail +Change the exit status of a pipeline to the last non-zero exit status of +any command in the pipeline, if any. +Since an exit due to +.Dv SIGPIPE +counts as a non-zero exit status, +this option may cause non-zero exit status for successful pipelines +if a command such as +.Xr head 1 +in the pipeline terminates with status 0 without reading its +input completely. +This option only has a long name. +.El +.Pp +The +.Fl c +option causes the commands to be read from the +.Ar string +operand instead of from the standard input. +Keep in mind that this option only accepts a single string as its +argument, hence multi-word strings must be quoted. +.Pp +The +.Fl /+o +option takes as its only argument the long name of an option +to be enabled or disabled. +For example, the following two invocations of +.Nm +both enable the built-in +.Xr emacs 1 +command line editor: +.Bd -literal -offset indent +set -E +set -o emacs +.Ed +.Pp +If used without an argument, the +.Fl o +option displays the current option settings in a human-readable format. +If +.Cm +o +is used without an argument, the current option settings are output +in a format suitable for re-input into the shell. +.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 called +.Dq operators , +which are special to the shell. +There are two types of operators: control operators and +redirection operators (their meaning is discussed later). +The following is a list of valid operators: +.Bl -tag -width indent +.It Control operators: +.Bl -column "XXX" "XXX" "XXX" "XXX" "XXX" -offset center -compact +.It Li & Ta Li && Ta Li \&( Ta Li \&) Ta Li \en +.It Li ;; Ta Li ;& Ta Li \&; Ta Li \&| Ta Li || +.El +.It Redirection operators: +.Bl -column "XXX" "XXX" "XXX" "XXX" "XXX" -offset center -compact +.It Li < Ta Li > Ta Li << Ta Li >> Ta Li <> +.It Li <& Ta Li >& Ta Li <<- Ta Li >| Ta \& +.El +.El +.Pp +The character +.Ql # +introduces a comment if used at the beginning of a word. +The word starting with +.Ql # +and the rest of the line are ignored. +.Pp +ASCII +.Dv NUL +characters (character code 0) are not allowed in shell input. +.Ss Quoting +Quoting is used to remove the special meaning of certain characters +or words to the shell, such as operators, whitespace, keywords, +or alias names. +.Pp +There are four types of quoting: matched single quotes, +dollar-single quotes, +matched double quotes, and backslash. +.Bl -tag -width indent +.It 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). +.It Dollar-Single Quotes +Enclosing characters between +.Li $' +and +.Li ' +preserves the literal meaning of all characters +except backslashes and single quotes. +A backslash introduces a C-style escape sequence: +.Bl -tag -width xUnnnnnnnn +.It \ea +Alert (ring the terminal bell) +.It \eb +Backspace +.It \ec Ns Ar c +The control character denoted by +.Li ^ Ns Ar c +in +.Xr stty 1 . +If +.Ar c +is a backslash, it must be doubled. +.It \ee +The ESC character (ASCII 0x1b) +.It \ef +Formfeed +.It \en +Newline +.It \er +Carriage return +.It \et +Horizontal tab +.It \ev +Vertical tab +.It \e\e +Literal backslash +.It \e\&' +Literal single-quote +.It \e\&" +Literal double-quote +.It \e Ns Ar nnn +The byte whose octal value is +.Ar nnn +(one to three digits) +.It \ex Ns Ar nn +The byte whose hexadecimal value is +.Ar nn +(one or more digits only the last two of which are used) +.It \eu Ns Ar nnnn +The Unicode code point +.Ar nnnn +(four hexadecimal digits) +.It \eU Ns Ar nnnnnnnn +The Unicode code point +.Ar nnnnnnnn +(eight hexadecimal digits) +.El +.Pp +The sequences for Unicode code points are currently only useful with +UTF-8 locales. +They reject code point 0 and UTF-16 surrogates. +.Pp +If an escape sequence would produce a byte with value 0, +that byte and the rest of the string until the matching single-quote +are ignored. +.Pp +Any other string starting with a backslash is an error. +.It Double Quotes +Enclosing characters within double quotes preserves the literal +meaning of all characters except dollar sign +.Pq Ql $ , +backquote +.Pq Ql ` , +and backslash +.Pq Ql \e . +The backslash inside double quotes is historically weird. +It remains literal unless it precedes the following characters, +which it serves to quote: +.Pp +.Bl -column "XXX" "XXX" "XXX" "XXX" "XXX" -offset center -compact +.It Li $ Ta Li ` Ta Li \&" Ta Li \e Ta Li \en +.El +.It Backslash +A backslash preserves the literal meaning of the following +character, with the exception of the newline character +.Pq Ql \en . +A backslash preceding a newline is treated as a line continuation. +.El +.Ss Keywords +Keywords or 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 keywords: +.Bl -column "doneXX" "elifXX" "elseXX" "untilXX" "whileX" -offset center +.It Li \&! Ta { Ta } Ta Ic case Ta Ic do +.It Ic done Ta Ic elif Ta Ic else Ta Ic esac Ta Ic fi +.It Ic for Ta Ic if Ta Ic then Ta Ic until Ta Ic while +.El +.Ss Aliases +An alias is a name and corresponding value set using the +.Ic alias +built-in command. +Wherever the command word of a simple command may occur, +and after checking for keywords if a keyword may occur, 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 Li lf +with the value +.Dq Li "ls -F" , +then the input +.Pp +.Dl "lf foobar" +.Pp +would become +.Pp +.Dl "ls -F foobar" +.Pp +Aliases are also recognized after an alias +whose value ends with a space or tab. +For example, if there is also an alias called +.Dq Li nohup +with the value +.Dq Li "nohup " , +then the input +.Pp +.Dl "nohup lf foobar" +.Pp +would become +.Pp +.Dl "nohup ls -F foobar" +.Pp +Aliases provide a convenient way for naive users to +create shorthands for commands without having to learn how +to create functions with arguments. +Using aliases in scripts is discouraged +because the command that defines them must be executed +before the code that uses them is parsed. +This is fragile and not portable. +.Pp +An alias name may be escaped in a command line, so that it is not +replaced by its alias value, by using quoting characters within or +adjacent to the alias name. +This is most often done by prefixing +an alias name with a backslash to execute a function, built-in, or +normal program with the same name. +See the +.Sx Quoting +subsection. +.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 +.St -p1003.2 +document). +Essentially though, a line is read and if +the first word of the line (or after a control operator) +is not a keyword, 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 +.It +Leading words of the form +.Dq Li name=value +are stripped off and assigned to the environment of +the simple command +(they do not affect expansions). +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 +.Sx Word 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 Li name=value +variable assignments recognized in 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 +.D1 Oo Ar n Oc Ar redir-op file +.Pp +The +.Ar redir-op +is one of the redirection operators mentioned +previously. +The following gives some examples of how these +operators can be used. +Note that stdin and stdout are commonly used abbreviations +for standard input and standard output respectively. +.Bl -tag -width "1234567890XX" -offset indent +.It Oo Ar n Oc Ns Li > Ar file +redirect stdout (or file descriptor +.Ar n ) +to +.Ar file +.It Oo Ar n Oc Ns Li >| Ar file +same as above, but override the +.Fl C +option +.It Oo Ar n Oc Ns Li >> Ar file +append stdout (or file descriptor +.Ar n ) +to +.Ar file +.It Oo Ar n Oc Ns Li < Ar file +redirect stdin (or file descriptor +.Ar n ) +from +.Ar file +.It Oo Ar n Oc Ns Li <> Ar file +redirect stdin (or file descriptor +.Ar n ) +to and from +.Ar file +.It Oo Ar n1 Oc Ns Li <& Ns Ar n2 +duplicate stdin (or file descriptor +.Ar n1 ) +from file descriptor +.Ar n2 +.It Oo Ar n Oc Ns Li <&- +close stdin (or file descriptor +.Ar n ) +.It Oo Ar n1 Oc Ns Li >& Ns Ar n2 +duplicate stdout (or file descriptor +.Ar n1 ) +to file descriptor +.Ar n2 +.It Oo Ar n Oc Ns Li >&- +close stdout (or file descriptor +.Ar n ) +.El +.Pp +The following redirection is often called a +.Dq here-document . +.Bd -unfilled -offset indent +.Oo Ar n Oc Ns Li << Ar delimiter +.Ar here-doc-text +.Ar ... +.Ar delimiter +.Ed +.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 +.Ar n +if it is specified. +If the +.Ar delimiter +as specified on the initial line is quoted, then the +.Ar 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 +.Sx Word Expansions ) . +If the operator is +.Dq Li <<- +instead of +.Dq Li << , +then leading tabs +in the +.Ar here-doc-text +are stripped. +.Ss Search and Execution +There are three types of commands: shell functions, +built-in commands, and normal programs. +The command is searched for (by name) in that order. +The three types of commands are all executed in a different way. +.Pp +When a shell function is executed, all of the shell positional +parameters (except +.Li $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 built-in commands are executed internally to the shell, without +spawning a new process. +There are two kinds of built-in commands: regular and special. +Assignments before special builtins persist after they finish +executing and assignment errors, redirection errors and certain +operand errors cause a script to be aborted. +Special builtins cannot be overridden with a function. +Both regular and special builtins can affect the shell in ways +normal programs cannot. +.Pp +Otherwise, if the command name does not match a function +or built-in command, 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 +.Dq "magic number" +whose ASCII representation is +.Dq Li #! , +resulting in an +.Er ENOEXEC +return value from +.Xr execve 2 ) +but appears to be a text file, +the shell will run a new instance of +.Nm +to interpret it. +.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 +.Dq "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 +built-in command by that name. +If a built-in 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 the +.Va PATH +variable +in turn for the command. +The value of the +.Va 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 behavior +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 built-in commands return exit codes, as does +an executed shell function. +.Pp +If a command is terminated by a signal, its exit status is greater than 128. +The signal name can be found by passing the exit status to +.Li kill -l . +.Pp +If there is no command word, +the exit status is the exit status of the last command substitution executed, +or zero if the command does not contain any command substitutions. +.Ss Complex Commands +Complex commands are combinations of simple commands +with control operators or keywords, together creating a larger complex +command. +More generally, a command is one of the following: +.Bl -item -offset indent +.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, +or zero if no simple command was executed. +.Ss Pipelines +A pipeline is a sequence of one or more commands separated +by the control operator +.Ql \&| . +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 +.D1 Oo Li \&! Oc Ar command1 Op Li \&| Ar command2 ... +.Pp +The standard output of +.Ar command1 +is connected to the standard input of +.Ar 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 +Note that unlike some other shells, +.Nm +executes each process in a pipeline with more than one command +in a subshell environment and as a child of the +.Nm +process. +.Pp +If the pipeline is not in the background (discussed later), +the shell waits for all commands to complete. +.Pp +If the keyword +.Ic !\& +does not precede the pipeline, the +exit status is the exit status of the last command specified +in the pipeline if the +.Cm pipefail +option is not set or all commands returned zero, +or the last non-zero exit status of any command in the pipeline otherwise. +Otherwise, the exit status is the logical +NOT of that exit status. +That is, if +that status is zero, the exit status is 1; if +that status is 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>&1 | command2" +.Pp +sends both the standard output and standard error of +.Ar command1 +to the standard input of +.Ar command2 . +.Pp +A +.Ql \&; +or newline terminator causes the preceding +AND-OR-list +(described below in the section called +.Sx Short-Circuit List Operators ) +to be executed sequentially; +an +.Ql & +causes asynchronous execution of the preceding AND-OR-list. +.Ss Background Commands (&) +If a command is terminated by the control operator ampersand +.Pq Ql & , +the shell executes the command in a subshell environment (see +.Sx Grouping Commands Together +below) and asynchronously; +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 +.D1 Ar command1 Li & Op Ar command2 Li & Ar ... +.Pp +If the shell is not interactive, the standard input of an +asynchronous command is set to +.Pa /dev/null . +.Pp +The exit status is zero. +.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 Li && +and +.Dq Li || +are AND-OR list operators. +.Dq Li && +executes the first command, and then executes the second command +if the exit status of the first command is zero. +.Dq Li || +is similar, but executes the second command if the exit +status of the first command is nonzero. +.Dq Li && +and +.Dq Li || +both have the same priority. +.Ss Flow-Control Constructs (if, while, for, case) +The syntax of the +.Ic if +command is: +.Bd -unfilled -offset indent -compact +.Ic if Ar list +.Ic then Ar list +.Oo Ic elif Ar list +.Ic then Ar list Oc Ar ... +.Op Ic else Ar list +.Ic fi +.Ed +.Pp +The exit status is that of selected +.Ic then +or +.Ic else +list, +or zero if no list was selected. +.Pp +The syntax of the +.Ic while +command is: +.Bd -unfilled -offset indent -compact +.Ic while Ar list +.Ic do Ar list +.Ic done +.Ed +.Pp +The two lists are executed repeatedly while the exit status of the +first list is zero. +The +.Ic until +command is similar, but has the word +.Ic until +in place of +.Ic while , +which causes it to +repeat until the exit status of the first list is zero. +.Pp +The exit status is that of the last execution of the second list, +or zero if it was never executed. +.Pp +The syntax of the +.Ic for +command is: +.Bd -unfilled -offset indent -compact +.Ic for Ar variable Op Ic in Ar word ... +.Ic do Ar list +.Ic done +.Ed +.Pp +If +.Ic in +and the following words are omitted, +.Ic in Li \&"$@\&" +is used instead. +The words are expanded, and then the list is executed +repeatedly with the variable set to each word in turn. +The +.Ic do +and +.Ic done +commands may be replaced with +.Ql { +and +.Ql } . +.Pp +The syntax of the +.Ic break +and +.Ic continue +commands is: +.D1 Ic break Op Ar num +.D1 Ic continue Op Ar num +.Pp +The +.Ic break +command terminates the +.Ar num +innermost +.Ic for +or +.Ic while +loops. +The +.Ic continue +command continues with the next iteration of the innermost loop. +These are implemented as special built-in commands. +.Pp +The syntax of the +.Ic case +command is: +.Bd -unfilled -offset indent -compact +.Ic case Ar word Ic in +.Ar pattern ) Ar list Li ;; +.Ar ... +.Ic esac +.Ed +.Pp +The pattern can actually be one or more patterns +(see +.Sx Shell Patterns +described later), +separated by +.Ql \&| +characters. +Tilde expansion, parameter expansion, command substitution, +arithmetic expansion and quote removal are applied to the word. +Then, each pattern is expanded in turn using tilde expansion, +parameter expansion, command substitution and arithmetic expansion and +the expanded form of the word is checked against it. +If a match is found, the corresponding list is executed. +If the selected list is terminated by the control operator +.Ql ;& +instead of +.Ql ;; , +execution continues with the next list, +continuing until a list terminated with +.Ql ;; +or the end of the +.Ic case +command. +.Ss Grouping Commands Together +Commands may be grouped by writing either +.Pp +.Sm off +.Bd -literal -offset -ident +.Po Ar list Pc +.Ed +.Sm on +.Pp +or +.Bd -literal -offset -ident +.No { Ar list ; } +.Ed +.Pp +The first form executes the commands in a subshell environment. +A subshell environment has its own copy of: +.Bl -enum +.It +The current working directory as set by +.Ic cd . +.It +The file creation mask as set by +.Ic umask . +.It +Resource limits as set by +.Ic ulimit . +.It +References to open files. +.It +Traps as set by +.Ic trap . +.It +Known jobs. +.It +Positional parameters and variables. +.It +Shell options. +.It +Shell functions. +.It +Shell aliases. +.El +.Pp +These are copied from the parent shell environment, +except that trapped (but not ignored) signals are reset to the default action +and known jobs are cleared. +Any changes do not affect the parent shell environment. +.Pp +A subshell environment may be implemented as a child process or differently. +If job control is enabled in an interactive shell, +commands grouped in parentheses can be suspended and continued as a unit. +.Pp +For compatibility with other shells, +two open parentheses in sequence should be separated by whitespace. +.Pp +The second form never forks another shell, +so it is slightly more efficient. +Grouping commands together this way allows the user to +redirect their output as though they were one program: +.Bd -literal -offset indent +{ echo -n "hello"; echo " world"; } > greeting +.Ed +.Ss Functions +The syntax of a function definition is +.Pp +.D1 Ar name Li \&( \&) Ar command +.Pp +A function definition is an executable statement; when +executed it installs a function named +.Ar name +and returns an +exit status of zero. +The +.Ar command +is normally a list +enclosed between +.Ql { +and +.Ql } . +.Pp +Variables may be declared to be local to a function by +using the +.Ic local +command. +This should appear as the first statement of a function, +and the syntax is: +.Pp +.D1 Ic local Oo Ar variable ... Oc Op Fl +.Pp +The +.Ic local +command is implemented as a built-in command. +The exit status is zero +unless the command is not in a function or a variable name is invalid. +.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 the variable +.Va x +is made local to function +.Em f , +which then calls function +.Em g , +references to the variable +.Va x +made inside +.Em g +will refer to the variable +.Va x +declared inside +.Em f , +not to the global variable named +.Va x . +.Pp +The only special parameter that can be made local is +.Ql - . +Making +.Ql - +local causes any shell options +(including those that only have long names) +that are +changed via the +.Ic set +command inside the function to be +restored to their original values when the function +returns. +.Pp +The syntax of the +.Ic return +command is +.Pp +.D1 Ic return Op Ar exitstatus +.Pp +It terminates the current executional scope, returning from the closest +nested function or sourced script; +if no function or sourced script is being executed, +it exits the shell instance. +The +.Ic return +command is implemented as a special built-in command. +.Ss Variables and Parameters +The shell maintains a set of parameters. +A parameter +denoted by a name +(consisting solely +of alphabetics, numerics, and underscores, +and starting with an alphabetic or an underscore) +is called a variable. +When starting up, +the shell turns all environment variables with valid names into shell +variables. +New variables can be set using the form +.Pp +.D1 Ar name Ns = Ns Ar value +.Pp +A parameter can also be denoted by a number +or a special character as explained below. +.Pp +Assignments are expanded differently from other words: +tilde expansion is also performed after the equals sign and after any colon +and usernames are also terminated by colons, +and field splitting and pathname expansion are not performed. +.Pp +This special expansion applies not only to assignments that form a simple +command by themselves or precede a command word, +but also to words passed to the +.Ic export , +.Ic local +or +.Ic readonly +built-in commands that have this form. +For this, the builtin's name must be literal +(not the result of an expansion) +and may optionally be preceded by one or more literal instances of +.Ic command +without options. +.Ss Positional Parameters +A positional parameter is a parameter denoted by a number greater than zero. +The shell sets these initially to the values of its command line +arguments that follow the name of the shell script. +The +.Ic set +built-in command can also be used to set or reset them. +.Ss Special Parameters +Special parameters are parameters denoted by a single special character +or the digit zero. +They are shown in the following list, exactly as they would appear in input +typed by the user or in the source of a shell script. +.Bl -hang +.It Li $* +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 +.Va IFS +variable, +or by a space if +.Va IFS +is unset. +.It Li $@ +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 +.Li @ +generates zero arguments, even when +.Li @ +is double-quoted. +What this basically means, for example, is +if +.Li $1 +is +.Dq Li abc +and +.Li $2 +is +.Dq Li "def ghi" , +then +.Li \&"$@\&" +expands to +the two arguments: +.Bd -literal -offset indent +"abc" "def ghi" +.Ed +.It Li $# +Expands to the number of positional parameters. +.It Li $? +Expands to the exit status of the most recent pipeline. +.It Li $- +(hyphen) Expands to the current option flags (the single-letter +option names concatenated into a string) as specified on +invocation, by the +.Ic set +built-in command, or implicitly +by the shell. +.It Li $$ +Expands to the process ID of the invoked shell. +A subshell +retains the same value of +.Va $ +as its parent. +.It Li $! +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. +If this parameter is referenced, the shell will remember +the process ID and its exit status until the +.Ic wait +built-in command reports completion of the process. +.It Li $0 +(zero) Expands to the name of the shell script if passed on the command line, +the +.Ar name +operand if given (with +.Fl c ) +or otherwise argument 0 passed to the shell. +.El +.Ss Special Variables +The following variables are set by the shell or +have special meaning to it: +.Bl -tag -width ".Va HISTSIZE" +.It Va CDPATH +The search path used with the +.Ic cd +built-in. +.It Va EDITOR +The fallback editor used with the +.Ic fc +built-in. +If not set, the default editor is +.Xr ed 1 . +.It Va FCEDIT +The default editor used with the +.Ic fc +built-in. +.It Va HISTFILE +The path to the file in which command history is saved. +History is loaded when +.Va HISTFILE +is set and saved on exit. +.It Va HISTSIZE +The number of previous commands that are accessible. +.It Va HOME +The user's home directory, +used in tilde expansion and as a default directory for the +.Ic cd +built-in. +.It Va IFS +Input Field Separators. +This is initialized at startup to +.Aq space , +.Aq tab , +and +.Aq newline +in that order. +This value also applies if +.Va IFS +is unset, but not if it is set to the empty string. +See the +.Sx White Space Splitting +section for more details. +.It Va LINENO +The current line number in the script or function. +.It Va MAIL +The name of a mail file, that will be checked for the arrival of new +mail. +Overridden by +.Va MAILPATH . +.It Va MAILPATH +A colon +.Pq Ql \&: +separated list of file names, for the shell to check for incoming +mail. +This variable overrides the +.Va MAIL +setting. +There is a maximum of 10 mailboxes that can be monitored at once. +.It Va OPTIND +The index of the next argument to be processed by +.Ic getopts . +This is initialized to 1 at startup. +.It Va PATH +The default search path for executables. +See the +.Sx Path Search +section for details. +.It Va PPID +The parent process ID of the invoked shell. +This is set at startup +unless this variable is in the environment. +A later change of parent process ID is not reflected. +A subshell retains the same value of +.Va PPID . +.It Va PS0 +The pre-prompt string, +which is printed before each new prompt. +.Va PS0 +may include any of the formatting sequences from +.Va PS1 . +.It Va PS1 +The primary prompt string, which defaults to +.Dq Li "$ " , +unless you are the superuser, in which case it defaults to +.Dq Li "# " . +.Va PS1 +may include any of the following formatting sequences, +which are replaced by the given information: +.Bl -tag -width indent +.It Li \eH +This system's fully-qualified hostname (FQDN). +.It Li \eh +This system's hostname. +.It Li \eW +The final component of the current working directory. +.It Li \ew +The entire path of the current working directory. +.It Li \e? +Exit status if it is non-zero, empty string otherwise. +.It Li \e$ +Superuser status. +.Dq Li "$ " +for normal users and +.Dq Li "# " +for superusers. +.It Li \e\e +A literal backslash. +.El +.It Va PS2 +The secondary prompt string, which defaults to +.Dq Li "> " . +.Va PS2 +may include any of the formatting sequences from +.Va PS1 . +.It Va PS4 +The prefix for the trace output (if +.Fl x +is active). +The default is +.Dq Li "+ " . +.It Va RPS1 +The primary right prompt string. +.Va RPS1 +may include any of the formatting sequences from +.Va PS1 . +.It Va RPS2 +The secondary right prompt string. +.Va RPS2 +may include any of the formatting sequences from +.Va PS1 . +.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 +.Va @ +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 +.Va IFS +variable is null. +.It +Pathname Expansion (unless the +.Fl f +option is in effect). +.It +Quote Removal. +.El +.Pp +The +.Ql $ +character is used to introduce parameter expansion, command +substitution, or arithmetic expansion. +.Ss Tilde Expansion (substituting a user's home directory) +A word beginning with an unquoted tilde character +.Pq Ql ~ +is +subjected to tilde expansion. +All the characters up to a slash +.Pq Ql / +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 +.D1 Li ${ Ns Ar expression Ns Li } +.Pp +where +.Ar expression +consists of all characters until the matching +.Ql } . +Any +.Ql } +escaped by a backslash or within a single-quoted or double-quoted +string, and characters in +embedded arithmetic expansions, command substitutions, and variable +expansions, are not examined in determining the matching +.Ql } . +If the variants with +.Ql + , +.Ql - , +.Ql = +or +.Ql ?\& +occur within a double-quoted string, +as an extension there may be unquoted parts +(via double-quotes inside the expansion); +.Ql } +within such parts are also not examined in determining the matching +.Ql } . +.Pp +The simplest form for parameter expansion is: +.Pp +.D1 Li ${ Ns Ar parameter Ns Li } +.Pp +The value, if any, of +.Ar 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 +Field splitting is not performed on the results of the +expansion, with the exception of the special parameter +.Va @ . +.It +Pathname expansion is not performed on the results of the +expansion. +.El +.Pp +In addition, a parameter expansion can be modified by using one of the +following formats. +.Bl -tag -width indent +.It Li ${ Ns Ar parameter Ns Li :- Ns Ar word Ns Li } +Use Default Values. +If +.Ar parameter +is unset or null, the expansion of +.Ar word +is substituted; otherwise, the value of +.Ar parameter +is substituted. +.It Li ${ Ns Ar parameter Ns Li := Ns Ar word Ns Li } +Assign Default Values. +If +.Ar parameter +is unset or null, the expansion of +.Ar word +is assigned to +.Ar parameter . +In all cases, the +final value of +.Ar parameter +is substituted. +Quoting inside +.Ar word +does not prevent field splitting or pathname expansion. +Only variables, not positional +parameters or special parameters, can be +assigned in this way. +.It Li ${ Ns Ar parameter Ns Li :? Ns Oo Ar word Oc Ns Li } +Indicate Error if Null or Unset. +If +.Ar parameter +is unset or null, the expansion of +.Ar word +(or a message indicating it is unset if +.Ar word +is omitted) is written to standard +error and the shell exits with a nonzero +exit status. +Otherwise, the value of +.Ar parameter +is substituted. +An +interactive shell need not exit. +.It Li ${ Ns Ar parameter Ns Li :+ Ns Ar word Ns Li } +Use Alternate Value. +If +.Ar parameter +is unset or null, null is substituted; +otherwise, the expansion of +.Ar 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. +.Pp +The +.Ar word +inherits the type of quoting +(unquoted, double-quoted or here-document) +from the surroundings, +with the exception that a backslash that quotes a closing brace is removed +during quote removal. +.Bl -tag -width indent +.It Li ${# Ns Ar parameter Ns Li } +String Length. +The length in characters of +the value of +.Ar 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 one of the special parameters +.Va * +or +.Va @ , +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 indent +.It Li ${ Ns Ar parameter Ns Li % Ns Ar word Ns Li } +Remove Smallest Suffix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar parameter , +with the smallest portion of the +suffix matched by the pattern deleted. +.It Li ${ Ns Ar parameter Ns Li %% Ns Ar word Ns Li } +Remove Largest Suffix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar parameter , +with the largest portion of the +suffix matched by the pattern deleted. +.It Li ${ Ns Ar parameter Ns Li # Ns Ar word Ns Li } +Remove Smallest Prefix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar parameter , +with the smallest portion of the +prefix matched by the pattern deleted. +.It Li ${ Ns Ar parameter Ns Li ## Ns Ar word Ns Li } +Remove Largest Prefix Pattern. +The +.Ar word +is expanded to produce a pattern. +The +parameter expansion then results in +.Ar 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 +.D1 Li $( Ns Ar command Ns Li )\& +.Pp +or the backquoted version: +.Pp +.D1 Li ` Ns Ar command Ns Li ` +.Pp +The shell expands the command substitution by executing command +and replacing the command substitution +with the standard output of the command, +removing sequences of one or more newlines at the end of the substitution. +Embedded newlines before the end of the output are not removed; +however, during field splitting, they may be translated into spaces +depending on the value of +.Va IFS +and the quoting that is in effect. +The command is executed in a subshell environment, +except that the built-in commands +.Ic jobid , +.Ic jobs , +and +.Ic trap +return information about the parent shell environment +and +.Ic times +returns information about the same process +if they are the only command in a command substitution. +.Pp +If a command substitution of the +.Li $( +form begins with a subshell, +the +.Li $( +and +.Li (\& +must be separated by whitespace +to avoid ambiguity with arithmetic expansion. +.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 +.D1 Li $(( Ns Ar expression Ns Li )) +.Pp +The +.Ar 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 +.Ar expression +for parameter expansion, +command substitution, +arithmetic expansion +and quote removal. +.Pp +The allowed expressions are a subset of C expressions, +summarized below. +.Bl -tag -width "Variables" -offset indent +.It Values +All values are of type +.Ft intmax_t . +.It Constants +Decimal, octal (starting with +.Li 0 ) +and hexadecimal (starting with +.Li 0x ) +integer constants. +.It Variables +Shell variables can be read and written +and contain integer constants. +.It Unary operators +.Li "! ~ + -" +.It Binary operators +.Li "* / % + - << >> < <= > >= == != & ^ | && ||"\& +.It Assignment operators +.Li "= += -= *= /= %= <<= >>= &= ^= |=" +.It Conditional operator +.Li "? :"\& +.El +.Pp +The result of the expression is substituted in decimal. +.Ss White Space Splitting (Field Splitting) +In certain contexts, +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 +Characters in +.Va IFS +that are whitespace +.Po +.Aq space , +.Aq tab , +and +.Aq newline +.Pc +are treated differently from other characters in +.Va IFS . +.Pp +Whitespace in +.Va IFS +at the beginning or end of a word is discarded. +.Pp +Subsequently, a field is delimited by either +.Bl -enum +.It +a non-whitespace character in +.Va IFS +with any whitespace in +.Va IFS +surrounding it, or +.It +one or more whitespace characters in +.Va IFS . +.El +.Pp +If a word ends with a non-whitespace character in +.Va IFS , +there is no empty field after this character. +.Pp +If no field is delimited, the word is discarded. +In particular, if a word consists solely of an unquoted substitution +and the result of the substitution is null, +it is removed by field splitting even if +.Va IFS +is null. +.Ss Pathname Expansion (File Name Generation) +Unless the +.Fl f +option 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 +Pathname Expansion, +the four varieties of parameter expansion for substring processing and the +.Ic case +command. +.Ss Shell Patterns +A pattern consists of normal characters, which match themselves, +and meta-characters. +The meta-characters are +.Ql * , +.Ql \&? , +and +.Ql \&[ . +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 Ql * +matches any string of characters. +A question mark +.Pq Ql \&? +matches any single character. +A left bracket +.Pq Ql \&[ +introduces a character class. +The end of the character class is indicated by a +.Ql \&] ; +if the +.Ql \&] +is missing then the +.Ql \&[ +matches a +.Ql \&[ +rather than introducing a character class. +A character class matches any of the characters between the square brackets. +A locale-dependent range of characters may be specified using a minus sign. +A named class of characters (see +.Xr wctype 3 ) +may be specified by surrounding the name with +.Ql \&[:\& +and +.Ql :\&] . +For example, +.Ql \&[\&[:alpha:\&]\&] +is a shell pattern that matches a single letter. +The character class may be complemented by making an exclamation point +.Pq Ql !\& +the first character of the character class. +A caret +.Pq Ql ^ +has the same effect but is non-standard. +.Pp +To include a +.Ql \&] +in a character class, make it the first character listed +(after the +.Ql \&! +or +.Ql ^ , +if any). +To include a +.Ql - , +make it the first or last character listed. +.Ss Built-in Commands +This section lists the built-in commands. +.Bl -tag -width indent +.It Ic \&: +A null command that returns a 0 (true) exit value. +.It Ic \&. Ar file +The commands in the specified file are read and executed by the shell. +The +.Ic return +command may be used to return to the +.Ic \&. +command's caller. +If +.Ar file +contains any +.Ql / +characters, it is used as is. +Otherwise, the shell searches the +.Va PATH +for the file. +If it is not found in the +.Va PATH , +it is sought in the current working directory. +.It Ic \&[ +A built-in equivalent of +.Xr test 1 . +.It Ic alias Oo Ar name Ns Oo = Ns Ar string Oc ... Oc +If +.Ar name Ns = Ns Ar 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 +built-in command prints the names and values of all defined aliases +(see +.Ic unalias ) . +Alias values are written with appropriate quoting so that they are +suitable for re-input to the shell. +Also see the +.Sx Aliases +subsection. +.It Ic bg Op Ar job ... +Continue the specified jobs +(or the current job if no jobs are given) +in the background. +.It Ic bind Oo Fl aeklrsv Oc Oo Ar key Oo Ar command Oc Oc +List or alter key bindings for the line editor. +This command is documented in +.Xr editrc 5 . +.It Ic break Op Ar num +See the +.Sx Flow-Control Constructs +subsection. +.It Ic builtin Ar cmd Op Ar arg ... +Execute the specified built-in command, +.Ar cmd . +This is useful when the user wishes to override a shell function +with the same name as a built-in command. +.It Ic cd Oo Fl L | P Oc Oo Fl e Oc Op Ar directory +.It Ic cd Fl +Switch to the specified +.Ar directory , +to the directory specified in the +.Va HOME +environment variable if no +.Ar directory +is specified or +to the directory specified in the +.Va OLDPWD +environment variable if +.Ar directory +is +.Fl . +If +.Ar directory +does not begin with +.Pa / , \&. , +or +.Pa .. , +then the directories listed in the +.Va CDPATH +variable will be +searched for the specified +.Ar directory . +If +.Va CDPATH +is unset, the current directory is searched. +The format of +.Va CDPATH +is the same as that of +.Va PATH . +In an interactive shell, +the +.Ic cd +command will print out the name of the directory +that it actually switched to +if the +.Va CDPATH +mechanism was used or if +.Ar directory +was +.Fl . +.Pp +If the +.Fl P +option is specified, +.Pa .. +is handled physically and symbolic links are resolved before +.Pa .. +components are processed. +If the +.Fl L +option is specified, +.Pa .. +is handled logically. +This is the default. +.Pp +The +.Fl e +option causes +.Ic cd +to return exit status 1 if the full pathname of the new directory +cannot be determined reliably or at all. +Normally this is not considered an error, +although a warning is printed. +.Pp +If changing the directory fails, the exit status is greater than 1. +If the directory is changed, the exit status is 0, or also 1 if +.Fl e +was given. +.It Ic chdir +A synonym for the +.Ic cd +built-in command. +.It Ic command Oo Fl p Oc Op Ar utility Op Ar argument ... +.It Ic command Oo Fl p Oc Fl v Ar utility +.It Ic command Oo Fl p Oc Fl V Ar utility +The first form of invocation executes the specified +.Ar utility , +ignoring shell functions in the search. +If +.Ar utility +is a special builtin, +it is executed as if it were a regular builtin. +.Pp +If the +.Fl p +option is specified, the command search is performed using a +default value of +.Va PATH +that is guaranteed to find all of the standard utilities. +.Pp +If the +.Fl v +option is specified, +.Ar utility +is not executed but a description of its interpretation by the shell is +printed. +For ordinary commands the output is the path name; for shell built-in +commands, shell functions and keywords only the name is written. +Aliases are printed as +.Dq Ic alias Ar name Ns = Ns Ar value . +.Pp +The +.Fl V +option is identical to +.Fl v +except for the output. +It prints +.Dq Ar utility Ic is Ar description +where +.Ar description +is either +the path name to +.Ar utility , +a special shell builtin, +a shell builtin, +a shell function, +a shell keyword +or +an alias for +.Ar value . +.It Ic continue Op Ar num +See the +.Sx Flow-Control Constructs +subsection. +.It Ic echo Oo Fl e | n Oc Op Ar string ... +Print a space-separated list of the arguments to the standard output +and append a newline character. +.Bl -tag -width indent +.It Fl n +Suppress the output of the trailing newline. +.It Fl e +Process C-style backslash escape sequences. +The +.Ic echo +command understands the following character escapes: +.Bl -tag -width indent +.It \ea +Alert (ring the terminal bell) +.It \eb +Backspace +.It \ec +Suppress the trailing newline (this has the side-effect of truncating the +line if it is not the last character) +.It \ee +The ESC character (ASCII 0x1b) +.It \ef +Formfeed +.It \en +Newline +.It \er +Carriage return +.It \et +Horizontal tab +.It \ev +Vertical tab +.It \e\e +Literal backslash +.It \e0nnn +(Zero) The character whose octal value is +.Ar nnn +.El +.Pp +If +.Ar string +is not enclosed in quotes then the backslash itself must be escaped +with a backslash to protect it from the shell. +For example +.Bd -literal -offset indent +$ echo -e "a\evb" +a + b +$ echo -e a\e\evb +a + b +$ echo -e "a\e\eb" +a\eb +$ echo -e a\e\e\e\eb +a\eb +.Ed +.El +.Pp +Only one of the +.Fl e +and +.Fl n +options may be specified. +.It Ic eval Ar string ... +Concatenate all the arguments with spaces. +Then re-parse and execute the command. +.It Ic exec Op Ar command Op arg ... +Unless +.Ar command +is omitted, +the shell process is replaced with the specified program +(which must be a real program, not a shell built-in command 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 Ic exit Op Ar exitstatus +Terminate the shell process. +If +.Ar exitstatus +is given +it is used as the exit status of the shell. +Otherwise, if the shell is executing an +.Cm EXIT +trap, the exit status of the last command before the trap is used; +if the shell is executing a trap for a signal, +the shell exits by resending the signal to itself. +Otherwise, the exit status of the preceding command is used. +The exit status should be an integer between 0 and 255. +.It Ic export Ar name ... +.It Ic export Op 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 +.Ic unset +it. +The shell allows the value of a variable to be set +at the same time as it is exported by writing +.Pp +.D1 Ic export Ar name Ns = Ns Ar value +.Pp +With no arguments the +.Ic export +command lists the names +of all exported variables. +If the +.Fl p +option is specified, the exported variables are printed as +.Dq Ic export Ar name Ns = Ns Ar value +lines, suitable for re-input to the shell. +.It Ic false +A null command that returns a non-zero (false) exit value. +.It Ic fc Oo Fl e Ar editor Oc Op Ar first Op Ar last +.It Ic fc Fl l Oo Fl nr Oc Op Ar first Op Ar last +.It Ic fc Fl s Oo Ar old Ns = Ns Ar new Oc Op Ar first +The +.Ic fc +built-in command lists, or edits and re-executes, +commands previously entered to an interactive shell. +.Bl -tag -width indent +.It Fl e Ar editor +Use the editor named by +.Ar editor +to edit the commands. +The +.Ar editor +string is a command name, +subject to search via the +.Va PATH +variable. +The value in the +.Va FCEDIT +variable is used as a default when +.Fl e +is not specified. +If +.Va FCEDIT +is null or unset, the value of the +.Va EDITOR +variable is used. +If +.Va 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 +.Ar first +and +.Ar last +operands, as affected by +.Fl r , +with each command preceded by the command number. +.It Fl n +Suppress command numbers when listing with +.Fl 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 Ar first +.It Ar last +Select the commands to list or edit. +The number of previous commands that can be accessed +are determined by the value of the +.Va HISTSIZE +variable. +The value of +.Ar first +or +.Ar last +or both are one of the following: +.Bl -tag -width indent +.It Oo Cm + Oc Ns Ar num +A positive number representing a command number; +command numbers can be displayed with the +.Fl l +option. +.It Fl Ar num +A negative decimal number representing the +command that was executed +.Ar num +of +commands previously. +For example, \-1 is the immediately previous command. +.It Ar string +A string indicating the most recently entered command +that begins with that string. +If the +.Ar old Ns = Ns Ar new +operand is not also specified with +.Fl s , +the string form of the first operand cannot contain an embedded equal sign. +.El +.El +.Pp +The following variables affect the execution of +.Ic fc : +.Bl -tag -width ".Va HISTSIZE" +.It Va FCEDIT +Name of the editor to use for history editing. +.It Va HISTSIZE +The number of previous commands that are accessible. +.El +.It Ic fg Op Ar job +Move the specified +.Ar job +or the current job to the foreground. +.It Ic getopts Ar optstring var +The POSIX +.Ic getopts +command. +The +.Ic getopts +command deprecates the older +.Xr getopt 1 +command. +The first argument should be a series of letters, each possibly +followed by a colon which indicates that the option takes an argument. +The specified variable is set to the parsed option. +The index of +the next argument is placed into the shell variable +.Va OPTIND . +If an option takes an argument, it is placed into the shell variable +.Va OPTARG . +If an invalid option is encountered, +.Ar var +is set to +.Ql \&? . +It returns a false value (1) when it encounters the end of the options. +A new set of arguments may be parsed by assigning +.Li OPTIND=1 . +.It Ic hash Oo Fl rv Oc Op 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. +.Pp +With arguments, the +.Ic hash +command removes each specified +.Ar command +from the hash table (unless they are functions) and then locates it. +With the +.Fl v +option, +.Ic hash +prints the locations of the commands as it finds them. +The +.Fl r +option causes the +.Ic hash +command to delete all the entries in the hash table except for functions. +.It Ic jobid Op Ar job +Print the process IDs of the processes in the specified +.Ar job . +If the +.Ar job +argument is omitted, use the current job. +.It Ic jobs Oo Fl lps Oc Op Ar job ... +Print information about the specified jobs, or all jobs if no +.Ar job +argument is given. +The information printed includes job ID, status and command name. +.Pp +If the +.Fl l +option is specified, the PID of each job is also printed. +If the +.Fl p +option is specified, only the process IDs for the process group leaders +are printed, one per line. +If the +.Fl s +option is specified, only the PIDs of the job commands are printed, one per +line. +.It Ic kill +A built-in equivalent of +.Xr kill 1 +that additionally supports sending signals to jobs. +.It Ic local Oo Ar variable ... Oc Op Fl +See the +.Sx Functions +subsection. +.It Ic printf +A built-in equivalent of +.Xr printf 1 . +.It Ic pwd Op Fl L | P +Print the path of the current directory. +The built-in command may +differ from the program of the same name because the +built-in 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 built-in version of +.Xr pwd 1 +will continue to print the old name for the directory. +.Pp +If the +.Fl P +option is specified, symbolic links are resolved. +If the +.Fl L +option is specified, the shell's notion of the current directory +is printed (symbolic links are not resolved). +This is the default. +.It Ic read Oo Fl p Ar prompt Oc Oo +.Fl t Ar timeout Oc Oo Fl er Oc Ar variable ... +The +.Ar 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 +.Sx White Space Splitting (Field Splitting)\& +above, and +the pieces are assigned to the variables in order. +If there are more pieces than variables, the remaining +pieces (along with the characters in +.Va 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. +.Pp +Backslashes are treated specially, unless the +.Fl r +option is +specified. +If a backslash is followed by +a newline, the backslash and the newline will be +deleted. +If a backslash is followed by any other +character, the backslash will be deleted and the following +character will be treated as though it were not in +.Va IFS , +even if it is. +.Pp +If the +.Fl t +option is specified and the +.Ar timeout +elapses before a complete line of input is supplied, +the +.Ic read +command will return an exit status as if terminated by +.Dv SIGALRM +without assigning any values. +The +.Ar timeout +value may optionally be followed by one of +.Ql s , +.Ql m +or +.Ql h +to explicitly specify seconds, minutes or hours. +If none is supplied, +.Ql s +is assumed. +.Pp +The +.Fl e +option exists only for backward compatibility with older scripts. +.Pp +The exit status is 0 on success, 1 on end of file, +between 2 and 128 if an error occurs +and greater than 128 if a trapped signal interrupts +.Ic read . +.It Ic readonly Oo Fl p Oc Op Ar name ... +Each specified +.Ar name +is marked as read only, +so that it cannot be subsequently modified or unset. +The shell allows the value of a variable to be set +at the same time as it is marked read only +by using the following form: +.Pp +.D1 Ic readonly Ar name Ns = Ns Ar value +.Pp +With no arguments the +.Ic readonly +command lists the names of all read only variables. +If the +.Fl p +option is specified, the read-only variables are printed as +.Dq Ic readonly Ar name Ns = Ns Ar value +lines, suitable for re-input to the shell. +.It Ic return Op Ar exitstatus +See the +.Sx Functions +subsection. +.It Ic set Oo Fl /+abCEefIimnpTuVvx Oc Oo Fl /+o Ar longname Oc Oo +.Fl c Ar string Oc Op Fl - Ar arg ... +The +.Ic set +command performs three different functions: +.Bl -item +.It +With no arguments, it lists the values of all shell variables. +.It +If options are given, +either in short form or using the long +.Dq Fl /+o Ar longname +form, +it sets or clears the specified options as described in the section called +.Sx Argument List Processing . +.It +If the +.Dq Fl - +option is specified, +.Ic set +will replace the shell's positional parameters with the subsequent +arguments. +If no arguments follow the +.Dq Fl - +option, +all the positional parameters will be cleared, +which is equivalent to executing the command +.Dq Li "shift $#" . +The +.Dq Fl - +flag may be omitted when specifying arguments to be used +as positional replacement parameters. +This is not recommended, +because the first argument may begin with a dash +.Pq Ql - +or a plus +.Pq Ql + , +which the +.Ic set +command will interpret as a request to enable or disable options. +.El +.It Ic setvar Ar variable value +Assigns the specified +.Ar value +to the specified +.Ar variable . +The +.Ic setvar +command is intended to be used in functions that +assign values to variables whose names are passed as parameters. +In general it is better to write +.Dq Ar variable Ns = Ns Ar value +rather than using +.Ic setvar . +.It Ic shift Op Ar n +Shift the positional parameters +.Ar n +times, or once if +.Ar n +is not specified. +A shift sets the value of +.Li $1 +to the value of +.Li $2 , +the value of +.Li $2 +to the value of +.Li $3 , +and so on, +decreasing the value of +.Li $# +by one. +For portability, shifting if there are zero positional parameters +should be avoided, since the shell may abort. +.It Ic test +A built-in equivalent of +.Xr test 1 . +.It Ic times +Print the amount of time spent executing the shell process and its children. +The first output line shows the user and system times for the shell process +itself, the second one contains the user and system times for the +children. +.It Ic trap Oo Ar action Oc Ar signal ... +.It Ic trap Fl l +Cause the shell to parse and execute +.Ar action +when any specified +.Ar signal +is received. +The signals are specified by name or number. +In addition, the pseudo-signal +.Cm EXIT +may be used to specify an +.Ar action +that is performed when the shell terminates. +The +.Ar action +may be an empty string or a dash +.Pq Ql - ; +the former causes the specified signal to be ignored +and the latter causes the default action to be taken. +Omitting the +.Ar action +and using only signal numbers is another way to request the default action. +In a subshell or utility environment, +the shell 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. +.Pp +Option +.Fl l +causes the +.Ic trap +command to display a list of valid signal names. +.It Ic true +A null command that returns a 0 (true) exit value. +.It Ic type Op Ar name ... +Interpret each +.Ar name +as a command and print the resolution of the command search. +Possible resolutions are: +shell keyword, alias, special shell builtin, 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 Ic ulimit Oo Fl HSabcdfklmnopstuvw Oc Op Ar limit +Set or display resource limits (see +.Xr getrlimit 2 ) . +If +.Ar limit +is specified, the named resource will be set; +otherwise the current resource value will be displayed. +.Pp +If +.Fl H +is specified, the hard limits will be set or displayed. +While everybody is allowed to reduce a hard limit, +only the superuser can increase it. +The +.Fl S +option +specifies the soft limits instead. +When displaying limits, +only one of +.Fl S +or +.Fl H +can be given. +The default is to display the soft limits, +and to set both the hard and the soft limits. +.Pp +Option +.Fl a +causes the +.Ic ulimit +command to display all resources. +The parameter +.Ar limit +is not acceptable in this mode. +.Pp +The remaining options specify which resource value is to be +displayed or modified. +They are mutually exclusive. +.Bl -tag -width indent +.It Fl b Ar sbsize +The maximum size of socket buffer usage, in bytes. +.It Fl c Ar coredumpsize +The maximal size of core dump files, in 512-byte blocks. +Setting +.Ar coredumpsize +to 0 prevents core dump files from being created. +.It Fl d Ar datasize +The maximal size of the data segment of a process, in kilobytes. +.It Fl f Ar filesize +The maximal size of a file, in 512-byte blocks. +.It Fl k Ar kqueues +The maximal number of kqueues +(see +.Xr kqueue 2 ) +for this user ID. +.It Fl l Ar lockedmem +The maximal size of memory that can be locked by a process, in +kilobytes. +.It Fl m Ar memoryuse +The maximal resident set size of a process, in kilobytes. +.It Fl n Ar nofiles +The maximal number of descriptors that could be opened by a process. +.It Fl o Ar umtxp +The maximal number of process-shared locks +(see +.Xr pthread 3 ) +for this user ID. +.It Fl p Ar pseudoterminals +The maximal number of pseudo-terminals for this user ID. +.It Fl s Ar stacksize +The maximal size of the stack segment, in kilobytes. +.It Fl t Ar time +The maximal amount of CPU time to be used by each process, in seconds. +.It Fl u Ar userproc +The maximal number of simultaneous processes for this user ID. +.It Fl v Ar virtualmem +The maximal virtual size of a process, in kilobytes. +.It Fl w Ar swapuse +The maximum amount of swap space reserved or used for this user ID, +in kilobytes. +.El +.It Ic umask Oo Fl S Oc Op Ar mask +Set the file creation mask (see +.Xr umask 2 ) +to the octal or symbolic (see +.Xr chmod 1 ) +value specified by +.Ar mask . +If the argument is omitted, the current mask value is printed. +If the +.Fl S +option is specified, the output is symbolic, otherwise the output is octal. +.It Ic unalias Oo Fl a Oc Op Ar name ... +The specified alias names are removed. +If +.Fl a +is specified, all aliases are removed. +.It Ic unset Oo Fl fv Oc Ar name ... +The specified variables or functions are unset and unexported. +If the +.Fl v +option is specified or no options are given, the +.Ar name +arguments are treated as variable names. +If the +.Fl f +option is specified, the +.Ar name +arguments are treated as function names. +.It Ic wait Op Ar job ... +Wait for each specified +.Ar job +to complete and return the exit status of the last process in the +last specified +.Ar job . +If any +.Ar job +specified is unknown to the shell, it is treated as if it +were a known job that exited with exit status 127. +If no operands are given, 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 Built-in Commands ) +can be edited using +.Nm vi Ns -mode +command line editing. +This mode uses commands similar +to a subset of those described in the +.Xr vi 1 +man page. +The command +.Dq Li "set -o vi" +(or +.Dq Li "set -V" ) +enables +.Nm vi Ns -mode +editing and places +.Nm +into +.Nm vi +insert mode. +With +.Nm vi Ns -mode +enabled, +.Nm +can be switched between insert mode and command mode by typing +.Aq ESC . +Hitting +.Aq return +while in command mode will pass the line to the shell. +.Pp +Similarly, the +.Dq Li "set -o emacs" +(or +.Dq Li "set -E" ) +command can be used to enable a subset of +.Nm emacs Ns -style +command line editing features. +.Sh ENVIRONMENT +The following environment variables affect the execution of +.Nm : +.Bl -tag -width ".Ev LANGXXXXXX" +.It Ev ENV +Initialization file for interactive shells. +.It Ev LANG , Ev LC_* +Locale settings. +These are inherited by children of the shell, +and is used in a limited manner by the shell itself. +.It Ev OLDPWD +The previous current directory. +This is used and updated by +.Ic cd . +.It Ev PWD +An absolute pathname for the current directory, +possibly containing symbolic links. +This is used and updated by the shell. +.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. +.El +.Pp +Additionally, environment variables are turned into shell variables +at startup, +which may affect the shell as described under +.Sx Special Variables . +.Sh FILES +.Bl -tag -width "/etc/suid_profileXX" -compact +.It Pa ~/.profile +User's login profile. +.It Pa /etc/profile +System login profile. +.It Pa /etc/shells +Shell database. +.It Pa /etc/suid_profile +Privileged shell profile. +.El +.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 +.Ic exit +builtin is used with a numeric argument, it +will return the argument. +.Sh SEE ALSO +.Xr 1sh-kill 1 , +.Xr 1sh-printf 1 , +.Xr 1sh-test 1 , +.Xr builtin 1 , +.Xr chsh 1 , +.Xr echo 1 , +.Xr ed 1 , +.Xr emacs 1 , +.Xr pwd 1 , +.Xr vi 1 , +.Xr execve 2 , +.Xr getrlimit 2 , +.Xr umask 2 , +.Xr wctype 3 , +.Xr editrc 5 , +.Xr shells 5 +.Sh HISTORY +A +.Nm sh +command, the Thompson shell, appeared in +.At v1 . +It was superseded in +.At v7 +by the Bourne shell, which inherited the name +.Nm sh . +.Pp +This version of +.Nm sh +was rewritten in 1989 under the +.Bx +license after the Bourne shell from +.At V.4 . +The +.Nm +command is based on sources from +.Fx 12.1 . +.Sh AUTHORS +This version of +.Nm +was originally written by +.An Kenneth Almquist . +.Sh BUGS +The +.Nm +utility does not recognize multibyte characters other than UTF-8. +Splitting using +.Va IFS +does not recognize multibyte characters. diff --git a/bin/1sh/Makefile b/bin/1sh/Makefile new file mode 100644 index 00000000..26ce125f --- /dev/null +++ b/bin/1sh/Makefile @@ -0,0 +1,84 @@ +PREFIX = /usr/local +MANDIR = ${PREFIX}/share/man + +CFLAGS += -std=c99 -Wall -Wextra -DSHELL +LDLIBS = -ledit + +-include config.mk + +SRCS += alias.c +SRCS += arith_yacc.c +SRCS += arith_yylex.c +SRCS += cd.c +SRCS += echo.c +SRCS += error.c +SRCS += eval.c +SRCS += exec.c +SRCS += expand.c +SRCS += histedit.c +SRCS += input.c +SRCS += jobs.c +SRCS += kill.c +SRCS += mail.c +SRCS += main.c +SRCS += memalloc.c +SRCS += miscbltin.c +SRCS += mystring.c +SRCS += options.c +SRCS += output.c +SRCS += parser.c +SRCS += printf.c +SRCS += redir.c +SRCS += show.c +SRCS += test.c +SRCS += trap.c +SRCS += var.c + +GENSRCS = builtins.c nodes.c syntax.c +GENHDRS = builtins.h nodes.h syntax.h token.h + +SRCS += ${GENSRCS} +OBJS = ${SRCS:.c=.o} + +MANS = 1sh.1 1sh-kill.1 1sh-printf.1 1sh-test.1 + +all: tags 1sh + +1sh: ${OBJS} + ${CC} ${LDFLAGS} ${OBJS} ${LDLIBS} -o $@ + +${OBJS}: ${GENHDRS} + +builtins.c builtins.h: mkbuiltins builtins.def + sh mkbuiltins . + +nodes.c nodes.h: mknodes nodetypes nodes.c.pat + ./mknodes nodetypes nodes.c.pat + +syntax.c syntax.h: mksyntax + ./mksyntax + +token.h: mktokens + sh mktokens + +tags: *.h *.c + ctags -w *.h *.c + +depend: ${SRCS} ${GENHDRS} + ${CC} ${CFLAGS} -MM ${SRCS} > .depend + +-include .depend + +clean: + rm -f 1sh ${OBJS} mknodes mksyntax ${GENSRCS} ${GENHDRS} tags .depend + +install: 1sh ${MANS} + install -d ${PREFIX}/bin ${MANDIR}/man1 + install 1sh ${PREFIX}/bin + for man in ${MANS}; do gzip -c $$man > ${MANDIR}/man1/$$man.gz; done + grep -q '^${PREFIX}/bin/1sh$$' /etc/shells \ + || echo '${PREFIX}/bin/1sh' >> /etc/shells + +uninstall: + rm -f ${PREFIX}/bin/1sh ${MANS:%=${MANDIR}/man1/%.gz} + sed -i sed '\;^${PREFIX}/bin/1sh$$;d' /etc/shells diff --git a/bin/1sh/TOUR b/bin/1sh/TOUR new file mode 100644 index 00000000..3f196c9b --- /dev/null +++ b/bin/1sh/TOUR @@ -0,0 +1,301 @@ +# @(#)TOUR 8.1 (Berkeley) 5/31/93 +# $FreeBSD: releng/12.1/bin/sh/TOUR 317882 2017-05-06 13:28:42Z jilles $ + +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 input files generates + ------- ----------- --------- + mkbuiltins builtins.def builtins.h builtins.c + mknodes nodetypes nodes.h nodes.c + mksyntax - syntax.h syntax.c + mktokens - token.h + +There are undoubtedly too many of these. + +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 uninterruptible 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 +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 -m 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 +four tables: one for normal use, one for use when inside single +quotes and dollar single quotes, one for use when inside double +quotes and one for use in arithmetic. The tables are machine +dependent because they are indexed by character variables 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 Parameter expansion + CTLENDVAR End of parameter expansion + CTLBACKQ Command substitution + CTLBACKQ|CTLQUOTE Command substitution inside double quotes + CTLARI Arithmetic expansion + CTLENDARI End of arithmetic expansion + 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} + VSTRIMLEFT ${var#text} + VSTRIMLEFTMAX ${var##text} + VSTRIMRIGHT ${var%text} + VSTRIMRIGHTMAX ${var%%text} + VSLENGTH ${#var} + VSERROR delayed error + +In addition, the type field will have the VSQUOTE flag set if the +variable is enclosed in double quotes and the VSLINENO flag if +LINENO is being expanded (the parameter name is the decimal line +number). The parameter's name comes next, terminated by an equals +sign. If the type is not VSNORMAL (including when it is VSLENGTH), +then the text field in the substitution follows, terminated by a +CTLENDVAR byte. + +The type VSERROR is used to allow parsing bad substitutions like +${var[7]} and generate an error when they are expanded. + +Commands in back quotes are parsed and stored in a linked list. +The locations of these commands in the string are indicated by +CTLBACKQ and CTLBACKQ+CTLQUOTE characters, depending upon whether +the back quotes were enclosed in double quotes. + +Arithmetic expansion starts with CTLARI and ends with CTLENDARI. + +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 file name generation, +have the CTLESC characters removed during the variable and command +substitution phase. Words which are subject to file name +generation have the CTLESC characters removed 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 do 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 been 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: As the routine argstr generates words by parameter +expansion, command substitution and arithmetic expansion, it +performs word splitting on the result. As each word is output, +the routine expandmeta performs file name generation (if enabled). + +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. + +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.def, which is processed by the +mkbuiltins 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 non- +special 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 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 program is to be compiled +stand-alone. A similar approach is used for a few utilities from +bin and usr.bin. + +CD.C: This file defines the cd and pwd builtins. + +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 its 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/1sh/alias.c b/bin/1sh/alias.c new file mode 100644 index 00000000..e343b853 --- /dev/null +++ b/bin/1sh/alias.c @@ -0,0 +1,256 @@ +/*- + * 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)alias.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/alias.c 317039 2017-04-16 22:10:02Z jilles $ */ + +#include <stdlib.h> +#include "shell.h" +#include "output.h" +#include "error.h" +#include "memalloc.h" +#include "mystring.h" +#include "alias.h" +#include "options.h" +#include "builtins.h" + +#define ATABSIZE 39 + +static struct alias *atab[ATABSIZE]; +static int aliases; + +static void setalias(const char *, const char *); +static int unalias(const char *); +static struct alias **hashalias(const char *); + +static +void +setalias(const char *name, const char *val) +{ + struct alias *ap, **app; + + unalias(name); + app = hashalias(name); + INTOFF; + ap = ckmalloc(sizeof (struct alias)); + ap->name = savestr(name); + ap->val = savestr(val); + ap->flag = 0; + ap->next = *app; + *app = ap; + aliases++; + INTON; +} + +static void +freealias(struct alias *ap) +{ + ckfree(ap->name); + ckfree(ap->val); + ckfree(ap); +} + +static int +unalias(const char *name) +{ + struct alias *ap, **app; + + app = hashalias(name); + + for (ap = *app; ap; app = &(ap->next), ap = ap->next) { + if (equal(name, ap->name)) { + /* + * if the alias is currently in use (i.e. its + * buffer is being used by the input routine) we + * just null out the name instead of freeing it. + * We could clear it out later, but this situation + * is so rare that it hardly seems worth it. + */ + if (ap->flag & ALIASINUSE) + *ap->name = '\0'; + else { + INTOFF; + *app = ap->next; + freealias(ap); + INTON; + } + aliases--; + return (0); + } + } + + return (1); +} + +static void +rmaliases(void) +{ + struct alias *ap, **app; + int i; + + INTOFF; + for (i = 0; i < ATABSIZE; i++) { + app = &atab[i]; + while (*app) { + ap = *app; + if (ap->flag & ALIASINUSE) { + *ap->name = '\0'; + app = &(*app)->next; + } else { + *app = ap->next; + freealias(ap); + } + } + } + aliases = 0; + INTON; +} + +struct alias * +lookupalias(const char *name, int check) +{ + struct alias *ap; + + if (aliases == 0) + return (NULL); + for (ap = *hashalias(name); ap; ap = ap->next) { + if (equal(name, ap->name)) { + if (check && (ap->flag & ALIASINUSE)) + return (NULL); + return (ap); + } + } + + return (NULL); +} + +static int +comparealiases(const void *p1, const void *p2) +{ + const struct alias *const *a1 = p1; + const struct alias *const *a2 = p2; + + return strcmp((*a1)->name, (*a2)->name); +} + +static void +printalias(const struct alias *a) +{ + out1fmt("%s=", a->name); + out1qstr(a->val); + out1c('\n'); +} + +static void +printaliases(void) +{ + int i, j; + struct alias **sorted, *ap; + + INTOFF; + sorted = ckmalloc(aliases * sizeof(*sorted)); + j = 0; + for (i = 0; i < ATABSIZE; i++) + for (ap = atab[i]; ap; ap = ap->next) + if (*ap->name != '\0') + sorted[j++] = ap; + qsort(sorted, aliases, sizeof(*sorted), comparealiases); + for (i = 0; i < aliases; i++) { + printalias(sorted[i]); + if (int_pending()) + break; + } + ckfree(sorted); + INTON; +} + +int +aliascmd(int argc __unused, char **argv __unused) +{ + char *n, *v; + int ret = 0; + struct alias *ap; + + nextopt(""); + + if (*argptr == NULL) { + printaliases(); + return (0); + } + while ((n = *argptr++) != NULL) { + if ((v = strchr(n+1, '=')) == NULL) /* n+1: funny ksh stuff */ + if ((ap = lookupalias(n, 0)) == NULL) { + warning("%s: not found", n); + ret = 1; + } else + printalias(ap); + else { + *v++ = '\0'; + setalias(n, v); + } + } + + return (ret); +} + +int +unaliascmd(int argc __unused, char **argv __unused) +{ + int i; + + while ((i = nextopt("a")) != '\0') { + if (i == 'a') { + rmaliases(); + return (0); + } + } + for (i = 0; *argptr; argptr++) + i |= unalias(*argptr); + + return (i); +} + +static struct alias ** +hashalias(const char *p) +{ + unsigned int hashval; + + hashval = (unsigned char)*p << 4; + while (*p) + hashval+= *p++; + return &atab[hashval % ATABSIZE]; +} diff --git a/bin/1sh/alias.h b/bin/1sh/alias.h new file mode 100644 index 00000000..625d1a50 --- /dev/null +++ b/bin/1sh/alias.h @@ -0,0 +1,45 @@ +/*- + * 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. + * + * @(#)alias.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/alias.h 314436 2017-02-28 23:42:47Z imp $ + */ + +#define ALIASINUSE 1 + +struct alias { + struct alias *next; + char *name; + char *val; + int flag; +}; + +struct alias *lookupalias(const char *, int); diff --git a/bin/1sh/arith.h b/bin/1sh/arith.h new file mode 100644 index 00000000..ecaad30f --- /dev/null +++ b/bin/1sh/arith.h @@ -0,0 +1,37 @@ +/*- + * Copyright (c) 1995 + * 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. + * + * @(#)arith.h 1.1 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/arith.h 315511 2017-03-18 20:41:07Z jilles $ + */ + +#include "shell.h" + +#define DIGITS(var) (3 + (2 + CHAR_BIT * sizeof((var))) / 3) + +arith_t arith(const char *); diff --git a/bin/1sh/arith_yacc.c b/bin/1sh/arith_yacc.c new file mode 100644 index 00000000..57285831 --- /dev/null +++ b/bin/1sh/arith_yacc.c @@ -0,0 +1,381 @@ +/*- + * 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 <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/arith_yacc.c 345117 2019-03-13 21:53:10Z jilles $ */ + +#include <limits.h> +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include "arith.h" +#include "arith_yacc.h" +#include "expand.h" +#include "shell.h" +#include "error.h" +#include "memalloc.h" +#include "output.h" +#include "options.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 + +int letcmd(int, char **); + +static __attribute__((noreturn)) void yyerror(const char *s) +{ + error("arithmetic expression: %s: \"%s\"", s, arith_startbuf); + /* NOTREACHED */ +} + +static arith_t arith_lookupvarint(char *varname) +{ + const char *str; + char *p; + arith_t result; + + str = lookupvar(varname); + if (uflag && str == NULL) + yyerror("variable not set"); + if (str == NULL || *str == '\0') + str = "0"; + errno = 0; + result = strtoarith_t(str, &p); + if (errno != 0 || *p != '\0') + yyerror("variable conversion error"); + return result; +} + +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 arith_t do_binop(int op, arith_t a, arith_t b) +{ + + switch (op) { + default: + case ARITH_REM: + case ARITH_DIV: + if (!b) + yyerror("division by zero"); + if (a == ARITH_MIN && b == -1) + yyerror("divide error"); + return op == ARITH_REM ? a % b : a / b; + case ARITH_MUL: + return (uintmax_t)a * (uintmax_t)b; + case ARITH_ADD: + return (uintmax_t)a + (uintmax_t)b; + case ARITH_SUB: + return (uintmax_t)a - (uintmax_t)b; + case ARITH_LSHIFT: + return (uintmax_t)a << (b & (sizeof(uintmax_t) * CHAR_BIT - 1)); + case ARITH_RSHIFT: + return a >> (b & (sizeof(uintmax_t) * CHAR_BIT - 1)); + 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 arith_t assignment(int var, int noeval); + +static arith_t primary(int token, union yystype *val, int op, int noeval) +{ + arith_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 : arith_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 arith_t binop2(arith_t a, int op, int precedence, int noeval) +{ + for (;;) { + union yystype val; + arith_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) >= precedence) + return a; + + op = op2; + } +} + +static arith_t binop(int token, union yystype *val, int op, int noeval) +{ + arith_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 arith_t and(int token, union yystype *val, int op, int noeval) +{ + arith_t a = binop(token, val, op, noeval); + arith_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 arith_t or(int token, union yystype *val, int op, int noeval) +{ + arith_t a = and(token, val, op, noeval); + arith_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 arith_t cond(int token, union yystype *val, int op, int noeval) +{ + arith_t a = or(token, val, op, noeval); + arith_t b; + arith_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 arith_t assignment(int var, int noeval) +{ + union yystype val = yylval; + int op = yylex(); + arith_t result; + char sresult[DIGITS(result) + 1]; + + 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; + + if (op != ARITH_ASS) + result = do_binop(op - 11, arith_lookupvarint(val.name), result); + snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR, result); + setvar(val.name, sresult, 0); + return result; +} + +arith_t arith(const char *s) +{ + struct stackmark smark; + arith_t result; + + setstackmark(&smark); + + arith_buf = arith_startbuf = s; + + result = assignment(yylex(), 0); + + if (last_token) + yyerror("expecting EOF"); + + popstackmark(&smark); + + return result; +} + +/* + * The exp(1) builtin. + */ +int +letcmd(int argc, char **argv) +{ + const char *p; + char *concat; + char **ap; + arith_t i; + + if (argc > 1) { + p = argv[1]; + if (argc > 2) { + /* + * Concatenate arguments. + */ + STARTSTACKSTR(concat); + ap = argv + 2; + for (;;) { + while (*p) + STPUTC(*p++, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + } else + p = ""; + + i = arith(p); + + out1fmt(ARITH_FORMAT_STR "\n", i); + return !i; +} diff --git a/bin/1sh/arith_yacc.h b/bin/1sh/arith_yacc.h new file mode 100644 index 00000000..433481bb --- /dev/null +++ b/bin/1sh/arith_yacc.h @@ -0,0 +1,94 @@ +/*- + * 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. + * + * $FreeBSD: releng/12.1/bin/sh/arith_yacc.h 345117 2019-03-13 21:53:10Z jilles $ + */ + +#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 + +extern const char *arith_buf; + +union yystype { + arith_t val; + char *name; +}; + +extern union yystype yylval; + +arith_t strtoarith_t(const char *restrict nptr, char **restrict endptr); +int yylex(void); diff --git a/bin/1sh/arith_yylex.c b/bin/1sh/arith_yylex.c new file mode 100644 index 00000000..da3a4f70 --- /dev/null +++ b/bin/1sh/arith_yylex.c @@ -0,0 +1,276 @@ +/*- + * 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 <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/arith_yylex.c 345117 2019-03-13 21:53:10Z jilles $ */ + +#include <ctype.h> +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> +#include "shell.h" +#include "arith_yacc.h" +#include "expand.h" +#include "error.h" +#include "memalloc.h" +#include "parser.h" +#include "syntax.h" + +#if ARITH_BOR + 11 != ARITH_BORASS || ARITH_ASS + 11 != ARITH_EQ +#error Arithmetic tokens are out of order. +#endif + +arith_t +strtoarith_t(const char *restrict nptr, char **restrict endptr) +{ + arith_t val; + + while (isspace((unsigned char)*nptr)) + nptr++; + switch (*nptr) { + case '-': + return strtoimax(nptr, endptr, 0); + case '0': + return (arith_t)strtoumax(nptr, endptr, 0); + default: + val = (arith_t)strtoumax(nptr, endptr, 0); + if (val >= 0) + return val; + else if (val == ARITH_MIN) { + errno = ERANGE; + return ARITH_MIN; + } else { + errno = ERANGE; + return ARITH_MAX; + } + } +} + +int +yylex(void) +{ + int value; + const char *buf = arith_buf; + char *end; + 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 = strtoarith_t(buf, &end); + arith_buf = end; + 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); + memcpy(yylval.name, p, buf - p); + yylval.name[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 '+': + if (buf[1] == '+') + return ARITH_BAD; + value += ARITH_ADD - '+'; + goto checkeq; + case '-': + if (buf[1] == '-') + return ARITH_BAD; + 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/1sh/bltin.h b/bin/1sh/bltin.h new file mode 100644 index 00000000..3d2cb4d3 --- /dev/null +++ b/bin/1sh/bltin.h @@ -0,0 +1,81 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + * + * @(#)bltin.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/bltin/bltin.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* + * 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" +#ifdef SHELL +#include "error.h" +#include "output.h" +#include "builtins.h" +#define FILE struct output +#undef stdout +#define stdout out1 +#undef stderr +#define stderr out2 +#define printf out1fmt +#undef putc +#define putc(c, file) outc(c, file) +#undef putchar +#define putchar(c) out1c(c) +#define fprintf outfmt +#define fputs outstr +#define fwrite(ptr, size, nmemb, file) outbin(ptr, (size) * (nmemb), file) +#define fflush flushout +#define INITARGS(argv) +#define warnx warning +#define warn(fmt, ...) warning(fmt ": %s", __VA_ARGS__, strerror(errno)) +#define errx(exitstatus, ...) error(__VA_ARGS__) + +#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 + +#include <unistd.h> + +pointer stalloc(int); +int killjob(const char *, int); + +extern char *commandname; diff --git a/bin/1sh/builtins.def b/bin/1sh/builtins.def new file mode 100644 index 00000000..5986a5a6 --- /dev/null +++ b/bin/1sh/builtins.def @@ -0,0 +1,96 @@ +#!/bin/sh - + +#- +# Copyright (c) 1991, 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. +# +# @(#)builtins.def 8.4 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/builtins.def 319576 2017-06-04 21:02:48Z bdrewery $ + +# +# This file lists all the builtin commands. The first column is the name +# of a C routine. +# The -j flag specifies that this command is to be excluded from systems +# without job control. +# The -h flag specifies that this command is to be excluded from systems +# based on the NO_HISTORY compile-time symbol. +# The -n flag specifies that this command can safely be run in the same +# process when it is the only command in a command substitution. Some +# commands have special logic defined in safe_builtin(). +# The -s flag specifies that this is a POSIX 'special built-in' command. +# The rest of the line specifies the command name or names used to run the +# command. The entry for bltincmd, which is run when the user does not specify +# a command, must come first. +# +# NOTE: bltincmd must come first! + +bltincmd -n builtin +aliascmd alias +bgcmd -j bg +bindcmd bind +breakcmd -s break -s continue +cdcmd cd chdir +commandcmd -n command +dotcmd -s . +echocmd -n echo +evalcmd -s eval +execcmd -s exec +exitcmd -s exit +letcmd let +exportcmd -s export -s readonly +#exprcmd expr +falsecmd -n false +fgcmd -j fg +freebsd_wordexpcmd freebsd_wordexp +getoptscmd getopts +hashcmd hash +histcmd -h fc +jobidcmd -n jobid +jobscmd -n jobs +killcmd -n kill +localcmd local +printfcmd -n printf +pwdcmd -n pwd +readcmd read +returncmd -s return +setcmd -s set +setvarcmd setvar +shiftcmd -s shift +testcmd -n test [ +timescmd -n -s times +trapcmd -s trap +truecmd -n -s : true +typecmd -n type +ulimitcmd ulimit +umaskcmd umask +unaliascmd unalias +unsetcmd -s unset +waitcmd wait +wordexpcmd wordexp diff --git a/bin/1sh/cd.c b/bin/1sh/cd.c new file mode 100644 index 00000000..61bb60cf --- /dev/null +++ b/bin/1sh/cd.c @@ -0,0 +1,430 @@ +/*- + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)cd.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/cd.c 336320 2018-07-15 21:55:17Z jilles $ */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> + +/* + * 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 "mystring.h" +#include "show.h" +#include "cd.h" +#include "builtins.h" + +static int cdlogical(char *); +static int cdphysical(char *); +static int docd(char *, int, int); +static char *getcomponent(char **); +static char *findcwd(char *); +static void updatepwd(char *); +static char *getpwd(void); +static char *getpwd2(void); + +static char *curdir = NULL; /* current working directory */ + +int +cdcmd(int argc __unused, char **argv __unused) +{ + const char *dest; + const char *path; + char *p; + struct stat statb; + int ch, phys, print = 0, getcwderr = 0; + int rc; + int errno1 = ENOENT; + + phys = Pflag; + while ((ch = nextopt("eLP")) != '\0') { + switch (ch) { + case 'e': + getcwderr = 1; + break; + case 'L': + phys = 0; + break; + case 'P': + phys = 1; + break; + } + } + + if (*argptr != NULL && argptr[1] != NULL) + error("too many arguments"); + + if ((dest = *argptr) == NULL && (dest = bltinlookup("HOME", 1)) == NULL) + error("HOME not set"); + if (*dest == '\0') + dest = "."; + if (dest[0] == '-' && dest[1] == '\0') { + dest = bltinlookup("OLDPWD", 1); + if (dest == NULL) + error("OLDPWD not set"); + print = 1; + } + if (dest[0] == '/' || + (dest[0] == '.' && (dest[1] == '/' || dest[1] == '\0')) || + (dest[0] == '.' && dest[1] == '.' && (dest[2] == '/' || dest[2] == '\0')) || + (path = bltinlookup("CDPATH", 1)) == NULL) + path = ""; + while ((p = padvance(&path, NULL, dest)) != NULL) { + if (stat(p, &statb) < 0) { + if (errno != ENOENT) + errno1 = errno; + } else if (!S_ISDIR(statb.st_mode)) + errno1 = ENOTDIR; + else { + if (!print) { + /* + * XXX - rethink + */ + if (p[0] == '.' && p[1] == '/' && p[2] != '\0') + print = strcmp(p + 2, dest); + else + print = strcmp(p, dest); + } + rc = docd(p, print, phys); + if (rc >= 0) + return getcwderr ? rc : 0; + if (errno != ENOENT) + errno1 = errno; + } + } + error("%s: %s", dest, strerror(errno1)); + /*NOTREACHED*/ + return 0; +} + + +/* + * Actually change the directory. In an interactive shell, print the + * directory name if "print" is nonzero. + */ +static int +docd(char *dest, int print, int phys) +{ + int rc; + + TRACE(("docd(\"%s\", %d, %d) called\n", dest, print, phys)); + + /* If logical cd fails, fall back to physical. */ + if ((phys || (rc = cdlogical(dest)) < 0) && (rc = cdphysical(dest)) < 0) + return (-1); + + if (print && iflag && curdir) { + out1fmt("%s\n", curdir); + /* + * Ignore write errors to preserve the invariant that the + * current directory is changed iff the exit status is 0 + * (or 1 if -e was given and the full pathname could not be + * determined). + */ + flushout(out1); + outclearerror(out1); + } + + return (rc); +} + +static int +cdlogical(char *dest) +{ + char *p; + char *q; + char *component; + char *path; + struct stat statb; + int first; + int badstat; + + /* + * Check each component of the path. If we find a symlink or + * something we can't stat, clear curdir to force a getcwd() + * next time we get the value of the current directory. + */ + badstat = 0; + path = stsavestr(dest); + STARTSTACKSTR(p); + if (*dest == '/') { + STPUTC('/', p); + path++; + } + first = 1; + while ((q = getcomponent(&path)) != NULL) { + if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0')) + continue; + if (! first) + STPUTC('/', p); + first = 0; + component = q; + STPUTS(q, p); + if (equal(component, "..")) + continue; + STACKSTRNUL(p); + if (lstat(stackblock(), &statb) < 0) { + badstat = 1; + break; + } + } + + INTOFF; + if ((p = findcwd(badstat ? NULL : dest)) == NULL || chdir(p) < 0) { + INTON; + return (-1); + } + updatepwd(p); + INTON; + return (0); +} + +static int +cdphysical(char *dest) +{ + char *p; + int rc = 0; + + INTOFF; + if (chdir(dest) < 0) { + INTON; + return (-1); + } + p = findcwd(NULL); + if (p == NULL) { + warning("warning: failed to get name of current directory"); + rc = 1; + } + updatepwd(p); + INTON; + return (rc); +} + +/* + * Get the next component of the path name pointed to by *path. + * This routine overwrites *path and the string pointed to by it. + */ +static char * +getcomponent(char **path) +{ + char *p; + char *start; + + if ((p = *path) == NULL) + return NULL; + start = *path; + while (*p != '/' && *p != '\0') + p++; + if (*p == '\0') { + *path = NULL; + } else { + *p++ = '\0'; + *path = p; + } + return start; +} + + +static char * +findcwd(char *dir) +{ + char *new; + char *p; + char *path; + + /* + * If our argument is NULL, we don't know the current directory + * any more because we traversed a symbolic link or something + * we couldn't stat(). + */ + if (dir == NULL || curdir == NULL) + return getpwd2(); + path = stsavestr(dir); + STARTSTACKSTR(new); + if (*dir != '/') { + STPUTS(curdir, new); + if (STTOPC(new) == '/') + STUNPUTC(new); + } + while ((p = getcomponent(&path)) != NULL) { + if (equal(p, "..")) { + while (new > stackblock() && (STUNPUTC(new), *new) != '/'); + } else if (*p != '\0' && ! equal(p, ".")) { + STPUTC('/', new); + STPUTS(p, new); + } + } + if (new == stackblock()) + STPUTC('/', new); + STACKSTRNUL(new); + return stackblock(); +} + +/* + * Update curdir (the name of the current directory) in response to a + * cd command. We also call hashcd to let the routines in exec.c know + * that the current directory has changed. + */ +static void +updatepwd(char *dir) +{ + char *prevdir; + + hashcd(); /* update command hash table */ + + setvar("PWD", dir, VEXPORT); + setvar("OLDPWD", curdir, VEXPORT); + prevdir = curdir; + curdir = dir ? savestr(dir) : NULL; + ckfree(prevdir); +} + +int +pwdcmd(int argc __unused, char **argv __unused) +{ + char *p; + int ch, phys; + + phys = Pflag; + while ((ch = nextopt("LP")) != '\0') { + switch (ch) { + case 'L': + phys = 0; + break; + case 'P': + phys = 1; + break; + } + } + + if (*argptr != NULL) + error("too many arguments"); + + if (!phys && getpwd()) { + out1str(curdir); + out1c('\n'); + } else { + if ((p = getpwd2()) == NULL) + error(".: %s", strerror(errno)); + out1str(p); + out1c('\n'); + } + + return 0; +} + +/* + * Get the current directory and cache the result in curdir. + */ +static char * +getpwd(void) +{ + char *p; + + if (curdir) + return curdir; + + p = getpwd2(); + if (p != NULL) + curdir = savestr(p); + + return curdir; +} + +#define MAXPWD 256 + +/* + * Return the current directory. + */ +static char * +getpwd2(void) +{ + char *pwd; + int i; + + for (i = MAXPWD;; i *= 2) { + pwd = stalloc(i); + if (getcwd(pwd, i) != NULL) + return pwd; + stunalloc(pwd); + if (errno != ERANGE) + break; + } + + return NULL; +} + +/* + * Initialize PWD in a new shell. + * If the shell is interactive, we need to warn if this fails. + */ +void +pwd_init(int warn) +{ + char *pwd; + struct stat stdot, stpwd; + + pwd = lookupvar("PWD"); + if (pwd && *pwd == '/' && stat(".", &stdot) != -1 && + stat(pwd, &stpwd) != -1 && + stdot.st_dev == stpwd.st_dev && + stdot.st_ino == stpwd.st_ino) { + if (curdir) + ckfree(curdir); + curdir = savestr(pwd); + } + if (getpwd() == NULL && warn) + out2fmt_flush("sh: cannot determine working directory\n"); + setvar("PWD", curdir, VEXPORT); +} diff --git a/bin/1sh/cd.h b/bin/1sh/cd.h new file mode 100644 index 00000000..dfa5c3e5 --- /dev/null +++ b/bin/1sh/cd.h @@ -0,0 +1,32 @@ +/*- + * Copyright (c) 1995 + * 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. + * + * $FreeBSD: releng/12.1/bin/sh/cd.h 314436 2017-02-28 23:42:47Z imp $ + */ + +void pwd_init(int); diff --git a/bin/1sh/echo.c b/bin/1sh/echo.c new file mode 100644 index 00000000..5fd50f59 --- /dev/null +++ b/bin/1sh/echo.c @@ -0,0 +1,109 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + * + * @(#)echo.c 8.2 (Berkeley) 5/4/95 + */ + +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/bltin/echo.c 326025 2017-11-20 19:49:47Z pfg $ */ + +/* + * Echo command. + */ + +#define main echocmd + +#include "bltin.h" + +/* #define eflag 1 */ + +int +main(int argc, char *argv[]) +{ + char **ap; + char *p; + char c; + int count; + int nflag = 0; +#ifndef eflag + int eflag = 0; +#endif + + ap = argv; + if (argc) + ap++; + if ((p = *ap) != NULL) { + if (equal(p, "-n")) { + nflag++; + ap++; + } else if (equal(p, "-e")) { +#ifndef eflag + eflag++; +#endif + ap++; + } + } + while ((p = *ap++) != NULL) { + while ((c = *p++) != '\0') { + if (c == '\\' && eflag) { + switch (*p++) { + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 'c': return 0; /* exit */ + case 'e': c = '\033'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case '\\': break; /* c = '\\' */ + case '0': + c = 0; + count = 3; + while (--count >= 0 && (unsigned)(*p - '0') < 8) + c = (c << 3) + (*p++ - '0'); + break; + default: + p--; + break; + } + } + putchar(c); + } + if (*ap) + putchar(' '); + } + if (! nflag) + putchar('\n'); + return 0; +} diff --git a/bin/1sh/error.c b/bin/1sh/error.c new file mode 100644 index 00000000..57c10fae --- /dev/null +++ b/bin/1sh/error.c @@ -0,0 +1,199 @@ +/*- + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)error.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/error.c 314436 2017-02-28 23:42:47Z imp $ */ + +/* + * Errors and exceptions. + */ + +#include "shell.h" +#include "eval.h" +#include "main.h" +#include "options.h" +#include "output.h" +#include "error.h" +#include "nodes.h" /* show.h needs nodes.h */ +#include "show.h" +#include "trap.h" +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> + + +/* + * Code to handle exceptions in C. + */ + +struct jmploc *handler; +volatile sig_atomic_t exception; +volatile sig_atomic_t suppressint; +volatile sig_atomic_t intpending; + + +static void exverror(int, const char *, va_list) __attribute__((format(printf, 2, 0))) __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". + * + * Interrupts are disabled; they should be reenabled when the exception is + * caught. + */ + +void +exraise(int e) +{ + INTOFF; + if (handler == NULL) + abort(); + exception = e; + longjmp(handler->loc, 1); +} + + +/* + * Called from trap.c when a SIGINT is received and not suppressed, or when + * an interrupt is pending and interrupts are re-enabled using INTON. + * (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. If SIGINTs are not + * suppressed and the shell is not a root shell, then we want to be + * terminated if we get here, as if we were terminated directly by a SIGINT. + * Arrange for this here. + */ + +void +onint(void) +{ + sigset_t sigs; + + intpending = 0; + sigemptyset(&sigs); + sigprocmask(SIG_SETMASK, &sigs, NULL); + + /* + * This doesn't seem to be needed, since main() emits a newline. + */ +#if 0 + if (tcgetpgrp(0) == getpid()) + write(STDERR_FILENO, "\n", 1); +#endif + if (rootshell && iflag) + exraise(EXINT); + else { + signal(SIGINT, SIG_DFL); + kill(getpid(), SIGINT); + _exit(128 + SIGINT); + } +} + + +static void +vwarning(const char *msg, va_list ap) +{ + if (commandname) + outfmt(out2, "%s: ", commandname); + else if (arg0) + outfmt(out2, "%s: ", arg0); + doformat(out2, msg, ap); + out2fmt_flush("\n"); +} + + +void +warning(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + vwarning(msg, ap); + va_end(ap); +} + + +/* + * Exverror is called to raise the error exception. If the first 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) +{ + /* + * An interrupt trumps an error. Certain places catch error + * exceptions or transform them to a plain nonzero exit code + * in child processes, and if an error exception can be handled, + * an interrupt can be handled as well. + * + * exraise() will disable interrupts for the exception handler. + */ + FORCEINTON; + +#ifdef DEBUG + if (msg) + TRACE(("exverror(%d, \"%s\") pid=%d\n", cond, msg, getpid())); + else + TRACE(("exverror(%d, NULL) pid=%d\n", cond, getpid())); +#endif + if (msg) + vwarning(msg, ap); + flushall(); + exraise(cond); +} + + +void +error(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + exverror(EXERROR, msg, ap); + va_end(ap); +} + + +void +exerror(int cond, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + exverror(cond, msg, ap); + va_end(ap); +} diff --git a/bin/1sh/error.h b/bin/1sh/error.h new file mode 100644 index 00000000..0401598f --- /dev/null +++ b/bin/1sh/error.h @@ -0,0 +1,95 @@ +/*- + * Copyright (c) 1991, 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. + * + * @(#)error.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/error.h 319591 2017-06-04 21:58:02Z jilles $ + */ + +/* + * 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 exception. 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. + */ + +#include <setjmp.h> +#include <signal.h> + +struct jmploc { + jmp_buf loc; +}; + +extern struct jmploc *handler; +extern volatile sig_atomic_t exception; + +/* exceptions */ +#define EXINT 0 /* SIGINT received */ +#define EXERROR 1 /* a generic error */ +#define EXEXEC 2 /* command execution failed */ +#define EXEXIT 3 /* call exitshell(exitstatus) */ + + +/* + * 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 volatile sig_atomic_t suppressint; +extern volatile sig_atomic_t intpending; + +#define INTOFF suppressint++ +#define INTON { if (--suppressint == 0 && intpending) onint(); } +#define is_int_on() suppressint +#define SETINTON(s) do { suppressint = (s); if (suppressint == 0 && intpending) onint(); } while (0) +#define FORCEINTON {suppressint = 0; if (intpending) onint();} +#define SET_PENDING_INT intpending = 1 +#define CLEAR_PENDING_INT intpending = 0 +#define int_pending() intpending + +void exraise(int) __attribute__((noreturn)); +void onint(void) __attribute__((noreturn)); +void warning(const char *, ...) __attribute__((format(printf, 1, 2))); +void error(const char *, ...) __attribute__((format(printf, 1, 2))) __attribute__((noreturn)); +void exerror(int, const char *, ...) __attribute__((format(printf, 2, 3))) __attribute__((noreturn)); + + +/* + * BSD setjmp saves the signal mask, which violates ANSI C and takes time, + * so we use _setjmp instead. + */ + +#define setjmp(jmploc) _setjmp(jmploc) +#define longjmp(jmploc, val) _longjmp(jmploc, val) diff --git a/bin/1sh/eval.c b/bin/1sh/eval.c new file mode 100644 index 00000000..f4baf39e --- /dev/null +++ b/bin/1sh/eval.c @@ -0,0 +1,1381 @@ +/*- + * 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)eval.c 8.9 (Berkeley) 6/8/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/eval.c 327212 2017-12-26 16:23:18Z jilles $ */ + +#include <paths.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/resource.h> +#include <errno.h> + +/* + * Evaluate a command. + */ + +#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 NO_HISTORY +#include "myhistedit.h" +#endif + + +int evalskip; /* set if we are skipping commands */ +int skipcount; /* number of levels to skip */ +static int loopnest; /* current loop nesting level */ +int funcnest; /* depth of function calls */ +static int builtin_flags; /* evalcommand flags for builtins */ + + +char *commandname; +struct arglist *cmdenviron; +int exitstatus; /* exit status of last command */ +int oexitstatus; /* saved exit status */ + + +static void evalloop(union node *, int); +static void evalfor(union node *, int); +static union node *evalcase(union node *); +static void evalsubshell(union node *, int); +static void evalredir(union node *, int); +static void exphere(union node *, struct arglist *); +static void expredir(union node *); +static void evalpipe(union node *); +static int is_valid_fast_cmdsubst(union node *n); +static void evalcommand(union node *, int, struct backcmd *); +static void prehash(union node *); + + +/* + * Called to reset things after an exception. + */ + +void +reseteval(void) +{ + evalskip = 0; + loopnest = 0; +} + + +/* + * The eval command. + */ + +int +evalcmd(int argc, char **argv) +{ + char *p; + char *concat; + char **ap; + + if (argc > 1) { + p = argv[1]; + if (argc > 2) { + STARTSTACKSTR(concat); + ap = argv + 2; + for (;;) { + STPUTS(p, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + evalstring(p, builtin_flags); + } else + exitstatus = 0; + return exitstatus; +} + + +/* + * Execute a command or commands contained in a string. + */ + +void +evalstring(const char *s, int flags) +{ + union node *n; + struct stackmark smark; + int flags_exit; + int any; + + flags_exit = flags & EV_EXIT; + flags &= ~EV_EXIT; + any = 0; + setstackmark(&smark); + setinputstring(s, 1); + while ((n = parsecmd(0)) != NEOF) { + if (n != NULL && !nflag) { + if (flags_exit && preadateof()) + evaltree(n, flags | EV_EXIT); + else + evaltree(n, flags); + any = 1; + if (evalskip) + break; + } + popstackmark(&smark); + setstackmark(&smark); + } + popfile(); + popstackmark(&smark); + if (!any) + exitstatus = 0; + if (flags_exit) + exraise(EXEXIT); +} + + +/* + * Evaluate a parse tree. The value is left in the global variable + * exitstatus. + */ + +void +evaltree(union node *n, int flags) +{ + int do_etest; + union node *next; + struct stackmark smark; + + setstackmark(&smark); + do_etest = 0; + if (n == NULL) { + TRACE(("evaltree(NULL) called\n")); + exitstatus = 0; + goto out; + } + do { + next = NULL; +#ifndef NO_HISTORY + displayhist = 1; /* show history substitutions done with fc */ +#endif + TRACE(("evaltree(%p: %d) called\n", (void *)n, n->type)); + switch (n->type) { + case NSEMI: + evaltree(n->nbinary.ch1, flags & ~EV_EXIT); + if (evalskip) + goto out; + next = n->nbinary.ch2; + break; + case NAND: + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip || exitstatus != 0) { + goto out; + } + next = n->nbinary.ch2; + break; + case NOR: + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip || exitstatus == 0) + goto out; + next = n->nbinary.ch2; + break; + case NREDIR: + evalredir(n, flags); + break; + case NSUBSHELL: + evalsubshell(n, flags); + do_etest = !(flags & EV_TESTED); + break; + case NBACKGND: + evalsubshell(n, flags); + break; + case NIF: { + evaltree(n->nif.test, EV_TESTED); + if (evalskip) + goto out; + if (exitstatus == 0) + next = n->nif.ifpart; + else if (n->nif.elsepart) + next = n->nif.elsepart; + else + exitstatus = 0; + break; + } + case NWHILE: + case NUNTIL: + evalloop(n, flags & ~EV_EXIT); + break; + case NFOR: + evalfor(n, flags & ~EV_EXIT); + break; + case NCASE: + next = evalcase(n); + break; + case NCLIST: + next = n->nclist.body; + break; + case NCLISTFALLTHRU: + if (n->nclist.body) { + evaltree(n->nclist.body, flags & ~EV_EXIT); + if (evalskip) + goto out; + } + next = n->nclist.next; + break; + case NDEFUN: + defun(n->narg.text, n->narg.next); + exitstatus = 0; + break; + case NNOT: + evaltree(n->nnot.com, EV_TESTED); + if (evalskip) + goto out; + exitstatus = !exitstatus; + break; + + case NPIPE: + evalpipe(n); + do_etest = !(flags & EV_TESTED); + break; + case NCMD: + evalcommand(n, flags, (struct backcmd *)NULL); + do_etest = !(flags & EV_TESTED); + break; + default: + out1fmt("Node type = %d\n", n->type); + flushout(&output); + break; + } + n = next; + popstackmark(&smark); + setstackmark(&smark); + } while (n != NULL); +out: + popstackmark(&smark); + if (pendingsig) + dotrap(); + if (eflag && exitstatus != 0 && do_etest) + exitshell(exitstatus); + if (flags & EV_EXIT) + exraise(EXEXIT); +} + + +static void +evalloop(union node *n, int flags) +{ + int status; + + loopnest++; + status = 0; + for (;;) { + if (!evalskip) + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + if (evalskip == SKIPRETURN) + status = exitstatus; + break; + } + if (n->type == NWHILE) { + if (exitstatus != 0) + break; + } else { + if (exitstatus == 0) + break; + } + evaltree(n->nbinary.ch2, flags); + status = exitstatus; + } + loopnest--; + exitstatus = status; +} + + + +static void +evalfor(union node *n, int flags) +{ + struct arglist arglist; + union node *argp; + int i; + int status; + + emptyarglist(&arglist); + for (argp = n->nfor.args ; argp ; argp = argp->narg.next) { + oexitstatus = exitstatus; + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + } + + loopnest++; + status = 0; + for (i = 0; i < arglist.count; i++) { + setvar(n->nfor.var, arglist.args[i], 0); + evaltree(n->nfor.body, flags); + status = exitstatus; + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + break; + } + } + loopnest--; + exitstatus = status; +} + + +/* + * Evaluate a case statement, returning the selected tree. + * + * The exit status needs care to get right. + */ + +static union node * +evalcase(union node *n) +{ + union node *cp; + union node *patp; + struct arglist arglist; + + emptyarglist(&arglist); + oexitstatus = exitstatus; + expandarg(n->ncase.expr, &arglist, EXP_TILDE); + for (cp = n->ncase.cases ; cp ; cp = cp->nclist.next) { + for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) { + if (casematch(patp, arglist.args[0])) { + while (cp->nclist.next && + cp->type == NCLISTFALLTHRU && + cp->nclist.body == NULL) + cp = cp->nclist.next; + if (cp->nclist.next && + cp->type == NCLISTFALLTHRU) + return (cp); + if (cp->nclist.body == NULL) + exitstatus = 0; + return (cp->nclist.body); + } + } + } + exitstatus = 0; + return (NULL); +} + + + +/* + * Kick off a subshell to evaluate a tree. + */ + +static void +evalsubshell(union node *n, int flags) +{ + struct job *jp; + int backgnd = (n->type == NBACKGND); + + oexitstatus = exitstatus; + expredir(n->nredir.redirect); + if ((!backgnd && flags & EV_EXIT && !have_traps()) || + forkshell(jp = makejob(n, 1), n, backgnd) == 0) { + if (backgnd) + flags &=~ EV_TESTED; + redirect(n->nredir.redirect, 0); + evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */ + } else if (! backgnd) { + INTOFF; + exitstatus = waitforjob(jp, (int *)NULL); + INTON; + } else + exitstatus = 0; +} + + +/* + * Evaluate a redirected compound command. + */ + +static void +evalredir(union node *n, int flags) +{ + struct jmploc jmploc; + struct jmploc *savehandler; + volatile int in_redirect = 1; + + oexitstatus = exitstatus; + expredir(n->nredir.redirect); + savehandler = handler; + if (setjmp(jmploc.loc)) { + int e; + + handler = savehandler; + e = exception; + popredir(); + if (e == EXERROR || e == EXEXEC) { + if (in_redirect) { + exitstatus = 2; + FORCEINTON; + return; + } + } + longjmp(handler->loc, 1); + } else { + INTOFF; + handler = &jmploc; + redirect(n->nredir.redirect, REDIR_PUSH); + in_redirect = 0; + INTON; + evaltree(n->nredir.n, flags); + } + INTOFF; + handler = savehandler; + popredir(); + INTON; +} + + +static void +exphere(union node *redir, struct arglist *fn) +{ + struct jmploc jmploc; + struct jmploc *savehandler; + struct localvar *savelocalvars; + int need_longjmp = 0; + unsigned char saveoptreset; + + redir->nhere.expdoc = ""; + savelocalvars = localvars; + localvars = NULL; + saveoptreset = shellparam.reset; + forcelocal++; + savehandler = handler; + if (setjmp(jmploc.loc)) + need_longjmp = exception != EXERROR && exception != EXEXEC; + else { + handler = &jmploc; + expandarg(redir->nhere.doc, fn, 0); + redir->nhere.expdoc = fn->args[0]; + INTOFF; + } + handler = savehandler; + forcelocal--; + poplocalvars(); + localvars = savelocalvars; + shellparam.reset = saveoptreset; + if (need_longjmp) + longjmp(handler->loc, 1); + INTON; +} + + +/* + * 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; + emptyarglist(&fn); + switch (redir->type) { + case NFROM: + case NTO: + case NFROMTO: + case NAPPEND: + case NCLOBBER: + expandarg(redir->nfile.fname, &fn, EXP_TILDE); + redir->nfile.expfname = fn.args[0]; + break; + case NFROMFD: + case NTOFD: + if (redir->ndup.vname) { + expandarg(redir->ndup.vname, &fn, EXP_TILDE); + fixredir(redir, fn.args[0], 1); + } + break; + case NXHERE: + exphere(redir, &fn); + 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 void +evalpipe(union node *n) +{ + struct job *jp; + struct nodelist *lp; + int pipelen; + int prevfd; + int pip[2]; + + TRACE(("evalpipe(%p) called\n", (void *)n)); + pipelen = 0; + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) + pipelen++; + 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) { + if (prevfd >= 0) + close(prevfd); + error("Pipe call failed: %s", strerror(errno)); + } + } + if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) { + INTON; + if (prevfd > 0) { + dup2(prevfd, 0); + close(prevfd); + } + if (pip[1] >= 0) { + if (!(prevfd >= 0 && pip[0] == 0)) + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + } + evaltree(lp->n, EV_EXIT); + } + if (prevfd >= 0) + close(prevfd); + prevfd = pip[0]; + if (pip[1] != -1) + close(pip[1]); + } + INTON; + if (n->npipe.backgnd == 0) { + INTOFF; + exitstatus = waitforjob(jp, (int *)NULL); + TRACE(("evalpipe: job done exit status %d\n", exitstatus)); + INTON; + } else + exitstatus = 0; +} + + + +static int +is_valid_fast_cmdsubst(union node *n) +{ + + return (n->type == NCMD); +} + +/* + * 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; + struct stackmark smark; + struct jmploc jmploc; + struct jmploc *savehandler; + struct localvar *savelocalvars; + unsigned char saveoptreset; + + result->fd = -1; + result->buf = NULL; + result->nleft = 0; + result->jp = NULL; + if (n == NULL) { + exitstatus = 0; + return; + } + setstackmark(&smark); + exitstatus = oexitstatus; + if (is_valid_fast_cmdsubst(n)) { + savelocalvars = localvars; + localvars = NULL; + saveoptreset = shellparam.reset; + forcelocal++; + savehandler = handler; + if (setjmp(jmploc.loc)) { + if (exception == EXERROR || exception == EXEXEC) + exitstatus = 2; + else if (exception != 0) { + handler = savehandler; + forcelocal--; + poplocalvars(); + localvars = savelocalvars; + shellparam.reset = saveoptreset; + longjmp(handler->loc, 1); + } + } else { + handler = &jmploc; + evalcommand(n, EV_BACKCMD, result); + } + handler = savehandler; + forcelocal--; + poplocalvars(); + localvars = savelocalvars; + shellparam.reset = saveoptreset; + } else { + if (pipe(pip) < 0) + error("Pipe call failed: %s", strerror(errno)); + 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]); + } + evaltree(n, EV_EXIT); + } + close(pip[1]); + result->fd = pip[0]; + result->jp = jp; + } + popstackmark(&smark); + TRACE(("evalbackcmd done: fd=%d buf=%p nleft=%d jp=%p\n", + result->fd, result->buf, result->nleft, result->jp)); +} + +static int +mustexpandto(const char *argtext, const char *mask) +{ + for (;;) { + if (*argtext == CTLQUOTEMARK || *argtext == CTLQUOTEEND) { + argtext++; + continue; + } + if (*argtext == CTLESC) + argtext++; + else if (BASESYNTAX[(int)*argtext] == CCTL) + return (0); + if (*argtext != *mask) + return (0); + if (*argtext == '\0') + return (1); + argtext++; + mask++; + } +} + +static int +isdeclarationcmd(struct narg *arg) +{ + int have_command = 0; + + if (arg == NULL) + return (0); + while (mustexpandto(arg->text, "command")) { + have_command = 1; + arg = &arg->next->narg; + if (arg == NULL) + return (0); + /* + * To also allow "command -p" and "command --" as part of + * a declaration command, add code here. + * We do not do this, as ksh does not do it either and it + * is not required by POSIX. + */ + } + return (mustexpandto(arg->text, "export") || + mustexpandto(arg->text, "readonly") || + (mustexpandto(arg->text, "local") && + (have_command || !isfunc("local")))); +} + +static void +xtracecommand(struct arglist *varlist, int argc, char **argv) +{ + char sep = 0; + const char *text, *p, *ps4; + int i; + + ps4 = expandstr(ps4val()); + out2str(ps4 != NULL ? ps4 : ps4val()); + for (i = 0; i < varlist->count; i++) { + text = varlist->args[i]; + if (sep != 0) + out2c(' '); + p = strchr(text, '='); + if (p != NULL) { + p++; + outbin(text, p - text, out2); + out2qstr(p); + } else + out2qstr(text); + sep = ' '; + } + for (i = 0; i < argc; i++) { + text = argv[i]; + if (sep != 0) + out2c(' '); + out2qstr(text); + sep = ' '; + } + out2c('\n'); + flushout(&errout); +} + +/* + * Check if a builtin can safely be executed in the same process, + * even though it should be in a subshell (command substitution). + * Note that jobid, jobs, times and trap can show information not + * available in a child process; this is deliberate. + * The arguments should already have been expanded. + */ +static int +safe_builtin(int idx, int argc, char **argv) +{ + /* Generated from builtins.def. */ + if (safe_builtin_always(idx)) + return (1); + if (idx == EXPORTCMD || idx == TRAPCMD || idx == ULIMITCMD || + idx == UMASKCMD) + return (argc <= 1 || (argc == 2 && argv[1][0] == '-')); + if (idx == SETCMD) + return (argc <= 1 || (argc == 2 && (argv[1][0] == '-' || + argv[1][0] == '+') && argv[1][1] == 'o' && + argv[1][2] == '\0')); + return (0); +} + +/* + * Execute a simple command. + * Note: This may or may not return if (flags & EV_EXIT). + */ + +static void +evalcommand(union node *cmd, int flags, struct backcmd *backcmd) +{ + union node *argp; + struct arglist arglist; + struct arglist varlist; + char **argv; + int argc; + char **envp; + int varflag; + int mode; + int pip[2]; + struct cmdentry cmdentry; + struct job *jp; + struct jmploc jmploc; + struct jmploc *savehandler; + char *savecmdname; + struct shparam saveparam; + struct localvar *savelocalvars; + struct parsefile *savetopfile; + volatile int e; + char *lastarg; + int signaled; + int do_clearcmdentry; + const char *path = pathval(); + int i; + + /* First expand the arguments. */ + TRACE(("evalcommand(%p, %d) called\n", (void *)cmd, flags)); + emptyarglist(&arglist); + emptyarglist(&varlist); + varflag = 1; + jp = NULL; + do_clearcmdentry = 0; + oexitstatus = exitstatus; + exitstatus = 0; + /* Add one slot at the beginning for tryexec(). */ + appendarglist(&arglist, nullstr); + for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) { + if (varflag && isassignment(argp->narg.text)) { + expandarg(argp, varflag == 1 ? &varlist : &arglist, + EXP_VARTILDE); + continue; + } else if (varflag == 1) + varflag = isdeclarationcmd(&argp->narg) ? 2 : 0; + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + } + appendarglist(&arglist, nullstr); + expredir(cmd->ncmd.redirect); + argc = arglist.count - 2; + argv = &arglist.args[1]; + + argv[argc] = NULL; + lastarg = NULL; + if (iflag && funcnest == 0 && argc > 0) + lastarg = argv[argc - 1]; + + /* Print the command if xflag is set. */ + if (xflag) + xtracecommand(&varlist, argc, argv); + + /* Now locate the command. */ + if (argc == 0) { + /* Variable assignment(s) without command */ + cmdentry.cmdtype = CMDBUILTIN; + cmdentry.u.index = BLTINCMD; + cmdentry.special = 0; + } else { + static const char PATH[] = "PATH="; + int cmd_flags = 0, bltinonly = 0; + + /* + * Modify the command lookup path, if a PATH= assignment + * is present + */ + for (i = 0; i < varlist.count; i++) + if (strncmp(varlist.args[i], PATH, sizeof(PATH) - 1) == 0) { + path = varlist.args[i] + sizeof(PATH) - 1; + /* + * On `PATH=... command`, we need to make + * sure that the command isn't using the + * non-updated hash table of the outer PATH + * setting and we need to make sure that + * the hash table isn't filled with items + * from the temporary setting. + * + * It would be better to forbit using and + * updating the table while this command + * runs, by the command finding mechanism + * is heavily integrated with hash handling, + * so we just delete the hash before and after + * the command runs. Partly deleting like + * changepatch() does doesn't seem worth the + * bookinging effort, since most such runs add + * directories in front of the new PATH. + */ + clearcmdentry(); + do_clearcmdentry = 1; + } + + for (;;) { + if (bltinonly) { + cmdentry.u.index = find_builtin(*argv, &cmdentry.special); + if (cmdentry.u.index < 0) { + cmdentry.u.index = BLTINCMD; + argv--; + argc++; + break; + } + } else + find_command(argv[0], &cmdentry, cmd_flags, path); + /* implement the bltin and command builtins here */ + if (cmdentry.cmdtype != CMDBUILTIN) + break; + if (cmdentry.u.index == BLTINCMD) { + if (argc == 1) + break; + argv++; + argc--; + bltinonly = 1; + } else if (cmdentry.u.index == COMMANDCMD) { + if (argc == 1) + break; + if (!strcmp(argv[1], "-p")) { + if (argc == 2) + break; + if (argv[2][0] == '-') { + if (strcmp(argv[2], "--")) + break; + if (argc == 3) + break; + argv += 3; + argc -= 3; + } else { + argv += 2; + argc -= 2; + } + path = _PATH_STDPATH; + clearcmdentry(); + do_clearcmdentry = 1; + } else if (!strcmp(argv[1], "--")) { + if (argc == 2) + break; + argv += 2; + argc -= 2; + } else if (argv[1][0] == '-') + break; + else { + argv++; + argc--; + } + cmd_flags |= DO_NOFUNC; + bltinonly = 0; + } else + break; + } + /* + * Special builtins lose their special properties when + * called via 'command'. + */ + if (cmd_flags & DO_NOFUNC) + cmdentry.special = 0; + } + + /* Fork off a child process if necessary. */ + if (((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN) + && ((flags & EV_EXIT) == 0 || have_traps())) + || ((flags & EV_BACKCMD) != 0 + && (cmdentry.cmdtype != CMDBUILTIN || + !safe_builtin(cmdentry.u.index, argc, argv)))) { + jp = makejob(cmd, 1); + mode = FORK_FG; + if (flags & EV_BACKCMD) { + mode = FORK_NOJOB; + if (pipe(pip) < 0) + error("Pipe call failed: %s", strerror(errno)); + } + if (cmdentry.cmdtype == CMDNORMAL && + cmd->ncmd.redirect == NULL && + varlist.count == 0 && + (mode == FORK_FG || mode == FORK_NOJOB) && + !disvforkset() && !iflag && !mflag) { + vforkexecshell(jp, argv, environment(), path, + cmdentry.u.index, flags & EV_BACKCMD ? pip : NULL); + goto parent; + } + if (forkshell(jp, cmd, mode) != 0) + goto parent; /* at end of routine */ + if (flags & EV_BACKCMD) { + FORCEINTON; + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + flags &= ~EV_BACKCMD; + } + flags |= EV_EXIT; + } + + /* This is the child process if a fork occurred. */ + /* Execute the command. */ + if (cmdentry.cmdtype == CMDFUNCTION) { +#ifdef DEBUG + trputs("Shell function: "); trargs(argv); +#endif + saveparam = shellparam; + shellparam.malloc = 0; + shellparam.reset = 1; + shellparam.nparam = argc - 1; + shellparam.p = argv + 1; + shellparam.optp = NULL; + shellparam.optnext = NULL; + INTOFF; + savelocalvars = localvars; + localvars = NULL; + reffunc(cmdentry.u.func); + savehandler = handler; + if (setjmp(jmploc.loc)) { + popredir(); + unreffunc(cmdentry.u.func); + poplocalvars(); + localvars = savelocalvars; + freeparam(&shellparam); + shellparam = saveparam; + funcnest--; + handler = savehandler; + longjmp(handler->loc, 1); + } + handler = &jmploc; + funcnest++; + redirect(cmd->ncmd.redirect, REDIR_PUSH); + INTON; + for (i = 0; i < varlist.count; i++) + mklocal(varlist.args[i]); + exitstatus = oexitstatus; + evaltree(getfuncnode(cmdentry.u.func), + flags & (EV_TESTED | EV_EXIT)); + INTOFF; + unreffunc(cmdentry.u.func); + poplocalvars(); + localvars = savelocalvars; + freeparam(&shellparam); + shellparam = saveparam; + handler = savehandler; + funcnest--; + popredir(); + INTON; + if (evalskip == SKIPRETURN) { + evalskip = 0; + skipcount = 0; + } + if (jp) + exitshell(exitstatus); + } else if (cmdentry.cmdtype == CMDBUILTIN) { +#ifdef DEBUG + trputs("builtin command: "); trargs(argv); +#endif + mode = (cmdentry.u.index == EXECCMD)? 0 : REDIR_PUSH; + if (flags == EV_BACKCMD) { + memout.nextc = memout.buf; + mode |= REDIR_BACKQ; + } + savecmdname = commandname; + savetopfile = getcurrentfile(); + cmdenviron = &varlist; + e = -1; + savehandler = handler; + if (setjmp(jmploc.loc)) { + e = exception; + if (e == EXINT) + exitstatus = SIGINT+128; + else if (e != EXEXIT) + exitstatus = 2; + goto cmddone; + } + handler = &jmploc; + redirect(cmd->ncmd.redirect, mode); + outclearerror(out1); + /* + * If there is no command word, redirection errors should + * not be fatal but assignment errors should. + */ + if (argc == 0) + cmdentry.special = 1; + listsetvar(cmdenviron, cmdentry.special ? 0 : VNOSET); + if (argc > 0) + bltinsetlocale(); + commandname = argv[0]; + argptr = argv + 1; + nextopt_optptr = NULL; /* initialize nextopt */ + builtin_flags = flags; + exitstatus = (*builtinfunc[cmdentry.u.index])(argc, argv); + flushall(); + if (outiserror(out1)) { + warning("write error on stdout"); + if (exitstatus == 0 || exitstatus == 1) + exitstatus = 2; + } +cmddone: + if (argc > 0) + bltinunsetlocale(); + cmdenviron = NULL; + out1 = &output; + out2 = &errout; + freestdout(); + handler = savehandler; + commandname = savecmdname; + if (jp) + exitshell(exitstatus); + if (flags == EV_BACKCMD) { + backcmd->buf = memout.buf; + backcmd->nleft = memout.buf != NULL ? + memout.nextc - memout.buf : 0; + memout.buf = NULL; + memout.nextc = NULL; + memout.bufend = NULL; + memout.bufsize = 64; + } + if (cmdentry.u.index != EXECCMD) + popredir(); + if (e != -1) { + if ((e != EXERROR && e != EXEXEC) + || cmdentry.special) + exraise(e); + popfilesupto(savetopfile); + if (flags != EV_BACKCMD) + FORCEINTON; + } + } else { +#ifdef DEBUG + trputs("normal command: "); trargs(argv); +#endif + redirect(cmd->ncmd.redirect, 0); + for (i = 0; i < varlist.count; i++) + setvareq(varlist.args[i], VEXPORT|VSTACK); + envp = environment(); + shellexec(argv, envp, path, cmdentry.u.index); + /*NOTREACHED*/ + } + goto out; + +parent: /* parent process gets here (if we forked) */ + if (mode == FORK_FG) { /* argument to fork */ + INTOFF; + exitstatus = waitforjob(jp, &signaled); + INTON; + if (iflag && loopnest > 0 && signaled) { + evalskip = SKIPBREAK; + skipcount = loopnest; + } + } else if (mode == FORK_NOJOB) { + backcmd->fd = pip[0]; + close(pip[1]); + backcmd->jp = jp; + } + +out: + if (lastarg) + setvar("_", lastarg, 0); + if (do_clearcmdentry) + clearcmdentry(); +} + + + +/* + * 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 && 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, a bltin command with no arguments, or a bltin command + * with an invalid name. + */ + +int +bltincmd(int argc, char **argv) +{ + if (argc > 1) { + out2fmt_flush("%s: not found\n", argv[1]); + return 127; + } + /* + * Preserve exitstatus of a previous possible command substitution + * as POSIX mandates + */ + return 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) +{ + long n; + char *end; + + if (argc > 1) { + /* Allow arbitrarily large numbers. */ + n = strtol(argv[1], &end, 10); + if (!is_digit(argv[1][0]) || *end != '\0') + error("Illegal number: %s", argv[1]); + } else + n = 1; + if (n > loopnest) + n = loopnest; + if (n > 0) { + evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; + skipcount = n; + } + return 0; +} + +/* + * The `command' command. + */ +int +commandcmd(int argc __unused, char **argv __unused) +{ + const char *path; + int ch; + int cmd = -1; + + path = bltinlookup("PATH", 1); + + while ((ch = nextopt("pvV")) != '\0') { + switch (ch) { + case 'p': + path = _PATH_STDPATH; + break; + case 'v': + cmd = TYPECMD_SMALLV; + break; + case 'V': + cmd = TYPECMD_BIGV; + break; + } + } + + if (cmd != -1) { + if (*argptr == NULL || argptr[1] != NULL) + error("wrong number of arguments"); + return typecmd_impl(2, argptr - 1, cmd, path); + } + if (*argptr != NULL) + error("commandcmd bad call"); + + /* + * Do nothing successfully if no command was specified; + * ksh also does this. + */ + return 0; +} + + +/* + * The return command. + */ + +int +returncmd(int argc, char **argv) +{ + int ret = argc > 1 ? number(argv[1]) : oexitstatus; + + evalskip = SKIPRETURN; + skipcount = 1; + return ret; +} + + +int +falsecmd(int argc __unused, char **argv __unused) +{ + return 1; +} + + +int +truecmd(int argc __unused, char **argv __unused) +{ + return 0; +} + + +int +execcmd(int argc, char **argv) +{ + int i; + + /* + * Because we have historically not supported any options, + * only treat "--" specially. + */ + if (argc > 1 && strcmp(argv[1], "--") == 0) + argc--, argv++; + if (argc > 1) { + iflag = 0; /* exit on error */ + mflag = 0; + optschanged(); + for (i = 0; i < cmdenviron->count; i++) + setvareq(cmdenviron->args[i], VEXPORT|VSTACK); + shellexec(argv + 1, environment(), pathval(), 0); + + } + return 0; +} + + +int +timescmd(int argc __unused, char **argv __unused) +{ + struct rusage ru; + long shumins, shsmins, chumins, chsmins; + double shusecs, shssecs, chusecs, chssecs; + + if (getrusage(RUSAGE_SELF, &ru) < 0) + return 1; + shumins = ru.ru_utime.tv_sec / 60; + shusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.; + shsmins = ru.ru_stime.tv_sec / 60; + shssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.; + if (getrusage(RUSAGE_CHILDREN, &ru) < 0) + return 1; + chumins = ru.ru_utime.tv_sec / 60; + chusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.; + chsmins = ru.ru_stime.tv_sec / 60; + chssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.; + out1fmt("%ldm%.3fs %ldm%.3fs\n%ldm%.3fs %ldm%.3fs\n", shumins, + shusecs, shsmins, shssecs, chumins, chusecs, chsmins, chssecs); + return 0; +} diff --git a/bin/1sh/eval.h b/bin/1sh/eval.h new file mode 100644 index 00000000..d413ccd5 --- /dev/null +++ b/bin/1sh/eval.h @@ -0,0 +1,70 @@ +/*- + * Copyright (c) 1991, 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. + * + * @(#)eval.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/eval.h 314436 2017-02-28 23:42:47Z imp $ + */ + +extern char *commandname; /* currently executing command */ +extern int exitstatus; /* exit status of last command */ +extern int oexitstatus; /* saved exit status */ +extern struct arglist *cmdenviron; /* environment for builtin command */ + + +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 */ +}; + +void reseteval(void); + +/* flags in argument to evaltree/evalstring */ +#define EV_EXIT 01 /* exit after evaluating tree */ +#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ +#define EV_BACKCMD 04 /* command executing within back quotes */ + +void evalstring(const char *, int); +union node; /* BLETCH for ansi C */ +void evaltree(union node *, int); +void evalbackcmd(union node *, struct backcmd *); + +/* in_function returns nonzero if we are currently evaluating a function */ +#define in_function() funcnest +extern int funcnest; +extern int evalskip; +extern int skipcount; + +/* reasons for skipping commands (see comment on breakcmd routine) */ +#define SKIPBREAK 1 +#define SKIPCONT 2 +#define SKIPRETURN 3 diff --git a/bin/1sh/exec.c b/bin/1sh/exec.c new file mode 100644 index 00000000..81efa565 --- /dev/null +++ b/bin/1sh/exec.c @@ -0,0 +1,784 @@ +/*- + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)exec.c 8.4 (Berkeley) 6/8/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/exec.c 336320 2018-07-15 21:55:17Z jilles $ */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <paths.h> +#include <stdlib.h> + +/* + * 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 "input.h" +#include "output.h" +#include "syntax.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "show.h" +#include "jobs.h" +#include "alias.h" + + +#define CMDTABLESIZE 31 /* should be prime */ + + + +struct tblentry { + struct tblentry *next; /* next entry in hash chain */ + union param param; /* definition of builtin function */ + int special; /* flag for special builtin commands */ + signed char cmdtype; /* index identifying command */ + char cmdname[]; /* name of command */ +}; + + +static struct tblentry *cmdtable[CMDTABLESIZE]; +static int cmdtable_cd = 0; /* cmdtable contains cd-dependent entries */ +int exerrno = 0; /* Last exec error */ + + +static void tryexec(char *, char **, char **); +static void printentry(struct tblentry *, int); +static struct tblentry *cmdlookup(const char *, int); +static void delete_cmd_entry(void); +static void addcmdentry(const char *, struct cmdentry *); + + + +/* + * Exec a program. Never returns. If you change this routine, you may + * have to change the find_command routine as well. + * + * The argv array may be changed and element argv[-1] should be writable. + */ + +void +shellexec(char **argv, char **envp, const char *path, int idx) +{ + char *cmdname; + const char *opt; + int e; + + if (strchr(argv[0], '/') != NULL) { + tryexec(argv[0], argv, envp); + e = errno; + } else { + e = ENOENT; + while ((cmdname = padvance(&path, &opt, argv[0])) != NULL) { + if (--idx < 0 && opt == NULL) { + tryexec(cmdname, argv, envp); + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + if (e == ENOEXEC) + break; + } + stunalloc(cmdname); + } + } + + /* Map to POSIX errors */ + if (e == ENOENT || e == ENOTDIR) { + exerrno = 127; + exerror(EXEXEC, "%s: not found", argv[0]); + } else { + exerrno = 126; + exerror(EXEXEC, "%s: %s", argv[0], strerror(e)); + } +} + + +static void +tryexec(char *cmd, char **argv, char **envp) +{ + int e, in; + ssize_t n; + char buf[256]; + + execve(cmd, argv, envp); + e = errno; + if (e == ENOEXEC) { + INTOFF; + in = open(cmd, O_RDONLY | O_NONBLOCK); + if (in != -1) { + n = pread(in, buf, sizeof buf, 0); + close(in); + if (n > 0 && memchr(buf, '\0', n) != NULL) { + errno = ENOEXEC; + return; + } + } + *argv = cmd; + *--argv = (char *)(_PATH_BSHELL); + execve(_PATH_BSHELL, argv, envp); + } + errno = e; +} + +/* + * 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 popt is not NULL, options + * are processed: if an option (indicated by a percent sign) appears in + * the path entry then *popt will be set to point to it; else *popt will be + * set to NULL. If popt is NULL, percent signs are not special. + */ + +char * +padvance(const char **path, const char **popt, const char *name) +{ + const char *p, *start; + char *q; + size_t len, namelen; + + if (*path == NULL) + return NULL; + start = *path; + if (popt != NULL) + for (p = start; *p && *p != ':' && *p != '%'; p++) + ; /* nothing */ + else + for (p = start; *p && *p != ':'; p++) + ; /* nothing */ + namelen = strlen(name); + len = p - start + namelen + 2; /* "2" is for '/' and '\0' */ + STARTSTACKSTR(q); + CHECKSTRSPACE(len, q); + if (p != start) { + memcpy(q, start, p - start); + q += p - start; + *q++ = '/'; + } + memcpy(q, name, namelen + 1); + if (popt != NULL) { + if (*p == '%') { + *popt = ++p; + while (*p && *p != ':') p++; + } else + *popt = NULL; + } + if (*p == ':') + *path = p + 1; + else + *path = NULL; + return stalloc(len); +} + + + +/*** Command hashing code ***/ + + +int +hashcmd(int argc __unused, char **argv __unused) +{ + struct tblentry **pp; + struct tblentry *cmdp; + int c; + int verbose; + struct cmdentry entry; + char *name; + int errors; + + errors = 0; + verbose = 0; + while ((c = nextopt("rv")) != '\0') { + if (c == 'r') { + clearcmdentry(); + } else if (c == 'v') { + verbose++; + } + } + if (*argptr == NULL) { + for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL) + printentry(cmdp, verbose); + } + } + return 0; + } + while ((name = *argptr) != NULL) { + if ((cmdp = cmdlookup(name, 0)) != NULL + && cmdp->cmdtype == CMDNORMAL) + delete_cmd_entry(); + find_command(name, &entry, DO_ERR, pathval()); + if (entry.cmdtype == CMDUNKNOWN) + errors = 1; + else if (verbose) { + cmdp = cmdlookup(name, 0); + if (cmdp != NULL) + printentry(cmdp, verbose); + else { + outfmt(out2, "%s: not found\n", name); + errors = 1; + } + flushall(); + } + argptr++; + } + return errors; +} + + +static void +printentry(struct tblentry *cmdp, int verbose) +{ + int idx; + const char *path, *opt; + char *name; + + if (cmdp->cmdtype == CMDNORMAL) { + idx = cmdp->param.index; + path = pathval(); + do { + name = padvance(&path, &opt, cmdp->cmdname); + stunalloc(name); + } while (--idx >= 0); + out1str(name); + } else if (cmdp->cmdtype == CMDBUILTIN) { + out1fmt("builtin %s", cmdp->cmdname); + } else if (cmdp->cmdtype == CMDFUNCTION) { + out1fmt("function %s", cmdp->cmdname); + if (verbose) { + INTOFF; + name = commandtext(getfuncnode(cmdp->param.func)); + out1c(' '); + out1str(name); + ckfree(name); + INTON; + } +#ifdef DEBUG + } else { + error("internal error: cmdtype %d", cmdp->cmdtype); +#endif + } + out1c('\n'); +} + + + +/* + * Resolve a command name. If you change this routine, you may have to + * change the shellexec routine as well. + */ + +void +find_command(const char *name, struct cmdentry *entry, int act, + const char *path) +{ + struct tblentry *cmdp, loc_cmd; + int idx; + const char *opt; + char *fullname; + struct stat statb; + int e; + int i; + int spec; + int cd; + + /* If name contains a slash, don't use the hash table */ + if (strchr(name, '/') != NULL) { + entry->cmdtype = CMDNORMAL; + entry->u.index = 0; + entry->special = 0; + return; + } + + cd = 0; + + /* If name is in the table, we're done */ + if ((cmdp = cmdlookup(name, 0)) != NULL) { + if (cmdp->cmdtype == CMDFUNCTION && act & DO_NOFUNC) + cmdp = NULL; + else + goto success; + } + + /* Check for builtin next */ + if ((i = find_builtin(name, &spec)) >= 0) { + INTOFF; + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) + cmdp = &loc_cmd; + cmdp->cmdtype = CMDBUILTIN; + cmdp->param.index = i; + cmdp->special = spec; + INTON; + goto success; + } + + /* We have to search path. */ + + e = ENOENT; + idx = -1; + for (;(fullname = padvance(&path, &opt, name)) != NULL; + stunalloc(fullname)) { + idx++; + if (opt) { + if (strncmp(opt, "func", 4) == 0) { + /* handled below */ + } else { + continue; /* ignore unimplemented options */ + } + } + if (fullname[0] != '/') + cd = 1; + if (stat(fullname, &statb) < 0) { + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + continue; + } + e = EACCES; /* if we fail, this will be the error */ + if (!S_ISREG(statb.st_mode)) + continue; + if (opt) { /* this is a %func directory */ + readcmdfile(fullname); + if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION) + error("%s not defined in %s", name, fullname); + stunalloc(fullname); + goto success; + } +#ifdef notdef + 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)); + INTOFF; + stunalloc(fullname); + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) + cmdp = &loc_cmd; + cmdp->cmdtype = CMDNORMAL; + cmdp->param.index = idx; + cmdp->special = 0; + INTON; + goto success; + } + + if (act & DO_ERR) { + if (e == ENOENT || e == ENOTDIR) + outfmt(out2, "%s: not found\n", name); + else + outfmt(out2, "%s: %s\n", name, strerror(e)); + } + entry->cmdtype = CMDUNKNOWN; + entry->u.index = 0; + entry->special = 0; + return; + +success: + if (cd) + cmdtable_cd = 1; + entry->cmdtype = cmdp->cmdtype; + entry->u = cmdp->param; + entry->special = cmdp->special; +} + + + +/* + * Search the table of builtin commands. + */ + +int +find_builtin(const char *name, int *special) +{ + const unsigned char *bp; + size_t len; + + len = strlen(name); + for (bp = builtincmd ; *bp ; bp += 2 + bp[0]) { + if (bp[0] == len && memcmp(bp + 2, name, len) == 0) { + *special = (bp[1] & BUILTIN_SPECIAL) != 0; + return bp[1] & ~BUILTIN_SPECIAL; + } + } + return -1; +} + + + +/* + * Called when a cd is done. If any entry in cmdtable depends on the current + * directory, simply clear cmdtable completely. + */ + +void +hashcd(void) +{ + if (cmdtable_cd) + clearcmdentry(); +} + + + +/* + * 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 __unused) +{ + clearcmdentry(); +} + + +/* + * Clear out cached utility locations. + */ + +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) { + *pp = cmdp->next; + ckfree(cmdp); + } else { + pp = &cmdp->next; + } + } + } + cmdtable_cd = 0; + 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. + */ + +static struct tblentry **lastcmdentry; + + +static struct tblentry * +cmdlookup(const char *name, int add) +{ + unsigned int hashval; + const char *p; + struct tblentry *cmdp; + struct tblentry **pp; + size_t len; + + p = name; + hashval = (unsigned char)*p << 4; + while (*p) + hashval += *p++; + pp = &cmdtable[hashval % CMDTABLESIZE]; + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (equal(cmdp->cmdname, name)) + break; + pp = &cmdp->next; + } + if (add && cmdp == NULL) { + INTOFF; + len = strlen(name); + cmdp = *pp = ckmalloc(sizeof (struct tblentry) + len + 1); + cmdp->next = NULL; + cmdp->cmdtype = CMDUNKNOWN; + memcpy(cmdp->cmdname, name, len + 1); + INTON; + } + 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; + ckfree(cmdp); + INTON; +} + + + +/* + * Add a new command entry, replacing any existing command entry for + * the same name. + */ + +static void +addcmdentry(const char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp; + + INTOFF; + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) { + unreffunc(cmdp->param.func); + } + cmdp->cmdtype = entry->cmdtype; + cmdp->param = entry->u; + cmdp->special = entry->special; + INTON; +} + + +/* + * Define a shell function. + */ + +void +defun(const char *name, union node *func) +{ + struct cmdentry entry; + + INTOFF; + entry.cmdtype = CMDFUNCTION; + entry.u.func = copyfunc(func); + entry.special = 0; + addcmdentry(name, &entry); + INTON; +} + + +/* + * Delete a function if it exists. + * Called with interrupts off. + */ + +int +unsetfunc(const char *name) +{ + struct tblentry *cmdp; + + if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) { + unreffunc(cmdp->param.func); + delete_cmd_entry(); + return (0); + } + return (0); +} + + +/* + * Check if a function by a certain name exists. + */ +int +isfunc(const char *name) +{ + struct tblentry *cmdp; + cmdp = cmdlookup(name, 0); + return (cmdp != NULL && cmdp->cmdtype == CMDFUNCTION); +} + + +/* + * Shared code for the following builtin commands: + * type, command -v, command -V + */ + +int +typecmd_impl(int argc, char **argv, int cmd, const char *path) +{ + struct cmdentry entry; + struct tblentry *cmdp; + const char *const *pp; + struct alias *ap; + int i; + int error1 = 0; + + if (path != pathval()) + clearcmdentry(); + + for (i = 1; i < argc; i++) { + /* First look at the keywords */ + for (pp = parsekwd; *pp; pp++) + if (**pp == *argv[i] && equal(*pp, argv[i])) + break; + + if (*pp) { + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else + out1fmt("%s is a shell keyword\n", argv[i]); + continue; + } + + /* Then look at the aliases */ + if ((ap = lookupalias(argv[i], 1)) != NULL) { + if (cmd == TYPECMD_SMALLV) { + out1fmt("alias %s=", argv[i]); + out1qstr(ap->val); + outcslow('\n', out1); + } else + out1fmt("%s is an alias for %s\n", argv[i], + ap->val); + continue; + } + + /* Then check if it is a tracked alias */ + if ((cmdp = cmdlookup(argv[i], 0)) != NULL) { + entry.cmdtype = cmdp->cmdtype; + entry.u = cmdp->param; + entry.special = cmdp->special; + } + else { + /* Finally use brute force */ + find_command(argv[i], &entry, 0, path); + } + + switch (entry.cmdtype) { + case CMDNORMAL: { + if (strchr(argv[i], '/') == NULL) { + const char *path2 = path; + const char *opt2; + char *name; + int j = entry.u.index; + do { + name = padvance(&path2, &opt2, argv[i]); + stunalloc(name); + } while (--j >= 0); + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", name); + else + out1fmt("%s is%s %s\n", argv[i], + (cmdp && cmd == TYPECMD_TYPE) ? + " a tracked alias for" : "", + name); + } else { + if (faccessat(AT_FDCWD, argv[i], X_OK, AT_EACCESS) == 0) { + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else + out1fmt("%s is %s\n", argv[i], + argv[i]); + } else { + if (cmd != TYPECMD_SMALLV) + outfmt(out2, "%s: %s\n", + argv[i], strerror(errno)); + error1 |= 127; + } + } + break; + } + case CMDFUNCTION: + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else + out1fmt("%s is a shell function\n", argv[i]); + break; + + case CMDBUILTIN: + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else if (entry.special) + out1fmt("%s is a special shell builtin\n", + argv[i]); + else + out1fmt("%s is a shell builtin\n", argv[i]); + break; + + default: + if (cmd != TYPECMD_SMALLV) + outfmt(out2, "%s: not found\n", argv[i]); + error1 |= 127; + break; + } + } + + if (path != pathval()) + clearcmdentry(); + + return error1; +} + +/* + * Locate and print what a word is... + */ + +int +typecmd(int argc, char **argv) +{ + if (argc > 2 && strcmp(argv[1], "--") == 0) + argc--, argv++; + return typecmd_impl(argc, argv, TYPECMD_TYPE, bltinlookup("PATH", 1)); +} diff --git a/bin/1sh/exec.h b/bin/1sh/exec.h new file mode 100644 index 00000000..f93c14c2 --- /dev/null +++ b/bin/1sh/exec.h @@ -0,0 +1,76 @@ +/*- + * Copyright (c) 1991, 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. + * + * @(#)exec.h 8.3 (Berkeley) 6/8/95 + * $FreeBSD: releng/12.1/bin/sh/exec.h 336320 2018-07-15 21:55:17Z jilles $ + */ + +/* values of cmdtype */ +#define CMDUNKNOWN -1 /* no entry in table for command */ +#define CMDNORMAL 0 /* command is an executable program */ +#define CMDBUILTIN 1 /* command is a shell builtin */ +#define CMDFUNCTION 2 /* command is a shell function */ + +/* values for typecmd_impl's third parameter */ +enum { + TYPECMD_SMALLV, /* command -v */ + TYPECMD_BIGV, /* command -V */ + TYPECMD_TYPE /* type */ +}; + +union node; +struct cmdentry { + int cmdtype; + union param { + int index; + struct funcdef *func; + } u; + int special; +}; + + +/* action to find_command() */ +#define DO_ERR 0x01 /* prints errors */ +#define DO_NOFUNC 0x02 /* don't return shell functions, for command */ + +extern int exerrno; /* last exec error */ + +void shellexec(char **, char **, const char *, int) __attribute__((noreturn)); +char *padvance(const char **, const char **, const char *); +void find_command(const char *, struct cmdentry *, int, const char *); +int find_builtin(const char *, int *); +void hashcd(void); +void changepath(const char *); +void defun(const char *, union node *); +int unsetfunc(const char *); +int isfunc(const char *); +int typecmd_impl(int, char **, int, const char *); +void clearcmdentry(void); diff --git a/bin/1sh/expand.c b/bin/1sh/expand.c new file mode 100644 index 00000000..3bdd0733 --- /dev/null +++ b/bin/1sh/expand.c @@ -0,0 +1,1553 @@ +/*- + * 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. + * Copyright (c) 2010-2015 + * Jilles Tjoelker <jilles@stack.nl>. 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/expand.c 341767 2018-12-09 19:14:21Z jilles $ */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <dirent.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wchar.h> +#include <wctype.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 "input.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "arith.h" +#include "show.h" +#include "builtins.h" + +enum wordstate { WORD_IDLE, WORD_WS_DELIMITED, WORD_QUOTEMARK }; + +struct worddest { + struct arglist *list; + enum wordstate state; +}; + +static char *expdest; /* output of current string */ + +static const char *argstr(const char *, struct nodelist **restrict, int, + struct worddest *); +static const char *exptilde(const char *, int); +static const char *expari(const char *, struct nodelist **restrict, int, + struct worddest *); +static void expbackq(union node *, int, int, struct worddest *); +static const char *subevalvar_trim(const char *, struct nodelist **restrict, + int, int, int); +static const char *subevalvar_misc(const char *, struct nodelist **restrict, + const char *, int, int, int); +static const char *evalvar(const char *, struct nodelist **restrict, int, + struct worddest *); +static int varisset(const char *, int); +static void strtodest(const char *, int, int, int, struct worddest *); +static void reprocess(int, int, int, int, struct worddest *); +static void varvalue(const char *, int, int, int, struct worddest *); +static void expandmeta(char *, struct arglist *); +static void expmeta(char *, char *, struct arglist *); +static int expsortcmp(const void *, const void *); +static int patmatch(const char *, const char *); +static void cvtnum(int, char *); +static int collate_range_cmp(wchar_t, wchar_t); + +void +emptyarglist(struct arglist *list) +{ + + list->args = list->smallarg; + list->count = 0; + list->capacity = sizeof(list->smallarg) / sizeof(list->smallarg[0]); +} + +void +appendarglist(struct arglist *list, char *str) +{ + char **newargs; + int newcapacity; + + if (list->count >= list->capacity) { + newcapacity = list->capacity * 2; + if (newcapacity < 16) + newcapacity = 16; + if (newcapacity > INT_MAX / (int)sizeof(newargs[0])) + error("Too many entries in arglist"); + newargs = stalloc(newcapacity * sizeof(newargs[0])); + memcpy(newargs, list->args, list->count * sizeof(newargs[0])); + list->args = newargs; + list->capacity = newcapacity; + } + list->args[list->count++] = str; +} + +static int +collate_range_cmp(wchar_t c1, wchar_t c2) +{ + wchar_t s1[2], s2[2]; + + s1[0] = c1; + s1[1] = L'\0'; + s2[0] = c2; + s2[1] = L'\0'; + return (wcscoll(s1, s2)); +} + +static char * +stputs_quotes(const char *data, const char *syntax, char *p) +{ + while (*data) { + CHECKSTRSPACE(2, p); + if (syntax[(int)*data] == CCTL) + USTPUTC(CTLESC, p); + USTPUTC(*data++, p); + } + return (p); +} +#define STPUTS_QUOTES(data, syntax, p) p = stputs_quotes((data), syntax, p) + +static char * +nextword(char c, int flag, char *p, struct worddest *dst) +{ + int is_ws; + + is_ws = c == '\t' || c == '\n' || c == ' '; + if (p != stackblock() || (is_ws ? dst->state == WORD_QUOTEMARK : + dst->state != WORD_WS_DELIMITED) || c == '\0') { + STPUTC('\0', p); + if (flag & EXP_GLOB) + expandmeta(grabstackstr(p), dst->list); + else + appendarglist(dst->list, grabstackstr(p)); + dst->state = is_ws ? WORD_WS_DELIMITED : WORD_IDLE; + } else if (!is_ws && dst->state == WORD_WS_DELIMITED) + dst->state = WORD_IDLE; + /* Reserve space while the stack string is empty. */ + appendarglist(dst->list, NULL); + dst->list->count--; + STARTSTACKSTR(p); + return p; +} +#define NEXTWORD(c, flag, p, dstlist) p = nextword(c, flag, p, dstlist) + +static char * +stputs_split(const char *data, const char *syntax, int flag, char *p, + struct worddest *dst) +{ + const char *ifs; + char c; + + ifs = ifsset() ? ifsval() : " \t\n"; + while (*data) { + CHECKSTRSPACE(2, p); + c = *data++; + if (strchr(ifs, c) != NULL) { + NEXTWORD(c, flag, p, dst); + continue; + } + if (flag & EXP_GLOB && syntax[(int)c] == CCTL) + USTPUTC(CTLESC, p); + USTPUTC(c, p); + } + return (p); +} +#define STPUTS_SPLIT(data, syntax, flag, p, dst) p = stputs_split((data), syntax, flag, p, dst) + +/* + * Perform expansions on an argument, placing the resulting list of arguments + * in arglist. Parameter expansion, command substitution and arithmetic + * expansion are always performed; additional expansions can be requested + * via flag (EXP_*). + * The result is left in the stack string. + * When arglist is NULL, perform here document expansion. + * + * When doing something that may cause this to be re-entered, make sure + * the stack string is empty via grabstackstr() and do not assume expdest + * remains valid. + */ +void +expandarg(union node *arg, struct arglist *arglist, int flag) +{ + struct worddest exparg; + struct nodelist *argbackq; + + if (fflag) + flag &= ~EXP_GLOB; + argbackq = arg->narg.backquote; + exparg.list = arglist; + exparg.state = WORD_IDLE; + STARTSTACKSTR(expdest); + argstr(arg->narg.text, &argbackq, flag, &exparg); + if (arglist == NULL) { + STACKSTRNUL(expdest); + return; /* here document expanded */ + } + if ((flag & EXP_SPLIT) == 0 || expdest != stackblock() || + exparg.state == WORD_QUOTEMARK) { + STPUTC('\0', expdest); + if (flag & EXP_SPLIT) { + if (flag & EXP_GLOB) + expandmeta(grabstackstr(expdest), exparg.list); + else + appendarglist(exparg.list, grabstackstr(expdest)); + } + } + if ((flag & EXP_SPLIT) == 0) + appendarglist(arglist, grabstackstr(expdest)); +} + + + +/* + * Perform parameter expansion, command substitution and arithmetic + * expansion, and tilde expansion if requested via EXP_TILDE/EXP_VARTILDE. + * Processing ends at a CTLENDVAR or CTLENDARI character as well as '\0'. + * This is used to expand word in ${var+word} etc. + * If EXP_GLOB or EXP_CASE are set, keep and/or generate CTLESC + * characters to allow for further processing. + * + * If EXP_SPLIT is set, dst receives any complete words produced. + */ +static const char * +argstr(const char *p, struct nodelist **restrict argbackq, int flag, + struct worddest *dst) +{ + char c; + int quotes = flag & (EXP_GLOB | EXP_CASE); /* do CTLESC */ + int firsteq = 1; + int split_lit; + int lit_quoted; + + split_lit = flag & EXP_SPLIT_LIT; + lit_quoted = flag & EXP_LIT_QUOTED; + flag &= ~(EXP_SPLIT_LIT | EXP_LIT_QUOTED); + if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE))) + p = exptilde(p, flag); + for (;;) { + CHECKSTRSPACE(2, expdest); + switch (c = *p++) { + case '\0': + return (p - 1); + case CTLENDVAR: + case CTLENDARI: + return (p); + case CTLQUOTEMARK: + lit_quoted = 1; + /* "$@" syntax adherence hack */ + if (p[0] == CTLVAR && (p[1] & VSQUOTE) != 0 && + p[2] == '@' && p[3] == '=') + break; + if ((flag & EXP_SPLIT) != 0 && expdest == stackblock()) + dst->state = WORD_QUOTEMARK; + break; + case CTLQUOTEEND: + lit_quoted = 0; + break; + case CTLESC: + c = *p++; + if (split_lit && !lit_quoted && + strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { + NEXTWORD(c, flag, expdest, dst); + break; + } + if (quotes) + USTPUTC(CTLESC, expdest); + USTPUTC(c, expdest); + break; + case CTLVAR: + p = evalvar(p, argbackq, flag, dst); + break; + case CTLBACKQ: + case CTLBACKQ|CTLQUOTE: + expbackq((*argbackq)->n, c & CTLQUOTE, flag, dst); + *argbackq = (*argbackq)->next; + break; + case CTLARI: + p = expari(p, argbackq, flag, dst); + break; + case ':': + case '=': + /* + * sort of a hack - expand tildes in variable + * assignments (after the first '=' and after ':'s). + */ + if (split_lit && !lit_quoted && + strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { + NEXTWORD(c, flag, expdest, dst); + break; + } + USTPUTC(c, expdest); + if (flag & EXP_VARTILDE && *p == '~' && + (c != '=' || firsteq)) { + if (c == '=') + firsteq = 0; + p = exptilde(p, flag); + } + break; + default: + if (split_lit && !lit_quoted && + strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) { + NEXTWORD(c, flag, expdest, dst); + break; + } + USTPUTC(c, expdest); + } + } +} + +/* + * Perform tilde expansion, placing the result in the stack string and + * returning the next position in the input string to process. + */ +static const char * +exptilde(const char *p, int flag) +{ + char c; + const char *startp = p; + const char *user; + struct passwd *pw; + char *home; + int len; + + for (;;) { + c = *p; + switch(c) { + case CTLESC: /* This means CTL* are always considered quoted. */ + case CTLVAR: + case CTLBACKQ: + case CTLBACKQ | CTLQUOTE: + case CTLARI: + case CTLENDARI: + case CTLQUOTEMARK: + return (startp); + case ':': + if ((flag & EXP_VARTILDE) == 0) + break; + /* FALLTHROUGH */ + case '\0': + case '/': + case CTLENDVAR: + len = p - startp - 1; + STPUTBIN(startp + 1, len, expdest); + STACKSTRNUL(expdest); + user = expdest - len; + if (*user == '\0') { + home = lookupvar("HOME"); + } else { + pw = getpwnam(user); + home = pw != NULL ? pw->pw_dir : NULL; + } + STADJUST(-len, expdest); + if (home == NULL || *home == '\0') + return (startp); + strtodest(home, flag, VSNORMAL, 1, NULL); + return (p); + } + p++; + } +} + + +/* + * Expand arithmetic expression. + */ +static const char * +expari(const char *p, struct nodelist **restrict argbackq, int flag, + struct worddest *dst) +{ + char *q, *start; + arith_t result; + int begoff; + int quoted; + int adj; + + quoted = *p++ == '"'; + begoff = expdest - stackblock(); + p = argstr(p, argbackq, 0, NULL); + STPUTC('\0', expdest); + start = stackblock() + begoff; + + q = grabstackstr(expdest); + result = arith(start); + ungrabstackstr(q, expdest); + + start = stackblock() + begoff; + adj = start - expdest; + STADJUST(adj, expdest); + + CHECKSTRSPACE((int)(DIGITS(result) + 1), expdest); + fmtstr(expdest, DIGITS(result), ARITH_FORMAT_STR, result); + adj = strlen(expdest); + STADJUST(adj, expdest); + /* + * If this is quoted, a '-' must not indicate a range in [...]. + * If this is not quoted, splitting may occur. + */ + if (quoted ? + result < 0 && begoff > 1 && flag & (EXP_GLOB | EXP_CASE) : + flag & EXP_SPLIT) + reprocess(expdest - adj - stackblock(), flag, VSNORMAL, quoted, + dst); + return p; +} + + +/* + * Perform command substitution. + */ +static void +expbackq(union node *cmd, int quoted, int flag, struct worddest *dst) +{ + struct backcmd in; + int i; + char buf[128]; + char *p; + char *dest = expdest; + char lastc; + char const *syntax = quoted? DQSYNTAX : BASESYNTAX; + int quotes = flag & (EXP_GLOB | EXP_CASE); + size_t nnl; + const char *ifs; + int startloc; + + INTOFF; + p = grabstackstr(dest); + evalbackcmd(cmd, &in); + ungrabstackstr(p, dest); + + p = in.buf; + startloc = dest - stackblock(); + nnl = 0; + if (!quoted && flag & EXP_SPLIT) + ifs = ifsset() ? ifsval() : " \t\n"; + else + ifs = ""; + /* Remove trailing newlines */ + for (;;) { + if (--in.nleft < 0) { + if (in.fd < 0) + break; + while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR) + ; + TRACE(("expbackq: read returns %d\n", i)); + if (i <= 0) + break; + p = buf; + in.nleft = i - 1; + } + lastc = *p++; + if (lastc == '\0') + continue; + if (nnl > 0 && lastc != '\n') { + NEXTWORD('\n', flag, dest, dst); + nnl = 0; + } + if (strchr(ifs, lastc) != NULL) { + if (lastc == '\n') + nnl++; + else + NEXTWORD(lastc, flag, dest, dst); + } else { + CHECKSTRSPACE(2, dest); + if (quotes && syntax[(int)lastc] == CCTL) + USTPUTC(CTLESC, dest); + USTPUTC(lastc, dest); + } + } + while (dest > stackblock() + startloc && STTOPC(dest) == '\n') + STUNPUTC(dest); + + if (in.fd >= 0) + close(in.fd); + if (in.buf) + ckfree(in.buf); + if (in.jp) { + p = grabstackstr(dest); + exitstatus = waitforjob(in.jp, (int *)NULL); + ungrabstackstr(p, dest); + } + TRACE(("expbackq: done\n")); + expdest = dest; + INTON; +} + + + +static void +recordleft(const char *str, const char *loc, char *startp) +{ + int amount; + + amount = ((str - 1) - (loc - startp)) - expdest; + STADJUST(amount, expdest); + while (loc != str - 1) + *startp++ = *loc++; +} + +static const char * +subevalvar_trim(const char *p, struct nodelist **restrict argbackq, int strloc, + int subtype, int startloc) +{ + char *startp; + char *loc = NULL; + char *str; + int c = 0; + int amount; + + p = argstr(p, argbackq, EXP_CASE | EXP_TILDE, NULL); + STACKSTRNUL(expdest); + startp = stackblock() + startloc; + str = stackblock() + strloc; + + switch (subtype) { + case VSTRIMLEFT: + for (loc = startp; loc < str; loc++) { + c = *loc; + *loc = '\0'; + if (patmatch(str, startp)) { + *loc = c; + recordleft(str, loc, startp); + return p; + } + *loc = c; + } + break; + + case VSTRIMLEFTMAX: + for (loc = str - 1; loc >= startp;) { + c = *loc; + *loc = '\0'; + if (patmatch(str, startp)) { + *loc = c; + recordleft(str, loc, startp); + return p; + } + *loc = c; + loc--; + } + break; + + case VSTRIMRIGHT: + for (loc = str - 1; loc >= startp;) { + if (patmatch(str, loc)) { + amount = loc - expdest; + STADJUST(amount, expdest); + return p; + } + loc--; + } + break; + + case VSTRIMRIGHTMAX: + for (loc = startp; loc < str - 1; loc++) { + if (patmatch(str, loc)) { + amount = loc - expdest; + STADJUST(amount, expdest); + return p; + } + } + break; + + + default: + abort(); + } + amount = (expdest - stackblock() - strloc) + 1; + STADJUST(-amount, expdest); + return p; +} + + +static const char * +subevalvar_misc(const char *p, struct nodelist **restrict argbackq, + const char *var, int subtype, int startloc, int varflags) +{ + const char *end; + char *startp; + int amount; + + end = argstr(p, argbackq, EXP_TILDE, NULL); + STACKSTRNUL(expdest); + startp = stackblock() + startloc; + + switch (subtype) { + case VSASSIGN: + setvar(var, startp, 0); + amount = startp - expdest; + STADJUST(amount, expdest); + return end; + + case VSQUESTION: + if (*p != CTLENDVAR) { + outfmt(out2, "%s\n", startp); + error((char *)NULL); + } + error("%.*s: parameter %snot set", (int)(p - var - 1), + var, (varflags & VSNUL) ? "null or " : ""); + + default: + abort(); + } +} + + +/* + * Expand a variable, and return a pointer to the next character in the + * input string. + */ + +static const char * +evalvar(const char *p, struct nodelist **restrict argbackq, int flag, + struct worddest *dst) +{ + int subtype; + int varflags; + const char *var; + const char *val; + int patloc; + int c; + int set; + int special; + int startloc; + int varlen; + int varlenb; + char buf[21]; + + varflags = (unsigned char)*p++; + subtype = varflags & VSTYPE; + var = p; + special = 0; + if (! is_name(*p)) + special = 1; + p = strchr(p, '=') + 1; + if (varflags & VSLINENO) { + set = 1; + special = 1; + val = NULL; + } else if (special) { + set = varisset(var, varflags & VSNUL); + val = NULL; + } else { + val = bltinlookup(var, 1); + if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) { + val = NULL; + set = 0; + } else + set = 1; + } + varlen = 0; + startloc = expdest - stackblock(); + if (!set && uflag && *var != '@' && *var != '*') { + switch (subtype) { + case VSNORMAL: + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + case VSLENGTH: + error("%.*s: parameter not set", (int)(p - var - 1), + var); + } + } + if (set && subtype != VSPLUS) { + /* insert the value of the variable */ + if (special) { + if (varflags & VSLINENO) { + if (p - var > (ptrdiff_t)sizeof(buf)) + abort(); + memcpy(buf, var, p - var - 1); + buf[p - var - 1] = '\0'; + strtodest(buf, flag, subtype, + varflags & VSQUOTE, dst); + } else + varvalue(var, varflags & VSQUOTE, subtype, flag, + dst); + if (subtype == VSLENGTH) { + varlenb = expdest - stackblock() - startloc; + varlen = varlenb; + if (localeisutf8) { + val = stackblock() + startloc; + for (;val != expdest; val++) + if ((*val & 0xC0) == 0x80) + varlen--; + } + STADJUST(-varlenb, expdest); + } + } else { + if (subtype == VSLENGTH) { + for (;*val; val++) + if (!localeisutf8 || + (*val & 0xC0) != 0x80) + varlen++; + } + else + strtodest(val, flag, subtype, + varflags & VSQUOTE, dst); + } + } + + if (subtype == VSPLUS) + set = ! set; + + switch (subtype) { + case VSLENGTH: + cvtnum(varlen, buf); + strtodest(buf, flag, VSNORMAL, varflags & VSQUOTE, dst); + break; + + case VSNORMAL: + return p; + + case VSPLUS: + case VSMINUS: + if (!set) { + return argstr(p, argbackq, + flag | (flag & EXP_SPLIT ? EXP_SPLIT_LIT : 0) | + (varflags & VSQUOTE ? EXP_LIT_QUOTED : 0), dst); + } + break; + + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + if (!set) + break; + /* + * Terminate the string and start recording the pattern + * right after it + */ + STPUTC('\0', expdest); + patloc = expdest - stackblock(); + p = subevalvar_trim(p, argbackq, patloc, subtype, startloc); + reprocess(startloc, flag, VSNORMAL, varflags & VSQUOTE, dst); + if (flag & EXP_SPLIT && *var == '@' && varflags & VSQUOTE) + dst->state = WORD_QUOTEMARK; + return p; + + case VSASSIGN: + case VSQUESTION: + if (!set) { + p = subevalvar_misc(p, argbackq, var, subtype, + startloc, varflags); + /* assert(subtype == VSASSIGN); */ + val = lookupvar(var); + strtodest(val, flag, subtype, varflags & VSQUOTE, dst); + return p; + } + break; + + case VSERROR: + c = p - var - 1; + error("${%.*s%s}: Bad substitution", c, var, + (c > 0 && *p != CTLENDVAR) ? "..." : ""); + + default: + abort(); + } + + { /* skip to end of alternative */ + int nesting = 1; + for (;;) { + if ((c = *p++) == CTLESC) + p++; + else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) + *argbackq = (*argbackq)->next; + else if (c == CTLVAR) { + if ((*p++ & VSTYPE) != VSNORMAL) + nesting++; + } else if (c == CTLENDVAR) { + if (--nesting == 0) + break; + } + } + } + return p; +} + + + +/* + * Test whether a special or positional parameter is set. + */ + +static int +varisset(const char *name, int nulok) +{ + + if (*name == '!') + return backgndpidset(); + else if (*name == '@' || *name == '*') { + if (*shellparam.p == NULL) + return 0; + + if (nulok) { + char **av; + + for (av = shellparam.p; *av; av++) + if (**av != '\0') + return 1; + return 0; + } + } else if (is_digit(*name)) { + char *ap; + long num; + + errno = 0; + num = strtol(name, NULL, 10); + if (errno != 0 || num > shellparam.nparam) + return 0; + + if (num == 0) + ap = arg0; + else + ap = shellparam.p[num - 1]; + + if (nulok && (ap == NULL || *ap == '\0')) + return 0; + } + return 1; +} + +static void +strtodest(const char *p, int flag, int subtype, int quoted, + struct worddest *dst) +{ + if (subtype == VSLENGTH || subtype == VSTRIMLEFT || + subtype == VSTRIMLEFTMAX || subtype == VSTRIMRIGHT || + subtype == VSTRIMRIGHTMAX) + STPUTS(p, expdest); + else if (flag & EXP_SPLIT && !quoted && dst != NULL) + STPUTS_SPLIT(p, BASESYNTAX, flag, expdest, dst); + else if (flag & (EXP_GLOB | EXP_CASE)) + STPUTS_QUOTES(p, quoted ? DQSYNTAX : BASESYNTAX, expdest); + else + STPUTS(p, expdest); +} + +static void +reprocess(int startloc, int flag, int subtype, int quoted, + struct worddest *dst) +{ + static char *buf = NULL; + static size_t buflen = 0; + char *startp; + size_t len, zpos, zlen; + + startp = stackblock() + startloc; + len = expdest - startp; + if (len >= SIZE_MAX / 2 || len > PTRDIFF_MAX) + abort(); + INTOFF; + if (len >= buflen) { + ckfree(buf); + buf = NULL; + } + if (buflen < 128) + buflen = 128; + while (len >= buflen) + buflen <<= 1; + if (buf == NULL) + buf = ckmalloc(buflen); + INTON; + memcpy(buf, startp, len); + buf[len] = '\0'; + STADJUST(-(ptrdiff_t)len, expdest); + for (zpos = 0;;) { + zlen = strlen(buf + zpos); + strtodest(buf + zpos, flag, subtype, quoted, dst); + zpos += zlen + 1; + if (zpos == len + 1) + break; + if (flag & EXP_SPLIT && (quoted || (zlen > 0 && zpos < len))) + NEXTWORD('\0', flag, expdest, dst); + } +} + +/* + * Add the value of a special or positional parameter to the stack string. + */ + +static void +varvalue(const char *name, int quoted, int subtype, int flag, + struct worddest *dst) +{ + int num; + char *p; + int i; + int splitlater; + char sep[2]; + char **ap; + char buf[(NSHORTOPTS > 10 ? NSHORTOPTS : 10) + 1]; + + if (subtype == VSLENGTH) + flag &= ~EXP_FULL; + splitlater = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX || + subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX; + + switch (*name) { + case '$': + num = rootpid; + break; + case '?': + num = oexitstatus; + break; + case '#': + num = shellparam.nparam; + break; + case '!': + num = backgndpidval(); + break; + case '-': + p = buf; + for (i = 0 ; i < NSHORTOPTS ; i++) { + if (optval[i]) + *p++ = optletter[i]; + } + *p = '\0'; + strtodest(buf, flag, subtype, quoted, dst); + return; + case '@': + if (flag & EXP_SPLIT && quoted) { + for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { + strtodest(p, flag, subtype, quoted, dst); + if (*ap) { + if (splitlater) + STPUTC('\0', expdest); + else + NEXTWORD('\0', flag, expdest, + dst); + } + } + if (shellparam.nparam > 0) + dst->state = WORD_QUOTEMARK; + return; + } + /* FALLTHROUGH */ + case '*': + if (ifsset()) + sep[0] = ifsval()[0]; + else + sep[0] = ' '; + sep[1] = '\0'; + for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { + strtodest(p, flag, subtype, quoted, dst); + if (!*ap) + break; + if (sep[0]) + strtodest(sep, flag, subtype, quoted, dst); + else if (flag & EXP_SPLIT && !quoted && **ap != '\0') { + if (splitlater) + STPUTC('\0', expdest); + else + NEXTWORD('\0', flag, expdest, dst); + } + } + return; + default: + if (is_digit(*name)) { + num = atoi(name); + if (num == 0) + p = arg0; + else if (num > 0 && num <= shellparam.nparam) + p = shellparam.p[num - 1]; + else + return; + strtodest(p, flag, subtype, quoted, dst); + } + return; + } + cvtnum(num, buf); + strtodest(buf, flag, subtype, quoted, dst); +} + + + +static char expdir[PATH_MAX]; +#define expdir_end (expdir + sizeof(expdir)) + +/* + * Perform pathname generation and remove control characters. + * At this point, the only control characters should be CTLESC. + * The results are stored in the list dstlist. + */ +static void +expandmeta(char *pattern, struct arglist *dstlist) +{ + char *p; + int firstmatch; + char c; + + firstmatch = dstlist->count; + p = pattern; + for (; (c = *p) != '\0'; p++) { + /* fast check for meta chars */ + if (c == '*' || c == '?' || c == '[') { + INTOFF; + expmeta(expdir, pattern, dstlist); + INTON; + break; + } + } + if (dstlist->count == firstmatch) { + /* + * no matches + */ + rmescapes(pattern); + appendarglist(dstlist, pattern); + } else { + qsort(&dstlist->args[firstmatch], + dstlist->count - firstmatch, + sizeof(dstlist->args[0]), expsortcmp); + } +} + + +/* + * Do metacharacter (i.e. *, ?, [...]) expansion. + */ + +static void +expmeta(char *enddir, char *name, struct arglist *arglist) +{ + const char *p; + const char *q; + const char *start; + char *endname; + int metaflag; + struct stat statb; + DIR *dirp; + struct dirent *dp; + int atend; + int matchdot; + int esc; + int namlen; + + metaflag = 0; + start = name; + for (p = name; esc = 0, *p; p += esc + 1) { + if (*p == '*' || *p == '?') + metaflag = 1; + else if (*p == '[') { + q = p + 1; + if (*q == '!' || *q == '^') + q++; + for (;;) { + if (*q == CTLESC) + q++; + if (*q == '/' || *q == '\0') + break; + if (*++q == ']') { + metaflag = 1; + break; + } + } + } else if (*p == '\0') + break; + else { + if (*p == CTLESC) + esc++; + if (p[esc] == '/') { + if (metaflag) + break; + start = p + esc + 1; + } + } + } + if (metaflag == 0) { /* we've reached the end of the file name */ + if (enddir != expdir) + metaflag++; + for (p = name ; ; p++) { + if (*p == CTLESC) + p++; + *enddir++ = *p; + if (*p == '\0') + break; + if (enddir == expdir_end) + return; + } + if (metaflag == 0 || lstat(expdir, &statb) >= 0) + appendarglist(arglist, stsavestr(expdir)); + return; + } + endname = name + (p - name); + if (start != name) { + p = name; + while (p < start) { + if (*p == CTLESC) + p++; + *enddir++ = *p++; + if (enddir == expdir_end) + return; + } + } + if (enddir == expdir) { + p = "."; + } else if (enddir == expdir + 1 && *expdir == '/') { + p = "/"; + } else { + p = expdir; + enddir[-1] = '\0'; + } + if ((dirp = opendir(p)) == NULL) + return; + if (enddir != expdir) + enddir[-1] = '/'; + if (*endname == 0) { + atend = 1; + } else { + atend = 0; + *endname = '\0'; + endname += esc + 1; + } + matchdot = 0; + p = start; + if (*p == CTLESC) + p++; + if (*p == '.') + matchdot++; + while (! int_pending() && (dp = readdir(dirp)) != NULL) { + if (dp->d_name[0] == '.' && ! matchdot) + continue; + if (patmatch(start, dp->d_name)) { + namlen = dp->d_namlen; + if (enddir + namlen + 1 > expdir_end) + continue; + memcpy(enddir, dp->d_name, namlen + 1); + if (atend) + appendarglist(arglist, stsavestr(expdir)); + else { + if (dp->d_type != DT_UNKNOWN && + dp->d_type != DT_DIR && + dp->d_type != DT_LNK) + continue; + if (enddir + namlen + 2 > expdir_end) + continue; + enddir[namlen] = '/'; + enddir[namlen + 1] = '\0'; + expmeta(enddir + namlen + 1, endname, arglist); + } + } + } + closedir(dirp); + if (! atend) + endname[-esc - 1] = esc ? CTLESC : '/'; +} + + +static int +expsortcmp(const void *p1, const void *p2) +{ + const char *s1 = *(const char * const *)p1; + const char *s2 = *(const char * const *)p2; + + return (strcoll(s1, s2)); +} + + + +static wchar_t +get_wc(const char **p) +{ + wchar_t c; + int chrlen; + + chrlen = mbtowc(&c, *p, 4); + if (chrlen == 0) + return 0; + else if (chrlen == -1) + c = 0; + else + *p += chrlen; + return c; +} + + +/* + * See if a character matches a character class, starting at the first colon + * of "[:class:]". + * If a valid character class is recognized, a pointer to the next character + * after the final closing bracket is stored into *end, otherwise a null + * pointer is stored into *end. + */ +static int +match_charclass(const char *p, wchar_t chr, const char **end) +{ + char name[20]; + const char *nameend; + wctype_t cclass; + + *end = NULL; + p++; + nameend = strstr(p, ":]"); + if (nameend == NULL || (size_t)(nameend - p) >= sizeof(name) || + nameend == p) + return 0; + memcpy(name, p, nameend - p); + name[nameend - p] = '\0'; + *end = nameend + 2; + cclass = wctype(name); + /* An unknown class matches nothing but is valid nevertheless. */ + if (cclass == 0) + return 0; + return iswctype(chr, cclass); +} + + +/* + * Returns true if the pattern matches the string. + */ + +static int +patmatch(const char *pattern, const char *string) +{ + const char *p, *q, *end; + const char *bt_p, *bt_q; + char c; + wchar_t wc, wc2; + + p = pattern; + q = string; + bt_p = NULL; + bt_q = NULL; + for (;;) { + switch (c = *p++) { + case '\0': + if (*q != '\0') + goto backtrack; + return 1; + case CTLESC: + if (*q++ != *p++) + goto backtrack; + break; + case '?': + if (*q == '\0') + return 0; + if (localeisutf8) { + wc = get_wc(&q); + /* + * A '?' does not match invalid UTF-8 but a + * '*' does, so backtrack. + */ + if (wc == 0) + goto backtrack; + } else + q++; + break; + case '*': + c = *p; + while (c == '*') + c = *++p; + /* + * If the pattern ends here, we know the string + * matches without needing to look at the rest of it. + */ + if (c == '\0') + return 1; + /* + * First try the shortest match for the '*' that + * could work. We can forget any earlier '*' since + * there is no way having it match more characters + * can help us, given that we are already here. + */ + bt_p = p; + bt_q = q; + break; + case '[': { + const char *savep, *saveq; + int invert, found; + wchar_t chr; + + savep = p, saveq = q; + invert = 0; + if (*p == '!' || *p == '^') { + invert++; + p++; + } + found = 0; + if (*q == '\0') + return 0; + if (localeisutf8) { + chr = get_wc(&q); + if (chr == 0) + goto backtrack; + } else + chr = (unsigned char)*q++; + c = *p++; + do { + if (c == '\0') { + p = savep, q = saveq; + c = '['; + goto dft; + } + if (c == '[' && *p == ':') { + found |= match_charclass(p, chr, &end); + if (end != NULL) { + p = end; + continue; + } + } + if (c == CTLESC) + c = *p++; + if (localeisutf8 && c & 0x80) { + p--; + wc = get_wc(&p); + if (wc == 0) /* bad utf-8 */ + return 0; + } else + wc = (unsigned char)c; + if (*p == '-' && p[1] != ']') { + p++; + if (*p == CTLESC) + p++; + if (localeisutf8) { + wc2 = get_wc(&p); + if (wc2 == 0) /* bad utf-8 */ + return 0; + } else + wc2 = (unsigned char)*p++; + if ( collate_range_cmp(chr, wc) >= 0 + && collate_range_cmp(chr, wc2) <= 0 + ) + found = 1; + } else { + if (chr == wc) + found = 1; + } + } while ((c = *p++) != ']'); + if (found == invert) + goto backtrack; + break; + } +dft: default: + if (*q == '\0') + return 0; + if (*q++ == c) + break; +backtrack: + /* + * If we have a mismatch (other than hitting the end + * of the string), go back to the last '*' seen and + * have it match one additional character. + */ + if (bt_p == NULL) + return 0; + if (*bt_q == '\0') + return 0; + bt_q++; + p = bt_p; + q = bt_q; + break; + } + } +} + + + +/* + * Remove any CTLESC and CTLQUOTEMARK characters from a string. + */ + +void +rmescapes(char *str) +{ + char *p, *q; + + p = str; + while (*p != CTLESC && *p != CTLQUOTEMARK && *p != CTLQUOTEEND) { + if (*p++ == '\0') + return; + } + q = p; + while (*p) { + if (*p == CTLQUOTEMARK || *p == CTLQUOTEEND) { + p++; + continue; + } + if (*p == CTLESC) + p++; + *q++ = *p++; + } + *q = '\0'; +} + + + +/* + * See if a pattern matches in a case statement. + */ + +int +casematch(union node *pattern, const char *val) +{ + struct stackmark smark; + struct nodelist *argbackq; + int result; + char *p; + + setstackmark(&smark); + argbackq = pattern->narg.backquote; + STARTSTACKSTR(expdest); + argstr(pattern->narg.text, &argbackq, EXP_TILDE | EXP_CASE, NULL); + STPUTC('\0', expdest); + p = grabstackstr(expdest); + result = patmatch(p, val); + popstackmark(&smark); + return result; +} + +/* + * Our own itoa(). + */ + +static void +cvtnum(int num, char *buf) +{ + char temp[32]; + int neg = num < 0; + char *p = temp + 31; + + temp[31] = '\0'; + + do { + *--p = num % 10 + '0'; + } while ((num /= 10) != 0); + + if (neg) + *--p = '-'; + + memcpy(buf, p, temp + 32 - p); +} + +/* + * Do most of the work for wordexp(3). + */ + +int +wordexpcmd(int argc, char **argv) +{ + size_t len; + int i; + + out1fmt("%08x", argc - 1); + for (i = 1, len = 0; i < argc; i++) + len += strlen(argv[i]); + out1fmt("%08x", (int)len); + for (i = 1; i < argc; i++) + outbin(argv[i], strlen(argv[i]) + 1, out1); + return (0); +} + +/* + * Do most of the work for wordexp(3), new version. + */ + +int +freebsd_wordexpcmd(int argc __unused, char **argv __unused) +{ + struct arglist arglist; + union node *args, *n; + size_t len; + int ch; + int protected = 0; + int fd = -1; + int i; + + while ((ch = nextopt("f:p")) != '\0') { + switch (ch) { + case 'f': + fd = number(shoptarg); + break; + case 'p': + protected = 1; + break; + } + } + if (*argptr != NULL) + error("wrong number of arguments"); + if (fd < 0) + error("missing fd"); + INTOFF; + setinputfd(fd, 1); + INTON; + args = parsewordexp(); + popfile(); /* will also close fd */ + if (protected) + for (n = args; n != NULL; n = n->narg.next) { + if (n->narg.backquote != NULL) { + outcslow('C', out1); + error("command substitution disabled"); + } + } + outcslow(' ', out1); + emptyarglist(&arglist); + for (n = args; n != NULL; n = n->narg.next) + expandarg(n, &arglist, EXP_FULL | EXP_TILDE); + for (i = 0, len = 0; i < arglist.count; i++) + len += strlen(arglist.args[i]); + out1fmt("%016x %016zx", arglist.count, len); + for (i = 0; i < arglist.count; i++) + outbin(arglist.args[i], strlen(arglist.args[i]) + 1, out1); + return (0); +} diff --git a/bin/1sh/expand.h b/bin/1sh/expand.h new file mode 100644 index 00000000..2bd9bbd9 --- /dev/null +++ b/bin/1sh/expand.h @@ -0,0 +1,62 @@ +/*- + * Copyright (c) 1991, 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. + * + * @(#)expand.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/expand.h 314436 2017-02-28 23:42:47Z imp $ + */ + +struct arglist { + char **args; + int count; + int capacity; + char *smallarg[1]; +}; + +/* + * expandarg() flags + */ +#define EXP_SPLIT 0x1 /* perform word splitting */ +#define EXP_TILDE 0x2 /* do normal tilde expansion */ +#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ +#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ +#define EXP_SPLIT_LIT 0x20 /* IFS split literal text ${v+-a b c} */ +#define EXP_LIT_QUOTED 0x40 /* for EXP_SPLIT_LIT, start off quoted */ +#define EXP_GLOB 0x80 /* perform file globbing */ + +#define EXP_FULL (EXP_SPLIT | EXP_GLOB) + + +void emptyarglist(struct arglist *); +void appendarglist(struct arglist *, char *); +union node; +void expandarg(union node *, struct arglist *, int); +void rmescapes(char *); +int casematch(union node *, const char *); diff --git a/bin/1sh/histedit.c b/bin/1sh/histedit.c new file mode 100644 index 00000000..e0bda06e --- /dev/null +++ b/bin/1sh/histedit.c @@ -0,0 +1,558 @@ +/*- + * 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/histedit.c 345613 2019-03-27 21:53:44Z jilles $ */ + +#include <sys/param.h> +#include <limits.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.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" +#ifndef NO_HISTORY +#include "myhistedit.h" +#include "error.h" +#include "eval.h" +#include "memalloc.h" +#include "builtins.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, *el_err; + +static void history_load(const char *hf); +static void history_save(const char *hf); + +static char *fc_replace(const char *, char *, char *); +static int not_fcnumber(const char *); +static int str_to_event(const char *, int); + +/* + * Set history and editing status. Called whenever the status may + * have changed (figures out what to do). + */ +void +histedit(void) +{ + +#define editing (Eflag || Vflag) + + if (iflag) { + if (!hist) { + /* + * turn history on + */ + INTOFF; + hist = history_init(); + INTON; + + if (hist != NULL) { + sethistsize(histsizeval()); + sethistfile(histfileval()); + } else + out2fmt_flush("sh: can't initialize history\n"); + } + if (editing && !el && isatty(0)) { /* && isatty(2) ??? */ + /* + * turn editing on + */ + char *term; + + INTOFF; + if (el_in == NULL) + el_in = fdopen(0, "r"); + if (el_err == NULL) + el_err = fdopen(1, "w"); + if (el_out == NULL) + el_out = fdopen(2, "w"); + if (el_in == NULL || el_err == NULL || el_out == NULL) + goto bad; + term = lookupvar("TERM"); + if (term) + setenv("TERM", term, 1); + else + unsetenv("TERM"); + 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); + el_set(el, EL_ADDFN, "sh-complete", + "Filename completion", + _el_fn_complete); + } else { +bad: + out2fmt_flush("sh: can't initialize editing\n"); + } + INTON; + } else if (!editing && el) { + INTOFF; + el_end(el); + el = NULL; + INTON; + } + if (el) { + el_source(el, NULL); + if (Vflag) + el_set(el, EL_EDITOR, "vi"); + else if (Eflag) + el_set(el, EL_EDITOR, "emacs"); + 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) { + if (*histfileval() != '\0') + history_save(histfileval()); + history_end(hist); + hist = NULL; + } + INTON; + } +} + + +void +sethistfile(const char *hf) +{ + if (hist != NULL && hf != NULL && *hf != '\0') + history_load(hf); +} + + +static void +history_load(const char *hf) +{ + HistEvent he; + + if (history(hist, &he, H_LOAD, hf) == -1) + warning("%s: %s", he.str, hf); +} + + +static void +history_save(const char *hf) +{ + HistEvent he; + + if (history(hist, &he, H_SAVE, hf) == -1) + warning("%s: %s", he.str, hf); +} + + +void +sethistsize(const char *hs) +{ + int histsize; + HistEvent he; + + if (hist != NULL) { + if (hs == NULL || !is_number(hs)) + histsize = 100; + else + histsize = atoi(hs); + history(hist, &he, H_SETSIZE, histsize); + history(hist, &he, H_SETUNIQUE, 1); + } +} + +void +setterm(const char *term) +{ + if (rootshell && el != NULL && term != NULL) + el_set(el, EL_TERMINAL, term); +} + +int +histcmd(int argc, char **argv __unused) +{ + 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 = NULL; + static int active = 0; + struct jmploc jmploc; + struct jmploc *savehandler; + char editfilestr[PATH_MAX]; + char *volatile editfile; + FILE *efp = NULL; + int oldhistnum; + + if (hist == NULL) + error("history not active"); + + if (argc == 1) + error("missing history argument"); + + while (not_fcnumber(*argptr) && (ch = nextopt("e:lnrs")) != '\0') + switch ((char)ch) { + case 'e': + editor = shoptarg; + break; + case 'l': + lflg = 1; + break; + case 'n': + nflg = 1; + break; + case 'r': + rflg = 1; + break; + case 's': + sflg = 1; + break; + } + + savehandler = handler; + /* + * If executing... + */ + if (lflg == 0 || editor || sflg) { + lflg = 0; /* ignore */ + editfile = NULL; + /* + * 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); + } + handler = &jmploc; + if (++active > MAXHISTLOOPS) { + active = 0; + displayhist = 0; + error("called recursively too many times"); + } + /* + * Set editor. + */ + if (sflg == 0) { + if (editor == NULL && + (editor = bltinlookup("FCEDIT", 1)) == NULL && + (editor = bltinlookup("EDITOR", 1)) == NULL) + editor = DEFEDITOR; + if (editor[0] == '-' && editor[1] == '\0') { + sflg = 1; /* no edit */ + editor = NULL; + } + } + } + + /* + * If executing, parse [old=new] now + */ + if (lflg == 0 && *argptr != NULL && + ((repl = strchr(*argptr, '=')) != NULL)) { + pat = *argptr; + *repl++ = '\0'; + argptr++; + } + /* + * determine [first] and [last] + */ + if (*argptr == NULL) { + firststr = lflg ? "-16" : "-1"; + laststr = "-1"; + } else if (argptr[1] == NULL) { + firststr = argptr[0]; + laststr = lflg ? "-1" : argptr[0]; + } else if (argptr[2] == NULL) { + firststr = argptr[0]; + laststr = argptr[1]; + } else + error("too many arguments"); + /* + * 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(editfilestr, "%s/_shXXXXXX", _PATH_TMP); + if ((fd = mkstemp(editfilestr)) < 0) + error("can't create temporary file %s", editfile); + editfile = editfilestr; + if ((efp = fdopen(fd, "w")) == NULL) { + close(fd); + error("Out of space"); + } + } + + /* + * 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); + flushout(out2); + } + evalstring(s, 0); + if (displayhist && hist) { + /* + * XXX what about recursive and + * relative histnums. + */ + oldhistnum = he.num; + history(hist, &he, H_ENTER, s); + /* + * XXX H_ENTER moves the internal + * cursor, set it back to the current + * entry. + */ + history(hist, &he, + H_NEXT_EVENT, oldhistnum); + } + } 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); + INTON; + editcmd = stalloc(strlen(editor) + strlen(editfile) + 2); + sprintf(editcmd, "%s %s", editor, editfile); + evalstring(editcmd, 0); /* XXX - should use no JC command */ + readcmdfile(editfile); /* XXX - should read back - quick tst */ + unlink(editfile); + } + + if (lflg == 0 && active > 0) + --active; + if (displayhist) + displayhist = 0; + handler = savehandler; + return 0; +} + +static 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) { + STPUTS(r, dest); + s += plen; + *p = '\0'; /* so no more matches */ + } else + STPUTC(*s++, dest); + } + STPUTC('\0', dest); + dest = grabstackstr(dest); + + return (dest); +} + +static int +not_fcnumber(const char *s) +{ + if (s == NULL) + return (0); + if (*s == '-') + s++; + return (!is_number(s)); +} + +static 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) + error("history number %s not found (internal error)", + str); + } else { + /* + * pattern + */ + retval = history(hist, &he, H_PREV_STR, str); + if (retval == -1) + error("history pattern not found: %s", str); + } + return (he.num); +} + +int +bindcmd(int argc, char **argv) +{ + int ret; + FILE *old; + FILE *out; + + if (el == NULL) + error("line editing is disabled"); + + INTOFF; + + out = out1fp(); + if (out == NULL) + error("Out of space"); + + el_get(el, EL_GETFP, 1, &old); + el_set(el, EL_SETFP, 1, out); + + ret = el_parse(el, argc, (const char **)(argv)); + + el_set(el, EL_SETFP, 1, old); + + fclose(out); + + INTON; + + return ret; +} + +#else +#include "error.h" + +int +histcmd(int argc, char **argv) +{ + + error("not compiled with history support"); + /*NOTREACHED*/ + return (0); +} + +int +bindcmd(int argc, char **argv) +{ + + error("not compiled with line editing support"); + return (0); +} +#endif diff --git a/bin/1sh/input.c b/bin/1sh/input.c new file mode 100644 index 00000000..37f689a5 --- /dev/null +++ b/bin/1sh/input.c @@ -0,0 +1,520 @@ +/*- + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)input.c 8.3 (Berkeley) 6/9/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/input.c 314436 2017-02-28 23:42:47Z imp $ */ + +#include <stdio.h> /* defines BUFSIZ */ +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +/* + * This file implements the input routines used by the parser. + */ + +#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 "myhistedit.h" +#include "trap.h" + +#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */ + +struct strpush { + struct strpush *prev; /* preceding string on stack */ + const char *prevstring; + int prevnleft; + int prevlleft; + struct alias *ap; /* if push was associated with an alias */ +}; + +/* + * 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 lines left in this buffer */ + const 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 */ +}; + + +int plinno = 1; /* input line number */ +int parsenleft; /* copy of parsefile->nleft */ +static int parselleft; /* copy of parsefile->lleft */ +const char *parsenextc; /* copy of parsefile->nextc */ +static char basebuf[BUFSIZ + 1];/* buffer for top level input file */ +static struct parsefile basepf = { /* top level input file */ + .nextc = basebuf, + .buf = basebuf +}; +static struct parsefile *parsefile = &basepf; /* current input file */ +int whichprompt; /* 1 == PS1, 2 == PS2 */ + +EditLine *el; /* cookie for editline package */ + +static void pushfile(void); +static int preadfd(void); +static void popstring(void); + +void +resetinput(void) +{ + popallfiles(); + parselleft = parsenleft = 0; /* clear input buffer */ +} + + + +/* + * Read a character from the script, returning PEOF on end of file. + * Nul characters in the input are silently discarded. + */ + +int +pgetc(void) +{ + return pgetc_macro(); +} + + +static int +preadfd(void) +{ + int nr; + parsenextc = parsefile->buf; + +retry: +#ifndef NO_HISTORY + if (parsefile->fd == 0 && el) { + static const char *rl_cp; + static int el_len; + + if (rl_cp == NULL) { + el_resize(el); + rl_cp = el_gets(el, &el_len); + } + if (rl_cp == NULL) + nr = el_len == 0 ? 0 : -1; + else { + nr = el_len; + if (nr > BUFSIZ) + nr = BUFSIZ; + memcpy(parsefile->buf, rl_cp, nr); + if (nr != el_len) { + el_len -= nr; + rl_cp += nr; + } else + rl_cp = NULL; + } + } else +#endif + nr = read(parsefile->fd, parsefile->buf, BUFSIZ); + + if (nr <= 0) { + 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) { + out2fmt_flush("sh: turning off NDELAY mode\n"); + goto retry; + } + } + } + } + nr = -1; + } + 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 there is more in this buffer, use it else call read to fill it. + * 4) Process input up to the next newline, deleting nul characters. + */ + +int +preadbuffer(void) +{ + char *p, *q, *r, *end; + char savec; + + while (parsefile->strpush) { + /* + * Add a space to the end of an alias to ensure that the + * alias remains in use while parsing its last word. + * This avoids alias recursions. + */ + if (parsenleft == -1 && parsefile->strpush->ap != NULL) + return ' '; + popstring(); + if (--parsenleft >= 0) + return (*parsenextc++); + } + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return PEOF; + +again: + if (parselleft <= 0) { + if ((parselleft = preadfd()) == -1) { + parselleft = parsenleft = EOF_NLEFT; + return PEOF; + } + } + + p = parsefile->buf + (parsenextc - parsefile->buf); + end = p + parselleft; + *end = '\0'; + q = strchr(p, '\n'); + if (!q) q = strchr(p, '\0'); + if (q != end && *q == '\0') { + /* delete nul characters */ + for (r = q; q != end; q++) { + if (*q != '\0') + *r++ = *q; + } + parselleft -= end - r; + if (parselleft == 0) + goto again; + end = p + parselleft; + *end = '\0'; + q = strchr(p, '\n'); + if (!q) q = strchr(p, '\0'); + } + if (q == end) { + parsenleft = parselleft; + parselleft = 0; + } else /* *q == '\n' */ { + q++; + parsenleft = q - parsenextc; + parselleft -= parsenleft; + } + parsenleft--; + + savec = *q; + *q = '\0'; + +#ifndef NO_HISTORY + if (parsefile->fd == 0 && hist && + parsenextc[strspn(parsenextc, " \t\n")] != '\0') { + HistEvent he; + INTOFF; + history(hist, &he, whichprompt == 1 ? H_ENTER : H_ADD, + parsenextc); + INTON; + } +#endif + + if (vflag) { + out2str(parsenextc); + flushout(out2); + } + + *q = savec; + + return *parsenextc++; +} + +/* + * Returns if we are certain we are at EOF. Does not cause any more input + * to be read from the outside world. + */ + +int +preadateof(void) +{ + if (parsenleft > 0) + return 0; + if (parsefile->strpush) + return 0; + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return 1; + return 0; +} + +/* + * Undo the last call to pgetc. Only one character may be pushed back. + * PEOF may be pushed back. + */ + +void +pungetc(void) +{ + parsenleft++; + parsenextc--; +} + +/* + * Push a string back onto the input at this current parsefile level. + * We handle aliases this way. + */ +void +pushstring(const char *s, int len, struct alias *ap) +{ + struct strpush *sp; + + INTOFF; +/*out2fmt_flush("*** 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 = parsenextc; + sp->prevnleft = parsenleft; + sp->prevlleft = parselleft; + sp->ap = ap; + if (ap) + ap->flag |= ALIASINUSE; + parsenextc = s; + parsenleft = len; + INTON; +} + +static void +popstring(void) +{ + struct strpush *sp = parsefile->strpush; + + INTOFF; + if (sp->ap) { + if (parsenextc != sp->ap->val && + (parsenextc[-1] == ' ' || parsenextc[-1] == '\t')) + forcealias(); + sp->ap->flag &= ~ALIASINUSE; + } + parsenextc = sp->prevstring; + parsenleft = sp->prevnleft; + parselleft = sp->prevlleft; +/*out2fmt_flush("*** 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. + */ + +void +setinputfile(const char *fname, int push) +{ + int fd; + int fd2; + + INTOFF; + if ((fd = open(fname, O_RDONLY | O_CLOEXEC)) < 0) + error("cannot open %s: %s", fname, strerror(errno)); + if (fd < 10) { + fd2 = fcntl(fd, F_DUPFD_CLOEXEC, 10); + close(fd); + if (fd2 < 0) + error("Out of file descriptors"); + fd = fd2; + } + setinputfd(fd, push); + INTON; +} + + +/* + * Like setinputfile, but takes an open file descriptor (which should have + * its FD_CLOEXEC flag already set). Call this with interrupts off. + */ + +void +setinputfd(int fd, int push) +{ + if (push) { + pushfile(); + parsefile->buf = ckmalloc(BUFSIZ + 1); + } + if (parsefile->fd > 0) + close(parsefile->fd); + parsefile->fd = fd; + if (parsefile->buf == NULL) + parsefile->buf = ckmalloc(BUFSIZ + 1); + parselleft = parsenleft = 0; + plinno = 1; +} + + +/* + * Like setinputfile, but takes input from a string. + */ + +void +setinputstring(const char *string, int push) +{ + INTOFF; + if (push) + pushfile(); + parsenextc = string; + parselleft = parsenleft = 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; + + parsefile->nleft = parsenleft; + parsefile->lleft = parselleft; + parsefile->nextc = parsenextc; + parsefile->linno = plinno; + pf = (struct parsefile *)ckmalloc(sizeof (struct parsefile)); + pf->prev = parsefile; + pf->fd = -1; + pf->strpush = NULL; + pf->basestrpush.prev = NULL; + 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); + parsenleft = parsefile->nleft; + parselleft = parsefile->lleft; + parsenextc = parsefile->nextc; + plinno = parsefile->linno; + INTON; +} + + +/* + * Return current file (to go back to it later using popfilesupto()). + */ + +struct parsefile * +getcurrentfile(void) +{ + return parsefile; +} + + +/* + * Pop files until the given file is on top again. Useful for regular + * builtins that read shell commands from files or strings. + * If the given file is not an active file, an error is raised. + */ + +void +popfilesupto(struct parsefile *file) +{ + while (parsefile != file && parsefile != &basepf) + popfile(); + if (parsefile != file) + error("popfilesupto() misused"); +} + +/* + * Return to top level. + */ + +void +popallfiles(void) +{ + while (parsefile != &basepf) + popfile(); +} + + + +/* + * Close the file(s) that the shell is reading commands from. Called + * after a fork is done. + */ + +void +closescript(void) +{ + popallfiles(); + if (parsefile->fd > 0) { + close(parsefile->fd); + parsefile->fd = 0; + } +} diff --git a/bin/1sh/input.h b/bin/1sh/input.h new file mode 100644 index 00000000..1d0047cc --- /dev/null +++ b/bin/1sh/input.h @@ -0,0 +1,65 @@ +/*- + * Copyright (c) 1991, 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. + * + * @(#)input.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/input.h 314436 2017-02-28 23:42:47Z imp $ + */ + +/* PEOF (the end of file marker) is defined in syntax.h */ + +/* + * 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. + */ +extern int plinno; +extern int parsenleft; /* number of characters left in input buffer */ +extern const char *parsenextc; /* next character in input buffer */ + +struct alias; +struct parsefile; + +void resetinput(void); +int pgetc(void); +int preadbuffer(void); +int preadateof(void); +void pungetc(void); +void pushstring(const char *, int, struct alias *); +void setinputfile(const char *, int); +void setinputfd(int, int); +void setinputstring(const char *, int); +void popfile(void); +struct parsefile *getcurrentfile(void); +void popfilesupto(struct parsefile *); +void popallfiles(void); +void closescript(void); + +#define pgetc_macro() (--parsenleft >= 0? *parsenextc++ : preadbuffer()) diff --git a/bin/1sh/jobs.c b/bin/1sh/jobs.c new file mode 100644 index 00000000..492251e0 --- /dev/null +++ b/bin/1sh/jobs.c @@ -0,0 +1,1569 @@ +/*- + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/jobs.c 345487 2019-03-24 22:10:26Z jilles $ */ + +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <signal.h> +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> + +#include "shell.h" +#if JOBS +#include <termios.h> +#undef CEOF /* syntax.h redefines this */ +#endif +#include "redir.h" +#include "exec.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 "var.h" +#include "builtins.h" + + +/* + * 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; /* status flags (defined above) */ + char *cmd; /* text of command being run */ +}; + + +/* states */ +#define JOBSTOPPED 1 /* all procs are stopped */ +#define JOBDONE 2 /* all procs are completed */ + + +struct job { + struct procstat ps0; /* status of process */ + struct procstat *ps; /* status or processes when more than one */ + short nprocs; /* number of processes */ + pid_t pgrp; /* process group of this job */ + char state; /* true if job is finished */ + char used; /* true if this entry is in used */ + char changed; /* true if status has changed */ + char foreground; /* true if running in the foreground */ + char remembered; /* true if $! referenced */ + char pipefail; /* pass any non-zero status */ +#if JOBS + char jobctl; /* job running under job control */ + struct job *next; /* job used after this one */ +#endif +}; + + +static struct job *jobtab; /* array of jobs */ +static int njobs; /* size of array */ +static pid_t backgndpid = -1; /* pid of last background process */ +static struct job *bgjob = NULL; /* last background process */ +#if JOBS +static struct job *jobmru; /* most recently used job list */ +static pid_t initialpgrp; /* pgrp of shell on invocation */ +#endif +static int ttyfd = -1; + +/* mode flags for dowait */ +#define DOWAIT_BLOCK 0x1 /* wait until a child exits */ +#define DOWAIT_SIG 0x2 /* if DOWAIT_BLOCK, abort on signal */ +#define DOWAIT_SIG_TRAP 0x4 /* if DOWAIT_SIG, abort on trapped signal only */ + +#if JOBS +static void restartjob(struct job *); +#endif +static void freejob(struct job *); +static int waitcmdloop(struct job *); +static struct job *getjob_nonotfound(const char *); +static struct job *getjob(const char *); +pid_t killjob(const char *, int); +static pid_t dowait(int, struct job *); +static void checkzombies(void); +static void cmdtxt(union node *); +static void cmdputs(const char *); +#if JOBS +static void setcurjob(struct job *); +static void deljob(struct job *); +static struct job *getcurjob(struct job *); +#endif +static int getjobstatus(const struct job *); +static void printjobcmd(struct job *); +static void showjob(struct job *, int); + + +/* + * Turn job control on and off. + */ + +static int jobctl; + +#if JOBS +static void +jobctl_notty(void) +{ + if (ttyfd >= 0) { + close(ttyfd); + ttyfd = -1; + } + if (!iflag) { + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + jobctl = 1; + return; + } + out2fmt_flush("sh: can't access tty; job control turned off\n"); + mflag = 0; +} + +void +setjobctl(int on) +{ + int i; + + if (on == jobctl || rootshell == 0) + return; + if (on) { + if (ttyfd != -1) + close(ttyfd); + if ((ttyfd = open(_PATH_TTY, O_RDWR | O_CLOEXEC)) < 0) { + i = 0; + while (i <= 2 && !isatty(i)) + i++; + if (i > 2 || + (ttyfd = fcntl(i, F_DUPFD_CLOEXEC, 10)) < 0) { + jobctl_notty(); + return; + } + } + if (ttyfd < 10) { + /* + * Keep our TTY file descriptor out of the way of + * the user's redirections. + */ + if ((i = fcntl(ttyfd, F_DUPFD_CLOEXEC, 10)) < 0) { + jobctl_notty(); + return; + } + close(ttyfd); + ttyfd = i; + } + do { /* while we are in the background */ + initialpgrp = tcgetpgrp(ttyfd); + if (initialpgrp < 0) { + jobctl_notty(); + return; + } + if (initialpgrp != getpgrp()) { + if (!iflag) { + initialpgrp = -1; + jobctl_notty(); + return; + } + kill(0, SIGTTIN); + continue; + } + } while (0); + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + setpgid(0, rootpid); + tcsetpgrp(ttyfd, rootpid); + } else { /* turning job control off */ + setpgid(0, initialpgrp); + if (ttyfd >= 0) { + tcsetpgrp(ttyfd, initialpgrp); + close(ttyfd); + ttyfd = -1; + } + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + } + jobctl = on; +} +#endif + + +#if JOBS +int +fgcmd(int argc __unused, char **argv __unused) +{ + struct job *jp; + pid_t pgrp; + int status; + + nextopt(""); + jp = getjob(*argptr); + if (jp->jobctl == 0) + error("job not created under job control"); + printjobcmd(jp); + flushout(&output); + pgrp = jp->ps[0].pid; + if (ttyfd >= 0) + tcsetpgrp(ttyfd, pgrp); + restartjob(jp); + jp->foreground = 1; + INTOFF; + status = waitforjob(jp, (int *)NULL); + INTON; + return status; +} + + +int +bgcmd(int argc __unused, char **argv __unused) +{ + struct job *jp; + + nextopt(""); + do { + jp = getjob(*argptr); + if (jp->jobctl == 0) + error("job not created under job control"); + if (jp->state == JOBDONE) + continue; + restartjob(jp); + jp->foreground = 0; + out1fmt("[%td] ", jp - jobtab + 1); + printjobcmd(jp); + } while (*argptr != NULL && *++argptr != NULL); + return 0; +} + + +static void +restartjob(struct job *jp) +{ + struct procstat *ps; + int i; + + if (jp->state == JOBDONE) + return; + setcurjob(jp); + INTOFF; + kill(-jp->ps[0].pid, SIGCONT); + for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { + if (WIFSTOPPED(ps->status)) { + ps->status = -1; + jp->state = 0; + } + } + INTON; +} +#endif + + +int +jobscmd(int argc __unused, char *argv[] __unused) +{ + char *id; + int ch, mode; + + mode = SHOWJOBS_DEFAULT; + while ((ch = nextopt("lps")) != '\0') { + switch (ch) { + case 'l': + mode = SHOWJOBS_VERBOSE; + break; + case 'p': + mode = SHOWJOBS_PGIDS; + break; + case 's': + mode = SHOWJOBS_PIDS; + break; + } + } + + if (*argptr == NULL) + showjobs(0, mode); + else + while ((id = *argptr++) != NULL) + showjob(getjob(id), mode); + + return (0); +} + +static int getjobstatus(const struct job *jp) +{ + int i, status; + + if (!jp->pipefail) + return (jp->ps[jp->nprocs - 1].status); + for (i = jp->nprocs - 1; i >= 0; i--) { + status = jp->ps[i].status; + if (status != 0) + return (status); + } + return (0); +} + +static void +printjobcmd(struct job *jp) +{ + struct procstat *ps; + int i; + + for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { + out1str(ps->cmd); + if (i > 0) + out1str(" | "); + } + out1c('\n'); +} + +static void +showjob(struct job *jp, int mode) +{ + char s[64]; + char statebuf[16]; + const char *statestr, *coredump; + struct procstat *ps; + struct job *j; + int col, curr, i, jobno, prev, procno, status; + char c; + + procno = (mode == SHOWJOBS_PGIDS) ? 1 : jp->nprocs; + jobno = jp - jobtab + 1; + curr = prev = 0; +#if JOBS + if ((j = getcurjob(NULL)) != NULL) { + curr = j - jobtab + 1; + if ((j = getcurjob(j)) != NULL) + prev = j - jobtab + 1; + } +#endif + coredump = ""; + status = getjobstatus(jp); + if (jp->state == 0) { + statestr = "Running"; +#if JOBS + } else if (jp->state == JOBSTOPPED) { + ps = jp->ps + jp->nprocs - 1; + while (!WIFSTOPPED(ps->status) && ps > jp->ps) + ps--; + if (WIFSTOPPED(ps->status)) + i = WSTOPSIG(ps->status); + else + i = -1; + statestr = strsignal(i); + if (statestr == NULL) + statestr = "Suspended"; +#endif + } else if (WIFEXITED(status)) { + if (WEXITSTATUS(status) == 0) + statestr = "Done"; + else { + fmtstr(statebuf, sizeof(statebuf), "Done(%d)", + WEXITSTATUS(status)); + statestr = statebuf; + } + } else { + i = WTERMSIG(status); + statestr = strsignal(i); + if (statestr == NULL) + statestr = "Unknown signal"; + if (WCOREDUMP(status)) + coredump = " (core dumped)"; + } + + for (ps = jp->ps ; procno > 0 ; ps++, procno--) { /* for each process */ + if (mode == SHOWJOBS_PIDS || mode == SHOWJOBS_PGIDS) { + out1fmt("%d\n", (int)ps->pid); + continue; + } + if (mode != SHOWJOBS_VERBOSE && ps != jp->ps) + continue; + if (jobno == curr && ps == jp->ps) + c = '+'; + else if (jobno == prev && ps == jp->ps) + c = '-'; + else + c = ' '; + if (ps == jp->ps) + fmtstr(s, 64, "[%d] %c ", jobno, c); + else + fmtstr(s, 64, " %c ", c); + out1str(s); + col = strlen(s); + if (mode == SHOWJOBS_VERBOSE) { + fmtstr(s, 64, "%d ", (int)ps->pid); + out1str(s); + col += strlen(s); + } + if (ps == jp->ps) { + out1str(statestr); + out1str(coredump); + col += strlen(statestr) + strlen(coredump); + } + do { + out1c(' '); + col++; + } while (col < 30); + if (mode == SHOWJOBS_VERBOSE) { + out1str(ps->cmd); + out1c('\n'); + } else + printjobcmd(jp); + } +} + +/* + * Print a list of jobs. If "change" is nonzero, only print jobs whose + * statuses have changed since the last call to showjobs. + * + * If the shell is interrupted in the process of creating a job, the + * result may be a job structure containing zero processes. Such structures + * will be freed here. + */ + +void +showjobs(int change, int mode) +{ + int jobno; + struct job *jp; + + TRACE(("showjobs(%d) called\n", change)); + checkzombies(); + for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) { + if (! jp->used) + continue; + if (jp->nprocs == 0) { + freejob(jp); + continue; + } + if (change && ! jp->changed) + continue; + showjob(jp, mode); + if (mode == SHOWJOBS_DEFAULT || mode == SHOWJOBS_VERBOSE) { + jp->changed = 0; + /* Hack: discard jobs for which $! has not been + * referenced in interactive mode when they terminate. + */ + if (jp->state == JOBDONE && !jp->remembered && + (iflag || jp != bgjob)) { + freejob(jp); + } + } + } +} + + +/* + * Mark a job structure as unused. + */ + +static void +freejob(struct job *jp) +{ + struct procstat *ps; + int i; + + INTOFF; + if (bgjob == jp) + bgjob = NULL; + 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; +#if JOBS + deljob(jp); +#endif + INTON; +} + + + +int +waitcmd(int argc __unused, char **argv __unused) +{ + struct job *job; + int retval; + + nextopt(""); + if (*argptr == NULL) + return (waitcmdloop(NULL)); + + do { + job = getjob_nonotfound(*argptr); + if (job == NULL) + retval = 127; + else + retval = waitcmdloop(job); + argptr++; + } while (*argptr != NULL); + + return (retval); +} + +static int +waitcmdloop(struct job *job) +{ + int status, retval, sig; + struct job *jp; + + /* + * Loop until a process is terminated or stopped, or a SIGINT is + * received. + */ + + do { + if (job != NULL) { + if (job->state == JOBDONE) { + status = getjobstatus(job); + if (WIFEXITED(status)) + retval = WEXITSTATUS(status); + else + retval = WTERMSIG(status) + 128; + if (! iflag || ! job->changed) + freejob(job); + else { + job->remembered = 0; + if (job == bgjob) + bgjob = NULL; + } + return retval; + } + } else { + for (jp = jobtab ; jp < jobtab + njobs; jp++) + if (jp->used && jp->state == JOBDONE) { + if (! iflag || ! jp->changed) + freejob(jp); + else { + jp->remembered = 0; + if (jp == bgjob) + bgjob = NULL; + } + } + for (jp = jobtab ; ; jp++) { + if (jp >= jobtab + njobs) { /* no running procs */ + return 0; + } + if (jp->used && jp->state == 0) + break; + } + } + } while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, (struct job *)NULL) != -1); + + sig = pendingsig_waitcmd; + pendingsig_waitcmd = 0; + return sig + 128; +} + + + +int +jobidcmd(int argc __unused, char **argv __unused) +{ + struct job *jp; + int i; + + nextopt(""); + jp = getjob(*argptr); + for (i = 0 ; i < jp->nprocs ; ) { + out1fmt("%d", (int)jp->ps[i].pid); + out1c(++i < jp->nprocs? ' ' : '\n'); + } + return 0; +} + + + +/* + * Convert a job name to a job structure. + */ + +static struct job * +getjob_nonotfound(const char *name) +{ + int jobno; + struct job *found, *jp; + size_t namelen; + pid_t pid; + int i; + + if (name == NULL) { +#if JOBS + name = "%+"; +#else + error("No current job"); +#endif + } + if (name[0] == '%') { + if (is_digit(name[1])) { + jobno = number(name + 1); + if (jobno > 0 && jobno <= njobs + && jobtab[jobno - 1].used != 0) + return &jobtab[jobno - 1]; +#if JOBS + } else if ((name[1] == '%' || name[1] == '+') && + name[2] == '\0') { + if ((jp = getcurjob(NULL)) == NULL) + error("No current job"); + return (jp); + } else if (name[1] == '-' && name[2] == '\0') { + if ((jp = getcurjob(NULL)) == NULL || + (jp = getcurjob(jp)) == NULL) + error("No previous job"); + return (jp); +#endif + } else if (name[1] == '?') { + found = NULL; + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && strstr(jp->ps[0].cmd, name + 2) != NULL) { + if (found) + error("%s: ambiguous", name); + found = jp; + } + } + if (found != NULL) + return (found); + } else { + namelen = strlen(name); + found = NULL; + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && strncmp(jp->ps[0].cmd, name + 1, + namelen - 1) == 0) { + if (found) + error("%s: ambiguous", name); + found = jp; + } + } + if (found) + return found; + } + } else if (is_number(name)) { + pid = (pid_t)number(name); + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && jp->ps[jp->nprocs - 1].pid == pid) + return jp; + } + } + return NULL; +} + + +static struct job * +getjob(const char *name) +{ + struct job *jp; + + jp = getjob_nonotfound(name); + if (jp == NULL) + error("No such job: %s", name); + return (jp); +} + + +int +killjob(const char *name, int sig) +{ + struct job *jp; + int i, ret; + + jp = getjob(name); + if (jp->state == JOBDONE) + return 0; + if (jp->jobctl) + return kill(-jp->ps[0].pid, sig); + ret = -1; + errno = ESRCH; + for (i = 0; i < jp->nprocs; i++) + if (jp->ps[i].status == -1 || WIFSTOPPED(jp->ps[i].status)) { + if (kill(jp->ps[i].pid, sig) == 0) + ret = 0; + } else + ret = 0; + return ret; +} + +/* + * Return a new job structure, + */ + +struct job * +makejob(union node *node __unused, int nprocs) +{ + int i; + struct job *jp; + + for (i = njobs, jp = jobtab ; ; jp++) { + if (--i < 0) { + INTOFF; + if (njobs == 0) { + jobtab = ckmalloc(4 * sizeof jobtab[0]); +#if JOBS + jobmru = NULL; +#endif + } else { + jp = ckmalloc((njobs + 4) * sizeof jobtab[0]); + memcpy(jp, jobtab, njobs * sizeof jp[0]); +#if JOBS + /* Relocate `next' pointers and list head */ + if (jobmru != NULL) + jobmru = &jp[jobmru - jobtab]; + for (i = 0; i < njobs; i++) + if (jp[i].next != NULL) + jp[i].next = &jp[jp[i].next - + jobtab]; +#endif + if (bgjob != NULL) + bgjob = &jp[bgjob - jobtab]; + /* Relocate `ps' pointers */ + for (i = 0; i < njobs; i++) + if (jp[i].ps == &jobtab[i].ps0) + jp[i].ps = &jp[i].ps0; + ckfree(jobtab); + jobtab = jp; + } + jp = jobtab + njobs; + for (i = 4 ; --i >= 0 ; jobtab[njobs++].used = 0) + ; + INTON; + break; + } + if (jp->used == 0) + break; + } + INTOFF; + jp->state = 0; + jp->used = 1; + jp->changed = 0; + jp->nprocs = 0; + jp->foreground = 0; + jp->remembered = 0; + jp->pipefail = pipefailflag; +#if JOBS + jp->jobctl = jobctl; + jp->next = NULL; +#endif + if (nprocs > 1) { + jp->ps = ckmalloc(nprocs * sizeof (struct procstat)); + } else { + jp->ps = &jp->ps0; + } + INTON; + TRACE(("makejob(%p, %d) returns %%%td\n", (void *)node, nprocs, + jp - jobtab + 1)); + return jp; +} + +#if JOBS +static void +setcurjob(struct job *cj) +{ + struct job *jp, *prev; + + for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { + if (jp == cj) { + if (prev != NULL) + prev->next = jp->next; + else + jobmru = jp->next; + jp->next = jobmru; + jobmru = cj; + return; + } + } + cj->next = jobmru; + jobmru = cj; +} + +static void +deljob(struct job *j) +{ + struct job *jp, *prev; + + for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { + if (jp == j) { + if (prev != NULL) + prev->next = jp->next; + else + jobmru = jp->next; + return; + } + } +} + +/* + * Return the most recently used job that isn't `nj', and preferably one + * that is stopped. + */ +static struct job * +getcurjob(struct job *nj) +{ + struct job *jp; + + /* Try to find a stopped one.. */ + for (jp = jobmru; jp != NULL; jp = jp->next) + if (jp->used && jp != nj && jp->state == JOBSTOPPED) + return (jp); + /* Otherwise the most recently used job that isn't `nj' */ + for (jp = jobmru; jp != NULL; jp = jp->next) + if (jp->used && jp != nj) + return (jp); + + return (NULL); +} + +#endif + +/* + * Fork of 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). + */ + +pid_t +forkshell(struct job *jp, union node *n, int mode) +{ + pid_t pid; + pid_t pgrp; + + TRACE(("forkshell(%%%td, %p, %d) called\n", jp - jobtab, (void *)n, + mode)); + INTOFF; + if (mode == FORK_BG && (jp == NULL || jp->nprocs == 0)) + checkzombies(); + flushall(); + pid = fork(); + if (pid == -1) { + TRACE(("Fork failed, errno=%d\n", errno)); + INTON; + error("Cannot fork: %s", strerror(errno)); + } + if (pid == 0) { + struct job *p; + int wasroot; + int i; + + TRACE(("Child shell %d\n", (int)getpid())); + wasroot = rootshell; + rootshell = 0; + handler = &main_handler; + closescript(); + INTON; + forcelocal = 0; + clear_traps(); +#if JOBS + jobctl = 0; /* do job control only in root shell */ + if (wasroot && mode != FORK_NOJOB && mflag) { + if (jp == NULL || jp->nprocs == 0) + pgrp = getpid(); + else + pgrp = jp->ps[0].pid; + if (setpgid(0, pgrp) == 0 && mode == FORK_FG && + ttyfd >= 0) { + /*** this causes superfluous TIOCSPGRPS ***/ + if (tcsetpgrp(ttyfd, pgrp) < 0) + error("tcsetpgrp failed, errno=%d", errno); + } + setsignal(SIGTSTP); + setsignal(SIGTTOU); + } else if (mode == FORK_BG) { + ignoresig(SIGINT); + ignoresig(SIGQUIT); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(_PATH_DEVNULL, O_RDONLY) != 0) + error("cannot open %s: %s", + _PATH_DEVNULL, strerror(errno)); + } + } +#else + if (mode == FORK_BG) { + ignoresig(SIGINT); + ignoresig(SIGQUIT); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(_PATH_DEVNULL, O_RDONLY) != 0) + error("cannot open %s: %s", + _PATH_DEVNULL, strerror(errno)); + } + } +#endif + INTOFF; + for (i = njobs, p = jobtab ; --i >= 0 ; p++) + if (p->used) + freejob(p); + INTON; + if (wasroot && iflag) { + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); + } + return pid; + } + if (rootshell && mode != FORK_NOJOB && mflag) { + if (jp == NULL || jp->nprocs == 0) + pgrp = pid; + else + pgrp = jp->ps[0].pid; + setpgid(pid, pgrp); + } + if (mode == FORK_BG) { + if (bgjob != NULL && bgjob->state == JOBDONE && + !bgjob->remembered && !iflag) + freejob(bgjob); + backgndpid = pid; /* set $! */ + bgjob = jp; + } + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd = nullstr; + if (iflag && rootshell && n) + ps->cmd = commandtext(n); + jp->foreground = mode == FORK_FG; +#if JOBS + setcurjob(jp); +#endif + } + INTON; + TRACE(("In parent shell: child = %d\n", (int)pid)); + return pid; +} + + +pid_t +vforkexecshell(struct job *jp, char **argv, char **envp, const char *path, int idx, int pip[2]) +{ + pid_t pid; + struct jmploc jmploc; + struct jmploc *savehandler; + + TRACE(("vforkexecshell(%%%td, %s, %p) called\n", jp - jobtab, argv[0], + (void *)pip)); + INTOFF; + flushall(); + savehandler = handler; + pid = vfork(); + if (pid == -1) { + TRACE(("Vfork failed, errno=%d\n", errno)); + INTON; + error("Cannot fork: %s", strerror(errno)); + } + if (pid == 0) { + TRACE(("Child shell %d\n", (int)getpid())); + if (setjmp(jmploc.loc)) + _exit(exception == EXEXEC ? exerrno : 2); + if (pip != NULL) { + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + } + handler = &jmploc; + shellexec(argv, envp, path, idx); + } + handler = savehandler; + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd = nullstr; + jp->foreground = 1; +#if JOBS + setcurjob(jp); +#endif + } + INTON; + TRACE(("In parent shell: child = %d\n", (int)pid)); + return pid; +} + + +/* + * 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 + * foreground 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. + */ + +int +waitforjob(struct job *jp, int *signaled) +{ +#if JOBS + int propagate_int = jp->jobctl && jp->foreground; +#endif + int status; + int st; + + INTOFF; + TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1)); + while (jp->state == 0) + if (dowait(DOWAIT_BLOCK | (Tflag ? DOWAIT_SIG | + DOWAIT_SIG_TRAP : 0), jp) == -1) + dotrap(); +#if JOBS + if (jp->jobctl) { + if (ttyfd >= 0 && tcsetpgrp(ttyfd, rootpid) < 0) + error("tcsetpgrp failed, errno=%d\n", errno); + } + if (jp->state == JOBSTOPPED) + setcurjob(jp); +#endif + status = getjobstatus(jp); + if (signaled != NULL) + *signaled = WIFSIGNALED(status); + /* convert to 8 bits */ + if (WIFEXITED(status)) + st = WEXITSTATUS(status); +#if JOBS + else if (WIFSTOPPED(status)) + st = WSTOPSIG(status) + 128; +#endif + else + st = WTERMSIG(status) + 128; + if (! JOBS || jp->state == JOBDONE) + freejob(jp); + if (int_pending()) { + if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGINT) + CLEAR_PENDING_INT; + } +#if JOBS + else if (rootshell && propagate_int && + WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) + kill(getpid(), SIGINT); +#endif + INTON; + return st; +} + + +static void +dummy_handler(int sig __unused) +{ +} + +/* + * Wait for a process to terminate. + */ + +static pid_t +dowait(int mode, struct job *job) +{ + struct sigaction sa, osa; + sigset_t mask, omask; + pid_t pid; + int status; + struct procstat *sp; + struct job *jp; + struct job *thisjob; + const char *sigstr; + int done; + int stopped; + int sig; + int coredump; + int wflags; + int restore_sigchld; + + TRACE(("dowait(%d, %p) called\n", mode, job)); + restore_sigchld = 0; + if ((mode & DOWAIT_SIG) != 0) { + sigfillset(&mask); + sigprocmask(SIG_BLOCK, &mask, &omask); + INTOFF; + if (!issigchldtrapped()) { + restore_sigchld = 1; + sa.sa_handler = dummy_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGCHLD, &sa, &osa); + } + } + do { +#if JOBS + if (iflag) + wflags = WUNTRACED | WCONTINUED; + else +#endif + wflags = 0; + if ((mode & (DOWAIT_BLOCK | DOWAIT_SIG)) != DOWAIT_BLOCK) + wflags |= WNOHANG; + pid = wait3(&status, wflags, (struct rusage *)NULL); + TRACE(("wait returns %d, status=%d\n", (int)pid, status)); + if (pid == 0 && (mode & DOWAIT_SIG) != 0) { + pid = -1; + if (((mode & DOWAIT_SIG_TRAP) != 0 ? + pendingsig : pendingsig_waitcmd) != 0) { + errno = EINTR; + break; + } + sigsuspend(&omask); + if (int_pending()) + break; + } + } while (pid == -1 && errno == EINTR); + if (pid == -1 && errno == ECHILD && job != NULL) + job->state = JOBDONE; + if ((mode & DOWAIT_SIG) != 0) { + if (restore_sigchld) + sigaction(SIGCHLD, &osa, NULL); + sigprocmask(SIG_SETMASK, &omask, NULL); + INTON; + } + if (pid <= 0) + return pid; + INTOFF; + thisjob = NULL; + for (jp = jobtab ; jp < jobtab + njobs ; jp++) { + if (jp->used && jp->nprocs > 0) { + done = 1; + stopped = 1; + for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) { + if (sp->pid == -1) + continue; + if (sp->pid == pid && (sp->status == -1 || + WIFSTOPPED(sp->status))) { + TRACE(("Changing status of proc %d from 0x%x to 0x%x\n", + (int)pid, sp->status, + status)); + if (WIFCONTINUED(status)) { + sp->status = -1; + jp->state = 0; + } else + sp->status = status; + thisjob = jp; + } + if (sp->status == -1) + stopped = 0; + else if (WIFSTOPPED(sp->status)) + done = 0; + } + if (stopped) { /* stopped or done */ + int state = done? JOBDONE : JOBSTOPPED; + if (jp->state != state) { + TRACE(("Job %td: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state)); + jp->state = state; + if (jp != job) { + if (done && !jp->remembered && + !iflag && jp != bgjob) + freejob(jp); +#if JOBS + else if (done) + deljob(jp); +#endif + } + } + } + } + } + INTON; + if (!thisjob || thisjob->state == 0) + ; + else if ((!rootshell || !iflag || thisjob == job) && + thisjob->foreground && thisjob->state != JOBSTOPPED) { + sig = 0; + coredump = 0; + for (sp = thisjob->ps; sp < thisjob->ps + thisjob->nprocs; sp++) + if (WIFSIGNALED(sp->status)) { + sig = WTERMSIG(sp->status); + coredump = WCOREDUMP(sp->status); + } + if (sig > 0 && sig != SIGINT && sig != SIGPIPE) { + sigstr = strsignal(sig); + if (sigstr != NULL) + out2str(sigstr); + else + out2str("Unknown signal"); + if (coredump) + out2str(" (core dumped)"); + out2c('\n'); + flushout(out2); + } + } else { + TRACE(("Not printing status, rootshell=%d, job=%p\n", rootshell, job)); + thisjob->changed = 1; + } + return pid; +} + + + +/* + * return 1 if there are stopped jobs, otherwise 0 + */ +int job_warning = 0; +int +stoppedjobs(void) +{ + int jobno; + struct job *jp; + + if (job_warning) + return (0); + for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) { + if (jp->used == 0) + continue; + if (jp->state == JOBSTOPPED) { + out2fmt_flush("You have stopped jobs.\n"); + job_warning = 2; + return (1); + } + } + + return (0); +} + + +static void +checkzombies(void) +{ + while (njobs > 0 && dowait(0, NULL) > 0) + ; +} + + +int +backgndpidset(void) +{ + return backgndpid != -1; +} + + +pid_t +backgndpidval(void) +{ + if (bgjob != NULL && !forcelocal) + bgjob->remembered = 1; + return backgndpid; +} + +/* + * Return a string identifying a command (to be printed by the + * jobs command. + */ + +static char *cmdnextc; +static int cmdnleft; +#define MAXCMDTEXT 200 + +char * +commandtext(union node *n) +{ + char *name; + + cmdnextc = name = ckmalloc(MAXCMDTEXT); + cmdnleft = MAXCMDTEXT - 4; + cmdtxt(n); + *cmdnextc = '\0'; + return name; +} + + +static void +cmdtxtdogroup(union node *n) +{ + cmdputs("; do "); + cmdtxt(n); + cmdputs("; done"); +} + + +static void +cmdtxtredir(union node *n, const char *op, int deffd) +{ + char s[2]; + + if (n->nfile.fd != deffd) { + s[0] = n->nfile.fd + '0'; + s[1] = '\0'; + cmdputs(s); + } + cmdputs(op); + if (n->type == NTOFD || n->type == NFROMFD) { + if (n->ndup.dupfd >= 0) + s[0] = n->ndup.dupfd + '0'; + else + s[0] = '-'; + s[1] = '\0'; + cmdputs(s); + } else { + cmdtxt(n->nfile.fname); + } +} + + +static void +cmdtxt(union node *n) +{ + union node *np; + struct nodelist *lp; + + if (n == NULL) + return; + switch (n->type) { + case NSEMI: + cmdtxt(n->nbinary.ch1); + cmdputs("; "); + cmdtxt(n->nbinary.ch2); + break; + case NAND: + cmdtxt(n->nbinary.ch1); + cmdputs(" && "); + cmdtxt(n->nbinary.ch2); + break; + case NOR: + cmdtxt(n->nbinary.ch1); + cmdputs(" || "); + cmdtxt(n->nbinary.ch2); + break; + case NPIPE: + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + cmdtxt(lp->n); + if (lp->next) + cmdputs(" | "); + } + break; + case NSUBSHELL: + cmdputs("("); + cmdtxt(n->nredir.n); + cmdputs(")"); + break; + case NREDIR: + case NBACKGND: + cmdtxt(n->nredir.n); + break; + case NIF: + cmdputs("if "); + cmdtxt(n->nif.test); + cmdputs("; then "); + cmdtxt(n->nif.ifpart); + cmdputs("..."); + break; + case NWHILE: + cmdputs("while "); + cmdtxt(n->nbinary.ch1); + cmdtxtdogroup(n->nbinary.ch2); + break; + case NUNTIL: + cmdputs("until "); + cmdtxt(n->nbinary.ch1); + cmdtxtdogroup(n->nbinary.ch2); + break; + case NFOR: + cmdputs("for "); + cmdputs(n->nfor.var); + cmdputs(" in ..."); + break; + case NCASE: + cmdputs("case "); + cmdputs(n->ncase.expr->narg.text); + cmdputs(" in ..."); + break; + case NDEFUN: + cmdputs(n->narg.text); + cmdputs("() ..."); + break; + case NNOT: + cmdputs("! "); + cmdtxt(n->nnot.com); + break; + case NCMD: + for (np = n->ncmd.args ; np ; np = np->narg.next) { + cmdtxt(np); + if (np->narg.next) + cmdputs(" "); + } + for (np = n->ncmd.redirect ; np ; np = np->nfile.next) { + cmdputs(" "); + cmdtxt(np); + } + break; + case NARG: + cmdputs(n->narg.text); + break; + case NTO: + cmdtxtredir(n, ">", 1); + break; + case NAPPEND: + cmdtxtredir(n, ">>", 1); + break; + case NTOFD: + cmdtxtredir(n, ">&", 1); + break; + case NCLOBBER: + cmdtxtredir(n, ">|", 1); + break; + case NFROM: + cmdtxtredir(n, "<", 0); + break; + case NFROMTO: + cmdtxtredir(n, "<>", 0); + break; + case NFROMFD: + cmdtxtredir(n, "<&", 0); + break; + case NHERE: + case NXHERE: + cmdputs("<<..."); + break; + default: + cmdputs("???"); + break; + } +} + + + +static void +cmdputs(const char *s) +{ + const char *p; + char *q; + char c; + int subtype = 0; + + if (cmdnleft <= 0) + return; + p = s; + q = cmdnextc; + while ((c = *p++) != '\0') { + if (c == CTLESC) + *q++ = *p++; + else if (c == CTLVAR) { + *q++ = '$'; + if (--cmdnleft > 0) + *q++ = '{'; + subtype = *p++; + if ((subtype & VSTYPE) == VSLENGTH && --cmdnleft > 0) + *q++ = '#'; + } else if (c == '=' && subtype != 0) { + *q = "}-+?=##%%\0X"[(subtype & VSTYPE) - VSNORMAL]; + if (*q) + q++; + else + cmdnleft++; + if (((subtype & VSTYPE) == VSTRIMLEFTMAX || + (subtype & VSTYPE) == VSTRIMRIGHTMAX) && + --cmdnleft > 0) + *q = q[-1], q++; + subtype = 0; + } else if (c == CTLENDVAR) { + *q++ = '}'; + } else if (c == CTLBACKQ || c == CTLBACKQ+CTLQUOTE) { + cmdnleft -= 5; + if (cmdnleft > 0) { + *q++ = '$'; + *q++ = '('; + *q++ = '.'; + *q++ = '.'; + *q++ = '.'; + *q++ = ')'; + } + } else if (c == CTLARI) { + cmdnleft -= 2; + if (cmdnleft > 0) { + *q++ = '$'; + *q++ = '('; + *q++ = '('; + } + p++; + } else if (c == CTLENDARI) { + if (--cmdnleft > 0) { + *q++ = ')'; + *q++ = ')'; + } + } else if (c == CTLQUOTEMARK || c == CTLQUOTEEND) + cmdnleft++; /* ignore */ + else + *q++ = c; + if (--cmdnleft <= 0) { + *q++ = '.'; + *q++ = '.'; + *q++ = '.'; + break; + } + } + cmdnextc = q; +} diff --git a/bin/1sh/jobs.h b/bin/1sh/jobs.h new file mode 100644 index 00000000..98c2eb5c --- /dev/null +++ b/bin/1sh/jobs.h @@ -0,0 +1,67 @@ +/*- + * Copyright (c) 1991, 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. + * + * @(#)jobs.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/jobs.h 327475 2018-01-01 22:31:52Z jilles $ + */ + +/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ +#define FORK_FG 0 +#define FORK_BG 1 +#define FORK_NOJOB 2 + +#include <signal.h> /* for sig_atomic_t */ + +struct job; + +enum { + SHOWJOBS_DEFAULT, /* job number, status, command */ + SHOWJOBS_VERBOSE, /* job number, PID, status, command */ + SHOWJOBS_PIDS, /* PID only */ + SHOWJOBS_PGIDS /* PID of the group leader only */ +}; + +extern int job_warning; /* user was warned about stopped jobs */ + +void setjobctl(int); +void showjobs(int, int); +struct job *makejob(union node *, int); +pid_t forkshell(struct job *, union node *, int); +pid_t vforkexecshell(struct job *, char **, char **, const char *, int, int []); +int waitforjob(struct job *, int *); +int stoppedjobs(void); +int backgndpidset(void); +pid_t backgndpidval(void); +char *commandtext(union node *); + +#if ! JOBS +#define setjobctl(on) /* do nothing */ +#endif diff --git a/bin/1sh/kill.c b/bin/1sh/kill.c new file mode 100644 index 00000000..6003fa9d --- /dev/null +++ b/bin/1sh/kill.c @@ -0,0 +1,215 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1988, 1993, 1994 + * 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. + */ +/* + * Important: This file is used both as a standalone program /bin/kill and + * as a builtin for /bin/sh (#define SHELL). + */ + +#if 0 +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1988, 1993, 1994\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static char sccsid[] = "@(#)kill.c 8.4 (Berkeley) 4/28/95"; +#endif /* not lint */ +#endif +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/kill/kill.c 326025 2017-11-20 19:49:47Z pfg $ */ + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef SHELL +#define main killcmd +#include "bltin.h" +#endif + +static void nosig(const char *); +static void printsignals(FILE *); +static int signame_to_signum(const char *); +static void usage(void); + +int +main(int argc, char *argv[]) +{ + long pidl; + pid_t pid; + int errors, numsig, ret; + char *ep; + + if (argc < 2) + usage(); + + numsig = SIGTERM; + + argc--, argv++; + if (!strcmp(*argv, "-l")) { + argc--, argv++; + if (argc > 1) + usage(); + if (argc == 1) { + if (!isdigit(**argv)) + usage(); + numsig = strtol(*argv, &ep, 10); + if (!**argv || *ep) + errx(2, "illegal signal number: %s", *argv); + if (numsig >= 128) + numsig -= 128; + if (numsig <= 0 || numsig >= NSIG) + nosig(*argv); + printf("%s\n", sys_signame[numsig]); + return (0); + } + printsignals(stdout); + return (0); + } + + if (!strcmp(*argv, "-s")) { + argc--, argv++; + if (argc < 1) { + warnx("option requires an argument -- s"); + usage(); + } + if (strcmp(*argv, "0")) { + if ((numsig = signame_to_signum(*argv)) < 0) + nosig(*argv); + } else + numsig = 0; + argc--, argv++; + } else if (**argv == '-' && *(*argv + 1) != '-') { + ++*argv; + if (isalpha(**argv)) { + if ((numsig = signame_to_signum(*argv)) < 0) + nosig(*argv); + } else if (isdigit(**argv)) { + numsig = strtol(*argv, &ep, 10); + if (!**argv || *ep) + errx(2, "illegal signal number: %s", *argv); + if (numsig < 0) + nosig(*argv); + } else + nosig(*argv); + argc--, argv++; + } + + if (argc > 0 && strncmp(*argv, "--", 2) == 0) + argc--, argv++; + + if (argc == 0) + usage(); + + for (errors = 0; argc; argc--, argv++) { +#ifdef SHELL + if (**argv == '%') + ret = killjob(*argv, numsig); + else +#endif + { + pidl = strtol(*argv, &ep, 10); + /* Check for overflow of pid_t. */ + pid = (pid_t)pidl; + if (!**argv || *ep || pid != pidl) + errx(2, "illegal process id: %s", *argv); + ret = kill(pid, numsig); + } + if (ret == -1) { + warn("%s", *argv); + errors = 1; + } + } + + return (errors); +} + +static int +signame_to_signum(const char *sig) +{ + int n; + + if (strncasecmp(sig, "SIG", 3) == 0) + sig += 3; + for (n = 1; n < NSIG; n++) { + if (!strcasecmp(sys_signame[n], sig)) + return (n); + } + return (-1); +} + +static void +nosig(const char *name) +{ + + warnx("unknown signal %s; valid signals:", name); + printsignals(stderr); +#ifdef SHELL + error(NULL); +#else + exit(2); +#endif +} + +static void +printsignals(FILE *fp) +{ + int n; + + for (n = 1; n < NSIG; n++) { + (void)fprintf(fp, "%s", sys_signame[n]); + if (n == (NSIG / 2) || n == (NSIG - 1)) + (void)fprintf(fp, "\n"); + else + (void)fprintf(fp, " "); + } +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "%s\n%s\n%s\n%s\n", + "usage: kill [-s signal_name] pid ...", + " kill -l [exit_status]", + " kill -signal_name pid ...", + " kill -signal_number pid ..."); +#ifdef SHELL + error(NULL); +#else + exit(2); +#endif +} diff --git a/bin/1sh/mail.c b/bin/1sh/mail.c new file mode 100644 index 00000000..adf54a06 --- /dev/null +++ b/bin/1sh/mail.c @@ -0,0 +1,120 @@ +/*- + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mail.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/mail.c 336303 2018-07-15 09:14:30Z jilles $ */ + +/* + * Routines to check for mail. (Perhaps make part of main.c?) + */ + +#include "shell.h" +#include "mail.h" +#include "var.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> + + +#define MAXMBOXES 10 + + +static int nmboxes; /* number of mailboxes */ +static time_t mailtime[MAXMBOXES]; /* times of mailboxes */ + + + +/* + * Print appropriate message(s) if mail has arrived. If the argument is + * nozero, then the value of MAIL has changed, so we just update the + * values. + */ + +void +chkmail(int silent) +{ + int i; + char *mpath; + char *p; + char *msg; + struct stackmark smark; + struct stat statb; + + if (silent) + nmboxes = 10; + if (nmboxes == 0) + return; + setstackmark(&smark); + mpath = stsavestr(mpathset()? mpathval() : mailval()); + for (i = 0 ; i < nmboxes ; i++) { + p = mpath; + if (*p == '\0') + break; + mpath = strchr(p, ':'); + if (!mpath) mpath = strchr(p, '\0'); + if (*mpath != '\0') { + *mpath++ = '\0'; + if (p == mpath - 1) + continue; + } + msg = strchr(p, '%'); + if (msg != NULL) + *msg++ = '\0'; +#ifdef notdef /* this is what the System V shell claims to do (it lies) */ + if (stat(p, &statb) < 0) + statb.st_mtime = 0; + if (statb.st_mtime > mailtime[i] && ! silent) { + out2str(msg? msg : "you have mail"); + out2c('\n'); + } + mailtime[i] = statb.st_mtime; +#else /* this is what it should do */ + if (stat(p, &statb) < 0) + statb.st_size = 0; + if (statb.st_size > mailtime[i] && ! silent) { + out2str(msg? msg : "you have mail"); + out2c('\n'); + } + mailtime[i] = statb.st_size; +#endif + } + nmboxes = i; + popstackmark(&smark); +} diff --git a/bin/1sh/mail.h b/bin/1sh/mail.h new file mode 100644 index 00000000..bc124ba3 --- /dev/null +++ b/bin/1sh/mail.h @@ -0,0 +1,38 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + * + * @(#)mail.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/mail.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +void chkmail(int); diff --git a/bin/1sh/main.c b/bin/1sh/main.c new file mode 100644 index 00000000..3f0bc1da --- /dev/null +++ b/bin/1sh/main.c @@ -0,0 +1,356 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + */ + +#if 0 +static char const copyright[] = +"@(#) Copyright (c) 1991, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 5/28/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/main.c 336320 2018-07-15 21:55:17Z jilles $ */ + +#include <stdio.h> +#include <signal.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <locale.h> +#include <errno.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 "mystring.h" +#include "exec.h" +#include "cd.h" +#include "redir.h" +#include "builtins.h" + +int rootpid; +int rootshell; +struct jmploc main_handler; +int localeisutf8, initial_localeisutf8; + +static void reset(void); +static void cmdloop(int); +static void read_profile(const char *); +static char *find_dot_file(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[]) +{ + struct stackmark smark, smark2; + volatile int state; + char *shinit; + + (void) setlocale(LC_ALL, ""); + initcharset(); + state = 0; + if (setjmp(main_handler.loc)) { + switch (exception) { + case EXEXEC: + exitstatus = exerrno; + break; + + case EXERROR: + exitstatus = 2; + break; + + default: + break; + } + + if (state == 0 || iflag == 0 || ! rootshell || + exception == EXEXIT) + exitshell(exitstatus); + reset(); + if (exception == EXINT) + out2fmt_flush("\n"); + popstackmark(&smark); + FORCEINTON; /* enable interrupts */ + if (state == 1) + goto state1; + else if (state == 2) + goto state2; + else if (state == 3) + goto state3; + else + goto state4; + } + handler = &main_handler; +#ifdef DEBUG + opentrace(); + trputs("Shell args: "); trargs(argv); +#endif + rootpid = getpid(); + rootshell = 1; + INTOFF; + initvar(); + setstackmark(&smark); + setstackmark(&smark2); + procargs(argc, argv); + pwd_init(iflag); + INTON; + if (iflag) + chkmail(1); + if (argv[0] && argv[0][0] == '-') { + state = 1; + read_profile("/etc/profile"); +state1: + state = 2; + if (privileged == 0) + read_profile("${HOME-}/.profile"); + else + read_profile("/etc/suid_profile"); + } +state2: + state = 3; + if (!privileged && iflag) { + if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') { + state = 3; + read_profile(shinit); + } + } +state3: + state = 4; + popstackmark(&smark2); + if (minusc) { + evalstring(minusc, sflag ? 0 : EV_EXIT); + } +state4: + if (sflag || minusc == NULL) { + cmdloop(1); + } + iflag = 0; + optschanged(); + exitshell(exitstatus); + /*NOTREACHED*/ + return 0; +} + +static void +reset(void) +{ + reseteval(); + resetinput(); +} + +/* + * Read and execute commands. "Top" is nonzero for the top level command + * loop; it turns on prompting if the shell is interactive. + */ + +static void +cmdloop(int top) +{ + union node *n; + struct stackmark smark; + int inter; + int numeof = 0; + + TRACE(("cmdloop(%d) called\n", top)); + setstackmark(&smark); + for (;;) { + if (pendingsig) + dotrap(); + inter = 0; + if (iflag && top) { + inter++; + showjobs(1, SHOWJOBS_DEFAULT); + chkmail(0); + flushout(&output); + } + n = parsecmd(inter); + /* showtree(n); DEBUG */ + if (n == NEOF) { + if (!top || numeof >= 50) + break; + if (!stoppedjobs()) { + if (!Iflag) + break; + out2fmt_flush("\nUse \"exit\" to leave shell.\n"); + } + numeof++; + } else if (n != NULL && nflag == 0) { + job_warning = (job_warning == 2) ? 1 : 0; + numeof = 0; + evaltree(n, 0); + } + popstackmark(&smark); + setstackmark(&smark); + if (evalskip != 0) { + if (evalskip == SKIPRETURN) + evalskip = 0; + break; + } + } + popstackmark(&smark); +} + + + +/* + * Read /etc/profile or .profile. Return on error. + */ + +static void +read_profile(const char *name) +{ + int fd; + const char *expandedname; + + expandedname = expandstr(name); + if (expandedname == NULL) + return; + INTOFF; + if ((fd = open(expandedname, O_RDONLY | O_CLOEXEC)) >= 0) + setinputfd(fd, 1); + INTON; + if (fd < 0) + return; + cmdloop(0); + popfile(); +} + + + +/* + * Read a file containing shell functions. + */ + +void +readcmdfile(const char *name) +{ + setinputfile(name, 1); + 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 *opt; + const char *path = pathval(); + struct stat statb; + + /* don't try this for absolute or relative paths */ + if( strchr(basename, '/')) + return basename; + + while ((fullname = padvance(&path, &opt, basename)) != NULL) { + if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { + /* + * Don't bother freeing here, since it will + * be freed by the caller. + */ + return fullname; + } + stunalloc(fullname); + } + return basename; +} + +int +dotcmd(int argc, char **argv) +{ + char *filename, *fullname; + + if (argc < 2) + error("missing filename"); + + exitstatus = 0; + + /* + * Because we have historically not supported any options, + * only treat "--" specially. + */ + filename = argc > 2 && strcmp(argv[1], "--") == 0 ? argv[2] : argv[1]; + + fullname = find_dot_file(filename); + setinputfile(fullname, 1); + commandname = fullname; + cmdloop(0); + popfile(); + return exitstatus; +} + + +int +exitcmd(int argc, char **argv) +{ + if (stoppedjobs()) + return 0; + iflag = 0; + optschanged(); + if (argc > 1) + exitshell(number(argv[1])); + else + exitshell_savedstatus(); +} diff --git a/bin/1sh/main.h b/bin/1sh/main.h new file mode 100644 index 00000000..329a5c8b --- /dev/null +++ b/bin/1sh/main.h @@ -0,0 +1,42 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + * + * @(#)main.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/main.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +extern int rootpid; /* pid of main shell */ +extern int rootshell; /* true if we aren't a child of the main shell */ +extern struct jmploc main_handler; /* top level exception handler */ + +void readcmdfile(const char *); diff --git a/bin/1sh/memalloc.c b/bin/1sh/memalloc.c new file mode 100644 index 00000000..c53521ad --- /dev/null +++ b/bin/1sh/memalloc.c @@ -0,0 +1,344 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)memalloc.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/memalloc.c 326025 2017-11-20 19:49:47Z pfg $ */ + +#include <sys/param.h> +#include "shell.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "expand.h" +#include <stdlib.h> +#include <unistd.h> + +/* + * Like malloc, but returns an error when out of space. + */ + +pointer +ckmalloc(size_t nbytes) +{ + pointer p; + + INTOFF; + p = malloc(nbytes); + INTON; + if (p == NULL) + error("Out of space"); + return p; +} + + +/* + * Same for realloc. + */ + +pointer +ckrealloc(pointer p, int nbytes) +{ + INTOFF; + p = realloc(p, nbytes); + INTON; + if (p == NULL) + error("Out of space"); + return p; +} + +void +ckfree(pointer p) +{ + INTOFF; + free(p); + INTON; +} + + +/* + * Make a copy of a string in safe storage. + */ + +char * +savestr(const char *s) +{ + char *p; + size_t len; + + len = strlen(s); + p = ckmalloc(len + 1); + memcpy(p, s, len + 1); + 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 496 was chosen because with 16-byte alignment the total size + * for the allocated block is 512. + */ + +#define MINSIZE 496 /* minimum size of a block. */ + + +struct stack_block { + struct stack_block *prev; + /* Data follows */ +}; +#define SPACE(sp) ((char*)(sp) + ALIGN(sizeof(struct stack_block))) + +static struct stack_block *stackp; +char *stacknxt; +int stacknleft; +char *sstrend; + + +static void +stnewblock(int nbytes) +{ + struct stack_block *sp; + int allocsize; + + if (nbytes < MINSIZE) + nbytes = MINSIZE; + + allocsize = ALIGN(sizeof(struct stack_block)) + ALIGN(nbytes); + + INTOFF; + sp = ckmalloc(allocsize); + sp->prev = stackp; + stacknxt = SPACE(sp); + stacknleft = allocsize - (stacknxt - (char*)sp); + sstrend = stacknxt + stacknleft; + stackp = sp; + INTON; +} + + +pointer +stalloc(int nbytes) +{ + char *p; + + nbytes = ALIGN(nbytes); + if (nbytes > stacknleft) + stnewblock(nbytes); + p = stacknxt; + stacknxt += nbytes; + stacknleft -= nbytes; + return p; +} + + +void +stunalloc(pointer p) +{ + if (p == NULL) { /*DEBUG */ + write(STDERR_FILENO, "stunalloc\n", 10); + abort(); + } + stacknleft += stacknxt - (char *)p; + stacknxt = p; +} + + +char * +stsavestr(const char *s) +{ + char *p; + size_t len; + + len = strlen(s); + p = stalloc(len + 1); + memcpy(p, s, len + 1); + return p; +} + + +void +setstackmark(struct stackmark *mark) +{ + mark->stackp = stackp; + mark->stacknxt = stacknxt; + mark->stacknleft = stacknleft; + /* Ensure this block stays in place. */ + if (stackp != NULL && stacknxt == SPACE(stackp)) + stalloc(1); +} + + +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 = stacknxt + 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(int min) +{ + char *p; + int newlen; + char *oldspace; + int oldlen; + struct stack_block *sp; + struct stack_block *oldstackp; + + if (min < stacknleft) + min = stacknleft; + if ((unsigned int)min >= + INT_MAX / 2 - ALIGN(sizeof(struct stack_block))) + error("Out of space"); + min += stacknleft; + min += ALIGN(sizeof(struct stack_block)); + newlen = 512; + while (newlen < min) + newlen <<= 1; + oldspace = stacknxt; + oldlen = stacknleft; + + if (stackp != NULL && stacknxt == SPACE(stackp)) { + INTOFF; + oldstackp = stackp; + stackp = oldstackp->prev; + sp = ckrealloc((pointer)oldstackp, newlen); + sp->prev = stackp; + stackp = sp; + stacknxt = SPACE(sp); + stacknleft = newlen - (stacknxt - (char*)sp); + sstrend = stacknxt + stacknleft; + INTON; + } else { + newlen -= ALIGN(sizeof(struct stack_block)); + p = stalloc(newlen); + if (oldlen != 0) + memcpy(p, oldspace, oldlen); + stunalloc(p); + } +} + + + +/* + * The following routines are somewhat easier to use that 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. + */ + +static char * +growstrstackblock(int n, int min) +{ + growstackblock(min); + return stackblock() + n; +} + +char * +growstackstr(void) +{ + int len; + + len = stackblocksize(); + return (growstrstackblock(len, 0)); +} + + +/* + * Called from CHECKSTRSPACE. + */ + +char * +makestrspace(int min, char *p) +{ + int len; + + len = p - stackblock(); + return (growstrstackblock(len, min)); +} + + +char * +stputbin(const char *data, size_t len, char *p) +{ + CHECKSTRSPACE(len, p); + memcpy(p, data, len); + return (p + len); +} + +char * +stputs(const char *data, char *p) +{ + return (stputbin(data, strlen(data), p)); +} diff --git a/bin/1sh/memalloc.h b/bin/1sh/memalloc.h new file mode 100644 index 00000000..f3acc03d --- /dev/null +++ b/bin/1sh/memalloc.h @@ -0,0 +1,88 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + * + * @(#)memalloc.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/memalloc.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +#include <string.h> + +struct stackmark { + struct stack_block *stackp; + char *stacknxt; + int stacknleft; +}; + + +extern char *stacknxt; +extern int stacknleft; +extern char *sstrend; + +pointer ckmalloc(size_t); +pointer ckrealloc(pointer, int); +void ckfree(pointer); +char *savestr(const char *); +pointer stalloc(int); +void stunalloc(pointer); +char *stsavestr(const char *); +void setstackmark(struct stackmark *); +void popstackmark(struct stackmark *); +char *growstackstr(void); +char *makestrspace(int, char *); +char *stputbin(const char *data, size_t len, char *p); +char *stputs(const char *data, char *p); + + + +#define stackblock() stacknxt +#define stackblocksize() stacknleft +#define grabstackblock(n) stalloc(n) +#define STARTSTACKSTR(p) p = stackblock() +#define STPUTC(c, p) do { if (p == sstrend) p = growstackstr(); *p++ = (c); } while(0) +#define CHECKSTRSPACE(n, p) { if ((size_t)(sstrend - p) < n) p = makestrspace(n, p); } +#define USTPUTC(c, p) (*p++ = (c)) +/* + * STACKSTRNUL's use is where we want to be able to turn a stack + * (non-sentinel, character counting string) into a C string, + * and later pretend the NUL is not there. + * Note: Because of STACKSTRNUL's semantics, STACKSTRNUL cannot be used + * on a stack that will grabstackstr()ed. + */ +#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 - stackblock()) +#define ungrabstackstr(s, p) stunalloc((s)) +#define STPUTBIN(s, len, p) p = stputbin((s), (len), p) +#define STPUTS(s, p) p = stputs((s), p) diff --git a/bin/1sh/miscbltin.c b/bin/1sh/miscbltin.c new file mode 100644 index 00000000..66329850 --- /dev/null +++ b/bin/1sh/miscbltin.c @@ -0,0 +1,534 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)miscbltin.c 8.4 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/miscbltin.c 326025 2017-11-20 19:49:47Z pfg $ */ + +/* + * Miscellaneous builtins. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "shell.h" +#include "options.h" +#include "var.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "syntax.h" +#include "trap.h" + +#undef eflag + +int readcmd(int, char **); +int umaskcmd(int, char **); +int ulimitcmd(int, char **); + +/* + * The read builtin. The -r option causes backslashes to be treated like + * ordinary characters. + * + * This uses unbuffered input, which may be avoidable in some cases. + * + * Note that if IFS=' :' then read x y should work so that: + * 'a b' x='a', y='b' + * ' a b ' x='a', y='b' + * ':b' x='', y='b' + * ':' x='', y='' + * '::' x='', y='' + * ': :' x='', y='' + * ':::' x='', y='::' + * ':b c:' x='', y='b c:' + */ + +int +readcmd(int argc __unused, char **argv __unused) +{ + char **ap; + int backslash; + char c; + int rflag; + char *prompt; + const char *ifs; + char *p; + int startword; + int status; + int i; + int is_ifs; + int saveall = 0; + ptrdiff_t lastnonifs, lastnonifsws; + struct timeval tv; + char *tvptr; + fd_set ifds; + ssize_t nread; + int sig; + + rflag = 0; + prompt = NULL; + tv.tv_sec = -1; + tv.tv_usec = 0; + while ((i = nextopt("erp:t:")) != '\0') { + switch(i) { + case 'p': + prompt = shoptarg; + break; + case 'e': + break; + case 'r': + rflag = 1; + break; + case 't': + tv.tv_sec = strtol(shoptarg, &tvptr, 0); + if (tvptr == shoptarg) + error("timeout value"); + switch(*tvptr) { + case 0: + case 's': + break; + case 'h': + tv.tv_sec *= 60; + /* FALLTHROUGH */ + case 'm': + tv.tv_sec *= 60; + break; + default: + error("timeout unit"); + } + break; + } + } + if (prompt && isatty(0)) { + out2str(prompt); + flushall(); + } + if (*(ap = argptr) == NULL) + error("arg count"); + if ((ifs = bltinlookup("IFS", 1)) == NULL) + ifs = " \t\n"; + + if (tv.tv_sec >= 0) { + /* + * Wait for something to become available. + */ + FD_ZERO(&ifds); + FD_SET(0, &ifds); + status = select(1, &ifds, NULL, NULL, &tv); + /* + * If there's nothing ready, return an error. + */ + if (status <= 0) { + sig = pendingsig; + return (128 + (sig != 0 ? sig : SIGALRM)); + } + } + + status = 0; + startword = 2; + backslash = 0; + STARTSTACKSTR(p); + lastnonifs = lastnonifsws = -1; + for (;;) { + nread = read(STDIN_FILENO, &c, 1); + if (nread == -1) { + if (errno == EINTR) { + sig = pendingsig; + if (sig == 0) + continue; + status = 128 + sig; + break; + } + warning("read error: %s", strerror(errno)); + status = 2; + break; + } else if (nread != 1) { + status = 1; + break; + } + if (c == '\0') + continue; + CHECKSTRSPACE(1, p); + if (backslash) { + backslash = 0; + if (c != '\n') { + startword = 0; + lastnonifs = lastnonifsws = p - stackblock(); + USTPUTC(c, p); + } + continue; + } + if (!rflag && c == '\\') { + backslash++; + continue; + } + if (c == '\n') + break; + if (strchr(ifs, c)) + is_ifs = strchr(" \t\n", c) ? 1 : 2; + else + is_ifs = 0; + + if (startword != 0) { + if (is_ifs == 1) { + /* Ignore leading IFS whitespace */ + if (saveall) + USTPUTC(c, p); + continue; + } + if (is_ifs == 2 && startword == 1) { + /* Only one non-whitespace IFS per word */ + startword = 2; + if (saveall) { + lastnonifsws = p - stackblock(); + USTPUTC(c, p); + } + continue; + } + } + + if (is_ifs == 0) { + /* append this character to the current variable */ + startword = 0; + if (saveall) + /* Not just a spare terminator */ + saveall++; + lastnonifs = lastnonifsws = p - stackblock(); + USTPUTC(c, p); + continue; + } + + /* end of variable... */ + startword = is_ifs; + + if (ap[1] == NULL) { + /* Last variable needs all IFS chars */ + saveall++; + if (is_ifs == 2) + lastnonifsws = p - stackblock(); + USTPUTC(c, p); + continue; + } + + STACKSTRNUL(p); + setvar(*ap, stackblock(), 0); + ap++; + STARTSTACKSTR(p); + lastnonifs = lastnonifsws = -1; + } + STACKSTRNUL(p); + + /* + * Remove trailing IFS chars: always remove whitespace, don't remove + * non-whitespace unless it was naked + */ + if (saveall <= 1) + lastnonifsws = lastnonifs; + stackblock()[lastnonifsws + 1] = '\0'; + setvar(*ap, stackblock(), 0); + + /* Set any remaining args to "" */ + while (*++ap != NULL) + setvar(*ap, "", 0); + return status; +} + + + +int +umaskcmd(int argc __unused, char **argv __unused) +{ + 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 u[4], g[4], o[4]; + + i = 0; + if ((mask & S_IRUSR) == 0) + u[i++] = 'r'; + if ((mask & S_IWUSR) == 0) + u[i++] = 'w'; + if ((mask & S_IXUSR) == 0) + u[i++] = 'x'; + u[i] = '\0'; + + i = 0; + if ((mask & S_IRGRP) == 0) + g[i++] = 'r'; + if ((mask & S_IWGRP) == 0) + g[i++] = 'w'; + if ((mask & S_IXGRP) == 0) + g[i++] = 'x'; + g[i] = '\0'; + + i = 0; + if ((mask & S_IROTH) == 0) + o[i++] = 'r'; + if ((mask & S_IWOTH) == 0) + o[i++] = 'w'; + if ((mask & S_IXOTH) == 0) + o[i++] = 'x'; + o[i] = '\0'; + + out1fmt("u=%s,g=%s,o=%s\n", u, g, o); + } else { + out1fmt("%.4o\n", mask); + } + } else { + if (is_digit(*ap)) { + mask = 0; + do { + if (*ap >= '8' || *ap < '0') + error("Illegal number: %s", *argptr); + mask = (mask << 3) + (*ap - '0'); + } while (*++ap != '\0'); + umask(mask); + } else { + void *set; + INTOFF; + if ((set = setmode (ap)) == NULL) + error("Illegal number: %s", ap); + + mask = getmode (set, ~mask & 0777); + umask(~mask & 0777); + free(set); + INTON; + } + } + return 0; +} + +/* + * 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; + const char *units; + int cmd; + short factor; /* multiply by to get rlim_{cur,max} values */ + char option; +}; + +static const struct limits limits[] = { +#ifdef RLIMIT_CPU + { "cpu time", "seconds", RLIMIT_CPU, 1, 't' }, +#endif +#ifdef RLIMIT_FSIZE + { "file size", "512-blocks", RLIMIT_FSIZE, 512, 'f' }, +#endif +#ifdef RLIMIT_DATA + { "data seg size", "kbytes", RLIMIT_DATA, 1024, 'd' }, +#endif +#ifdef RLIMIT_STACK + { "stack size", "kbytes", RLIMIT_STACK, 1024, 's' }, +#endif +#ifdef RLIMIT_CORE + { "core file size", "512-blocks", RLIMIT_CORE, 512, 'c' }, +#endif +#ifdef RLIMIT_RSS + { "max memory size", "kbytes", RLIMIT_RSS, 1024, 'm' }, +#endif +#ifdef RLIMIT_MEMLOCK + { "locked memory", "kbytes", RLIMIT_MEMLOCK, 1024, 'l' }, +#endif +#ifdef RLIMIT_NPROC + { "max user processes", (char *)0, RLIMIT_NPROC, 1, 'u' }, +#endif +#ifdef RLIMIT_NOFILE + { "open files", (char *)0, RLIMIT_NOFILE, 1, 'n' }, +#endif +#ifdef RLIMIT_VMEM + { "virtual mem size", "kbytes", RLIMIT_VMEM, 1024, 'v' }, +#endif +#ifdef RLIMIT_SWAP + { "swap limit", "kbytes", RLIMIT_SWAP, 1024, 'w' }, +#endif +#ifdef RLIMIT_SBSIZE + { "socket buffer size", "bytes", RLIMIT_SBSIZE, 1, 'b' }, +#endif +#ifdef RLIMIT_NPTS + { "pseudo-terminals", (char *)0, RLIMIT_NPTS, 1, 'p' }, +#endif +#ifdef RLIMIT_KQUEUES + { "kqueues", (char *)0, RLIMIT_KQUEUES, 1, 'k' }, +#endif +#ifdef RLIMIT_UMTXP + { "umtx shared locks", (char *)0, RLIMIT_UMTXP, 1, 'o' }, +#endif + { (char *) 0, (char *)0, 0, 0, '\0' } +}; + +enum limithow { SOFT = 0x1, HARD = 0x2 }; + +static void +printlimit(enum limithow how, const struct rlimit *limit, + const struct limits *l) +{ + rlim_t val = 0; + + if (how & SOFT) + val = limit->rlim_cur; + else if (how & HARD) + val = limit->rlim_max; + if (val == RLIM_INFINITY) + out1str("unlimited\n"); + else + { + val /= l->factor; + out1fmt("%jd\n", (intmax_t)val); + } +} + +int +ulimitcmd(int argc __unused, char **argv __unused) +{ + rlim_t val = 0; + enum limithow how = SOFT | HARD; + const struct limits *l; + int set, all = 0; + int optc, what; + struct rlimit limit; + + what = 'f'; + while ((optc = nextopt("HSatfdsmcnuvlbpwko")) != '\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->name && l->option != what; l++) + ; + if (!l->name) + error("internal error (%c)", what); + + set = *argptr ? 1 : 0; + if (set) { + char *p = *argptr; + + if (all || argptr[1]) + error("too many arguments"); + if (strcmp(p, "unlimited") == 0) + val = RLIM_INFINITY; + else { + char *end; + uintmax_t uval; + + if (*p < '0' || *p > '9') + error("bad number"); + errno = 0; + uval = strtoumax(p, &end, 10); + if (errno != 0 || *end != '\0') + error("bad number"); + if (uval > UINTMAX_MAX / l->factor) + error("bad number"); + uval *= l->factor; + val = (rlim_t)uval; + if (val < 0 || (uintmax_t)val != uval || + val == RLIM_INFINITY) + error("bad number"); + } + } + if (all) { + for (l = limits; l->name; l++) { + char optbuf[40]; + if (getrlimit(l->cmd, &limit) < 0) + error("can't get limit: %s", strerror(errno)); + + if (l->units) + snprintf(optbuf, sizeof(optbuf), + "(%s, -%c) ", l->units, l->option); + else + snprintf(optbuf, sizeof(optbuf), + "(-%c) ", l->option); + out1fmt("%-18s %18s ", l->name, optbuf); + printlimit(how, &limit, l); + } + return 0; + } + + if (getrlimit(l->cmd, &limit) < 0) + error("can't get limit: %s", strerror(errno)); + if (set) { + if (how & SOFT) + limit.rlim_cur = val; + if (how & HARD) + limit.rlim_max = val; + if (setrlimit(l->cmd, &limit) < 0) + error("bad limit: %s", strerror(errno)); + } else + printlimit(how, &limit, l); + return 0; +} diff --git a/bin/1sh/mkbuiltins b/bin/1sh/mkbuiltins new file mode 100755 index 00000000..cbcf0ec1 --- /dev/null +++ b/bin/1sh/mkbuiltins @@ -0,0 +1,137 @@ +#!/bin/sh - + +#- +# Copyright (c) 1991, 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. +# +# @(#)mkbuiltins 8.2 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/mkbuiltins 328934 2018-02-06 15:41:35Z arichardson $ + +temp=`mktemp` +havehist=1 +if [ "X$1" = "X-h" ]; then + havehist=0 + shift +fi +srcdir=$1 +havejobs=0 +if grep '^#define[ ]*JOBS[ ]*1' $srcdir/shell.h > /dev/null +then havejobs=1 +fi +exec > builtins.c +cat <<\! +/* + * This file was generated by the mkbuiltins program. + */ + +#include <stdlib.h> +#include "shell.h" +#include "builtins.h" + +! +awk '/^[^#]/ {if(('$havejobs' || $2 != "-j") && ('$havehist' || $2 != "-h")) \ + print $0}' $srcdir/builtins.def | sed 's/-[hj]//' > $temp +echo 'int (*const builtinfunc[])(int, char **) = {' +awk '/^[^#]/ { printf "\t%s,\n", $1}' $temp +echo '}; + +const unsigned char builtincmd[] = {' +awk '{ for (i = 2 ; i <= NF ; i++) { + if ($i == "-s") { + spc = 1; + } else if ($i == "-n") { + # Handled later for builtins.h + continue + } else { + printf "\t\"\\%03o\\%03o%s\"\n", length($i), (spc ? 128 : 0) + NR-1, $i + spc = 0; + } + }}' $temp +echo '};' + +exec > builtins.h +cat <<\! +/* + * This file was generated by the mkbuiltins program. + */ + +#include <sys/cdefs.h> +! +tr abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ < $temp | + awk '{ printf "#define %s %d\n", $1, NR-1}' +echo ' +#define BUILTIN_SPECIAL 0x80 + +extern int (*const builtinfunc[])(int, char **); +extern const unsigned char builtincmd[]; +' +awk '{ printf "int %s(int, char **);\n", $1}' $temp + +# Build safe_builtin_always() +cat <<EOF + +static inline int +safe_builtin_always(int idx) +{ +EOF +awk ' +BEGIN { printed = 0 } +{ + for (i = 2 ; i <= NF ; i++) { + if ($i == "-s") { + continue + } else if ($i == "-n") { + nofork = 1; + } else { + if (nofork == 0) { + continue + } + if (printed == 1) { + printf " || \n\t " + } else { + printf "\tif (" + } + printf "idx == " toupper($1) + printed = 1 + nofork = 0; + # Only need to check each once + break + } + } +}' $temp + +cat << EOF +) + return (1); + return(0); +} +EOF + +rm -f $temp diff --git a/bin/1sh/mknodes.c b/bin/1sh/mknodes.c new file mode 100644 index 00000000..9ec4dfbd --- /dev/null +++ b/bin/1sh/mknodes.c @@ -0,0 +1,461 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + */ + +#if 0 +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1991, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static char sccsid[] = "@(#)mknodes.c 8.2 (Berkeley) 5/4/95"; +#endif /* not lint */ +#endif +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/mknodes.c 326025 2017-11-20 19:49:47Z pfg $ */ + +/* + * 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 <errno.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 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(FILE *); +static void error(const char *, ...) __attribute__((format(printf, 1, 2))) __attribute__((noreturn)); +static char *savestr(const char *); + + +int +main(int argc, char *argv[]) +{ + FILE *infp; + + if (argc != 3) + error("usage: mknodes file"); + if ((infp = fopen(argv[1], "r")) == NULL) + error("Can't open %s: %s", argv[1], strerror(errno)); + while (readline(infp)) { + if (line[0] == ' ' || line[0] == '\t') + parsefield(); + else if (line[0] != '\0') + parsenode(); + } + fclose(infp); + output(argv[2]); + exit(0); +} + + + +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++; +} + + +static const 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: %s", file, strerror(errno)); + if ((hfile = fopen("nodes.h", "w")) == NULL) + error("Can't create nodes.h: %s", strerror(errno)); + 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 funcdef;\n", hfile); + fputs("struct funcdef *copyfunc(union node *);\n", hfile); + fputs("union node *getfuncnode(struct funcdef *);\n", hfile); + fputs("void reffunc(struct funcdef *);\n", hfile); + fputs("void unreffunc(struct funcdef *);\n", hfile); + if (ferror(hfile)) + error("Can't write to nodes.h"); + if (fclose(hfile)) + error("Can't close nodes.h"); + + 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); + } + fclose(patfile); + if (ferror(cfile)) + error("Can't write to nodes.c"); + if (fclose(cfile)) + error("Can't close nodes.c"); +} + + + +static void +outsizes(FILE *cfile) +{ + int i; + + fprintf(cfile, "static const short nodesize[%d] = {\n", ntypes); + for (i = 0 ; i < ntypes ; i++) { + fprintf(cfile, " 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(" result->blocksize += nodesize[n->type];\n", cfile); + else { + fputs(" new = state->block;\n", cfile); + fputs(" state->block = (char *)state->block + 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, result);\n", + sp->tag, fp->name); + } else { + indent(12, cfile); + fprintf(cfile, "new->%s.%s = copynode(n->%s.%s, state);\n", + sp->tag, fp->name, sp->tag, fp->name); + } + break; + case T_NODELIST: + if (calcsize) { + indent(12, cfile); + fprintf(cfile, "sizenodelist(n->%s.%s, result);\n", + sp->tag, fp->name); + } else { + indent(12, cfile); + fprintf(cfile, "new->%s.%s = copynodelist(n->%s.%s, state);\n", + sp->tag, fp->name, sp->tag, fp->name); + } + break; + case T_STRING: + if (calcsize) { + indent(12, cfile); + fprintf(cfile, "result->stringsize += strlen(n->%s.%s) + 1;\n", + sp->tag, fp->name); + } else { + indent(12, cfile); + fprintf(cfile, "new->%s.%s = nodesavestr(n->%s.%s, state);\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(FILE *infp) +{ + 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); +} + + + +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/1sh/mksyntax.c b/bin/1sh/mksyntax.c new file mode 100644 index 00000000..b61378a9 --- /dev/null +++ b/bin/1sh/mksyntax.c @@ -0,0 +1,332 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + */ + +#if 0 +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1991, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static char sccsid[] = "@(#)mksyntax.c 8.2 (Berkeley) 5/4/95"; +#endif /* not lint */ +#endif +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/mksyntax.c 334008 2018-05-21 21:52:48Z jilles $ */ + +/* + * This program creates syntax.h and syntax.c. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "parser.h" + + +struct synclass { + const char *name; + const char *comment; +}; + +/* Syntax classes */ +static const struct synclass synclass[] = { + { "CWORD", "character is nothing special" }, + { "CNL", "newline character" }, + { "CQNL", "newline character in quotes" }, + { "CBACK", "a backslash character" }, + { "CSBACK", "a backslash character in single quotes" }, + { "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. + */ +static const 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 const char writer[] = "\ +/*\n\ + * This file was generated by the mksyntax program.\n\ + */\n\ +\n"; + + +static FILE *cfile; +static FILE *hfile; + +static void add_default(void); +static void finish(void); +static void init(const char *); +static void add(const char *, const char *); +static void output_type_macros(void); + +int +main(int argc __unused, char **argv __unused) +{ + 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 <sys/cdefs.h>\n", hfile); + fputs("#include <limits.h>\n\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); + fputs("#define SYNBASE (1 - CHAR_MIN)\n", hfile); + fputs("#define PEOF -SYNBASE\n\n", hfile); + 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 \"parser.h\"\n", cfile); + fputs("#include \"shell.h\"\n", cfile); + fputs("#include \"syntax.h\"\n\n", cfile); + + fputs("/* syntax table used when not in quotes */\n", cfile); + init("basesyntax"); + add_default(); + add("\n", "CNL"); + add("\\", "CBACK"); + add("'", "CSQUOTE"); + add("\"", "CDQUOTE"); + add("`", "CBQUOTE"); + add("$", "CVAR"); + add("}", "CENDVAR"); + add("<>();&| \t", "CSPCL"); + finish(); + + fputs("\n/* syntax table used when in double quotes */\n", cfile); + init("dqsyntax"); + add_default(); + add("\n", "CQNL"); + add("\\", "CBACK"); + add("\"", "CENDQUOTE"); + add("`", "CBQUOTE"); + add("$", "CVAR"); + add("}", "CENDVAR"); + /* ':/' for tilde expansion, '-^]' for [a\-x] pattern ranges */ + add("!*?[]=~:/-^", "CCTL"); + finish(); + + fputs("\n/* syntax table used when in single quotes */\n", cfile); + init("sqsyntax"); + add_default(); + add("\n", "CQNL"); + add("\\", "CSBACK"); + add("'", "CENDQUOTE"); + /* ':/' for tilde expansion, '-^]' for [a\-x] pattern ranges */ + add("!*?[]=~:/-^", "CCTL"); + finish(); + + fputs("\n/* syntax table used when in arithmetic */\n", cfile); + init("arisyntax"); + add_default(); + add("\n", "CQNL"); + add("\\", "CBACK"); + add("`", "CBQUOTE"); + add("\"", "CIGN"); + add("$", "CVAR"); + add("}", "CENDVAR"); + add("(", "CLP"); + add(")", "CRP"); + finish(); + + fputs("\n/* character classification table */\n", cfile); + init("is_type"); + add("0123456789", "ISDIGIT"); + add("abcdefghijklmnopqrstuvwxyz", "ISLOWER"); + add("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "ISUPPER"); + add("_", "ISUNDER"); + add("#?$!-*@", "ISSPECL"); + finish(); + + exit(0); +} + + +/* + * Output the header and declaration of a syntax table. + */ + +static void +init(const char *name) +{ + fprintf(hfile, "extern const char %s[];\n", name); + fprintf(cfile, "const char %s[SYNBASE + CHAR_MAX + 1] = {\n", name); +} + + +static void +add_one(const char *key, const char *type) +{ + fprintf(cfile, "\t[SYNBASE + %s] = %s,\n", key, type); +} + + +/* + * Add default values to the syntax table. + */ + +static void +add_default(void) +{ + add_one("PEOF", "CEOF"); + add_one("CTLESC", "CCTL"); + add_one("CTLVAR", "CCTL"); + add_one("CTLENDVAR", "CCTL"); + add_one("CTLBACKQ", "CCTL"); + add_one("CTLBACKQ + CTLQUOTE", "CCTL"); + add_one("CTLARI", "CCTL"); + add_one("CTLENDARI", "CCTL"); + add_one("CTLQUOTEMARK", "CCTL"); + add_one("CTLQUOTEEND", "CCTL"); +} + + +/* + * Output the footer of a syntax table. + */ + +static void +finish(void) +{ + fputs("};\n", cfile); +} + + +/* + * Add entries to the syntax table. + */ + +static void +add(const char *p, const char *type) +{ + for (; *p; ++p) { + char c = *p; + switch (c) { + case '\t': c = 't'; break; + case '\n': c = 'n'; break; + case '\'': c = '\''; break; + case '\\': c = '\\'; break; + + default: + fprintf(cfile, "\t[SYNBASE + '%c'] = %s,\n", c, type); + continue; + } + fprintf(cfile, "\t[SYNBASE + '\\%c'] = %s,\n", c, type); + } +} + + +/* + * Output character classification macros (e.g. is_digit). If digits are + * contiguous, we can test for them quickly. + */ + +static const char *macro[] = { + "#define is_digit(c)\t((unsigned int)((c) - '0') <= 9)", + "#define is_eof(c)\t((c) == PEOF)", + "#define is_alpha(c)\t((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER))", + "#define is_name(c)\t((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER|ISUNDER))", + "#define is_in_name(c)\t((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER|ISUNDER|ISDIGIT))", + "#define is_special(c)\t((is_type+SYNBASE)[(int)c] & (ISSPECL|ISDIGIT))", + "#define digit_val(c)\t((c) - '0')", + NULL +}; + +static void +output_type_macros(void) +{ + const char **pp; + + for (pp = macro ; *pp ; pp++) + fprintf(hfile, "%s\n", *pp); +} diff --git a/bin/1sh/mktokens b/bin/1sh/mktokens new file mode 100644 index 00000000..f0d80cc5 --- /dev/null +++ b/bin/1sh/mktokens @@ -0,0 +1,93 @@ +#!/bin/sh - + +#- +# Copyright (c) 1991, 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. +# +# @(#)mktokens 8.1 (Berkeley) 5/31/93 +# $FreeBSD: releng/12.1/bin/sh/mktokens 328934 2018-02-06 15:41:35Z arichardson $ + +# 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. + +temp=`mktemp` +cat > $temp <<\! +TEOF 1 end of file +TNL 0 newline +TSEMI 0 ";" +TBACKGND 0 "&" +TAND 0 "&&" +TOR 0 "||" +TPIPE 0 "|" +TLP 0 "(" +TRP 1 ")" +TENDCASE 1 ";;" +TFALLTHRU 1 ";&" +TREDIR 0 redirection +TWORD 0 word +TIF 0 "if" +TTHEN 1 "then" +TELSE 1 "else" +TELIF 1 "elif" +TFI 1 "fi" +TWHILE 0 "while" +TUNTIL 0 "until" +TFOR 0 "for" +TDO 1 "do" +TDONE 1 "done" +TBEGIN 0 "{" +TEND 1 "}" +TCASE 0 "case" +TESAC 1 "esac" +TNOT 0 "!" +! +nl=`wc -l $temp` +exec > token.h +awk '{print "#define " $1 " " NR-1}' $temp +echo ' +/* Array indicating which tokens mark the end of a list */ +static const char tokendlist[] = {' +awk '{print "\t" $2 ","}' $temp +echo '}; + +static const char *const tokname[] = {' +sed -e 's/"/\\"/g' \ + -e 's/[^ ]*[ ][ ]*[^ ]*[ ][ ]*\(.*\)/ "\1",/' \ + $temp +echo '}; +' +sed 's/"//g' $temp | awk ' +/TIF/{print "#define KWDOFFSET " NR-1; print ""; print "const char *const parsekwd[] = {"} +/TIF/,/neverfound/{print " \"" $3 "\","}' +echo ' 0 +};' + +rm $temp diff --git a/bin/1sh/myhistedit.h b/bin/1sh/myhistedit.h new file mode 100644 index 00000000..e9a2be2c --- /dev/null +++ b/bin/1sh/myhistedit.h @@ -0,0 +1,45 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 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. + * + * @(#)myhistedit.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/myhistedit.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +#include <histedit.h> + +extern History *hist; +extern EditLine *el; +extern int displayhist; + +void histedit(void); +void sethistfile(const char *); +void sethistsize(const char *); +void setterm(const char *); + diff --git a/bin/1sh/mystring.c b/bin/1sh/mystring.c new file mode 100644 index 00000000..ddeea4c1 --- /dev/null +++ b/bin/1sh/mystring.c @@ -0,0 +1,100 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mystring.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/mystring.c 326025 2017-11-20 19:49:47Z pfg $ */ + +/* + * String functions. + * + * equal(s1, s2) Return true if strings are equal. + * number(s) Convert a string of digits to an integer. + * is_number(s) Return true if s is a string of digits. + */ + +#include <stdlib.h> +#include "shell.h" +#include "syntax.h" +#include "error.h" +#include "mystring.h" + + +char nullstr[1]; /* zero length string */ + +/* + * equal - #defined in mystring.h + */ + + +/* + * Convert a string of digits to an integer, printing an error message on + * failure. + */ + +int +number(const char *s) +{ + if (! is_number(s)) + error("Illegal number: %s", s); + return atoi(s); +} + + + +/* + * Check for a valid number. This should be elsewhere. + */ + +int +is_number(const char *p) +{ + const char *q; + + if (*p == '\0') + return 0; + while (*p == '0') + p++; + for (q = p; *q != '\0'; q++) + if (! is_digit(*q)) + return 0; + if (q - p > 10 || + (q - p == 10 && memcmp(p, "2147483647", 10) > 0)) + return 0; + return 1; +} diff --git a/bin/1sh/mystring.h b/bin/1sh/mystring.h new file mode 100644 index 00000000..9f4960e5 --- /dev/null +++ b/bin/1sh/mystring.h @@ -0,0 +1,43 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + * + * @(#)mystring.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/mystring.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +#include <string.h> + +int number(const char *); +int is_number(const char *); + +#define equal(s1, s2) (strcmp(s1, s2) == 0) diff --git a/bin/1sh/nodes.c.pat b/bin/1sh/nodes.c.pat new file mode 100644 index 00000000..b687b570 --- /dev/null +++ b/bin/1sh/nodes.c.pat @@ -0,0 +1,193 @@ +/*- + * Copyright (c) 1991, 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. + * + * @(#)nodes.c.pat 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/nodes.c.pat 314436 2017-02-28 23:42:47Z imp $ + */ + +#include <sys/param.h> +#include <stdlib.h> +#include <stddef.h> +/* + * Routine for dealing with parsed shell commands. + */ + +#include "shell.h" +#include "nodes.h" +#include "memalloc.h" +#include "mystring.h" + + +struct nodesize { + int blocksize; /* size of structures in function */ + int stringsize; /* size of strings in node */ +}; + +struct nodecopystate { + pointer block; /* block to allocate function from */ + char *string; /* block to allocate strings from */ +}; + +%SIZES + + +static void calcsize(union node *, struct nodesize *); +static void sizenodelist(struct nodelist *, struct nodesize *); +static union node *copynode(union node *, struct nodecopystate *); +static struct nodelist *copynodelist(struct nodelist *, struct nodecopystate *); +static char *nodesavestr(const char *, struct nodecopystate *); + + +struct funcdef { + unsigned int refcount; + union node n; +}; + +/* + * Make a copy of a parse tree. + */ + +struct funcdef * +copyfunc(union node *n) +{ + struct nodesize sz; + struct nodecopystate st; + struct funcdef *fn; + + if (n == NULL) + return NULL; + sz.blocksize = offsetof(struct funcdef, n); + sz.stringsize = 0; + calcsize(n, &sz); + fn = ckmalloc(sz.blocksize + sz.stringsize); + fn->refcount = 1; + st.block = (char *)fn + offsetof(struct funcdef, n); + st.string = (char *)fn + sz.blocksize; + copynode(n, &st); + return fn; +} + + +union node * +getfuncnode(struct funcdef *fn) +{ + return fn == NULL ? NULL : &fn->n; +} + + +static void +calcsize(union node *n, struct nodesize *result) +{ + %CALCSIZE +} + + + +static void +sizenodelist(struct nodelist *lp, struct nodesize *result) +{ + while (lp) { + result->blocksize += ALIGN(sizeof(struct nodelist)); + calcsize(lp->n, result); + lp = lp->next; + } +} + + + +static union node * +copynode(union node *n, struct nodecopystate *state) +{ + union node *new; + + %COPY + return new; +} + + +static struct nodelist * +copynodelist(struct nodelist *lp, struct nodecopystate *state) +{ + struct nodelist *start; + struct nodelist **lpp; + + lpp = &start; + while (lp) { + *lpp = state->block; + state->block = (char *)state->block + + ALIGN(sizeof(struct nodelist)); + (*lpp)->n = copynode(lp->n, state); + lp = lp->next; + lpp = &(*lpp)->next; + } + *lpp = NULL; + return start; +} + + + +static char * +nodesavestr(const char *s, struct nodecopystate *state) +{ + const char *p = s; + char *q = state->string; + char *rtn = state->string; + + while ((*q++ = *p++) != '\0') + continue; + state->string = q; + return rtn; +} + + +void +reffunc(struct funcdef *fn) +{ + if (fn) + fn->refcount++; +} + + +/* + * Decrement the reference count of a function definition, freeing it + * if it falls to 0. + */ + +void +unreffunc(struct funcdef *fn) +{ + if (fn) { + fn->refcount--; + if (fn->refcount > 0) + return; + ckfree(fn); + } +} diff --git a/bin/1sh/nodetypes b/bin/1sh/nodetypes new file mode 100644 index 00000000..51facbe8 --- /dev/null +++ b/bin/1sh/nodetypes @@ -0,0 +1,145 @@ +#- +# Copyright (c) 1991, 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. +# +# @(#)nodetypes 8.2 (Berkeley) 5/4/95 +# $FreeBSD: releng/12.1/bin/sh/nodetypes 314436 2017-02-28 23:42:47Z imp $ + +# 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. + +NSEMI nbinary # two commands separated by a semicolon + type int + ch1 nodeptr # the first child + ch2 nodeptr # the second child + +NCMD ncmd # a simple command + type int + 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 compex command) + type 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 + +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 + args nodeptr # for var in args + body nodeptr # do body; done + var string # the for variable + +NCASE ncase # a case statement + type int + expr nodeptr # the word to switch on + cases nodeptr # the list of cases (NCLIST nodes) + +NCLIST nclist # a case ending with ;; + 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 + +NCLISTFALLTHRU nclist # a case ending with ;& + +NDEFUN narg # define a function. The "next" field contains + # the body of the function. + +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 +NFROM nfile # fd< fname +NFROMTO nfile # fd<> fname +NAPPEND nfile # fd>> fname +NCLOBBER nfile # fd>| fname + type int + fd int # file descriptor being redirected + next nodeptr # next redirection in list + 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 + fd int # file descriptor being redirected + next nodeptr # next redirection in list + dupfd int # file descriptor to duplicate + vname nodeptr # file name if fd>&$var + + +NHERE nhere # fd<<\! +NXHERE nhere # fd<<! + type int + fd int # file descriptor being redirected + next nodeptr # next redirection in list + doc nodeptr # input to command (NARG node) + expdoc temp const char *expdoc # actual document (for NXHERE) + +NNOT nnot # ! command (actually pipeline) + type int + com nodeptr diff --git a/bin/1sh/options.c b/bin/1sh/options.c new file mode 100644 index 00000000..a1bb57ca --- /dev/null +++ b/bin/1sh/options.c @@ -0,0 +1,594 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)options.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/options.c 326025 2017-11-20 19:49:47Z pfg $ */ + +#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" +#include "builtins.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + +char *arg0; /* value of $0 */ +struct shparam shellparam; /* current positional parameters */ +char **argptr; /* argument list for builtin commands */ +char *shoptarg; /* set by nextopt (like getopt) */ +char *nextopt_optptr; /* used by nextopt */ + +char *minusc; /* argument to -c option */ + + +static void options(int); +static void minus_o(char *, int); +static void setoption(int, int); +static void setoptionbyindex(int, int); +static void setparam(int, char **); +static int getopts(char *, char *, char **, char ***, char **); + + +/* + * Process the shell command line arguments. + */ + +void +procargs(int argc, char **argv) +{ + int i; + char *scriptname; + + argptr = argv; + if (argc > 0) + argptr++; + for (i = 0; i < NOPTS; i++) + optval[i] = 2; + privileged = (getuid() != geteuid() || getgid() != getegid()); + options(1); + if (*argptr == NULL && minusc == NULL) + sflag = 1; + if (iflag != 0 && sflag == 1 && isatty(0) && isatty(1)) { + iflag = 1; + if (Eflag == 2) + Eflag = 1; + } + if (mflag == 2) + mflag = iflag; + for (i = 0; i < NOPTS; i++) + if (optval[i] == 2) + optval[i] = 0; + arg0 = argv[0]; + if (sflag == 0 && minusc == NULL) { + scriptname = *argptr++; + setinputfile(scriptname, 0); + commandname = arg0 = scriptname; + } + /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */ + if (argptr && minusc && *argptr) + arg0 = *argptr++; + + shellparam.p = argptr; + shellparam.reset = 1; + /* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */ + while (*argptr) { + shellparam.nparam++; + argptr++; + } + optschanged(); +} + + +void +optschanged(void) +{ + setinteractive(); +#ifndef NO_HISTORY + histedit(); +#endif + setjobctl(mflag); +} + +/* + * Process shell options. The global variable argptr contains a pointer + * to the argument list; we advance it past the options. + * If cmdline is true, process the shell's argv; otherwise, process arguments + * to the set special builtin. + */ + +static void +options(int cmdline) +{ + char *kp, *p; + int val; + int c; + + if (cmdline) + minusc = NULL; + while ((p = *argptr) != NULL) { + argptr++; + if ((c = *p++) == '-') { + val = 1; + /* A "-" or "--" terminates options */ + if (p[0] == '\0') + goto end_options1; + if (p[0] == '-' && p[1] == '\0') + goto end_options2; + /** + * For the benefit of `#!' lines in shell scripts, + * treat a string of '-- *#.*' the same as '--'. + * This is needed so that a script starting with: + * #!/bin/sh -- # -*- perl -*- + * will continue to work after a change is made to + * kern/imgact_shell.c to NOT token-ize the options + * specified on a '#!' line. A bit of a kludge, + * but that trick is recommended in documentation + * for some scripting languages, and we might as + * well continue to support it. + */ + if (p[0] == '-') { + kp = p + 1; + while (*kp == ' ' || *kp == '\t') + kp++; + if (*kp == '#' || *kp == '\0') + goto end_options2; + } + } else if (c == '+') { + val = 0; + } else { + argptr--; + break; + } + while ((c = *p++) != '\0') { + if (c == 'c' && cmdline) { + char *q; + + q = *argptr++; + if (q == NULL || minusc != NULL) + error("Bad -c option"); + minusc = q; + } else if (c == 'o') { + minus_o(*argptr, val); + if (*argptr) + argptr++; + } else + setoption(c, val); + } + } + return; + + /* When processing `set', a single "-" means turn off -x and -v */ +end_options1: + if (!cmdline) { + xflag = vflag = 0; + return; + } + + /* + * When processing `set', a "--" means the remaining arguments + * replace the positional parameters in the active shell. If + * there are no remaining options, then all the positional + * parameters are cleared (equivalent to doing ``shift $#''). + */ +end_options2: + if (!cmdline) { + if (*argptr == NULL) + setparam(0, argptr); + return; + } + + /* + * At this point we are processing options given to 'sh' on a command + * line. If an end-of-options marker ("-" or "--") is followed by an + * arg of "#", then skip over all remaining arguments. Some scripting + * languages (e.g.: perl) document that /bin/sh will implement this + * behavior, and they recommend that users take advantage of it to + * solve certain issues that can come up when writing a perl script. + * Yes, this feature is in /bin/sh to help users write perl scripts. + */ + p = *argptr; + if (p != NULL && p[0] == '#' && p[1] == '\0') { + while (*argptr != NULL) + argptr++; + /* We need to keep the final argument */ + argptr--; + } +} + +static void +minus_o(char *name, int val) +{ + int i; + const unsigned char *on; + size_t len; + + if (name == NULL) { + if (val) { + /* "Pretty" output. */ + out1str("Current option settings\n"); + for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1) + out1fmt("%-16.*s%s\n", *on, on + 1, + optval[i] ? "on" : "off"); + } else { + /* Output suitable for re-input to shell. */ + for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1) + out1fmt("%s %co %.*s%s", + i % 6 == 0 ? "set" : "", + optval[i] ? '-' : '+', + *on, on + 1, + i % 6 == 5 || i == NOPTS - 1 ? "\n" : ""); + } + } else { + len = strlen(name); + for (i = 0, on = optname; i < NOPTS; i++, on += *on + 1) + if (*on == len && memcmp(on + 1, name, len) == 0) { + setoptionbyindex(i, val); + return; + } + error("Illegal option -o %s", name); + } +} + + +static void +setoptionbyindex(int idx, int val) +{ + if (&optval[idx] == &privileged && !val && privileged) { + if (setgid(getgid()) == -1) + error("setgid"); + if (setuid(getuid()) == -1) + error("setuid"); + } + optval[idx] = val; + if (val) { + /* #%$ hack for ksh semantics */ + if (&optval[idx] == &Vflag) + Eflag = 0; + else if (&optval[idx] == &Eflag) + Vflag = 0; + } +} + +static void +setoption(int flag, int val) +{ + int i; + + for (i = 0; i < NSHORTOPTS; i++) + if (optletter[i] == flag) { + setoptionbyindex(i, val); + return; + } + error("Illegal option -%c", flag); +} + + +/* + * Set the shell parameters. + */ + +static void +setparam(int argc, char **argv) +{ + char **newparam; + char **ap; + + ap = newparam = ckmalloc((argc + 1) * sizeof *ap); + while (*argv) { + *ap++ = savestr(*argv++); + } + *ap = NULL; + freeparam(&shellparam); + shellparam.malloc = 1; + shellparam.nparam = argc; + shellparam.p = newparam; + shellparam.optp = NULL; + shellparam.reset = 1; + shellparam.optnext = NULL; +} + + +/* + * Free the list of positional parameters. + */ + +void +freeparam(struct shparam *param) +{ + char **ap; + + if (param->malloc) { + for (ap = param->p ; *ap ; ap++) + ckfree(*ap); + ckfree(param->p); + } + if (param->optp) { + for (ap = param->optp ; *ap ; ap++) + ckfree(*ap); + ckfree(param->optp); + } +} + + + +/* + * The shift builtin command. + */ + +int +shiftcmd(int argc, char **argv) +{ + int i, n; + + n = 1; + if (argc > 1) + n = number(argv[1]); + if (n > shellparam.nparam) + return 1; + INTOFF; + shellparam.nparam -= n; + if (shellparam.malloc) + for (i = 0; i < n; i++) + ckfree(shellparam.p[i]); + memmove(shellparam.p, shellparam.p + n, + (shellparam.nparam + 1) * sizeof(shellparam.p[0])); + shellparam.reset = 1; + INTON; + return 0; +} + + + +/* + * The set builtin command. + */ + +int +setcmd(int argc, char **argv) +{ + if (argc == 1) + return showvarscmd(argc, argv); + INTOFF; + options(0); + optschanged(); + if (*argptr != NULL) { + setparam(argc - (argptr - argv), argptr); + } + INTON; + return 0; +} + + +void +getoptsreset(const char *value) +{ + while (*value == '0') + value++; + if (strcmp(value, "1") == 0) + shellparam.reset = 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 = NULL, **ap; + int i; + + if (argc < 3) + error("usage: getopts optstring var [arg]"); + + if (shellparam.reset == 1) { + INTOFF; + if (shellparam.optp) { + for (ap = shellparam.optp ; *ap ; ap++) + ckfree(*ap); + ckfree(shellparam.optp); + shellparam.optp = NULL; + } + if (argc > 3) { + shellparam.optp = ckmalloc((argc - 2) * sizeof *ap); + memset(shellparam.optp, '\0', (argc - 2) * sizeof *ap); + for (i = 0; i < argc - 3; i++) + shellparam.optp[i] = savestr(argv[i + 3]); + } + INTON; + optbase = argc == 3 ? shellparam.p : shellparam.optp; + shellparam.optnext = optbase; + shellparam.optptr = NULL; + shellparam.reset = 0; + } else + optbase = shellparam.optp ? shellparam.optp : shellparam.p; + + return getopts(argv[1], argv[2], optbase, &shellparam.optnext, + &shellparam.optptr); +} + +static int +getopts(char *optstr, char *optvar, char **optfirst, char ***optnext, + char **optptr) +{ + char *p, *q; + char c = '?'; + int done = 0; + int ind = 0; + int err = 0; + char s[10]; + const char *newoptarg = NULL; + + if ((p = *optptr) == NULL || *p == '\0') { + /* Current word is done, advance */ + if (*optnext == NULL) + return 1; + p = **optnext; + if (p == NULL || *p != '-' || *++p == '\0') { +atend: + ind = *optnext - optfirst + 1; + *optnext = NULL; + 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'; + newoptarg = s; + } + else + out2fmt_flush("Illegal option -%c\n", c); + c = '?'; + goto out; + } + if (*++q == ':') + q++; + } + + if (*++q == ':') { + if (*p == '\0' && (p = **optnext) == NULL) { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + newoptarg = s; + c = ':'; + } + else { + out2fmt_flush("No arg for -%c option\n", c); + c = '?'; + } + goto out; + } + + if (p == **optnext) + (*optnext)++; + newoptarg = p; + p = NULL; + } + +out: + if (*optnext != NULL) + ind = *optnext - optfirst + 1; + *optptr = p; + if (newoptarg != NULL) + err |= setvarsafe("OPTARG", newoptarg, 0); + else { + INTOFF; + err |= unsetvar("OPTARG"); + INTON; + } + fmtstr(s, sizeof(s), "%d", ind); + err |= setvarsafe("OPTIND", s, VNOFUNC); + s[0] = c; + s[1] = '\0'; + err |= setvarsafe(optvar, s, 0); + if (err) { + *optnext = NULL; + *optptr = NULL; + flushall(); + exraise(EXERROR); + } + return done; +} + +/* + * 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 returns the option, or '\0' on + * end of input. + */ + +int +nextopt(const char *optstring) +{ + char *p; + const char *q; + char c; + + if ((p = nextopt_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') + error("Illegal option -%c", c); + if (*++q == ':') + q++; + } + if (*++q == ':') { + if (*p == '\0' && (p = *argptr++) == NULL) + error("No arg for -%c option", c); + shoptarg = p; + p = NULL; + } + nextopt_optptr = p; + return c; +} diff --git a/bin/1sh/options.h b/bin/1sh/options.h new file mode 100644 index 00000000..007ea312 --- /dev/null +++ b/bin/1sh/options.h @@ -0,0 +1,117 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + * + * @(#)options.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/options.h 345487 2019-03-24 22:10:26Z jilles $ + */ + +struct shparam { + int nparam; /* # of positional parameters (without $0) */ + unsigned char malloc; /* if parameter list dynamically allocated */ + unsigned char reset; /* if getopts has been reset */ + char **p; /* parameter list */ + char **optp; /* parameter list for getopts */ + char **optnext; /* next parameter to be processed by getopts */ + char *optptr; /* used by getopts */ +}; + + + +#define eflag optval[0] +#define fflag optval[1] +#define Iflag optval[2] +#define iflag optval[3] +#define mflag optval[4] +#define nflag optval[5] +#define sflag optval[6] +#define xflag optval[7] +#define vflag optval[8] +#define Vflag optval[9] +#define Eflag optval[10] +#define Cflag optval[11] +#define aflag optval[12] +#define bflag optval[13] +#define uflag optval[14] +#define privileged optval[15] +#define Tflag optval[16] +#define Pflag optval[17] +#define hflag optval[18] +#define nologflag optval[19] +#define pipefailflag optval[20] + +#define NSHORTOPTS 19 +#define NOPTS 21 + +extern char optval[NOPTS]; +extern const char optletter[NSHORTOPTS]; +#ifdef DEFINE_OPTIONS +char optval[NOPTS]; +const char optletter[NSHORTOPTS] = "efIimnsxvVECabupTPh"; +static const unsigned char optname[] = + "\007errexit" + "\006noglob" + "\011ignoreeof" + "\013interactive" + "\007monitor" + "\006noexec" + "\005stdin" + "\006xtrace" + "\007verbose" + "\002vi" + "\005emacs" + "\011noclobber" + "\011allexport" + "\006notify" + "\007nounset" + "\012privileged" + "\012trapsasync" + "\010physical" + "\010trackall" + "\005nolog" + "\010pipefail" +; +#endif + + +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 *shoptarg; /* set by nextopt */ +extern char *nextopt_optptr; /* used by nextopt */ + +void procargs(int, char **); +void optschanged(void); +void freeparam(struct shparam *); +int nextopt(const char *); +void getoptsreset(const char *); diff --git a/bin/1sh/output.c b/bin/1sh/output.c new file mode 100644 index 00000000..af93ec6d --- /dev/null +++ b/bin/1sh/output.c @@ -0,0 +1,376 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)output.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/output.c 345613 2019-03-27 21:53:44Z jilles $ */ + +/* + * 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. + */ + +#include <stdio.h> /* defines BUFSIZ */ +#include <string.h> +#include <stdarg.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <wchar.h> +#include <wctype.h> + +#include "shell.h" +#include "syntax.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "var.h" + + +#define OUTBUFSIZ BUFSIZ +#define MEM_OUT -2 /* output to dynamically allocated memory */ +#define OUTPUT_ERR 01 /* error occurred on output */ + +static int doformat_wr(void *, const char *, int); + +struct output output = {NULL, NULL, NULL, OUTBUFSIZ, 1, 0}; +struct output errout = {NULL, NULL, NULL, 256, 2, 0}; +struct output memout = {NULL, NULL, NULL, 64, MEM_OUT, 0}; +struct output *out1 = &output; +struct output *out2 = &errout; + +void +outcslow(int c, struct output *file) +{ + outc(c, file); +} + +void +out1str(const char *p) +{ + outstr(p, out1); +} + +void +out1qstr(const char *p) +{ + outqstr(p, out1); +} + +void +out2str(const char *p) +{ + outstr(p, out2); +} + +void +out2qstr(const char *p) +{ + outqstr(p, out2); +} + +void +outstr(const char *p, struct output *file) +{ + outbin(p, strlen(p), file); +} + +static void +byteseq(int ch, struct output *file) +{ + char seq[4]; + + seq[0] = '\\'; + seq[1] = (ch >> 6 & 0x3) + '0'; + seq[2] = (ch >> 3 & 0x7) + '0'; + seq[3] = (ch & 0x7) + '0'; + outbin(seq, 4, file); +} + +static void +outdqstr(const char *p, struct output *file) +{ + const char *end; + mbstate_t mbs; + size_t clen; + wchar_t wc; + + memset(&mbs, '\0', sizeof(mbs)); + end = p + strlen(p); + outstr("$'", file); + while ((clen = mbrtowc(&wc, p, end - p + 1, &mbs)) != 0) { + if (clen == (size_t)-2) { + while (p < end) + byteseq(*p++, file); + break; + } + if (clen == (size_t)-1) { + memset(&mbs, '\0', sizeof(mbs)); + byteseq(*p++, file); + continue; + } + if (wc == L'\n') + outcslow('\n', file), p++; + else if (wc == L'\r') + outstr("\\r", file), p++; + else if (wc == L'\t') + outstr("\\t", file), p++; + else if (!iswprint(wc)) { + for (; clen > 0; clen--) + byteseq(*p++, file); + } else { + if (wc == L'\'' || wc == L'\\') + outcslow('\\', file); + outbin(p, clen, file); + p += clen; + } + } + outcslow('\'', file); +} + +/* Like outstr(), but quote for re-input into the shell. */ +void +outqstr(const char *p, struct output *file) +{ + int i; + + if (p[0] == '\0') { + outstr("''", file); + return; + } + for (i = 0; p[i] != '\0'; i++) { + if ((p[i] > '\0' && p[i] < ' ' && p[i] != '\n') || + (p[i] & 0x80) != 0 || p[i] == '\'') { + outdqstr(p, file); + return; + } + } + + if (p[strcspn(p, "|&;<>()$`\\\" \n*?[~#=")] == '\0' || + strcmp(p, "[") == 0) { + outstr(p, file); + return; + } + + outcslow('\'', file); + outstr(p, file); + outcslow('\'', file); +} + +void +outbin(const void *data, size_t len, struct output *file) +{ + const char *p; + + p = data; + while (len-- > 0) + outc(*p++, file); +} + +void +emptyoutbuf(struct output *dest) +{ + int offset, newsize; + + if (dest->buf == NULL) { + INTOFF; + dest->buf = ckmalloc(dest->bufsize); + dest->nextc = dest->buf; + dest->bufend = dest->buf + dest->bufsize; + INTON; + } else if (dest->fd == MEM_OUT) { + offset = dest->nextc - dest->buf; + newsize = dest->bufsize << 1; + INTOFF; + dest->buf = ckrealloc(dest->buf, newsize); + dest->bufsize = newsize; + dest->bufend = dest->buf + newsize; + dest->nextc = dest->buf + offset; + INTON; + } else { + flushout(dest); + } +} + + +void +flushall(void) +{ + flushout(&output); + flushout(&errout); +} + + +void +flushout(struct output *dest) +{ + + if (dest->buf == NULL || dest->nextc == dest->buf || dest->fd < 0) + return; + if (xwrite(dest->fd, dest->buf, dest->nextc - dest->buf) < 0) + dest->flags |= OUTPUT_ERR; + dest->nextc = dest->buf; +} + + +void +freestdout(void) +{ + output.nextc = output.buf; +} + + +int +outiserror(struct output *file) +{ + return (file->flags & OUTPUT_ERR); +} + + +void +outclearerror(struct output *file) +{ + file->flags &= ~OUTPUT_ERR; +} + + +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); +} + +void +out2fmt_flush(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(out2, fmt, ap); + va_end(ap); + flushout(out2); +} + +void +fmtstr(char *outbuf, int length, const char *fmt, ...) +{ + va_list ap; + + INTOFF; + va_start(ap, fmt); + vsnprintf(outbuf, length, fmt, ap); + va_end(ap); + INTON; +} + +static int +doformat_wr(void *cookie, const char *buf, int len) +{ + struct output *o; + + o = (struct output *)cookie; + outbin(buf, len, o); + + return (len); +} + +void +doformat(struct output *dest, const char *f, va_list ap) +{ + FILE *fp; + + if ((fp = fwopen(dest, doformat_wr)) != NULL) { + vfprintf(fp, f, ap); + fclose(fp); + } +} + +FILE * +out1fp(void) +{ + return fwopen(out1, doformat_wr); +} + +/* + * Version of write which resumes after a signal is caught. + */ + +int +xwrite(int fd, const char *buf, int nbytes) +{ + int ntry; + int i; + int n; + + n = nbytes; + ntry = 0; + for (;;) { + i = write(fd, buf, n); + if (i > 0) { + if ((n -= i) <= 0) + return nbytes; + buf += i; + ntry = 0; + } else if (i == 0) { + if (++ntry > 10) + return nbytes - n; + } else if (errno != EINTR) { + return -1; + } + } +} diff --git a/bin/1sh/output.h b/bin/1sh/output.h new file mode 100644 index 00000000..8d5ded6f --- /dev/null +++ b/bin/1sh/output.h @@ -0,0 +1,87 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + * + * @(#)output.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/output.h 345613 2019-03-27 21:53:44Z jilles $ + */ + +#ifndef OUTPUT_INCL + +#include <stdarg.h> +#include <stddef.h> +#include <stdio.h> + +struct output { + char *nextc; + char *bufend; + char *buf; + int bufsize; + short fd; + short flags; +}; + +extern struct output output; /* to fd 1 */ +extern struct output errout; /* to fd 2 */ +extern struct output memout; +extern struct output *out1; /* &memout if backquote, otherwise &output */ +extern struct output *out2; /* &memout if backquote with 2>&1, otherwise + &errout */ + +void outcslow(int, struct output *); +void out1str(const char *); +void out1qstr(const char *); +void out2str(const char *); +void out2qstr(const char *); +void outstr(const char *, struct output *); +void outqstr(const char *, struct output *); +void outbin(const void *, size_t, struct output *); +void emptyoutbuf(struct output *); +void flushall(void); +void flushout(struct output *); +void freestdout(void); +int outiserror(struct output *); +void outclearerror(struct output *); +void outfmt(struct output *, const char *, ...) __attribute__((format(printf, 2, 3))); +void out1fmt(const char *, ...) __attribute__((format(printf, 1, 2))); +void out2fmt_flush(const char *, ...) __attribute__((format(printf, 1, 2))); +void fmtstr(char *, int, const char *, ...) __attribute__((format(printf, 3, 4))); +void doformat(struct output *, const char *, va_list) __attribute__((format(printf, 2, 0))); +FILE *out1fp(void); +int xwrite(int, const char *, int); + +#define outc(c, file) ((file)->nextc == (file)->bufend ? (emptyoutbuf(file), *(file)->nextc++ = (c)) : (*(file)->nextc++ = (c))) +#define out1c(c) outc(c, out1); +#define out2c(c) outcslow(c, out2); + +#define OUTPUT_INCL +#endif diff --git a/bin/1sh/parser.c b/bin/1sh/parser.c new file mode 100644 index 00000000..b2f761c8 --- /dev/null +++ b/bin/1sh/parser.c @@ -0,0 +1,2182 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)parser.c 8.7 (Berkeley) 5/16/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/parser.c 334008 2018-05-21 21:52:48Z jilles $ */ + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> + +#include "shell.h" +#include "parser.h" +#include "nodes.h" +#include "expand.h" /* defines rmescapes() */ +#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 "eval.h" +#include "exec.h" /* to check for special builtins */ +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + +/* + * Shell command parser. + */ + +#define PROMPTLEN 128 + +/* values of checkkwd variable */ +#define CHKALIAS 0x1 +#define CHKKWD 0x2 +#define CHKNL 0x4 + +/* values returned by readtoken */ +#include "token.h" + + + +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 parser_temp { + struct parser_temp *next; + void *data; +}; + + +static struct heredoc *heredoclist; /* list of here documents to read */ +static int doprompt; /* if set, prompt the user */ +static int needprompt; /* true if interactive and at start of line */ +static int lasttoken; /* last token read */ +static int tokpushback; /* last token pushed back */ +static char *wordtext; /* text of last word returned by readtoken */ +static int checkkwd; +static struct nodelist *backquotelist; +static union node *redirnode; +static struct heredoc *heredoc; +static int quoteflag; /* set if (part of) last token was quoted */ +static int startlinno; /* line # where last token started */ +static int funclinno; /* line # where the current function started */ +static struct parser_temp *parser_temp; + +#define NOEOFMARK ((const char *)&heredoclist) + + +static union node *list(int); +static union node *andor(void); +static union node *pipeline(void); +static union node *command(void); +static union node *simplecmd(union node **, union node *); +static union node *makename(void); +static union node *makebinary(int type, union node *n1, union node *n2); +static void parsefname(void); +static void parseheredoc(void); +static int peektoken(void); +static int readtoken(void); +static int xxreadtoken(void); +static int readtoken1(int, const char *, const char *, int); +static int noexpand(char *); +static void consumetoken(int); +static void synexpect(int) __attribute__((noreturn)); +static void synerror(const char *) __attribute__((noreturn)); +static void setprompt(int); +static char *expandprompt(const char *); +static int pgetc_linecont(void); + + +static void * +parser_temp_alloc(size_t len) +{ + struct parser_temp *t; + + INTOFF; + t = ckmalloc(sizeof(*t)); + t->data = NULL; + t->next = parser_temp; + parser_temp = t; + t->data = ckmalloc(len); + INTON; + return t->data; +} + + +static void * +parser_temp_realloc(void *ptr, size_t len) +{ + struct parser_temp *t; + + INTOFF; + t = parser_temp; + if (ptr != t->data) + error("bug: parser_temp_realloc misused"); + t->data = ckrealloc(t->data, len); + INTON; + return t->data; +} + + +static void +parser_temp_free_upto(void *ptr) +{ + struct parser_temp *t; + int done = 0; + + INTOFF; + while (parser_temp != NULL && !done) { + t = parser_temp; + parser_temp = t->next; + done = t->data == ptr; + ckfree(t->data); + ckfree(t); + } + INTON; + if (!done) + error("bug: parser_temp_free_upto misused"); +} + + +static void +parser_temp_free_all(void) +{ + struct parser_temp *t; + + INTOFF; + while (parser_temp != NULL) { + t = parser_temp; + parser_temp = t->next; + ckfree(t->data); + ckfree(t); + } + INTON; +} + + +/* + * 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) +{ + int t; + + /* This assumes the parser is not re-entered, + * which could happen if we add command substitution on PS1/PS2. + */ + parser_temp_free_all(); + heredoclist = NULL; + + tokpushback = 0; + checkkwd = 0; + doprompt = interact; + if (doprompt) + setprompt(1); + else + setprompt(0); + needprompt = 0; + t = readtoken(); + if (t == TEOF) + return NEOF; + if (t == TNL) + return NULL; + tokpushback++; + return list(1); +} + + +/* + * Read and parse words for wordexp. + * Returns a list of NARG nodes; NULL if there are no words. + */ +union node * +parsewordexp(void) +{ + union node *n, *first = NULL, **pnext; + int t; + + /* This assumes the parser is not re-entered, + * which could happen if we add command substitution on PS1/PS2. + */ + parser_temp_free_all(); + heredoclist = NULL; + + tokpushback = 0; + checkkwd = 0; + doprompt = 0; + setprompt(0); + needprompt = 0; + pnext = &first; + while ((t = readtoken()) != TEOF) { + if (t != TWORD) + synexpect(TWORD); + n = makename(); + *pnext = n; + pnext = &n->narg.next; + } + return first; +} + + +static union node * +list(int nlflag) +{ + union node *ntop, *n1, *n2, *n3; + int tok; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (!nlflag && tokendlist[peektoken()]) + return NULL; + ntop = n1 = NULL; + for (;;) { + n2 = andor(); + tok = readtoken(); + if (tok == TBACKGND) { + if (n2 != NULL && n2->type == NPIPE) { + n2->npipe.backgnd = 1; + } else if (n2 != NULL && n2->type == NREDIR) { + n2->type = NBACKGND; + } else { + n3 = (union node *)stalloc(sizeof (struct nredir)); + n3->type = NBACKGND; + n3->nredir.n = n2; + n3->nredir.redirect = NULL; + n2 = n3; + } + } + if (ntop == NULL) + ntop = n2; + else if (n1 == NULL) { + n1 = makebinary(NSEMI, ntop, n2); + ntop = n1; + } + else { + n3 = makebinary(NSEMI, n1->nbinary.ch2, n2); + n1->nbinary.ch2 = n3; + n1 = n3; + } + switch (tok) { + case TBACKGND: + case TSEMI: + tok = readtoken(); + /* FALLTHROUGH */ + case TNL: + if (tok == TNL) { + parseheredoc(); + if (nlflag) + return ntop; + } else if (tok == TEOF && nlflag) { + parseheredoc(); + return ntop; + } else { + tokpushback++; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (!nlflag && tokendlist[peektoken()]) + return ntop; + break; + case TEOF: + if (heredoclist) + parseheredoc(); + else + pungetc(); /* push back EOF on input */ + return ntop; + default: + if (nlflag) + synexpect(-1); + tokpushback++; + return ntop; + } + } +} + + + +static union node * +andor(void) +{ + union node *n; + int t; + + n = pipeline(); + for (;;) { + if ((t = readtoken()) == TAND) { + t = NAND; + } else if (t == TOR) { + t = NOR; + } else { + tokpushback++; + return n; + } + n = makebinary(t, n, pipeline()); + } +} + + + +static union node * +pipeline(void) +{ + union node *n1, *n2, *pipenode; + struct nodelist *lp, *prev; + int negate, t; + + negate = 0; + checkkwd = CHKNL | CHKKWD | CHKALIAS; + TRACE(("pipeline: entered\n")); + while (readtoken() == TNOT) + negate = !negate; + 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; + t = readtoken(); + tokpushback++; + if (t == TNOT) + lp->n = pipeline(); + else + 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; + int t; + int is_subshell; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + is_subshell = 0; + redir = NULL; + n1 = NULL; + rpp = &redir; + + /* Check for redirection which may precede command */ + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); + } + tokpushback++; + + switch (readtoken()) { + case TIF: + n1 = (union node *)stalloc(sizeof (struct nif)); + n1->type = NIF; + if ((n1->nif.test = list(0)) == NULL) + synexpect(-1); + consumetoken(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; + if ((n2->nif.test = list(0)) == NULL) + synexpect(-1); + consumetoken(TTHEN); + n2->nif.ifpart = list(0); + } + if (lasttoken == TELSE) + n2->nif.elsepart = list(0); + else { + n2->nif.elsepart = NULL; + tokpushback++; + } + consumetoken(TFI); + checkkwd = CHKKWD | CHKALIAS; + break; + case TWHILE: + case TUNTIL: + t = lasttoken; + if ((n1 = list(0)) == NULL) + synexpect(-1); + consumetoken(TDO); + n1 = makebinary((t == TWHILE)? NWHILE : NUNTIL, n1, list(0)); + consumetoken(TDONE); + checkkwd = CHKKWD | CHKALIAS; + 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.var = wordtext; + while (readtoken() == TNL) + ; + if (lasttoken == TWORD && ! quoteflag && equal(wordtext, "in")) { + app = ≈ + while (readtoken() == TWORD) { + n2 = makename(); + *app = n2; + app = &n2->narg.next; + } + *app = NULL; + n1->nfor.args = ap; + if (lasttoken != TNL && lasttoken != TSEMI) + synexpect(-1); + } else { + static char argvars[5] = { + CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' + }; + n2 = (union node *)stalloc(sizeof (struct narg)); + n2->type = NARG; + n2->narg.text = argvars; + 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 != TNL && lasttoken != TSEMI) + tokpushback++; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if ((t = readtoken()) == TDO) + t = TDONE; + else if (t == TBEGIN) + t = TEND; + else + synexpect(-1); + n1->nfor.body = list(0); + consumetoken(t); + checkkwd = CHKKWD | CHKALIAS; + break; + case TCASE: + n1 = (union node *)stalloc(sizeof (struct ncase)); + n1->type = NCASE; + consumetoken(TWORD); + n1->ncase.expr = makename(); + while (readtoken() == TNL); + if (lasttoken != TWORD || ! equal(wordtext, "in")) + synerror("expecting \"in\""); + cpp = &n1->ncase.cases; + checkkwd = CHKNL | CHKKWD, readtoken(); + while (lasttoken != TESAC) { + *cpp = cp = (union node *)stalloc(sizeof (struct nclist)); + cp->type = NCLIST; + app = &cp->nclist.pattern; + if (lasttoken == TLP) + readtoken(); + for (;;) { + *app = ap = makename(); + checkkwd = CHKNL | CHKKWD; + if (readtoken() != TPIPE) + break; + app = &ap->narg.next; + readtoken(); + } + ap->narg.next = NULL; + if (lasttoken != TRP) + synexpect(TRP); + cp->nclist.body = list(0); + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if ((t = readtoken()) != TESAC) { + if (t == TENDCASE) + ; + else if (t == TFALLTHRU) + cp->type = NCLISTFALLTHRU; + else + synexpect(TENDCASE); + checkkwd = CHKNL | CHKKWD, readtoken(); + } + cpp = &cp->nclist.next; + } + *cpp = NULL; + checkkwd = CHKKWD | CHKALIAS; + break; + case TLP: + n1 = (union node *)stalloc(sizeof (struct nredir)); + n1->type = NSUBSHELL; + n1->nredir.n = list(0); + n1->nredir.redirect = NULL; + consumetoken(TRP); + checkkwd = CHKKWD | CHKALIAS; + is_subshell = 1; + break; + case TBEGIN: + n1 = list(0); + consumetoken(TEND); + checkkwd = CHKKWD | CHKALIAS; + break; + /* A simple command must have at least one redirection or word. */ + case TBACKGND: + case TSEMI: + case TAND: + case TOR: + case TPIPE: + case TENDCASE: + case TFALLTHRU: + case TEOF: + case TNL: + case TRP: + if (!redir) + synexpect(-1); + case TWORD: + tokpushback++; + n1 = simplecmd(rpp, redir); + return n1; + default: + synexpect(-1); + } + + /* Now check for redirection which may follow command */ + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); + } + tokpushback++; + *rpp = NULL; + if (redir) { + if (!is_subshell) { + n2 = (union node *)stalloc(sizeof (struct nredir)); + n2->type = NREDIR; + n2->nredir.n = n1; + n1 = n2; + } + n1->nredir.redirect = redir; + } + + return n1; +} + + +static union node * +simplecmd(union node **rpp, union node *redir) +{ + union node *args, **app; + union node **orig_rpp = rpp; + union node *n = NULL; + int special; + int savecheckkwd; + + /* If we don't have any redirections already, then we must reset */ + /* rpp to be the address of the local redir variable. */ + if (redir == NULL) + rpp = &redir; + + args = NULL; + app = &args; + /* + * We save the incoming value, because we need this for shell + * functions. There can not be a redirect or an argument between + * the function name and the open parenthesis. + */ + orig_rpp = rpp; + + savecheckkwd = CHKALIAS; + + for (;;) { + checkkwd = savecheckkwd; + if (readtoken() == TWORD) { + n = makename(); + *app = n; + app = &n->narg.next; + if (savecheckkwd != 0 && !isassignment(wordtext)) + savecheckkwd = 0; + } else if (lasttoken == TREDIR) { + *rpp = n = redirnode; + rpp = &n->nfile.next; + parsefname(); /* read name of redirection file */ + } else if (lasttoken == TLP && app == &args->narg.next + && rpp == orig_rpp) { + /* We have a function */ + consumetoken(TRP); + funclinno = plinno; + /* + * - Require plain text. + * - Functions with '/' cannot be called. + * - Reject name=(). + * - Reject ksh extended glob patterns. + */ + if (!noexpand(n->narg.text) || quoteflag || + strchr(n->narg.text, '/') || + strchr("!%*+-=?@}~", + n->narg.text[strlen(n->narg.text) - 1])) + synerror("Bad function name"); + rmescapes(n->narg.text); + if (find_builtin(n->narg.text, &special) >= 0 && + special) + synerror("Cannot override a special builtin with a function"); + n->type = NDEFUN; + n->narg.next = command(); + funclinno = 0; + return n; + } else { + tokpushback++; + break; + } + } + *app = NULL; + *rpp = NULL; + n = (union node *)stalloc(sizeof (struct ncmd)); + n->type = NCMD; + n->ncmd.args = args; + 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; +} + +static union node * +makebinary(int type, union node *n1, union node *n2) +{ + union node *n; + + n = (union node *)stalloc(sizeof (struct nbinary)); + n->type = type; + n->nbinary.ch1 = n1; + n->nbinary.ch2 = n2; + return (n); +} + +void +forcealias(void) +{ + checkkwd |= CHKALIAS; +} + +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; + + consumetoken(TWORD); + if (n->type == NHERE) { + struct heredoc *here = heredoc; + struct heredoc *p; + + if (quoteflag == 0) + n->type = NXHERE; + TRACE(("Here document %d\n", n->type)); + if (here->striptabs) { + while (*wordtext == '\t') + wordtext++; + } + if (! noexpand(wordtext)) + synerror("Illegal eof marker for << redirection"); + 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; + + while (heredoclist) { + here = heredoclist; + heredoclist = here->next; + if (needprompt) { + setprompt(2); + needprompt = 0; + } + readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX, + here->eofmark, here->striptabs); + n = makename(); + here->here->nhere.doc = n; + } +} + +static int +peektoken(void) +{ + int t; + + t = readtoken(); + tokpushback++; + return (t); +} + +static int +readtoken(void) +{ + int t; + struct alias *ap; +#ifdef DEBUG + int alreadyseen = tokpushback; +#endif + + top: + t = xxreadtoken(); + + /* + * eat newlines + */ + if (checkkwd & CHKNL) { + while (t == TNL) { + parseheredoc(); + t = xxreadtoken(); + } + } + + /* + * check for keywords and aliases + */ + if (t == TWORD && !quoteflag) + { + const char * const *pp; + + if (checkkwd & CHKKWD) + for (pp = parsekwd; *pp; pp++) { + if (**pp == *wordtext && equal(*pp, wordtext)) + { + lasttoken = t = pp - parsekwd + KWDOFFSET; + TRACE(("keyword %s recognized\n", tokname[t])); + goto out; + } + } + if (checkkwd & CHKALIAS && + (ap = lookupalias(wordtext, 1)) != NULL) { + pushstring(ap->val, strlen(ap->val), ap); + goto top; + } + } +out: + if (t != TNOT) + checkkwd = 0; + +#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); +} + + +/* + * 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. + * In all cases, the variable startlinno is set to the number of the line + * on which the token starts. + * + * [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); + needprompt = 0; + } + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_macro(); + switch (c) { + case ' ': case '\t': + continue; + case '#': + while ((c = pgetc()) != '\n' && c != PEOF); + pungetc(); + continue; + case '\\': + if (pgetc() == '\n') { + startlinno = ++plinno; + if (doprompt) + setprompt(2); + else + setprompt(0); + continue; + } + pungetc(); + /* FALLTHROUGH */ + default: + return readtoken1(c, BASESYNTAX, (char *)NULL, 0); + case '\n': + plinno++; + needprompt = doprompt; + RETURN(TNL); + case PEOF: + RETURN(TEOF); + case '&': + if (pgetc_linecont() == '&') + RETURN(TAND); + pungetc(); + RETURN(TBACKGND); + case '|': + if (pgetc_linecont() == '|') + RETURN(TOR); + pungetc(); + RETURN(TPIPE); + case ';': + c = pgetc_linecont(); + if (c == ';') + RETURN(TENDCASE); + else if (c == '&') + RETURN(TFALLTHRU); + pungetc(); + RETURN(TSEMI); + case '(': + RETURN(TLP); + case ')': + RETURN(TRP); + } + } +#undef RETURN +} + + +#define MAXNEST_static 8 +struct tokenstate +{ + const char *syntax; /* *SYNTAX */ + int parenlevel; /* levels of parentheses in arithmetic */ + enum tokenstate_category + { + TSTATE_TOP, + TSTATE_VAR_OLD, /* ${var+-=?}, inherits dquotes */ + TSTATE_VAR_NEW, /* other ${var...}, own dquote state */ + TSTATE_ARITH + } category; +}; + + +/* + * 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. + * The new value of c is returned. + */ + +static int +checkend(int c, const char *eofmark, int striptabs) +{ + if (striptabs) { + while (c == '\t') + c = pgetc(); + } + if (c == *eofmark) { + int c2; + const char *q; + + for (q = eofmark + 1; c2 = pgetc(), *q != '\0' && c2 == *q; q++) + ; + if ((c2 == PEOF || c2 == '\n') && *q == '\0') { + c = PEOF; + if (c2 == '\n') { + plinno++; + needprompt = doprompt; + } + } else { + pungetc(); + pushstring(eofmark + 1, q - (eofmark + 1), NULL); + } + } else if (c == '\n' && *eofmark == '\0') { + c = PEOF; + plinno++; + needprompt = doprompt; + } + return (c); +} + + +/* + * 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. + */ + +static void +parseredir(char *out, int c) +{ + char fd = *out; + union node *np; + + np = (union node *)stalloc(sizeof (struct nfile)); + if (c == '>') { + np->nfile.fd = 1; + c = pgetc_linecont(); + if (c == '>') + np->type = NAPPEND; + else if (c == '&') + np->type = NTOFD; + else if (c == '|') + np->type = NCLOBBER; + else { + np->type = NTO; + pungetc(); + } + } else { /* c == '<' */ + np->nfile.fd = 0; + c = pgetc_linecont(); + if (c == '<') { + 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_linecont()) == '-') { + heredoc->striptabs = 1; + } else { + heredoc->striptabs = 0; + pungetc(); + } + } else if (c == '&') + np->type = NFROMFD; + else if (c == '>') + np->type = NFROMTO; + else { + np->type = NFROM; + pungetc(); + } + } + if (fd != '\0') + np->nfile.fd = digit_val(fd); + redirnode = np; +} + +/* + * Called to parse command substitutions. + */ + +static char * +parsebackq(char *out, struct nodelist **pbqlist, + int oldstyle, int dblquote, int quoted) +{ + struct nodelist **nlpp; + union node *n; + char *volatile str; + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + size_t savelen; + int saveprompt; + const int bq_startlinno = plinno; + char *volatile ostr = NULL; + struct parsefile *const savetopfile = getcurrentfile(); + struct heredoc *const saveheredoclist = heredoclist; + struct heredoc *here; + + str = NULL; + if (setjmp(jmploc.loc)) { + popfilesupto(savetopfile); + if (str) + ckfree(str); + if (ostr) + ckfree(ostr); + heredoclist = saveheredoclist; + handler = savehandler; + if (exception == EXERROR) { + startlinno = bq_startlinno; + synerror("Error in command substitution"); + } + longjmp(handler->loc, 1); + } + INTOFF; + savelen = out - stackblock(); + if (savelen > 0) { + str = ckmalloc(savelen); + memcpy(str, stackblock(), savelen); + } + handler = &jmploc; + heredoclist = NULL; + INTON; + 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 *oout; + int c; + int olen; + + + STARTSTACKSTR(oout); + for (;;) { + if (needprompt) { + setprompt(2); + needprompt = 0; + } + CHECKSTRSPACE(2, oout); + c = pgetc_linecont(); + if (c == '`') + break; + switch (c) { + case '\\': + c = pgetc(); + if (c != '\\' && c != '`' && c != '$' + && (!dblquote || c != '"')) + USTPUTC('\\', oout); + break; + + case '\n': + plinno++; + needprompt = doprompt; + break; + + case PEOF: + startlinno = plinno; + synerror("EOF in backquote substitution"); + break; + + default: + break; + } + USTPUTC(c, oout); + } + USTPUTC('\0', oout); + olen = oout - stackblock(); + INTOFF; + ostr = ckmalloc(olen); + memcpy(ostr, stackblock(), olen); + setinputstring(ostr, 1); + INTON; + } + nlpp = pbqlist; + while (*nlpp) + nlpp = &(*nlpp)->next; + *nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + (*nlpp)->next = NULL; + + if (oldstyle) { + saveprompt = doprompt; + doprompt = 0; + } + + n = list(0); + + if (oldstyle) { + if (peektoken() != TEOF) + synexpect(-1); + doprompt = saveprompt; + } else + consumetoken(TRP); + + (*nlpp)->n = n; + if (oldstyle) { + /* + * Start reading from old file again, ignoring any pushed back + * tokens left from the backquote parsing + */ + popfile(); + tokpushback = 0; + } + STARTSTACKSTR(out); + CHECKSTRSPACE(savelen + 1, out); + INTOFF; + if (str) { + memcpy(out, str, savelen); + STADJUST(savelen, out); + ckfree(str); + str = NULL; + } + if (ostr) { + ckfree(ostr); + ostr = NULL; + } + here = saveheredoclist; + if (here != NULL) { + while (here->next != NULL) + here = here->next; + here->next = heredoclist; + heredoclist = saveheredoclist; + } + handler = savehandler; + INTON; + if (quoted) + USTPUTC(CTLBACKQ | CTLQUOTE, out); + else + USTPUTC(CTLBACKQ, out); + return out; +} + + +/* + * Called to parse a backslash escape sequence inside $'...'. + * The backslash has already been read. + */ +static char * +readcstyleesc(char *out) +{ + int c, vc, i, n; + unsigned int v; + + c = pgetc(); + switch (c) { + case '\0': + synerror("Unterminated quoted string"); + case '\n': + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + return out; + case '\\': + case '\'': + case '"': + v = c; + break; + case 'a': v = '\a'; break; + case 'b': v = '\b'; break; + case 'e': v = '\033'; break; + case 'f': v = '\f'; break; + case 'n': v = '\n'; break; + case 'r': v = '\r'; break; + case 't': v = '\t'; break; + case 'v': v = '\v'; break; + case 'x': + v = 0; + for (;;) { + c = pgetc(); + if (c >= '0' && c <= '9') + v = (v << 4) + c - '0'; + else if (c >= 'A' && c <= 'F') + v = (v << 4) + c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + v = (v << 4) + c - 'a' + 10; + else + break; + } + pungetc(); + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + v = c - '0'; + c = pgetc(); + if (c >= '0' && c <= '7') { + v <<= 3; + v += c - '0'; + c = pgetc(); + if (c >= '0' && c <= '7') { + v <<= 3; + v += c - '0'; + } else + pungetc(); + } else + pungetc(); + break; + case 'c': + c = pgetc(); + if (c < 0x3f || c > 0x7a || c == 0x60) + synerror("Bad escape sequence"); + if (c == '\\' && pgetc() != '\\') + synerror("Bad escape sequence"); + if (c == '?') + v = 127; + else + v = c & 0x1f; + break; + case 'u': + case 'U': + n = c == 'U' ? 8 : 4; + v = 0; + for (i = 0; i < n; i++) { + c = pgetc(); + if (c >= '0' && c <= '9') + v = (v << 4) + c - '0'; + else if (c >= 'A' && c <= 'F') + v = (v << 4) + c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + v = (v << 4) + c - 'a' + 10; + else + synerror("Bad escape sequence"); + } + if (v == 0 || (v >= 0xd800 && v <= 0xdfff)) + synerror("Bad escape sequence"); + /* We really need iconv here. */ + if (initial_localeisutf8 && v > 127) { + CHECKSTRSPACE(4, out); + /* + * We cannot use wctomb() as the locale may have + * changed. + */ + if (v <= 0x7ff) { + USTPUTC(0xc0 | v >> 6, out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } else if (v <= 0xffff) { + USTPUTC(0xe0 | v >> 12, out); + USTPUTC(0x80 | ((v >> 6) & 0x3f), out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } else if (v <= 0x10ffff) { + USTPUTC(0xf0 | v >> 18, out); + USTPUTC(0x80 | ((v >> 12) & 0x3f), out); + USTPUTC(0x80 | ((v >> 6) & 0x3f), out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } + } + if (v > 127) + v = '?'; + break; + default: + synerror("Bad escape sequence"); + } + vc = (char)v; + /* + * We can't handle NUL bytes. + * POSIX says we should skip till the closing quote. + */ + if (vc == '\0') { + while ((c = pgetc()) != '\'') { + if (c == '\\') + c = pgetc(); + if (c == PEOF) + synerror("Unterminated quoted string"); + if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } + } + pungetc(); + return out; + } + if (SQSYNTAX[vc] == CCTL) + USTPUTC(CTLESC, out); + USTPUTC(vc, out); + return out; +} + + +/* + * 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 PARSESUB() {goto parsesub; parsesub_return:;} +#define PARSEARITH() {goto parsearith; parsearith_return:;} + +static int +readtoken1(int firstc, char const *initialsyntax, const char *eofmark, + int striptabs) +{ + int c = firstc; + char *out; + int len; + struct nodelist *bqlist; + int quotef; + int newvarnest; + int level; + int synentry; + struct tokenstate state_static[MAXNEST_static]; + int maxnest = MAXNEST_static; + struct tokenstate *state = state_static; + int sqiscstyle = 0; + + startlinno = plinno; + quotef = 0; + bqlist = NULL; + newvarnest = 0; + level = 0; + state[level].syntax = initialsyntax; + state[level].parenlevel = 0; + state[level].category = TSTATE_TOP; + + STARTSTACKSTR(out); + loop: { /* for each line, until end of word */ + if (eofmark && eofmark != NOEOFMARK) + /* set c to PEOF if at end of here document */ + c = checkend(c, eofmark, striptabs); + for (;;) { /* until end of line or end of word */ + CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ + + synentry = state[level].syntax[c]; + + switch(synentry) { + case CNL: /* '\n' */ + if (level == 0) + goto endword; /* exit outer loop */ + /* FALLTHROUGH */ + case CQNL: + USTPUTC(c, out); + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + c = pgetc(); + goto loop; /* continue outer loop */ + case CSBACK: + if (sqiscstyle) { + out = readcstyleesc(out); + break; + } + /* FALLTHROUGH */ + case CWORD: + USTPUTC(c, out); + break; + case CCTL: + if (eofmark == NULL || initialsyntax != SQSYNTAX) + USTPUTC(CTLESC, out); + USTPUTC(c, out); + break; + case CBACK: /* backslash */ + c = pgetc(); + if (c == PEOF) { + USTPUTC('\\', out); + pungetc(); + } else if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } else { + if (state[level].syntax == DQSYNTAX && + c != '\\' && c != '`' && c != '$' && + (c != '"' || (eofmark != NULL && + newvarnest == 0)) && + (c != '}' || state[level].category != TSTATE_VAR_OLD)) + USTPUTC('\\', out); + if ((eofmark == NULL || + newvarnest > 0) && + state[level].syntax == BASESYNTAX) + USTPUTC(CTLQUOTEMARK, out); + if (SQSYNTAX[c] == CCTL) + USTPUTC(CTLESC, out); + USTPUTC(c, out); + if ((eofmark == NULL || + newvarnest > 0) && + state[level].syntax == BASESYNTAX && + state[level].category == TSTATE_VAR_OLD) + USTPUTC(CTLQUOTEEND, out); + quotef++; + } + break; + case CSQUOTE: + USTPUTC(CTLQUOTEMARK, out); + state[level].syntax = SQSYNTAX; + sqiscstyle = 0; + break; + case CDQUOTE: + USTPUTC(CTLQUOTEMARK, out); + state[level].syntax = DQSYNTAX; + break; + case CENDQUOTE: + if (eofmark != NULL && newvarnest == 0) + USTPUTC(c, out); + else { + if (state[level].category == TSTATE_VAR_OLD) + USTPUTC(CTLQUOTEEND, out); + state[level].syntax = BASESYNTAX; + quotef++; + } + break; + case CVAR: /* '$' */ + PARSESUB(); /* parse substitution */ + break; + case CENDVAR: /* '}' */ + if (level > 0 && + ((state[level].category == TSTATE_VAR_OLD && + state[level].syntax == + state[level - 1].syntax) || + (state[level].category == TSTATE_VAR_NEW && + state[level].syntax == BASESYNTAX))) { + if (state[level].category == TSTATE_VAR_NEW) + newvarnest--; + level--; + USTPUTC(CTLENDVAR, out); + } else { + USTPUTC(c, out); + } + break; + case CLP: /* '(' in arithmetic */ + state[level].parenlevel++; + USTPUTC(c, out); + break; + case CRP: /* ')' in arithmetic */ + if (state[level].parenlevel > 0) { + USTPUTC(c, out); + --state[level].parenlevel; + } else { + if (pgetc_linecont() == ')') { + if (level > 0 && + state[level].category == TSTATE_ARITH) { + level--; + USTPUTC(CTLENDARI, out); + } else + USTPUTC(')', out); + } else { + /* + * unbalanced parens + * (don't 2nd guess - no error) + */ + pungetc(); + USTPUTC(')', out); + } + } + break; + case CBQUOTE: /* '`' */ + out = parsebackq(out, &bqlist, 1, + state[level].syntax == DQSYNTAX && + (eofmark == NULL || newvarnest > 0), + state[level].syntax == DQSYNTAX || state[level].syntax == ARISYNTAX); + break; + case CEOF: + goto endword; /* exit outer loop */ + case CIGN: + break; + default: + if (level == 0) + goto endword; /* exit outer loop */ + USTPUTC(c, out); + } + c = pgetc_macro(); + } + } +endword: + if (state[level].syntax == ARISYNTAX) + synerror("Missing '))'"); + if (state[level].syntax != BASESYNTAX && eofmark == NULL) + synerror("Unterminated quoted string"); + if (state[level].category == TSTATE_VAR_OLD || + state[level].category == TSTATE_VAR_NEW) { + startlinno = plinno; + synerror("Missing '}'"); + } + if (state != state_static) + parser_temp_free_upto(state); + USTPUTC('\0', out); + len = out - stackblock(); + out = stackblock(); + if (eofmark == NULL) { + if ((c == '>' || c == '<') + && quotef == 0 + && len <= 2 + && (*out == '\0' || is_digit(*out))) { + parseredir(out, c); + return lasttoken = TREDIR; + } else { + pungetc(); + } + } + quoteflag = quotef; + backquotelist = bqlist; + grabstackblock(len); + wordtext = out; + return lasttoken = TWORD; +/* end of readtoken routine */ + + +/* + * Parse a substitution. At this point, we have read the dollar sign + * and nothing else. + */ + +parsesub: { + int subtype; + int typeloc; + int flags; + char *p; + static const char types[] = "}-+?="; + int linno; + int length; + int c1; + + c = pgetc_linecont(); + if (c == '(') { /* $(command) or $((arith)) */ + if (pgetc_linecont() == '(') { + PARSEARITH(); + } else { + pungetc(); + out = parsebackq(out, &bqlist, 0, + state[level].syntax == DQSYNTAX && + (eofmark == NULL || newvarnest > 0), + state[level].syntax == DQSYNTAX || + state[level].syntax == ARISYNTAX); + } + } else if (c == '{' || is_name(c) || is_special(c)) { + USTPUTC(CTLVAR, out); + typeloc = out - stackblock(); + USTPUTC(VSNORMAL, out); + subtype = VSNORMAL; + flags = 0; + if (c == '{') { + c = pgetc_linecont(); + subtype = 0; + } +varname: + if (!is_eof(c) && is_name(c)) { + length = 0; + do { + STPUTC(c, out); + c = pgetc_linecont(); + length++; + } while (!is_eof(c) && is_in_name(c)); + if (length == 6 && + strncmp(out - length, "LINENO", length) == 0) { + /* Replace the variable name with the + * current line number. */ + STADJUST(-6, out); + CHECKSTRSPACE(11, out); + linno = plinno; + if (funclinno != 0) + linno -= funclinno - 1; + length = snprintf(out, 11, "%d", linno); + if (length > 10) + length = 10; + out += length; + flags |= VSLINENO; + } + } else if (is_digit(c)) { + if (subtype != VSNORMAL) { + do { + STPUTC(c, out); + c = pgetc_linecont(); + } while (is_digit(c)); + } else { + USTPUTC(c, out); + c = pgetc_linecont(); + } + } else if (is_special(c)) { + c1 = c; + c = pgetc_linecont(); + if (subtype == 0 && c1 == '#') { + subtype = VSLENGTH; + if (strchr(types, c) == NULL && c != ':' && + c != '#' && c != '%') + goto varname; + c1 = c; + c = pgetc_linecont(); + if (c1 != '}' && c == '}') { + pungetc(); + c = c1; + goto varname; + } + pungetc(); + c = c1; + c1 = '#'; + subtype = 0; + } + USTPUTC(c1, out); + } else { + subtype = VSERROR; + if (c == '}') + pungetc(); + else if (c == '\n' || c == PEOF) + synerror("Unexpected end of line in substitution"); + else if (BASESYNTAX[c] != CCTL) + USTPUTC(c, out); + } + if (subtype == 0) { + switch (c) { + case ':': + flags |= VSNUL; + c = pgetc_linecont(); + /*FALLTHROUGH*/ + default: + p = strchr(types, c); + if (p == NULL) { + if (c == '\n' || c == PEOF) + synerror("Unexpected end of line in substitution"); + if (flags == VSNUL) + STPUTC(':', out); + if (BASESYNTAX[c] != CCTL) + STPUTC(c, out); + subtype = VSERROR; + } else + subtype = p - types + VSNORMAL; + break; + case '%': + case '#': + { + int cc = c; + subtype = c == '#' ? VSTRIMLEFT : + VSTRIMRIGHT; + c = pgetc_linecont(); + if (c == cc) + subtype++; + else + pungetc(); + break; + } + } + } else if (subtype != VSERROR) { + if (subtype == VSLENGTH && c != '}') + subtype = VSERROR; + pungetc(); + } + STPUTC('=', out); + if (state[level].syntax == DQSYNTAX || + state[level].syntax == ARISYNTAX) + flags |= VSQUOTE; + *(stackblock() + typeloc) = subtype | flags; + if (subtype != VSNORMAL) { + if (level + 1 >= maxnest) { + maxnest *= 2; + if (state == state_static) { + state = parser_temp_alloc( + maxnest * sizeof(*state)); + memcpy(state, state_static, + MAXNEST_static * sizeof(*state)); + } else + state = parser_temp_realloc(state, + maxnest * sizeof(*state)); + } + level++; + state[level].parenlevel = 0; + if (subtype == VSMINUS || subtype == VSPLUS || + subtype == VSQUESTION || subtype == VSASSIGN) { + /* + * For operators that were in the Bourne shell, + * inherit the double-quote state. + */ + state[level].syntax = state[level - 1].syntax; + state[level].category = TSTATE_VAR_OLD; + } else { + /* + * The other operators take a pattern, + * so go to BASESYNTAX. + * Also, ' and " are now special, even + * in here documents. + */ + state[level].syntax = BASESYNTAX; + state[level].category = TSTATE_VAR_NEW; + newvarnest++; + } + } + } else if (c == '\'' && state[level].syntax == BASESYNTAX) { + /* $'cstylequotes' */ + USTPUTC(CTLQUOTEMARK, out); + state[level].syntax = SQSYNTAX; + sqiscstyle = 1; + } else { + USTPUTC('$', out); + pungetc(); + } + goto parsesub_return; +} + + +/* + * Parse an arithmetic expansion (indicate start of one and set state) + */ +parsearith: { + + if (level + 1 >= maxnest) { + maxnest *= 2; + if (state == state_static) { + state = parser_temp_alloc( + maxnest * sizeof(*state)); + memcpy(state, state_static, + MAXNEST_static * sizeof(*state)); + } else + state = parser_temp_realloc(state, + maxnest * sizeof(*state)); + } + level++; + state[level].syntax = ARISYNTAX; + state[level].parenlevel = 0; + state[level].category = TSTATE_ARITH; + USTPUTC(CTLARI, out); + if (state[level - 1].syntax == DQSYNTAX) + USTPUTC('"',out); + else + USTPUTC(' ',out); + goto parsearith_return; +} + +} /* end of readtoken */ + + +/* + * Returns true if the text contains nothing to expand (no dollar signs + * or backquotes). + */ + +static int +noexpand(char *text) +{ + char *p; + char c; + + p = text; + while ((c = *p++) != '\0') { + if ( c == CTLQUOTEMARK) + continue; + if (c == CTLESC) + p++; + else if (BASESYNTAX[(int)c] == CCTL) + return 0; + } + return 1; +} + + +/* + * Return true if the argument is a legal variable name (a letter or + * underscore followed by zero or more letters, underscores, and digits). + */ + +int +goodname(const char *name) +{ + const char *p; + + p = name; + if (! is_name(*p)) + return 0; + while (*++p) { + if (! is_in_name(*p)) + return 0; + } + return 1; +} + + +int +isassignment(const char *p) +{ + if (!is_name(*p)) + return 0; + p++; + for (;;) { + if (*p == '=') + return 1; + else if (!is_in_name(*p)) + return 0; + p++; + } +} + + +static void +consumetoken(int token) +{ + if (readtoken() != token) + synexpect(token); +} + + +/* + * 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); +} + + +static void +synerror(const char *msg) +{ + if (commandname) + outfmt(out2, "%s: %d: ", commandname, startlinno); + else if (arg0) + outfmt(out2, "%s: ", arg0); + outfmt(out2, "Syntax error: %s\n", msg); + error((char *)NULL); +} + +static void +setprompt(int which) +{ + whichprompt = which; + if (which == 0) + return; + + if (which == 1 && *ps0val()) { + out2str(expandprompt(ps0val())); + flushout(out2); + } + +#ifndef NO_HISTORY + if (!el) +#endif + { + out2str(getprompt(NULL)); + flushout(out2); + } +} + +static int +pgetc_linecont(void) +{ + int c; + + while ((c = pgetc_macro()) == '\\') { + c = pgetc(); + if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } else { + pungetc(); + /* Allow the backslash to be pushed back. */ + pushstring("\\", 1, NULL); + return (pgetc()); + } + } + return (c); +} + +static char * +expandprompt(const char *fmt) { + static char ps[PROMPTLEN]; + const char *pwd, *home; + int i, trim; + + /* + * Format prompt string. + */ + for (i = 0; (i < PROMPTLEN - 1) && (*fmt != '\0'); i++, fmt++) + if (*fmt == '\\') + switch (*++fmt) { + + /* + * Hostname. + * + * \h specifies just the local hostname, + * \H specifies fully-qualified hostname. + */ + case 'h': + case 'H': + ps[i] = '\0'; + gethostname(&ps[i], PROMPTLEN - i - 1); + ps[PROMPTLEN - 1] = '\0'; + /* Skip to end of hostname. */ + trim = (*fmt == 'h') ? '.' : '\0'; + while ((ps[i] != '\0') && (ps[i] != trim)) + i++; + --i; + break; + + /* + * Working directory. + * + * \W specifies just the final component, + * \w specifies the entire path. + */ + case 'W': + case 'w': + pwd = lookupvar("PWD"); + home = lookupvar("HOME"); + if (pwd == NULL || *pwd == '\0') + pwd = "?"; + if (home == NULL || *home == '\0') + home = "?"; + if (*fmt == 'W' && + *pwd == '/' && pwd[1] != '\0' && + strcmp(pwd, home) != 0) { + strlcpy(&ps[i], strrchr(pwd, '/') + 1, + PROMPTLEN - i); + } else { + if (strncmp(pwd, home, strlen(home)) == 0) { + ps[i++] = '~'; + pwd += strlen(home); + } + strlcpy(&ps[i], pwd, PROMPTLEN - i); + } + /* Skip to end of path. */ + while (ps[i] != '\0') + i++; + --i; + break; + + /* + * Exit status if non-zero. + */ + case '?': + if (exitstatus != 0) { + snprintf(&ps[i], PROMPTLEN - i, "%d", exitstatus); + while (ps[i] != '\0') + i++; + } + i--; + break; + + /* + * Superuser status. + * + * '$' for normal users, '#' for root. + */ + case '$': + ps[i] = (geteuid() != 0) ? '$' : '#'; + break; + + /* + * A literal \. + */ + case '\\': + ps[i] = '\\'; + break; + + /* + * Emit unrecognized formats verbatim. + */ + default: + ps[i] = '\\'; + if (i < PROMPTLEN - 2) + ps[++i] = *fmt; + break; + } + else + ps[i] = *fmt; + ps[i] = '\0'; + return (ps); +} + +/* + * called by editline -- any expansions to the prompt + * should be added here. + */ +char * +getprompt(void *unused __unused) +{ + const char *fmt; + static char internal_error[] = "??"; + + /* + * Select prompt format. + */ + switch (whichprompt) { + case 0: + fmt = ""; + break; + case 1: + fmt = ps1val(); + break; + case 2: + fmt = ps2val(); + break; + default: + return internal_error; + } + + return expandprompt(fmt); +} + +char * +getrprompt(void *unused __unused) +{ + const char *fmt; + static char internal_error[] = "??"; + + /* + * Select prompt format. + */ + switch (whichprompt) { + case 0: + fmt = ""; + break; + case 1: + fmt = rps1val(); + break; + case 2: + fmt = rps2val(); + break; + default: + return internal_error; + } + + return expandprompt(fmt); +} + + +const char * +expandstr(const char *ps) +{ + union node n; + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + const int saveprompt = doprompt; + struct parsefile *const savetopfile = getcurrentfile(); + struct parser_temp *const saveparser_temp = parser_temp; + const char *result = NULL; + + if (!setjmp(jmploc.loc)) { + handler = &jmploc; + parser_temp = NULL; + setinputstring(ps, 1); + doprompt = 0; + readtoken1(pgetc(), DQSYNTAX, NOEOFMARK, 0); + if (backquotelist != NULL) + error("Command substitution not allowed here"); + + n.narg.type = NARG; + n.narg.next = NULL; + n.narg.text = wordtext; + n.narg.backquote = backquotelist; + + expandarg(&n, NULL, 0); + result = stackblock(); + INTOFF; + } + handler = savehandler; + doprompt = saveprompt; + popfilesupto(savetopfile); + if (parser_temp != saveparser_temp) { + parser_temp_free_all(); + parser_temp = saveparser_temp; + } + if (result != NULL) { + INTON; + } else if (exception == EXINT) + raise(SIGINT); + return result; +} diff --git a/bin/1sh/parser.h b/bin/1sh/parser.h new file mode 100644 index 00000000..171bc994 --- /dev/null +++ b/bin/1sh/parser.h @@ -0,0 +1,88 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + * + * @(#)parser.h 8.3 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/parser.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* control characters in argument strings */ +#define CTLESC '\300' +#define CTLVAR '\301' +#define CTLENDVAR '\371' +#define CTLBACKQ '\372' +#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ +/* CTLBACKQ | CTLQUOTE == '\373' */ +#define CTLARI '\374' +#define CTLENDARI '\375' +#define CTLQUOTEMARK '\376' +#define CTLQUOTEEND '\377' /* only for ${v+-...} */ + +/* variable substitution byte (follows CTLVAR) */ +#define VSTYPE 0x0f /* type of variable substitution */ +#define VSNUL 0x10 /* colon--treat the empty string as unset */ +#define VSLINENO 0x20 /* expansion of $LINENO, the line number \ + follows immediately */ +#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */ + +/* 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 VSTRIMLEFT 0x6 /* ${var#pattern} */ +#define VSTRIMLEFTMAX 0x7 /* ${var##pattern} */ +#define VSTRIMRIGHT 0x8 /* ${var%pattern} */ +#define VSTRIMRIGHTMAX 0x9 /* ${var%%pattern} */ +#define VSLENGTH 0xa /* ${#var} */ +#define VSERROR 0xb /* Syntax error, issue when expanded */ + + +/* + * NEOF is returned by parsecmd when it encounters an end of file. It + * must be distinct from NULL. + */ +#define NEOF ((union node *)-1) +extern int whichprompt; /* 1 == PS1, 2 == PS2 */ +extern const char *const parsekwd[]; + + +union node *parsecmd(int); +union node *parsewordexp(void); +void forcealias(void); +void fixredir(union node *, const char *, int); +int goodname(const char *); +int isassignment(const char *); +char *getprompt(void *); +char *getrprompt(void *); +const char *expandstr(const char *); diff --git a/bin/1sh/printf.c b/bin/1sh/printf.c new file mode 100644 index 00000000..2545fb62 --- /dev/null +++ b/bin/1sh/printf.c @@ -0,0 +1,686 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright 2018 Staysail Systems, Inc. <info@staysail.tech> + * Copyright 2014 Garrett D'Amore <garrett@damore.org> + * Copyright 2010 Nexenta Systems, Inc. All rights reserved. + * Copyright (c) 1989, 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. + */ +/* + * Important: This file is used both as a standalone program /usr/bin/printf + * and as a builtin for /bin/sh (#define SHELL). + */ + +#ifndef SHELL +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1989, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ +#endif + +#if 0 +static char const sccsid[] = "@(#)printf.c 8.1 (Berkeley) 7/20/93"; +static const char rcsid[] = + "$FreeBSD: releng/12.1/usr.bin/printf/printf.c 337618 2018-08-11 11:13:34Z jilles $"; +#endif + +#include <sys/types.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wchar.h> + +#ifdef SHELL +#define main printfcmd +#include "bltin.h" +#include "options.h" +#endif + +#define PF(f, func) do { \ + if (havewidth) \ + if (haveprec) \ + (void)printf(f, fieldwidth, precision, func); \ + else \ + (void)printf(f, fieldwidth, func); \ + else if (haveprec) \ + (void)printf(f, precision, func); \ + else \ + (void)printf(f, func); \ +} while (0) + +static int asciicode(void); +static char *printf_doformat(char *, int *); +static int escape(char *, int, size_t *); +static int getchr(void); +static int getfloating(long double *, int); +static int getint(int *); +static int getnum(intmax_t *, uintmax_t *, int); +static const char + *getstr(void); +static char *mknum(char *, char); +static void usage(void); + +static const char digits[] = "0123456789"; + +static char end_fmt[1]; + +static int myargc; +static char **myargv; +static char **gargv; +static char **maxargv; + +int +main(int argc, char *argv[]) +{ + size_t len; + int end, rval; + char *format, *fmt, *start; +#ifndef SHELL + int ch; + + (void) setlocale(LC_ALL, ""); +#endif + +#ifdef SHELL + nextopt(""); + argc -= argptr - argv; + argv = argptr; +#else + while ((ch = getopt(argc, argv, "")) != -1) + switch (ch) { + case '?': + default: + usage(); + return (1); + } + argc -= optind; + argv += optind; +#endif + + if (argc < 1) { + usage(); + return (1); + } + +#ifdef SHELL + INTOFF; +#endif + /* + * 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. + */ + fmt = format = *argv; + escape(fmt, 1, &len); /* backslash interpretation */ + rval = end = 0; + gargv = ++argv; + + for (;;) { + maxargv = gargv; + + myargv = gargv; + for (myargc = 0; gargv[myargc]; myargc++) + /* nop */; + start = fmt; + while (fmt < format + len) { + if (fmt[0] == '%') { + fwrite(start, 1, fmt - start, stdout); + if (fmt[1] == '%') { + /* %% prints a % */ + putchar('%'); + fmt += 2; + } else { + fmt = printf_doformat(fmt, &rval); + if (fmt == NULL || fmt == end_fmt) { +#ifdef SHELL + INTON; +#endif + return (fmt == NULL ? 1 : rval); + } + end = 0; + } + start = fmt; + } else + fmt++; + if (gargv > maxargv) + maxargv = gargv; + } + gargv = maxargv; + + if (end == 1) { + warnx("missing format character"); +#ifdef SHELL + INTON; +#endif + return (1); + } + fwrite(start, 1, fmt - start, stdout); + if (!*gargv) { +#ifdef SHELL + INTON; +#endif + return (rval); + } + /* Restart at the beginning of the format string. */ + fmt = format; + end = 1; + } + /* NOTREACHED */ +} + + +static char * +printf_doformat(char *fmt, int *rval) +{ + static const char skip1[] = "#'-+ 0"; + int fieldwidth, haveprec, havewidth, mod_ldbl, precision; + char convch, nextch; + char start[strlen(fmt) + 1]; + char **fargv; + char *dptr; + int l; + + dptr = start; + *dptr++ = '%'; + *dptr = 0; + + fmt++; + + /* look for "n$" field index specifier */ + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + if (gargv > maxargv) + maxargv = gargv; + fmt += l + 1; + + /* save format argument */ + fargv = gargv; + } else { + fargv = NULL; + } + + /* skip to field width */ + while (*fmt && strchr(skip1, *fmt) != NULL) { + *dptr++ = *fmt++; + *dptr = 0; + } + + if (*fmt == '*') { + + fmt++; + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (fargv == NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + fmt += l + 1; + } else if (fargv != NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + + if (getint(&fieldwidth)) + return (NULL); + if (gargv > maxargv) + maxargv = gargv; + havewidth = 1; + + *dptr++ = '*'; + *dptr = 0; + } else { + havewidth = 0; + + /* skip to possible '.', get following precision */ + while (isdigit(*fmt)) { + *dptr++ = *fmt++; + *dptr = 0; + } + } + + if (*fmt == '.') { + /* precision present? */ + fmt++; + *dptr++ = '.'; + + if (*fmt == '*') { + + fmt++; + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (fargv == NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + fmt += l + 1; + } else if (fargv != NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + + if (getint(&precision)) + return (NULL); + if (gargv > maxargv) + maxargv = gargv; + haveprec = 1; + *dptr++ = '*'; + *dptr = 0; + } else { + haveprec = 0; + + /* skip to conversion char */ + while (isdigit(*fmt)) { + *dptr++ = *fmt++; + *dptr = 0; + } + } + } else + haveprec = 0; + if (!*fmt) { + warnx("missing format character"); + return (NULL); + } + *dptr++ = *fmt; + *dptr = 0; + + /* + * Look for a length modifier. POSIX doesn't have these, so + * we only support them for floating-point conversions, which + * are extensions. This is useful because the L modifier can + * be used to gain extra range and precision, while omitting + * it is more likely to produce consistent results on different + * architectures. This is not so important for integers + * because overflow is the only bad thing that can happen to + * them, but consider the command printf %a 1.1 + */ + if (*fmt == 'L') { + mod_ldbl = 1; + fmt++; + if (!strchr("aAeEfFgG", *fmt)) { + warnx("bad modifier L for %%%c", *fmt); + return (NULL); + } + } else { + mod_ldbl = 0; + } + + /* save the current arg offset, and set to the format arg */ + if (fargv != NULL) { + gargv = fargv; + } + + convch = *fmt; + nextch = *++fmt; + + *fmt = '\0'; + switch (convch) { + case 'b': { + size_t len; + char *p; + int getout; + + /* Convert "b" to "s" for output. */ + start[strlen(start) - 1] = 's'; + if ((p = strdup(getstr())) == NULL) { + warnx("%s", strerror(ENOMEM)); + return (NULL); + } + getout = escape(p, 0, &len); + PF(start, p); + /* Restore format for next loop. */ + + free(p); + if (getout) + return (end_fmt); + break; + } + case 'c': { + char p; + + p = getchr(); + if (p != '\0') + PF(start, p); + break; + } + case 's': { + const char *p; + + p = getstr(); + PF(start, p); + break; + } + case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': { + char *f; + intmax_t val; + uintmax_t uval; + int signedconv; + + signedconv = (convch == 'd' || convch == 'i'); + if ((f = mknum(start, convch)) == NULL) + return (NULL); + if (getnum(&val, &uval, signedconv)) + *rval = 1; + if (signedconv) + PF(f, val); + else + PF(f, uval); + break; + } + case 'e': case 'E': + case 'f': case 'F': + case 'g': case 'G': + case 'a': case 'A': { + long double p; + + if (getfloating(&p, mod_ldbl)) + *rval = 1; + if (mod_ldbl) + PF(start, p); + else + PF(start, (double)p); + break; + } + default: + warnx("illegal format character %c", convch); + return (NULL); + } + *fmt = nextch; + /* return the gargv to the next element */ + return (fmt); +} + +static char * +mknum(char *str, char ch) +{ + static char *copy; + static size_t copy_size; + char *newcopy; + size_t len, newlen; + + len = strlen(str) + 2; + if (len > copy_size) { + newlen = ((len + 1023) >> 10) << 10; + if ((newcopy = realloc(copy, newlen)) == NULL) { + warnx("%s", strerror(ENOMEM)); + return (NULL); + } + copy = newcopy; + copy_size = newlen; + } + + memmove(copy, str, len - 3); + copy[len - 3] = 'j'; + copy[len - 2] = ch; + copy[len - 1] = '\0'; + return (copy); +} + +static int +escape(char *fmt, int percent, size_t *len) +{ + char *save, *store, c; + int value; + + for (save = store = fmt; ((c = *fmt) != 0); ++fmt, ++store) { + if (c != '\\') { + *store = c; + continue; + } + switch (*++fmt) { + case '\0': /* EOS, user error */ + *store = '\\'; + *++store = '\0'; + *len = store - save; + return (0); + case '\\': /* backslash */ + case '\'': /* single quote */ + *store = *fmt; + break; + case 'a': /* bell/alert */ + *store = '\a'; + break; + case 'b': /* backspace */ + *store = '\b'; + break; + case 'c': + if (!percent) { + *store = '\0'; + *len = store - save; + return (1); + } + *store = 'c'; + break; + case 'f': /* form-feed */ + *store = '\f'; + break; + case 'n': /* newline */ + *store = '\n'; + break; + case 'r': /* carriage-return */ + *store = '\r'; + break; + case 't': /* horizontal tab */ + *store = '\t'; + break; + case 'v': /* vertical tab */ + *store = '\v'; + break; + /* octal constant */ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c = (!percent && *fmt == '0') ? 4 : 3; + for (value = 0; + c-- && *fmt >= '0' && *fmt <= '7'; ++fmt) { + value <<= 3; + value += *fmt - '0'; + } + --fmt; + if (percent && value == '%') { + *store++ = '%'; + *store = '%'; + } else + *store = (char)value; + break; + default: + *store = *fmt; + break; + } + } + *store = '\0'; + *len = store - save; + return (0); +} + +static int +getchr(void) +{ + if (!*gargv) + return ('\0'); + return ((int)**gargv++); +} + +static const char * +getstr(void) +{ + if (!*gargv) + return (""); + return (*gargv++); +} + +static int +getint(int *ip) +{ + intmax_t val; + uintmax_t uval; + int rval; + + if (getnum(&val, &uval, 1)) + return (1); + rval = 0; + if (val < INT_MIN || val > INT_MAX) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + *ip = (int)val; + return (rval); +} + +static int +getnum(intmax_t *ip, uintmax_t *uip, int signedconv) +{ + char *ep; + int rval; + + if (!*gargv) { + *ip = *uip = 0; + return (0); + } + if (**gargv == '"' || **gargv == '\'') { + if (signedconv) + *ip = asciicode(); + else + *uip = asciicode(); + return (0); + } + rval = 0; + errno = 0; + if (signedconv) + *ip = strtoimax(*gargv, &ep, 0); + else + *uip = strtoumax(*gargv, &ep, 0); + if (ep == *gargv) { + warnx("%s: expected numeric value", *gargv); + rval = 1; + } + else if (*ep != '\0') { + warnx("%s: not completely converted", *gargv); + rval = 1; + } + if (errno == ERANGE) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + ++gargv; + return (rval); +} + +static int +getfloating(long double *dp, int mod_ldbl) +{ + char *ep; + int rval; + + if (!*gargv) { + *dp = 0.0; + return (0); + } + if (**gargv == '"' || **gargv == '\'') { + *dp = asciicode(); + return (0); + } + rval = 0; + errno = 0; + if (mod_ldbl) + *dp = strtold(*gargv, &ep); + else + *dp = strtod(*gargv, &ep); + if (ep == *gargv) { + warnx("%s: expected numeric value", *gargv); + rval = 1; + } else if (*ep != '\0') { + warnx("%s: not completely converted", *gargv); + rval = 1; + } + if (errno == ERANGE) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + ++gargv; + return (rval); +} + +static int +asciicode(void) +{ + int ch; + wchar_t wch; + mbstate_t mbs; + + ch = (unsigned char)**gargv; + if (ch == '\'' || ch == '"') { + memset(&mbs, 0, sizeof(mbs)); + switch (mbrtowc(&wch, *gargv + 1, MB_LEN_MAX, &mbs)) { + case (size_t)-2: + case (size_t)-1: + wch = (unsigned char)gargv[0][1]; + break; + case 0: + wch = 0; + break; + } + ch = wch; + } + ++gargv; + return (ch); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: printf format [arguments ...]\n"); +} diff --git a/bin/1sh/redir.c b/bin/1sh/redir.c new file mode 100644 index 00000000..f8fd9324 --- /dev/null +++ b/bin/1sh/redir.c @@ -0,0 +1,365 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)redir.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/redir.c 326025 2017-11-20 19:49:47Z pfg $ */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <signal.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> + +/* + * Code for dealing with input/output redirection. + */ + +#include "shell.h" +#include "nodes.h" +#include "jobs.h" +#include "expand.h" +#include "redir.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "options.h" + + +#define EMPTY -2 /* marks an unused slot in redirtab */ +#define CLOSED -1 /* fd was not open before redir */ + + +struct redirtab { + struct redirtab *next; + int renamed[10]; + int fd0_redirected; + unsigned int empty_redirs; +}; + + +static struct redirtab *redirlist; + +/* + * We keep track of whether or not fd0 has been redirected. This is for + * background commands, where we want to redirect fd0 to /dev/null only + * if it hasn't already been redirected. +*/ +static int fd0_redirected = 0; + +/* Number of redirtabs that have not been allocated. */ +static unsigned int empty_redirs = 0; + +static void openredirect(union node *, char[10 ]); +static int openhere(union node *); + + +/* + * 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. +* + * We suppress interrupts so that we won't leave open file + * descriptors around. Because the signal handler remains + * installed and we do not use system call restart, interrupts + * will still abort blocking opens such as fifos (they will fail + * with EINTR). There is, however, a race condition if an interrupt + * arrives after INTOFF and before open blocks. + */ + +void +redirect(union node *redir, int flags) +{ + union node *n; + struct redirtab *sv = NULL; + int i; + int fd; + char memory[10]; /* file descriptors to write to memory */ + + INTOFF; + for (i = 10 ; --i >= 0 ; ) + memory[i] = 0; + memory[1] = flags & REDIR_BACKQ; + if (flags & REDIR_PUSH) { + empty_redirs++; + if (redir != NULL) { + sv = ckmalloc(sizeof (struct redirtab)); + for (i = 0 ; i < 10 ; i++) + sv->renamed[i] = EMPTY; + sv->fd0_redirected = fd0_redirected; + sv->empty_redirs = empty_redirs - 1; + sv->next = redirlist; + redirlist = sv; + empty_redirs = 0; + } + } + for (n = redir ; n ; n = n->nfile.next) { + fd = n->nfile.fd; + if (fd == 0) + fd0_redirected = 1; + if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) && + n->ndup.dupfd == fd) + continue; /* redirect from/to same file descriptor */ + + if ((flags & REDIR_PUSH) && sv->renamed[fd] == EMPTY) { + INTOFF; + if ((i = fcntl(fd, F_DUPFD_CLOEXEC, 10)) == -1) { + switch (errno) { + case EBADF: + i = CLOSED; + break; + default: + INTON; + error("%d: %s", fd, strerror(errno)); + break; + } + } + sv->renamed[fd] = i; + INTON; + } + openredirect(n, memory); + INTON; + INTOFF; + } + if (memory[1]) + out1 = &memout; + if (memory[2]) + out2 = &memout; + INTON; +} + + +static void +openredirect(union node *redir, char memory[10]) +{ + struct stat sb; + int fd = redir->nfile.fd; + const char *fname; + int f; + int e; + + memory[fd] = 0; + switch (redir->nfile.type) { + case NFROM: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDONLY)) < 0) + error("cannot open %s: %s", fname, strerror(errno)); + break; + case NFROMTO: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDWR|O_CREAT, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + break; + case NTO: + if (Cflag) { + fname = redir->nfile.expfname; + if (stat(fname, &sb) == -1) { + if ((f = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + } else if (!S_ISREG(sb.st_mode)) { + if ((f = open(fname, O_WRONLY, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + if (fstat(f, &sb) != -1 && S_ISREG(sb.st_mode)) { + close(f); + error("cannot create %s: %s", fname, + strerror(EEXIST)); + } + } else + error("cannot create %s: %s", fname, + strerror(EEXIST)); + break; + } + /* FALLTHROUGH */ + case NCLOBBER: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + break; + case NAPPEND: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + break; + case NTOFD: + case NFROMFD: + if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ + if (memory[redir->ndup.dupfd]) + memory[fd] = 1; + else { + if (dup2(redir->ndup.dupfd, fd) < 0) + error("%d: %s", redir->ndup.dupfd, + strerror(errno)); + } + } else { + close(fd); + } + return; + case NHERE: + case NXHERE: + f = openhere(redir); + break; + default: + abort(); + } + if (f != fd) { + if (dup2(f, fd) == -1) { + e = errno; + close(f); + error("%d: %s", fd, strerror(e)); + } + close(f); + } +} + + +/* + * 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) +{ + const char *p; + int pip[2]; + size_t len = 0; + int flags; + ssize_t written = 0; + + if (pipe(pip) < 0) + error("Pipe call failed: %s", strerror(errno)); + + if (redir->type == NXHERE) + p = redir->nhere.expdoc; + else + p = redir->nhere.doc->narg.text; + len = strlen(p); + if (len == 0) + goto out; + flags = fcntl(pip[1], F_GETFL, 0); + if (flags != -1 && fcntl(pip[1], F_SETFL, flags | O_NONBLOCK) != -1) { + written = write(pip[1], p, len); + if (written < 0) + written = 0; + if ((size_t)written == len) + goto out; + fcntl(pip[1], F_SETFL, flags); + } + + 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); + signal(SIGTSTP, SIG_IGN); + signal(SIGPIPE, SIG_DFL); + xwrite(pip[1], p + written, len - written); + _exit(0); + } +out: + close(pip[1]); + return pip[0]; +} + + + +/* + * Undo the effects of the last redirection. + */ + +void +popredir(void) +{ + struct redirtab *rp = redirlist; + int i; + + INTOFF; + if (empty_redirs > 0) { + empty_redirs--; + INTON; + return; + } + for (i = 0 ; i < 10 ; i++) { + if (rp->renamed[i] != EMPTY) { + if (rp->renamed[i] >= 0) { + dup2(rp->renamed[i], i); + close(rp->renamed[i]); + } else { + close(i); + } + } + } + fd0_redirected = rp->fd0_redirected; + empty_redirs = rp->empty_redirs; + redirlist = rp->next; + ckfree(rp); + INTON; +} + +/* Return true if fd 0 has already been redirected at least once. */ +int +fd0_redirected_p(void) +{ + return fd0_redirected != 0; +} + +/* + * Discard all saved file descriptors. + */ + +void +clearredir(void) +{ + struct redirtab *rp; + int i; + + for (rp = redirlist ; rp ; rp = rp->next) { + for (i = 0 ; i < 10 ; i++) { + if (rp->renamed[i] >= 0) { + close(rp->renamed[i]); + } + rp->renamed[i] = EMPTY; + } + } +} diff --git a/bin/1sh/redir.h b/bin/1sh/redir.h new file mode 100644 index 00000000..6946f6a7 --- /dev/null +++ b/bin/1sh/redir.h @@ -0,0 +1,47 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + * + * @(#)redir.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/redir.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* flags passed to redirect */ +#define REDIR_PUSH 01 /* save previous values of file descriptors */ +#define REDIR_BACKQ 02 /* save the command output in memory */ + +union node; +void redirect(union node *, int); +void popredir(void); +int fd0_redirected_p(void); +void clearredir(void); + diff --git a/bin/1sh/shell.h b/bin/1sh/shell.h new file mode 100644 index 00000000..98ac98df --- /dev/null +++ b/bin/1sh/shell.h @@ -0,0 +1,77 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + * + * @(#)shell.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/shell.h 345117 2019-03-13 21:53:10Z jilles $ + */ + +#ifndef SHELL_H_ +#define SHELL_H_ + +#include <inttypes.h> + +/* + * The follow should be set to reflect the type of system you have: + * JOBS -> 1 if you have Berkeley job control, 0 otherwise. + * define DEBUG=1 to compile in debugging (set global "debug" to turn on) + * define DEBUG=2 to compile in and turn on debugging. + * + * When debugging is on, debugging info will be written to ./trace and + * a quit signal will generate a core dump. + */ + + +#define JOBS 1 +/* #define DEBUG 1 */ + +/* + * Type of used arithmetics. SUSv3 requires us to have at least signed long. + */ +typedef intmax_t arith_t; +#define ARITH_FORMAT_STR "%" PRIdMAX +#define ARITH_MIN INTMAX_MIN +#define ARITH_MAX INTMAX_MAX + +typedef void *pointer; + +#include <sys/cdefs.h> + +extern char nullstr[1]; /* null string */ + +#ifdef DEBUG +#define TRACE(param) sh_trace param +#else +#define TRACE(param) +#endif + +#endif /* !SHELL_H_ */ diff --git a/bin/1sh/show.c b/bin/1sh/show.c new file mode 100644 index 00000000..a87a506d --- /dev/null +++ b/bin/1sh/show.c @@ -0,0 +1,410 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)show.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/show.c 326025 2017-11-20 19:49:47Z pfg $ */ + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> + +#include "shell.h" +#include "parser.h" +#include "nodes.h" +#include "mystring.h" +#include "show.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; + 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; + 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 NAPPEND: s = ">>"; dftfd = 1; break; + case NTOFD: s = ">&"; dftfd = 1; break; + case NCLOBBER: s = ">|"; dftfd = 1; break; + case NFROM: s = "<"; dftfd = 0; break; + case NFROMTO: s = "<>"; dftfd = 0; break; + case NFROMFD: s = "<&"; dftfd = 0; break; + case NHERE: s = "<<"; dftfd = 0; break; + case NXHERE: 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) { + if (np->ndup.dupfd >= 0) + fprintf(fp, "%d", np->ndup.dupfd); + else + fprintf(fp, "-"); + } else if (np->nfile.type == NHERE) { + fprintf(fp, "HERE"); + } else if (np->nfile.type == NXHERE) { + fprintf(fp, "XHERE"); + } 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); + fflush(stdout); + abort(); + } + bqlist = arg->narg.backquote; + for (p = arg->narg.text ; *p ; p++) { + switch (*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: + case CTLBACKQ|CTLQUOTE: + 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; + +#if DEBUG >= 2 +int debug = 1; +#else +int debug = 0; +#endif + + +void +trputc(int c) +{ + if (tracefile == NULL) + return; + putc(c, tracefile); + if (c == '\n') + fflush(tracefile); +} + + +void +sh_trace(const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + if (tracefile != NULL) { + (void) vfprintf(tracefile, fmt, va); + if (strchr(fmt, '\n')) + (void) fflush(tracefile); + } + va_end(va); +} + + +void +trputs(const char *s) +{ + if (tracefile == NULL) + return; + fputs(s, tracefile); + if (strchr(s, '\n')) + fflush(tracefile); +} + + +static void +trstring(char *s) +{ + char *p; + char c; + + if (tracefile == NULL) + return; + putc('"', tracefile); + for (p = s ; *p ; p++) { + switch (*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 CTLVAR+CTLQUOTE: c = 'V'; goto backslash; + case CTLBACKQ: c = 'q'; goto backslash; + case CTLBACKQ+CTLQUOTE: 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 (tracefile == NULL) + return; + while (*ap) { + trstring(*ap++); + if (*ap) + putc(' ', tracefile); + else + putc('\n', tracefile); + } + fflush(tracefile); +} + + +void +opentrace(void) +{ + char s[100]; + int flags; + + if (!debug) + return; +#ifdef not_this_way + { + char *p; + if ((p = getenv("HOME")) == NULL) { + if (geteuid() == 0) + p = "/"; + else + p = "/tmp"; + } + strcpy(s, p); + strcat(s, "/trace"); + } +#else + strcpy(s, "./trace"); +#endif /* not_this_way */ + if ((tracefile = fopen(s, "a")) == NULL) { + fprintf(stderr, "Can't open %s: %s\n", s, strerror(errno)); + return; + } + if ((flags = fcntl(fileno(tracefile), F_GETFL, 0)) >= 0) + fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND); + fputs("\nTracing started.\n", tracefile); + fflush(tracefile); +} +#endif /* DEBUG */ diff --git a/bin/1sh/show.h b/bin/1sh/show.h new file mode 100644 index 00000000..68840549 --- /dev/null +++ b/bin/1sh/show.h @@ -0,0 +1,42 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1995 + * 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. + * + * @(#)show.h 1.1 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/show.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +void showtree(union node *); +#ifdef DEBUG +void sh_trace(const char *, ...) __attribute__((format(printf, 1, 2))); +void trargs(char **); +void trputc(int); +void trputs(const char *); +void opentrace(void); +#endif diff --git a/bin/1sh/test.c b/bin/1sh/test.c new file mode 100644 index 00000000..54c16eef --- /dev/null +++ b/bin/1sh/test.c @@ -0,0 +1,630 @@ +/* $NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $ */ + +/*- + * 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. + */ +/* + * Important: This file is used both as a standalone program /bin/test and + * as a builtin for /bin/sh (#define SHELL). + */ + +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/test/test.c 298232 2016-04-19 00:38:07Z araujo $ */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef SHELL +#define main testcmd +#include "bltin.h" +#else +#include <locale.h> + +static void error(const char *, ...) __attribute__((noreturn)) __attribute__((format(printf, 1, 2))); + +static void +error(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + verrx(2, msg, ap); + /*NOTREACHED*/ + va_end(ap); +} +#endif + +/* 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_types { + UNOP = 0x100, + BINOP = 0x200, + BUNOP = 0x300, + BBINOP = 0x400, + PAREN = 0x500 +}; + +enum token { + EOI, + OPERAND, + FILRD = UNOP + 1, + FILWR, + FILEX, + FILEXIST, + FILREG, + FILDIR, + FILCDEV, + FILBDEV, + FILFIFO, + FILSOCK, + FILSYM, + FILGZ, + FILTT, + FILSUID, + FILSGID, + FILSTCK, + STREZ, + STRNZ, + FILUID, + FILGID, + FILNT = BINOP + 1, + FILOT, + FILEQ, + STREQ, + STRNE, + STRLT, + STRGT, + INTEQ, + INTNE, + INTGE, + INTGT, + INTLE, + INTLT, + UNOT = BUNOP + 1, + BAND = BBINOP + 1, + BOR, + LPAREN = PAREN + 1, + RPAREN +}; + +#define TOKEN_TYPE(token) ((token) & 0xff00) + +static const struct t_op { + char op_text[2]; + short op_num; +} ops1[] = { + {"=", STREQ}, + {"<", STRLT}, + {">", STRGT}, + {"!", UNOT}, + {"(", LPAREN}, + {")", RPAREN}, +}, opsm1[] = { + {"r", FILRD}, + {"w", FILWR}, + {"x", FILEX}, + {"e", FILEXIST}, + {"f", FILREG}, + {"d", FILDIR}, + {"c", FILCDEV}, + {"b", FILBDEV}, + {"p", FILFIFO}, + {"u", FILSUID}, + {"g", FILSGID}, + {"k", FILSTCK}, + {"s", FILGZ}, + {"t", FILTT}, + {"z", STREZ}, + {"n", STRNZ}, + {"h", FILSYM}, /* for backwards compat */ + {"O", FILUID}, + {"G", FILGID}, + {"L", FILSYM}, + {"S", FILSOCK}, + {"a", BAND}, + {"o", BOR}, +}, ops2[] = { + {"==", STREQ}, + {"!=", STRNE}, +}, opsm2[] = { + {"eq", INTEQ}, + {"ne", INTNE}, + {"ge", INTGE}, + {"gt", INTGT}, + {"le", INTLE}, + {"lt", INTLT}, + {"nt", FILNT}, + {"ot", FILOT}, + {"ef", FILEQ}, +}; + +static int nargc; +static char **t_wp; +static int parenlevel; + +static int aexpr(enum token); +static int binop(enum token); +static int equalf(const char *, const char *); +static int filstat(char *, enum token); +static int getn(const char *); +static intmax_t getq(const char *); +static int intcmp(const char *, const char *); +static int isunopoperand(void); +static int islparenoperand(void); +static int isrparenoperand(void); +static int newerf(const char *, const char *); +static int nexpr(enum token); +static int oexpr(enum token); +static int olderf(const char *, const char *); +static int primary(enum token); +static void syntax(const char *, const char *); +static enum token t_lex(char *); + +int +main(int argc, char **argv) +{ + int res; + char *p; + + if ((p = strrchr(argv[0], '/')) == NULL) + p = argv[0]; + else + p++; + if (strcmp(p, "[") == 0) { + if (strcmp(argv[--argc], "]") != 0) + error("missing ]"); + argv[argc] = NULL; + } + + /* no expression => false */ + if (--argc <= 0) + return 1; + +#ifndef SHELL + (void)setlocale(LC_CTYPE, ""); +#endif + nargc = argc; + t_wp = &argv[1]; + parenlevel = 0; + if (nargc == 4 && strcmp(*t_wp, "!") == 0) { + /* Things like ! "" -o x do not fit in the normal grammar. */ + --nargc; + ++t_wp; + res = oexpr(t_lex(*t_wp)); + } else + res = !oexpr(t_lex(*t_wp)); + + if (--nargc > 0) + syntax(*t_wp, "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; + + res = aexpr(n); + if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR) + return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) || + res; + t_wp--; + nargc++; + return res; +} + +static int +aexpr(enum token n) +{ + int res; + + res = nexpr(n); + if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND) + return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) && + res; + t_wp--; + nargc++; + return res; +} + +static int +nexpr(enum token n) +{ + if (n == UNOT) + return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)); + return primary(n); +} + +static int +primary(enum token n) +{ + enum token nn; + int res; + + if (n == EOI) + return 0; /* missing expression */ + if (n == LPAREN) { + parenlevel++; + if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) == + RPAREN) { + parenlevel--; + return 0; /* missing expression */ + } + res = oexpr(nn); + if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN) + syntax(NULL, "closing paren expected"); + parenlevel--; + return res; + } + if (TOKEN_TYPE(n) == UNOP) { + /* unary expression */ + if (--nargc == 0) + syntax(NULL, "argument expected"); /* impossible */ + switch (n) { + case STREZ: + return strlen(*++t_wp) == 0; + case STRNZ: + return strlen(*++t_wp) != 0; + case FILTT: + return isatty(getn(*++t_wp)); + default: + return filstat(*++t_wp, n); + } + } + + nn = t_lex(nargc > 0 ? t_wp[1] : NULL); + if (TOKEN_TYPE(nn) == BINOP) + return binop(nn); + + return strlen(*t_wp) > 0; +} + +static int +binop(enum token n) +{ + const char *opnd1, *op, *opnd2; + + opnd1 = *t_wp; + op = nargc > 0 ? (--nargc, *++t_wp) : NULL; + + if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL) + syntax(op, "argument expected"); + + switch (n) { + 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 intcmp(opnd1, opnd2) == 0; + case INTNE: + return intcmp(opnd1, opnd2) != 0; + case INTGE: + return intcmp(opnd1, opnd2) >= 0; + case INTGT: + return intcmp(opnd1, opnd2) > 0; + case INTLE: + return intcmp(opnd1, opnd2) <= 0; + case INTLT: + return intcmp(opnd1, opnd2) < 0; + case FILNT: + return newerf (opnd1, opnd2); + case FILOT: + return olderf (opnd1, opnd2); + case FILEQ: + return equalf (opnd1, opnd2); + default: + abort(); + /* NOTREACHED */ + } +} + +static int +filstat(char *nm, enum token mode) +{ + struct stat s; + + if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) + return 0; + + switch (mode) { + case FILRD: + return (faccessat(AT_FDCWD, nm, R_OK, AT_EACCESS) == 0); + case FILWR: + return (faccessat(AT_FDCWD, nm, W_OK, AT_EACCESS) == 0); + case FILEX: + /* XXX work around eaccess(2) false positives for superuser */ + if (faccessat(AT_FDCWD, nm, X_OK, AT_EACCESS) != 0) + return 0; + if (S_ISDIR(s.st_mode) || geteuid() != 0) + return 1; + return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; + case FILEXIST: + return (faccessat(AT_FDCWD, nm, F_OK, AT_EACCESS) == 0); + 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 > (off_t)0; + case FILUID: + return s.st_uid == geteuid(); + case FILGID: + return s.st_gid == getegid(); + default: + return 1; + } +} + +static int +find_op_1char(const struct t_op *op, const struct t_op *end, const char *s) +{ + char c; + + c = s[0]; + while (op != end) { + if (c == *op->op_text) + return op->op_num; + op++; + } + return OPERAND; +} + +static int +find_op_2char(const struct t_op *op, const struct t_op *end, const char *s) +{ + while (op != end) { + if (s[0] == op->op_text[0] && s[1] == op->op_text[1]) + return op->op_num; + op++; + } + return OPERAND; +} + +static int +find_op(const char *s) +{ + if (s[0] == '\0') + return OPERAND; + else if (s[1] == '\0') + return find_op_1char(ops1, (&ops1)[1], s); + else if (s[2] == '\0') + return s[0] == '-' ? find_op_1char(opsm1, (&opsm1)[1], s + 1) : + find_op_2char(ops2, (&ops2)[1], s); + else if (s[3] == '\0') + return s[0] == '-' ? find_op_2char(opsm2, (&opsm2)[1], s + 1) : + OPERAND; + else + return OPERAND; +} + +static enum token +t_lex(char *s) +{ + int num; + + if (s == NULL) { + return EOI; + } + num = find_op(s); + if (((TOKEN_TYPE(num) == UNOP || TOKEN_TYPE(num) == BUNOP) + && isunopoperand()) || + (num == LPAREN && islparenoperand()) || + (num == RPAREN && isrparenoperand())) + return OPERAND; + return num; +} + +static int +isunopoperand(void) +{ + char *s; + char *t; + int num; + + if (nargc == 1) + return 1; + s = *(t_wp + 1); + if (nargc == 2) + return parenlevel == 1 && strcmp(s, ")") == 0; + t = *(t_wp + 2); + num = find_op(s); + return TOKEN_TYPE(num) == BINOP && + (parenlevel == 0 || t[0] != ')' || t[1] != '\0'); +} + +static int +islparenoperand(void) +{ + char *s; + int num; + + if (nargc == 1) + return 1; + s = *(t_wp + 1); + if (nargc == 2) + return parenlevel == 1 && strcmp(s, ")") == 0; + if (nargc != 3) + return 0; + num = find_op(s); + return TOKEN_TYPE(num) == BINOP; +} + +static int +isrparenoperand(void) +{ + char *s; + + if (nargc == 1) + return 0; + s = *(t_wp + 1); + if (nargc == 2) + return parenlevel == 1 && strcmp(s, ")") == 0; + return 0; +} + +/* atoi with error detection */ +static int +getn(const char *s) +{ + char *p; + long r; + + errno = 0; + r = strtol(s, &p, 10); + + if (s == p) + error("%s: bad number", s); + + if (errno != 0) + error((errno == EINVAL) ? "%s: bad number" : + "%s: out of range", s); + + while (isspace((unsigned char)*p)) + p++; + + if (*p) + error("%s: bad number", s); + + return (int) r; +} + +/* atoi with error detection and 64 bit range */ +static intmax_t +getq(const char *s) +{ + char *p; + intmax_t r; + + errno = 0; + r = strtoimax(s, &p, 10); + + if (s == p) + error("%s: bad number", s); + + if (errno != 0) + error((errno == EINVAL) ? "%s: bad number" : + "%s: out of range", s); + + while (isspace((unsigned char)*p)) + p++; + + if (*p) + error("%s: bad number", s); + + return r; +} + +static int +intcmp (const char *s1, const char *s2) +{ + intmax_t q1, q2; + + + q1 = getq(s1); + q2 = getq(s2); + + if (q1 > q2) + return 1; + + if (q1 < q2) + return -1; + + return 0; +} + +static int +newerf (const char *f1, const char *f2) +{ + struct stat b1, b2; + + if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0) + return 0; + + if (b1.st_mtimespec.tv_sec > b2.st_mtimespec.tv_sec) + return 1; + if (b1.st_mtimespec.tv_sec < b2.st_mtimespec.tv_sec) + return 0; + + return (b1.st_mtimespec.tv_nsec > b2.st_mtimespec.tv_nsec); +} + +static int +olderf (const char *f1, const char *f2) +{ + return (newerf(f2, f1)); +} + +static int +equalf (const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat (f1, &b1) == 0 && + stat (f2, &b2) == 0 && + b1.st_dev == b2.st_dev && + b1.st_ino == b2.st_ino); +} diff --git a/bin/1sh/trap.c b/bin/1sh/trap.c new file mode 100644 index 00000000..fed13fcf --- /dev/null +++ b/bin/1sh/trap.c @@ -0,0 +1,553 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)trap.c 8.5 (Berkeley) 6/5/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/trap.c 326025 2017-11-20 19:49:47Z pfg $ */ + +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> + +#include "shell.h" +#include "main.h" +#include "nodes.h" /* for other headers */ +#include "eval.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" +#include "builtins.h" +#include "myhistedit.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 permanently */ +#define S_RESET 5 /* temporary - to reset a hard ignored sig */ + + +static char sigmode[NSIG]; /* current value of signal */ +volatile sig_atomic_t pendingsig; /* indicates some signal received */ +volatile sig_atomic_t pendingsig_waitcmd; /* indicates wait builtin should be interrupted */ +static int in_dotrap; /* do we execute in a trap handler? */ +static char *volatile trap[NSIG]; /* trap handler commands */ +static volatile sig_atomic_t gotsig[NSIG]; + /* indicates specified signal received */ +static int ignore_sigchld; /* Used while handling SIGCHLD traps. */ +static int last_trapsig; + +static int exiting; /* exitshell() has been called */ +static int exiting_exitstatus; /* value passed to exitshell() */ + +static int getsigaction(int, sig_t *); + + +/* + * Map a string to a signal number. + * + * Note: the signal number may exceed NSIG. + */ +static int +sigstring_to_signum(char *sig) +{ + + if (is_number(sig)) { + int signo; + + signo = atoi(sig); + return ((signo >= 0 && signo < NSIG) ? signo : (-1)); + } else if (strcasecmp(sig, "EXIT") == 0) { + return (0); + } else { + int n; + + if (strncasecmp(sig, "SIG", 3) == 0) + sig += 3; + for (n = 1; n < NSIG; n++) + if (sys_signame[n] && + strcasecmp(sys_signame[n], sig) == 0) + return (n); + } + return (-1); +} + + +/* + * Print a list of valid signal names. + */ +static void +printsignals(void) +{ + int n, outlen; + + outlen = 0; + for (n = 1; n < NSIG; n++) { + if (sys_signame[n]) { + out1fmt("%s", sys_signame[n]); + outlen += strlen(sys_signame[n]); + } else { + out1fmt("%d", n); + outlen += 3; /* good enough */ + } + ++outlen; + if (outlen > 71 || n == NSIG - 1) { + out1str("\n"); + outlen = 0; + } else { + out1c(' '); + } + } +} + + +/* + * The trap builtin. + */ +int +trapcmd(int argc __unused, char **argv) +{ + char *action; + int signo; + int errors = 0; + int i; + + while ((i = nextopt("l")) != '\0') { + switch (i) { + case 'l': + printsignals(); + return (0); + } + } + argv = argptr; + + if (*argv == NULL) { + for (signo = 0 ; signo < NSIG ; signo++) { + if (signo < NSIG && trap[signo] != NULL) { + out1str("trap -- "); + out1qstr(trap[signo]); + if (signo == 0) { + out1str(" EXIT\n"); + } else if (sys_signame[signo]) { + out1fmt(" %s\n", sys_signame[signo]); + } else { + out1fmt(" %d\n", signo); + } + } + } + return 0; + } + action = NULL; + if (*argv && !is_number(*argv)) { + if (strcmp(*argv, "-") == 0) + argv++; + else { + action = *argv; + argv++; + } + } + for (; *argv; argv++) { + if ((signo = sigstring_to_signum(*argv)) == -1) { + warning("bad signal %s", *argv); + errors = 1; + continue; + } + INTOFF; + if (action) + action = savestr(action); + if (trap[signo]) + ckfree(trap[signo]); + trap[signo] = action; + if (signo != 0) + setsignal(signo); + INTON; + } + return errors; +} + + +/* + * Clear traps on a fork. + */ +void +clear_traps(void) +{ + char *volatile *tp; + + for (tp = trap ; tp <= &trap[NSIG - 1] ; tp++) { + if (*tp && **tp) { /* trap not NULL or SIG_IGN */ + INTOFF; + ckfree(*tp); + *tp = NULL; + if (tp != &trap[0]) + setsignal(tp - trap); + INTON; + } + } +} + + +/* + * Check if we have any traps enabled. + */ +int +have_traps(void) +{ + char *volatile *tp; + + for (tp = trap ; tp <= &trap[NSIG - 1] ; tp++) { + if (*tp && **tp) /* trap not NULL or SIG_IGN */ + return 1; + } + 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; + sig_t sigact = SIG_DFL; + struct sigaction sa; + char *t; + + if ((t = trap[signo]) == NULL) + action = S_DFL; + else if (*t != '\0') + action = S_CATCH; + else + action = S_IGN; + if (action == S_DFL) { + switch (signo) { + case SIGINT: + action = S_CATCH; + break; + case SIGQUIT: +#ifdef DEBUG + { + extern int debug; + + if (debug) + break; + } +#endif + action = S_CATCH; + break; + case SIGTERM: + if (rootshell && iflag) + action = S_IGN; + break; +#if JOBS + case SIGTSTP: + case SIGTTOU: + if (rootshell && mflag) + action = S_IGN; + break; +#endif + } + } + + t = &sigmode[signo]; + if (*t == 0) { + /* + * current setting unknown + */ + if (!getsigaction(signo, &sigact)) { + /* + * 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 (sigact == SIG_IGN) { + if (mflag && (signo == SIGTSTP || + signo == SIGTTIN || signo == SIGTTOU)) { + *t = S_IGN; /* don't hard ignore these */ + } else + *t = S_HARD_IGN; + } else { + *t = S_RESET; /* force to be set */ + } + } + if (*t == S_HARD_IGN || *t == action) + return; + switch (action) { + case S_DFL: sigact = SIG_DFL; break; + case S_CATCH: sigact = onsig; break; + case S_IGN: sigact = SIG_IGN; break; + } + *t = action; + sa.sa_handler = sigact; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(signo, &sa, NULL); +} + + +/* + * Return the current setting for sig w/o changing it. + */ +static int +getsigaction(int signo, sig_t *sigact) +{ + struct sigaction sa; + + if (sigaction(signo, (struct sigaction *)0, &sa) == -1) + return 0; + *sigact = (sig_t) sa.sa_handler; + return 1; +} + + +/* + * Ignore a signal. + */ +void +ignoresig(int signo) +{ + + if (sigmode[signo] == 0) + setsignal(signo); + if (sigmode[signo] != S_IGN && sigmode[signo] != S_HARD_IGN) { + signal(signo, SIG_IGN); + sigmode[signo] = S_IGN; + } +} + + +int +issigchldtrapped(void) +{ + + return (trap[SIGCHLD] != NULL && *trap[SIGCHLD] != '\0'); +} + + +/* + * Signal handler. + */ +void +onsig(int signo) +{ + + if (signo == SIGINT && trap[SIGINT] == NULL) { + /* + * The !in_dotrap here is safe. The only way we can arrive + * here with in_dotrap set is that a trap handler set SIGINT to + * SIG_DFL and killed itself. + */ + if (suppressint && !in_dotrap) + SET_PENDING_INT; + else + onint(); + return; + } + + /* If we are currently in a wait builtin, prepare to break it */ + if (signo == SIGINT || signo == SIGQUIT) + pendingsig_waitcmd = signo; + + if (trap[signo] != NULL && trap[signo][0] != '\0' && + (signo != SIGCHLD || !ignore_sigchld)) { + gotsig[signo] = 1; + pendingsig = signo; + pendingsig_waitcmd = signo; + } +} + + +/* + * Called to execute a trap. Perhaps we should avoid entering new trap + * handlers while we are executing a trap handler. + */ +void +dotrap(void) +{ + struct stackmark smark; + int i; + int savestatus, prev_evalskip, prev_skipcount; + + in_dotrap++; + for (;;) { + pendingsig = 0; + pendingsig_waitcmd = 0; + for (i = 1; i < NSIG; i++) { + if (gotsig[i]) { + gotsig[i] = 0; + if (trap[i]) { + /* + * Ignore SIGCHLD to avoid infinite + * recursion if the trap action does + * a fork. + */ + if (i == SIGCHLD) + ignore_sigchld++; + + /* + * Backup current evalskip + * state and reset it before + * executing a trap, so that the + * trap is not disturbed by an + * ongoing break/continue/return + * statement. + */ + prev_evalskip = evalskip; + prev_skipcount = skipcount; + evalskip = 0; + + last_trapsig = i; + savestatus = exitstatus; + setstackmark(&smark); + evalstring(stsavestr(trap[i]), 0); + popstackmark(&smark); + + /* + * If such a command was not + * already in progress, allow a + * break/continue/return in the + * trap action to have an effect + * outside of it. + */ + if (evalskip == 0 || + prev_evalskip != 0) { + evalskip = prev_evalskip; + skipcount = prev_skipcount; + exitstatus = savestatus; + } + + if (i == SIGCHLD) + ignore_sigchld--; + } + break; + } + } + if (i >= NSIG) + break; + } + in_dotrap--; +} + + +/* + * Controls whether the shell is interactive or not based on iflag. + */ +void +setinteractive(void) +{ + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); +} + + +/* + * Called to exit the shell. + */ +void +exitshell(int status) +{ + TRACE(("exitshell(%d) pid=%d\n", status, getpid())); + exiting = 1; + exiting_exitstatus = status; + exitshell_savedstatus(); +} + +void +exitshell_savedstatus(void) +{ + struct jmploc loc1, loc2; + char *p; + int sig = 0; + sigset_t sigs; + + if (!exiting) { + if (in_dotrap && last_trapsig) { + sig = last_trapsig; + exiting_exitstatus = sig + 128; + } else + exiting_exitstatus = oexitstatus; + } + exitstatus = oexitstatus = exiting_exitstatus; + if (!setjmp(loc1.loc)) { + handler = &loc1; + if ((p = trap[0]) != NULL && *p != '\0') { + /* + * Reset evalskip, or the trap on EXIT could be + * interrupted if the last command was a "return". + */ + evalskip = 0; + trap[0] = NULL; + FORCEINTON; + evalstring(p, 0); + } + } + if (!setjmp(loc2.loc)) { + handler = &loc2; /* probably unnecessary */ + FORCEINTON; + flushall(); +#if JOBS + setjobctl(0); +#endif + } + if (sig != 0 && sig != SIGSTOP && sig != SIGTSTP && sig != SIGTTIN && + sig != SIGTTOU) { + signal(sig, SIG_DFL); + sigemptyset(&sigs); + sigaddset(&sigs, sig); + sigprocmask(SIG_UNBLOCK, &sigs, NULL); + kill(getpid(), sig); + /* If the default action is to ignore, fall back to _exit(). */ + } + _exit(exiting_exitstatus); +} diff --git a/bin/1sh/trap.h b/bin/1sh/trap.h new file mode 100644 index 00000000..75427d56 --- /dev/null +++ b/bin/1sh/trap.h @@ -0,0 +1,50 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + * + * @(#)trap.h 8.3 (Berkeley) 6/5/95 + * $FreeBSD: releng/12.1/bin/sh/trap.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +extern volatile sig_atomic_t pendingsig; +extern volatile sig_atomic_t pendingsig_waitcmd; + +void clear_traps(void); +int have_traps(void); +void setsignal(int); +void ignoresig(int); +int issigchldtrapped(void); +void onsig(int); +void dotrap(void); +void setinteractive(void); +void exitshell(int) __attribute__((noreturn)); +void exitshell_savedstatus(void) __attribute__((noreturn)); diff --git a/bin/1sh/var.c b/bin/1sh/var.c new file mode 100644 index 00000000..44742169 --- /dev/null +++ b/bin/1sh/var.c @@ -0,0 +1,983 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +/* $FreeBSD: releng/12.1/bin/sh/var.c 329221 2018-02-13 16:48:57Z bdrewery $ */ + +#include <unistd.h> +#include <stdlib.h> +#include <paths.h> + +/* + * Shell variables. + */ + +#include <locale.h> +#include <langinfo.h> + +#include "shell.h" +#include "output.h" +#include "expand.h" +#include "nodes.h" /* for other headers */ +#include "eval.h" /* defines cmdenviron */ +#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 "builtins.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + + +#ifndef VTABSIZE +#define VTABSIZE 39 +#endif + + +struct varinit { + struct var *var; + int flags; + const char *text; + void (*func)(const char *); +}; + + +#ifndef NO_HISTORY +struct var vhistfile; +struct var vhistsize; +struct var vterm; +#endif +struct var vifs; +struct var vmail; +struct var vmpath; +struct var vpath; +struct var vps0; +struct var vps1; +struct var vps2; +struct var vps4; +struct var vrps1; +struct var vrps2; +static struct var voptind; +struct var vdisvfork; + +struct localvar *localvars; +int forcelocal; + +static const struct varinit varinit[] = { +#ifndef NO_HISTORY + { &vhistfile, VUNSET, "HISTFILE=", + sethistfile }, + { &vhistsize, VUNSET, "HISTSIZE=", + sethistsize }, +#endif + { &vifs, 0, "IFS= \t\n", + NULL }, + { &vmail, VUNSET, "MAIL=", + NULL }, + { &vmpath, VUNSET, "MAILPATH=", + NULL }, + { &vpath, 0, "PATH=" _PATH_DEFPATH, + changepath }, + { &vps0, VUNSET, "PS0=", + NULL }, + /* + * vps1 depends on uid + */ + { &vps2, 0, "PS2=> ", + NULL }, + { &vps4, 0, "PS4=+ ", + NULL }, + { &vrps1, VUNSET, "RPS1=", + NULL }, + { &vrps2, VUNSET, "RPS2=", + NULL }, +#ifndef NO_HISTORY + { &vterm, VUNSET, "TERM=", + setterm }, +#endif + { &voptind, 0, "OPTIND=1", + getoptsreset }, + { &vdisvfork, VUNSET, "SH_DISABLE_VFORK=", + NULL }, + { NULL, 0, NULL, + NULL } +}; + +static struct var *vartab[VTABSIZE]; + +static const char *const locale_names[7] = { + "LC_COLLATE", "LC_CTYPE", "LC_MONETARY", + "LC_NUMERIC", "LC_TIME", "LC_MESSAGES", NULL +}; +static const int locale_categories[7] = { + LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME, LC_MESSAGES, 0 +}; + +static int varequal(const char *, const char *); +static struct var *find_var(const char *, struct var ***, int *); +static int localevar(const char *); +static void setvareq_const(const char *s, int flags); + +extern char **environ; + +/* + * This routine initializes the builtin variables and imports the environment. + * It is called when the shell is initialized. + */ + +void +initvar(void) +{ + char ppid[20]; + const struct varinit *ip; + struct var *vp; + struct var **vpp; + char **envp; + + for (ip = varinit ; (vp = ip->var) != NULL ; ip++) { + if (find_var(ip->text, &vpp, &vp->name_len) != NULL) + continue; + vp->next = *vpp; + *vpp = vp; + vp->text = (char *)(ip->text); + vp->flags = ip->flags | VSTRFIXED | VTEXTFIXED; + vp->func = ip->func; + } + /* + * PS1 depends on uid + */ + if (find_var("PS1", &vpp, &vps1.name_len) == NULL) { + vps1.next = *vpp; + *vpp = &vps1; + vps1.text = (char *)(geteuid() ? "PS1=$ " : "PS1=# "); + vps1.flags = VSTRFIXED|VTEXTFIXED; + } + fmtstr(ppid, sizeof(ppid), "%d", (int)getppid()); + setvarsafe("PPID", ppid, 0); + for (envp = environ ; *envp ; envp++) { + if (strchr(*envp, '=')) { + setvareq(*envp, VEXPORT|VTEXTFIXED); + } + } + setvareq_const("OPTIND=1", 0); + setvareq_const("IFS= \t\n", 0); +} + +/* + * Safe version of setvar, returns 1 on success 0 on failure. + */ + +int +setvarsafe(const char *name, const char *val, int flags) +{ + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + int err = 0; + int inton; + + inton = is_int_on(); + if (setjmp(jmploc.loc)) + err = 1; + else { + handler = &jmploc; + setvar(name, val, flags); + } + handler = savehandler; + SETINTON(inton); + return err; +} + +/* + * Set the value of a variable. The flags argument is stored with the + * flags of the variable. If val is NULL, the variable is unset. + */ + +void +setvar(const char *name, const char *val, int flags) +{ + const char *p; + size_t len; + size_t namelen; + size_t vallen; + char *nameeq; + int isbad; + + isbad = 0; + p = name; + if (! is_name(*p)) + isbad = 1; + p++; + for (;;) { + if (! is_in_name(*p)) { + if (*p == '\0' || *p == '=') + break; + isbad = 1; + } + p++; + } + namelen = p - name; + if (isbad) + error("%.*s: bad variable name", (int)namelen, name); + len = namelen + 2; /* 2 is space for '=' and '\0' */ + if (val == NULL) { + flags |= VUNSET; + vallen = 0; + } else { + vallen = strlen(val); + len += vallen; + } + INTOFF; + nameeq = ckmalloc(len); + memcpy(nameeq, name, namelen); + nameeq[namelen] = '='; + if (val) + memcpy(nameeq + namelen + 1, val, vallen + 1); + else + nameeq[namelen + 1] = '\0'; + setvareq(nameeq, flags); + INTON; +} + +static int +localevar(const char *s) +{ + const char *const *ss; + + if (*s != 'L') + return 0; + if (varequal(s + 1, "ANG")) + return 1; + if (strncmp(s + 1, "C_", 2) != 0) + return 0; + if (varequal(s + 3, "ALL")) + return 1; + for (ss = locale_names; *ss ; ss++) + if (varequal(s + 3, *ss + 3)) + return 1; + return 0; +} + + +/* + * Sets/unsets an environment variable from a pointer that may actually be a + * pointer into environ where the string should not be manipulated. + */ +static void +change_env(const char *s, int set) +{ + char *eqp; + char *ss; + + INTOFF; + ss = savestr(s); + if ((eqp = strchr(ss, '=')) != NULL) + *eqp = '\0'; + if (set && eqp != NULL) + (void) setenv(ss, eqp + 1, 1); + else + (void) unsetenv(ss); + ckfree(ss); + INTON; + + return; +} + + +/* + * 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. + */ + +void +setvareq(char *s, int flags) +{ + struct var *vp, **vpp; + int nlen; + + if (aflag) + flags |= VEXPORT; + if (forcelocal && !(flags & (VNOSET | VNOLOCAL))) + mklocal(s); + vp = find_var(s, &vpp, &nlen); + if (vp != NULL) { + if (vp->flags & VREADONLY) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + error("%.*s: is read only", vp->name_len, vp->text); + } + if (flags & VNOSET) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + return; + } + INTOFF; + + if (vp->func && (flags & VNOFUNC) == 0) + (*vp->func)(s + vp->name_len + 1); + + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(vp->text); + + vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET); + vp->flags |= flags; + vp->text = s; + + /* + * We could roll this to a function, to handle it as + * a regular variable function callback, but why bother? + * + * Note: this assumes iflag is not set to 1 initially. + * As part of initvar(), this is called before arguments + * are looked at. + */ + if ((vp == &vmpath || (vp == &vmail && ! mpathset())) && + iflag == 1) + chkmail(1); + if ((vp->flags & VEXPORT) && localevar(s)) { + change_env(s, 1); + (void) setlocale(LC_ALL, ""); + updatecharset(); + } + INTON; + return; + } + /* not found */ + if (flags & VNOSET) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + return; + } + INTOFF; + vp = ckmalloc(sizeof (*vp)); + vp->flags = flags; + vp->text = s; + vp->name_len = nlen; + vp->next = *vpp; + vp->func = NULL; + *vpp = vp; + if ((vp->flags & VEXPORT) && localevar(s)) { + change_env(s, 1); + (void) setlocale(LC_ALL, ""); + updatecharset(); + } + INTON; +} + + +static void +setvareq_const(const char *s, int flags) +{ + setvareq((char *)(s), flags | VTEXTFIXED); +} + + +/* + * Process a linked list of variable assignments. + */ + +void +listsetvar(struct arglist *list, int flags) +{ + int i; + + INTOFF; + for (i = 0; i < list->count; i++) + setvareq(savestr(list->args[i]), flags); + INTON; +} + + + +/* + * Find the value of a variable. Returns NULL if not set. + */ + +char * +lookupvar(const char *name) +{ + struct var *v; + + v = find_var(name, NULL, NULL); + if (v == NULL || v->flags & VUNSET) + return NULL; + return v->text + v->name_len + 1; +} + + + +/* + * Search the environment of a builtin command. If the second argument + * is nonzero, return the value of a variable even if it hasn't been + * exported. + */ + +char * +bltinlookup(const char *name, int doall) +{ + struct var *v; + char *result; + int i; + + result = NULL; + if (cmdenviron) for (i = 0; i < cmdenviron->count; i++) { + if (varequal(cmdenviron->args[i], name)) + result = strchr(cmdenviron->args[i], '=') + 1; + } + if (result != NULL) + return result; + + v = find_var(name, NULL, NULL); + if (v == NULL || v->flags & VUNSET || + (!doall && (v->flags & VEXPORT) == 0)) + return NULL; + return v->text + v->name_len + 1; +} + + +/* + * Set up locale for a builtin (LANG/LC_* assignments). + */ +void +bltinsetlocale(void) +{ + int act = 0; + char *loc, *locdef; + int i; + + if (cmdenviron) for (i = 0; i < cmdenviron->count; i++) { + if (localevar(cmdenviron->args[i])) { + act = 1; + break; + } + } + if (!act) + return; + loc = bltinlookup("LC_ALL", 0); + INTOFF; + if (loc != NULL) { + setlocale(LC_ALL, loc); + INTON; + updatecharset(); + return; + } + locdef = bltinlookup("LANG", 0); + for (i = 0; locale_names[i] != NULL; i++) { + loc = bltinlookup(locale_names[i], 0); + if (loc == NULL) + loc = locdef; + if (loc != NULL) + setlocale(locale_categories[i], loc); + } + INTON; + updatecharset(); +} + +/* + * Undo the effect of bltinlocaleset(). + */ +void +bltinunsetlocale(void) +{ + int i; + + INTOFF; + if (cmdenviron) for (i = 0; i < cmdenviron->count; i++) { + if (localevar(cmdenviron->args[i])) { + setlocale(LC_ALL, ""); + updatecharset(); + break; + } + } + INTON; +} + +/* + * Update the localeisutf8 flag. + */ +void +updatecharset(void) +{ + char *charset; + + charset = nl_langinfo(CODESET); + localeisutf8 = !strcmp(charset, "UTF-8"); +} + +void +initcharset(void) +{ + updatecharset(); + initial_localeisutf8 = localeisutf8; +} + +/* + * Generate a list of exported variables. This routine is used to construct + * the third argument to execve when executing a program. + */ + +char ** +environment(void) +{ + int nenv; + struct var **vpp; + struct var *vp; + char **env, **ep; + + nenv = 0; + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) + if (vp->flags & VEXPORT) + nenv++; + } + ep = env = stalloc((nenv + 1) * sizeof *env); + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) + if (vp->flags & VEXPORT) + *ep++ = vp->text; + } + *ep = NULL; + return env; +} + + +static int +var_compare(const void *a, const void *b) +{ + const char *const *sa, *const *sb; + + sa = a; + sb = b; + /* + * This compares two var=value strings which creates a different + * order from what you would probably expect. POSIX is somewhat + * ambiguous on what should be sorted exactly. + */ + return strcoll(*sa, *sb); +} + + +/* + * Command to list all variables which are set. This is invoked from the + * set command when it is called without any options or operands. + */ + +int +showvarscmd(int argc __unused, char **argv __unused) +{ + struct var **vpp; + struct var *vp; + const char *s; + const char **vars; + int i, n; + + /* + * POSIX requires us to sort the variables. + */ + n = 0; + for (vpp = vartab; vpp < vartab + VTABSIZE; vpp++) { + for (vp = *vpp; vp; vp = vp->next) { + if (!(vp->flags & VUNSET)) + n++; + } + } + + INTOFF; + vars = ckmalloc(n * sizeof(*vars)); + i = 0; + for (vpp = vartab; vpp < vartab + VTABSIZE; vpp++) { + for (vp = *vpp; vp; vp = vp->next) { + if (!(vp->flags & VUNSET)) + vars[i++] = vp->text; + } + } + + qsort(vars, n, sizeof(*vars), var_compare); + for (i = 0; i < n; i++) { + /* + * Skip improper variable names so the output remains usable as + * shell input. + */ + if (!isassignment(vars[i])) + continue; + s = strchr(vars[i], '='); + s++; + outbin(vars[i], s - vars[i], out1); + out1qstr(s); + out1c('\n'); + } + ckfree(vars); + INTON; + + return 0; +} + + + +/* + * The export and readonly commands. + */ + +int +exportcmd(int argc __unused, char **argv) +{ + struct var **vpp; + struct var *vp; + char **ap; + char *name; + char *p; + char *cmdname; + int ch, values; + int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT; + + cmdname = argv[0]; + values = 0; + while ((ch = nextopt("p")) != '\0') { + switch (ch) { + case 'p': + values = 1; + break; + } + } + + if (values && *argptr != NULL) + error("-p requires no arguments"); + if (*argptr != NULL) { + for (ap = argptr; (name = *ap) != NULL; ap++) { + if ((p = strchr(name, '=')) != NULL) { + p++; + } else { + vp = find_var(name, NULL, NULL); + if (vp != NULL) { + vp->flags |= flag; + if ((vp->flags & VEXPORT) && localevar(vp->text)) { + change_env(vp->text, 1); + (void) setlocale(LC_ALL, ""); + updatecharset(); + } + continue; + } + } + setvar(name, p, flag); + } + } else { + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) { + if (vp->flags & flag) { + if (values) { + /* + * Skip improper variable names + * so the output remains usable + * as shell input. + */ + if (!isassignment(vp->text)) + continue; + out1str(cmdname); + out1c(' '); + } + if (values && !(vp->flags & VUNSET)) { + outbin(vp->text, + vp->name_len + 1, out1); + out1qstr(vp->text + + vp->name_len + 1); + } else + outbin(vp->text, vp->name_len, + out1); + out1c('\n'); + } + } + } + } + return 0; +} + + +/* + * The "local" command. + */ + +int +localcmd(int argc __unused, char **argv __unused) +{ + char *name; + + nextopt(""); + if (! in_function()) + error("Not in a function"); + while ((name = *argptr++) != NULL) { + mklocal(name); + } + 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) +{ + struct localvar *lvp; + struct var **vpp; + struct var *vp; + + INTOFF; + lvp = ckmalloc(sizeof (struct localvar)); + if (name[0] == '-' && name[1] == '\0') { + lvp->text = ckmalloc(sizeof optval); + memcpy(lvp->text, optval, sizeof optval); + vp = NULL; + } else { + vp = find_var(name, &vpp, NULL); + if (vp == NULL) { + if (strchr(name, '=')) + setvareq(savestr(name), VSTRFIXED | VNOLOCAL); + else + setvar(name, NULL, VSTRFIXED | VNOLOCAL); + vp = *vpp; /* the new variable */ + lvp->text = NULL; + lvp->flags = VUNSET; + } else { + lvp->text = vp->text; + lvp->flags = vp->flags; + vp->flags |= VSTRFIXED|VTEXTFIXED; + if (name[vp->name_len] == '=') + setvareq(savestr(name), VNOLOCAL); + } + } + lvp->vp = vp; + lvp->next = localvars; + localvars = lvp; + INTON; +} + + +/* + * Called after a function returns. + */ + +void +poplocalvars(void) +{ + struct localvar *lvp; + struct var *vp; + int islocalevar; + + INTOFF; + while ((lvp = localvars) != NULL) { + localvars = lvp->next; + vp = lvp->vp; + if (vp == NULL) { /* $- saved */ + memcpy(optval, lvp->text, sizeof optval); + ckfree(lvp->text); + optschanged(); + } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + vp->flags &= ~VREADONLY; + (void)unsetvar(vp->text); + } else { + islocalevar = (vp->flags | lvp->flags) & VEXPORT && + localevar(lvp->text); + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + vp->flags = lvp->flags; + vp->text = lvp->text; + if (vp->func) + (*vp->func)(vp->text + vp->name_len + 1); + if (islocalevar) { + change_env(vp->text, vp->flags & VEXPORT && + (vp->flags & VUNSET) == 0); + setlocale(LC_ALL, ""); + updatecharset(); + } + } + ckfree(lvp); + } + INTON; +} + + +int +setvarcmd(int argc, char **argv) +{ + if (argc <= 2) + return unsetcmd(argc, argv); + else if (argc == 3) + setvar(argv[1], argv[2], 0); + else + error("too many arguments"); + return 0; +} + + +/* + * The unset builtin command. + */ + +int +unsetcmd(int argc __unused, char **argv __unused) +{ + char **ap; + int i; + int flg_func = 0; + int flg_var = 0; + int ret = 0; + + while ((i = nextopt("vf")) != '\0') { + if (i == 'f') + flg_func = 1; + else + flg_var = 1; + } + if (flg_func == 0 && flg_var == 0) + flg_var = 1; + + INTOFF; + for (ap = argptr; *ap ; ap++) { + if (flg_func) + ret |= unsetfunc(*ap); + if (flg_var) + ret |= unsetvar(*ap); + } + INTON; + return ret; +} + + +/* + * Unset the specified variable. + * Called with interrupts off. + */ + +int +unsetvar(const char *s) +{ + struct var **vpp; + struct var *vp; + + vp = find_var(s, &vpp, NULL); + if (vp == NULL) + return (0); + if (vp->flags & VREADONLY) + return (1); + if (vp->text[vp->name_len + 1] != '\0') + setvar(s, "", 0); + if ((vp->flags & VEXPORT) && localevar(vp->text)) { + change_env(s, 0); + setlocale(LC_ALL, ""); + updatecharset(); + } + vp->flags &= ~VEXPORT; + vp->flags |= VUNSET; + if ((vp->flags & VSTRFIXED) == 0) { + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + *vpp = vp->next; + ckfree(vp); + } + return (0); +} + + + +/* + * Returns true if the two strings specify the same variable. The first + * variable name is terminated by '='; the second may be terminated by + * either '=' or '\0'. + */ + +static int +varequal(const char *p, const char *q) +{ + while (*p == *q++) { + if (*p++ == '=') + return 1; + } + if (*p == '=' && *(q - 1) == '\0') + return 1; + return 0; +} + +/* + * Search for a variable. + * 'name' may be terminated by '=' or a NUL. + * vppp is set to the pointer to vp, or the list head if vp isn't found + * lenp is set to the number of characters in 'name' + */ + +static struct var * +find_var(const char *name, struct var ***vppp, int *lenp) +{ + unsigned int hashval; + int len; + struct var *vp, **vpp; + const char *p = name; + + hashval = 0; + while (*p && *p != '=') + hashval = 2 * hashval + (unsigned char)*p++; + len = p - name; + + if (lenp) + *lenp = len; + vpp = &vartab[hashval % VTABSIZE]; + if (vppp) + *vppp = vpp; + + for (vp = *vpp ; vp ; vpp = &vp->next, vp = *vpp) { + if (vp->name_len != len) + continue; + if (memcmp(vp->text, name, len) != 0) + continue; + if (vppp) + *vppp = vpp; + return vp; + } + return NULL; +} diff --git a/bin/1sh/var.h b/bin/1sh/var.h new file mode 100644 index 00000000..d06d8688 --- /dev/null +++ b/bin/1sh/var.h @@ -0,0 +1,140 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1991, 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. + * + * @(#)var.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: releng/12.1/bin/sh/var.h 326025 2017-11-20 19:49:47Z pfg $ + */ + +/* + * 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 VNOLOCAL 0x100 /* ignore forcelocal */ + + +struct var { + struct var *next; /* next entry in hash list */ + int flags; /* flags are defined above */ + int name_len; /* length of name */ + 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 */ + char *text; /* saved text */ +}; + + +extern struct localvar *localvars; +extern int forcelocal; + +extern struct var vifs; +extern struct var vmail; +extern struct var vmpath; +extern struct var vpath; +extern struct var vps0; +extern struct var vps1; +extern struct var vps2; +extern struct var vps4; +extern struct var vrps1; +extern struct var vrps2; +extern struct var vdisvfork; +#ifndef NO_HISTORY +extern struct var vhistfile; +extern struct var vhistsize; +extern struct var vterm; +#endif + +extern int localeisutf8; +/* The parser uses the locale that was in effect at startup. */ +extern int initial_localeisutf8; + +/* + * 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 ps0val() (vps0.text + 4) +#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) +#ifndef NO_HISTORY +#define histfileval() (vhistfile.text + 9) +#define histsizeval() (vhistsize.text + 9) +#define termval() (vterm.text + 5) +#endif + +#define mpathset() ((vmpath.flags & VUNSET) == 0) +#define disvforkset() ((vdisvfork.flags & VUNSET) == 0) + +void initvar(void); +void setvar(const char *, const char *, int); +void setvareq(char *, int); +struct arglist; +void listsetvar(struct arglist *, int); +char *lookupvar(const char *); +char *bltinlookup(const char *, int); +void bltinsetlocale(void); +void bltinunsetlocale(void); +void updatecharset(void); +void initcharset(void); +char **environment(void); +int showvarscmd(int, char **); +void mklocal(char *); +void poplocalvars(void); +int unsetvar(const char *); +int setvarsafe(const char *, const char *, int); 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..556f3088 --- /dev/null +++ b/bin/beef.c @@ -0,0 +1,141 @@ +/* Copyright (C) 2019 June 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..7843419e --- /dev/null +++ b/bin/bit.y @@ -0,0 +1,194 @@ +/* Copyright (C) 2019 June 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..c9bf6b0c --- /dev/null +++ b/bin/bri.c @@ -0,0 +1,85 @@ +/* Copyright (C) 2017 June 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/COPYING b/bin/dash/COPYING index 37f8189c..37f8189c 100644 --- a/COPYING +++ b/bin/dash/COPYING diff --git a/ChangeLog b/bin/dash/ChangeLog index 406e20c0..406e20c0 100644 --- a/ChangeLog +++ b/bin/dash/ChangeLog diff --git a/ChangeLog.O b/bin/dash/ChangeLog.O index dfdb2cec..dfdb2cec 100644 --- a/ChangeLog.O +++ b/bin/dash/ChangeLog.O diff --git a/Makefile.am b/bin/dash/Makefile.am index af437a64..af437a64 100644 --- a/Makefile.am +++ b/bin/dash/Makefile.am diff --git a/autogen.sh b/bin/dash/autogen.sh index 9879c53e..9879c53e 100755 --- a/autogen.sh +++ b/bin/dash/autogen.sh diff --git a/configure.ac b/bin/dash/configure.ac index 33f0b08c..33f0b08c 100644 --- a/configure.ac +++ b/bin/dash/configure.ac diff --git a/src/.gitignore b/bin/dash/src/.gitignore index 644eccb8..644eccb8 100644 --- a/src/.gitignore +++ b/bin/dash/src/.gitignore diff --git a/src/Makefile.am b/bin/dash/src/Makefile.am index 17324653..17324653 100644 --- a/src/Makefile.am +++ b/bin/dash/src/Makefile.am diff --git a/src/TOUR b/bin/dash/src/TOUR index e30836e1..e30836e1 100644 --- a/src/TOUR +++ b/bin/dash/src/TOUR diff --git a/src/alias.c b/bin/dash/src/alias.c index daeacbb8..daeacbb8 100644 --- a/src/alias.c +++ b/bin/dash/src/alias.c diff --git a/src/alias.h b/bin/dash/src/alias.h index fb841d64..fb841d64 100644 --- a/src/alias.h +++ b/bin/dash/src/alias.h diff --git a/src/arith_yacc.c b/bin/dash/src/arith_yacc.c index 1a087c32..1a087c32 100644 --- a/src/arith_yacc.c +++ b/bin/dash/src/arith_yacc.c diff --git a/src/arith_yacc.h b/bin/dash/src/arith_yacc.h index ff34d524..ff34d524 100644 --- a/src/arith_yacc.h +++ b/bin/dash/src/arith_yacc.h diff --git a/src/arith_yylex.c b/bin/dash/src/arith_yylex.c index ec5b5b25..ec5b5b25 100644 --- a/src/arith_yylex.c +++ b/bin/dash/src/arith_yylex.c diff --git a/src/bltin/bltin.h b/bin/dash/src/bltin/bltin.h index f5ac06f2..f5ac06f2 100644 --- a/src/bltin/bltin.h +++ b/bin/dash/src/bltin/bltin.h diff --git a/src/bltin/echo.1 b/bin/dash/src/bltin/echo.1 index fbc7fb43..fbc7fb43 100644 --- a/src/bltin/echo.1 +++ b/bin/dash/src/bltin/echo.1 diff --git a/src/bltin/printf.1 b/bin/dash/src/bltin/printf.1 index 38731732..38731732 100644 --- a/src/bltin/printf.1 +++ b/bin/dash/src/bltin/printf.1 diff --git a/src/bltin/printf.c b/bin/dash/src/bltin/printf.c index 7785735b..7785735b 100644 --- a/src/bltin/printf.c +++ b/bin/dash/src/bltin/printf.c diff --git a/src/bltin/test.1 b/bin/dash/src/bltin/test.1 index 42435fb3..42435fb3 100644 --- a/src/bltin/test.1 +++ b/bin/dash/src/bltin/test.1 diff --git a/src/bltin/test.c b/bin/dash/src/bltin/test.c index c7fc479d..c7fc479d 100644 --- a/src/bltin/test.c +++ b/bin/dash/src/bltin/test.c diff --git a/src/bltin/times.c b/bin/dash/src/bltin/times.c index 1166a68e..1166a68e 100644 --- a/src/bltin/times.c +++ b/bin/dash/src/bltin/times.c diff --git a/src/builtins.def.in b/bin/dash/src/builtins.def.in index 95e420cc..95e420cc 100644 --- a/src/builtins.def.in +++ b/bin/dash/src/builtins.def.in diff --git a/src/cd.c b/bin/dash/src/cd.c index 1ef1dc56..1ef1dc56 100644 --- a/src/cd.c +++ b/bin/dash/src/cd.c diff --git a/src/cd.h b/bin/dash/src/cd.h index 87631619..87631619 100644 --- a/src/cd.h +++ b/bin/dash/src/cd.h diff --git a/src/dash.1 b/bin/dash/src/dash.1 index 32f6ac0d..32f6ac0d 100644 --- a/src/dash.1 +++ b/bin/dash/src/dash.1 diff --git a/src/error.c b/bin/dash/src/error.c index 728ff885..728ff885 100644 --- a/src/error.c +++ b/bin/dash/src/error.c diff --git a/src/error.h b/bin/dash/src/error.h index 94e30a27..94e30a27 100644 --- a/src/error.h +++ b/bin/dash/src/error.h diff --git a/src/eval.c b/bin/dash/src/eval.c index 1b5d61dc..1b5d61dc 100644 --- a/src/eval.c +++ b/bin/dash/src/eval.c diff --git a/src/eval.h b/bin/dash/src/eval.h index 63e7d865..63e7d865 100644 --- a/src/eval.h +++ b/bin/dash/src/eval.h diff --git a/src/exec.c b/bin/dash/src/exec.c index 87354d49..87354d49 100644 --- a/src/exec.c +++ b/bin/dash/src/exec.c diff --git a/src/exec.h b/bin/dash/src/exec.h index 423b07e6..423b07e6 100644 --- a/src/exec.h +++ b/bin/dash/src/exec.h diff --git a/src/expand.c b/bin/dash/src/expand.c index 1730670e..1730670e 100644 --- a/src/expand.c +++ b/bin/dash/src/expand.c diff --git a/src/expand.h b/bin/dash/src/expand.h index c44b8481..c44b8481 100644 --- a/src/expand.h +++ b/bin/dash/src/expand.h diff --git a/src/funcs/cmv b/bin/dash/src/funcs/cmv index 91a67c53..91a67c53 100644 --- a/src/funcs/cmv +++ b/bin/dash/src/funcs/cmv diff --git a/src/funcs/dirs b/bin/dash/src/funcs/dirs index 5f6ce635..5f6ce635 100644 --- a/src/funcs/dirs +++ b/bin/dash/src/funcs/dirs diff --git a/src/funcs/kill b/bin/dash/src/funcs/kill index c5df95f5..c5df95f5 100644 --- a/src/funcs/kill +++ b/bin/dash/src/funcs/kill diff --git a/src/funcs/login b/bin/dash/src/funcs/login index 215e5352..215e5352 100644 --- a/src/funcs/login +++ b/bin/dash/src/funcs/login diff --git a/src/funcs/newgrp b/bin/dash/src/funcs/newgrp index ec0e7e5a..ec0e7e5a 100644 --- a/src/funcs/newgrp +++ b/bin/dash/src/funcs/newgrp diff --git a/src/funcs/popd b/bin/dash/src/funcs/popd index 7bccf50d..7bccf50d 100644 --- a/src/funcs/popd +++ b/bin/dash/src/funcs/popd diff --git a/src/funcs/pushd b/bin/dash/src/funcs/pushd index 19ac8e08..19ac8e08 100644 --- a/src/funcs/pushd +++ b/bin/dash/src/funcs/pushd diff --git a/src/funcs/suspend b/bin/dash/src/funcs/suspend index 44844678..44844678 100644 --- a/src/funcs/suspend +++ b/bin/dash/src/funcs/suspend diff --git a/src/histedit.c b/bin/dash/src/histedit.c index f5c90aba..f5c90aba 100644 --- a/src/histedit.c +++ b/bin/dash/src/histedit.c diff --git a/src/init.h b/bin/dash/src/init.h index d56fb28e..d56fb28e 100644 --- a/src/init.h +++ b/bin/dash/src/init.h diff --git a/src/input.c b/bin/dash/src/input.c index 17544e78..17544e78 100644 --- a/src/input.c +++ b/bin/dash/src/input.c diff --git a/src/input.h b/bin/dash/src/input.h index 8acc6e9f..8acc6e9f 100644 --- a/src/input.h +++ b/bin/dash/src/input.h diff --git a/src/jobs.c b/bin/dash/src/jobs.c index d4c13c0f..d4c13c0f 100644 --- a/src/jobs.c +++ b/bin/dash/src/jobs.c diff --git a/src/jobs.h b/bin/dash/src/jobs.h index 6ac6c56d..6ac6c56d 100644 --- a/src/jobs.h +++ b/bin/dash/src/jobs.h diff --git a/src/machdep.h b/bin/dash/src/machdep.h index f2ff0ad8..f2ff0ad8 100644 --- a/src/machdep.h +++ b/bin/dash/src/machdep.h diff --git a/src/mail.c b/bin/dash/src/mail.c index 8eacb2d0..8eacb2d0 100644 --- a/src/mail.c +++ b/bin/dash/src/mail.c diff --git a/src/mail.h b/bin/dash/src/mail.h index 3c6b21d2..3c6b21d2 100644 --- a/src/mail.h +++ b/bin/dash/src/mail.h diff --git a/src/main.c b/bin/dash/src/main.c index 7a285346..7a285346 100644 --- a/src/main.c +++ b/bin/dash/src/main.c diff --git a/src/main.h b/bin/dash/src/main.h index 19e49835..19e49835 100644 --- a/src/main.h +++ b/bin/dash/src/main.h diff --git a/src/memalloc.c b/bin/dash/src/memalloc.c index 60637da1..60637da1 100644 --- a/src/memalloc.c +++ b/bin/dash/src/memalloc.c diff --git a/src/memalloc.h b/bin/dash/src/memalloc.h index b9adf764..b9adf764 100644 --- a/src/memalloc.h +++ b/bin/dash/src/memalloc.h diff --git a/src/miscbltin.c b/bin/dash/src/miscbltin.c index 5ccbbcb8..5ccbbcb8 100644 --- a/src/miscbltin.c +++ b/bin/dash/src/miscbltin.c diff --git a/src/miscbltin.h b/bin/dash/src/miscbltin.h index dd9a8d1c..dd9a8d1c 100644 --- a/src/miscbltin.h +++ b/bin/dash/src/miscbltin.h diff --git a/src/mkbuiltins b/bin/dash/src/mkbuiltins index f1f25932..f1f25932 100644 --- a/src/mkbuiltins +++ b/bin/dash/src/mkbuiltins diff --git a/src/mkinit.c b/bin/dash/src/mkinit.c index 9025862e..9025862e 100644 --- a/src/mkinit.c +++ b/bin/dash/src/mkinit.c diff --git a/src/mknodes.c b/bin/dash/src/mknodes.c index 1903a605..1903a605 100644 --- a/src/mknodes.c +++ b/bin/dash/src/mknodes.c diff --git a/src/mksignames.c b/bin/dash/src/mksignames.c index a832eab9..a832eab9 100644 --- a/src/mksignames.c +++ b/bin/dash/src/mksignames.c diff --git a/src/mksyntax.c b/bin/dash/src/mksyntax.c index a23c18ca..a23c18ca 100644 --- a/src/mksyntax.c +++ b/bin/dash/src/mksyntax.c diff --git a/src/mktokens b/bin/dash/src/mktokens index 78055be8..78055be8 100644 --- a/src/mktokens +++ b/bin/dash/src/mktokens diff --git a/src/myhistedit.h b/bin/dash/src/myhistedit.h index 22e5c438..22e5c438 100644 --- a/src/myhistedit.h +++ b/bin/dash/src/myhistedit.h diff --git a/src/mystring.c b/bin/dash/src/mystring.c index de624b89..de624b89 100644 --- a/src/mystring.c +++ b/bin/dash/src/mystring.c diff --git a/src/mystring.h b/bin/dash/src/mystring.h index 083ea98c..083ea98c 100644 --- a/src/mystring.h +++ b/bin/dash/src/mystring.h diff --git a/src/nodes.c.pat b/bin/dash/src/nodes.c.pat index 9125bc73..9125bc73 100644 --- a/src/nodes.c.pat +++ b/bin/dash/src/nodes.c.pat diff --git a/src/nodetypes b/bin/dash/src/nodetypes index ceaf478c..ceaf478c 100644 --- a/src/nodetypes +++ b/bin/dash/src/nodetypes diff --git a/src/options.c b/bin/dash/src/options.c index a46c23b9..a46c23b9 100644 --- a/src/options.c +++ b/bin/dash/src/options.c diff --git a/src/options.h b/bin/dash/src/options.h index 975fe339..975fe339 100644 --- a/src/options.h +++ b/bin/dash/src/options.h diff --git a/src/output.c b/bin/dash/src/output.c index e9ee9b4d..e9ee9b4d 100644 --- a/src/output.c +++ b/bin/dash/src/output.c diff --git a/src/output.h b/bin/dash/src/output.h index c43d4937..c43d4937 100644 --- a/src/output.h +++ b/bin/dash/src/output.h diff --git a/src/parser.c b/bin/dash/src/parser.c index a47022e0..a47022e0 100644 --- a/src/parser.c +++ b/bin/dash/src/parser.c diff --git a/src/parser.h b/bin/dash/src/parser.h index 524ac1c7..524ac1c7 100644 --- a/src/parser.h +++ b/bin/dash/src/parser.h diff --git a/src/redir.c b/bin/dash/src/redir.c index 895140c3..895140c3 100644 --- a/src/redir.c +++ b/bin/dash/src/redir.c diff --git a/src/redir.h b/bin/dash/src/redir.h index 1cf27616..1cf27616 100644 --- a/src/redir.h +++ b/bin/dash/src/redir.h diff --git a/src/shell.h b/bin/dash/src/shell.h index 98edc8be..98edc8be 100644 --- a/src/shell.h +++ b/bin/dash/src/shell.h diff --git a/src/show.c b/bin/dash/src/show.c index 4a049e93..4a049e93 100644 --- a/src/show.c +++ b/bin/dash/src/show.c diff --git a/src/show.h b/bin/dash/src/show.h index d0ccac79..d0ccac79 100644 --- a/src/show.h +++ b/bin/dash/src/show.h diff --git a/src/system.c b/bin/dash/src/system.c index 844a6410..844a6410 100644 --- a/src/system.c +++ b/bin/dash/src/system.c diff --git a/src/system.h b/bin/dash/src/system.h index 007952c5..007952c5 100644 --- a/src/system.h +++ b/bin/dash/src/system.h diff --git a/src/trap.c b/bin/dash/src/trap.c index cd84814f..cd84814f 100644 --- a/src/trap.c +++ b/bin/dash/src/trap.c diff --git a/src/trap.h b/bin/dash/src/trap.h index beaf6605..beaf6605 100644 --- a/src/trap.h +++ b/bin/dash/src/trap.h diff --git a/src/var.c b/bin/dash/src/var.c index ef9c2bde..ef9c2bde 100644 --- a/src/var.c +++ b/bin/dash/src/var.c diff --git a/src/var.h b/bin/dash/src/var.h index aa7575a7..aa7575a7 100644 --- a/src/var.h +++ b/bin/dash/src/var.h diff --git a/bin/dtch.c b/bin/dtch.c new file mode 100644 index 00000000..026493dd --- /dev/null +++ b/bin/dtch.c @@ -0,0 +1,271 @@ +/* Copyright (C) 2017-2019 June 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..7838af4b --- /dev/null +++ b/bin/ever.c @@ -0,0 +1,116 @@ +/* Copyright (C) 2017 June 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..8b7e6e40 --- /dev/null +++ b/bin/fbatt.c @@ -0,0 +1,123 @@ +/* Copyright (C) 2018 June 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..e1e418b9 --- /dev/null +++ b/bin/fbclock.c @@ -0,0 +1,132 @@ +/* Copyright (C) 2018 June 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..acb2615c --- /dev/null +++ b/bin/glitch.c @@ -0,0 +1,538 @@ +/* Copyright (C) 2018 June 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..23d69574 --- /dev/null +++ b/bin/hi.c @@ -0,0 +1,766 @@ +/* vim: set foldmethod=marker foldlevel=0: */ +/* Copyright (C) 2019 June 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(®ex, pattern, REG_EXTENDED | flags); + if (!error) return regex; + char buf[256]; + regerror(error, ®ex, 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( + ®ex, &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(®ex); + } +} + +static void check(void) { + for (size_t i = 0; i < ARRAY_LEN(Languages); ++i) { + regex_t regex = compile(Languages[i].pattern, REG_NOSUB); + regfree(®ex); + 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(®ex); + } + } +} + +#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("""); + break; case '&': run = 1; printf("&"); + break; case '<': run = 1; printf("<"); + break; case '>': run = 1; printf(">"); + 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(®ex, name, 0, NULL, 0); + regfree(®ex); + 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..4fcc42a1 --- /dev/null +++ b/bin/hnel.c @@ -0,0 +1,120 @@ +/* Copyright (C) 2017 June 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..4392e071 --- /dev/null +++ b/bin/modem.c @@ -0,0 +1,102 @@ +/* Copyright (C) 2018 June 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..8ae916eb --- /dev/null +++ b/bin/nudge.c @@ -0,0 +1,78 @@ +/* Copyright (C) 2020 June 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..95f1036e --- /dev/null +++ b/bin/order.y @@ -0,0 +1,193 @@ +/* Copyright (C) 2019 June 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..58326053 --- /dev/null +++ b/bin/pbd.c @@ -0,0 +1,152 @@ +/* Copyright (C) 2017 June 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..0df4699b --- /dev/null +++ b/bin/png.h @@ -0,0 +1,108 @@ +/* Copyright (C) 2018 June 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..080e0b95 --- /dev/null +++ b/bin/pngo.c @@ -0,0 +1,812 @@ +/* Copyright (C) 2018 June 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..c36238a0 --- /dev/null +++ b/bin/psf2png.c @@ -0,0 +1,107 @@ +/* Copyright (C) 2018 June 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..3f72816a --- /dev/null +++ b/bin/psfed.c @@ -0,0 +1,577 @@ +/* Copyright (C) 2018 June 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..6bc20fc5 --- /dev/null +++ b/bin/ptee.c @@ -0,0 +1,116 @@ +/* Copyright (C) 2019 June 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..fd799462 --- /dev/null +++ b/bin/relay.c @@ -0,0 +1,218 @@ +/* Copyright (C) 2019 June 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..9acb2e31 --- /dev/null +++ b/bin/scheme.c @@ -0,0 +1,256 @@ +/* Copyright (C) 2018, 2019 June 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..de7fc8ac --- /dev/null +++ b/bin/shotty.c @@ -0,0 +1,648 @@ +/* Copyright (C) 2019 June 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(¶m, 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("&"); + break; case '<': printf("<"); + break; case '>': printf(">"); + 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..47ff720a --- /dev/null +++ b/bin/title.c @@ -0,0 +1,211 @@ +/* Copyright (C) 2019 June 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(®ex, pattern, REG_EXTENDED | flags); + if (!error) return regex; + + char buf[256]; + regerror(error, ®ex, buf, sizeof(buf)); + errx(EX_SOFTWARE, "regcomp: %s: %s", buf, pattern); +} + +static const struct Entity { + wchar_t ch; + const char *name; +} Entities[] = { + { L'"', """ }, + { L'&', "&" }, + { L'<', "<" }, + { L'>', ">" }, + { L'', " " }, +}; + +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..b91c416d --- /dev/null +++ b/bin/ttpre.c @@ -0,0 +1,66 @@ +/* Copyright (C) 2018 June 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("&"); + break; case L'<': printf("<"); + break; case L'>': printf(">"); + 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..bba10c40 --- /dev/null +++ b/bin/when.y @@ -0,0 +1,250 @@ +/* Copyright (C) 2019 June 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..39d7ec07 --- /dev/null +++ b/bin/xx.c @@ -0,0 +1,142 @@ +/* Copyright (C) 2017 June 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/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="
"/> + <key code="37" output="l"/> + <key code="38" output="j"/> + <key code="39" output="'"/> + <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="	"/> + <key action="5" code="49"/> + <key code="50" output="`"/> + <key code="51" output=""/> + <key code="52" output=""/> + <key code="53" output=""/> + <key code="64" output=""/> + <key code="65" output="."/> + <key code="66" output=""/> + <key code="67" output="*"/> + <key code="69" output="+"/> + <key code="70" output=""/> + <key code="71" output=""/> + <key code="72" output=""/> + <key code="75" output="/"/> + <key code="76" output=""/> + <key code="77" output=""/> + <key code="78" output="-"/> + <key code="79" output=""/> + <key code="80" output=""/> + <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=""/> + <key code="97" output=""/> + <key code="98" output=""/> + <key code="99" output=""/> + <key code="100" output=""/> + <key code="101" output=""/> + <key code="102" output=""/> + <key code="103" output=""/> + <key code="104" output=""/> + <key code="105" output=""/> + <key code="106" output=""/> + <key code="107" output=""/> + <key code="108" output=""/> + <key code="109" output=""/> + <key code="110" output=""/> + <key code="111" output=""/> + <key code="112" output=""/> + <key code="113" output=""/> + <key code="114" output=""/> + <key code="115" output=""/> + <key code="116" output=""/> + <key code="117" output=""/> + <key code="118" output=""/> + <key code="119" output=""/> + <key code="120" output=""/> + <key code="121" output=""/> + <key code="122" output=""/> + <key code="123" output=""/> + <key code="124" output=""/> + <key code="125" output=""/> + <key code="126" output=""/> + </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="&"/> + <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="
"/> + <key code="37" output="L"/> + <key code="38" output="J"/> + <key code="39" output="""/> + <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="	"/> + <key action="5" code="49"/> + <key code="50" output="~"/> + <key code="51" output=""/> + <key code="52" output=""/> + <key code="53" output=""/> + <key code="64" output=""/> + <key code="65" output="."/> + <key code="66" output="*"/> + <key code="67" output="*"/> + <key code="69" output="+"/> + <key code="70" output="+"/> + <key code="71" output=""/> + <key code="72" output="="/> + <key code="75" output="/"/> + <key code="76" output=""/> + <key code="77" output="/"/> + <key code="78" output="-"/> + <key code="79" output=""/> + <key code="80" output=""/> + <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=""/> + <key code="97" output=""/> + <key code="98" output=""/> + <key code="99" output=""/> + <key code="100" output=""/> + <key code="101" output=""/> + <key code="102" output=""/> + <key code="103" output=""/> + <key code="104" output=""/> + <key code="105" output=""/> + <key code="106" output=""/> + <key code="107" output=""/> + <key code="108" output=""/> + <key code="109" output=""/> + <key code="110" output=""/> + <key code="111" output=""/> + <key code="112" output=""/> + <key code="113" output=""/> + <key code="114" output=""/> + <key code="115" output=""/> + <key code="116" output=""/> + <key code="117" output=""/> + <key code="118" output=""/> + <key code="119" output=""/> + <key code="120" output=""/> + <key code="121" output=""/> + <key code="122" output=""/> + <key code="123" output=""/> + <key code="124" output=""/> + <key code="125" output=""/> + <key code="126" output=""/> + </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="
"/> + <key code="37" output="L"/> + <key code="38" output="J"/> + <key code="39" output="'"/> + <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="	"/> + <key action="5" code="49"/> + <key code="50" output="`"/> + <key code="51" output=""/> + <key code="52" output=""/> + <key code="53" output=""/> + <key code="64" output=""/> + <key code="65" output="."/> + <key code="66" output=""/> + <key code="67" output="*"/> + <key code="69" output="+"/> + <key code="70" output=""/> + <key code="71" output=""/> + <key code="72" output=""/> + <key code="75" output="/"/> + <key code="76" output=""/> + <key code="77" output=""/> + <key code="78" output="-"/> + <key code="79" output=""/> + <key code="80" output=""/> + <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=""/> + <key code="97" output=""/> + <key code="98" output=""/> + <key code="99" output=""/> + <key code="100" output=""/> + <key code="101" output=""/> + <key code="102" output=""/> + <key code="103" output=""/> + <key code="104" output=""/> + <key code="105" output=""/> + <key code="106" output=""/> + <key code="107" output=""/> + <key code="108" output=""/> + <key code="109" output=""/> + <key code="110" output=""/> + <key code="111" output=""/> + <key code="112" output=""/> + <key code="113" output=""/> + <key code="114" output=""/> + <key code="115" output=""/> + <key code="116" output=""/> + <key code="117" output=""/> + <key code="118" output=""/> + <key code="119" output=""/> + <key code="120" output=""/> + <key code="121" output=""/> + <key code="122" output=""/> + <key code="123" output=""/> + <key code="124" output=""/> + <key code="125" output=""/> + <key code="126" output=""/> + </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="
"/> + <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="	"/> + <key code="49" output=" "/> + <key action="1" code="50"/> + <key code="51" output=""/> + <key code="52" output=""/> + <key code="53" output=""/> + <key code="64" output=""/> + <key code="65" output="."/> + <key code="66" output=""/> + <key code="67" output="*"/> + <key code="69" output="+"/> + <key code="70" output=""/> + <key code="71" output=""/> + <key code="72" output=""/> + <key code="75" output="/"/> + <key code="76" output=""/> + <key code="77" output=""/> + <key code="78" output="-"/> + <key code="79" output=""/> + <key code="80" output=""/> + <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=""/> + <key code="97" output=""/> + <key code="98" output=""/> + <key code="99" output=""/> + <key code="100" output=""/> + <key code="101" output=""/> + <key code="102" output=""/> + <key code="103" output=""/> + <key code="104" output=""/> + <key code="105" output=""/> + <key code="106" output=""/> + <key code="107" output=""/> + <key code="108" output=""/> + <key code="109" output=""/> + <key code="110" output=""/> + <key code="111" output=""/> + <key code="112" output=""/> + <key code="113" output=""/> + <key code="114" output=""/> + <key code="115" output=""/> + <key code="116" output=""/> + <key code="117" output=""/> + <key code="118" output=""/> + <key code="119" output=""/> + <key code="120" output=""/> + <key code="121" output=""/> + <key code="122" output=""/> + <key code="123" output=""/> + <key code="124" output=""/> + <key code="125" output=""/> + <key code="126" output=""/> + </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="fi"/> + <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="
"/> + <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="	"/> + <key code="49" output=" "/> + <key code="50" output="`"/> + <key code="51" output=""/> + <key code="52" output=""/> + <key code="53" output=""/> + <key code="64" output=""/> + <key code="65" output="."/> + <key code="66" output="*"/> + <key code="67" output="*"/> + <key code="69" output="+"/> + <key code="70" output="+"/> + <key code="71" output=""/> + <key code="72" output="="/> + <key code="75" output="/"/> + <key code="76" output=""/> + <key code="77" output="/"/> + <key code="78" output="-"/> + <key code="79" output=""/> + <key code="80" output=""/> + <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=""/> + <key code="97" output=""/> + <key code="98" output=""/> + <key code="99" output=""/> + <key code="100" output=""/> + <key code="101" output=""/> + <key code="102" output=""/> + <key code="103" output=""/> + <key code="104" output=""/> + <key code="105" output=""/> + <key code="106" output=""/> + <key code="107" output=""/> + <key code="108" output=""/> + <key code="109" output=""/> + <key code="110" output=""/> + <key code="111" output=""/> + <key code="112" output=""/> + <key code="113" output=""/> + <key code="114" output=""/> + <key code="115" output=""/> + <key code="116" output=""/> + <key code="117" output=""/> + <key code="118" output=""/> + <key code="119" output=""/> + <key code="120" output=""/> + <key code="121" output=""/> + <key code="122" output=""/> + <key code="123" output=""/> + <key code="124" output=""/> + <key code="125" output=""/> + <key code="126" output=""/> + </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="
"/> + <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="	"/> + <key code="49" output=" "/> + <key code="50" output="`"/> + <key code="51" output=""/> + <key code="52" output=""/> + <key code="53" output=""/> + <key code="64" output=""/> + <key code="65" output="."/> + <key code="66" output=""/> + <key code="67" output="*"/> + <key code="69" output="+"/> + <key code="70" output=""/> + <key code="71" output=""/> + <key code="72" output=""/> + <key code="75" output="/"/> + <key code="76" output=""/> + <key code="77" output=""/> + <key code="78" output="-"/> + <key code="79" output=""/> + <key code="80" output=""/> + <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=""/> + <key code="97" output=""/> + <key code="98" output=""/> + <key code="99" output=""/> + <key code="100" output=""/> + <key code="101" output=""/> + <key code="102" output=""/> + <key code="103" output=""/> + <key code="104" output=""/> + <key code="105" output=""/> + <key code="106" output=""/> + <key code="107" output=""/> + <key code="108" output=""/> + <key code="109" output=""/> + <key code="110" output=""/> + <key code="111" output=""/> + <key code="112" output=""/> + <key code="113" output=""/> + <key code="114" output=""/> + <key code="115" output=""/> + <key code="116" output=""/> + <key code="117" output=""/> + <key code="118" output=""/> + <key code="119" output=""/> + <key code="120" output=""/> + <key code="121" output=""/> + <key code="122" output=""/> + <key code="123" output=""/> + <key code="124" output=""/> + <key code="125" output=""/> + <key code="126" output=""/> + </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="
"/> + <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="	"/> + <key code="49" output=" "/> + <key code="50" output="`"/> + <key code="51" output=""/> + <key code="52" output=""/> + <key code="53" output=""/> + <key code="64" output=""/> + <key code="65" output="."/> + <key code="66" output=""/> + <key code="67" output="*"/> + <key code="69" output="+"/> + <key code="70" output=""/> + <key code="71" output=""/> + <key code="72" output=""/> + <key code="75" output="/"/> + <key code="76" output=""/> + <key code="77" output=""/> + <key code="78" output="-"/> + <key code="79" output=""/> + <key code="80" output=""/> + <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=""/> + <key code="97" output=""/> + <key code="98" output=""/> + <key code="99" output=""/> + <key code="100" output=""/> + <key code="101" output=""/> + <key code="102" output=""/> + <key code="103" output=""/> + <key code="104" output=""/> + <key code="105" output=""/> + <key code="106" output=""/> + <key code="107" output=""/> + <key code="108" output=""/> + <key code="109" output=""/> + <key code="110" output=""/> + <key code="111" output=""/> + <key code="112" output=""/> + <key code="113" output=""/> + <key code="114" output=""/> + <key code="115" output=""/> + <key code="116" output=""/> + <key code="117" output=""/> + <key code="118" output=""/> + <key code="119" output=""/> + <key code="120" output=""/> + <key code="121" output=""/> + <key code="122" output=""/> + <key code="123" output=""/> + <key code="124" output=""/> + <key code="125" output=""/> + <key code="126" output=""/> + </keyMap> + <keyMap index="7"> + <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="0"/> + <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="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 code="31" output=""/> + <key code="32" output=""/> + <key code="33" output=""/> + <key code="34" output="	"/> + <key code="35" output=""/> + <key code="36" output="
"/> + <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="	"/> + <key action="5" code="49"/> + <key code="50" output="`"/> + <key code="51" output=""/> + <key code="52" output=""/> + <key code="53" output=""/> + <key code="64" output=""/> + <key code="65" output="."/> + <key code="66" output=""/> + <key code="67" output="*"/> + <key code="69" output="+"/> + <key code="70" output=""/> + <key code="71" output=""/> + <key code="72" output=""/> + <key code="75" output="/"/> + <key code="76" output=""/> + <key code="77" output=""/> + <key code="78" output="-"/> + <key code="79" output=""/> + <key code="80" output=""/> + <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=""/> + <key code="97" output=""/> + <key code="98" output=""/> + <key code="99" output=""/> + <key code="100" output=""/> + <key code="101" output=""/> + <key code="102" output=""/> + <key code="103" output=""/> + <key code="104" output=""/> + <key code="105" output=""/> + <key code="106" output=""/> + <key code="107" output=""/> + <key code="108" output=""/> + <key code="109" output=""/> + <key code="110" output=""/> + <key code="111" output=""/> + <key code="112" output=""/> + <key code="113" output=""/> + <key code="114" output=""/> + <key code="115" output=""/> + <key code="116" output=""/> + <key code="117" output=""/> + <key code="118" output=""/> + <key code="119" output=""/> + <key code="120" output=""/> + <key code="121" output=""/> + <key code="122" output=""/> + <key code="123" output=""/> + <key code="124" output=""/> + <key code="125" output=""/> + <key code="126" output=""/> + </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="&"/> + <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="""/> + <key code="22" 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="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..005272ba --- /dev/null +++ b/etc/agpl.c @@ -0,0 +1,20 @@ +/* Copyright (C) 2020 June 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..0bc3dd36 --- /dev/null +++ b/etc/gpl.c @@ -0,0 +1,20 @@ +/* Copyright (C) 2020 June 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/.bash_profile b/home/.bash_profile new file mode 100644 index 00000000..8dd976b3 --- /dev/null +++ b/home/.bash_profile @@ -0,0 +1,2 @@ +. ~/.profile +. ~/.bashrc diff --git a/home/.bashrc b/home/.bashrc new file mode 100644 index 00000000..61c9d191 --- /dev/null +++ b/home/.bashrc @@ -0,0 +1,13 @@ +. ~/.shrc + +alias ls='ls -p --color=auto' +alias grep='grep --color=auto' + +_PS0=${PS0/$'\n'/} +unset PS0 +RPS1=${RPS1/'\?'/'${?/#0/}'} + +rprompt() { + printf '%*s\r' $((COLUMNS - 1)) "${RPS1@P}" +} +PS1='\n\[${_PS0@P}$(rprompt)\]'"${PS1}" diff --git a/home/.config/git/config b/home/.config/git/config new file mode 100644 index 00000000..36b96776 --- /dev/null +++ b/home/.config/git/config @@ -0,0 +1,21 @@ +[user] + name = June 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..78d9e9f3 --- /dev/null +++ b/home/.shrc @@ -0,0 +1,47 @@ +set -o noclobber -o nounset -o vi + +HISTFILE=~/.history +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 + builtin 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 + builtin cd "${1%/*}" && $EDITOR "${1##*/}" + else + builtin cd "$@" + fi +} +if [ -n "${SSH_CD:-}" ]; then + cd "${SSH_CD}" + unset SSH_CD +fi + +PS0=$'\n' +PS1='\$ ' +RPS1="\? ${SSH_CLIENT:+\h:}\w" + +if [ "${TERM%-*}" = 'xterm' ]; then + tsl=$'\e]0;' + fsl=$'\e\\' + PS0="${tsl}${SSH_CLIENT:+\h:}\W${fsl}${PS0}" +fi 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[22;0;0t[1;24r(B[m[4l[?7h[39;49m[?1h=[?1004h[?2004h[39;49m(B[m[H[2J(B[0;7m[90m 0 <network> [21d(B[0m[95mcatgirl[39m(B[m is GPLv3 fwee softwawe ^w^ code is avaiwable fwom[22;9Hhttps://git.causal.agency/catgirl +Traveling... +]2;chat.freenode.net <network>[H +[3M[21d[31m-adams.freenode.net-[37m *** Looking up your hostname... +[31m-adams.freenode.net-[37m *** Checking Ident +[31m-adams.freenode.net-[37m *** Couldn't look up your hostname +[39m(B[m[H +[M[23d[31m-adams.freenode.net-[37m *** No Ident response +[39m(B[m[H +[M[23dYou arrive in freenode +[H +[6M[18d[90m-[39m(B[m Welcome to adams.freenode.net. Thanks to ATW Internet Kft +[90m-[39m(B[m (http://www.atw.hu) for sponsoring this server! +[90m- +-[39m(B[m ADAMS, DOUGLAS (1952-2001). Author of The Hitch Hikers Guide +[90m-[39m(B[m to the Galaxy and many other witty and humourous books, +[90m-[39m(B[m portrayed in his uniquely British irony. He is sorely missed +[H +[6M[18d[90m-[39m(B[m by many millions of devoted fans. "So long and thanks for all +[90m-[39m(B[m the books!" +[90m- +-[39m(B[m Welcome to freenode - supporting the free and open source +[90m-[39m(B[m software communities since 1998. +[90m- +[39m(B[m[H +[3M[21d[90m-[39m(B[m By connecting to freenode you indicate that you have read and +[90m-[39m(B[m accept our policies and guidelines as set out on https://freenode.net +[90m- +[39m(B[m[H +[3M[21d[90m-[39m(B[m In the event that you observe behaviour that contravenes our policies, +[90m-[39m(B[m please notify a volunteer staff member via private message, or send us an +[90m-[39m(B[m e-mail to complaints@freenode.net -- we will do our best to address the +[H +[4M[20d[90m-[39m(B[m situation within a reasonable period of time, and we may request further +[90m-[39m(B[m information or, as appropriate, involve other parties such as channel + operators +[90m-[39m(B[m Group Contacts representing an on-topic group. +[H +[6M[18d[90m- +-[39m(B[m freenode runs an open proxy scanner. +[90m- +-[39m(B[m If you are looking for assistance, you may be able to find a list of +[90m-[39m(B[m volunteer staff on '/stats p' (shows only on-call staff) or by joining +[90m-[39m(B[m #freenode and using the '/who freenode/staff/*' command. You may message +[H +[5M[19d[90m-[39m(B[m any of us at any time. Please note that freenode predominantly provides +[90m-[39m(B[m assistance via private message, and while we have a network channel the +[90m-[39m(B[m primary venue for support requests is via private message to a member +[90m-[39m(B[m of the volunteer staff team. +[90m- +[39m(B[m[H +[5M[19d[90m-[39m(B[m From time to time, volunteer staff may send server-wide notices relating to +[90m-[39m(B[m the project, or the communities that we host. The majority of such notices +[90m-[39m(B[m will be sent as wallops, and you can '/mode <yournick> +w' to ensure that you +[90m-[39m(B[m do not miss them. Important messages relating to the freenode project, + including +[H +[7M[17d[90m-[39m(B[m notices of upcoming maintenance and other scheduled downtime will be issued + as +[90m-[39m(B[m global notices. +[90m- +-[39m(B[m Representing an on-topic project? Don't forget to register, more information +[90m-[39m(B[m can be found on the https://freenode.net website under "Group Registration". +[90m- +[39m(B[m[H +[5M[19d[90m-[39m(B[m Thank you also to our server sponsors for the sustained support in keeping + the +[90m-[39m(B[m network going for close to two decades. +[90m- +-[39m(B[m Thank you for using freenode! +[1;14H[95m 1 freenode-connect ([97m2[95m) [24d[39m(B[m]2;freenode <network> (+2!)[H[95m 1 freenode-connect ([97m2[95m) (B[0;7m[31m 2 #ascii.town +[39m(B[m[K +[K +[K +[K +[K +[K +[K +[K +[K +[K +[K +[K +[K +[K +[K +[K +[K +[96mcatgirl[39m(B[m arrives in [31m#ascii.town[39m(B[m[K +The sign in [31m#ascii.town[39m(B[m reads: https://ascii.town public SSH services and IRC +things <3 AGPL[K +In [31m#ascii.town[39m(B[m are catgirl, gjabell, danopia, larbob, ep, nonlinear, epilys, +benharri, june, yourfate, josuah +[96m<catgirl> [39m(B[m]2;freenode #ascii.town (+2!) /[Kclose 1[H(B[0;7m[31m 1[39m(B[m[24P [24d[96m<catgirl> [39m(B[m]2;freenode #ascii.townhello, world![H +[M[23;10H +[96m<catgirl> [39m(B[m[2;23r[23;1H[1S[1;24r[1;16H(B[0;7m[31m(1) [23d(B[0m[3m[34m* june[39m(B[0m[3m waves +(B[m]2;freenode #ascii.town (1)[1;16H[K[24;11H]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 @@ +[1;24r[m[4l[?12l[?25h[?1h=[39;49m[?25l[?1h=[39;49m[m[H[2J[1;30H0 + + [38;5;7m[48;5;0m [7C[39;49m[mUse the arrow keys to + [38;5;7m[48;5;0m . . . . [7C[39;49m[mslide and merge tiles. + [38;5;7m[48;5;0m [7C[39;49m[mPress q to quit. + [38;5;7m[48;5;0m +[28D . . . . +[28D +[28D [38;5;15m[48;5;1m[1m +[28D[m[38;5;7m[48;5;0m . . [38;5;15m[48;5;1m[1m 2 2 +[28D[m[38;5;7m[48;5;0m [38;5;15m[48;5;1m[1m +[28D[m[38;5;7m[48;5;0m +[28D . . . . +[28D [39;49m[m[1;30H4[6;10H[38;5;15m[48;5;1m[1m [7;10H 2 [8;10H +[14D[38;5;15m[48;5;2m [7C[m[38;5;7m[48;5;0m +[28D[38;5;15m[48;5;2m[1m 4 [7C[m[38;5;7m[48;5;0m . . +[28D[38;5;15m[48;5;2m[1m [7C[m[38;5;7m[48;5;0m [39;49m[m[3;24H[38;5;15m[48;5;1m[1m [4;24H 2 [5;24H [6;10H[m[38;5;7m[48;5;0m [7;10H . [8;10H +[14D +[7D . +[7D +[7D[38;5;15m[48;5;2m[1m [38;5;15m[48;5;1m +[14D[38;5;15m[48;5;2m 4 [38;5;15m[48;5;1m 2 +[14D[38;5;15m[48;5;2m [38;5;15m[48;5;1m [39;49m[m \ 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 @@ +[40m [41m [42m [43m [44m [45m [46m [47m [m +[40m [41m [42m [43m [44m [45m [46m [47m [m +[40m [41m [42m [43m [44m [45m [46m [47m [m +[40m [41m [42m [43m [44m [45m [46m [47m [m +[40m [41m [42m [43m [44m [45m [46m [47m [m +[100m [101m [102m [103m [104m [105m [106m [107m [m +[100m [101m [102m [103m [104m [105m [106m [107m [m +[100m [101m [102m [103m [104m [105m [106m [107m [m +[100m [101m [102m [103m [104m [105m [106m [107m [m +[100m [101m [102m [103m [104m [105m [106m [107m [m[H 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 @@ +[1;25r[m[4l[39;49m[?1h=[?25l[39;49m[38;5;0m[48;5;0m[m[39;49m[38;5;0m[48;5;0m[H[2J[38;5;3m[48;5;0m┌──────────────────────────────────────────────────────────────────────────────┐[2;1H│[38;5;7m[48;5;0m Welcome to [38;5;12m[48;5;0ma[38;5;14m[48;5;0ms[38;5;10m[48;5;0mc[38;5;11m[48;5;0mi[38;5;9m[48;5;0mi[38;5;13m[48;5;0m.[38;5;12m[48;5;0mt[38;5;14m[48;5;0mo[38;5;10m[48;5;0mw[38;5;11m[48;5;0mn[38;5;7m[48;5;0m! [38;5;15m[48;5;0mq[38;5;7m[48;5;0m quit [38;5;15m[48;5;0mQ[38;5;7m[48;5;0m teleport [38;5;15m[48;5;0mm[38;5;7m[48;5;0m mini-map [38;5;15m[48;5;0m?[38;5;7m[48;5;0m help [38;5;3m[48;5;0m│[3;1H│[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[4;1H│[38;5;7m[48;5;0m [38;5;15m[48;5;0mk[38;5;7m[48;5;0m [38;5;8m[48;5;0m0[38;5;7m[48;5;0m [38;5;1m[48;5;0m1[38;5;7m[48;5;0m [38;5;2m[48;5;0m2[38;5;7m[48;5;0m [38;5;3m[48;5;0m3[38;5;7m[48;5;0m [38;5;4m[48;5;0m4[38;5;7m[48;5;0m [38;5;5m[48;5;0m5[38;5;7m[48;5;0m [38;5;6m[48;5;0m6[38;5;7m[48;5;0m 7 [38;5;15m[48;5;0mesc[38;5;7m[48;5;0m navigation mode [38;5;15m[48;5;0ms[38;5;7m[48;5;0m copy [38;5;3m[48;5;0m│[5;1H│[38;5;7m[48;5;0m [38;5;15m[48;5;0my[38;5;7m[48;5;0m ↑ [38;5;15m[48;5;0mu[38;5;7m[48;5;0m [38;5;15m[48;5;0mi[38;5;7m[48;5;0m insert mode [38;5;15m[48;5;0mp[38;5;7m[48;5;0m paste [38;5;3m[48;5;0m│[6;1H│[38;5;7m[48;5;0m [38;5;15m[48;5;0mh[38;5;7m[48;5;0m ←∙→ [38;5;15m[48;5;0ml[38;5;7m[48;5;0m [38;5;8m[48;5;0m)[38;5;7m[48;5;0m [38;5;0m[48;5;1m![38;5;7m[48;5;0m [38;5;0m[48;5;2m@[38;5;7m[48;5;0m [38;5;0m[48;5;3m#[38;5;7m[48;5;0m [38;5;0m[48;5;4m$[38;5;7m[48;5;0m [38;5;0m[48;5;5m%[38;5;7m[48;5;0m [38;5;0m[48;5;6m^[38;5;7m[48;5;0m [38;5;0m[48;5;7m&[38;5;7m[48;5;0m [38;5;15m[48;5;0ma[38;5;7m[48;5;0m insert after [38;5;15m[48;5;0m~[38;5;7m[48;5;0m paint color [38;5;3m[48;5;0m│[7;1H│[38;5;7m[48;5;0m [38;5;15m[48;5;0mb[38;5;7m[48;5;0m ↓ [38;5;15m[48;5;0mn[38;5;7m[48;5;0m [38;5;15m[48;5;0mI[38;5;7m[48;5;0m insert direction [38;5;15m[48;5;0m*[38;5;7m[48;5;0m paint bright [38;5;3m[48;5;0m│[8;1H│[38;5;7m[48;5;0m [38;5;15m[48;5;0mj[38;5;7m[48;5;0m [38;5;15m[48;5;0m8[38;5;7m[48;5;0m bright [38;5;15m[48;5;0m9[38;5;7m[48;5;0m invert [38;5;15m[48;5;0mR[38;5;7m[48;5;0m draw mode [38;5;15m[48;5;0m([38;5;7m[48;5;0m paint invert [38;5;3m[48;5;0m│[9;1H│[38;5;7m[48;5;0m [38;5;15m[48;5;0m`[38;5;7m[48;5;0m pipette [38;5;15m[48;5;0m.[38;5;7m[48;5;0m line mode [38;5;15m[48;5;0mC-a[38;5;7m[48;5;0m increment [38;5;3m[48;5;0m│[10;1H│[38;5;7m[48;5;0m [38;5;15m[48;5;0m\[38;5;7m[48;5;0m fast [38;5;15m[48;5;0mr[38;5;7m[48;5;0m replace [38;5;15m[48;5;0mC-x[38;5;7m[48;5;0m decrement [38;5;3m[48;5;0m│[11;1H│[38;5;7m[48;5;0m [38;5;15m[48;5;0mx[38;5;7m[48;5;0m erase [38;5;15m[48;5;0mHJKLYUBN[38;5;7m[48;5;0m swap cell [38;5;3m[48;5;0m│[12;1H│[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[13;1H│[38;5;7m[48;5;0m [38;5;15m[48;5;0mF1[38;5;7m[48;5;0m @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmno [38;5;3m[48;5;0m│[14;1H│[38;5;7m[48;5;0m [38;5;15m[48;5;0mF2[38;5;7m[48;5;0m ☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ [38;5;3m[48;5;0m│[15;1H│[38;5;7m[48;5;0m [38;5;15m[48;5;0mF3[38;5;7m[48;5;0m αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ [38;5;3m[48;5;0m│[16;1H│[38;5;7m[48;5;0m [38;5;15m[48;5;0mF4[38;5;7m[48;5;0m ░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀ [38;5;3m[48;5;0m│[17;1H│[38;5;7m[48;5;0m [38;5;15m[48;5;0mF5[38;5;7m[48;5;0m ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«» [38;5;3m[48;5;0m│[18;1H│[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[19;1H│[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[20;1H│[38;5;7m[48;5;0m This is [38;5;3m[48;5;0mAGPLv3[38;5;7m[48;5;0m Free Software! [38;5;3m[48;5;0m│[21;1H│[38;5;7m[48;5;0m Code is available from [38;5;6m[48;5;0m<https://code.causal.agency/june/torus>[38;5;7m[48;5;0m. [38;5;3m[48;5;0m│[22;1H│[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[23;1H│[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[24;1H│[38;5;7m[48;5;0m Press [38;5;15m[48;5;0m? [38;5;7m[48;5;0mto open this help again. Press any key to continue... [38;5;3m[48;5;0m│[25;1H└──────────────────────────────────────────────────────────────────────────────┘[4h─[4l[m[39;49m[38;5;0m[48;5;0m[?12l[?25h[H[38;5;7m[48;5;0mPress [38;5;15m[48;5;0m?[38;5;7m[48;5;0m for help![38;5;3m[48;5;0m│[38;5;7m[48;5;0m this space now frees [38;5;9m[48;5;0m [38;5;5m[48;5;0m\[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m\[38;5;7m[48;5;0m [38;5;6m[48;5;0mCP437[38;5;14m[48;5;0m [38;5;11m[48;5;0m☻ [38;5;9m[48;5;7m♥♦[38;5;0m[48;5;7m♣♠[38;5;14m[48;5;0m [38;5;4m[48;5;0m░▒▓█[38;5;3m[48;5;0m [38;5;6m[48;5;0m [38;5;7m[48;5;0m(000,000)[2;1H[38;5;3m[48;5;0m─────────────────┘ [38;5;7m[48;5;0m [38;5;11m[48;5;0mIt's Free Real Estate[38;5;7m[48;5;0m [38;5;1m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;5m[48;5;0m\[38;5;3m[48;5;0m [38;5;5m[48;5;0my[38;5;3m[48;5;0m [38;5;5m[48;5;0m\ [38;5;3m[48;5;0m ╒══════════════════════╕[K +[38;5;9m[48;5;0mFor a good [3;25H[38;5;5m[48;5;0m(0_0)[3;41H[m[39;49m[38;5;0m[48;5;0m [7C[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;13m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;15m[48;5;0mascii.town guestbook[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;1m[48;5;0m [38;5;7m[48;5;0m[K +[38;5;9m[48;5;0mtime, go in[38;5;7m[48;5;0m a [m[39;49m[38;5;0m[48;5;0m [38;5;3m[48;5;0m [38;5;5m[48;5;0m|[38;5;3m[48;5;0m [38;5;5m[48;5;0my[38;5;3m[48;5;0m [38;5;5m[48;5;0m|[38;5;3m[48;5;0m ╞[38;5;7m[48;5;0m══════════════════════[38;5;3m[48;5;0m╡[38;5;7m[48;5;0m[K +[38;5;9m[48;5;0many direction.[5;31H[38;5;10m[48;5;0m [38;5;7m[48;5;0m RR w [38;5;5m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0m| y[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;6m[48;5;0m-[38;5;7m[48;5;0m [38;5;6m[48;5;0mjune [38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;7m[48;5;0m[2P +[38;5;12m[48;5;0mPls don't[38;5;7m[48;5;0m publicg@conferencRRRRuRRRRR.forsale [38;5;5m[48;5;0m |[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;13m[48;5;0m-[38;5;5m[48;5;0m [38;5;13m[48;5;0mscott [38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;7m[48;5;0m[2P +[38;5;12m[48;5;0mvandal. There[7;28H[38;5;7m[48;5;0mRRRRRRRRRR [38;5;5m[48;5;0m | | [38;5;3m[48;5;0m│ [38;5;4m[48;5;0m- cjm [38;5;3m[48;5;0m [38;5;7m[48;5;0m[2P +[38;5;12m[48;5;0mare huuuuuge[38;5;15m[48;5;0m [38;5;7m[48;5;0m [38;5;4m[48;5;0mhelo woldr[38;5;7m[48;5;0m RRRRRRRR r a [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0ml[38;5;2m[48;5;0m?[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;10m[48;5;0m-[38;5;7m[48;5;0m [38;5;10m[48;5;0merik[38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;7m[48;5;0m[2P +[38;5;12m[48;5;0mtracts of [6C[38;5;7m[48;5;0m aaaac RRRRRR What's up?j[38;5;2m[48;5;0m/[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;7m[48;5;5m-[38;5;10m[48;5;0m [38;5;3m[48;5;0mswgillespie[38;5;7m[48;5;0m[2P +[38;5;12m[48;5;0munused land.[10;31H[38;5;7m[48;5;0mRRRR [38;5;9m[48;5;0m [38;5;7m[48;5;0m poop [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0ma[38;5;2m[48;5;0m▼[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m -[38;5;3m[48;5;0m [38;5;7m[48;5;0mdikaiosune[2P +[38;5;10m[48;5;0mif u do vandal[38;5;3m[48;5;0m [38;5;7m[48;5;0mRR[38;5;3m[48;5;0m [38;5;7m[48;5;0mtest[38;5;3m[48;5;0m [38;5;5m[48;5;0m |[38;5;7m[48;5;0m [38;5;5m[48;5;0mo[38;5;7m[48;5;0m [38;5;12m[48;5;0m-[38;5;7m[48;5;0m [38;5;12m[48;5;0mdanopia[38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;7m[48;5;0m[2P +[38;5;10m[48;5;0mmake it funny[38;5;7m[48;5;0m [38;5;7m[48;5;5mbut maybe dont[38;5;7m[48;5;0m [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0mhey [m[39;49m[38;5;0m[48;5;0m [38;5;1m[48;5;0m000[38;5;7m[48;5;0m sdddddd[38;5;5m[48;5;0m| |[38;5;7m[48;5;3m [38;5;3m[48;5;0m│[38;5;8m[48;5;0m - [38;5;7m[48;5;0mConnor[38;5;8m[48;5;0m_____[38;5;7m[48;5;0m[2P +PROTIP: Press [38;5;15m[48;5;0mESC[38;5;7m[48;5;0m to escape input modes [38;5;1m[48;5;0m [38;5;7m[48;5;0mq,,,,,,[38;5;5m[48;5;0m| a[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;2m[48;5;0m- tokenrove [38;5;3m[48;5;0m│[38;5;7m[48;5;0m[K +[38;5;3m[48;5;0m2048 clone: [38;5;11m[48;5;0mssh play@ascii.town[38;5;7m[48;5;0m <3 aa [38;5;5m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0m| y[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0m-[38;5;7m[48;5;0m [38;5;3m[48;5;0mmykey[14;72H[38;5;5m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0m [38;5;1m[48;5;0m [38;5;5m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m[K +[38;5;5m[48;5;0m [38;5;7m[48;5;0mG O O D B Y E W I T C H E S . T Os[38;5;5m[48;5;0mjjj[38;5;7m[48;5;0m :( [38;5;5m[48;5;0m | y |[m[39;49m[38;5;0m[48;5;0m [38;5;3m[48;5;0m│[38;5;2m[48;5;0m [38;5;7m[48;5;0m-[38;5;2m[48;5;0m [38;5;7m[48;5;0mdmrd[38;5;9m[48;5;0m [38;5;1m[48;5;0m [38;5;5m[48;5;0m [38;5;1m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0m [38;5;3m[48;5;0m│[38;5;5m[48;5;0m [38;5;7m[48;5;0m[K +[38;5;1m[48;5;0mvim controls, eh?[38;5;7m[48;5;0m.__________________. Con[38;5;5m[48;5;0mj[38;5;7m[48;5;0mor [38;5;5m[48;5;0m |[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m - hiya [38;5;1m[48;5;0m [38;5;7m[48;5;0m [38;5;9m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m│[C[K +[38;5;11m[48;5;0m [38;5;10m[48;5;0m.[38;5;11m[48;5;0m [38;5;10m[48;5;0m.[38;5;11m[48;5;0m [38;5;10m[48;5;0m.[38;5;13m[48;5;0m [38;5;10m[48;5;0m.[38;5;7m[48;5;0m [38;5;10m[48;5;0m.[38;5;7m[48;5;0m [38;5;10m[48;5;0m.[38;5;7m[48;5;0m [38;5;10m[48;5;0m.[38;5;7m[48;5;0m [38;5;10m[48;5;0m.[38;5;7m[48;5;0m |[38;5;15m[48;5;0m Out of space? [38;5;7m[48;5;0m|[38;5;15m[48;5;0m [38;5;7m[48;5;0mWas[38;5;5m[48;5;0mj[38;5;7m[48;5;0m whut?[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m - [38;5;1m[48;5;0marke [38;5;7m[48;5;0m[2P +[38;5;10m[48;5;0m.[38;5;13m[48;5;0m [38;5;10m[48;5;0mWelcome to.[38;5;14m[48;5;0m [38;5;10m[48;5;0m.[38;5;14m[48;5;0m [38;5;10m[48;5;0m.[38;5;7m[48;5;0m|[38;5;15m[48;5;0mTry typing[38;5;11m[48;5;0m [38;5;9m[48;5;0m1Q[38;5;15m[48;5;0m,[38;5;11m[48;5;0m [38;5;10m[48;5;0m2Q[38;5;15m[48;5;0m,[38;5;7m[48;5;0m|[38;5;11m[48;5;0m [38;5;7m[48;5;0mHer[38;5;5m[48;5;0mj[38;5;7m[48;5;0m [38;5;5m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0m| y[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m - cmr[2P + [38;5;10m[48;5;0m.[38;5;7m[48;5;0m [38;5;10m[48;5;0m.[38;5;7m[48;5;0m [38;5;10m[48;5;0m. the [38;5;12m[48;5;0mT[38;5;9m[48;5;0mO[38;5;11m[48;5;0mW[38;5;14m[48;5;0mN[38;5;10m[48;5;0m.[38;5;7m[48;5;0m |[38;5;11m[48;5;0m3Q[38;5;15m[48;5;0m or [38;5;12m[48;5;0m4Q[38;5;15m[48;5;0m to travel[38;5;7m[48;5;0m|[38;5;15m[48;5;0m [38;5;10m[48;5;0m [38;5;4m[48;5;0mw[38;5;5m[48;5;0mj[38;5;4m[48;5;0mof[38;5;7m[48;5;0mt[38;5;11m[48;5;0m [38;5;5m[48;5;0m | y | [38;5;3m[48;5;0m│[38;5;7m[48;5;0m - [38;5;5m[48;5;0mGrissess (:D)[38;5;6m[48;5;0m [38;5;7m[48;5;0m [38;5;6m[48;5;0m [38;5;9m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m[K + [38;5;10m[48;5;0m.[38;5;7m[48;5;0m [38;5;10m[48;5;0m.[38;5;7m[48;5;0m [38;5;10m[48;5;0m.[38;5;7m[48;5;0m [38;5;10m[48;5;0m.[38;5;7m[48;5;0m [38;5;10m[48;5;0m.[38;5;7m[48;5;0m [38;5;10m[48;5;0m.[38;5;7m[48;5;0m [38;5;10m[48;5;0m.[38;5;7m[48;5;0m [38;5;10m[48;5;0m.[38;5;7m[48;5;0m|[38;5;15m[48;5;0mto far-away lands [38;5;7m[48;5;0m|[38;5;15m[48;5;0m [38;5;7m[48;5;0m [38;5;10m[48;5;0m [38;5;4m[48;5;0ma[38;5;5m[48;5;0mj[38;5;4m[48;5;0moo[38;5;15m[48;5;0m [38;5;6m[48;5;0m [38;5;5m[48;5;0m |[38;5;7m[48;5;0m [38;5;5m[48;5;0my | [38;5;3m[48;5;0m│[38;5;5m[48;5;0m [38;5;7m[48;5;0m- [38;5;12m[48;5;0meternale[38;5;7m[48;5;0my[38;5;12m[48;5;0me[38;5;7m[48;5;0m [38;5;0m[48;5;7m(ipv6)[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m[K +[38;5;12m[48;5;0m [38;5;10m[48;5;0m [38;5;2m[48;5;0m.. . . . . .[38;5;10m[48;5;0m .[38;5;7m[48;5;0m '------------------' [38;5;5m[48;5;0mj [38;5;7m[48;5;0mtest[38;5;5m[48;5;0m |[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m - meena [38;5;3m[48;5;0m [38;5;7m[48;5;0m[2P + sad[38;5;2m[48;5;0m [38;5;7m[48;5;0m hi friends [38;5;3m[48;5;0mhello[38;5;7m[48;5;0m [38;5;8m[48;5;0m [38;5;7m[48;5;0mcybre[38;5;7m[48;5;6m![38;5;7m[48;5;0m [38;5;7m[48;5;6m^Yes?[38;5;7m[48;5;0m [38;5;5m[48;5;0m j [38;5;7m[48;5;0mni [38;5;5m[48;5;0m| y[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;6m[48;5;0m- b0rk[38;5;7m[48;5;0m [38;5;5m[48;5;0m [38;5;7m[48;5;0m boy [38;5;15m[48;5;0m__ [38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m[K + nethack [38;5;3m[48;5;0myo how do i select black???[38;5;7m[48;5;0m [38;5;1m[48;5;0ma[38;5;5m[48;5;0mj[38;5;14m[48;5;0m [38;5;7m[48;5;0msxD [38;5;5m[48;5;0m |[38;5;7m[48;5;0m [38;5;5m[48;5;0my | [38;5;3m[48;5;0m│[38;5;5m[48;5;0m [38;5;7m[48;5;0m- fbernier[38;5;5m[48;5;0m [38;5;15m[48;5;0m / '[38;5;3m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m[K +[38;5;12m[48;5;0m [38;5;7m[48;5;0mboi[38;5;12m[48;5;0m [38;5;7m[48;5;0m [38;5;9m[48;5;0mc[38;5;10m[48;5;0ml[38;5;11m[48;5;0mo[38;5;12m[48;5;0mw[38;5;13m[48;5;0mn[38;5;3m[48;5;0m [38;5;14m[48;5;0mt[38;5;9m[48;5;0mo[38;5;10m[48;5;0mw[38;5;11m[48;5;0mn[38;5;7m[48;5;0mjj [38;5;1m[48;5;0mthis is as wired [38;5;8m[48;5;0m0[38;5;5m[48;5;0mj[38;5;1m[48;5;0m [38;5;7m[48;5;0mdasdksajklj[38;5;3m[48;5;0m│[38;5;7m[48;5;0m -[38;5;5m[48;5;0m mguaypaq[38;5;2m[48;5;0m [38;5;3m[48;5;0m [38;5;15m[48;5;0m [38;5;3m[48;5;0m [38;5;15m[48;5;0m| [38;5;2m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m[K +[38;5;1m[48;5;0m [38;5;9m[48;5;0m [38;5;12m[48;5;0m* [38;5;5m[48;5;0m [38;5;8m[48;5;1mhot tip:[38;5;7m[48;5;1m [38;5;0m[48;5;1mdon't[38;5;7m[48;5;1m [38;5;8m[48;5;1mruin sh[38;5;7m[48;5;0mit [38;5;12m[48;5;0m [38;5;7m[48;5;0m [38;5;12m[48;5;0m* [38;5;5m[48;5;0m [38;5;3m[48;5;0m [38;5;5m[48;5;0m [38;5;12m[48;5;0m*[38;5;7m[48;5;0m [38;5;5m[48;5;0mj [38;5;12m[48;5;0m* [38;5;5m[48;5;0m /[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0mq[38;5;5m[48;5;0m/ [38;5;12m[48;5;0m*[38;5;5m[48;5;0m [38;5;3m[48;5;0m│[38;5;5m[48;5;0m [38;5;7m[48;5;0m- [38;5;2m[48;5;0mrkallos[38;5;7m[48;5;0m [38;5;15m[48;5;0m [38;5;3m[48;5;0m [38;5;15m[48;5;0mV [38;5;3m[48;5;0m │[38;5;7m[48;5;0m[K[H[m[39;49m[38;5;0m[48;5;0m[13;41H + + + + + + + + + + + +[1;41H [38;5;6m[48;5;0m(The client tries to check if you'll[38;5;7m[48;5;0m [38;5;5m[48;5;0mj[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m///[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;15m[48;5;0m-[38;5;7m[48;5;0m [38;5;15m[48;5;0mfsj[38;5;7m[48;5;0m [38;5;14m[48;5;0m [38;5;3m[48;5;0m│[7C[38;5;7m[48;5;0m1 +[38;5;6m[48;5;0mbe able to see all the colours now.)[38;5;7m[48;5;0m [38;5;5m[48;5;0mj[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m\\\[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;6m[48;5;0m-[38;5;7m[48;5;0m [38;5;6m[48;5;0mSylvhem[38;5;7m[48;5;0m [38;5;3m[48;5;0m└──────┐[38;5;2m[48;5;0m//[3;1H[38;5;6m[48;5;0m [38;5;7m[48;5;0m [38;5;9m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0mj[38;5;7m[48;5;0m [38;5;1m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m///[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;1m[48;5;0m- iliana[38;5;3m[48;5;0m [38;5;1m[48;5;0m<3[38;5;7m[48;5;0m [38;5;5m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;2m[48;5;0m\\[4;1H[38;5;6m[48;5;0m [38;5;15m[48;5;0m][m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0mgas pedal ┐ this is just [38;5;15m[48;5;0m\[38;5;7m[48;5;0m [38;5;11m[48;5;0m [38;5;5m[48;5;0mj[38;5;7m[48;5;0m [38;5;1m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m\\\[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;14m[48;5;0m- tjk[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;2m[48;5;0m//[5;1H[38;5;7m[48;5;0m [38;5;15m[48;5;0m.__[38;5;7m[48;5;0m [38;5;14m[48;5;0m [38;5;15m[48;5;0m[[m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0mbrakes ┘ to toggle fast now[38;5;5m[48;5;0mj[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m///[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - duckinator [7C[38;5;2m[48;5;0m\\[6;1H[38;5;7m[48;5;0m [38;5;15m[48;5;0m|\[38;5;7m[48;5;0m } curly gas pedal [38;5;1m[48;5;0m:)[38;5;7m[48;5;0m [38;5;5m[48;5;0mj[38;5;7m[48;5;0m [38;5;1m[48;5;1m [m[39;49m[38;5;0m[48;5;0melo[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m\\\[38;5;3m[48;5;0m│[38;5;7m[48;5;0m (pup@mastodon.social)[38;5;3m[48;5;0m│[38;5;2m[48;5;0m//[7;1H[38;5;7m[48;5;0m [38;5;15m[48;5;0m\[38;5;7m[48;5;0m { curly brakes [38;5;6m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0mj[38;5;7m[48;5;0m [38;5;1m[48;5;1m [38;5;6m[48;5;0m [38;5;1m[48;5;1m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m///[38;5;3m[48;5;0m│[38;5;6m[48;5;0m [38;5;3m[48;5;0m- bug[38;5;0m[48;5;3m(@chitter.xyz)[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;2m[48;5;0m\\[8;1H[38;5;7m[48;5;0m Looking for something [38;5;15m[48;5;0minteresting[38;5;11m[48;5;0m?[38;5;7m[48;5;0m [38;5;5m[48;5;0mj[38;5;7m[48;5;0m [38;5;1m[48;5;1m [38;5;7m[48;5;0m [38;5;1m[48;5;1m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m\\\[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - peidran [9C[38;5;2m[48;5;0m//[9;1H[38;5;7m[48;5;0m Aside from scattered pages all over, [38;5;5m[48;5;0mj[38;5;7m[48;5;0m [38;5;1m[48;5;1m [38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m///[38;5;3m[48;5;0m│[38;5;7m[48;5;0m -[38;5;3m[48;5;0m [38;5;7m[48;5;0mKitRedgrave[9C[38;5;2m[48;5;0m\\[10;1H[38;5;7m[48;5;0m there's some development [38;5;12m[48;5;0mleft[38;5;7m[48;5;0m and [38;5;10m[48;5;0mup[38;5;7m[48;5;0m fro[38;5;5m[48;5;0mj[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m\\\[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - [38;5;4m[48;5;0mvimtingu[38;5;7m[48;5;0m [10C[38;5;2m[48;5;0m//[11;1H[38;5;7m[48;5;0m here. The bulletin is over to the [38;5;9m[48;5;0mright[38;5;5m[48;5;0mj[38;5;15m[48;5;0m->[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m///[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - thomas@touhey.org [38;5;3m[48;5;0m│[38;5;2m[48;5;0m\\[12;1H[38;5;7m[48;5;0m somewhere. And there's stuff all along [38;5;5m[48;5;0mj[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m\\\[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - frewsxcv \.fr [38;5;3m[48;5;0m│[38;5;2m[48;5;0m//[13;1H[38;5;7m[48;5;0m the [38;5;13m[48;5;0mtorus[38;5;7m[48;5;0m if you follow it around. [38;5;5m[48;5;0mj[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m///[38;5;3m[48;5;0m│[38;5;7m[48;5;0m -[38;5;3m[48;5;0m NecroTechno <3[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;2m[48;5;0m\\[14;1H[38;5;7m[48;5;0m [38;5;15m[48;5;0m\[38;5;7m[48;5;0m [38;5;5m[48;5;0mj[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m\\\[38;5;3m[48;5;0m│ [38;5;7m[48;5;0m-[38;5;5m[48;5;0m er1n[38;5;7m[48;5;0m [14;72H [38;5;3m[48;5;0m│[38;5;2m[48;5;0m//[15;1H [38;5;11m[48;5;0mNEW: press [38;5;15m[48;5;0mm[38;5;11m[48;5;0m to open the mini-map[38;5;7m[48;5;0m [38;5;15m[48;5;0m_\|[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m///[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;5m[48;5;0m- quantified [38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;2m[48;5;0m\\[16;1H[38;5;7m[48;5;0m [38;5;11m[48;5;0mand see what's nearby[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m\\\[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;5m[48;5;0m@cybre.space[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;2m[48;5;0m//[17;1H[38;5;7m[48;5;0m [38;5;15m[48;5;0m [38;5;10m[48;5;0m [38;5;15m[48;5;0m [38;5;7m[48;5;0m [38;5;10m[48;5;0m [38;5;7m[48;5;0myoooooo yw! [38;5;10m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m///[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;10m[48;5;0m- nightpool (cybre) [38;5;3m[48;5;0m│[38;5;10m[48;5;0m\[38;5;2m[48;5;0m\[18;1H[38;5;7m[48;5;0m [38;5;10m[48;5;0m [38;5;2m[48;5;0maaa\]\\[38;5;12m[48;5;0my wat[38;5;10m[48;5;0m [38;5;7m[48;5;0mhey vanta [38;5;10m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my |[38;5;2m[48;5;0m\\\[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - ghosty[14C[38;5;2m[48;5;0m//[19;2H[38;5;7m[48;5;0m [38;5;15m[48;5;0m [38;5;7m[48;5;0mUrFU !d[38;5;10m[48;5;0m [38;5;7m[48;5;0mthis is dope[38;5;10m[48;5;0m [38;5;7m[48;5;0m [38;5;10m[48;5;0m [38;5;5m[48;5;0m| y[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m///[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;10m[48;5;0m- lynn (@chordbug)[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;2m[48;5;0m\\[20;1H[38;5;10m[48;5;0m [38;5;7m[48;5;0mRTF - chempion![38;5;10m[48;5;0m [38;5;7m[48;5;0mthx 4 sharing [38;5;10m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m\\\[38;5;3m[48;5;0m│[38;5;5m[48;5;0m [38;5;7m[48;5;0m-[38;5;5m[48;5;0m [38;5;7m[48;5;0mvantablack [38;5;3m[48;5;0m│[38;5;2m[48;5;0m//[21;1H[38;5;7m[48;5;0m This is actually amazing. [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m///[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;5m[48;5;0m//wxyzzyrd [38;5;7m[48;5;0m [9C[38;5;2m[48;5;0m\\[22;2H[38;5;7m[48;5;0mDon't forget to grab the source: [38;5;14m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m\\\[38;5;3m[48;5;0m│[38;5;7m[48;5;0m //international <3 [38;5;3m[48;5;0m│[38;5;2m[48;5;0m//[23;2H[38;5;7m[48;5;0m hi RC! [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my |[38;5;2m[48;5;0m///[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - [38;5;13m[48;5;0mmaren[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;2m[48;5;0m\\[24;1H[38;5;7m[48;5;0m $ git clone \ [38;5;5m[48;5;0m| y[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m\\\[38;5;3m[48;5;0m│[38;5;14m[48;5;0m [38;5;13m[48;5;0m- [38;5;0m[48;5;2mrose[38;5;14m[48;5;0m [38;5;2m[48;5;0m [38;5;3m[48;5;0m│[38;5;2m[48;5;0m//[25;1H[38;5;7m[48;5;0m https://code.causal.agency/june/torus.git [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;2m[48;5;0m///[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;13m[48;5;0m(@BLASTPROCESSlNG)[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;2m[48;5;0m/\[4h/[4l[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H [38;5;4m[48;5;0m~~[38;5;7m[48;5;0m~[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~[38;5;3m[48;5;0m│[38;5;1m[48;5;0m [38;5;5m[48;5;0m- grainloom[38;5;7m[48;5;0m [8C2 +[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~~~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~[38;5;12m[48;5;0m~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;5m[48;5;0m@cybre.space[8C[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[3;1H~~~~~~~[38;5;7m[48;5;0mhi[38;5;4m[48;5;0m~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~~~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~~[38;5;12m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;1m[48;5;2m@slimelia[38;5;7m[48;5;0m [9C[38;5;4m[48;5;0m~~[4;1H[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~~~~~~~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~[38;5;5m[48;5;5m~[38;5;4m[48;5;0m~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - jfo[17C[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[5;1H[38;5;4m[48;5;0m~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~[38;5;5m[48;5;5m~~~jh[38;5;4m[48;5;0m~~~~[38;5;5m[48;5;5m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;1m[48;5;0m@Phairupegiont[6C[38;5;4m[48;5;0m~~[6;1H~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~~~~~~~~[38;5;12m[48;5;0m~~[38;5;4m[48;5;0m~~~~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [m[39;49m[38;5;0m[48;5;0m- [38;5;2m[48;5;0mchr@cybre.space[38;5;7m[48;5;0m [38;5;5m[48;5;0m<3[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[7;1H[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~[38;5;7m[48;5;0m i want to marry lynn [38;5;4m[48;5;0m~~~~~~~~~~~~~~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~~[38;5;12m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - elomatreb :3 [38;5;3m[48;5;0m│[38;5;4m[48;5;0m~~[8;1H~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~[38;5;14m[48;5;0msame[38;5;4m[48;5;0m~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - minerobber[10C[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[9;1H[38;5;4m[48;5;0m~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~[38;5;7m[48;5;0msame tbh [38;5;4m[48;5;0m~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~[38;5;7m[48;5;0m [38;5;12m[48;5;0m~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m @tilde.town[9C[38;5;4m[48;5;0m~~[10;1H~~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~[38;5;7m[48;5;0m I iiiiikhjk been [38;5;4m[48;5;0m~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - [38;5;13m[48;5;3mselfsame[38;5;7m[48;5;0m(~town) [38;5;3m[48;5;0m│[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[11;1H[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~[38;5;7m[48;5;0m moving around [38;5;4m[48;5;0m~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~~[38;5;12m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - [38;5;6m[48;5;0mZhorken[38;5;7m[48;5;0m@awoo.space [38;5;3m[48;5;0m│[38;5;4m[48;5;0m~~[12;1H~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~[38;5;7m[48;5;0m I am bad at VIM [38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~[38;5;7m[48;5;0m I didnt know [38;5;4m[48;5;0m~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - [38;5;1m[48;5;0mnoiob[38;5;2m[48;5;0m@[38;5;3m[48;5;0mawoo[38;5;4m[48;5;0m.[38;5;5m[48;5;0mspace[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[13;1H[38;5;4m[48;5;0m~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~~~~~~~~[38;5;7m[48;5;0m this is[38;5;4m[48;5;0m~[38;5;7m[48;5;0mvim[38;5;12m[48;5;0m~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m -[38;5;2m[48;5;0m [38;5;4m[48;5;2m [38;5;2m[48;5;0m [38;5;3m[48;5;2m [38;5;7m[48;5;0m [6C[38;5;4m[48;5;0m~~[14;1H~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~[38;5;7m[48;5;3mit's vim[38;5;4m[48;5;0m~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - aeon[16C[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[15;1H~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~[38;5;12m[48;5;0m~~[38;5;7m[48;5;0mvim is [38;5;2m[48;5;0m bad[38;5;7m[48;5;0m hello lynn[38;5;4m[48;5;0m~~~~~~~~~[38;5;7m[48;5;0m [38;5;4m[48;5;0m~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~~[38;5;12m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;2m[48;5;0m- unascribed[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;4m[48;5;0m~~[16;1H~~~~~~~~[38;5;7m[48;5;0m(lovely application, this)[38;5;4m[48;5;0m~~~~~~~~~~~~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;12m[48;5;0m~ nee (hidamari.blue)[38;5;3m[48;5;0m│[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[17;1H~[38;5;4m[48;5;0m~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m ~ curiouser [38;5;3m[48;5;0m│[38;5;4m[48;5;0m~~[18;1H~~~~~~[38;5;7m[48;5;0mthis is as much nethack[38;5;5m[48;5;3m [38;5;4m[48;5;0m~~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - clouded[38;5;8m[48;5;0m [11C[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[19;1H~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~[38;5;7m[48;5;0mas it is vim [38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~[38;5;5m[48;5;3m [38;5;0m[48;5;2msry i cnt drw[38;5;5m[48;5;3m [38;5;4m[48;5;0m~~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~~[38;5;12m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - flacs (@f1ac5) [38;5;3m[48;5;0m│[38;5;4m[48;5;0m~~[20;1H~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~[38;5;7m[48;5;0m [38;5;4m[48;5;0m~~~~~~~[38;5;5m[48;5;3m [38;5;4m[48;5;0m~~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;4m[48;5;0m- xenonnsmb (~town) [38;5;3m[48;5;0m│[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[21;1H[38;5;4m[48;5;0m~~~~~~~~~~~~~~~~~~[38;5;7m[48;5;0memacs keys pls[38;5;4m[48;5;0m~[38;5;7m[48;5;0m+[38;5;2m[48;5;0m2[38;5;4m[48;5;0m~~~~~~~~~~~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - Jakob [38;5;3m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;2m[48;5;0m [38;5;1m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;4m[48;5;0m~~[22;1H~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - [38;5;13m[48;5;0mrose! [38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[23;1H[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~[38;5;7m[48;5;0moh no its vim[38;5;4m[48;5;0m~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~[38;5;5m[48;5;3mwhoops[38;5;4m[48;5;0m~~~~~~~~~~[38;5;12m[48;5;0m~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;4m[48;5;0m~~[38;5;12m[48;5;0m~[38;5;3m[48;5;0m│ [38;5;13m[48;5;0m(@mahoushoujorose)[38;5;3m[48;5;0m │[38;5;4m[48;5;0m~~[24;1H~~~~~~[38;5;7m[48;5;0mhelp im trapped in a vim factory[38;5;4m[48;5;0m~~~~~~~~[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my |[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m - [38;5;5m[48;5;0mjess (@dogs ~town)[m[39;49m[38;5;0m[48;5;0m [38;5;3m[48;5;0m│[38;5;4m[48;5;0m~[38;5;12m[48;5;0m~[25;1H[38;5;4m[48;5;0m~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~~~~~[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~~~~~~[38;5;7m[48;5;0mI messed[38;5;5m[48;5;0m| y[38;5;7m[48;5;0mq[38;5;5m[48;5;0m|[38;5;12m[48;5;0m~[38;5;4m[48;5;0m~~[38;5;3m[48;5;0m│[38;5;7m[48;5;0m -[38;5;5m[48;5;0m [38;5;13m[48;5;0mrachel (@arjache)[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;4m[48;5;0m~~[4h~[4l[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H [38;5;6m[48;5;6m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;2m[48;5;0m - Falkreon[38;5;7m[48;5;0m [11C3 + Roses are [38;5;9m[48;5;0mred[38;5;7m[48;5;0m. [38;5;6m[48;5;6m^[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;7m[48;5;6m- mitosis@manhater.io[38;5;3m[48;5;0m│[K +[38;5;7m[48;5;0m Violets are [38;5;12m[48;5;0mblue[38;5;7m[48;5;0m. [38;5;6m[48;5;6m^[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;5m[48;5;0m- yrgfm[38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;7m[48;5;0m [11C[K +Some people don't think [38;5;6m[48;5;6m^[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my |[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;6m[48;5;0m- felix[38;5;7m[48;5;0m [38;5;3m[48;5;0m [12C[38;5;7m[48;5;0m[K + ascii.town be like it is. [38;5;7m[48;5;6m [38;5;7m[48;5;0m [38;5;5m[48;5;0m| y[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m - ndiesslin [6C[K + But it [38;5;13m[48;5;0mdo[38;5;7m[48;5;0m! [38;5;6m[48;5;6m^[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│ [38;5;7m[48;5;0m- matth [38;5;3m[48;5;0m [38;5;7m[48;5;0m [2C[K + [38;5;6m[48;5;6m^[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│ [38;5;1m[48;5;0m- timi[38;5;3m[48;5;0m [38;5;7m[48;5;0m [8C[K + .-. .-. .-. .-. .-..-. .----. .-. .-. [38;5;6m[48;5;6m^[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;3m[48;5;0m │ [38;5;2m[48;5;0m- @andwhatnot2[38;5;3m[48;5;0m [7C[38;5;7m[48;5;0m[K + } \/ { \ \/ / | ' / } |__} \ \ / [38;5;6m[48;5;6m^[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;3m[48;5;0m │ [38;5;7m[48;5;0m- Spocky [9C[K + | { } | `-\ } | . \ } '__} `- } [38;5;7m[48;5;6m [38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;3m[48;5;0m │ [38;5;2m[48;5;0m-[38;5;3m[48;5;0m [38;5;2m[48;5;0mKid Iccurus[38;5;3m[48;5;0m [38;5;7m[48;5;0m [5C[K + `-' `-' `-' `-'`-` `----' -' [38;5;7m[48;5;6m [38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;3m[48;5;0m │ [38;5;7m[48;5;0m- tom@slime.global :3[C[K + [38;5;7m[48;5;6m [38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;4m[48;5;1m-[38;5;7m[48;5;0m [38;5;0m[48;5;1m [38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0m[K +[38;5;7m[48;5;6m [38;5;6m[48;5;6m [38;5;7m[48;5;6m [38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;0m[48;5;1m lr[38;5;7m[48;5;0m [38;5;3m[48;5;0m [8C[38;5;7m[48;5;0m [38;5;3m[48;5;0m[K +[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m - fennecs (helo)[6C[K + [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m this poem sd [38;5;4m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│ [38;5;7m[48;5;0m- @amsomniac (mastod[2C[K + [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m is factually [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│ [38;5;7m[48;5;0m on) [C[K + incorrect[38;5;8m[48;5;0m, but[38;5;15m[48;5;0m [38;5;7m[48;5;0m [38;5;2m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my |[38;5;7m[48;5;0m [38;5;3m[48;5;0m│ [38;5;7m[48;5;0m- maxj [11C[K + [38;5;8m[48;5;0mat least it rhymes[38;5;15m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0m| y |[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;1m [38;5;7m[48;5;0m AdamThePhantump was[C[K + [38;5;2m[48;5;0m [38;5;7m[48;5;0mi love this place [38;5;2m[48;5;0m [38;5;5m[48;5;0m | y[38;5;7m[48;5;0m [38;5;5m[48;5;0m| [38;5;3m[48;5;0m│ [38;5;7m[48;5;0mhere. Boo! :)[38;5;3m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m└┐[38;5;7m[48;5;0m[K + [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│ [38;5;7m[48;5;0mTsuki "Hello guys !"[38;5;3m[48;5;0m│[38;5;7m[48;5;0m[K + now I can't sleep [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my |[38;5;7m[48;5;0m [38;5;3m[48;5;0m│ [38;5;7m[48;5;0m- @dejawu_ [38;5;3m[48;5;0m┌┘[38;5;7m[48;5;0m[K + [38;5;5m[48;5;0m| y[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│ [38;5;7m[48;5;0m- @eal [14C[K + zzzz [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│ [38;5;7m[48;5;2m@social.sakamoto.gq[C[38;5;7m[48;5;0m[K +[38;5;1m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│ [38;5;7m[48;5;0m- frenata [C[K +[38;5;11m[48;5;0mwe are addicts. we need an intervention [38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0mq[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│ [38;5;7m[48;5;0m- banjo [3C[K[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H [38;5;7m[48;5;0mWhat is this big fucking line of 'y'? [1;56H [38;5;1m[48;5;0m-Ethan[38;5;12m[48;5;0m [38;5;7m[48;5;0m [12C4 + [38;5;13m[48;5;0mWhat is this big fucking line of 'y', you [2;57H[38;5;3m[48;5;0m- [38;5;2m[48;5;0mProfpatsch[38;5;7m[48;5;0m [38;5;3m[48;5;0m└──────┐[38;5;7m[48;5;0m[K + [38;5;13m[48;5;0mmay ask yourself? It's the torus ouroboros,[3;59H[38;5;5m[48;5;0mviv@cybre.space +[38;5;7m[48;5;0m [38;5;13m[48;5;0mayying its own lmao. [38;5;9m[48;5;0m [38;5;7m[48;5;0m [38;5;13m[48;5;0m [38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [6C- dom96 ;)[5;18H [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [5;59Hcloin[38;5;3m[48;5;0m [38;5;7m[48;5;0m [6;15H [6;44H [6;56H [38;5;5m[48;5;0m- dbucklin[38;5;7m[48;5;0m [11C[38;5;2m[48;5;0m[K[7;10Hi went all the way around the[38;5;7m[48;5;0m [7;56H[38;5;4m[48;5;0m - hiljusti[38;5;7m[48;5;0m + [38;5;7m[48;5;3m [38;5;2m[48;5;0mros[38;5;7m[48;5;0m [7C [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;1m[48;5;0m- @ercts_mxms [38;5;7m[48;5;0m + [38;5;2m[48;5;0m leave some notes on the[38;5;7m[48;5;0m [7C [38;5;3m[48;5;0m│[38;5;7m[48;5;0m -f@r4ch0 + [38;5;2m[48;5;0mway for your fellow travellersr[38;5;7m[48;5;0m [7C [38;5;3m[48;5;0m│[38;5;7m[48;5;0m ffffff[38;5;3m[48;5;0m +[C[38;5;7m[48;5;0m [38;5;2m[48;5;0m~chr[38;5;7m[48;5;0m [7C [38;5;3m[48;5;0m│[38;5;7m[48;5;0m -Harper +[73D[38;5;8m[48;5;0m [38;5;9m[48;5;0mthis is pretty sweet [12;44H[38;5;7m[48;5;0m [12;53H [38;5;3m[48;5;0m│[38;5;7m[48;5;0m -@discordalert[8C[K + [38;5;9m[48;5;0m -meena[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my |[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m - [38;5;2m[48;5;0mvon [7C[38;5;7m[48;5;0m [8C[K[14;52H[38;5;5m[48;5;0m [6C[38;5;7m[48;5;0mShadowRZ [15;11Hworld wide web! [15;44H [15;56H (@ShadowRZ@mastodon[16;13Hinternet [16;56H [11C.xyz + [38;5;2m[48;5;0mYour journey begins now. [38;5;7m[48;5;0m [17;50H [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m - [38;5;8m[48;5;0mQB[38;5;6m[48;5;0mFr[38;5;15m[48;5;0meak +[38;5;2m[48;5;0m [38;5;7m[48;5;0m [18;48H [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m - [38;5;6m[48;5;0m@scanlime[38;5;7m[48;5;0m +Tuco [38;5;1m[48;5;0m<3[19;15H[38;5;7m[48;5;0mwow this is very exciting [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;6m[48;5;0m@diode.zone[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m[K[20;29H-b0rk[20;56H [38;5;10m[48;5;0m- zer00[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m[K[21;14H O [21;50H [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;5m[48;5;0m- kokakoda[11C[38;5;3m[48;5;0m│[38;5;7m[48;5;0m[K + ifdfd[22;21H\|/[22;37H[38;5;9m[48;5;0m([38;5;15m[48;5;0m![38;5;9m[48;5;0m)[22;48H[38;5;7m[48;5;0m [7C[38;5;2m[48;5;0m - m3tax +[70D[38;5;7m[48;5;0mo_O[23;22H|[7C [38;5;9m[48;5;0m([38;5;15m[48;5;0m!!!!![38;5;9m[48;5;0m)[23;56H[38;5;7m[48;5;0m ~~~zgebiit~~~ + [24;21H/ \[24;32H[38;5;12m[48;5;0mhey b0rk!![24;56H[38;5;7m[48;5;0m - gauntlet + a - lastrik[K[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H [38;5;7m[48;5;0m [1;57H- CX [14C5 +[38;5;6m[48;5;0m [38;5;4m[48;5;0m######################################[38;5;7m[48;5;0m [2;57H- tomjschwanke [7C[38;5;3m[48;5;0m[K +[C[38;5;4m[48;5;0m# [38;5;7m[48;5;0m [38;5;4m[48;5;0m#[38;5;7m[48;5;0m [3;57H- @aliasless +[38;5;4m[48;5;0m #[38;5;7m[48;5;0m [38;5;4m[48;5;0mMastodon #[38;5;7m[48;5;0m [4;57H @awoo.wolfgirl. + [38;5;4m[48;5;0m#[5;12H[38;5;6m[48;5;0m [5;39H[38;5;4m[48;5;0m#[5;57H[38;5;7m[48;5;0m engineering + [38;5;4m[48;5;0m# Giving social networks back to you[38;5;6m[48;5;0m [38;5;4m[48;5;0m#[38;5;6m[48;5;0m [6;57H[38;5;7m[48;5;0m- cosine [12C[K + [38;5;4m[48;5;0m# Free, decentralized microblogging #[38;5;6m[48;5;0m [7;56H[38;5;7m[48;5;0m [38;5;0m[48;5;1m- stevenleeg +[C[38;5;4m[48;5;0m#[8;17H[38;5;7m[48;5;0m [8;39H[38;5;4m[48;5;0m#[8;57H[38;5;7m[48;5;0m- steampunc + [38;5;4m[48;5;0m# https://joinmastodon.org #[9;57H[38;5;2m[48;5;0m- @blinry +[C[38;5;4m[48;5;0m######################################[38;5;7m[48;5;0m [10;57H- ben@tilde.team [11;35H [11;56H[38;5;1m[48;5;0m [38;5;7m[48;5;0m- Alpatron#6158 +[67D [38;5;4m[48;5;0m [38;5;5m[48;5;0m####################################[12;58H[38;5;7m[48;5;0m Calamitous +[63D[38;5;5m[48;5;0m# [13;43H#[6C[38;5;7m[48;5;0m [6C [38;5;1m[48;5;0m [38;5;7m[48;5;0m +[55D[38;5;5m[48;5;0m#[14;19HWitches Town[14;43H#[14;52H[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;1m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m +[59D[38;5;5m[48;5;0m# #[15;59H[38;5;1m[48;5;0m [38;5;7m[48;5;0m +[70D[38;5;5m[48;5;0m# Mastodon insta[38;5;7m[48;5;0mn[38;5;5m[48;5;0mce for [38;5;7m[48;5;0m [38;5;5m[48;5;0m# [16;59H[38;5;1m[48;5;0m [14C[38;5;7m[48;5;0m + [38;5;5m[48;5;0m#[38;5;7m[48;5;0m [38;5;5m[48;5;0mqueer[38;5;2m[48;5;0m [38;5;5m[48;5;0m feminist[6Canarchists #[17;57H[38;5;7m[48;5;0m + [38;5;5m[48;5;0m# [38;5;7m[48;5;0m [18;43H[38;5;5m[48;5;0m#[18;57H[38;5;7m[48;5;0m +[70D[38;5;5m[48;5;0m# https://witches.town # [19;59H[38;5;7m[48;5;0m +[62D[38;5;5m[48;5;0m####################################[20;57H[38;5;7m[48;5;0m +<<<more advertising goes over there<<<[21;57H + [38;5;3m[48;5;7m ,-._.~~~^-v^v^v**=>[38;5;7m[48;5;0m [22;37H [22;56H + [38;5;3m[48;5;7m/[38;5;4m[48;5;7mlife is better on[38;5;3m[48;5;7m/ [7C[38;5;9m[48;5;0m<3[38;5;7m[48;5;0m [23;60H + [38;5;3m[48;5;7m*[38;5;4m[48;5;7m/a.weirder.earth/[38;5;3m[48;5;7m* [38;5;7m[48;5;0m [24;32H [24;57H + [38;5;3m[48;5;7m \______________/* [38;5;7m[48;5;0m love you[25;47H[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0mq[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [12C[38;5;3m[48;5;0m│[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H[1;57H[38;5;7m[48;5;0m [18C6 + [38;5;9m[48;5;0msomeone.. feel free to reuse this space.[2;57H[38;5;7m[48;5;0m [38;5;3m[48;5;0m└[7C[38;5;7m[48;5;0m[K + [38;5;9m[48;5;0mit's uh... i wouldn't feel bad about [3;57H[38;5;7m[48;5;0m + [38;5;9m[48;5;0merasing[38;5;1m[48;5;0m [38;5;9m[48;5;0mwhatever this is [38;5;7m[48;5;0m [4;60H + [5;12H[38;5;2m[48;5;0m [38;5;7m[48;5;0m [38;5;11m[48;5;0m [5;39H[38;5;7m[48;5;0m [5;61H + [38;5;9m[48;5;0m [38;5;7m[48;5;0m [38;5;15m[48;5;0m [38;5;8m[48;5;0mTidied. The ASCII Janitor.[38;5;15m[48;5;0m [38;5;7m[48;5;0m [6;57H + [38;5;6m[48;5;0m [38;5;7m[48;5;0m [38;5;11m[48;5;0m [38;5;7m[48;5;0m [7;57H + [8;15H[38;5;6m[48;5;0m [38;5;11m[48;5;0m [38;5;6m[48;5;0m [m[39;49m[38;5;0m[48;5;0m [38;5;6m[48;5;0m [8;57H[38;5;7m[48;5;0m + WHATS UP MY GLIB GLOBS?[38;5;11m[48;5;0m [38;5;7m[48;5;0m [9;57H + W U B A L U B A D U B D U B[38;5;11m[48;5;0m [10;57H[38;5;7m[48;5;0m [11;12H*~~~~~~~~~~~~~~~~~~~[38;5;15m[48;5;0m~~~~[38;5;11m[48;5;0m* [11;56H[38;5;7m[48;5;0m +[65D < tilde.town <3s u > [12;57H +[61D *~~~~~~~~~~~~~~~~~~~~~~~*[6C [13;58H [38;5;3m[48;5;0m . +[59D[38;5;7m[48;5;0m [14;19H< shell in today >[6C [14;58H +[59D *[38;5;15m[48;5;0m~~~~~~~~~~~~~~~~*[38;5;7m[48;5;0m [15;59H +[52D [38;5;11m[48;5;0m [38;5;15m[48;5;0m < :3 >[38;5;7m[48;5;0m [16;57H[38;5;3m[48;5;0mi want to avoid +[64D[38;5;7m[48;5;0m *[38;5;15m[48;5;0m~~~~*[38;5;7m[48;5;0m [17;57H[38;5;3m[48;5;0mconfusion for new +[66D[38;5;7m[48;5;0m [18;43H [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0mpeople though[9C[38;5;12m[48;5;0m[K +[71D[38;5;7m[48;5;0m [38;5;1m[48;5;0mthis thing is cool[38;5;7m[48;5;0m [19;57H[38;5;4m[48;5;0mor make the guestbook +[C[38;5;1m[48;5;0mtoo bad i[38;5;7m[48;5;0m [38;5;2m[48;5;0mssss[38;5;7m[48;5;0m [38;5;2m[48;5;0mu[38;5;7m[48;5;0m [38;5;2m[48;5;0mu[38;5;7m[48;5;0m [38;5;2m[48;5;0mk[38;5;7m[48;5;0m [38;5;2m[48;5;0mk[38;5;7m[48;5;0m [20;56H[38;5;4m[48;5;0m go AROUNDWAYS[38;5;7m[48;5;0m [38;5;11m[48;5;0m [38;5;3m[48;5;0m│[38;5;11m[48;5;0m +[38;5;7m[48;5;0m [38;5;2m[48;5;0ms[38;5;7m[48;5;0m [38;5;2m[48;5;0mu[38;5;7m[48;5;0m [38;5;2m[48;5;0mu[38;5;7m[48;5;0m [38;5;2m[48;5;0mkk [38;5;7m[48;5;0m [21;57H[38;5;3m[48;5;0mto be continued... +[72D[38;5;7m[48;5;0m [38;5;2m[48;5;0mssss[38;5;7m[48;5;0m [38;5;2m[48;5;0mu[38;5;7m[48;5;0m [38;5;2m[48;5;0mu[38;5;7m[48;5;0m [38;5;2m[48;5;0mkk [22;51H[38;5;5m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0mwe need to move the +[73D[38;5;7m[48;5;0m [38;5;2m[48;5;0ms u[38;5;7m[48;5;0m [38;5;2m[48;5;0mu[38;5;7m[48;5;0m [38;5;2m[48;5;0mk k[38;5;7m[48;5;0m [23;51H[38;5;3m[48;5;0m├───┘[38;5;7m[48;5;0m [38;5;3m[48;5;0mtext below elsewhere[38;5;7m[48;5;0m [38;5;3m[48;5;0m└─┐[24;3H[38;5;7m[48;5;0m [38;5;2m[48;5;0mssss[38;5;7m[48;5;0m [38;5;2m[48;5;0muuuu[38;5;7m[48;5;0m [38;5;2m[48;5;0mk[38;5;7m[48;5;0m [38;5;2m[48;5;0m k[38;5;7m[48;5;0m [38;5;1m[48;5;0mat ascii art[24;51H[38;5;5m[48;5;0m│[38;5;7m[48;5;0m (Snek petting dude moved to[38;5;3m[48;5;0m│[25;3H[38;5;7m[48;5;0m [38;5;1m[48;5;0m-xenonnsmb, from ~town[25;54H[38;5;7m[48;5;0m000,009.)[15C [38;5;3m[48;5;0m│[4h[38;5;7m[48;5;0m [4l[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H[1;24H [1;37H[38;5;3m[48;5;0m [41C[38;5;7m[48;5;0m7 + [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;7m[48;5;0m + [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;7m[48;5;0m [3;57H[38;5;3m[48;5;0mShrine of the church +[75D[38;5;7m[48;5;0m [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [4;37H[38;5;3m[48;5;0m [4;60H [5;10H[38;5;7m[48;5;0mempty lot [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [6C[38;5;3m[48;5;0m [5;61H[38;5;12m[48;5;4mo o o o +[66D[38;5;7m[48;5;0m COME ON DOWN [38;5;3m[48;5;0m [38;5;7m[48;5;0m [6;60H[38;5;12m[48;5;4mo[38;5;12m[48;5;5m [38;5;12m[48;5;4mo[7;15H[38;5;7m[48;5;0mTO ASCII TOWN~~~[6C[38;5;3m[48;5;0m [7;59H[38;5;12m[48;5;4mo[38;5;12m[48;5;5m [38;5;15m[48;5;5m..[38;5;12m[48;5;5m [38;5;12m[48;5;4mo[8;15H[38;5;7m[48;5;0m [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;7m[48;5;0m [8;58H[38;5;12m[48;5;4mo[38;5;15m[48;5;5m |----[38;5;12m[48;5;5m [38;5;12m[48;5;4mo +[65D[38;5;7m[48;5;0m [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [6C[38;5;3m[48;5;0m [9;58H[38;5;12m[48;5;4mo[38;5;15m[48;5;5m ||[38;5;12m[48;5;5m [38;5;15m[48;5;5m [38;5;12m[48;5;4mo +[64D[38;5;7m[48;5;0m uiwiidr [38;5;3m[48;5;0m [38;5;7m[48;5;0m [10;58H[38;5;12m[48;5;4mo[38;5;15m[48;5;5m --- [38;5;12m[48;5;4mo[38;5;7m[48;5;0m [38;5;3m[48;5;0mof[11;12H[38;5;7m[48;5;0m [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;7m[48;5;0m [11;58H[38;5;12m[48;5;4mo[38;5;15m[48;5;5m |. [38;5;12m[48;5;4mo[12;12H[38;5;7m[48;5;0m [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [6C [38;5;3m[48;5;0m [12;58H[38;5;12m[48;5;4mo[38;5;15m[48;5;5m .. [38;5;12m[48;5;4mo[13;12H[38;5;7m[48;5;0m [38;5;6m[48;5;0m [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m [13;58H[38;5;12m[48;5;4mo[38;5;15m[48;5;5m \--- [38;5;12m[48;5;4mo[14;13H[38;5;7m[48;5;0mI walked the [38;5;3m[48;5;0m [14;50H[38;5;5m[48;5;0m [7C[38;5;12m[48;5;4mo[38;5;15m[48;5;5m ,/ [38;5;12m[48;5;4mo[15;16H[38;5;5m[48;5;0mourobouros[38;5;7m[48;5;0m [38;5;3m[48;5;0m [15;48H[38;5;5m[48;5;0m [15;59H[38;5;12m[48;5;4mo[38;5;15m[48;5;5m ' [38;5;12m[48;5;4mo[16;18H[38;5;7m[48;5;0mand all I got was this[16;57H [38;5;12m[48;5;4mo o[38;5;15m[48;5;5m [38;5;12m[48;5;4mo o[38;5;7m[48;5;0m [17;23Hlousy t-shirt [38;5;3m[48;5;0m [17;57H[38;5;7m[48;5;0m [38;5;12m[48;5;4m o [38;5;7m[48;5;0m +[68D_---.___,---_ [m[39;49m[38;5;0m[48;5;0m [18;37H[38;5;3m[48;5;0m [18;47H[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;12m[48;5;0m┌[38;5;7m[48;5;0m┘[38;5;3m[48;5;0m GNU Emacs [6C[38;5;7m[48;5;0m└[38;5;12m[48;5;0m─┐[19;5H[38;5;7m[48;5;0m/ \ [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [19;37H[38;5;3m[48;5;0m [19;54H[38;5;12m[48;5;0m│[38;5;12m[48;5;5m=========================[38;5;12m[48;5;0m│[20;2H[38;5;7m[48;5;0m /| [38;5;5m[48;5;0m|y|[38;5;7m[48;5;0m |\ [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [20;37H[38;5;3m[48;5;0m [20;54H[38;5;12m[48;5;0m│[38;5;12m[48;5;4m https://gnu.org/s/emacs [38;5;12m[48;5;0m│[21;8H[38;5;7m[48;5;0m| | [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [21;37H[38;5;3m[48;5;0m [21;54H[38;5;12m[48;5;0m│[38;5;12m[48;5;5m=========================[38;5;12m[48;5;0m│[22;8H[38;5;7m[48;5;0m| | <- amazing shirt[22;51H[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;12m[48;5;0m└[38;5;7m[48;5;0m┐ ┌[38;5;12m[48;5;0m─┘[23;8H[38;5;7m[48;5;0m|_______| would i buy it?[23;51H[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0mEmacs faithfuls [38;5;7m[48;5;0m [38;5;6m[48;5;0m| [38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m[K[24;14H [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;7m[48;5;0m [24;51H[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;7m[48;5;0m [38;5;6m[48;5;0m\|/[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m[K[25;16H maybe [38;5;3m[48;5;0m [25;54H[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0msign[m[39;49m[38;5;0m[48;5;0mq[38;5;3m[48;5;0mbelow:[6C[38;5;6m[48;5;0mv[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m[K[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H[1;24H[38;5;7m[48;5;0m [1;38H[38;5;3m[48;5;0m [1;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0m- [X] [38;5;6m[48;5;0mnee [8C[38;5;7m[48;5;0m8 + [38;5;1m[48;5;0mi may prefer vim, but emacs[38;5;7m[48;5;0m [38;5;1m[48;5;0mhas tetris[38;5;3m[48;5;0m [2;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0m- [X] [38;5;2m[48;5;0mbyxor[11C[38;5;5m[48;5;5m[K +[C[38;5;1m[48;5;0mwhich is a stellar feature[3;38H[38;5;3m[48;5;0m [3;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m - [38;5;3m[48;5;0m[X[38;5;7m[48;5;0m [38;5;3m[48;5;0m @amsomniac ([38;5;7m[48;5;0m [C[38;5;5m[48;5;5m[K +[38;5;1m[48;5;0mhonestly makes me like emacs[4;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m ][38;5;3m[48;5;0mmastodon[38;5;4m[48;5;0m [38;5;3m[48;5;0m) [38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;5m [38;5;7m[48;5;3m[K[5;10H[38;5;7m[48;5;0m [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [m[39;49m[38;5;0m[48;5;0m [5;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m - [X[38;5;2m[48;5;0m][38;5;7m[48;5;0m tokenrove[7C[38;5;7m[48;5;5m [38;5;7m[48;5;3m[K +[76D[38;5;1m[48;5;0ml[38;5;7m[48;5;0m [38;5;5m[48;5;0mshame it doesn't have[6;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;2m[48;5;0m- [X[38;5;7m[48;5;0m][38;5;2m[48;5;0m rkallos[9C[38;5;7m[48;5;5m [38;5;7m[48;5;4mG[7;4H[38;5;1m[48;5;0mj[38;5;7m[48;5;0m [38;5;5m[48;5;0ma text editor[38;5;7m[48;5;0m [7;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m - [X[m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m nephariuz[7C[38;5;7m[48;5;5m [38;5;7m[48;5;4m[K +[38;5;5m[48;5;0m fucking heretics[38;5;7m[48;5;0m SHAME!!! [38;5;3m[48;5;0m [38;5;7m[48;5;0m [8;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m -[m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0m [m[39;49m[38;5;0m[48;5;0m [38;5;7m[48;5;0mevoxel [7C[38;5;7m[48;5;5m [38;5;7m[48;5;4m[K +[C[38;5;7m[48;5;0memacs doesn't have :smile though :)[9;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;6m[48;5;0m- :wq!<CR> [38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;5mM[38;5;7m[48;5;4m[K[10;18H[38;5;7m[48;5;0m [10;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;5m [38;5;7m[48;5;4m[K[11;24H[38;5;7m[48;5;0m l[11;42Hi[11;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [7C[38;5;7m[48;5;5m [38;5;7m[48;5;4m[K[12;24H[38;5;7m[48;5;0m [12;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;1m[48;5;0m - [M-x] zge[38;5;7m[48;5;0m [7C[38;5;7m[48;5;5m [38;5;7m[48;5;4mN[13;15H[38;5;7m[48;5;0m [13;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;1m[48;5;0m [6C[38;5;7m[48;5;5m [38;5;7m[48;5;4m[K[14;13H[38;5;7m[48;5;0m [14;38H [14;50H [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [8C[38;5;7m[48;5;5m [38;5;7m[48;5;4m[K +[38;5;7m[48;5;0mo---------o o----o [15;48H [38;5;5m[48;5;0my[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [9C[38;5;7m[48;5;5mA[38;5;7m[48;5;4m[K +[38;5;7m[48;5;0m|[16;11H| | [38;5;8m[48;5;4m [38;5;7m[48;5;0m | [38;5;3m[48;5;0m [38;5;7m[48;5;0m [16;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [10C[38;5;7m[48;5;5m [38;5;7m[48;5;4m[K +[38;5;7m[48;5;0m|[17;11H| |[38;5;8m[48;5;0m [38;5;8m[48;5;4m [38;5;7m[48;5;0m | [17;53H[38;5;7m[48;5;5m [17;63H[38;5;7m[48;5;0m [13C[38;5;7m[48;5;5m [38;5;7m[48;5;4mU[18;1H[38;5;7m[48;5;0m| [38;5;7m[48;5;2m [38;5;7m[48;5;0m [38;5;7m[48;5;3m [38;5;7m[48;5;0m | | | [18;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [6C[38;5;3m[48;5;0m│[38;5;7m[48;5;5m [38;5;7m[48;5;3m[K +[38;5;7m[48;5;0m| [38;5;8m[48;5;3m [38;5;7m[48;5;0m [38;5;7m[48;5;2m [38;5;7m[48;5;0m [38;5;7m[48;5;3m [38;5;7m[48;5;0m | o----o [19;38Hstop here[6C[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;5m [38;5;7m[48;5;3m[K +[38;5;7m[48;5;0m| [38;5;8m[48;5;3m [38;5;7m[48;5;0m [38;5;7m[48;5;2m [38;5;7m[48;5;0m [38;5;7m[48;5;5m [38;5;7m[48;5;3m [38;5;7m[48;5;0m| [6C [20;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;5mC[38;5;7m[48;5;3m[K +[38;5;7m[48;5;0m|[38;5;8m[48;5;0m [38;5;8m[48;5;3m [38;5;7m[48;5;4m [38;5;7m[48;5;5m [38;5;7m[48;5;0m [38;5;7m[48;5;5m [38;5;7m[48;5;0m| 000129 go down until you[7C[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;5m [38;5;7m[48;5;3m[K +[38;5;7m[48;5;0m| [38;5;7m[48;5;3m [38;5;7m[48;5;4m [38;5;7m[48;5;5m [38;5;7m[48;5;6m [38;5;7m[48;5;5m [38;5;7m[48;5;0m| reach ok[22;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[22C│[38;5;7m[48;5;5m [38;5;7m[48;5;3m[K +[38;5;7m[48;5;0m| [38;5;7m[48;5;1m [38;5;7m[48;5;6m [38;5;7m[48;5;5m [38;5;7m[48;5;0m| [23;26H100 [23;53H[38;5;7m[48;5;5m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;5m [38;5;7m[48;5;3m[K +[38;5;7m[48;5;0mo---------o[24;24H [24;50H[38;5;0m[48;5;7me[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;7m[48;5;5m [38;5;3m[48;5;0m│ [m[39;49m[38;5;0m[48;5;0mq[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;5mS[38;5;7m[48;5;3m[K +[38;5;0m[48;5;7m U:%*- *Tetris* Top (1,0) (Tetris Proj[38;5;7m[48;5;0mq[38;5;0m[48;5;7mctile[rms])[K[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H[38;5;7m[48;5;0m [1;53H [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [8C9 +[38;5;2m[48;5;0m +/////////////////+[38;5;7m[48;5;0m [2;53H [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [11C[K + [38;5;2m[48;5;0m //you@cybre.space//[38;5;7m[48;5;0m [3;37H [3;53H [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [4C[K + [38;5;2m[48;5;0m +/////////////////+[38;5;7m[48;5;0m [4;37H [4;53H [6C [5C[K +[74D[38;5;3m[48;5;0m/\/\/\/\/[38;5;2m[48;5;0m\\\[38;5;3m[48;5;0m/\/\/\/\/\[38;5;7m[48;5;0m [5;53H [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [7C[K + [38;5;3m[48;5;0m/ / [6;37H[38;5;7m[48;5;0m [6;53H [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [9C[K + [38;5;3m[48;5;0m \ pleroma.soykaf.com \[7;37H[38;5;7m[48;5;0m [7;53H [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [7C[K + [38;5;3m[48;5;0m/[38;5;7m[48;5;0m [38;5;3m[48;5;0msee the[38;5;7m[48;5;0m [38;5;3m[48;5;0m /[38;5;7m[48;5;0m [8;53H [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [9C[K + [38;5;3m[48;5;0m\[38;5;7m[48;5;0m [38;5;2m[48;5;0m- entire -[38;5;7m[48;5;0m [38;5;3m[48;5;0m \[38;5;7m[48;5;0m I miss Karl...[9;53H [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [4C[K + [38;5;3m[48;5;0m/[38;5;7m[48;5;0m [38;5;3m[48;5;0mfediverse[6C /[10;37H[38;5;7m[48;5;0m [10;53H [24C[K + [38;5;3m[48;5;0m\[11;25H \[38;5;7m[48;5;0m [6C [11;53H [24C[K + [38;5;3m[48;5;0m \/\/\/\/\[38;5;2m[48;5;0m///[38;5;3m[48;5;0m\/\/\/\/\/[12;37H[38;5;7m[48;5;0m [12;53H [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [11C[K[13;37H [13;53H [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [38;5;3m[48;5;0m.[38;5;7m[48;5;0m [6C[K[14;21H[38;5;12m[48;5;0m/---------------------\[14;53H[38;5;7m[48;5;0m [24C[K + [38;5;12m[48;5;0m|I'm petting the snek.| [15;53H[38;5;7m[48;5;0m [24C[K + [16;11H [38;5;12m[48;5;0m| |[16;53H[38;5;7m[48;5;0m [24C[K + [17;11H [38;5;12m[48;5;0m| ^U^ |[17;53H[38;5;7m[48;5;0m [24C[K + [38;5;12m[48;5;0m\---------------------/[18;53H[38;5;7m[48;5;0m [24C[K + [X] TODO: move this too[38;5;12m[48;5;0m \[38;5;7m[48;5;0m [6C [24C[K + [m[39;49m[38;5;0m[48;5;0m[ ] TODO: write todo list[38;5;7m[48;5;0m [38;5;12m[48;5;0m [38;5;7m[48;5;0m [38;5;4m[48;5;0m@/[6C[38;5;7m[48;5;0m [21C[38;5;6m[48;5;0m [2C[38;5;7m[48;5;0m[K + [X] TODO: hey, me too [38;5;4m[48;5;0m/| [6C[38;5;7m[48;5;0m [24C[K + [22;28H [22;41H [38;5;4m[48;5;0m/ \[6C[38;5;7m[48;5;0m [24C[K + ##########################[23;53H [24C[K + # TODO: advertise things # [24;50H [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [17C[K + ########################## [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[38;5;7m[48;5;0mq[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m [m[39;49m[38;5;0m[48;5;0mq[38;5;7m[48;5;0m [38;5;3m[48;5;0m│[38;5;7m[48;5;0m[K[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H[1;55H[38;5;0m[48;5;3m│[1;71H│[6C[38;5;7m[48;5;0m10 + [2;55H[38;5;0m[48;5;3m│[2;71H└──────┐ +[C[38;5;7m[48;5;0m [3;55H[38;5;0m[48;5;3m│[22C│ +[38;5;1m[48;5;0m [38;5;7m[48;5;0m # [4;55H[38;5;0m[48;5;3m│[22C│ +[74D[38;5;7m[48;5;0m ## # ## [5;55H[38;5;0m[48;5;3m│[22C│ +[75D[38;5;7m[48;5;0m ## # ## [6;55H[38;5;0m[48;5;3m│[22C│ +[76D[38;5;7m[48;5;0m # [7;55H[38;5;0m[48;5;3m│[22C│ +[75D[38;5;7m[48;5;0m #### [6C [8;55H[38;5;0m[48;5;3m│[22C│ +[75D[38;5;7m[48;5;0m ## [9;55H[38;5;0m[48;5;3m│[22C│ +[75D[38;5;7m[48;5;0m # ### ## [10;55H[38;5;0m[48;5;3m│[38;5;7m[48;5;0m [38;5;3m[48;5;0ma[16C[38;5;0m[48;5;3m│ +[75D[38;5;7m[48;5;0m ## ## ## ## [11;55H[38;5;0m[48;5;3m│[38;5;3m[48;5;0m(hells [7C[38;5;0m[48;5;3m│ +[75D[38;5;7m[48;5;0m ### ### Capouet[12;55H[38;5;0m[48;5;3m│[38;5;3m[48;5;0m guettbook [6C[38;5;0m[48;5;3m│ +[76D[38;5;7m[48;5;0mlol this is awesome[13;55H[38;5;0m[48;5;3m│[38;5;7m[48;5;0m [38;5;3m[48;5;0m s retch[11C[38;5;0m[48;5;3m│[14;21H[38;5;7m[48;5;0m [14;55H[38;5;0m[48;5;3m│[38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0mgoals)[10C[38;5;0m[48;5;3m│[15;21H[38;5;7m[48;5;0m [15;55H[38;5;0m[48;5;3m│[22C│[16;21H[38;5;7m[48;5;0m [16;55H[38;5;0m[48;5;3m│[22C│[17;21H[38;5;7m[48;5;0m [17;55H[38;5;0m[48;5;3m│[22C│[18;21H[38;5;7m[48;5;0m [18;55H[38;5;0m[48;5;3m│[22C│ +[71D[38;5;7m[48;5;0m [19;55H[38;5;0m[48;5;3m│[22C│ +[71D[38;5;7m[48;5;0m [20;43H [20;55H[38;5;0m[48;5;3m│[20C[38;5;7m[48;5;0m [38;5;0m[48;5;3m│ +[72D[38;5;7m[48;5;0mtodo: write todo list [21;44H [21;55H[38;5;0m[48;5;3m│[22C│[22;44H[38;5;7m[48;5;0m [22;55H[38;5;3m[48;5;0m▓[22C▓ +[71D[38;5;7m[48;5;0m [23;55H[38;5;3m[48;5;0m▒[22C▒ +[71D[38;5;7m[48;5;0m [24;55H[38;5;3m[48;5;0m░[22C░ +[71D[38;5;7m[48;5;0m [25;55H [6C[K[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H [1;55H[38;5;3m[48;5;0m [1;71H[38;5;7m[48;5;0m [m[39;49m[38;5;0m[48;5;0m(000,011)[2;37H [2;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[3;37H[m[39;49m[38;5;0m[48;5;0m [3;46H[38;5;7m[48;5;0mW[3;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K + [4;13H [4;37H[m[39;49m[38;5;0m[48;5;0m [4;46H[38;5;7m[48;5;0mE[4;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K +[48D [7C [5;37H[m[39;49m[38;5;0m[48;5;0m [5;46H[38;5;7m[48;5;0mE[5;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K +[48D [38;5;6m[48;5;0m3[38;5;5m[48;5;0m333333[38;5;3m[48;5;0m3[38;5;6m[48;5;0m33333333[6;37H[m[39;49m[38;5;0m[48;5;0m [6;46H[38;5;7m[48;5;0mE[6;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[7;9H[38;5;6m[48;5;0m3[38;5;9m[48;5;0m33333333333[38;5;7m[48;5;0m [38;5;6m[48;5;0m3[7;37H[m[39;49m[38;5;0m[48;5;0m [7;46H[38;5;7m[48;5;0mE[7;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[8;9H[38;5;6m[48;5;0m3[38;5;9m[48;5;0m33[38;5;7m[48;5;0m [38;5;13m[48;5;0m3[38;5;15m[48;5;0m3[38;5;7m[48;5;0m33 [38;5;6m[48;5;0m3[38;5;7m[48;5;0m [38;5;6m[48;5;0m3[8;37H[m[39;49m[38;5;0m[48;5;0m [8;46H[38;5;7m[48;5;0mE[8;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[9;9H[38;5;6m[48;5;0m333333333333[38;5;3m[48;5;0m3333[9;37H[m[39;49m[38;5;0m[48;5;0m [9;46H[38;5;7m[48;5;0mE[9;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[10;10H [10;37H[m[39;49m[38;5;0m[48;5;0m [10;46H[38;5;7m[48;5;0mE[10;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[11;10H [11;37H[m[39;49m[38;5;0m[48;5;0m [11;46H[38;5;7m[48;5;0mH[11;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[12;11H [12;37H[m[39;49m[38;5;0m[48;5;0m [12;46H[38;5;7m[48;5;0mE[12;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K + [13;37H[m[39;49m[38;5;0m[48;5;0m [13;46H[38;5;7m[48;5;0mE[13;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m [38;5;3m[48;5;0m [38;5;4m[48;5;0m.[38;5;7m[48;5;0m[K[14;37H[m[39;49m[38;5;0m[48;5;0m [14;46H[38;5;7m[48;5;0mH[14;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[15;37H[m[39;49m[38;5;0m[48;5;0m [15;46H[38;5;7m[48;5;0mE[15;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K + [38;5;6m[48;5;0mvi[38;5;7m[48;5;0m for the [38;5;6m[48;5;0mvi[38;5;7m[48;5;0mn![16;37H[m[39;49m[38;5;0m[48;5;0m [16;46H[38;5;7m[48;5;0mE[16;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[17;15H[38;5;6m[48;5;0m [17;37H[m[39;49m[38;5;0m[48;5;0m [17;46H[38;5;7m[48;5;0mE[17;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[18;37H[m[39;49m[38;5;0m[48;5;0m [7C[38;5;7m[48;5;0mE[18;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[19;37H[m[39;49m[38;5;0m[48;5;0m [19;46H[38;5;7m[48;5;0mE[19;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[20;37H[m[39;49m[38;5;0m[48;5;0m [20;46H[38;5;7m[48;5;0mE[20;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K +[49D [38;5;0m[48;5;1m [38;5;7m[48;5;0m [21;37H[m[39;49m[38;5;0m[48;5;0m [21;46H[38;5;7m[48;5;0mE[21;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[22;13H[38;5;0m[48;5;1m [38;5;0m[48;5;2m [38;5;0m[48;5;1m [38;5;0m[48;5;2m [38;5;0m[48;5;1m [22;37H[m[39;49m[38;5;0m[48;5;0m [22;46H[38;5;7m[48;5;0mE[22;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[23;13H[38;5;0m[48;5;1m [23;37H[m[39;49m[38;5;0m[48;5;0m [23;46H[38;5;7m[48;5;0mE[23;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[24;13H[38;5;0m[48;5;1m [38;5;0m[48;5;2m [38;5;0m[48;5;1m [24;37H[m[39;49m[38;5;0m[48;5;0m [24;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[25;13H[38;5;0m[48;5;1m [25;37H[m[39;49m[38;5;0m[48;5;0m [25;55H[38;5;3m[48;5;0m [38;5;7m[48;5;0m[K[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H[1;14H[38;5;0m[48;5;1m [1;55H[38;5;7m[48;5;0m [23C[m[39;49m[38;5;0m[48;5;0m2[2;55H[38;5;7m[48;5;0m [3;46H [5C[K[4;17Hiiit[4;38H[m[39;49m[38;5;0m[48;5;0m [7C[38;5;7m[48;5;0m [5C[K[5;19Hllgwfth[5;46H [5C[K[6;9H [6;38H[m[39;49m[38;5;0m[48;5;0m [7C[38;5;7m[48;5;0m [5C[K[7;9H [7;46H [5C[K[8;9H [8;46H [5C[K[9;9H [38;5;5m[48;5;0mAcme is best editor :P[9;46H[38;5;7m[48;5;0m [5C[K +[6D [5C[K[11;18His that a plan9 reference [5C[K +[6D [5C[K[13;22His that a Jojo's referenc[13;55H [13;65H [38;5;3m[48;5;0m.[14;37H[38;5;7m[48;5;0m [14;46H [5C[K[15;25His that a let's encryp[5C [38;5;3m[48;5;0m +[58D[38;5;7m[48;5;0m [16;46H [5C[K[17;15H [17;46H [5C[K[18;38H [7C [18;55H[38;5;2m[48;5;0md[19;46H[38;5;7m[48;5;0m [19;55H[38;5;2m[48;5;0ms[20;46H[38;5;7m[48;5;0m [20;55H[38;5;2m[48;5;0mf[21;13H[38;5;7m[48;5;0m [21;46H [21;55H[38;5;2m[48;5;0ms[22;13H[38;5;7m[48;5;0m [22;46H [22;55H[38;5;2m[48;5;0mffd[38;5;7m[48;5;0m [38;5;2m[48;5;0mhello[38;5;7m[48;5;0m [38;5;2m[48;5;0mthere[23;13H[38;5;7m[48;5;0m [23;46H [23;55H[38;5;2m[48;5;0mo[38;5;7m[48;5;0m [38;5;2m[48;5;0mf[38;5;7m[48;5;0m [38;5;2m[48;5;0ml [24;13H[38;5;7m[48;5;0m eGenre l?[24;38H [24;55H[38;5;2m[48;5;0md[25;13H[38;5;7m[48;5;0m [25;55H[38;5;2m[48;5;0mj[38;5;3m[48;5;0m[1P[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H[1;14H[38;5;7m[48;5;0m [1;37H [1;55H[38;5;2m[48;5;0mp[23C[m[39;49m[38;5;0m[48;5;0m3[2;37H[38;5;7m[48;5;0m [2;52H[38;5;3m[48;5;0m[1P[3;37H[38;5;7m[48;5;0m [3;59H[38;5;2m[48;5;0mx[38;5;3m[48;5;0m[1P[4;17H[38;5;7m[48;5;0m [4;37H [4;59H[38;5;2m[48;5;0mm[38;5;3m[48;5;0m[1P[5;19H[38;5;7m[48;5;0m [5;37H [5;59H[38;5;2m[48;5;0mp[6;37H[38;5;7m[48;5;0m [6;59H[38;5;2m[48;5;0mp[7;37H[38;5;7m[48;5;0m [7;59H[38;5;2m[48;5;0m +[53D[38;5;7m[48;5;0mi want to pet a soft girl [8;59H[38;5;2m[48;5;0mp +[52D[38;5;7m[48;5;0mand call her by her first name[9;59H[38;5;2m[48;5;0mu +[52D[38;5;7m[48;5;0mgod, i'm so lonely.[10;37H [10;59H[38;5;2m[48;5;0mb[11;18H[38;5;7m[48;5;0m [11;59H[38;5;2m[48;5;0ml +[54D[38;5;10m[48;5;0mYou can do it[12;37H[38;5;7m[48;5;0m [12;59H[38;5;2m[48;5;0mi[13;22H[38;5;7m[48;5;0m [13;59H[38;5;2m[48;5;0mc +[54D[38;5;10m[48;5;0mGo out there and talk to people[14;59H[38;5;2m[48;5;0mg[15;25H[38;5;7m[48;5;0m [15;59H[38;5;2m[48;5;0m@[38;5;7m[48;5;0m[K +[54D[38;5;10m[48;5;0mEven if it doesnt go anywhere, don't[16;59H[38;5;2m[48;5;0mc +[54D[38;5;10m[48;5;0msee it as a loss, see it as practice[17;59H[38;5;2m[48;5;0mo[18;37H[38;5;7m[48;5;0m [18;55H [38;5;2m[48;5;0mn +[54D[38;5;10m[48;5;0mI believe in you[19;37H[38;5;7m[48;5;0m [19;55H [38;5;2m[48;5;0mf[20;37H[38;5;7m[48;5;0m [20;55H [38;5;2m[48;5;0me +[54D[38;5;10m[48;5;0mYou will be succesful[21;37H[38;5;7m[48;5;0m [21;55H [38;5;2m[48;5;0mr[22;37H[38;5;7m[48;5;0m [22;55H [38;5;2m[48;5;0me[38;5;7m[48;5;0m[K +[54D[38;5;10m[48;5;0mYou just have to try...[23;37H[38;5;7m[48;5;0m [23;55H [38;5;2m[48;5;0mn[38;5;7m[48;5;0m[K[24;15H [24;37H [24;55H [38;5;2m[48;5;0mc +[54D[38;5;7m[48;5;0mUwU[25;37H [25;55H [38;5;2m[48;5;0me[38;5;7m[48;5;0m[K[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H[1;55H[38;5;7m[48;5;0m [38;5;2m[48;5;0m.[19C[m[39;49m[38;5;0m[48;5;0m4[2;59H[38;5;2m[48;5;0my[38;5;7m[48;5;0m[K +[38;5;2m[48;5;0mo[38;5;7m[48;5;0m[K +[38;5;2m[48;5;0mu[38;5;7m[48;5;0m[K +[38;5;2m[48;5;0mr +d +a +[53D[38;5;7m[48;5;0m [8;59H[38;5;2m[48;5;0mt +[52D[38;5;7m[48;5;0m [9;59H[38;5;2m[48;5;0ma +[52D[38;5;7m[48;5;0m [10;59H[38;5;2m[48;5;0m. +f +[54D[38;5;7m[48;5;0m [12;59H[38;5;2m[48;5;0mo +r +[54D[38;5;7m[48;5;0m [38;5;10m[48;5;0mwhere[14;59H[38;5;2m[48;5;0ms[6C[38;5;3m[48;5;0m [15;36H[38;5;10m[48;5;0mare[15;59H[38;5;2m[48;5;0ma +[54D[38;5;7m[48;5;0m [38;5;10m[48;5;0myou[38;5;7m[48;5;0m [16;59H[38;5;2m[48;5;0ml +[54D[38;5;7m[48;5;0m [38;5;10m[48;5;0mgoing[38;5;7m[48;5;0m [17;59H[38;5;2m[48;5;0me[18;36H[38;5;10m[48;5;0mthere[18;52H[38;5;7m[48;5;0m[K +[46D [19;36H[38;5;10m[48;5;0mis[19;52H[38;5;7m[48;5;0m[K[20;36H[38;5;10m[48;5;0mnothing[20;52H[38;5;7m[48;5;0m[K +[46D [21;36H[38;5;10m[48;5;0mdown[21;52H[38;5;7m[48;5;0m[K[22;36H[38;5;10m[48;5;0mhere[22;52H[38;5;7m[48;5;0m[K +[46D [7C[38;5;10m[48;5;0mi [23;52H[38;5;7m[48;5;0m[K[24;36H[38;5;10m[48;5;0mpromise[24;52H[38;5;7m[48;5;0m[K +[46D [25;36H(there is)[6C[K[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H[1;59H[38;5;7m[48;5;0m [1;72H 015[K[2;59H + +[6D[38;5;1m[48;5;0mor is there?[5;59H[38;5;7m[48;5;0m +[5Dwow[K + +[5Dyou all just[9;59H +[5Dbb[K[11;19Hhello[11;52H[K[12;59H + [14;36H [38;5;3m[48;5;0m| |[5C[38;5;7m[48;5;0m[K[15;36H [15;52H[K[16;36H [16;52H[K[17;36H [17;52H[K +[46DRewrite this in Nim for better +[35Dperformance and easier maintenance. +[35Dhttps://nim-lang.org[20;36H +[37DSorry, couldn't resist :P [22;10HRewrite it in Idris to prove its[23;10Hcorrectness :P[23;36H +[32DRemove it :P[24;36H +[35Ddasdasdasdsa[25;36H [1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H[38C[38;5;7m[48;5;0m6[4;54H[K[5;36Hwrite.[6;55H[K + +[K[9;39Hjjjjjjjjjjj +[5C[K[11;19H [13;35H[38;5;10m[48;5;0msee[38;5;7m[48;5;0m,[13;54HWhat's that creeping[14;35Hi told you [7Cwaaaaayyy over there?[15;54H-------------------->[18;6H +[30D +[35D +[20D [22;10H [23;10H +[18D +[10D[m[39;49m[38;5;0m[48;5;0mq[38;5;7m[48;5;0m [1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H[38C[38;5;7m[48;5;0m7[4;27HWhat is this ?:wq[5;27Hq[5;36H [7;57H[38;5;12m[48;5;0mthere is[8;57Htrust me[9;39H[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0my[7C[38;5;12m[48;5;0mi wrapped[13;35H[38;5;7m[48;5;0m [13;54H [38;5;3m[48;5;0m.[38;5;7m[48;5;0m[K[14;35H [7C[K + [K[25;8H [1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H[38C[38;5;7m[48;5;0m8[4;27H +[38;5;11m[48;5;0mAncient legend tells of the treasure +of the legendary pirate, +Cap'n Jasmine Sharkbait![7;52H[38;5;7m[48;5;0m[K +[38;5;11m[48;5;0mNo one knows where its shiny and well shaded[7C[38;5;7m[48;5;0m[K +[38;5;11m[48;5;0mglory is hidden, and many have perished[9;52H[38;5;7m[48;5;0m[K +[38;5;11m[48;5;0mtrying to find it. + + [15;28H[38;5;1m[48;5;0mstuff is here[17;26H[38;5;6m[48;5;0m [38;5;5m[48;5;0m |[38;5;6m[48;5;0m [1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H[1;72H[38;5;7m[48;5;0m(000,019)[3;29H[38;5;7m[48;5;1mMDG - boi + +[38;5;7m[48;5;0m [38;5;15m[48;5;0m [38;5;7m[48;5;0m + + + [8;62Hwhy though +======================================D + + + [15;28H [15;63H[38;5;3m[48;5;0m(i legit[16;63Hput tape[17;26H[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [17;63H[38;5;3m[48;5;0mon my [18;63Hscreen +[6Dto do[38;5;7m[48;5;0m [38;5;3m[48;5;0mthis)[20;63H[38;5;1m[48;5;0myou are crazy[22;55H[38;5;4m[48;5;0myou will be seen as[23;55Ha visionary[24;24H[38;5;7m[48;5;0mhello tilde.town here[25;37H[m[39;49m[38;5;0m[48;5;0mq[25;58H[38;5;7m[48;5;0mweird flex but ok[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H[1;72H[38;5;7m[48;5;0m 020[K[3;29H +[38;5;10m[48;5;0mplease [5;15H[38;5;7m[48;5;0m [38;5;10m[48;5;0mstop +this +is +silly[8;52H[38;5;7m[48;5;0m[K + [9;59H[38;5;12m[48;5;0mit[6C[38;5;3m[48;5;0mllllllllllllll[10;27H[38;5;1m[48;5;0m [6C[38;5;7m[48;5;0mno[10;59H[38;5;12m[48;5;0mis[11;12H[38;5;1m[48;5;0m [11;59H[38;5;12m[48;5;0mlovely +[59D[38;5;7m[48;5;0m"Root? Where we're going we[12;54Ht +[48Ddon't need root!"[15;63H[K[16;34Hpeople have[7C[K[17;34Hdied trying[7C[K[18;34Hdon't try :([6C[K + [38;5;9m[48;5;0mdon't stop![38;5;7m[48;5;0m[K[20;55H[38;5;9m[48;5;0mthere's nice art[38;5;7m[48;5;0m[K[21;37Huwu[21;55H[38;5;9m[48;5;0mjust ahead[22;55H [38;5;7m[48;5;0m[K +[38;5;9m[48;5;0m|||[38;5;7m[48;5;0m[K[24;24H [24;55H[38;5;9m[48;5;0mvvv[25;37H[38;5;7m[48;5;0m [25;52H[K[1;41H[m[39;49m[38;5;0m[48;5;0m + + + + + + + + + + + + + + + + + + + + + + + +[1;41H [38;5;12m[48;5;6m [38;5;4m[48;5;6m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;10m[48;5;0m comment space!!! [38;5;9m[48;5;0m<3[38;5;7m[48;5;0m(000,021)[2;1H[38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;4m[48;5;6m [38;5;0m[48;5;4m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@ +[38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;9m[48;5;0m__________________ +[38;5;0m[48;5;4m [38;5;5m[48;5;0m>[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m>[38;5;7m[48;5;0m [38;5;9m[48;5;0m|[38;5;2m[48;5;0m [38;5;9m[48;5;0m | +[38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;12m[48;5;6m [38;5;0m[48;5;4m [38;5;5m[48;5;0m>[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m>[38;5;7m[48;5;0m [38;5;9m[48;5;0m|[38;5;2m[48;5;0m [38;5;9m[48;5;0m>[38;5;10m[48;5;0m>[38;5;14m[48;5;0m> [38;5;11m[48;5;0mM[38;5;3m[48;5;0mORE[38;5;14m[48;5;0m [38;5;11m[48;5;0mA[38;5;3m[48;5;0mRT[38;5;14m[48;5;0m >[38;5;10m[48;5;0m>[38;5;9m[48;5;0m>[38;5;2m[48;5;0m [38;5;9m[48;5;0m| +[38;5;0m[48;5;4m [38;5;2m[48;5;7m [38;5;0m[48;5;4m [38;5;5m[48;5;0m>[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m>[38;5;7m[48;5;0m [38;5;9m[48;5;0m| [38;5;3m[48;5;0m(on (001, 021)) [38;5;2m[48;5;0m [38;5;9m[48;5;0m|[38;5;2m[48;5;0m +[38;5;0m[48;5;4m [38;5;2m[48;5;7m [38;5;8m[48;5;7m#[38;5;2m[48;5;7m [38;5;0m[48;5;4m [38;5;2m[48;5;7m [38;5;0m[48;5;4m [38;5;2m[48;5;7m [38;5;0m[48;5;4m [38;5;5m[48;5;0m> @[38;5;7m[48;5;0m [38;5;5m[48;5;0m>[38;5;7m[48;5;0m [38;5;9m[48;5;0m|___________[38;5;7m[48;5;0m [38;5;9m[48;5;0m_____|[38;5;2m[48;5;0m +[38;5;0m[48;5;4m [38;5;8m[48;5;7m#############[38;5;0m[48;5;4m [38;5;8m[48;5;7m#[38;5;0m[48;5;4m [38;5;8m[48;5;7m##############[38;5;0m[48;5;4m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[23C[38;5;2m[48;5;0m +[38;5;0m[48;5;4m [38;5;8m[48;5;4m [38;5;0m[48;5;4m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;9m[48;5;0m [38;5;2m[48;5;0m [38;5;1m[48;5;2mooh what you drawing[38;5;7m[48;5;0m[K +[38;5;0m[48;5;4m [38;5;8m[48;5;4m [38;5;6m[48;5;4m/[38;5;8m[48;5;4m [38;5;6m[48;5;4m [38;5;4m[48;5;4m [38;5;6m[48;5;4m /[38;5;8m[48;5;4m [38;5;6m[48;5;4m [38;5;8m[48;5;4m [38;5;14m[48;5;4m/[38;5;8m[48;5;4m [38;5;6m[48;5;4m/[38;5;8m[48;5;4m [38;5;6m[48;5;4m/[38;5;0m[48;5;4m [38;5;8m[48;5;4m [38;5;0m[48;5;4m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;0m[48;5;1mjust a little landscape +[38;5;0m[48;5;4m [38;5;6m[48;5;4m [38;5;0m[48;5;4m [38;5;6m[48;5;4m/[38;5;0m[48;5;4m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;1m[48;5;2mnice!! keep it up ^o^ +[38;5;0m[48;5;4m [38;5;6m[48;5;4m/[38;5;0m[48;5;4m [38;5;14m[48;5;4m/[38;5;0m[48;5;4m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;0m[48;5;1mthank you =v= +[38;5;0m[48;5;4m [38;5;6m[48;5;4m/[38;5;0m[48;5;4m [38;5;6m[48;5;4m/[38;5;0m[48;5;4m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[13;67H[38;5;3m[48;5;0m +[38;5;0m[48;5;4m [38;5;14m[48;5;4m/[38;5;0m[48;5;4m [38;5;6m[48;5;4m/[38;5;0m[48;5;4m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[14;60H[38;5;1m[48;5;2moh wait hi rose +[38;5;0m[48;5;4m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m this is wonderful +[38;5;0m[48;5;4m [38;5;14m[48;5;4m/[38;5;0m[48;5;4m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[7C[38;5;8m[48;5;7mhi alice!! +[38;5;0m[48;5;4m [38;5;10m[48;5;4m####[38;5;0m[48;5;4m [38;5;10m[48;5;4m####[38;5;0m[48;5;4m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m +[38;5;0m[48;5;4m [38;5;10m[48;5;4m#[38;5;2m[48;5;4m#####[38;5;10m[48;5;4m#[38;5;0m[48;5;4m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m [38;5;12m[48;5;0mart restored to its[38;5;5m[48;5;0m[K +[38;5;0m[48;5;4m [38;5;6m[48;5;4m/[38;5;0m[48;5;4m [38;5;10m[48;5;4m#[38;5;2m[48;5;4m#[38;5;8m[48;5;4m|[38;5;2m[48;5;4m#[38;5;10m[48;5;4m#[38;5;0m[48;5;4m [38;5;14m[48;5;4m/[38;5;0m[48;5;4m [38;5;4m[48;5;4m [38;5;0m[48;5;4m [38;5;4m[48;5;4m [38;5;0m[48;5;4m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m [38;5;12m[48;5;0mformer glory[38;5;5m[48;5;0m [38;5;4m[48;5;0m +[38;5;0m[48;5;4m [38;5;10m[48;5;4m##[38;5;2m[48;5;4m##[38;5;8m[48;5;4m|[38;5;2m[48;5;4m##[38;5;10m[48;5;4m##[38;5;0m[48;5;4m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m [38;5;12m[48;5;0mcmon, play nice yall +[38;5;0m[48;5;4m [38;5;10m[48;5;4m##[38;5;0m[48;5;4m |[38;5;10m[48;5;4m [38;5;0m[48;5;4m [38;5;10m[48;5;4m##[38;5;0m[48;5;4m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [7C[38;5;2m[48;5;0myeah! +[38;5;2m[48;5;4m||||||||||||||||||||[38;5;0m[48;5;4m\[38;5;2m[48;5;4m|||||||||||||||||||||||||[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[2C[38;5;7m[48;5;0m[K +[38;5;8m[48;5;2m#####################[38;5;0m[48;5;2m\[38;5;8m[48;5;2m########################[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m| [38;5;2m[48;5;0mthanks for watching +[38;5;8m[48;5;2m######################[38;5;0m[48;5;2m\[38;5;8m[48;5;2m#######################[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0m [38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;2m[48;5;0m i guess it's done now +[38;5;8m[48;5;2m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;5m[48;5;0m@[38;5;7m[48;5;0mq[38;5;5m[48;5;0m|[38;5;7m[48;5;0m [38;5;9m[48;5;0mwonderful :) [38;5;2m[48;5;0mmight do more[4hr[4l[1;41H[m[39;49m[38;5;0m[48;5;0m \ 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/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..9e7b4ff7 --- /dev/null +++ b/www/temp.causal.agency/up.c @@ -0,0 +1,156 @@ +/* Copyright (C) 2020 June 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/&/\&/g + s/</\</g + s/"/\"/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>' |