[Exherbo-dev] [RFC v2] A new approach to CONFIG_PROTECT

Alex Elsayed eternaleye at gmail.com
Sun Mar 31 06:30:03 UTC 2013

I recently decided to revisit this, and have made some changes.

The current layout of a directory under CONFIG_PROTECT is somewhat
less than ideal. An update to a protected directory results in
._cfg0000_.* files strewn throughout, with no information as to their
origin. Also, this layout does not interact well with configuration
management tools such as etckeeper. Here I will propose an alternate
method for achieving the goals of CONFIG_PROTECT.

First, a different layout could bring many benefits both in terms of
functionality and performance. In all examples, we will be treating
/etc as the directory under CONFIG_PROTECT.

For a new layout, we should avoid splattering dotfiles all over the
directory tree. It would also be beneficial to record the package that
tried to merge the file, as well as a timestamp.  Conveniently,
exndbam already stores a timestamp.

This is how I imagine the new layout might look:


When a package adds a path to either CONFIG_PROTECT or CONFIG_PROTECT_MASK,
it creates a directory in ${IMAGE}/etc/.config-protect/protected/.
The directory is the path with a suffix of either .protect or .mask.
As an example, if /var/foo was being protected and /var/foo/bar was being
masked, then the package would create the directories
${IMAGE}/etc/.config-protect/protected/var/foo.protect and
Inside each of these it will create an entry of the form
(hereafter referred to as 'exndbam naming').

During merge, the merger will check if a path is under config protection 
as described in the following bash pseudocode:

function is_protected() {
    local PATH=$1
    while [[ -n "${PATH}" ]]; do
        for cfpro in {"${IMAGE}",/etc}/.config-protect/protected; do
             if [[ -e "${cfpro}/${path}.protect" ]]; then
                echo true
            elsif [[ -e -e "${cfpro}/${path}.mask" ]]; then
                echo false
    echo false

This has the benefit that unmerging a package will take its entry with it,
and due to the 'remove empty directories' semantics of the merger,
a CONFIG_PROTECT entry will disappear when all packages that request it are

When the merger comes across a file with a destination that
is under CONFIG_PROTECT, it should create a directory named in the
'exndbam naming' style under 'new'. It will then merge the file with the
CONFIG_PROTECTed destination to the newly-created directory as if it were
the destination. In the case that there is no pre-existing version of the
file, the package manager will merge to both and use the checksum and mtime
from the package contents to check whether they are the same file. This
avoids problems incases where the file must exist for later parts of the
same resolution, without introducing corner cases for programs that may not,
for example, accept symlinks as configuration files. Another option is to
use hardlinks, but that is likely too clever by half.

When the user next runs 'eclectic config', it will scan
/etc/.config-protect/new/ to see what action needs to be taken.

I see no need to change the user interface of 'eclectic config' at
all. It will behave exactly like before from the user's perspective.

However, in order to support configuration management properly, we
should try to avoid changing the running system if we have other options.
Therefore, when the user would perform an action via 'eclectic config'
that could modify the original file (such as a merge), the original file
is instead copied to the 'old' directory and the action is performed on
the copy.

To give an example, if we have a config file /etc/foo/bar.conf which
has an update pending in
and the user chooses to merge them, the original will be copied to
and the merge tool will be called on that copy.

As a resolution is decided upon, the final resolution is placed at

If there are multiple pending updates for a single file, then by default
only the update with the newest timestamp is considered for compatibility
with existing behavior.

I feel there should be a setting to which causes such updates to be applied
serially in chronological order, and also takes advantage of the 'old/'
logic described above. The copying of the old version would simply use the
'resolved' entry from the previous update to that file. This is necessary
for full, proper support of configuration management.

After all updates have been addressed (which can be checked for by
testing whether for every file in 'new' there is a corresponding file
in 'resolved'), 'eclectic config' iterates over
/etc/.config-protect/resolved/*/* in order of timestamp from oldest to
newest. For each directory, it moves the original protected files to the
'/etc/.config-protect/historic/' subdirectory using the exndbam-name of
the replacing file; then puts the new file in its place. It may be best to
implement this as a copy to 'historic/' followed by a rename from 
so as to avoid leaving a time when no file is present.

At this point, if etckeeper or some other configuration management
system supported by eclectic is enabled (support provided via
plugins?), the files changed by that specific update are treated to the
equivalent of

git add /etc/foo/baz.conf
git commit

The default message would be something like
Conf update for ${CATEGORY}/${PNVR} from `date --rfc-3339=ns -d

but would be open to editing.

Immediately after the changes for a package are committed, the new,
old, and resolved entries for that package are deleted.

It should be noted that /etc/.config-protect/ would be in the
equivalent of .gitignore for whatever system is being used.

More information about the Exherbo-dev mailing list