diff options
Diffstat (limited to '')
27 files changed, 4163 insertions, 0 deletions
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"); +} |