Have you ever wondered why an increasing number of programs are configured by placing small files in .d directories instead of by just editing a single file?

Have you ever wondered why these .d directories seem to proliferate in UnixLinux installations? Because a pretty bare-bones Debian installation (WSL) shows… a ton of them:

debian:/etc> ls -ld *.d */*.d
drwxr-xr-x 1 root root 4096 Aug 14 22:23 apparmor.d
drwxr-xr-x 1 root root 4096 Aug  9 08:10 apt/apt.conf.d
drwxr-xr-x 1 root root 4096 May 12 05:57 apt/auth.conf.d
drwxr-xr-x 1 root root 4096 May 12 05:57 apt/preferences.d
drwxr-xr-x 1 root root 4096 May 12 05:57 apt/sources.list.d
drwxr-xr-x 1 root root 4096 Aug  9 08:10 apt/trusted.gpg.d
drwxr-xr-x 1 root root 4096 Aug 12 16:08 bash_completion.d
drwxr-xr-x 1 root root 4096 Apr 27 13:02 binfmt.d
drwxr-xr-x 1 root root 4096 Jun  3 14:09 ca-certificates/update.d
drwxr-xr-x 1 root root 4096 Aug  9 08:10 cron.d
drwxr-xr-x 1 root root 4096 Jul  5 12:10 dbus-1/session.d
drwxr-xr-x 1 root root 4096 Jul  5 12:10 dbus-1/system.d
drwxr-xr-x 1 root root 4096 Aug  9 08:10 dhcp/dhclient-enter-hooks.d
drwxr-xr-x 1 root root 4096 Aug  9 08:10 dhcp/dhclient-exit-hooks.d
drwxr-xr-x 1 root root 4096 Jun  3  2019 dpkg/dpkg.cfg.d
drwxr-xr-x 1 root root 4096 Aug 12 18:41 emacs/site-start.d
drwxr-xr-x 1 root root 4096 Aug 12 18:41 exim4/conf.d
drwxr-xr-x 1 root root 4096 Jun 18  2019 gss/mech.d
drwxr-xr-x 1 root root 4096 Aug 12 18:41 init.d
drwxr-xr-x 1 root root 4096 Aug  9 08:10 iproute2/rt_protos.d
drwxr-xr-x 1 root root 4096 Aug  9 08:10 iproute2/rt_tables.d
drwxr-xr-x 1 root root 4096 Apr 27 13:02 kernel/install.d
drwxr-xr-x 1 root root 4096 Aug  9 08:10 kernel/postinst.d
drwxr-xr-x 1 root root 4096 Aug 14 22:40 ld.so.conf.d
drwxr-xr-x 1 root root 4096 Aug 12 18:41 logrotate.d
drwxr-xr-x 1 root root 4096 Feb  9  2019 modprobe.d
drwxr-xr-x 1 root root 4096 Aug  9 08:10 modules-load.d
drwxr-xr-x 1 root root 4096 Aug 12 18:41 mysql/conf.d
drwxr-xr-x 1 root root 4096 Jul  4 08:31 mysql/mariadb.conf.d
drwxr-xr-x 1 root root 4096 Jan 28  2019 network/if-down.d
drwxr-xr-x 1 root root 4096 Jan 28  2019 network/if-post-down.d
drwxr-xr-x 1 root root 4096 Jan 28  2019 network/if-pre-up.d
drwxr-xr-x 1 root root 4096 Jan 28  2019 network/if-up.d
drwxr-xr-x 1 root root 4096 Jan 28  2019 network/interfaces.d
drwxr-xr-x 1 root root 4096 Aug  9 08:10 pam.d
drwxr-xr-x 1 root root 4096 Aug 12 18:41 ppp/ip-up.d
drwxr-xr-x 1 root root 4096 Jul 10 17:04 profile.d
drwxr-xr-x 1 root root 4096 Aug 12 18:41 rc0.d
drwxr-xr-x 1 root root 4096 Aug 12 18:41 rc1.d
drwxr-xr-x 1 root root 4096 Aug 12 18:41 rc2.d
drwxr-xr-x 1 root root 4096 Aug 12 18:41 rc3.d
drwxr-xr-x 1 root root 4096 Aug 12 18:41 rc4.d
drwxr-xr-x 1 root root 4096 Aug 12 18:41 rc5.d
drwxr-xr-x 1 root root 4096 Aug 12 18:41 rc6.d
drwxr-xr-x 1 root root 4096 Aug  9 08:10 rcS.d
drwxr-xr-x 1 root root 4096 Feb 26  2019 rsyslog.d
drwxr-xr-x 1 root root 4096 Feb 14  2019 security/limits.d
drwxr-xr-x 1 root root 4096 Feb 14  2019 security/namespace.d
drwxr-xr-x 1 root root 4096 Aug  9 08:10 sudoers.d
drwxr-xr-x 1 root root 4096 Aug  9 08:10 sysctl.d
drwxr-xr-x 1 root root 4096 Apr 27 13:02 tmpfiles.d
drwxr-xr-x 1 root root 4096 Apr 27 13:02 udev/hwdb.d
drwxr-xr-x 1 root root 4096 Apr 27 13:02 udev/rules.d
drwxr-xr-x 1 root root 4096 Aug  9 08:10 update-motd.d
debian:/etc>

… while a fresh installation of the more traditional NetBSD barely shows any:

netbsd:/etc> ls -ld *.d */*.d
drwxr-xr-x  2 root  wheel   512 Aug  9 07:43 fonts/conf.d
drwxr-xr-x  2 root  wheel   512 Aug  9 18:54 pam.d
drwxr-xr-x  2 root  wheel   512 Aug  9 07:43 rc.conf.d
drwxr-xr-x  2 root  wheel  2560 Aug  9 18:54 rc.d
drwxr-xr-x  4 root  wheel   512 Aug  9 07:43 saslc.d
netbsd:/etc>

