2024-12-31
My unusual X desktop wasn't made 'from scratch' in a conventional sense
There are people out there who set up unusual (Unix) environments for themselves from scratch; for example, Mike Hoye recently wrote Idiosyncra. While I have an unusual desktop, I haven't built it from scratch in quite the same way that Mike Hoye and other people have; instead I've wound up with my desktop through a rather easier process.
It would be technically accurate to say that my current desktop environment has been built up gradually over time (including over the time I've been writing Wandering Thoughts, such as my addition of dmenu). But this isn't really how it happened, in that I didn't start from a normal desktop and slowly change it into my current one. The real story is that the core of my desktop dates from the days when everyone's X desktops looked like mine does. Technically there were what we would call full desktops back in those days, if you had licensed the necessary software from your Unix vendor and chose to run it, but hardware was sufficiently slow back then that people at universities almost always chose to run more lightweight environments (especially since they were often already using the inexpensive and slow versions of workstations).
(Depending on how much work your local university system administrators had done, your new Unix account might start out with the Unix vendor's X setup, or it could start out with what X11R<whatever> defaulted to when built from source, or it might be some locally customized setup. In all cases you often were left to learn about the local tastes in X desktops and how to improve yours from people around you.)
To show how far back this goes (which is to say how little of it has been built 'from scratch' recently), my 1996 SGI Indy desktop has much of the look and the behavior of my current desktop, and its look and behavior wasn't new then; it was an evolution of my desktop from earlier Unix workstations. When I started using Linux, I migrated my Indy X environment to my new (and better) x86 hardware, and then as Linux has evolved and added more and more things you have to run to have a usable desktop with things like volume control, your SSH agent, and automatically mounted removable media, I've added them piece by piece (and sometimes updated them as how you do this keeps changing).
(At some point I moved from twm as my window manager to fvwm, but that was merely redoing my twm configuration in fvwm, not designing a new configuration from scratch.)
I wouldn't want to start from scratch today to create a new custom desktop environment; it would be a lot of work (and the one time I looked at it I wound up giving up). Someday I will have to move from X, fvwm, dmenu, and so on to some sort of Wayland based environment, but even when I do I expect to make the result as similar to my current X setup as I can, rather than starting from a clean sheet design. I know what I want because I'm very used to my current environment and I've been using variants of it for a very long time now.
(This entry was sparked by Ian Z aka nobrowser's comment on my entry from yesterday.)
PS: Part of the long lineage and longevity of my X desktop is that I've been lucky and determined enough to use Unix and X continuously at work, and for a long time at home as well. So I've never had a time when I moved away from X on my desktop(s) and then had to come back to reconstruct an environment and catch it up to date.
PPS: This is one of the roots of my xdm heresy, where
my desktops boot into a text console and I log in there to manually
start X with a personal script that's a derivative of the ancient
startx
command.
2024-12-28
In an unconfigured Vim, I want to do ':set paste' right away
Recently I wound up using a FreeBSD machine, where I promptly installed vim for my traditional reason. When I started modifying some files, I had contents to paste in from another xterm window, so I tapped my middle mouse button while in insert mode (ie, I did the standard xterm 'paste text' thing). You may imagine the 'this is my face' meme when what vim inserted was the last thing I'd deleted in vim on that FreeBSD machine, instead of my X text selection.
For my future use, the cure for this is ':set paste', which turns off
basically all of vim's special handling of pasted text. I've
traditionally used this to override things like vim auto-indenting
or auto-commenting the text I'm pasting in, but it also turns off
vim's special mouse handling, which is generally
active in terminal windows, including over SSH.
(The defaults for ':set mouse' seem to vary from system to system
and probably vim build to vim build. For whatever reason, this
FreeBSD system and its vim defaulted to 'mouse=a', ie special mouse
handling was active all the time. I've run into mouse handling
limits in vim before, although things
may have changed since then.)
In theory, as covered in Vim's X11 selection mechanism, I might be
able to paste from another xterm (or whatever) using "*p (to use
the '"*' register, which is the primary selection or the cut
buffer if there's no primary selection).
In practice I think this only works under limited circumstances
(although I'm not sure what they are) and the Vim manual itself
tells you to get used to using Shift with your middle mouse button.
I would rather set paste mode, because that gets everything; a vim
that has the mouse active probably has other things I don't want
turned on too.
(Some day I'll put together a complete but minimal collection of vim settings to disable everything I want disabled, but that day isn't today.)
PS: If I'm reading various things correctly, I think vim has to be built with the 'xterm_clipboard' option in order to pull out selection information from xterm. Xterm itself must have 'Window Ops' allowed, which is not a normal setting; with this turned on, vim (or any other program) can use the selection manipulation escape sequences that xterm documents in "Operating System Commands". These escape sequences don't require that vim have direct access to your X display, so they can be used over plain SSH connections. Support for these escape sequences is probably available in other terminal emulators too, and these terminal emulators may have them always enabled.
(Note that access to your selection is a potential security risk, which is probably part of why xterm doesn't allow it by default.)
2024-12-26
WireGuard on OpenBSD just works (at least as a VPN server)
A year or so ago I mentioned that I'd set up WireGuard on an Android and an iOS device in a straightforward VPN configuration. What I didn't mention in that entry is that the other end of the VPN was not on a Linux machine, but on one of our OpenBSD VPN servers. At the time it was running whatever was the then-current OpenBSD version, and today it's running OpenBSD 7.6, which is the current version at the moment. Over that time (and before it, since the smartphones weren't its first WireGuard clients), WireGuard on OpenBSD has been trouble free and has just worked.
In our configuration, OpenBSD WireGuard requires installing the 'wireguard-tools' package, setting up an /etc/wireguard/wg0.conf (perhaps plus additional files for generated keys), and creating an appropriate /etc/hostname.wg0. I believe that all of these are covered as part of the standard OpenBSD documentation for setting up WireGuard. For this VPN server I allocated a /24 inside the RFC 1918 range we use for VPN service to be used for WireGuard, since I don't expect too many clients on this server. The server NATs WireGuard connections just as it NATs connections from the other VPNs it supports, which requires nothing special for WireGuard in its /etc/pf.conf.
(I did have to remember to allow incoming traffic to the WireGuard UDP port. For this server, we allow WireGuard clients to send traffic to each other through the VPN server if they really want to, but in another one we might want to restrict that with additional pf rules.)
Everything I'd expect to work does work, both in terms of the
WireGuard tools (I believe the information 'wg' prints is identical
between Linux and OpenBSD, for example) and for basic system metrics
(as read out by, for example, the OpenBSD version of the Prometheus
host agent, which has overall metrics for the 'wg0' interface). If
we wanted per-client statistics, I believe we could probably get
them through this third party WireGuard Prometheus exporter, which uses an
underlying package to talk to WireGuard that does apparently work
on OpenBSD (although this particular exporter can potentially have
label cardinality issues), or generate them ourselves by parsing
'wg' output (likely from 'wg show all dump').
This particular OpenBSD VPN server is sufficiently low usage that I haven't tried to measure either the possible bandwidth we can achieve with WireGuard or the CPU usage of WireGuard. Historically, neither are particularly critical for our VPNs in general, which have generally not been capable of particularly high bandwidth (with either OpenVPN or L2TP, our two general usage VPN types so far; our WireGuard VPN is for system staff only).
(In an ideal world, none of this should count as surprising. In this world, I like to note when things that are a bit out of the mainstream just work for me, with a straightforward setup process and trouble free operation.)
2024-12-08
Unix's buffered IO in assembly and in C
Recently on the Fediverse, I said something related to Unix's pre-V7 situation with buffered IO:
(I think the V1 approach is right for an assembly based minimal OS, while the stdio approach kind of wants malloc() and friends.)
The V1 approach, as documented in its putc.3 and getw.3 manual pages, is that the caller to the buffered IO routines supplies the data area used for buffering, and the library functions merely initialize it and later use it. How you get the data area is up to you and your program; you might, for example, simply have a static block of memory in your BSS segment. You can dynamically allocate this area if you want to, but you don't have to. The V2 and later putchar have a similar approach but this time they contain a static buffer area and you just have to do a bit of initialization (possibly putchar was in V1 too, I don't know for sure).
Stdio of course has a completely different API. In stdio, you don't
provide the data area; instead, stdio provides you an opaque reference
(a 'FILE *') to the information and buffers it maintains
internally. This is an interface that definitely wants some degree
of dynamic memory allocation, for example for the actual buffers
themselves, and in modern usage most of the FILE objects will
be dynamically allocated too.
(The V7 stdio implementation had a fixed set of FILE structs
and so would error out if you used too many of them.
However, it did use malloc() for the buffer associated with them,
in filbuf.c
and flsbuf.c.)
You can certainly do dynamic memory allocation in assembly, but I think it's much more natural in C, and certainly the C standard library is more heavyweight than the relatively small and minimal assembly language stuff early Unix programs (written in assembly) seem to have required. So I think it makes a lot of sense that Unix started with a buffering approach where the caller supplies the buffer (and probably doesn't dynamically allocate it), then moved to one where the library does at least some allocation and supplies the buffer (and other data) itself.
2024-12-05
Buffered IO in Unix before V7 introduced stdio
I recently read Julia Evans' Why pipes sometimes get "stuck":
buffering.
Part of the reason is that almost every Unix program does some
amount of buffering for what it prints (or writes) to standard
output and standard error. For C programs, this buffering is built
into the standard library, specifically into stdio, which includes
familiar functions like printf(). Stdio is one of the many things
that appeared first in Research Unix V7.
This might leave you wondering if this sort of IO was buffered in
earlier versions of Research Unix and if it was, how it was done.
The very earliest version of Research Unix is V1, and in V1 there is putc.3 (at that point entirely about assembly, since C was yet to come). This set of routines allows you to set up and then use a 'struct' to implement IO buffering for output. There is a similar set of buffered functions for input, in getw.3, and I believe the memory blocks the two sets of functions use are compatible with each other. The V1 manual pages note it as a bug that the buffer wasn't 512 bytes, but also notes that several programs would break if the size was changed; the buffer size will be increased to 512 bytes by V3.
In V2, I believe we still have putc and getw, but we see the first appearance of another approach, in putchr.s. This implements putchar(), which is used by printf() and which (from later evidence) uses an internal buffer (under some circumstances) that has to be explicitly flush()'d by programs. In V3, there's manual pages for putc.3 and getc.3 that are very similar to the V1 versions, which is why I expect these were there in V2 as well. In V4, we have manual pages for both putc.3 (plus getc.3) and putch[a]r.3, and there is also a getch[a]r.3 that's the input version of putchar(). Since we have a V4 manual page for putchar(), we can finally see the somewhat tangled way it works, rather than having to read the PDP-11 assembly. I don't have links to V5 manuals, but the V5 library source says that we still have both approaches to buffered IO.
(If you want to see how the putchar() approach was used, you can look at, for example, the V6 grep.c, which starts out with the 'fout = dup(1);' that the manual page suggests for buffered putchar() usage, and then periodically calls flush().)
In V6, there is a third approach that was added, in /usr/source/iolib, although I don't know if any programs used it. Iolib has a global array of structs, that were statically associated with a limited number of low-numbered file descriptors; an iolib function such as cflush() would be passed a file descriptor and use that to look up the corresponding struct. One innovation iolib implicitly adds is that its copen() effectively 'allocates' the struct for you, in contrast to putc() and getc(), where you supply the memory area and fopen()/fcreate() merely initialize it with the correct information.
Finally V7 introduces stdio and sorts all of this out, at the cost
of some code changes. There's still getc() and
putc(), but
now they take a FILE *, instead of their own structure, and you
get the FILE * from things like fopen() instead of supplying
it yourself and having a stdio function initialize it. Putchar()
(and getchar()) still exist but are now redone to work with stdio
buffering instead of their own buffering, and 'flush()' has become
fflush() and
takes an explicit FILE * argument instead of implicitly flushing
putchar()'s buffer, and generally it's not necessary any more. The
V7 grep.c still
uses printf(), but now it doesn't explicitly flush anything by
calling fflush(); it just trusts in stdio.