Mathematics in Maxima on FreeBSD

This page is a short introduction to numeric and symbolic mathematics in Maxima on FreeBSD.

What is Maxima?

Maxima is an interactive environment for numeric and symbolic mathematics. It does differentiation, integration and solutions of equations. It plots 2 and 3 dimensional graphics.

# pkg install maxima

Plotting with Maxima.

Plot 2 sin waves, which is from -2π to 2π.

$ maxima
(%i1) plot2d( sin(x), [ x, -2*%pi, 2*%pi ] );
(%i2) quit();

Functions, solving and differentiating with Maxima.

Define 2 functions y1 and y2. Plot their intersection. Solve their intersection for x. Differentiate them.

$ maxima
(%i1) y1(x) := 2*x+1;
(%o1) y1(x) := 2 x + 1
(%i2) y2(x) := -x+4;
(%o2) y2(x) := - x + 4
(%i3) plot2d( [ y1(x), y2(x) ], [ x, -5, 5 ] );
(%i4) solve( y1(x) = y2(x), x );
(%o4) [x = 1]
(%i5) diff( [ y1(x), y2(x) ], x );
(%o5) [2, - 1]
(%i6) quit();

Define 2 functions y1 and y2, where y2 is of 2nd order. Set decimal precision. Solve their intersections and display in decimal number. Differentiate y2. Reference the result with % and evaluate it for x at -1. Plot their intersections.

$ maxima
(%i1) y1(x) := 2*x-9;
(%o1) y1(x) := 2 x - 9
(%i2) y2(x) := -2*x^2-x+15;
2
(%o2) y2(x) := - 2 x - x + 15
(%i3) fpprintprec: 2;
(%o3) 2
(%i4) float(solve( y1(x) = y2(x), x ));
(%o4) [x = - 4.3, x = 2.8]
(%i5) diff( y2(x), x );
(%o5) - 4 x - 1
(%i6) ev( %, x=-1 );
(%o6) 3
(%i7) plot2d( [ y1(x), y2(x) ], [ x, -5, 5 ] );
(%i8) quit();

Documenting input commands and output in console shell with SCRIPT.

This is the procedure for documenting input commands and output on the console, terminal or in shell to a text file with the default typescript utility SCRIPT.

What is SCRIPT?

SCRIPT is a default FreeBSD and UNIX utility, that makes a typescript of a terminal session. It works on the console, on terminals and in a shell. The typescript is stored in a text file, that serves as documentation later.

Make typescript with SCRIPT.

In this example, input and output is written to the script text file test.txt for later documentation.

$ script test.txt
Script started, output file is test.txt
$ date
Fri Sep 5 13:52:51 CEST 2025
$ freebsd-version
14.3-RELEASE-p2
$ exit
Script done, output file is test.txt

Additional input and output can be appended to the script. This avoids overwriting the initial script.

$ script -a test.txt
Script started, output file is test.txt

Review typescript from SCRIPT.

The script can later be reviewed with default utilities or even replayed by SCRIPT itself.

$ cat test.txt 
Script started on Fri Sep 5 13:52:48 2025
$ date
Fri Sep 5 13:52:51 CEST 2025
$ freebsd-version
14.3-RELEASE-p2
$ exit
Script done on Fri Sep 5 13:53:13 2025

Manual for SCRIPT.

See the manual for features, such as realtime playback and similar features.

$ man script

Changing GELI password on multiple disks

This is the procedure for changing the GEOM ELI (GELI) password on encrypted disks. This includes multiple disks in stripe or RAID with FreeBSD and ZFS-on-Root.

GELI password and encryption.

The default install of FreeBSD offers full disk encryption of one or more disks with GELI. The full disk encryption is seamless and simple to use. The user simply needs to type in the password on boot. If the password is correct, then the computer boots FreeBSD.

During the installation of FreeBSD, GELI generates a random master key for each disk. The master key is different on each disk. The master key is not written anywhere else than on the disk. The master key is used to encrypt data.

The GELI password is not used directly for encryption. The password is used to derive a key-encryption key (KEK) for each disk. This is also known as a user key. The user key is an encrypted copy of the master key for the disk. The same password is by default used for each user key. This is why the same password can decrypt several disks at boot.

The FreeBSD implementation of GELI support the use of an additional user key for each disk. This means, that 2 different passwords could be used. The user key does not have to be assigned to a password. It can also be assigned to a key file or both.

Backup before attempting to change password.

As always, it is adviced to have a backup of the file system, before attempting to change the password for an encrypted file system. It is also important to be certain, that the file system can be restored. Any mistake can brick the computer in an instant.

Choosing a new password.