What’s up with that? The reason is simple:

Supporting configuration updates as part of first-class package management.

But why do .d directories help? Let’s first look at a couple of examples to understand how these directories work and then we’ll get into the why.

An example: sysctl.conf and sysctl.d

To understand how configuration directories work, let’s look at a particular example that exists across the two systems we are looking at. That is the mechanism to apply sysctl configuration changes.

For those not familiar with it, sysctl is a tool and an interface to tune kernel-level settings. On Linux, the sysctl(8) tool is a simple wrapper over the contents of /proc/sys/, but on NetBSD it’s a direct client to the kernel’s sysctl(3) API. For a sneak peek of what these kernel settings involve, type sysctl -a to get a list of all available knobs and their current values.

Because sysctl offers access to tuning kernel-level knobs for the whole system, it seems natural to have a mechanism to configure those at system boot time. After all, if you want to modify how e.g. the virtual memory subsystem works, you will want do that during the early stages of the system’s boot process.

Looking at a NetBSD system, here is what we have:

netbsd:/etc> ls -l sysctl*
-rw-r--r--  1 root  wheel  797 Oct 12  2018 sysctl.conf
netbsd:/etc>

In this scenario, all kernel settings must be put in the single /etc/sysctl.conf text file, which is then trivially read at boot time by the /etc/rc.d/sysctl script. Both human administrators and automated processes have to update this single file if they want to effect change on sysctl settings at boot time.

Let’s compare this to a what Debian system has:

debian:/etc> ls -l sysctl*
-rw-r--r-- 1 root root 2351 May 31  2018 sysctl.conf

sysctl.d:
total 4K
lrwxrwxrwx 1 root root  14 Apr 27 13:02 99-sysctl.conf -> ../sysctl.conf
-rw-r--r-- 1 root root 639 May 31  2018 README.sysctl
-rw-r--r-- 1 root root 324 May 31  2018 protect-links.conf
debian:/etc>

