Fixed some inconsistencies and fine-tuned a few things. Also
used pseudocode where it conveyed the point better.
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.
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. Here, I attempt to
integrate this with exndbam by the following methods:
1.) Have all config protect entries in a single location.
2.) Record the category, package name, version, slot, and cookie
directly in both the config protect staging entries and the
entries that request a path be protected.
3.) Use files that appear in CONTENTS to register protected and
masked paths, allowing the merger and unmerger to handle
them intelligently.
This is how I imagine the new layout might look:
/etc/
.config-protect/
new/
old/
historic/
resolved/
protected/
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
${IMAGE}/etc/.config-protect/protected/var/foo/bar.mask. Inside each of
these it will create an entry of the form
${CATEGORY}---${PN}/${PVR}:${SLOT}:C.${PID}.${TIME_SEC}.${TIME_USEC}.C
(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
PATH="${PATH%%/}
while [[ -n "${PATH}" ]]; do
for cfpro in {"${IMAGE}",/etc}/.config-protect/protected; do
if [[ -e "${cfpro}/${path}.protect" ]]; then
echo true
return
elsif [[ -e -e "${cfpro}/${path}.mask" ]]; then
echo false
return
fi
done
PATH="${PATH%/*}"
done
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 gone.
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 in cases where the file must
exist for later parts of the same resolution, without introducing
errors 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 following occurs:
1.) The current file on / is copied to the 'old/' directory (using
the same naming as the update being acted on)
2.) If the file in 'new/' does not have an equivalent in 'historic/',
copy it there.
3.) For cases like merge where a 3-way diff is beneficial, use the
newest version of the conflicted file in historic/ which is
*not* from the same update as the base. The cookie portion of
the exndbam naming convention is helpful here.
To give an example, if we have a config file /etc/foo/bar.conf which
has an update pending in
new/foo---bar/0:0:C.31.14.15.C/etc/foo/bar.conf,
and the user chooses to merge them, then:
The original will be copied to
old/foo---bar/0:0:C.31.14.15.C/etc/foo/bar.conf
The new one will be copied to
historic/foo---bar/0:0:C.31.14.15.C/etc/foo/bar.conf
(as long as it doesn't already exist),
and the mergetool will use the 'historic/' entry as the merge base.
Once a resolution is decided upon, the final resolution is placed at
/etc/.config-protect/resolved/foo---bar/0:0:C.31.14.15.C/foo/bar.conf.
If no potentially-destructive changes have caused the new/ file to be
copied to historic/ yet, do so now.
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' applies the updates as described
by the following pseudocode:
for update in resolved/*/* sorted by timestamp ascending; do
for file in update; do
move file from resolved to /
git add /file
done
git commit
done
The default commit message would be something like
"Conf update for ${CATEGORY}/${PNVR} from $(date --rfc-3339=ns -d
"@${TIMESTAMP}")"
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/ should be in the equivalent of
.gitignore for whatever system is being used.
_______________________________________________
Exherbo-dev mailing list
[email protected]
http://lists.exherbo.org/mailman/listinfo/exherbo-dev