Merging OpenBSD file systems non-destructively
4 November 2025
Halloween 2019 had just passed when I installed uptimed to track the uptime
of an OpenBSD virtual machine from OpenBSD.amsterdam
that I had booked a couple of months before. According to uptimed, the
latest release of the system at the time was 6.6, and the longest uptime
clocked at 139 days using the 7.3 release. The same install has been updated
all the way to 7.8 since then, which means this particular install has gone
through twelve updates. Not bad at all!
Data Accumulates
The machine runs a few services using tools included in the base system, plus a few installed by hand or using readily available packages. The heavy lifting for public facing services goes through httpd(8). relayd(8). All quite light.
Nevertheless, it is a well known fact that disks tend to fill up; but contrary to what the reader may expect, in this case not by running a pastebin service (with anonymous uploads disabled) nor an IRC bouncer that has been saving chat history for years (clocking “only” at ~6 GiB). The real culprit has been regular use of an install that has gone through those twelve system updates.
Each release has been getting slightly bigger, and the kernel with them. Which
gets re-linked on each
boot, a process that
started failing on my install a little while back due to /usr/obj being full.
Also it did not help having used the ports tree at some point and leaving files
behind.
After some poking around doing spring autumn cleaning the issue was
gone, but I kept wondering: can we make that partition bigger without losing
data? Maybe that would prevent the same from happening again.
Growing Pains
It is not unheard of file systems which may both be resized. Most of them can easily grow, with some like XFS—my go-to file system on Linux—not only allowing to be resized while mounted, but requiring it.
OpenBSD is not precisely known for its ground breaking file system innovations. It still uses the venerable (but quite outdated) Berkeley FFS, a.k.a. Fast File System, where “Fast” stands for “Somewhat Slow”. Not even soft updates are supported anymore. Can it even resize file systems?
Well, yes it can. There is a growfs(8) tool
which has been available forever, since OpenBSD 3.4, and I did know nothing
about. It does need the file system to have been cleanly unmounted before
using growfs, then checked after resizing before mounting it back.
Luckily enough, the folks at OpenBSD.amsterdam allow connecting to the host to manage the virtual machine and connect to its serial console. We can even start the “RAM disk kernel” by hand:
vmctl stop vmNN
vmctl start -c -b /var/vmm/bsd.rd vmNN
At the end of the boot process there will be an option to start a shell, and from there all the tools available to perform disk surgery are readily available.
Making Room
Let’s look how the disk label after the automated install looks like:
# disklabel -h sd0 | tail -n12
# size offset fstype [fsize bsize cpg]
a: 1024.0M 64 4.2BSD 2048 16384 1 # /
b: 752.0M 2097216 swap
c: 51200.0M 0 unused
d: 3401.4M 3637312 4.2BSD 2048 16384 1 # /tmp
e: 5088.0M 10603328 4.2BSD 2048 16384 1 # /var
f: 2048.0M 21023552 4.2BSD 2048 16384 1 # /usr
g: 1024.0M 25217856 4.2BSD 2048 16384 1 # /usr/X11R6
h: 7176.6M 27315008 4.2BSD 2048 16384 1 # /usr/local
i: 2048.0M 42012640 4.2BSD 2048 16384 1 # /usr/src
j: 6144.0M 46206944 4.2BSD 2048 16384 1 # /usr/obj
k: 22493.3M 58789856 4.2BSD 2048 16384 1 # /home
Let’s see what can be reasonably done:
Slices
sd0fandsd0gcontain/usrand/usr/X11R6, and both have the same mount options. We can move the contents of the second into the first, deletesd0gwith the disk label editor, then change the size ofsd0fand applygrowfsonto/dev/sd0f.Slices
sd0iandsd0jcontain/usr/srcand/usr/obj, with the same mount options. We can apply the same idea: deletesd0jand add the space tosd0i. Then to keep things working create a/usr/src/.objdirectory and a/usr/objsymlink pointing to it.
This is how the disk label looks like now:
# disklabel -h sd0 | tail -n10
# size offset fstype [fsize bsize cpg]
a: 1024.0M 64 4.2BSD 2048 16384 12958 # /
b: 752.0M 2097216 swap # none
c: 51200.0M 0 unused
d: 3565.2M 3637312 4.2BSD 2048 16384 12958 # /tmp
e: 5088.0M 10938880 4.2BSD 2048 16384 12958 # /var
f: 3072.0M 21359104 4.2BSD 2048 16384 38128 # /usr
h: 7483.8M 27650560 4.2BSD 2048 16384 12958 # /usr/local
i: 6842.6M 42977344 4.2BSD 2048 16384 38128 # /usr/src
k: 23371.7M 56991008 4.2BSD 2048 16384 12958 # /home
While it should be possible to also merge slices sd0d and sd0e,
respectively /tmp and /var, I feel more comfortable with /tmp being
on its own, so this is it for now.
Was It Worth It?
Time will tell; but I expect less trouble in the future because having
/usr/src and /usr/obj in the same place added ~1 GiB of headroom.
In practice, the main point of this exercise was to poke around and make
an old dog learn a new trick. And that’s always good in my book.