We still have the traditional /etc/sysctl.conf file but we also have an /etc/sysctl.d/ subdirectory. In this case, the systemd-sysctl boot service—because it had to be systemd, of course—iterates over all files under /etc/sysctl.d/ in lexicographical order and applies their settings.

Note that I did not mention /etc/sysctl.conf and that’s because systemd-sysctl couldn’t care less about it. That file location exists for the administrator’s convenience as the traditional place to perform manual edits. But the way systemd-sysctl finds this file is thanks to its /etc/sysctl.d/99-sysctl.conf symlink. The 99 prefix is important, as this causes the administrator-controlled file to sort after all others. In this way, its contents override any other settings that might have been put under the sysctl.d directory by automated tools.

But that’s not the only way configuration directories interact with their corresponding legacy file.

Another example: ld.so.conf and ld.so.conf.d

In our previous example, we saw that the /etc/sysctl.conf file on a Debian system exists purely for the convenience of the administrator. The system does not care about this file directly so its existence in this specific location might as well be removed.

The alternative is to go the complete opposite route: keep the configuration file as the primary location for the configuration, but then make this file explicitly include all contents of a subdirectory.

An obvious example of this behavior is the configuration for the dynamic linker: the ldconfig tool reads the /etc/ld.so.conf file to discover which directories contain dynamic libraries, but is otherwise unaware of the existence of the /etc/ld.so.conf.d/ directory. So how is the latter read then? Easy:

debian:/etc> cat ld.so.conf
include /etc/ld.so.conf.d/*.conf
debian:/etc>

As we can see, /etc/ld.so.conf continues to be the primary file, but it then includes all individual files from the configuration directory. This requires native support in the tool to allow file inclusions and also makes the semantics of glob expansion tool-specific (are they in lexicographic order?).

Note that, in this case, the administrator has the freedom to append custom configuration directly to /etc/ld.so.conf or to create a new unmanaged file under /etc/ld.so.conf.d/ (say, 99-zzz.conf).

The real purpose behind .d directories

Once you start looking, as we did in the first listing, you’ll notice that this pattern repeats everywhere: for any given configuration that needs edits from multiple independent sources, you will likely find a traditional configuration file accompanied by a configuration subdirectory.

So now that we have all necessary knowledge, we are ready to tackle the questions: What are configuration directories for? Why have these configuration directories proliferated? What benefit do they bring? And the answer to them all is the same: they make automated edits straightforward, which is important when you want a package manager to perform and own such edits.

You see: if you have a plain text file and you want to edit it programmatically, you have two options. You can have a parser that understands the format of the file and is able to modify it, but then preserving comments and formatting (both of which are important for configuration!) is difficult. Or you can add comment-like markers to the file and perform unstructured edits, but then those edits break if the user happens to modify the file in an unexpected manner.

Contrariwise, if you have a directory where you can put individual files in, and the whole configuration is determined to be the logical concatenation of all these files, all of a sudden automated edits become trivial. A package wanting to add new settings to the system will only need to add a new file in that directory, and the package manager will be able to track the provenance of that file. And if you uninstall the package, the package manager will be able to remove its configuration without issues (possibly leaving behind the files if they were manually modified).

Parting words

So, are configuration directories better than files?

Well… they are different. With directories, we trade the ability to read and modify a single plain text file by hand with a scheme that is maybe-trickier to hand-tune but that is amenable to automated edits. As a consequence, we shift the complexity from a package installer’s scripts to the tools that read the configuration.

And I think this tradeoff is well worth it. A package manager that can track the provenance of almost all files in a system and that can perform automated reconfigurations in a robust manner is a blessing. In particular because, you, as the system administrator, will have increased confidence that the package manager will not revert any “99” local-only files created on your own. And also, shifting this logic out of the package manager and putting it into the tools ensures consistency across different systems (especially across different Linux distributions).

One last thought: is this concept of configuration directories restricted to configuration? Nope! As we shall see in the next post, this can be applied to system-wide databases as well.