dpkg-divert: override a file in a Debian package (Shallow Thoughts)

Akkana's Musings on Open Source Computing and Technology, Science, and Nature.

Mon, 10 Oct 2022

dpkg-divert: override a file in a Debian package

I boot Linux in text mode, with all the boot-time messages showing. There are several reasons for this, but one is that I want to be able to see any errors that might arise — boot-time errors aren't otherwise shown to the user.

However, many Linux distros, including Debian and Ubuntu, clear the screen before showing a login prompt, making it impossible to read the last few messages or find any errors.

Some years back, I looked into why this was happening, and found the answer in Stop Clearing My God Damned Console. It comes down to a line in getty@tty1.service: TTYVTDisallocate=yes. Change that to TTYVTDisallocate=no and the terminal will stop clearing before you log in.

The page suggests creating the directory /etc/systemd/system/getty@.service.d, then creating a file inside it, noclear.conf. But I never got that to work; maybe I missed one of the steps, maybe it had to do with the difference between /lib/systemd and /etc/systemd, or maybe I was hit by one of systemd's periodic syntax changes. The only way I got TTYVTDisallocate=no was by modifying /lib/systemd/system/getty@.service itself.

But that file is very frequently overwritten: any changes to it are erased every time there's an update to the systemd package. Systemd doesn't check, like some packages do, to see whether your version is different from the new version; it just overwrites the file silently. So my change would work for a few weeks, then I'd boot to a blank screen and realize my change had gotten overwritten again, and I had to go find my instructions to re-edit that file.

How could I stop dpkg from overwriting my changes? At least on some filesystems, you can use chattr to make a file unwriteable. I tried that for a while: chattr +i /lib/systemd/system/getty@.service

It worked; but chattr is a blunt tool and I worried that dpkg might eventually overwrite it anyway, or else a package installation would fail because it couldn't write that file.


Eventually I found a better way: dpkg-divert.

This tool lets you tell dpkg that when it wants to install a file with pathname X, instead it should install to path Y. In this case:

dpkg-divert --divert /lib/systemd/system/getty@.service.new --no-rename /lib/systemd/system/getty@.service

That tells dpkg that when a package says to install a new version of /lib/systemd/system/getty@.service, instead, put it in /lib/systemd/system/getty@.service.new and leave the existing file alone. Note that the arguments are the opposite of what you'd expect: the new filename comes first, the expected filename last.

You can list your system's current diversions:

$ dpkg-divert --list
[ ... ]
local diversion of /lib/systemd/system/getty@.service to /lib/systemd/system/getty@.service.new
[ ... ]
You'll probably find there are already several in place.

Remove a diversion thus:

dpkg-divert --remove --no-rename /lib/systemd/system/getty@.service

I confess to being shaky about the --no-rename part: I found the documentation very unclear on that (why would it be renaming anything when removing a diversion?) but dpkg-divert requires that you specify either --rename or --no-rename even on a --remove. As for creating a diversion, I created my diversion with --rename, but from what I can glean from the documentation, copying the file rather than renaming it probably makes more sense, so --no-rename is probably better (and besides, --rename will bail if the file already exists). When removing a diversion, if you don't specify one or the other you get a long warning message saying you should specify one or the other explicitly, but I'm not sure why it would make any difference at all in the --remove case.

I'd be interested to hear from any readers who understand the purpose of --rename, or what difference it makes when removing a diversion. I couldn't find a clear explanation.

Checking Your Work

Of course, I was curious as to whether my diversion was really working. So I added this to my .zlogin:

if [[ -e /lib/systemd/system/getty@.service.new ]]; then
    echo '*****************'
    echo "Systemd tried to install a new /lib/systemd/system/getty@.service"
    echo "diff /lib/systemd/system/getty@.service /lib/systemd/system/getty@.service.new"
    echo '*****************'

Now every couple of weeks I see that message upon logging in. Then I run the indicated diff command to see that invariably, the file hasn't changed at all and the only difference is that it would have flipped back to TTYVTDisallocate=yes. That means it' safe to remove the new /lib/systemd/system/getty@.service.new.

Tags: ,
[ 19:08 Oct 10, 2022    More linux | permalink to this entry | ]

Comments via Disqus:

blog comments powered by Disqus