90-dependencies.source
Jump to navigation
Jump to search
#
# Implements dependencies and conflicts among FAI classes.
#
# Copyright 2010, Stefan Goetz stefan.goetz@web.de
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License with
# the Debian GNU/Linux distribution in file /usr/share/common-licenses/GPL-2;
# if not, write to the Free Software Foundation, Inc., 51 Franklin St,
# Fifth Floor, Boston, MA 02110-1301 USA or look on the World Wide Web
# at http://www.gnu.org/copyleft/gpl.html.
#
#
# FAI Class Dependencies
# ======================
#
# This script handles both dependency and conflict rules among FAI classes.
# To use it, drop it into the FAI class directory and put the dependency rules
# into the sub-directory dependencies (i.e. $FAI/class/dependencies).
#
# To specify that class X depends on classes D1 and D2 and D3, create a
# rule file with the name X and the contents "D1 D2 D3". Thus, if the
# current class list contains class X, then D1, D2, and D3 are added to the
# class list as well.
#
# To specify that class X conflicts with classes C1 and C2 and C3, put
# "-C1 -C2 -C3" in dependency file X. Thus, if the current class list contains
# class X, any one class of C1, C2, and C3 present in the class list is removed
# from it. If X is not in the class list, C1, C2, and C3 remain in the class
# list if present.
#
# For dependency files that are not executable, all their content is interpreted
# as class names as describe above.
# Depency files that are executable are executed and their output on stdout is
# evaluated as class names as described above. Different class names must be
# separated by at least one blank, tab, or new-line character. Note that
# dependency resolution runs in multiple passes over all rule files and
# executable rule files are executed once per pass.
#
# Chains of dependencies are supported (e.g., MOUNT_HOME depending on NFS
# depending on (NFSv3 NTP) and NFSv3 depending on PORTMAP). Note that there are
# no sanity checks in this procedure, in particular no checks for cyclic
# dependencies. Therefore, degenerate rules can lead to unstable results or to
# the resolution process not terminating.
#
# This script prints status messages on stdout and error messages on stderr.
#
#
# Changes
# =======
#
# 2010-09-03 stg: initial revision
# 2010-09-15 stg: resolution bug: when multiple rules modify the class list in a
# single pass so that the class list is the same before and after the pass, the
# resolution could stop but it does not. Fix: change the condition that
# terminates the resolution process from 'class list was modified' to 'class
# list is different at end of pass from beginning of pass'.
#
#
# Test whether a class is already present int he global list of classes.
# @param CLASS_NAME the name of the class to check for.
# @return 0 if the class is present, a non-zero value otherwise
#
faidep_test_class () {
local CLASS_NAME="${1:?}"
grep -q "^${CLASS_NAME}$" "${FAIDEP_CLASS_FILE}"
}
#
# Add a class to the global list of classes.
# @param CLASS_NAME the name of the class to add.
# @param BEFORE_CLASS If given, add the class CLASS_NAME before the class
# BEFORE_CLASS. This parameter is optional.
# @return 0 on success
#
faidep_add_class () {
local CLASS_NAME="${1:?}"
local BEFORE_CLASS="${2:-}"
if [ "${BEFORE_CLASS}" ]; then
sed -ri "/\b${BEFORE_CLASS}\b/ i\
${CLASS_NAME}" "${FAIDEP_CLASS_FILE}"
else
echo "${CLASS_NAME}" >> "${FAIDEP_CLASS_FILE}"
fi
}
#
# Remove a class from the global list of classes.
# @param CLASS_NAME the name of the class to remove.
# @return 0 if ${CLASS_NAME} was succesfully removed, 1 if ${CLASS_NAME} was not
# present, >1 otherwise
#
faidep_remove_class () {
local CLASS_NAME="${1:?}"
if faidep_test_class "${CLASS_NAME}"; then
sed -i "/^${CLASS_NAME}$/ d" "${FAIDEP_CLASS_FILE}"
return ${?}
fi
return 1
}
#
# Get a fingerprint for the current class list (current with respect to
# modifications via faidep_add/remove_class(). When two fingerprints are equal,
# the class lists from which they were created can be assumed to be equal.
# @return a fingerprint on stdout.
#
faidep_classes_fingerprint () {
sort -u "${FAIDEP_CLASS_FILE}" | md5sum | cut -d ' ' -f 1
}
#
# Resolve the rules in a dependency file.
# @param FILE the dependency file to evaluate.
# @return 0
#
faidep_resolve_file () {
local FILE="${1:?}"
#echo "Evaluating dependency file: '${FILE}'" >&2
local CLASS="$(basename "${FILE}")"
# is CLASS in $classes, i.e., do we need to resolve this dependency?
if faidep_test_class "${CLASS}"; then
local RULES RULE
if [ -x "${FILE}" ]; then
RULES="$(${FILE})"
else
RULES="$(cat "${FILE}")"
fi
for RULE in $RULES; do
#echo "Rule: '${RULE}'" >&2
if echo "${RULE}" | egrep -q '^-'; then
CONFLICT="$(echo "${RULE}" | sed 's/^-//')"
# is the conflict in the class list and needs to be removed?
if faidep_test_class "${CONFLICT}"; then
#echo "Removing conflicting class '${CONFLICT}'" >&2
faidep_remove_class "${CONFLICT}"
fi
else
DEPENDENCY="${RULE}"
# is the dependency missing from the class list and needs to be added?
if ! faidep_test_class "${DEPENDENCY}"; then
#echo "Adding dependency class '${DEPENDENCY}'" >&2
faidep_add_class "${DEPENDENCY}" "${CLASS}"
fi
fi
done
fi
}
#
# Run a single resolution pass over the dependency files.
# @return 0
#
faidep_resolve_files () {
# iterate over all dependency files
local FILE IFS
IFS='
'
for FILE in $(find "${FAIDEP_DIR}" -mindepth 1 -maxdepth 1 -type f); do
faidep_resolve_file "${FILE}"
done
}
#
# Run the dependency resolution until the class list is longer modified and a
# stable state is reached.
#
faidep_resolve_until_stable () {
local FINGERPRINT_BEFORE=0
local FINGERPRINT_AFTER=1
local LOOP_MAX=100 # better safe than sorry
local LOOP_COUNTER=0
while [ "$FINGERPRINT_BEFORE" != "$FINGERPRINT_AFTER" ] && LOOP_COUNTER=$(( ${LOOP_COUNTER} + 1 )) && [ ${LOOP_COUNTER} -lt ${LOOP_MAX} ]; do
FINGERPRINT_BEFORE="$(faidep_classes_fingerprint)"
faidep_resolve_files
FINGERPRINT_AFTER="$(faidep_classes_fingerprint)"
done
if [ ${LOOP_COUNTER} -ge ${LOOP_MAX} ]; then
echo "FAILED to fully resolve class dependencies. Either the dependency configuration in '${FAIDEP_DIR}' is extremely complex or there is a dependency/collision loop." >&2
return 2
fi
}
# store shell options so they can be restored at the end of the script (without
# restoring them, they would propagate to the FAI scripts and wreak havoc there)
FAIDEP_SHELL_OPTIONS="$(set +o)"
set -u # complain about unset variables
# where dependency information is stored
FAIDEP_DIR="${FAI}/class/dependencies"
#
# There are two documented approaches in fai-class to *adding* additional classes
# 1) *.source files: list and export them in the variable ${newclasses}
# 2) non-*.source files: print them on stdout
#
# Removing a class from the list of classes is, however, not available directly
# through fai-class.
# Thus, we need to use a backdoor:
# fai-classes stores the global class list in the file ${LOGDIR}/FAI_CLASSES.
# This file is, unfortunately, not hardcoded but specified on the command line
# of fai-class. We can only hope that fai itself uses fai-class always with that
# particular file because this is what we operate on here.
#
FAIDEP_CLASS_FILE="${LOGDIR}/FAI_CLASSES"
if [ -f "${FAIDEP_CLASS_FILE}" ]; then
#echo "Class list before dependency resolution: '$(cat ${FAIDEP_CLASS_FILE})'"
faidep_resolve_until_stable
#echo "Class list after dependency resolution: '$(cat ${FAIDEP_CLASS_FILE})'"
else
echo "FAILED to resolve class dependencies. The file '${FAIDEP_CLASS_FILE}', expected to contain the global class list, does not exist!" >&2
fi
# restore options
eval "${FAIDEP_SHELL_OPTIONS}"