The GELI password prompt does not in itself protect the system against brute force attacks with delay, rate limiting, lock-out nor similar protection. Instead, when the password is given, GELI applies a large number of hash iterations. This is by default tuned, so it takes an amount of time, that slows brute force down by several orders of magnitude.

For this reason, the password should meet a good balance between being a strong password, a human-friendly password and a keyboard safe password. It is important to know, that a simple password from a dictionary, such as “Pencil” in the famous movie War Games, is not a strong password and could be cracked by an offline GPU cluster.

The strengh of a password is measured in entropy, which is the amount of predictable possiblities. Low entropy can be hacked in minutes. High entropy could be impossible to hack with current and future computer power. I suggest an entropy of 100 bits or more for a GELI password. 100 bits of entropy is astronomically strong.

A strong password includes non-dictionary letters, numbers and special characters, that can be memorized. The password should also be safe, so it can be typed on a default keyboard layout. The following examples include non-dictionary letters, numbers and special characters, that can be memorized, while also being keyboard safe.

42Jamping!GaraffeYpD
now2+Tomarrowww-Vm3V
3p3NoBeech!4MedGaze?

Changing GELI password.

Changing the GELI password, that is used to decrypt each disk, is done by using the GELI utility.

In this example, the machine has a Root-on-ZFS file system, that is stored on 2 disk partition components in stripe and registered as ada0p3 and ada1p3. The new password is written to the file newpassword.txt. The new password is then used to generate a new user key for each disk.

# geli status
# vi newpassword.txt
# geli setkey -J newpassword.txt ada0p3
# geli setkey -J newpassword.txt ada1p3

The GELI password has now been changed. The old password can no longer be used to decrypt the file system.

Adding an extra GELI password.

I would generally not advice using several GELI passwords, but on critical systems, a GELI password change could be performed without deleting the original key, until the new key is tested to work. This is also true, if the new password also requires a keyfile.

In this example, the machine has 1 disk partition, that is registered as ada0p3. The extra password is written to the file extrapassword.txt. The extra password is then used to generate a new user key in slot 1 for the disk. The password and user key in slot 0 remains unchanged.

# geli status
# vi extrapassword.txt
# geli setkey -J extrapassword.txt -n 1 ada0p3

The extra GELI password has now been added. The extra password in slot 1, and the password in slot 0, can be used to decrypt the file system.

Warning: GELI metadata backup files can decrypt.

It is important to know, that a backup of GELI metadata for a disk, can be used to decrypt that disk. This is even true, if the password was changed on the disk. The reason is, that the master key never changes. Only the key-encrypted copy changes.

I do not recommend the use of GELI metadata backup files. Instead, I recommend, that an external file system backup is always available. There is a reason, that metadata corrupts. It usually means a new disk. And that means a fresh install and a restore.

References.

CALENDAR: The UNIX Reminder Service

As a part of my general interest in time management, I wanted to take a look into the UNIX utility CALENDAR, which is a reminder service, that is used on the console.

What is CALENDAR?

CALENDAR is a reminder service and default system utility on FreeBSD and UNIX operating systems. CALENDAR offers simple, effective and secure time management for systems administrators and users. CALENDAR is based on text files, which makes it easy to update, synchronize and backup.

$ calendar
Aug 20 Weather forecast says 11-17 °C 💧
Aug 20* Daily coffee meeting with David Lightman at 15:00
Aug 20 Patch WOPR mainframe in data center
Aug 20 Simulate first-strike on WOPR
Aug 20 Change password for listing games on WOPR

Calendar files

The CALENDAR reminder service uses one or more calendar files. Much like the good old days, when you would have one or more calendars. You might have a private one for personal events, one for recurring birthdays, a shared one for family events and a shared one for project events. Each calendar can hold tasks, recurring events.

By default, CALENDAR will search for a calendar file in the user home. If that is not found, then it will look for a calendar file in the hidden dot calendar directory and the following shared directories. The default name for a calendar is “calendar”.

~
~/.calendar
/usr/local/share/calendar
/usr/share/calendar

Creating calendar files

I recommend, that the hidden calendar directory is used. This protect the calendars from accidental deletion. In this example, a calender for one-time events and a calendar for recurring events is created.

