diff options
Diffstat (limited to '')
134 files changed, 5003 insertions, 172 deletions
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..95f52120 --- /dev/null +++ b/www/causal.agency/Makefile @@ -0,0 +1,20 @@ +WEBROOT = /usr/local/www/causal.agency + +FILES = index.html style.css scheme.css scheme.png + +all: ${FILES} + +index.html: index.7 + mandoc -T html -O style=style.css index.7 > index.html + +scheme.css: + scheme -st > scheme.css + +scheme.png: + scheme -g > scheme.png + +install: ${FILES} + install -C -m 644 ${FILES} ${WEBROOT} + +clean: + rm -f index.html scheme.css scheme.png diff --git a/www/causal.agency/index.7 b/www/causal.agency/index.7 new file mode 100644 index 00000000..489b0b53 --- /dev/null +++ b/www/causal.agency/index.7 @@ -0,0 +1,70 @@ +.Dd May 19, 2021 +.Dt CAUSAL.AGENCY 7 +.Os "Causal Agency" +. +.Sh NAME +.Nm june +.Nd computer enthusiast!! +. +.Sh SYNOPSIS +.Nm mail +.Mt june@causal.agency +.Nm +in +.Li #ascii.town +on tilde.chat +. +.Sh DESCRIPTION +I make mostly IRC software in C. +I like +.Fx +and +.Ox +but also the GPL. +I want to learn to talk to strangers +and seek out as much magic as I can. +. +.Pp +.Lk https://git.causal.agency code +\(em +.Lk https://text.causal.agency words +.Pq Lk gopher://text.causal.agency gopher +\(em +.Lk /list/ mailist +\(em +.Lk https://liberapay.com/june/ liberapay +. +.Pp +These are some things I've done: +.Bl -tag -width Ds +.It Lk https://git.causal.agency/pounce/about pounce +a multi-client-first IRC bouncer +.It Lk https://git.causal.agency/catgirl/about catgirl +a cosy IRC client +.It Lk https://git.causal.agency/litterbox/about litterbox +a full-text search IRC logger +.It Lk https://git.causal.agency/scooper/about scooper +a web interface for litterbox +.It Lk https://git.causal.agency/catsit/about catsit +a process supervisor +.It Lk https://git.causal.agency/imbox/about "imbox & git-fetch-email" +a tool to pull patches out of IMAP +.It Lk https://git.causal.agency/bubger/about bubger +a mailing list archive generator for IMAP +.It Lk https://git.causal.agency/notemap/about notemap +a tool to mirror text files to IMAP notes +.It Lk https://ascii.town/explore.html torus@ascii.town +a collaborative ASCII art project +.It Lk ssh://play@ascii.town play@ascii.town +a 2048 and snake game over +.Xr ssh 1 +.It Lk https://git.causal.agency/cards/about cards +a +.Pa CARDS.DLL +loader for SDL +.It Lk scheme.png scheme +an earthy terminal colour scheme +.El +. +.Sh SEE ALSO +.Lk /bin/ bin diff --git a/www/causal.agency/style.css b/www/causal.agency/style.css new file mode 100644 index 00000000..368d8da1 --- /dev/null +++ b/www/causal.agency/style.css @@ -0,0 +1,23 @@ +@import url("scheme.css"); + +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; } + +html { font-family: monospace; line-height: 1.25em; } +body { max-width: 80ch; margin: 1em auto; padding: 0 1ch; } +table { border-collapse: collapse; } +table.Nm code.Nm { padding-right: 1ch; } +table.foot { margin-top: 1em; } + +html { background-color: var(--ansi16); color: var(--ansi17); } +a { color: var(--ansi4); } +a:visited { color: var(--ansi5); } +a.permalink { color: var(--ansi3); text-decoration: none; } diff --git a/www/git.causal.agency/.gitignore b/www/git.causal.agency/.gitignore new file mode 100644 index 00000000..a0ae074c --- /dev/null +++ b/www/git.causal.agency/.gitignore @@ -0,0 +1,6 @@ +about-filter +hilex +htagml +mtags +owner-filter +source-filter diff --git a/www/git.causal.agency/Makefile b/www/git.causal.agency/Makefile new file mode 100644 index 00000000..638c21e7 --- /dev/null +++ b/www/git.causal.agency/Makefile @@ -0,0 +1,20 @@ +ETC = /usr/local/etc +WWW = /usr/local/www/cgit +LIBEXEC = /usr/local/libexec + +BIN = ../../bin +BINS = about-filter source-filter owner-filter hilex htagml mtags + +all: ${BINS} + +install: cgitrc custom.css ${BINS} + install -m 644 cgitrc ${ETC} + install -m 644 custom.css ${WWW} + install ${BINS} ${LIBEXEC} + +hilex htagml mtags:: + ${MAKE} -C ${BIN} $@ + ln -f ${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..2ff645e2 --- /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/hilex -l text -f html -o pre + ;; +esac diff --git a/www/git.causal.agency/cgit/.gitignore b/www/git.causal.agency/cgit/.gitignore new file mode 100644 index 00000000..bca98fcf --- /dev/null +++ b/www/git.causal.agency/cgit/.gitignore @@ -0,0 +1,13 @@ +# Files I don't care to see in git-status/commit +/cgit +/git +cgit.conf +CGIT-CFLAGS +VERSION +cgitrc.5 +cgitrc.5.fo +cgitrc.5.html +cgitrc.5.pdf +cgitrc.5.xml +*.o +*.d diff --git a/.mailmap b/www/git.causal.agency/cgit/.mailmap index 03b54796..03b54796 100644 --- a/.mailmap +++ b/www/git.causal.agency/cgit/.mailmap diff --git a/AUTHORS b/www/git.causal.agency/cgit/AUTHORS index 031de338..031de338 100644 --- a/AUTHORS +++ b/www/git.causal.agency/cgit/AUTHORS diff --git a/COPYING b/www/git.causal.agency/cgit/COPYING index d159169d..d159169d 100644 --- a/COPYING +++ b/www/git.causal.agency/cgit/COPYING diff --git a/Makefile b/www/git.causal.agency/cgit/Makefile index d13c5bd1..358a3c8e 100644 --- a/Makefile +++ b/www/git.causal.agency/cgit/Makefile @@ -15,7 +15,7 @@ pdfdir = $(docdir) mandir = $(prefix)/share/man SHA1_HEADER = <openssl/sha.h> GIT_VER = 2.32.0 -GIT_URL = https://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.xz +GIT_URL = https://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.gz INSTALL = install COPYTREE = cp -r MAN5_TXT = $(wildcard *.5.txt) @@ -157,7 +157,7 @@ clean-doc: $(RM) cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo get-git: - curl -L $(GIT_URL) | tar -xJf - && rm -rf git && mv git-$(GIT_VER) git + curl -L $(GIT_URL) | tar -xzf - && rm -rf git && mv git-$(GIT_VER) git tags: $(QUIET_TAGS)find . -name '*.[ch]' | xargs ctags diff --git a/README b/www/git.causal.agency/cgit/README index 7a6b4a40..371cf21f 100644 --- a/README +++ b/www/git.causal.agency/cgit/README @@ -32,18 +32,6 @@ This will install `cgit.cgi` and `cgit.css` into `/var/www/htdocs/cgit`. You can configure this location (and a few other things) by providing a `cgit.conf` file (see the Makefile for details). -If you'd like to compile without Lua support, you may use: - - $ make NO_LUA=1 - -And if you'd like to specify a Lua implementation, you may use: - - $ make LUA_PKGCONFIG=lua5.1 - -If this is not specified, the Lua implementation will be auto-detected, -preferring LuaJIT if many are present. Acceptable values are generally "lua", -"luajit", "lua5.1", and "lua5.2". - Dependencies ------------ @@ -51,7 +39,6 @@ Dependencies * libzip * libcrypto (OpenSSL) * libssl (OpenSSL) -* optional: luajit or lua, most reliably used when pkg-config is available Apache configuration -------------------- diff --git a/cache.c b/www/git.causal.agency/cgit/cache.c index 55199e8f..578b73b0 100644 --- a/cache.c +++ b/www/git.causal.agency/cgit/cache.c @@ -265,6 +265,13 @@ static int process_slot(struct cache_slot *slot) { int err; + /* + * Make sure any buffered data is flushed before we redirect, + * do sendfile(2) or write(2) + */ + if (fflush(stdout)) + return errno; + err = open_slot(slot); if (!err && slot->match) { if (is_expired(slot)) { diff --git a/cache.h b/www/git.causal.agency/cgit/cache.h index 470da4fc..470da4fc 100644 --- a/cache.h +++ b/www/git.causal.agency/cgit/cache.h diff --git a/cgit.c b/www/git.causal.agency/cgit/cgit.c index 08d81a1d..a86970de 100644 --- a/cgit.c +++ b/www/git.causal.agency/cgit/cgit.c @@ -674,7 +674,7 @@ static inline void authenticate_post(void) len = MAX_AUTHENTICATION_POST_BYTES; if ((len = read(STDIN_FILENO, buffer, len)) < 0) die_errno("Could not read POST from stdin"); - if (write(STDOUT_FILENO, buffer, len) < 0) + if (fwrite(buffer, 1, len, stdout) < len) die_errno("Could not write POST to stdout"); cgit_close_filter(ctx.cfg.auth_filter); exit(0); @@ -964,12 +964,6 @@ static void cgit_parse_args(int argc, const char **argv) for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--version")) { printf("CGit %s | https://git.zx2c4.com/cgit/\n\nCompiled in features:\n", CGIT_VERSION); -#ifdef NO_LUA - printf("[-] "); -#else - printf("[+] "); -#endif - printf("Lua scripting\n"); #ifndef HAVE_LINUX_SENDFILE printf("[-] "); #else @@ -1051,7 +1045,6 @@ int cmd_main(int argc, const char **argv) const char *path; int err, ttl; - cgit_init_filters(); atexit(cgit_cleanup_filters); prepare_context(); diff --git a/cgit.css b/www/git.causal.agency/cgit/cgit.css index dfa144d0..f3dbb7a9 100644 --- a/cgit.css +++ b/www/git.causal.agency/cgit/cgit.css @@ -75,7 +75,7 @@ div#cgit table.tabs td { } div#cgit table.tabs td a { - padding: 2px 0.75em; + padding: 2px 0.25em; color: #777; font-size: 110%; } @@ -437,11 +437,6 @@ div#cgit div.commit-subject { padding: 0em; } -div#cgit div.commit-msg { - white-space: pre; - font-family: monospace; -} - div#cgit div.notes-header { font-weight: bold; padding-top: 1.5em; @@ -538,26 +533,20 @@ div#cgit table.diff { width: 100%; } -div#cgit table.diff td { - font-family: monospace; - white-space: pre; -} - -div#cgit table.diff td div.head { +div#cgit table.diff td span.head { font-weight: bold; - margin-top: 1em; color: black; } -div#cgit table.diff td div.hunk { +div#cgit table.diff td span.hunk { color: #009; } -div#cgit table.diff td div.add { +div#cgit table.diff td span.add { color: green; } -div#cgit table.diff td div.del { +div#cgit table.diff td span.del { color: red; } @@ -581,7 +570,6 @@ div#cgit table.list td.reposection { div#cgit a.button { font-size: 80%; - padding: 0em 0.5em; } div#cgit a.primary { @@ -671,7 +659,6 @@ div#cgit div.footer a:hover { div#cgit a.branch-deco { color: #000; - margin: 0px 0.5em; padding: 0px 0.25em; background-color: #88ff88; border: solid 1px #007700; @@ -679,7 +666,6 @@ div#cgit a.branch-deco { div#cgit a.tag-deco { color: #000; - margin: 0px 0.5em; padding: 0px 0.25em; background-color: #ffff88; border: solid 1px #777700; @@ -687,7 +673,6 @@ div#cgit a.tag-deco { div#cgit a.tag-annotated-deco { color: #000; - margin: 0px 0.5em; padding: 0px 0.25em; background-color: #ffcc88; border: solid 1px #777700; @@ -695,7 +680,6 @@ div#cgit a.tag-annotated-deco { div#cgit a.remote-deco { color: #000; - margin: 0px 0.5em; padding: 0px 0.25em; background-color: #ccccff; border: solid 1px #000077; @@ -703,7 +687,6 @@ div#cgit a.remote-deco { div#cgit a.deco { color: #000; - margin: 0px 0.5em; padding: 0px 0.25em; background-color: #ff8888; border: solid 1px #770000; @@ -714,7 +697,6 @@ div#cgit div.commit-subject a.tag-deco, div#cgit div.commit-subject a.tag-annotated-deco, div#cgit div.commit-subject a.remote-deco, div#cgit div.commit-subject a.deco { - margin-left: 1em; font-size: 75%; } diff --git a/cgit.h b/www/git.causal.agency/cgit/cgit.h index 69b5c132..72fcd849 100644 --- a/cgit.h +++ b/www/git.causal.agency/cgit/cgit.h @@ -385,7 +385,6 @@ extern void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char extern void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv); extern struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype); extern void cgit_cleanup_filters(void); -extern void cgit_init_filters(void); extern void cgit_prepare_repo_env(struct cgit_repo * repo); diff --git a/cgit.mk b/www/git.causal.agency/cgit/cgit.mk index 3fcc1ca3..5b9ed5be 100644 --- a/cgit.mk +++ b/www/git.causal.agency/cgit/cgit.mk @@ -27,32 +27,6 @@ ifdef NO_C99_FORMAT CFLAGS += -DNO_C99_FORMAT endif -ifdef NO_LUA - LUA_MESSAGE := linking without specified Lua support - CGIT_CFLAGS += -DNO_LUA -else -ifeq ($(LUA_PKGCONFIG),) - LUA_PKGCONFIG := $(shell for pc in luajit lua lua5.2 lua5.1; do \ - $(PKG_CONFIG) --exists $$pc 2>/dev/null && echo $$pc && break; \ - done) - LUA_MODE := autodetected -else - LUA_MODE := specified -endif -ifneq ($(LUA_PKGCONFIG),) - LUA_MESSAGE := linking with $(LUA_MODE) $(LUA_PKGCONFIG) - LUA_LIBS := $(shell $(PKG_CONFIG) --libs $(LUA_PKGCONFIG) 2>/dev/null) - LUA_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(LUA_PKGCONFIG) 2>/dev/null) - CGIT_LIBS += $(LUA_LIBS) - CGIT_CFLAGS += $(LUA_CFLAGS) -else - LUA_MESSAGE := linking without autodetected Lua support - NO_LUA := YesPlease - CGIT_CFLAGS += -DNO_LUA -endif - -endif - # Add -ldl to linker flags on systems that commonly use GNU libc. ifneq (,$(filter $(uname_S),Linux GNU GNU/kFreeBSD)) CGIT_LIBS += -ldl @@ -130,7 +104,6 @@ $(CGIT_OBJS): %.o: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS $(missing_dep_dirs) $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $< $(CGIT_PREFIX)cgit: $(CGIT_OBJS) GIT-LDFLAGS $(GITLIBS) - @echo 1>&1 " * $(LUA_MESSAGE)" $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) $(CGIT_LIBS) CGIT_SP_OBJS := $(patsubst %.o,%.sp,$(CGIT_OBJS)) diff --git a/cgit.png b/www/git.causal.agency/cgit/cgit.png index 425528ee..425528ee 100644 --- a/cgit.png +++ b/www/git.causal.agency/cgit/cgit.png Binary files differdiff --git a/cgitrc.5.txt b/www/git.causal.agency/cgit/cgitrc.5.txt index 33a6a8c0..8d663952 100644 --- a/cgitrc.5.txt +++ b/www/git.causal.agency/cgit/cgitrc.5.txt @@ -632,37 +632,6 @@ specification with the relevant string; available values are: 'exec:':: The default "one process per filter" mode. -'lua:':: - Executes the script using a built-in Lua interpreter. The script is - loaded once per execution of cgit, and may be called multiple times - during cgit's lifetime, making it a good choice for repeated filters - such as the 'email filter'. It responds to three functions: - - 'filter_open(argument1, argument2, argument3, ...)':: - This is called upon activation of the filter for a particular - set of data. - 'filter_write(buffer)':: - This is called whenever cgit writes data to the webpage. - 'filter_close()':: - This is called when the current filtering operation is - completed. It must return an integer value. Usually 0 - indicates success. - - Additionally, cgit exposes to the Lua the following built-in functions: - - 'html(str)':: - Writes 'str' to the webpage. - 'html_txt(str)':: - HTML escapes and writes 'str' to the webpage. - 'html_attr(str)':: - HTML escapes for an attribute and writes "str' to the webpage. - 'html_url_path(str)':: - URL escapes for a path and writes 'str' to the webpage. - 'html_url_arg(str)':: - URL escapes for an argument and writes 'str' to the webpage. - 'html_include(file)':: - Includes 'file' in webpage. - Parameters are provided to filters as follows. @@ -696,9 +665,6 @@ auth filter:: with a 302 redirect, and write to output one or more "Set-Cookie" HTTP headers, each followed by a newline. - Please see `filters/simple-authentication.lua` for a clear example - script that may be modified. - commit filter:: This filter is given no arguments. The commit message text that is to be filtered is available on standard input and the filtered text is diff --git a/cmd.c b/www/git.causal.agency/cgit/cmd.c index 0eb75b1d..0eb75b1d 100644 --- a/cmd.c +++ b/www/git.causal.agency/cgit/cmd.c diff --git a/cmd.h b/www/git.causal.agency/cgit/cmd.h index 6249b1d8..6249b1d8 100644 --- a/cmd.h +++ b/www/git.causal.agency/cgit/cmd.h diff --git a/configfile.c b/www/git.causal.agency/cgit/configfile.c index e0391091..e0391091 100644 --- a/configfile.c +++ b/www/git.causal.agency/cgit/configfile.c diff --git a/configfile.h b/www/git.causal.agency/cgit/configfile.h index af7ca197..af7ca197 100644 --- a/configfile.h +++ b/www/git.causal.agency/cgit/configfile.h diff --git a/contrib/hooks/post-receive.agefile b/www/git.causal.agency/cgit/contrib/hooks/post-receive.agefile index 2f72ae9c..2f72ae9c 100755 --- a/contrib/hooks/post-receive.agefile +++ b/www/git.causal.agency/cgit/contrib/hooks/post-receive.agefile diff --git a/favicon.ico b/www/git.causal.agency/cgit/favicon.ico index 56ff5938..56ff5938 100644 --- a/favicon.ico +++ b/www/git.causal.agency/cgit/favicon.ico Binary files differdiff --git a/www/git.causal.agency/cgit/filter.c b/www/git.causal.agency/cgit/filter.c new file mode 100644 index 00000000..2b6c838e --- /dev/null +++ b/www/git.causal.agency/cgit/filter.c @@ -0,0 +1,222 @@ +/* filter.c: filter framework functions + * + * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ + +#include "cgit.h" +#include "html.h" + +static inline void reap_filter(struct cgit_filter *filter) +{ + if (filter && filter->cleanup) + filter->cleanup(filter); +} + +void cgit_cleanup_filters(void) +{ + int i; + reap_filter(ctx.cfg.about_filter); + reap_filter(ctx.cfg.commit_filter); + reap_filter(ctx.cfg.source_filter); + reap_filter(ctx.cfg.email_filter); + reap_filter(ctx.cfg.owner_filter); + reap_filter(ctx.cfg.auth_filter); + for (i = 0; i < cgit_repolist.count; ++i) { + reap_filter(cgit_repolist.repos[i].about_filter); + reap_filter(cgit_repolist.repos[i].commit_filter); + reap_filter(cgit_repolist.repos[i].source_filter); + reap_filter(cgit_repolist.repos[i].email_filter); + reap_filter(cgit_repolist.repos[i].owner_filter); + } +} + +static int open_exec_filter(struct cgit_filter *base, va_list ap) +{ + struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base; + int pipe_fh[2]; + int i; + + for (i = 0; i < filter->base.argument_count; i++) + filter->argv[i + 1] = va_arg(ap, char *); + + chk_zero(fflush(stdout), "unable to flush STDOUT"); + filter->old_stdout = chk_positive(dup(STDOUT_FILENO), + "Unable to duplicate STDOUT"); + chk_zero(pipe(pipe_fh), "Unable to create pipe to subprocess"); + filter->pid = chk_non_negative(fork(), "Unable to create subprocess"); + if (filter->pid == 0) { + close(pipe_fh[1]); + chk_non_negative(dup2(pipe_fh[0], STDIN_FILENO), + "Unable to use pipe as STDIN"); + execvp(filter->cmd, filter->argv); + die_errno("Unable to exec subprocess %s", filter->cmd); + } + close(pipe_fh[0]); + chk_non_negative(dup2(pipe_fh[1], STDOUT_FILENO), + "Unable to use pipe as STDOUT"); + close(pipe_fh[1]); + return 0; +} + +static int close_exec_filter(struct cgit_filter *base) +{ + struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base; + int i, exit_status = 0; + + chk_zero(fflush(stdout), "unable to flush STDOUT"); + chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO), + "Unable to restore STDOUT"); + close(filter->old_stdout); + if (filter->pid < 0) + goto done; + waitpid(filter->pid, &exit_status, 0); + if (WIFEXITED(exit_status)) + goto done; + die("Subprocess %s exited abnormally", filter->cmd); + +done: + for (i = 0; i < filter->base.argument_count; i++) + filter->argv[i + 1] = NULL; + return WEXITSTATUS(exit_status); + +} + +static void fprintf_exec_filter(struct cgit_filter *base, FILE *f, const char *prefix) +{ + struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base; + fprintf(f, "%sexec:%s\n", prefix, filter->cmd); +} + +static void cleanup_exec_filter(struct cgit_filter *base) +{ + struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base; + if (filter->argv) { + free(filter->argv); + filter->argv = NULL; + } + if (filter->cmd) { + free(filter->cmd); + filter->cmd = NULL; + } +} + +static struct cgit_filter *new_exec_filter(const char *cmd, int argument_count) +{ + struct cgit_exec_filter *f; + int args_size = 0; + + f = xmalloc(sizeof(*f)); + /* We leave argv for now and assign it below. */ + cgit_exec_filter_init(f, xstrdup(cmd), NULL); + f->base.argument_count = argument_count; + args_size = (2 + argument_count) * sizeof(char *); + f->argv = xmalloc(args_size); + memset(f->argv, 0, args_size); + f->argv[0] = f->cmd; + return &f->base; +} + +void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv) +{ + memset(filter, 0, sizeof(*filter)); + filter->base.open = open_exec_filter; + filter->base.close = close_exec_filter; + filter->base.fprintf = fprintf_exec_filter; + filter->base.cleanup = cleanup_exec_filter; + filter->cmd = cmd; + filter->argv = argv; + /* The argument count for open_filter is zero by default, unless called from new_filter, above. */ + filter->base.argument_count = 0; +} + +int cgit_open_filter(struct cgit_filter *filter, ...) +{ + int result; + va_list ap; + if (!filter) + return 0; + va_start(ap, filter); + result = filter->open(filter, ap); + va_end(ap); + return result; +} + +int cgit_close_filter(struct cgit_filter *filter) +{ + if (!filter) + return 0; + return filter->close(filter); +} + +void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix) +{ + filter->fprintf(filter, f, prefix); +} + + + +static const struct { + const char *prefix; + struct cgit_filter *(*ctor)(const char *cmd, int argument_count); +} filter_specs[] = { + { "exec", new_exec_filter }, +}; + +struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype) +{ + char *colon; + int i; + size_t len; + int argument_count; + + if (!cmd || !cmd[0]) + return NULL; + + colon = strchr(cmd, ':'); + len = colon - cmd; + /* + * In case we're running on Windows, don't allow a single letter before + * the colon. + */ + if (len == 1) + colon = NULL; + + switch (filtertype) { + case AUTH: + argument_count = 12; + break; + + case EMAIL: + argument_count = 2; + break; + + case OWNER: + argument_count = 0; + break; + + case SOURCE: + case ABOUT: + argument_count = 1; + break; + + case COMMIT: + default: + argument_count = 0; + break; + } + + /* If no prefix is given, exec filter is the default. */ + if (!colon) + return new_exec_filter(cmd, argument_count); + + for (i = 0; i < ARRAY_SIZE(filter_specs); i++) { + if (len == strlen(filter_specs[i].prefix) && + !strncmp(filter_specs[i].prefix, cmd, len)) + return filter_specs[i].ctor(colon + 1, argument_count); + } + + die("Invalid filter type: %.*s", (int) len, cmd); +} diff --git a/filters/about-formatting.sh b/www/git.causal.agency/cgit/filters/about-formatting.sh index 85daf9c2..85daf9c2 100755 --- a/filters/about-formatting.sh +++ b/www/git.causal.agency/cgit/filters/about-formatting.sh diff --git a/filters/commit-links.sh b/www/git.causal.agency/cgit/filters/commit-links.sh index 796ac308..796ac308 100755 --- a/filters/commit-links.sh +++ b/www/git.causal.agency/cgit/filters/commit-links.sh diff --git a/filters/email-gravatar.py b/www/git.causal.agency/cgit/filters/email-gravatar.py index d70440ea..012113c5 100755 --- a/filters/email-gravatar.py +++ b/www/git.causal.agency/cgit/filters/email-gravatar.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# Please prefer the email-gravatar.lua using lua: as a prefix over this script. This -# script is very slow, in comparison. -# # This script may be used with the email-filter or repo.email-filter settings in cgitrc. # # The following environment variables can be used to retrieve the configuration diff --git a/filters/html-converters/man2html b/www/git.causal.agency/cgit/filters/html-converters/man2html index 0ef78841..0ef78841 100755 --- a/filters/html-converters/man2html +++ b/www/git.causal.agency/cgit/filters/html-converters/man2html diff --git a/filters/html-converters/md2html b/www/git.causal.agency/cgit/filters/html-converters/md2html index 59f43a84..59f43a84 100755 --- a/filters/html-converters/md2html +++ b/www/git.causal.agency/cgit/filters/html-converters/md2html diff --git a/filters/html-converters/rst2html b/www/git.causal.agency/cgit/filters/html-converters/rst2html index 02d90f81..02d90f81 100755 --- a/filters/html-converters/rst2html +++ b/www/git.causal.agency/cgit/filters/html-converters/rst2html diff --git a/filters/html-converters/txt2html b/www/git.causal.agency/cgit/filters/html-converters/txt2html index 495eeceb..495eeceb 100755 --- a/filters/html-converters/txt2html +++ b/www/git.causal.agency/cgit/filters/html-converters/txt2html diff --git a/filters/syntax-highlighting.py b/www/git.causal.agency/cgit/filters/syntax-highlighting.py index e912594c..e912594c 100755 --- a/filters/syntax-highlighting.py +++ b/www/git.causal.agency/cgit/filters/syntax-highlighting.py diff --git a/filters/syntax-highlighting.sh b/www/git.causal.agency/cgit/filters/syntax-highlighting.sh index 840bc34f..840bc34f 100755 --- a/filters/syntax-highlighting.sh +++ b/www/git.causal.agency/cgit/filters/syntax-highlighting.sh diff --git a/gen-version.sh b/www/git.causal.agency/cgit/gen-version.sh index 80cf49af..80cf49af 100755 --- a/gen-version.sh +++ b/www/git.causal.agency/cgit/gen-version.sh diff --git a/html.c b/www/git.causal.agency/cgit/html.c index 7f81965f..cefcf5e7 100644 --- a/html.c +++ b/www/git.causal.agency/cgit/html.c @@ -80,7 +80,7 @@ char *fmtalloc(const char *format, ...) void html_raw(const char *data, size_t size) { - if (write(STDOUT_FILENO, data, size) != size) + if (fwrite(data, 1, size, stdout) != size) die_errno("write error on html output"); } diff --git a/html.h b/www/git.causal.agency/cgit/html.h index fa4de775..fa4de775 100644 --- a/html.h +++ b/www/git.causal.agency/cgit/html.h diff --git a/parsing.c b/www/git.causal.agency/cgit/parsing.c index 72b59b3c..72b59b3c 100644 --- a/parsing.c +++ b/www/git.causal.agency/cgit/parsing.c diff --git a/robots.txt b/www/git.causal.agency/cgit/robots.txt index 4ce948fe..1b33266d 100644 --- a/robots.txt +++ b/www/git.causal.agency/cgit/robots.txt @@ -1,3 +1,4 @@ User-agent: * Disallow: /*/snapshot/* +Disallow: /*/blame/* Allow: / diff --git a/scan-tree.c b/www/git.causal.agency/cgit/scan-tree.c index 6a2f65a8..6a2f65a8 100644 --- a/scan-tree.c +++ b/www/git.causal.agency/cgit/scan-tree.c diff --git a/scan-tree.h b/www/git.causal.agency/cgit/scan-tree.h index 1afbd4bb..1afbd4bb 100644 --- a/scan-tree.h +++ b/www/git.causal.agency/cgit/scan-tree.h diff --git a/shared.c b/www/git.causal.agency/cgit/shared.c index 8115469a..8115469a 100644 --- a/shared.c +++ b/www/git.causal.agency/cgit/shared.c diff --git a/tests/.gitignore b/www/git.causal.agency/cgit/tests/.gitignore index 3fd2e965..3fd2e965 100644 --- a/tests/.gitignore +++ b/www/git.causal.agency/cgit/tests/.gitignore diff --git a/tests/Makefile b/www/git.causal.agency/cgit/tests/Makefile index 65e11173..65e11173 100644 --- a/tests/Makefile +++ b/www/git.causal.agency/cgit/tests/Makefile diff --git a/tests/filters/dump.sh b/www/git.causal.agency/cgit/tests/filters/dump.sh index da6f7a1b..da6f7a1b 100755 --- a/tests/filters/dump.sh +++ b/www/git.causal.agency/cgit/tests/filters/dump.sh diff --git a/tests/setup.sh b/www/git.causal.agency/cgit/tests/setup.sh index 8db810ff..31e7d5bb 100755 --- a/tests/setup.sh +++ b/www/git.causal.agency/cgit/tests/setup.sh @@ -60,12 +60,6 @@ fi FILTER_DIRECTORY=$(cd ../filters && pwd) -if cgit --version | grep -F -q "[+] Lua scripting"; then - export CGIT_HAS_LUA=1 -else - export CGIT_HAS_LUA=0 -fi - mkrepo() { name=$1 count=$2 @@ -144,19 +138,6 @@ repo.email-filter=exec:$FILTER_DIRECTORY/dump.sh repo.source-filter=exec:$FILTER_DIRECTORY/dump.sh repo.readme=master:a+b EOF - - if [ $CGIT_HAS_LUA -eq 1 ]; then - cat >>cgitrc <<EOF -repo.url=filter-lua -repo.path=$PWD/repos/filter/.git -repo.desc=filtered repo -repo.about-filter=lua:$FILTER_DIRECTORY/dump.lua -repo.commit-filter=lua:$FILTER_DIRECTORY/dump.lua -repo.email-filter=lua:$FILTER_DIRECTORY/dump.lua -repo.source-filter=lua:$FILTER_DIRECTORY/dump.lua -repo.readme=master:a+b -EOF - fi } cgit_query() diff --git a/tests/t0001-validate-git-versions.sh b/www/git.causal.agency/cgit/tests/t0001-validate-git-versions.sh index dd84fe3f..dd84fe3f 100755 --- a/tests/t0001-validate-git-versions.sh +++ b/www/git.causal.agency/cgit/tests/t0001-validate-git-versions.sh diff --git a/tests/t0010-validate-html.sh b/www/git.causal.agency/cgit/tests/t0010-validate-html.sh index ca08d69d..ca08d69d 100755 --- a/tests/t0010-validate-html.sh +++ b/www/git.causal.agency/cgit/tests/t0010-validate-html.sh diff --git a/tests/t0020-validate-cache.sh b/www/git.causal.agency/cgit/tests/t0020-validate-cache.sh index 657765d8..657765d8 100755 --- a/tests/t0020-validate-cache.sh +++ b/www/git.causal.agency/cgit/tests/t0020-validate-cache.sh diff --git a/tests/t0101-index.sh b/www/git.causal.agency/cgit/tests/t0101-index.sh index 82ef9b04..82ef9b04 100755 --- a/tests/t0101-index.sh +++ b/www/git.causal.agency/cgit/tests/t0101-index.sh diff --git a/tests/t0102-summary.sh b/www/git.causal.agency/cgit/tests/t0102-summary.sh index b8864cb1..b8864cb1 100755 --- a/tests/t0102-summary.sh +++ b/www/git.causal.agency/cgit/tests/t0102-summary.sh diff --git a/tests/t0103-log.sh b/www/git.causal.agency/cgit/tests/t0103-log.sh index bdf1435a..bdf1435a 100755 --- a/tests/t0103-log.sh +++ b/www/git.causal.agency/cgit/tests/t0103-log.sh diff --git a/tests/t0104-tree.sh b/www/git.causal.agency/cgit/tests/t0104-tree.sh index 2e140f59..2e140f59 100755 --- a/tests/t0104-tree.sh +++ b/www/git.causal.agency/cgit/tests/t0104-tree.sh diff --git a/tests/t0105-commit.sh b/www/git.causal.agency/cgit/tests/t0105-commit.sh index 1a12ee39..cfed1e7d 100755 --- a/tests/t0105-commit.sh +++ b/www/git.causal.agency/cgit/tests/t0105-commit.sh @@ -11,7 +11,7 @@ test_expect_success 'find commit subject' ' grep "<div class=.commit-subject.>commit 5<" tmp ' -test_expect_success 'find commit msg' 'grep "<div class=.commit-msg.></div>" tmp' +test_expect_success 'find commit msg' 'grep "<pre class=.commit-msg.></pre>" tmp' test_expect_success 'find diffstat' 'grep "<table summary=.diffstat. class=.diffstat.>" tmp' test_expect_success 'find diff summary' ' @@ -29,8 +29,8 @@ test_expect_success 'root commit contains diffstat' ' ' test_expect_success 'root commit contains diff' ' - grep ">diff --git a/file-1 b/file-1<" tmp && - grep "<div class=.add.>+1</div>" tmp + grep ">diff --git a/file-1 b/file-1" tmp && + grep "<span class=.add.>+1</span>" tmp ' test_done diff --git a/tests/t0106-diff.sh b/www/git.causal.agency/cgit/tests/t0106-diff.sh index 82b645ec..62a0a74a 100755 --- a/tests/t0106-diff.sh +++ b/www/git.causal.agency/cgit/tests/t0106-diff.sh @@ -9,11 +9,11 @@ test_expect_success 'find blob link' 'grep "<a href=./foo/tree/file-5?id=" tmp' test_expect_success 'find added file' 'grep "new file mode 100644" tmp' test_expect_success 'find hunk header' ' - grep "<div class=.hunk.>@@ -0,0 +1 @@</div>" tmp + grep "<span class=.hunk.>@@ -0,0 +1 @@</span>" tmp ' test_expect_success 'find added line' ' - grep "<div class=.add.>+5</div>" tmp + grep "<span class=.add.>+5</span>" tmp ' test_done diff --git a/tests/t0107-snapshot.sh b/www/git.causal.agency/cgit/tests/t0107-snapshot.sh index 0811ec40..0811ec40 100755 --- a/tests/t0107-snapshot.sh +++ b/www/git.causal.agency/cgit/tests/t0107-snapshot.sh diff --git a/tests/t0108-patch.sh b/www/git.causal.agency/cgit/tests/t0108-patch.sh index 013d6802..013d6802 100755 --- a/tests/t0108-patch.sh +++ b/www/git.causal.agency/cgit/tests/t0108-patch.sh diff --git a/tests/t0109-gitconfig.sh b/www/git.causal.agency/cgit/tests/t0109-gitconfig.sh index 189ef281..189ef281 100755 --- a/tests/t0109-gitconfig.sh +++ b/www/git.causal.agency/cgit/tests/t0109-gitconfig.sh diff --git a/tests/t0110-rawdiff.sh b/www/git.causal.agency/cgit/tests/t0110-rawdiff.sh index 66fa7d5d..66fa7d5d 100755 --- a/tests/t0110-rawdiff.sh +++ b/www/git.causal.agency/cgit/tests/t0110-rawdiff.sh diff --git a/tests/t0111-filter.sh b/www/git.causal.agency/cgit/tests/t0111-filter.sh index 2fdc3669..e5d35750 100755 --- a/tests/t0111-filter.sh +++ b/www/git.causal.agency/cgit/tests/t0111-filter.sh @@ -4,9 +4,6 @@ test_description='Check filtered content' . ./setup.sh prefixes="exec" -if [ $CGIT_HAS_LUA -eq 1 ]; then - prefixes="$prefixes lua" -fi for prefix in $prefixes do diff --git a/tests/valgrind/bin/cgit b/www/git.causal.agency/cgit/tests/valgrind/bin/cgit index dcdfbe53..dcdfbe53 100755 --- a/tests/valgrind/bin/cgit +++ b/www/git.causal.agency/cgit/tests/valgrind/bin/cgit diff --git a/ui-atom.c b/www/git.causal.agency/cgit/ui-atom.c index 1056f363..1056f363 100644 --- a/ui-atom.c +++ b/www/git.causal.agency/cgit/ui-atom.c diff --git a/ui-atom.h b/www/git.causal.agency/cgit/ui-atom.h index dda953bb..dda953bb 100644 --- a/ui-atom.h +++ b/www/git.causal.agency/cgit/ui-atom.h diff --git a/ui-blame.c b/www/git.causal.agency/cgit/ui-blame.c index 03136f78..4adec2b9 100644 --- a/ui-blame.c +++ b/www/git.causal.agency/cgit/ui-blame.c @@ -152,6 +152,10 @@ static void print_object(const struct object_id *oid, const char *path, cgit_tree_link("tree", NULL, NULL, ctx.qry.head, rev, path); html(")\n"); + if (buffer_is_binary(buf, size)) { + html("<div class='error'>blob is binary.</div>"); + goto cleanup; + } if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) { htmlf("<div class='error'>blob size (%ldKB)" " exceeds display size limit (%dKB).</div>", diff --git a/ui-blame.h b/www/git.causal.agency/cgit/ui-blame.h index 5b97e035..5b97e035 100644 --- a/ui-blame.h +++ b/www/git.causal.agency/cgit/ui-blame.h diff --git a/ui-blob.c b/www/git.causal.agency/cgit/ui-blob.c index c10ae42e..c10ae42e 100644 --- a/ui-blob.c +++ b/www/git.causal.agency/cgit/ui-blob.c diff --git a/ui-blob.h b/www/git.causal.agency/cgit/ui-blob.h index 16847b20..16847b20 100644 --- a/ui-blob.h +++ b/www/git.causal.agency/cgit/ui-blob.h diff --git a/ui-clone.c b/www/git.causal.agency/cgit/ui-clone.c index 5dccb639..5dccb639 100644 --- a/ui-clone.c +++ b/www/git.causal.agency/cgit/ui-clone.c diff --git a/ui-clone.h b/www/git.causal.agency/cgit/ui-clone.h index 3e460a3d..3e460a3d 100644 --- a/ui-clone.h +++ b/www/git.causal.agency/cgit/ui-clone.h diff --git a/ui-commit.c b/www/git.causal.agency/cgit/ui-commit.c index 948118c4..b49259e6 100644 --- a/ui-commit.c +++ b/www/git.causal.agency/cgit/ui-commit.c @@ -39,10 +39,11 @@ void cgit_print_commit(char *hex, const char *prefix) } info = cgit_parse_commit(commit); - format_display_notes(&oid, ¬es, PAGE_ENCODING, 0); + format_display_notes(&oid, ¬es, PAGE_ENCODING, 1); load_ref_decorations(NULL, DECORATE_FULL_REFS); + ctx.page.title = fmtalloc("%s - %s", info->subject, ctx.page.title); cgit_print_layout_start(); cgit_print_diff_ctrls(); html("<table summary='commit info' class='commit-info'>\n"); @@ -120,11 +121,11 @@ void cgit_print_commit(char *hex, const char *prefix) cgit_close_filter(ctx.repo->commit_filter); show_commit_decorations(commit); html("</div>"); - html("<div class='commit-msg'>"); + html("<pre class='commit-msg'>"); cgit_open_filter(ctx.repo->commit_filter); html_txt(info->msg); cgit_close_filter(ctx.repo->commit_filter); - html("</div>"); + html("</pre>"); if (notes.len != 0) { html("<div class='notes-header'>Notes</div>"); html("<div class='notes'>"); diff --git a/ui-commit.h b/www/git.causal.agency/cgit/ui-commit.h index 8198b4ba..8198b4ba 100644 --- a/ui-commit.h +++ b/www/git.causal.agency/cgit/ui-commit.h diff --git a/ui-diff.c b/www/git.causal.agency/cgit/ui-diff.c index 5ed5990c..2a64ae8f 100644 --- a/ui-diff.c +++ b/www/git.causal.agency/cgit/ui-diff.c @@ -231,11 +231,11 @@ static void print_line(char *line, int len) else if (line[0] == '@') class = "hunk"; - htmlf("<div class='%s'>", class); + htmlf("<span class='%s'>", class); line[len-1] = '\0'; html_txt(line); - html("</div>"); line[len-1] = c; + html("</span>\n"); } static void header(const struct object_id *oid1, char *path1, int mode1, @@ -245,22 +245,23 @@ static void header(const struct object_id *oid1, char *path1, int mode1, int subproject; subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2)); - html("<div class='head'>"); + html("<span class='head'>"); html("diff --git a/"); html_txt(path1); html(" b/"); html_txt(path2); + html("\n"); if (mode1 == 0) - htmlf("<br/>new file mode %.6o", mode2); + htmlf("new file mode %.6o\n", mode2); if (mode2 == 0) - htmlf("<br/>deleted file mode %.6o", mode1); + htmlf("deleted file mode %.6o\n", mode1); if (!subproject) { abbrev1 = xstrdup(find_unique_abbrev(oid1, DEFAULT_ABBREV)); abbrev2 = xstrdup(find_unique_abbrev(oid2, DEFAULT_ABBREV)); - htmlf("<br/>index %s..%s", abbrev1, abbrev2); + htmlf("index %s..%s", abbrev1, abbrev2); free(abbrev1); free(abbrev2); if (mode1 != 0 && mode2 != 0) { @@ -268,28 +269,31 @@ static void header(const struct object_id *oid1, char *path1, int mode1, if (mode2 != mode1) htmlf("..%.6o", mode2); } + html("\n"); if (is_null_oid(oid1)) { path1 = "dev/null"; - html("<br/>--- /"); + html("--- /"); } else - html("<br/>--- a/"); + html("--- a/"); if (mode1 != 0) cgit_tree_link(path1, NULL, NULL, ctx.qry.head, oid_to_hex(old_rev_oid), path1); else html_txt(path1); + html("\n"); if (is_null_oid(oid2)) { path2 = "dev/null"; - html("<br/>+++ /"); + html("+++ /"); } else - html("<br/>+++ b/"); + html("+++ b/"); if (mode2 != 0) cgit_tree_link(path2, NULL, NULL, ctx.qry.head, oid_to_hex(new_rev_oid), path2); else html_txt(path2); + html("\n"); } - html("</div>"); + html("</span>"); } static void filepair_cb(struct diff_filepair *pair) @@ -488,12 +492,12 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, html("<table summary='ssdiff' class='ssdiff'>"); } else { html("<table summary='diff' class='diff'>"); - html("<tr><td>"); + html("<tr><td><pre>"); } cgit_diff_tree(old_rev_oid, new_rev_oid, filepair_cb, prefix, ctx.qry.ignorews); if (!use_ssdiff) - html("</td></tr>"); + html("</pre></td></tr>"); html("</table>"); if (show_ctrls) diff --git a/ui-diff.h b/www/git.causal.agency/cgit/ui-diff.h index 39264a16..39264a16 100644 --- a/ui-diff.h +++ b/www/git.causal.agency/cgit/ui-diff.h diff --git a/ui-log.c b/www/git.causal.agency/cgit/ui-log.c index 20774bf8..b443ca73 100644 --- a/ui-log.c +++ b/www/git.causal.agency/cgit/ui-log.c @@ -75,11 +75,13 @@ void show_commit_decorations(struct commit *commit) * don't display anything. */ break; case DECORATION_REF_LOCAL: + html(" "); cgit_log_link(buf, NULL, "branch-deco", buf, NULL, ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg, 0); break; case DECORATION_REF_TAG: + html(" "); if (!read_ref(deco->name, &oid_tag) && !peel_iterated_oid(&oid_tag, &peeled)) is_annotated = !oideq(&oid_tag, &peeled); cgit_tag_link(buf, NULL, is_annotated ? "tag-annotated-deco" : "tag-deco", buf); @@ -87,12 +89,14 @@ void show_commit_decorations(struct commit *commit) case DECORATION_REF_REMOTE: if (!ctx.repo->enable_remote_branches) break; + html(" "); cgit_log_link(buf, NULL, "remote-deco", NULL, oid_to_hex(&commit->object.oid), ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg, 0); break; default: + html(" "); cgit_commit_link(buf, NULL, "deco", ctx.qry.head, oid_to_hex(&commit->object.oid), ctx.qry.vpath); diff --git a/ui-log.h b/www/git.causal.agency/cgit/ui-log.h index 325607cd..325607cd 100644 --- a/ui-log.h +++ b/www/git.causal.agency/cgit/ui-log.h diff --git a/ui-patch.c b/www/git.causal.agency/cgit/ui-patch.c index 4ac03cbe..4ac03cbe 100644 --- a/ui-patch.c +++ b/www/git.causal.agency/cgit/ui-patch.c diff --git a/ui-patch.h b/www/git.causal.agency/cgit/ui-patch.h index 7a6cacd5..7a6cacd5 100644 --- a/ui-patch.h +++ b/www/git.causal.agency/cgit/ui-patch.h diff --git a/ui-plain.c b/www/git.causal.agency/cgit/ui-plain.c index 65a205fa..65a205fa 100644 --- a/ui-plain.c +++ b/www/git.causal.agency/cgit/ui-plain.c diff --git a/ui-plain.h b/www/git.causal.agency/cgit/ui-plain.h index 5bff07b8..5bff07b8 100644 --- a/ui-plain.h +++ b/www/git.causal.agency/cgit/ui-plain.h diff --git a/ui-refs.c b/www/git.causal.agency/cgit/ui-refs.c index 456f610d..456f610d 100644 --- a/ui-refs.c +++ b/www/git.causal.agency/cgit/ui-refs.c diff --git a/ui-refs.h b/www/git.causal.agency/cgit/ui-refs.h index 1d4a54a2..1d4a54a2 100644 --- a/ui-refs.h +++ b/www/git.causal.agency/cgit/ui-refs.h diff --git a/ui-repolist.c b/www/git.causal.agency/cgit/ui-repolist.c index 529a2038..97b11c5f 100644 --- a/ui-repolist.c +++ b/www/git.causal.agency/cgit/ui-repolist.c @@ -321,7 +321,7 @@ void cgit_print_repolist(void) } htmlf("<tr><td class='%s'>", !sorted && section ? "sublevel-repo" : "toplevel-repo"); - cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); + cgit_summary_link(ctx.repo->name, NULL, NULL, NULL); html("</td><td>"); repourl = cgit_repourl(ctx.repo->url); html_link_open(repourl, NULL, NULL); @@ -353,8 +353,10 @@ void cgit_print_repolist(void) if (ctx.cfg.enable_index_links) { html("<td>"); cgit_summary_link("summary", NULL, "button", NULL); + html(" "); cgit_log_link("log", NULL, "button", NULL, NULL, NULL, 0, NULL, NULL, ctx.qry.showmsg, 0); + html(" "); cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); html("</td>"); } diff --git a/ui-repolist.h b/www/git.causal.agency/cgit/ui-repolist.h index 1b6b3227..1b6b3227 100644 --- a/ui-repolist.h +++ b/www/git.causal.agency/cgit/ui-repolist.h diff --git a/ui-shared.c b/www/git.causal.agency/cgit/ui-shared.c index acd8ab55..dfaf5952 100644 --- a/ui-shared.c +++ b/www/git.causal.agency/cgit/ui-shared.c @@ -835,7 +835,7 @@ void cgit_print_docend(void) if (ctx.cfg.footer) html_include(ctx.cfg.footer); else { - htmlf("<div class='footer'>generated by <a href='https://git.zx2c4.com/cgit/about/'>cgit %s</a> " + htmlf("<div class='footer'>generated by <a href='https://git.causal.agency/src/log/www/git.causal.agency/cgit'>cgit %s</a> " "(<a href='https://git-scm.com/'>git %s</a>) at ", cgit_version, git_version_string); html_txt(show_date(time(NULL), 0, cgit_date_mode(DATE_ISO8601))); html("</div>\n"); @@ -894,6 +894,15 @@ void cgit_add_clone_urls(void (*fn)(const char *)) add_clone_urls(fn, ctx.cfg.clone_prefix, ctx.repo->url); } +static int print_this_commit_option(void) +{ + struct object_id oid; + if (!ctx.qry.head || get_oid(ctx.qry.head, &oid)) + return 1; + html_option(oid_to_hex(&oid), "this commit", ctx.qry.head); + return 0; +} + static int print_branch_option(const char *refname, const struct object_id *oid, int flags, void *cb_data) { @@ -995,15 +1004,18 @@ static void print_header(void) if (ctx.repo) { cgit_index_link("index", NULL, NULL, NULL, NULL, 0, 1); html(" : "); - cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); + cgit_summary_link(ctx.repo->name, NULL, NULL, NULL); if (ctx.env.authenticated) { html("</td><td class='form'>"); html("<form method='get'>\n"); cgit_add_hidden_formfields(0, 1, ctx.qry.page); html("<select name='h' onchange='this.form.submit();'>\n"); + print_this_commit_option(); + html("<optgroup label='branches'>"); for_each_branch_ref(print_branch_option, ctx.qry.head); if (ctx.repo->enable_remote_branches) for_each_remote_ref(print_branch_option, ctx.qry.head); + html("</optgroup>"); html("</select> "); html("<input type='submit' value='switch'/>"); html("</form>"); @@ -1016,7 +1028,13 @@ static void print_header(void) if (ctx.repo) { html_txt(ctx.repo->desc); html("</td><td class='sub right'>"); - html_txt(ctx.repo->owner); + if (ctx.repo->owner_filter) { + cgit_open_filter(ctx.repo->owner_filter); + html_txt(ctx.repo->owner); + cgit_close_filter(ctx.repo->owner_filter); + } else { + html_txt(ctx.repo->owner); + } } else { if (ctx.cfg.root_desc) html_txt(ctx.cfg.root_desc); @@ -1032,32 +1050,41 @@ void cgit_print_pageheader(void) html("<table class='tabs'><tr><td>\n"); if (ctx.env.authenticated && ctx.repo) { - if (ctx.repo->readme.nr) + if (ctx.repo->readme.nr) { reporevlink("about", "about", NULL, hc("about"), ctx.qry.head, NULL, NULL); + html(" "); + } cgit_summary_link("summary", NULL, hc("summary"), ctx.qry.head); + html(" "); cgit_refs_link("refs", NULL, hc("refs"), ctx.qry.head, ctx.qry.oid, NULL); + html(" "); cgit_log_link("log", NULL, hc("log"), ctx.qry.head, NULL, ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg, ctx.qry.follow); + html(" "); if (ctx.qry.page && !strcmp(ctx.qry.page, "blame")) cgit_blame_link("blame", NULL, hc("blame"), ctx.qry.head, ctx.qry.oid, ctx.qry.vpath); else cgit_tree_link("tree", NULL, hc("tree"), ctx.qry.head, ctx.qry.oid, ctx.qry.vpath); + html(" "); cgit_commit_link("commit", NULL, hc("commit"), ctx.qry.head, ctx.qry.oid, ctx.qry.vpath); + html(" "); cgit_diff_link("diff", NULL, hc("diff"), ctx.qry.head, ctx.qry.oid, ctx.qry.oid2, ctx.qry.vpath); - if (ctx.repo->max_stats) + if (ctx.repo->max_stats) { + html(" "); cgit_stats_link("stats", NULL, hc("stats"), ctx.qry.head, ctx.qry.vpath); + } if (ctx.repo->homepage) { - html("<a href='"); + html(" <a href='"); html_attr(ctx.repo->homepage); html("'>homepage</a>"); } @@ -1201,9 +1228,12 @@ void cgit_set_title_from_path(const char *path) if (!path) return; - for (last_slash = path + strlen(path); (slash = memrchr(path, '/', last_slash - path)) != NULL; last_slash = slash) { + last_slash = path + strlen(path); + for (slash = last_slash; slash > path; --slash) { + if (*slash != '/') continue; strbuf_add(&sb, slash + 1, last_slash - slash - 1); strbuf_addstr(&sb, " \xc2\xab "); + last_slash = slash; } strbuf_add(&sb, path, last_slash - path); strbuf_addf(&sb, " - %s", ctx.page.title); diff --git a/ui-shared.h b/www/git.causal.agency/cgit/ui-shared.h index 6964873a..6964873a 100644 --- a/ui-shared.h +++ b/www/git.causal.agency/cgit/ui-shared.h diff --git a/ui-snapshot.c b/www/git.causal.agency/cgit/ui-snapshot.c index 18361a65..28013935 100644 --- a/ui-snapshot.c +++ b/www/git.causal.agency/cgit/ui-snapshot.c @@ -37,6 +37,9 @@ static int write_archive_type(const char *format, const char *hex, const char *p /* strvec guarantees a trailing NULL entry. */ memcpy(nargv, argv.v, sizeof(char *) * (argv.nr + 1)); + if (fflush(stdout)) + return errno; + result = write_archive(argv.nr, nargv, NULL, the_repository, NULL, 0); strvec_clear(&argv); free(nargv); diff --git a/ui-snapshot.h b/www/git.causal.agency/cgit/ui-snapshot.h index a8deec36..a8deec36 100644 --- a/ui-snapshot.h +++ b/www/git.causal.agency/cgit/ui-snapshot.h diff --git a/ui-ssdiff.c b/www/git.causal.agency/cgit/ui-ssdiff.c index af8bc9e0..af8bc9e0 100644 --- a/ui-ssdiff.c +++ b/www/git.causal.agency/cgit/ui-ssdiff.c diff --git a/ui-ssdiff.h b/www/git.causal.agency/cgit/ui-ssdiff.h index 11f27144..11f27144 100644 --- a/ui-ssdiff.h +++ b/www/git.causal.agency/cgit/ui-ssdiff.h diff --git a/ui-stats.c b/www/git.causal.agency/cgit/ui-stats.c index 09b3625e..09b3625e 100644 --- a/ui-stats.c +++ b/www/git.causal.agency/cgit/ui-stats.c diff --git a/ui-stats.h b/www/git.causal.agency/cgit/ui-stats.h index 0e61b03d..0e61b03d 100644 --- a/ui-stats.h +++ b/www/git.causal.agency/cgit/ui-stats.h diff --git a/ui-summary.c b/www/git.causal.agency/cgit/ui-summary.c index 947812a8..947812a8 100644 --- a/ui-summary.c +++ b/www/git.causal.agency/cgit/ui-summary.c diff --git a/ui-summary.h b/www/git.causal.agency/cgit/ui-summary.h index cba696af..cba696af 100644 --- a/ui-summary.h +++ b/www/git.causal.agency/cgit/ui-summary.h diff --git a/ui-tag.c b/www/git.causal.agency/cgit/ui-tag.c index 424bbccd..05952429 100644 --- a/ui-tag.c +++ b/www/git.causal.agency/cgit/ui-tag.c @@ -25,9 +25,9 @@ static void print_tag_content(char *buf) html_txt(buf); html("</div>"); if (p) { - html("<div class='commit-msg'>"); + html("<pre class='commit-msg'>"); html_txt(++p); - html("</div>"); + html("</pre>"); } } diff --git a/ui-tag.h b/www/git.causal.agency/cgit/ui-tag.h index d295cdcd..d295cdcd 100644 --- a/ui-tag.h +++ b/www/git.causal.agency/cgit/ui-tag.h diff --git a/ui-tree.c b/www/git.causal.agency/cgit/ui-tree.c index b61f6f54..21e0b884 100644 --- a/ui-tree.c +++ b/www/git.causal.agency/cgit/ui-tree.c @@ -89,6 +89,7 @@ static void print_object(const struct object_id *oid, const char *path, const ch enum object_type type; char *buf; unsigned long size; + int is_binary; type = oid_object_info(the_repository, oid, &size); if (type == OBJ_BAD) { @@ -103,6 +104,7 @@ static void print_object(const struct object_id *oid, const char *path, const ch "Error reading object %s", oid_to_hex(oid)); return; } + is_binary = buffer_is_binary(buf, size); cgit_set_title_from_path(path); @@ -110,7 +112,7 @@ static void print_object(const struct object_id *oid, const char *path, const ch htmlf("blob: %s (", oid_to_hex(oid)); cgit_plain_link("plain", NULL, NULL, ctx.qry.head, rev, path); - if (ctx.repo->enable_blame) { + if (ctx.repo->enable_blame && !is_binary) { html(") ("); cgit_blame_link("blame", NULL, NULL, ctx.qry.head, rev, path); @@ -123,7 +125,7 @@ static void print_object(const struct object_id *oid, const char *path, const ch return; } - if (buffer_is_binary(buf, size)) + if (is_binary) print_binary_buffer(buf, size); else print_text_buffer(basename, buf, size); @@ -202,9 +204,11 @@ static int ls_item(const struct object_id *oid, struct strbuf *base, struct walk_tree_context *walk_tree_ctx = cbdata; char *name; struct strbuf fullpath = STRBUF_INIT; + struct strbuf linkpath = STRBUF_INIT; struct strbuf class = STRBUF_INIT; enum object_type type; unsigned long size = 0; + char *buf; name = xstrdup(pathname); strbuf_addf(&fullpath, "%s%s%s", ctx.qry.path ? ctx.qry.path : "", @@ -216,8 +220,7 @@ static int ls_item(const struct object_id *oid, struct strbuf *base, htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", name, oid_to_hex(oid)); - free(name); - return 0; + goto cleanup; } } @@ -237,22 +240,45 @@ static int ls_item(const struct object_id *oid, struct strbuf *base, cgit_tree_link(name, NULL, class.buf, ctx.qry.head, walk_tree_ctx->curr_rev, fullpath.buf); } + if (S_ISLNK(mode)) { + html(" -> "); + buf = read_object_file(oid, &type, &size); + if (!buf) { + htmlf("Error reading object: %s", oid_to_hex(oid)); + goto cleanup; + } + strbuf_addbuf(&linkpath, &fullpath); + strbuf_addf(&linkpath, "/../%s", buf); + strbuf_normalize_path(&linkpath); + cgit_tree_link(buf, NULL, class.buf, ctx.qry.head, + walk_tree_ctx->curr_rev, linkpath.buf); + free(buf); + strbuf_release(&linkpath); + } htmlf("</td><td class='ls-size'>%li</td>", size); html("<td>"); cgit_log_link("log", NULL, "button", ctx.qry.head, walk_tree_ctx->curr_rev, fullpath.buf, 0, NULL, NULL, ctx.qry.showmsg, 0); - if (ctx.repo->max_stats) + if (ctx.repo->max_stats) { + html(" "); cgit_stats_link("stats", NULL, "button", ctx.qry.head, fullpath.buf); - if (!S_ISGITLINK(mode)) + } + if (!S_ISGITLINK(mode)) { + html(" "); cgit_plain_link("plain", NULL, "button", ctx.qry.head, walk_tree_ctx->curr_rev, fullpath.buf); - if (!S_ISDIR(mode) && ctx.repo->enable_blame) + } + if (!S_ISDIR(mode) && ctx.repo->enable_blame) { + html(" "); cgit_blame_link("blame", NULL, "button", ctx.qry.head, walk_tree_ctx->curr_rev, fullpath.buf); + } html("</td></tr>\n"); + +cleanup: free(name); strbuf_release(&fullpath); strbuf_release(&class); diff --git a/ui-tree.h b/www/git.causal.agency/cgit/ui-tree.h index bbd34e35..bbd34e35 100644 --- a/ui-tree.h +++ b/www/git.causal.agency/cgit/ui-tree.h diff --git a/www/git.causal.agency/cgitrc b/www/git.causal.agency/cgitrc new file mode 100644 index 00000000..8ccd7c72 --- /dev/null +++ b/www/git.causal.agency/cgitrc @@ -0,0 +1,29 @@ +root-title=causal agency +root-desc=“I think some people from the Gentoo project are behind this.” +logo= + +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 +owner-filter=/usr/local/libexec/owner-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..3bc61c90 --- /dev/null +++ b/www/git.causal.agency/custom.css @@ -0,0 +1,86 @@ +/* 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.sub { + border-top: none; +} +div#cgit table#header td.sub.right { + padding-right: 1em; +} +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; +} + +/* for hilex(1) */ +div#cgit pre .Ke { color: dimgray; } +div#cgit pre .Ma { color: green; } +div#cgit pre .Co { color: navy; } +div#cgit pre .St { color: teal; } +div#cgit pre .Fo { color: teal; font-weight: bold; } +div#cgit pre .Su { color: olive; } + +/* for htagml(1) */ +div#cgit pre a.tag { color: inherit; text-decoration: underline; } +div#cgit pre a.tag:target { color: goldenrod; outline: none; } + +/* for 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/owner-filter.sh b/www/git.causal.agency/owner-filter.sh new file mode 100644 index 00000000..18e74cf1 --- /dev/null +++ b/www/git.causal.agency/owner-filter.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu + +cat <<EOF +<a href="https://liberapay.com/june/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a> +EOF diff --git a/www/git.causal.agency/source-filter.sh b/www/git.causal.agency/source-filter.sh new file mode 100644 index 00000000..514272db --- /dev/null +++ b/www/git.causal.agency/source-filter.sh @@ -0,0 +1,25 @@ +#!/bin/sh +set -eu + +ctags=/usr/bin/ctags +mtags=/usr/local/libexec/mtags +hilex=/usr/local/libexec/hilex +htagml=/usr/local/libexec/htagml + +case "$1" in + (*.[chlmy]|Makefile|*.mk|*.[1-9]|.profile|.shrc|*.sh) + tmp=$(mktemp -d -t source-filter) + trap 'rm -fr "${tmp}"' EXIT + cd "${tmp}" + cat >"$1" + touch tags + case "$1" in + (*.[chlmy]) $ctags -w "$1";; + (*) $mtags "$1";; + esac + $hilex -f html "$1" | $htagml -i "$1" + ;; + (*) + exec $hilex -t -n "$1" -f html + ;; +esac 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..8fe3acc9 --- /dev/null +++ b/www/text.causal.agency/.gitignore @@ -0,0 +1,3 @@ +*.txt +feed.atom +igp 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..515a30ab --- /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 tilde.chat +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 tilde.chat. +. +.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/014-using-vi.7 b/www/text.causal.agency/014-using-vi.7 new file mode 100644 index 00000000..e6a6a7a0 --- /dev/null +++ b/www/text.causal.agency/014-using-vi.7 @@ -0,0 +1,135 @@ +.Dd January 11, 2021 +.Dt USING-VI 7 +.Os "Causal Agency" +. +.Sh NAME +.Nm Using vi +.Nd simpler tools +. +.Sh DESCRIPTION +Happy new year +and hello from +.Xr vi 1 ! +I'm in the mood to post something +but not in the mood for +.Dq social +media. +This one will probably be short. +. +.Pp +Yesterday I was trying to work on sandboxing +.Xr catgirl 1 +(that's the IRC client I work on) +with +.Xr pledge 2 +and +.Xr unveil 2 +on +.Ox , +as suggested by the maintainer of its port. +I've done similar things before, +but only on server software +rather than user software. +. +.Pp +Anyway I was in +.Xr ssh 1 +to my +.Ox +VM +.Po +sadly I don't currently have any hardware to run +.Ox +on +.Pc +using my usual editor, +which is +.Xr nvim 1 . +I'm honestly not very thrilled +with what neovim is doing lately, +but the cleaned up defaults +make my configuration files happier. +. +.Pp +The real problem with +.Xr nvim 1 , +though, +is that it's laggy as hell on +.Ox . +There is significant delay +on every single keystroke, +as if I'm typing remotely to a server +on the other side of the world, +but this is on a local VM! +. +.Pp +So I did the only reasonable thing: +I typed +.Sy :qa +followed by +.Sy vi . +The difference was astonishing. +Typing and editing suddenly felt +.Em physical +again. +(I put that in italics even though I know it won't render.) +Not only was it a vast improvement over +.Xr nvim 1 +in +.Xr ssh 1 +in a VM, +it was a marked improvement over +.Xr nvim 1 +running locally and natively. +. +.Pp +Now obviously +.Xr vi 1 +doesn't have all the bells and whistles +of newer editors, +but of course the core editing model +that makes +.Xr vim 1 +and +.Xr nvim 1 +so good is there, +and in purer form, +I think. +The +.Xr vi 1 +manual page +is feasible to just sit down and read, +and learn everything there is to know about the editor. +I set up a basic configuration +and got coding. +.Bd -literal -offset indent +export EXINIT='set ai ic sm sw=4 ts=4' +.Ed +. +.Pp +After I finished my +.Xr pledge 2 +and +.Xr unveil 2 +patch, +I was so pleased with +.Xr vi 1 +that I kept on using it +yesterday and today +for other work, +and obviously to write this post. +Despite the lack of editor amenities, +its responsiveness and simplicity +are enough to make using it +.Em comfortable +and perhaps +.Em cosy . +I'm not sure I'll ever use +.Xr vi 1 +full-time, +but for now I am much less likely +to launch +.Xr nvim 1 . +. +.Sh AUTHORS +.An june Aq Mt june@causal.agency diff --git a/www/text.causal.agency/015-reusing-tags.7 b/www/text.causal.agency/015-reusing-tags.7 new file mode 100644 index 00000000..19546496 --- /dev/null +++ b/www/text.causal.agency/015-reusing-tags.7 @@ -0,0 +1,155 @@ +.Dd January 17, 2021 +.Dt REUSING-TAGS 7 +.Os "Causal Agency" +. +.Sh NAME +.Nm reusing tags +.Nd beyond ctags +. +.Sh DESCRIPTION +I've tried to start writing this post a couple times now +and I keep getting bogged down in explanations, +so I'm just going to tell you +about some cool things I did +and hope they make sense. +. +.Pp +When I wrote my first syntax highlighter, +I decided that function definitions +should have anchor links, +because line number anchor links +are entirely useless +if you expect the file to change at all. +Since the syntax highlighter +was somewhat deliberately just a big pile of regex, +I hacked in more regex to try +to identify function and type definitions. +It wasn't elegant and it didn't always work well. +It did work though, +and I found the links very useful. +. +.Pp +Recently I was thinking about +the lexer generator +.Xr lex 1 +and decided to +rewrite the syntax highlighter +using it. +Really syntax highlighting +is no different than lexical analysis. +I ran into a problem though, +trying to preserve my anchor link function, +because really that should involve +some amount of parsing. +Trying to port my regex hacks to +.Xr lex 1 +made the lexers way more complicated +and less reliable, +so I gave up on it for a while. +. +.Pp +And then, +probably in the shower, +I realized I was approaching it +completely from the wrong direction. +There's already a tool that does what I want, +and I already use it: +.Xr ctags 1 . +All I need to do is use its output +to insert anchor links +into my syntax highlighter output. +In an afternoon I wrote +.Xr htagml 1 , +which loads tag definitions for its input file, +then scans through the input for where they match. +It can either HTML-escape +the input as it goes, +or use already formatted HTML +being piped into it from a syntax highlighter. +. +.Pp +The result is three simple tools +working together to accomplish +what a more complex tool +couldn't reliably achieve. +I'm very pleased with it, +and I've updated my site and cgit +to use the new +.Xr lex 1 Ns -based +highlighter, +.Xr ctags 1 +and +.Xr htagml 1 . +I'm currently missing a lexer for +.Xr sh 1 , +but I plan to write it eventually. +I also want to write a tool +to generate tags for +.Xr make 1 , +.Xr mdoc 7 +and perhaps +.Xr sh 1 . +The cool thing about generating more kinds of tags +is that they'll not only improve +the HTML output, +they'll also be usable in my editor. +. +.Pp +Speaking of generating different kinds of tags, +I also wrote some scripts not too long ago +for reading IETF RFCs offline. +The plain text files are available to +.Xr rsync 1 , +but they're hard to navigate on their own. +By scanning the files for headings +and generating tags, +it allows jumping to sections using +.Ic :ta +or +.Ic ^] +in +.Xr vi 1 . +For +.Xr nvim 1 +I also added an +.Ic :RFC +command to open an RFC by number +and set up +.Ic ^] +to work optimally for them. +. +.Pp +I'm still using +.Xr vi 1 +for most of my editing, +by the way. +And of course +.Xr ctags 1 +was made to work with it! +Simple old tools +are really doing it for me lately. +. +.Sh SEE ALSO +.Bl -item -compact +.It +.Lk https://causal.agency/bin/htagml.html htagml +.It +.Lk https://causal.agency/bin/hilex.html hilex +.It +.Lk https://git.causal.agency/src/tree/doc/rfc rfctags +.El +. +.Sh AUTHORS +.An june Aq Mt june@causal.agency +. +.Sh ADDENDUM +.Xr catgirl 1 , +.Xr pounce 1 , +.Xr litterbox 1 +and +.Xr scooper 1 +all have new releases, +if you're using any of them. +Also, this space is now +available over gopher, +if that's your sort of thing. diff --git a/www/text.causal.agency/016-using-openbsd.7 b/www/text.causal.agency/016-using-openbsd.7 new file mode 100644 index 00000000..b843e3c3 --- /dev/null +++ b/www/text.causal.agency/016-using-openbsd.7 @@ -0,0 +1,505 @@ +.Dd February 14, 2021 +.Dt USING-OPENBSD 7 +.Os "Causal Agency" +. +.Sh NAME +.Nm Using OpenBSD +.Nd for real +. +.Sh DESCRIPTION +Hello from +.Ox ! +After wishing one too many times +that I had a real BSD +on a physical machine, +I finally got around to +just installing one on my +mid-2014 MacBook Pro. +I hadn't done it sooner +because I didn't realize +how easy it would be. +It helped that I already had a +.Dq Boot Camp +partition with a disused Windows 8 install +that I could replace. +. +.Pp +I roughly followed an old jcs gist +along with the +.Ox +Disk Setup guide. +I'm once again happy +that I bought a printer\(em +they're very useful for instructions +to install an operating system +on your only usable computer. +I set up encrypted softraid +and the operating system +installed smoothly. +. +.Pp +Next I had to install rEFInd, +since the default Mac boot manager +is really not keen on booting much. +Installing it requires using the +macOS recovery partition these days. +But there was a problem +with my new boot menu: +I was promised a picture of Puffy, +and instead I just got some abstract coloured circles! +Turns out a bunch of OS icons +got removed from rEFInd at some point, +and I had to rescue Puffy +from the git history. +. +.Pp +So I could happily boot +.Ox +by selecting Puffy, +but I had no networking. +I thought the wifi chip might be supported by +.Xr bwfm 4 , +but I got unlucky and it's a BCM4360, +which everything hates. +Based on the jcs gist, +I checked the list of hardware +supported by the +.Xr urtwn 4 +driver for a wifi dongle to order. +Just having a clear list +in the driver manual is wonderful. +I went with the Edimax EW-7811Un v2, +which I could get for around $20. +It's nice and tiny, +though it has a piercing blue LED +(destroy all blue LEDs) +which I had to cover with electrical tape. +. +.Pp +I had to do one other thing +before I could get it all working, though. +When I had checked the +.Xr urtwn 4 +hardware list, +I had been looking at +.Ox Ns -current , +but I had installed +.Ox 6.8 , +and support for the v2 hardware +I had bought was added after that release. +So I downloaded a snapshot +.Pa bsd.rd +along with the +.Xr urtwn 4 +firmware file +to a USB drive +and upgraded the system. +. +.Pp +Connecting to wifi with +.Xr ifconfig 8 +is a breeze, by the way, +and then you just write the same thing to a +.Xr hostname.if 5 +file to make it automatic. +I wanted to use +.Ox +for exactly this reason: +simple, consistent, cohesive, well-documented tools. +. +.Pp +Finally, I got to configuring. +The console is configured with +.Xr wsconsctl 8 , +and similarly you can put the commands in +.Xr wsconsctl.conf 5 +to have them run at boot. +I added +.Li display.brightness=50% +to tone down the brightness, +which is initially 100%, +and +.Li keyboard.backlight=0% +to turn off those annoying lights. +.Xr wsconsctl.conf 5 +is also where you can set +trackpad settings if you're not using +.Xr synaptics 4 . +I ended up using: +.Bd -literal -offset indent +mouse1.tp.tapping=1 +mouse1.tp.scaling=0.2 +mouse1.reverse_scrolling=1 +.Ed +.Pp +This enables tapping with several fingers +to simulate different mouse buttons, +makes the cursor move at a reasonable speed +and scrolling move in the right direction. +I also set up my usual modified QWERTY layout. +. +.Pp +For +.Xr X 7 +I had enabled +.Xr xenodm 1 , +which seems quite nice. +It automatically prompts you to add your +.Xr ssh 1 +keys to +.Xr ssh-agent 1 +when you log in. +One of the reasons I had not wanted +to set up another graphical system +is that I thought +I would have to make too many choices, +and that I would have to choose least bad options +rather than actually good options, +but +.Ox +already includes reasonable choices. +I wanted to use +.Xr cwm 1 , +so I started a basic +.Pa .xsession +file: +.Bd -literal -offset indent +\&. ~/.profile +export LC_CTYPE=en_US.UTF-8 +xset r rate 175 m 5/4 0 +xmodmap ~/.config/X/modmap +xrdb -load ~/.config/X/resources +exec cwm -c ~/.config/cwm/cwmrc +.Ed +. +.Pp +The +.Xr xset 1 +command sets keyboard repeat rate +and mouse acceleration. +I spent some time going through +.Xr cwm 1 Ap s +functions and writing up bindings +that would get me something close enough +to what I'm used to in macOS. +Most importantly, +putting everything on the 4 modifier (command key). +. +.Pp +I also added key bindings on F1 and F2 +to adjust the brightness with +.Xr xbacklight 1 , +and on F10, F11 and F12 +to adjust volume with +.Xr sndioctl 1 . +I'm not sure why the F keys +just send regular F1, F2, etc.\& +regardless of the Fn key. +I don't use F keys for anything else though, +so I'm not too concerned. +Once again, +.Xr sndioctl 1 +is such an easy straightforward tool: +.Bd -literal -offset indent +bind-key F10 "sndioctl output.mute=!" +bind-key F11 "sndioctl output.level=-0.05" +bind-key F12 "sndioctl output.level=+0.05" +.Ed +. +.Pp +For aesthetic configuration, +I added a new output to my +.Xr scheme 1 +colour scheme tool for +.Xr X 7 Ns -style +RGB and +.Xr xterm 1 +resources. +Normally I use the +.Em Go Mono +font, +but since +.Ox +already includes +.Em Luxi Mono , +which +.Em Go Mono +is based on, +I used that. +The most important configuration +to make anything readable on a high-DPI display is: +.Bd -literal -offset indent +Xft.dpi: 144 +Xft.antialias: true +Xft.hinting: false +.Ed +. +.Pp +I'm annoyed that I haven't found +where these resources are actually documented. +I would hope they'd be in +.Xr Xft 3 +or something, +but they're not. +Anyway, +turning off hinting +seems absolutely necessary +to prevent text from looking like garbage. +. +.Pp +It seems that to get a reasonably sized cursor +I need to install +.Sy xcursor-dmz . +I'd prefer if there wasn't this one +extra package that I needed +for a reasonable setup. +Tangentially, +I've never understood why +the black versions of dmz cursors +are called +.Dq aa +when it seems like that +would stand for antialiasing +or something. +.Bd -literal -offset indent +Xcursor.size: 64 +Xcursor.theme: dmz-aa +.Ed +. +.Pp +For a desktop background, +I found a cute bitmap (little picture) +of snowflakes already in the system +and used colours from my usual scheme: +.Bd -literal -offset indent +xsetroot -bitmap /usr/X11R6/include/X11/bitmaps/xsnow \e + -bg rgb:14/13/0E -fg rgb:7A/49/55 +.Ed +. +.Pp +Since I'd rather not install anything +I don't have to, +I went with the default +.Xr xterm 1 . +It seems more than adequate, honestly. +I read through its RESOURCES +section to configure it how I like. +The important settings are +.Sy XTerm*utf8 +and +.Sy XTerm*metaSendsEscape . +Since I'm used to copying and pasting on macOS, +I added equivalent +.Dq translations : +.Bd -literal -offset indent +XTerm*VT100*translations: #override \en\e + Super <Key>C: copy-selection(CLIPBOARD) \en\e + Super <Key>V: insert-selection(CLIPBOARD) +.Ed +. +.Pp +The next thing I needed +was a clock and battery indicator. +I actually had my battery die on me +while I was doing all this, +which reminded me. +.Xr xclock 1 +would be perfect, +but then I'd need something else +for battery. +There are a couple basic battery indicators +for X in ports, +but they're terribly ugly. +I wanted something as simple as +.Xr xclock 1 , +but that I could add some other text to. +Then I realized I could just use +.Xr xterm 1 +for that. +To my +.Pa xsession +I added: +.Bd -literal -offset indent +xterm -name clock -geometry 14x1-0+0 -sl 0 -e clock & +.Ed +.Pp +This places a little terminal +in the top-right corner of the screen +with no scrollback lines, +running a script called +.Pa clock . +To have +.Xr cwm 1 +treat it like a +.Dq panel +and show it on every desktop, +I added this to my +.Pa cwmrc : +.Bd -literal -offset indent +ignore clock +autogroup 0 clock,XTerm +.Ed +.Pp +The +.Pa clock +script simply uses +.Xr date 1 +and +.Xr apm 8 +to print the time and battery charge +every minute: +.Bd -literal -offset indent +tput civis +sleep=$(( 60 - $(date +'%S' | sed 's/^0//') )) +while :; do + if [ $(apm -a) -eq 1 ]; then + printf '%3s%%' "$(apm -l)" + else + test $(apm -b) -eq 2 && tput setaf 1 bold + printf '%3.3sm' "$(apm -m)" + tput sgr0 + fi + printf ' %s\r' "$(date +'%a %H:%M')" + sleep $sleep + sleep=60 +done +.Ed +.Pp +The initial setting of +.Va sleep +is to align the updates +with the minute ticking over. +I made the battery output +a bit fancier by showing +percentage while charging, +minutes left while discharging, +and highlighting in red +when the battery is +.Dq critical . +. +.Pp +Now is a good time to mention adding +.Ql apmd_flags=-A +to +.Pa /etc/rc.conf.local +to enable +.Dq automatic performance adjustment , +or not running your battery flat +as fast as possible mode. +It seems like I can get up to 3 hours +of battery life depending on the screen brightness, +but this is quite an old battery by now. +. +.Pp +The other thing I needed +was something to tone down +that awful, evil blue light from the screen. +I asked around and someone told me about +.Xr sct 1 , +originally written by tedu. +The package also includes a little +.Xr sctd 1 +script that you can add to your +.Pa .xsession +to have it automatically adjust +the colour temperature throughout the day. +My eyes are no longer being assaulted. +. +.Pp +While I was doing all this, +I of course needed to talk about it on IRC, +and it was very nice to be able to +install my own IRC client with: +.Bd -literal -offset indent +doas pkg_add catgirl +.Ed +.Pp +I don't plan to do +general Web Browsing on +.Ox , +and there is definitely +no good choice for browser, +so I just installed +.Xr imv 1 , +.Xr mpv 1 , +.Xr youtube-dl 1 +and +.Xr w3m 1 . +I wrote a script +to open images by piping +.Xr curl 1 +into +.Xr imv 1 , +videos with +.Xr mpv 1 , +and everything else with +.Xr w3m 1 +in a new +.Xr xterm 1 . +Annoyingly, +.Xr mpv 1 +seems incapable of exiting +without segfaulting. +That's quality. +. +.Pp +One thing I am still missing +is automatic brightness adjustment +based on ambient light +like macOS can do. +I can read the sensor with +.Xr sysctl 8 +.Cm hw.sensors.asmc0.illuminance0 , +which is measured in lux. +I tried doing something with it in a script, +but it seems tricky to map its value +to brightness adjustments +and to play nice with manual brightness changes, +so I'll just keep doing it manually for now. +. +.Pp +Update: +prx sent mail to let me know about +.Aq Lk https://github.com/jcs/xdimmer . +I should've guessed jcs had written something. +. +.Pp +And that's my current +.Ox +setup after a week of using it. +I'm quite enjoying it, +and still being pleasantly surprised +by the quality-of-life from +.Ox +tools and documentation. +For a small example, +I can jump to sections +or flag definitions in +.Xr man 1 +using +.Ic :t . +Systems without basic usability like that +should be ashamed. +. +.Pp +I would post a screenshot, +but this is +.Li text.causal.agency +;) +. +.Sh SEE ALSO +.Lk https://gist.github.com/jcs/5573685 +.Pp +My full configurations are in +.Aq Lk https://git.causal.agency/src . +. +.Sh AUTHORS +.An june Aq Mt june@causal.agency +. +.Sh BUGS +There's a red LED +inside the headphone jack +that is always on +and I have no idea how to turn off. +If anyone knows +please send me an email. diff --git a/www/text.causal.agency/017-unpasswords.7 b/www/text.causal.agency/017-unpasswords.7 new file mode 100644 index 00000000..f9643f2f --- /dev/null +++ b/www/text.causal.agency/017-unpasswords.7 @@ -0,0 +1,153 @@ +.Dd February 20, 2021 +.Dt UNPASSWORDS 7 +.Os "Causal Agency" +. +.Sh NAME +.Nm Unpasswords +.Nd password anti-management +. +.Sh DESCRIPTION +Right away I want to say +that I'm not trying to tell anyone +how to manage their online authentication. +This is just how I do it, +and I haven't seen anyone else write about it. +. +.Pp +I don't use a password manager. +It's not a type of software +I want to deal with. +For the small handful of sites +that I use regularly +and that actually matter, +I use strong passwords +(stored in my noggin) +and TOTP. +For everything else, +I simply do not know the password, +and neither does any software. +. +.Pp +I think I started doing this one time +when I had legitimately forgotten +the password to some old account. +I clicked on +.Dq forgot my password +and opened the email, +but I didn't want to +come up with a new password +I would just forget again. +Instead I set a random one +.Po +I usually use +.Ql openssl rand -base64 33 +for this +.Pc +and immediately used that to log in +while it was still in my clipboard. +Next time I wanted to log in, +I could use +.Dq forgot my password +again. +. +.Pp +Thinking about it, +I realized that any web authentication +with an email password reset flow +is only ever as strong as +the authentication for your email account. +So what is the point of having +all these passwords set on different sites? +They all answer to your email account, +and storing them in a password manager +seems to add another potential point of failure. +May as well have no other passwords at all, +or as close as possible. +.Po +Shout out to sites like Liberapay +and asciienema +which let me not set a password at all. +.Pc +. +.Pp +So I started doing that for any site +that I don't regularly log in to. +Going through the password reset flow +can be a bit slow, +but it doesn't need to be done often. +And I can do it from anywhere +I have access to my email, +which I feel is more easily reliable +than syncing password management databases. +It's quite stress-free. +. +.Pp +After doing this manually for years, +this week I finally got around to +writing some automation for it. +A while ago I had written +.Xr imbox 1 , +a tool to directly export mail +in mboxrd format from IMAP, +along with +.Xr git-fetch-email 1 , +a wrapper which offloads configuration to +.Xr git-config 1 . +It can match emails by +Subject, From, To and Cc. +This week I added a flag +to use IMAP IDLE +to wait for a matching message +if there isn't one already, +and a flag to move matching messages +(for example to Trash) +after exporting them. +. +.Pp +With those two new flags, +I started writing some shell scripts +to automate the password reset flow +using +.Xr curl 1 +to submit forms and +.Xr git-fetch-email 1 +with +.Xr sed 1 +to pull the reset tokens +from my inbox. +At the end of the script, +the random password it set +is copied to the clipboard +and the login page for the site is opened. +So now logging in is as simple +as running a command, +waiting for the login page to open, +and pasting. +. +.Pp +The script isn't sophisticated, +but I don't think it needs to be. +I've written functions +for a couple different sites already, +and they all work in mostly the same way. +Writing a new one is just a matter +of identifying the form URLs and fields +along with where the token is in the email. +I'm not going to turn this automation +into any kind of generally usable project, +because I don't want to have to +maintain functions for tonnes of different services. +If you're interested in this idea, +I encourage you to use my script as a template +and implement the functions for services you use. +. +.Sh SEE ALSO +.Bl -item -compact +.It +.Lk https://git.causal.agency/imbox +.It +.Lk https://causal.agency/bin/sup.html +.El +. +.Sh AUTHORS +.An june Aq Mt june@causal.agency diff --git a/www/text.causal.agency/018-operating-systems.7 b/www/text.causal.agency/018-operating-systems.7 new file mode 100644 index 00000000..691102e2 --- /dev/null +++ b/www/text.causal.agency/018-operating-systems.7 @@ -0,0 +1,86 @@ +.Dd February 22, 2021 +.Dt OPERATING-SYSTEMS 7 +.Os "Causal Agency" +. +.Sh NAME +.Nm Operating systems +.Nd criteria +. +.Sh DESCRIPTION +Sometimes in conversation +I use the term +.Dq real operating system +which people, +perhaps rightfully, +take as inflammatory. +But I have actually thought about +what I mean when I say +.Dq real operating system +and come up with +this list of criteria. +. +.Pp +An operating system should be... +.Bl -bullet +.It +Consistent and cohesive: +all parts of the system should have similar +usage, configuration, documentation and so on. +Parts of the system should naturally work together, +because they were designed to do so. +. +.It +Documented: +the system should include its own documentation. +A user should not have to +search some external wiki +to learn how the system works. +It should be obvious +where to find documentation +on a particular topic. +. +.It +Programmable: +the system should provide +a way to program the computer. +A computer which cannot be programmed +is not a computer at all. +Usually this takes the form +of a C compiler +and the tools that go with it. +In earlier times, +it might have been +a BASIC interpreter. +. +.It +Examinable and modifiable: +the full source tree +for the system should be included, +or easily obtainable +through official means. +A user should have no trouble +finding the corresponding source +for a part of the system. +Together with the previous point, +the source tree should be +compiled by the included toolchain, +allowing local modification. +.El +. +.Pp +Some things that may be parts +of real operating systems, +but are not themselves operating systems: +a kernel, +a package manager, +a collection of packages. +. +.Pp +I will leave it as an +.Dq exercise for the reader +to guess which operating systems +meet these criteria +and which don't. +. +.Sh AUTHORS +.An june Aq Mt june@causal.agency diff --git a/www/text.causal.agency/019-mailing-list.7 b/www/text.causal.agency/019-mailing-list.7 new file mode 100644 index 00000000..3c70b160 --- /dev/null +++ b/www/text.causal.agency/019-mailing-list.7 @@ -0,0 +1,287 @@ +.Dd March 4, 2021 +.Dt MAILING-LIST 7 +.Os "Causal Agency" +. +.Sh NAME +.Nm Mailing List +.Nd a small-scale approach +. +.Sh DESCRIPTION +When I initially published +some software I expected +other people to use, +I just asked that patches +be mailed directly to me, +but I figured that +if more people were interested, +it would be better +to have a mailing list. +Unfortunately +email software, +mailing list options in particular, +are quite daunting. +I wanted a light-weight option +that would require me to host +as little software as possible. +. +.Pp +My regular email is hosted by Fastmail, +and I poked around its settings +to see what I could do. +It turns out Fastmail lets you +configure address aliases to +.Dq also send to all contacts in +a contacts group. +That's a mailing list! +I created a group called +.Dq List +and an alias called +.Mt list@causal.agency +configured to deliver to that group. +So it's really just an alias +for my regular address +that happens to also +deliver to another group of people. +. +.Pp +It's easier to just configure +and manage one mailing list, +so what I do is ask patches and feedback +to be sent to +.Mt list+catgirl@causal.agency , +for example. +Fastmail treats any +.Ar +suffix +the same as the base address, +but the full address can be used +by subscribers to filter mail by topic +if they wish. +. +.Pp +To subscribe someone to the list, +I add their contact to the group. +For a long time I was planning +to write some software +to manage these subscriptions. +It should be possible +to process subscription requests from IMAP +and manipulate the contact group with CardDAV. +When I went to start implementing this, +however, +I found CardDAV (and WebDAV in general) +completely inscrutable. +It's the kind of protocol +that is split across like 20 +different RFCs +and you can't understand anything +by just reading +the one you actually care about. +So I've given up on that +and will keep manually subscribing people +on request. +. +.Pp +The only thing missing, then, +is a way for people to read +mail sent to the list +while they aren't subscribed. +All the existing +mailing list archive software +I know of +expects to have the mail locally, +but I'd rather keep all my mail in IMAP. +First, +in order to make sure +I keep a complete archive +of the mailing list in IMAP, +I added a small amount +of Sieve code +to my Fastmail filters configuration: +.Bd -literal -offset indent +if address :matches ["To", "Cc"] "list*@causal.agency" { + fileinto :copy :flags "\e\eSeen" "INBOX.List"; +} +.Ed +. +.Pp +Sieve is a small standard language +specifically for filtering mail. +This bit of code matches +anything sent to the list +and adds a copy of it +(the original is going into my inbox) +to the +.Dq List +folder +and marks the copy as read. +. +.Pp +With a pristine IMAP mailbox +to export from, +I wrote a new archive generator. +It's called +.Xr bubger 1 +kirg (have it in a way). +My goal was to render directly from IMAP +and produce only static files as output, +making it not only easy to serve, +but also to run in one place +and copy the files elsewhere. +That's important to me +because it has access to my email, +so I'd rather run it +on my local network and +.Xr rsync 1 +its output into The Cloud. +The static files are in +HTML, Atom and mboxrd formats. +. +.Pp +The architecture of +.Xr bubger 1 +is that for each piece of mail, +identified by its UID in the mailbox, +HTML and Atom fragments +are exported along with the mboxrd. +Those fragments are then stitched together +using the IMAP SORT and THREAD extensions +to make full pages and feeds +for each thread. +The fragments act as a cache +for subsequent runs. +. +.Pp +I admit I did some +pretty questionable things +to achieve this. +Namely, +I wrote a small string templating engine in C. +I use it to produce the HTML +and XML for Atom, +as well as to generate URLs +and paths. +I'm really happy with how it works, actually. +This is also where +I really started using +one of my favourite C hacks: +.Bd -literal -offset indent +#define Q(...) #__VA_ARGS__ +.Ed +. +.Pp +I quote all my HTML/XML templates +with this and it's lovely. +. +.Pp +I've been working on +.Xr bubger 1 +on and off for almost a year now, +and it's been interesting. +I learned a lot about how email +works from having to deal with +all the ways a message can be. +Thankfully a lot of that dealing +is done by the IMAP server. +. +.Pp +As for running it, +I initially just ran it with +.Xr cron 8 , +and that's still a good way to go. +To hook it up to +.Xr rsync 1 , +pipe it like so: +.Bd -literal -offset indent +bubger -C list [...] | rsync -a --files-from=- list remote:list +.Ed +. +.Pp +Later, +I got a little annoyed +with having to wait +for the next run +if I wanted to link +to some mail I just received. +I added an option +to use IMAP IDLE +to wait for new mail continuously +and I started running it +under my process supervisor, +.Xr catsitd 8 . +. +.Pp +The setup is a little more complex +to feed the list of updated files to +.Xr rsync 1 . +I added the +.Xr catsit-watch 1 +utility to run a command +when a file changes, +and in my +.Xr catsit.conf 5 +I have the following: +.Bd -literal -offset indent +bubger ~/.local/libexec/bubger +rsync catsit-watch -i -f ~/list/UIDNEXT ~/.local/libexec/rsync +.Ed +. +.Pp +The +.Pa ~/.local/libexec/bubger +script runs +.Xr bubger 1 , +writing the list of updated paths to +.Pa ~/list/FILES , +truncating it before each update: +.Bd -literal -offset indent +exec bubger -it -C ~/list [...] >~/list/FILES +.Ed +. +.Pp +And the +.Pa ~/.local/libexec/rsync +script gets run each time a +.Xr bubger 1 +update completes +.Po +.Pa UIDNEXT +is always the last file written +.Pc +and copies the listed files +to the remote host: +.Bd -literal -offset indent +exec rsync -a --files-from=$HOME/list/FILES ~/list remote:list +.Ed +. +.Pp +I haven't tagged any +.Xr bubger 1 +releases yet +because it hasn't gotten +a huge amount of testing, +and I'm not sure anyone but me +would even want to use it. +But I'm happy +with how it's working right now, +so I might tag 1.0 soon +just for fun. +. +.Sh SEE ALSO +.Bl -item -compact +.It +.Lk https://causal.agency/list/ +.It +.Lk https://git.causal.agency/bubger/about +.It +.Lk https://git.causal.agency/catsit/about +.El +. +.Sh AUTHORS +.An june Aq Mt june@causal.agency +. +.Sh BUGS +Almost every time +I try to type +.Dq mailing list +I instead type +.Dq mailist list . diff --git a/www/text.causal.agency/020-c-style.7 b/www/text.causal.agency/020-c-style.7 new file mode 100644 index 00000000..9816dbc3 --- /dev/null +++ b/www/text.causal.agency/020-c-style.7 @@ -0,0 +1,172 @@ +.Dd March 16, 2021 +.Dt C-STYLE 7 +.Os "Causal Agency" +. +.Sh NAME +.Nm C Style +.Nd a rough description +. +.Sh DESCRIPTION +This is a rough description +of the style in which I write C, +since it's uncommon +but some people seem to like it. +I don't have any hard rules, +it just needs to look right. +. +.Ss Superficialities +I use tabs +and they're set to 4 characters wide +in my editor. +I keep my lines shorter than 80 columns, +which I enforce by +not resizing my terminal's width. +I use block indentation only, +meaning I write long function calls +like this: +.Bd -literal -offset indent +fprintf( + imap.w, "%s UID THREAD %s UTF-8 %s\er\en", + Atoms[thread], algo, search +); +.Ed +.Pp +Anything that can be sorted +should be sorted, +with trailing commas where possible. +This and block indentation +make for simpler diffs. +.Pp +I either write single-line ifs +or always use braces. +I put parentheses +around ternary expressions. +I use camelCase +for functions and variables, +and PascalCase for types and constants. +When an acronym appears +in an identifier, +it's in either all lower case +or all upper case. +The despicable SCREAMING_SNAKE_CASE +is reserved for macros. +I don't set globals or statics to zero +since that is already the default. +I don't compare against zero or NULL +unnecessarily. +. +.Ss \&No side-effects in control flow +I never write a function call +with side-effects +inside the condition of an if statement. +I find this makes following the +.Dq happy path +through functions +much easier. +I write things like this: +.Bd -literal -offset indent +pidFile = open(pidPath, O_WRONLY | O_CREAT | O_CLOEXEC, 0600); +if (pidFile < 0) err(EX_CANTCREAT, "%s", pidPath); + +error = flock(pidFile, LOCK_EX | LOCK_NB); +if (error && errno != EWOULDBLOCK) err(EX_IOERR, "%s", pidPath); +if (error) errx(EX_CANTCREAT, "%s: file is locked", pidPath); +.Ed +.Pp +I do write side-effects +inside for and while statement heads, +since that's generally expected. +For some reason +I like to write the constant first +if I'm comparing the result of an assignment +with a side-effect. +.Bd -literal -offset indent +for (ssize_t len; 0 <= (len = getline(&buf, &cap, file)); ++line) +.Ed +. +.Ss Paragraphs +I leave blank lines +between logical chunks of +.Dq things happening . +This is usually between side-effects +with their related error handling, +or between groups of closely related side-effects. +I try to keep variable declarations +glued to the top of the bit of code +they're used in. +. +.Ss Leading break +I've mentioned this previously. +I write my switch statement breaks +before each case label. +Doing this aligns nicely, +and being in the habit +means I always avoid +accidental fallthrough. +.Bd -literal -offset indent +switch (opt) { + break; case 'a': append = 1; + break; case 'd': delay = strtol(optarg, NULL, 10); + break; case 'f': watch(kq, optarg); + break; case 'i': init = 1; + break; default: return EX_USAGE; +} +.Ed +. +.Ss Function type definitions +Function types are always typedef'd, +and it's the function type itself +that is defined, +not a function pointer type! +I put the typedef above any functions +that are supposed to be of that type +so it's clear what the pattern is. +.Bd -literal -offset indent +typedef void Action(struct Service *service); +Action *fn = NULL; +.Ed +. +.Ss Constants +I prefer enums over #defines +for integer constants, +and static const strings over #defines +unless I want to do concatenation. +.Bd -literal -offset indent +enum { Cap = 1024 }; +.Ed +.Pp +I avoid the preprocessor +wherever possible, +with the notable exception of X macros, +which I've talked about previously. +Doing things in the actual language +makes for easier debugging. +. +.Ss Organization +I usually use only one header file +in each project. +The dependency is easy to declare +and the complete rebuild +when the header changes +isn't a problem for small projects. +Unless it's a single-file program, +I name the file which contains main +something generic, +since the name of the project +isn't relevant to its function. +I name functions like +.Ar nounVerb , +and all the functions for a +.Ar noun +are defined in +.Pa noun.c . +Not really to do with C, +but I always put a FILES section +in my README pages +to briefly describe +the layout of the code +for anyone looking to +read or make changes to it. +. +.Sh AUTHORS +.An june Aq Mt june@causal.agency diff --git a/www/text.causal.agency/021-time-machine.7 b/www/text.causal.agency/021-time-machine.7 new file mode 100644 index 00000000..93d35c1e --- /dev/null +++ b/www/text.causal.agency/021-time-machine.7 @@ -0,0 +1,144 @@ +.Dd April 25, 2021 +.Dt TIME-MACHINE 7 +.Os "Causal Agency" +. +.Sh NAME +.Nm Time Machine +.Nd an awful one +. +.Sh DESCRIPTION +If, like me, +you have a Raspberry Pi 3 at home +that you've just upgraded to +.Fx 13.0 +which has a hard drive +from an old laptop +attached to it by USB adapter +with ZFS on it +and you want to +use that as a Time Machine +backup destination +over SMB using +.Xr samba 8 , +despite +.Xr samba 8 +being awful software +and using ZFS on a system +with only 1 GB of RAM +being a terrible idea, +this is how to do it. +. +.Pp +In +.Pa /usr/local/etc/smb4.conf : +.Bd -literal -offset indent +[global] +vfs objects = zfsacl catia fruit streams_xattr +fruit:metadata = stream +fruit:model = Macmini + +[TimeMachine] +read only = no +path = /media/zhdd/backup/TimeMachine +fruit:time machine = yes +fruit:time machine max size = 250G +.Ed +. +.Pp +The important thing here is +.Sy zfsacl +in the vfs objects list. +Most pages will tell you about the others, +but without +.Sy zfsacl +Time Machine will just fail to +create the backup +and not provide any useful error. +I'm not actually sure if the +.Sy fruit:metadata +setting is required, +but a bunch of pages recommend it. +The +.Sy fruit:model +just makes it look nice in Finder. +The rest creates an SMB share called +.Dq TimeMachine +that macOS will be willing to use. +You can limit the size of the share that +.Xr samba 8 +reports so that Time Machine +doesn't fill up the whole drive. +. +.Pp +The other important thing to do +is to create some swap space. +When I first tried backing up +to this share, +it stopped after a while +because +.Xr smbd 8 +got killed +when there was nowhere to swap pages to. +A wiki page told me to +create swap on ZFS like this: +.Bd -literal -offset indent +zfs create -V 2G \e + -o org.freebsd:swap=on \e + -o checksum=off \e + -o compression=off \e + -o dedup=off \e + -o sync=disabled \e + -o primarycache=none \e + zhdd/swap +swapon /dev/zvol/zhdd/swap +.Ed +. +.Pp +To be fair to +.Xr samba 8 , +most of the memory +is being used by the ZFS ARC +.Po +which you can see in +.Xr top 1 +.Pc , +but +.Xr smbd 8 +still seems to be using +far more memory than is reasonable. +It's interesting seeing processes +with 0 RES in +.Xr htop 1 +because they're all being swapped out +while the ARC takes half the available RAM. +And having to wait for my shell +to be paged back in when I quit +.Xr htop 1 . +. +.Pp +Anyway, +as expected this whole thing +is terribly slow. +On my initial backup, +I'm currently at 26.49 GB +of 104.22 GB +with an estimate of 8 hours remaining. +Normally transfer time estimates +are wildly inaccurate, +but I think in this case it's right. +. +.Sh AUTHORS +.An june Aq Mt june@causal.agency +. +.Sh BUGS +.Fx +doesn't seem to want to mount +the ZFS volumes on the hard-drive-over-USB +automatically at boot. +I have to +.Xr zpool-import 8 +the drive manually each time. +I don't know if there's a workaround for this, +but I don't have anything essential +to the system on the drive, +and it doesn't need to reboot often. diff --git a/www/text.causal.agency/022-swans-are-dead.7 b/www/text.causal.agency/022-swans-are-dead.7 new file mode 100644 index 00000000..8664e886 --- /dev/null +++ b/www/text.causal.agency/022-swans-are-dead.7 @@ -0,0 +1,164 @@ +.Dd May 5, 2021 +.Dt SWANS-ARE-DEAD 7 +.Os "Causal Agency" +. +.Sh NAME +.Nm Swans Are Dead +.Nd album by Swans +. +.Sh DESCRIPTION +Swans Are Dead +is the best Swans album. +Among my favourites are +Soundtracks for the Blind, +To Be Kind +and Love of Life, +but Swans Are Dead +is the one I come back to +most consistently. +I'm always in the mood +to listen to these tunes. +. +.Pp +It's interesting to me +that I enjoy it so much, +I think because I had the expectation +that live albums +are not of the same quality +as studio albums, +but that's just completely untrue +in the case of Swans. +The performances are excellent +and the recording is +for the most part perfect. +The album feels live, +without any distracting deficiencies +of live recording +that would take you out +of just enjoying the music. +. +.Bl -ohang +.It Dq Feel Happiness +This track feels kind of special +since it's the only song on the album +that was never released +as part of another project. +I absolutely love this format of song. +It's like 10 minutes of build +before any lyrics happen, +which you only get after +the wave of the first part +of the song collapses. +It bookends the first disc nicely with +.Dq Blood Promise, +I think, +which is sort of the reverse. +. +.It Dq Blood On Yr Hands +This is such a great start +to the Jarboe-focused +section of the black disc. +A cappella apart from the hum +of the equipment on stage, +I love this vocal performance. +I sing this song, +terribly, +in the shower. +The lack of instrumental +seems to make it stick in my mind even more. +. +.It Dq I Crawled +This is another great vocal performance +by Jarboe. +It's so much more dynamic and intense +than the version of this song +released much earlier on Young God +with Gira's vocals. +I remember seeing a bad comment +somewhere online +from someone who couldn't stand +any Swans song Jarboe sang on. +They must have never heard +this version of +.Dq I Crawled. +Incredible. +. +.It Dq Blood Promise +My favourite track on +Swans Are Dead, +by far. +I had actually never heard of +.Dq The Whiffenpoof Song +until I looked up +the recording they use +to introduce this song +and indicate it's the last of the show. +Anyway, +this track highlights +what makes Swans live albums +so interesting. +This performance of the song +has evolved so much +from the studio recording on +The Great Annihilator. +They share the same lyrics, +but the earlier version is only 4:15, +to the live version's fifteen and a half minutes! +And it sucks me in the whole time. +As the song winds down +you can hear an audience member yell, +.Dq Don't stop! +and I agree. +. +.It Dq The Sound +One of my all-time favourite songs. +It's the one that got me to listen to +Soundtracks for the Blind, +and might've gotten me into Swans altogether. +I don't quite remember +what order I started listening to things in. +This version of it is great. +I don't think I could choose +between this and the studio recording. +There are just +two ways to enjoy it. +I love how frantic the guitars get +at the height of this track. +. +.It Dq I See Them All Lined Up +This version of the song +is way more harsh +than the version on Soundtracks. +It loses some contrast +between the verses +and the explosions of sound +punctuating them, +it just hits hard +the whole time. +. +.It Dq Yum Yab +An absolute banger. +The drums sound so good on this +and they really get me moving. +The whole thing is delightfully nasty and fun. +.El +. +.Pp +Everything else on the album +is good too, +of course, +I just don't have as much to say. +There's almost two and a half hours of music +on this thing! +. +.Sh AUTHORS +.An june Aq Mt june@causal.agency +.Pp +I want to try writing +about different kinds of things here, +and this is my first attempt +at doing so. +There's more music +I want to write about, +and maybe some other +non-computer topics. diff --git a/www/text.causal.agency/Makefile b/www/text.causal.agency/Makefile new file mode 100644 index 00000000..192bb272 --- /dev/null +++ b/www/text.causal.agency/Makefile @@ -0,0 +1,47 @@ +WEBROOT ?= /usr/local/www/text.causal.agency +LIBEXEC ?= /usr/local/libexec + +CFLAGS += -Wall -Wextra + +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 +TXTS += 014-using-vi.txt +TXTS += 015-reusing-tags.txt +TXTS += 016-using-openbsd.txt +TXTS += 017-unpasswords.txt +TXTS += 018-operating-systems.txt +TXTS += 019-mailing-list.txt +TXTS += 020-c-style.txt +TXTS += 021-time-machine.txt +TXTS += 022-swans-are-dead.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 igp + +install: ${TXTS} feed.atom + install -p -m 644 ${TXTS} feed.atom ${WEBROOT} + +install-igp: igp + install igp ${LIBEXEC} + install -p -m 644 igp.c ${WEBROOT} diff --git a/www/text.causal.agency/feed.sh b/www/text.causal.agency/feed.sh new file mode 100644 index 00000000..f45bd326 --- /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 txt in *.txt; do + entry="${txt%.txt}.7" + 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>' diff --git a/www/text.causal.agency/igp.1 b/www/text.causal.agency/igp.1 new file mode 100644 index 00000000..ccfaeaa6 --- /dev/null +++ b/www/text.causal.agency/igp.1 @@ -0,0 +1,49 @@ +.Dd January 14, 2021 +.Dt IGP 1 +.Os +. +.Sh NAME +.Nm igp +.Nd insane gopher posse +. +.Sh SYNOPSIS +.Nm +.Op Fl h Ar host +.Op Fl p Ar port +.Ar directory +. +.Sh DESCRIPTION +The +.Nm +utility is a simple +Internet Gopher server +which can be started by +.Xr inetd 8 . +It serves directory listings +and files under +.Ar directory . +. +.Pp +The arguments are as follows: +.Bl -tag -width Ds +.It Fl h Ar host +Set the host used in directory listings. +The default is the local host name. +.It Fl p Ar port +Set the port used in directory listeings. +The default is port 70. +.El +. +.Sh STANDARDS +.Rs +.%A B. Alberti +.%A F. Anklesaria +.%A D. Johnson +.%A P. Lindner +.%A M. McCahill +.%A D. Torrey +.%T The Internet Gopher Protocol +.%I IETF +.%R RFC 1436 +.%D March 1993 +.Re diff --git a/www/text.causal.agency/igp.c b/www/text.causal.agency/igp.c new file mode 100644 index 00000000..cda604d8 --- /dev/null +++ b/www/text.causal.agency/igp.c @@ -0,0 +1,143 @@ +/* Copyright (C) 2021 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <sysexits.h> +#include <time.h> +#include <unistd.h> + +#ifdef __FreeBSD__ +#include <sys/capsicum.h> +#endif + +static int compar(const void *_a, const void *_b) { + const struct dirent *a = _a; + const struct dirent *b = _b; + if (a->d_type != b->d_type) { + return (a->d_type > b->d_type) - (a->d_type < b->d_type); + } + return strcmp(a->d_name, b->d_name); +} + +int main(int argc, char *argv[]) { + int error; + const char *host = NULL; + const char *port = "70"; + for (int opt; 0 < (opt = getopt(argc, argv, "h:p:"));) { + switch (opt) { + break; case 'h': host = optarg; + break; case 'p': port = optarg; + break; default: return EX_USAGE; + } + } + if (optind == argc) return EX_USAGE; + if (!host) { + static char buf[256]; + error = gethostname(buf, sizeof(buf)); + if (error) abort(); + host = buf; + } + + const char *path = argv[optind]; + int root = open(path, O_RDONLY | O_DIRECTORY); + if (root < 0) err(EX_NOINPUT, "/"); + +#ifdef __FreeBSD__ + cap_rights_t cap; + error = cap_enter() + || cap_rights_limit(STDIN_FILENO, cap_rights_init(&cap, CAP_READ)) + || cap_rights_limit(STDOUT_FILENO, cap_rights_init(&cap, CAP_WRITE)) + || cap_rights_limit(STDERR_FILENO, &cap) + || cap_rights_limit( + root, cap_rights_init(&cap, CAP_PREAD, CAP_FSTATAT, CAP_FSTATFS) + ); + if (error) abort(); +#else +#warning "This is completely insecure without capsicum(4)!" +#endif + + char buf[1024]; + if (!fgets(buf, sizeof(buf), stdin)) return EX_PROTOCOL; + char *ptr = buf; + char *sel = strsep(&ptr, "\t\r\n"); + if (sel[0] == '/') sel++; + + int fd = (sel[0] ? openat(root, sel, O_RDONLY) : root); + if (fd < 0) err(EX_NOINPUT, "%s", sel); + + struct stat stat; + error = fstat(fd, &stat); + if (error) err(EX_IOERR, "%s", sel); + if (!(stat.st_mode & (S_IFREG | S_IFDIR))) { + errx(EX_NOINPUT, "%s: Not a file or directory", sel); + } + + if (stat.st_mode & S_IFREG) { +#ifdef __FreeBSD__ + error = sendfile(fd, STDOUT_FILENO, 0, 0, NULL, NULL, 0); + if (!error) return EX_OK; +#endif + char buf[4096]; + for (ssize_t len; 0 < (len = read(fd, buf, sizeof(buf)));) { + fwrite(buf, len, 1, stdout); + } + return EX_OK; + } + + DIR *dir = fdopendir(fd); + if (!dir) err(EX_IOERR, "%s", sel); + + size_t len = 0; + size_t width = 0; + static struct dirent ents[4096]; + for (struct dirent *ent; len < 4096 && (ent = readdir(dir));) { + if (ent->d_name[0] == '.') continue; + if (ent->d_type != DT_REG && ent->d_type != DT_DIR) continue; + if (ent->d_namlen > width) width = ent->d_namlen; + ents[len++] = *ent; + } + + qsort(ents, len, sizeof(ents[0]), compar); + for (size_t i = 0; i < len; ++i) { + char mtime[26] = ""; + if (ents[i].d_type == DT_REG) { + error = fstatat(fd, ents[i].d_name, &stat, 0); + if (error) err(EX_IOERR, "%s/%s", sel, ents[i].d_name); + ctime_r(&stat.st_mtime, mtime); + mtime[24] = '\0'; + } + printf( + "%c%-*s %s\t%s%s%s\t%s\t%s\r\n", + (ents[i].d_type == DT_DIR ? '1' : '0'), + (int)width, ents[i].d_name, mtime, + sel, (sel[0] ? "/" : ""), ents[i].d_name, host, port + ); + } + + printf("i-- \t\t%s\t%s\r\n", host, port); + printf("0Served by IGP (AGPLv3)\tigp.c\ttext.causal.agency\t70\r\n"); + printf(".\r\n"); +} |