PulseAudio with changing machine-id
I've been using Devuan GNU/Linux on several of my machines in the recent months. On one of these machines, I have a somewhat elaborate audio setup with multiple input and output devices plugged in. Not long after setting up Devuan unstable, I noticed PulseAudio ('pulse' for short from here on) would forget my configurations after each reboot. I would set the default input and output devices and their volumes to my liking, only for pulse to forget it all on the next boot. Interestingly, I'd never run into this issue on Debian or its other derivatives like Trisquel over the years.
To try and figure out what was going on, I set out by checking
if I could spot any suspicious-looking configuration values for
pulse itself or any of its modules. From a cursory look, the
/etc/pulse/default.pa global configuration file looked
familiar and reasonable enough. Next, I thought I would check if
Devuan's pulseaudio package was forked from Debian
(in case their default configuration had any problematic bits that
Debian's didn't), or if they shipped Debian's as-is. It was the
latter: Devuan was shipping Debian's pulseaudio directly,
with no Devuan-specific changes. This lead me to think that this
problematic behaviour of pulse may be due to a bad interaction with
another part of the system, one where Devuan does differ from
Debian.
At this point, without any concrete clues to go on by, I decided
to check whether pulse was saving my configurations at all, and
if so, what was happening on each boot that caused it to "forget"
them. So I checked the ~/.config/pulse user
configurations directory and the "what" became somewhat apparent:
there were multiple sets of the pulse configuration and database
files, but with different prefixes. Something like this:
$ ls ~/.config/pulse/ 19b7ae2816d8f92780787a62680a53c8-card-database.tdb 19b7ae2816d8f92780787a62680a53c8-default-sink 19b7ae2816d8f92780787a62680a53c8-default-source 19b7ae2816d8f92780787a62680a53c8-device-volumes.tdb 19b7ae2816d8f92780787a62680a53c8-stream-volumes.tdb bbe80bf75c4b6ac9a09693be6bf36645-card-database.tdb bbe80bf75c4b6ac9a09693be6bf36645-default-sink bbe80bf75c4b6ac9a09693be6bf36645-default-source bbe80bf75c4b6ac9a09693be6bf36645-device-volumes.tdb bbe80bf75c4b6ac9a09693be6bf36645-stream-volumes.tdb cookie
I tried rebooting the machine once again, and sure enough, there was a new set of files with a new prefix, and pulse no longer touched the previous ones. So, whatever these random-looking 32-character hex numbers were, a new one was generated on each boot, and because pulse used them in its per-user configuration and database file names, it could not find the previous files at the next boot.
The next logical question was: just what are these random-looking numbers, and where does pulse get them from? So I went digging in pulse's sources. I started by searching the pulse sources for the file name of one of its databases:
$ grep -rn stream-volumes
src/modules/module-stream-restore.c:2386: if (!(u->database = pa_database_open(state_path, "stream-volumes", true, true))) {
Looking at the definition of
pa_database_open:
pa_database* pa_database_open(const char *path, const char *fn, bool prependmid, bool for_write) {
const char *filename_suffix = pa_database_get_filename_suffix();
/* [... omitted for brevity ...] */
if (prependmid && !(machine_id = pa_machine_id())) {
return NULL;
}
/* Database file name starts with ${machine_id}-${fn} */
if (machine_id)
filename_prefix = pa_sprintf_malloc("%s-%s", machine_id, fn);
else
filename_prefix = pa_xstrdup(fn);
/* [... omitted for brevity ...] */
}
And
pa_machine_id:
char *pa_machine_id(void) {
FILE *f;
/* [... omitted for brevity ...] */
if ((f = pa_fopen_cloexec(PA_MACHINE_ID, "r")) ||
(f = pa_fopen_cloexec(PA_MACHINE_ID_FALLBACK, "r")) ||
#if !defined(OS_IS_WIN32)
(f = pa_fopen_cloexec("/etc/machine-id", "r")) ||
(f = pa_fopen_cloexec("/var/lib/dbus/machine-id", "r"))
#else
false
#endif
) {
/* [... omitted for brevity ...] */
}
/* [... omitted for brevity ...] */
}
AHA!! So this is where the prefix for the above database files
come from; it's the machine-id.note 1 We see that pulse tries to read it
from /etc/machine-id, which is absent in Devuan, and if
that fails it tries /var/lib/dbus/machine-id next, which
does exist in Devuan.
Having found what the file name prefix is and where it comes from, we can try to work around the issue. What came to my mind was to store the current machine-id in some file, and on the next boot use it to find and rename the right set of pulse configuration and database files to use the newly generated machine-id.
So, I wrote a tiny POSIX shell script to be run during startup
that does exactly that. It expects the previous machine-id in
$XDG_CACHE_HOME/tmp-prevmid, and uses it to match and
rename the files in $XDG_CONFIG_HOME/pulse that have that
prefix, so that pulse could find its own configuration and database
files again.note
2 The script does the same for libcanberra's event sound
cache database as well (another project with the same author as
pulse).
Here is the script, which I named pacify (PAcify),
in its entirety:
#!/bin/sh
XDG_CACHE_HOME="${XDG_CACHE_HOME=:-$HOME/.cache}"
XDG_CONFIG_HOME="${XDG_CONFIG_HOME=:-$HOME/.config}"
cur_id_path=/var/lib/dbus/machine-id
prev_id_path="$XDG_CACHE_HOME/tmp-prevmid"
cur_id="$(cat $cur_id_path)"
prev_id="$(cat $prev_id_path)"
if [ -d "$XDG_CONFIG_HOME/pulse" ] && [ -s "$prev_id_path" ]; then
for f in $XDG_CONFIG_HOME/pulse/$prev_id-* \
$XDG_CACHE_HOME/event-sound-cache.tdb.$prev_id.*; do
fnew="$(echo $f | sed "s/$prev_id/$cur_id/")"
mv $f $fnew
done
fi
cp -p $cur_id_path $prev_id_path
I disclaim any rights to the above script. You're welcome to treat it as public domain, and do with it as you wish.
Take care, and so long for now.
-
Some background on machine-id: the concept originated in D-Bus, but has since made its way into systemd (of course). On systems that run systemd,
systemd-machine-id-setupis typically used to initialize/etc/machine-idat install time (this is what Debian does, for instance). Then,/usr/lib/tmpfiles.d/dbus.conffrom thedbuspackage is used to make/var/lib/dbus/machine-ida symlink to/etc/machine-id. Devuan systems, not using systemd, do not have a/etc/machine-idfile, and Devuan'sdbuspackage does not include/usr/lib/tmpfiles.d/dbus.conf. Instead, in Devuan/var/lib/dbus/machine-idis a regular file generated in the post-install hook of thedbuspackage usingdbus-uuidgen. See alsodbus.README.Devuan. -
$XDG_CACHE_HOMEusually defaults to~/.cache, and$XDG_CONFIG_HOMEto~/.config. See the XDG Base Directory Specification for more details.