$ mkdir -m 700 ~/.calendar
$ touch ~/.calendar/calendar
$ touch ~/.calendar/calendar.recurring
$ chmod 600 ~/.calendar/*

Calendar file syntax and format

The calendar file format is text based and consist of a list of dates and description. These are separated by a tab. Other calendars can be included.

$ nano -T 12 ~/calendar/calendar
#include <calender.recurring>
#include <calendar.freebsd>
#include <calendar.weather>
15 * Every 15th
10/2 2nd of October
July Every 1st of July
Thursday Every Thursday
2025/9/15 15th of September 2025
04/TueLast Every last Tuesday in April

Using CALENDAR

If you want to see todays calendar events, then run CALENDAR without any arguments. CALENDAR will read the calendar, and any included calendars, and output todays events. You might want to format the date to your local preference in the shell configuration or on the command line.

$ calendar
Aug 20* Daily coffee meeting with David Lightman at 15:00
Aug 20 Patch WOPR mainframe in data center

You can also output todays time and day and only list todays events, just like a good old physcal calendar. By default, the weekend is shown as one, but by using the argument -W, weekends can be treated as any other day.

$ date "+%H:%M %A %d %B %Y"; calendar -W 0
08:21 Wednesday 20 August 2025
Aug 20* Daily coffee meeting with David Lightman at 15:00
Aug 20 Patch WOPR mainframe in data center

And you can take a look at tomorrow, or any day, just like a good old physical calendar, by using the argument -t.

$ date -v+1d "+%A %d %B %Y"; calendar -t $(date -v+1d +%d.%m.%Y) -W 0
Thursday 21 August 2025
Aug 21* Daily coffee meeting with David Lightman at 15:00
Aug 21 Test Tic-Tac-Toe game on WOPR

If you want to see all events, that is planned days ahead in time, or happened days back in time, you can use the ahead argument -A and back argument -B. They can be also combined.

$ calendar -B 1 -A 3

The UNIX calendar reminder service is highly fleksible and easy to combine with other default UNIX utilities. You might even find it to be superior to modern calendars.

The manual is very well written.

$ man calendar

The FreeBSD calendar

On FreeBSD systems, there is a calendar file, that has birthdays of FreeBSD contributers.

$ cat /usr/share/calendar/calendar.freebsd | grep Percival
02/24 Colin Percival <> born in Burnaby, Canada, 1981

Todays events at login

If you would like, that each user gets todays events at the login console, then configure it in the global shell configuration. In this example, the SH is configured.

# nano /etc/profile
events=$(calendar -W 0 2> /dev/null)
if [ -n "$events" ]; then
echo "$events"
fi

Todays events via email

If you want an automatic daily email with todays events, you can configure CRON to do this. In the following example, CRON on a FreeBSD server is configured to automatically send todays events to each system user.

# nano /etc/crontab
0 8 * * * root LC_TIME=C /usr/bin/calendar -a

Todays weather forecast in calendar

If you want to include todays weather forecast, for today and the next days, you can use an online service, that generate ICS calendar files and convert them to CALENDAR format.

In this example, a special URL to the weather forecast was generated on the brilliant website of Meteomatics, which is a free service. The weather forecast comes as a file in ICS format. This file contains the weather forecast for each day through 15 days. Python is used to convert it to CALENDAR format and saved as an optional calender, that local users can include in their calendars.

# nano ~/bin/meteomatics
#!/usr/bin/env python3.11
from icalendar import Calendar
from datetime import datetime
import re
with open("/tmp/meteomat.ics", "rb") as f:
cal = Calendar.from_ical(f.read())
for component in cal.walk("vevent"):
dtstart = component.decoded("dtstart")
if isinstance(dtstart, datetime):
dt = dtstart.date()
else:
dt = dtstart
desc = component.get("description")
if not desc:
continue
temps = [int(x) for x in re.findall(r"([-]?\d+)°C", desc)]
if not temps:
continue
tmin, tmax = min(temps), max(temps)
rain = "💧" if "💧" in desc else ""
print(f"{dt.month}/{dt.day}\tWeather forecast says {tmin}-{tmax} °C {rain}")

CRON is used to update the weather forecast calendar automatically. In this example, it happens daily at 13:37.

# nano /etc/crontab
37 13 * * * root fetch -o /tmp/meteomat.ics "https://ical.meteomatics.com/calendar/Knippelsbro%202%2C%201409%20K%C3%B8benhavn%20K%2C%20Denmark/55.674679_12.587408/en/meteomat.ics" && /root/bin/meteomatics > /usr/share/calendar/calendar.weather

Synchronizing CALENDAR a cross devices with Unison

The calendar can be synchronized a cross devices and systems with a synchronization utility, such as RSYNC or Unison.

In the following example, Unison is installed and configured for synchronizing the calender via SSH to a remote UNIX system, that also has Unison installed. The options prefer, auto and batch are used, so conflicts are solved automatically. An example of a conflict could be, that local and remote calendars were changed. Unison can not merge changes.

# pkg install unison-nox11
$ nano .unison/calendar.prf
root = /home/lightman/.calendar
root = ssh://lightman@wopr//home/lightman/.calendar
prefer = /home/lightman/.calendar
auto = true
batch = true
$ unison calendar

In the following example, the events for the next 21 days are sent via SCP to a remote device, such as an SSH server on port 1337 on an Android phone, and stored as a text file. The directory on the remote device is the default, which can be configured on the SSH server on the remote device.

$ ssh-copy-id -s lightman@android
$ calendar -W 21 > ~/.calendar/calendar.txt
$ scp -P 1337 ~/.calendar/calendar.txt lightman@android:

References

Alias-based webroot method for Let’s Encrypt

This is the procedure for configuring Apache HTTP Server and Let’s Encrypt to use the alias-based webroot method for the ACME challenge by Let’s Encrypt. The benefit of this over stop of Apache is no downtime.

Be aware, that this method does not work, if some virtual hosts redirects port 80.

Create the webroot directory for the ACME challenge by Let’s Encrypt.

# mkdir -p /usr/local/www/letsencrypt/.well-known/acme-challenge/
# chmod -R 755 /usr/local/www/letsencrypt
# chown -R www:www /usr/local/www/letsencrypt

Edit the virtual host configuration file for Apache.

# nano /usr/local/etc/apache24/extra/httpd-vhosts.conf

Add the global alias to the webroot directory for the ACME challenge by Let’s Encrypt. If the Apache server is hosting HTTP as well as HTTPS sites, then a global alias is best.

Alias /.well-known/acme-challenge/ "/usr/local/www/letsencrypt/.well-known/acme-challenge/"
<Directory "/usr/local/www/letsencrypt/.well-known/acme-challenge/">
AllowOverride None
Options None
Require all granted
</Directory>

Perform a sanity check of the configuration file and restart Apache.

# service apache24 configtest
# service apache24 restart

Switch to webroot method for domains. This can be automated with a script.

#!/bin/sh
for f in /usr/local/etc/letsencrypt/renewal/*.conf; do
sed -i '' \
-e '/^authenticator =/d' \
-e '/^webroot_path =/d' \
-e '/^pre_hook *=/d' \
-e '/^post_hook *=/d' \
"$f"
echo "authenticator = webroot" >> "$f"
echo "webroot_path = /usr/local/www/letsencrypt" >> "$f"
done

Edit the crontab file.

# nano /etc/crontab

Configure automatic renew by Let’s Encrypt. In this example, every Monday at 13:13.

13 13 * * 1 root /usr/local/bin/certbot renew

Activate the change.

# service cron restart

Creating a quick backup of a directory with TAR

The is the procedure for creating a quick backup of a directory, and the files in it, by creating an archive with TAR on FreeBSD.

What is TAR on FreeBSD?

TAR is an archive utility, that can be used to archive directories and files into a single archive file. TAR is a fast, safe and native utililty on FreeBSD. TAR works much like ZIP, which is not a native utility on FreeBSD. The benefit of no compression is faster speed and less risc of data corruption.

Creating a backup of a directory with TAR.

In the following example, the BIND DNS directory /usr/local/etc/namedb, and the directories and files in it, will be archived with TAR. The file name for the archive will be named after the ISO 8601 date and the directory. This naming convension is a good practice for quick backup archives. The archive will be stored in current directory, which by default is the home directory. It is a good practice to not leave archives in the active system directories, despite being convenient.

# tar -cf 2025-06-24-namedb.tar /usr/local/etc/namedb

The directory, and the directories and files in it, has now been archived and stored in current directory.

# ls *.tar
2025-06-24-namedb.tar

Listing files in a TAR archive.

In the following example, the contents of the archive is listed to standard out. The archived directories and files are relative to the current directory by default.

# tar -tf 2025-06-24-namedb.tar
usr/local/etc/namedb/
usr/local/etc/namedb/named.conf

Reading (extracting) a file in a TAR archive.

Reading a file in a TAR archive, which means extracting it to default out, can be used for inspecting without extracting to file system first. In the following example, the named.conf is extracted from the archive to standard out. This will show the contents of the file and the output can be piped to utilities, such as GREP, MORE, LESS and TAIL.

# tar -xOf 2025-06-24-namedb.tar usr/local/etc/namedb/named.conf

Extracting from a TAR archive.

In the following example, the directory, and its directories and files, is extracted from the archive. The directory will be extracted to the current directory by default.

# tar -xf 2025-06-24-namedb.tar

In the following example, the directory, and its directories and files, is extracted from the archive to an optional temporary directory.

# tar -xf 2025-06-24-namedb.tar -C tmp/

In the following example, the directory, and its directories and files, is extracted from the archive to the root directory. The will restore the backup to original location. Warning! This will overwrite the original files.

# tar -xf 2025-06-24-namedb.tar -C /

Encrypting a TAR archive with GPG.

The TAR archive, or any file, can be encrypted with a password by using GPG. In the following example, the an encrypted copy of the TAR archive is created with GPG.

# tar -cf - /usr/local/etc/namedb | gpg --symmetric --pinentry-mode loopback > 2025-06-24-namedb.tar.gpg"
Enter passphrase: Pencil

This will create an encrypted copy, that can only be decrypted with the same password. No GPG keys will be needed.

# ls *.gpg
2025-06-24-namedb.tar.gpg

The encrypted TAR archive can be decrypted with GPG and extracted with TAR.

# gpg --decrypt 2025-06-24-namedb.tar.gpg | tar -xf -
Please enter the passphrase for decryption.
Passphrase: Pencil

References.

Safe erasure with zero padding on FreeBSD

What is zero padding?

Zero padding or random padding is the practice of secure wiping of removable storage devices, such as USB sticks, SD cards and external SSDs, so they can be safely re-used within an organization. Zero padding or random padding fills the storage device with zeroes or random bytes. This over-writes existing documents and files on the storage device.

$ dd if=/dev/zero bs=1 count=16 status=none | hexdump -C
00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
$ dd if=/dev/urandom bs=1 count=16 status=none | hexdump -C
00000000 24 e0 e2 a0 69 90 e4 8a 9a d4 7d b9 95 d3 60 c4 |$...i.....}...`.|

If the storage devices was just quickly re-formatted, then documents and files would be recoverable by forensic software or basic operating system utilities, because modern storage devices distribute data to new places on the storage device to ensure optimal life time.

# dd if=/dev/da0 bs=1 skip=1048576 count=512 status=none | hexdump -C
00000000 4e 1e 40 db 85 ab 9f 63 48 9d 51 e2 8f ef 6f a8 |.Pass.is.Pencil.|

Modern SSDs and larger storage devices might require specialized commands or vendor utilities. In such cases, or when the storage device was used to store highly classified documents, consider physically schredding or destroying the storage device instead.

Identifying storage device to be zero padded.

Identify the storage device. In this example, an external 32 GB USB 3.0 stick was attached and identified as da0.

$ dmesg
da0: <Generic Flash Disk 8.13> Removable Direct Access SPC-4 SCSI device
da0: Serial Number 42011337510013
da0: 40.000MB/s transfers
da0: 29600MB (60620800 512 byte sectors)
da0: quirks=0x2<NO_6_BYTE>

Unmounting file systems on storage device.

Make sure, that none of its file systems are mounted.

# umount /dev/da0*

Zero padding a storage device.

Zero padding will fill the entire storage device with zeroes and thereby defeat any attempts of casual recovery of documents of files on it. If you require an even more secure erasure, you can use random padding instead.

# dd if=/dev/zero of=/dev/da0 bs=1M status=progress
dd: /dev/da0: end of device 29 GiB) transferred 2207.307s, 14 MB/s
29601+0 records in
29600+0 records out

In this example, the zero padding of a 32 GB USB 3.0 stick took 37 min. Low write speeds are not uncommon for lower quality USB sticks, despite labeled as USB 3.0.

Random padding a storage device.

Random padding will fill the entire storage device with random data and thereby not only defeat any attempts of casual recovery of documents of files on it, but also provide stronger protection by preventing pattern detection from hardware residue. Random padding takes slightly longer time to complete.

# dd if=/dev/urandom of=/dev/da0 bs=1M status=progress
dd: /dev/da0: end of device 29 GiB) transferred 2450.009s, 13 MB/s bytes (25 GB, 24 GiB) transferred 2008.177s, 13 MB/s
29601+0 records in
29600+0 records out
31037849600 bytes transferred in 2450.640376 secs (12665200 bytes/sec)

In this example, the random padding of a 32 GB USB 3.0 stick took 41 min. This is only 4 min longer than zero padding.

If the storage medium is of magnetic type or you require an even more secure erase, you can perform the random padding several times. However, multiple passes on modern storage media has dimishing returns. In this example, the storage device is random padded 3 times. This is, however, not necessary on modern drivess.

# for i in 1 2 3; do dd if=/dev/urandom of=/dev/da0 bs=1M status=progress; done

Formatting an USB stick with EXFAT.

The storage device has now been safely erased and it can be formatted with a new file system, so it can be used again. In this example, the USB stick is formatted with the well-supported EXFAT file system. The EXFAT file system willl not have a partition scheme. This ensures compatibility.

# mkexfatfs /dev/da0
mkexfatfs 1.4.0
Creating... done.
Flushing... done.
File system created successfully.

Confirm, that it works.

# kldload fusefs
# mount.exfat /dev/da0 /mnt
FUSE exfat 1.4.0 (libfuse2)
# umount /mnt

References.

Encrypting files with GPG

If you will be transferring a secret file from one file system to another, you might want to protect the file from theft or exposure during the transfer. This could be an encryption key file for decrypting a multi-challenge encrypted file system or similar secret file, that are to be transferred on an unencrypted storage medium, such as an USB stick with EXFAT, NTFS or MSDOS file system on it. If you will be using the GNU Privacy Guard (GPG) without GPG key pairs, then you can encrypt with a symmetric cipher by specifying a pass phrase.

Encrypting with symmetric cipher

If you will be using GPG without GPG key pairs, then you can encrypt with a symmetric cipher by specifying a pass phrase. In cryptography, this means, that the same pass phrase will be used to encrypt and decrypt. The user, who will encrypt the file, will choose a pass phrase, which will be used to write a new encrypted file, that contain the secret file. The pass phrase should be as long and non-dictionairy complex as possible. The default cipher is AES-128.

$ gpg --symmetric keyfile.bin

The encrypted file can now be transferred safely.

$ ls keyfile.bin.gpg

You can test, that the file can be decrypted and compare it to the original file.

$ gpg --yes --output keyfile.bin.decrypted --decrypt keyfile.bin.gpg
$ cmp -s keyfile.bin keyfile.bin.decrypted && echo "Pass" || echo "Fail"

The encrypted file can be decrypted on the target file system by anyone, who know the pass phrase, that was used to encrypt the file. It is not required, that the target user has a GPG key pair.

$ gpg --yes --output keyfile.bin --decrypt keyfile.bin.gpg

The decrypted secret file can now be used.

$ ls keyfile.bin

References

Mounting ZFS failed with error 6 during FreeBSD boot.

On a machine with FreeBSD 13.5, that was originally prepared with GELI encryption and ZFS file system by the FreeBSD 11 installer, the following error message appears during the automatic FreeBSD boot proces, before it stops at the moutroot prompt.

Mounting from zfs:zroot/ROOT/default failed with error 6.
Loader variables:
vfs.root.mountfrom=zfs:zroot/ROOT/default
Manual root filesystem specification:
mountroot>

Trying to mount gives label error?

mountroot> zfs: zroot/ROOT/default ?
Sun Solaris label '' ?

The problem occurred after a second, and never used before, SSD was added to the machine with the plan to provide extra storage to the existing ZFS storage pool. The secondary SSD was prepared with GELI and ZFS. The machine was then rebooted.

If the machine is booted from a FreeBSD USB memory stick, then GELI can attach each SSD, if ZFS imports the boot partition with the GELI encryption keyfile first.

POSIX error code 6? Missing device?

# cat loader.conf 
geli_ada0p4_keyfile0_load="YES"
geli_ada0p4_keyfile0_type="ada0p4:geli_keyfile0"
geli_ada0p4_keyfile0_name="/boot/encryption.key"
aesni_load="YES"
geom_eli_load="YES"
geom_eli_passphrase_prompt="YES"
vfs.root.mountfrom="zfs:zroot/ROOT/default"
kern.geom.label.gptid.enable="0"
zpool_cache_load="YES"
zpool_cache_type="/boot/zfs/zpool.cache"
zpool_cache_name="/boot/zfs/zpool.cache"
zfs_load="YES"
pf_load="YES"
pflog_load="YES"

Setting boot flag.

# kldload geom_eli
# kldload zfs
# zpool import -R /mnt bootpool
# geli attach -k /mnt/boot/encryption.key ada0p4
GEOM_ELI: Device ada0p4.eli created.
# geli attach ada1p1
# zpool export bootpool
GEOM_ELI: Device ada1p1.eli created.
# geli info ada0p4.eli
Flags: BOOT
# geli info ada1p1
Flags: AUTORESIZE
# geli configure -b ada1p1
# geli info ada1p1
Flags: BOOT, AUTORESIZE
# geli detach ..
# geli detach ..
# reboot

The solved the issue about the error 6. However, further action was needed, as it stopped at mounting filesystems.

ZFS cache.

Import the ZFS root storage pool. This ensures, that ZFS can see all the devices, and, that the pool is cleanly imported.

# kbdcontrol -l /usr/share/syscons/keymaps/danish.iso.kbd
# kldload geom_eli
# kldload zfs
# mdmfs -s 100m md0 /mnt
# zpool import -f -R /mnt bootpool
# geli attach -k /mnt/boot/encryption.key ada0p4
GEOM_ELI: Device ada0p4.eli created.
# geli attach ada1p1
GEOM_ELI: Device ada1p1.eli created.
# zpool import
  pool: zroot
 state: ONLINE
config:
  zroot         ONLINE
    ada0p4.eli  ONLINE
    ada1p1.eli  ONLINE
# zpool import -f -R /mnt zpool
# zpool status
  pool: zroot
 state: ONLINE
errors: No known data errors

Replace the symbolic link with a directory, that can be used for mounting the boot partition. Export and import the ZFS storage pools, but this time, so the root is complete with the boot partition, where the ZFS cache is stored. Create a backup of the old ZFS cache files and remove them. Generate a new ZFS cache file.

# file /mnt/boot
/mnt/boot: broken symbolic link to /bootpool/pool
# rm /mnt/boot
# mkdir -p /mnt/boot
# zpool export bootpool
# zpool export zroot
# zpool import -f -R /mnt zpool
# zpool import -f -R /mnt/boot bootpool
# find /mnt -type f -name 'zpool.cache'
# cp /mnt/boot/boot/zfs/zpool.cache /mnt/boot/boot/zfs/zpool.cache.bak
# rm /mnt/boot/boot/zfs/zpool.cache
# cp /mnt/etc/zfs/zpool.cache /mnt/etc/zfs/zpool.cache.bak
# rm /mnt/etc/zfs/zpool.cache
# zpool set cachefile=/mnt/boot/boot/zfs/zpool.cache zroot
# zpool set comment="Test" zroot
# find /mnt -type f -name 'zpool.cache'
# zpool export bootpool
# rmdir /mnt/boot
# ln -s /bootpool/boot /mnt/boot ?
# zpool export zroot
# geli detach ada1p1
# geli detach ada0p4
# reboot

This caused bootpool to not get imported. When imported, it mounted as root and bricked.

Boot not mounted.

If /boot is a symbolic link, then delete it.

# ls -ld /boot
/boot -> /bootpool/boot
# zfs get mountpoint bootpool
# zpool set cachefile=/boot/zfs/zpool.cache zroot
# find / -type f -name 'zpool.cache' -delete

X?

Could this be caused, if GELI on the second SSD was configured with same passphrase as primary SSD, but without the requirement for the keyfile component? Could this be caused by the ZFS cache?

Ideas?

  • Suspect that the GELI boot flag are required on secondary SSD for the boot loader to decrypt it. The boot flag might be required for the boot loader to decrypt partitions, that are required for mouting the root.
  • Suspect that ZFS cache causes the error. Refresh it? Delete it?
  • Suspect that incorrect or lack of ZFS labels causes the error. Clear or set?
  • Un-merge the secondary SSD from the ZFS storage pool and attempt to revert to previous working state of the machine.
  • Suspect that lack of GELI keyfile requirement for secondary SSD causes the error. Enable the use of the GELI keyfile for secondary SSD, so both SSDs can be attached with the same user passphrase and the same keyfile by the FreeBSD automatic boot proces.
  • Suspect that lack of GELI keyfile requirement for secondary SSD causes the error. Disable the use of the GELI keyfile for the primary SSD, so both SSDs can be attached with the same user passphrase by the boot loader.

Any ideas?

Conclusion.

Do not add extra storage devices to a FreeBSD machine, that use GELI and ZFS, by simply merging it into the existing pool. You will end up with a bricked system, that is full of catch 22 type critical errors. This is caused by, how the ZFS cache works, and, how the boot partition and root partitions are inter-connected through a symbolic link, that blocks mounting. The way to add extra storage devices is a fresh re-install of the machine. You get the current FreeBSD, which include file system related changes, such as GELI and ZFS changes. You also save yourself a lot of time.

References.

English Language and Danish Regional Setting in FreeBSD

This is the procedure for configuring localization (locale) for English language and Danish regional setting in FreeBSD. Tested on FreeBSD 14.2 with XFCE 4.20 on 2025-03-31.

Introduction.

I have tested the official FreeBSD handbook procedure. I have tested countless guides and advice from forums, discussions and website. They have all failed to meet my basic requirements: English language and Danish regional setting in system console, window manager and applications. See my test below. I wanted Danish ISO 8601 standard format for date, time and numbers. This is also known as YYYY-MM-DD HH:MM:SS. I did not want to see US imperial format for date, time and numbers. I suddently had an idea of hacking an existing English locale and simply linking to a Danish for regional setting. It turned out to work perfect.

What is a locale setting?

A locale setting is made up of the language, the regional setting and the character encoding standard. As an example, en_DK.UTF-8 locale would be for user, who prefer English language in a Danish regional setting with the UTF-8 character encoding standard.

Locale specific information can be proveded with the locale utility. It is important to know, that the utility respect the PATH_LOCALE environment variable. If this is set, then this is used instead of the default locale directory.

# locale -a | grep DK
da_DK.ISO8859-1
da_DK.ISO8859-15
da_DK.UTF-8

Create a new locale.

Create a new locale definition, that is based on English. This new locale definition will be English language with Danish regional setting and UTF-8 character encoding standard.

# cd /usr/share/locale/
# mkdir -p en_DK.UTF-8
# cp -r en_GB.UTF-8/* en_DK.UTF-8/

Link to Danish regional setting.

For time format, numeric format and monetary format, create symbolic links to the Danish locale definition.

# cd en_DK.UTF-8/
# ls
LC_COLLATE LC_CTYPE LC_MESSAGES LC_MONETARY LC_NUMERIC LC_TIME
# ln -sf ../da_DK.UTF-8/LC_TIME .
# ln -sf ../da_DK.UTF-8/LC_NUMERIC .
# ln -sf ../da_DK.UTF-8/LC_MONETARY .

Confirm new locale.

# locale -a | grep DK
da_DK.ISO8859-1
da_DK.ISO8859-15
da_DK.UTF-8
en_DK.UTF-8

Configure FreeBSD login class for English language and Danish regional setting locale.

Configure the FreeBSD login class for English language, Danish regional setting and UTF-8 character encoding standard. This will work for system console, window manager and applications without further configuration. Examples are XFCE and Thunderbird Mail.

# nano /etc/login.conf
default:\
:passwd_format=sha512:\
:copyright=/etc/COPYRIGHT:\
:welcome=/var/run/motd:\
:setenv=BLOCKSIZE=K:\
:mail=/var/mail/$:\
:path=/sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin ~/bin:\
:nologin=/var/run/nologin:\
:cputime=unlimited:\
:datasize=unlimited:\
:stacksize=unlimited:\
:memorylocked=64K:\
:memoryuse=unlimited:\
:filesize=unlimited:\
:coredumpsize=unlimited:\
:openfiles=unlimited:\
:maxproc=unlimited:\
:sbsize=unlimited:\
:vmemoryuse=unlimited:\
:swapuse=unlimited:\
:pseudoterminals=unlimited:\
:kqueues=unlimited:\
:umtxp=unlimited:\
:pipebuf=unlimited:\
:priority=0:\
:ignoretime@:\
:umask=022:\
:charset=UTF-8:\
:lang=en_DK.UTF-8:

Apply the changes by running the capability database utility.

# cap_mkdb /etc/login.conf

Test English language and Danish regional settings.

You can now log out of all virtual terminals and system consoles and test, that the new locale works.

  • English language in FreeBSD system console.
  • Danish special characters input in FreeBSD system console.
  • Danish special characters output in FreeBSD system console.
  • Danish ISO 8601 date and time format in FreeBSD system console.
  • Danish number format in FreeBSD system console.
  • English language in XFCE window manager menu, panels and dialogs.
  • Danish ISO 8601 date and time format in XFCE window manager.
  • English language in Thunderbird Mail.
  • Danish ISO 8601 date and time format in Thunderbird Mail.
  • Danish number format in Thunderbird Mail.

Backup and restore the new locale.

Create a backup of the new locale. This ensures, that it can be restored, if it should get deleted. It can also be used to install on another host.

# tar -cvpzf ~/en_DK.UTF-8.tar.gz -C /usr/share/locale en_DK.UTF-8
a en_DK.UTF-8
a en_DK.UTF-8/LC_COLLATE
a en_DK.UTF-8/LC_TIME
a en_DK.UTF-8/LC_CTYPE
a en_DK.UTF-8/LC_MONETARY
a en_DK.UTF-8/LC_NUMERIC
a en_DK.UTF-8/LC_MESSAGES

The content can be confirmed.

# tar -tvf ~/en_DK.UTF-8.tar.gz

The locale setting can be restored.

# tar -xvpzf ~/en_DK.UTF-8.tar.gz -C /usr/share/locale
x en_DK.UTF-8/
x en_DK.UTF-8/LC_COLLATE
x en_DK.UTF-8/LC_TIME
x en_DK.UTF-8/LC_CTYPE
x en_DK.UTF-8/LC_MONETARY
x en_DK.UTF-8/LC_NUMERIC
x en_DK.UTF-8/LC_MESSAGES

Issue: Port misc/locale-en_DK not working.

I tested the official port and package misc/locale-en_DK. This failed Danish special characters input in FreeBSD system console. I even spent extra time testing custom environment variables.

# pkg install locale-en_DK
Message from locale-en_DK-0.1.1:
In order to set the en_DK.UTF-8 locale for the login shell of an single
user add the following configuration to ~/.login_conf:
me:\
:charset=UTF-8:\
:lang=en_DK.UTF-8:\
:setenv=PATH_LOCALE=/usr/local/share/locale:
More information about the process of configuring login class methods in
available in the handbook.
More information about the PATH_LOCALE environment variable is available in the
locale(1) manual page.

References.