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}"