90-dependencies.source
- 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
- 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 }
- Resolve the rules in a dependency file.
- @param FILE the dependency file to evaluate.
- @return 0 if the resolution process did not modify the class list, 1 if the
- resolution process modified the class list, 2 on error.
faidep_resolve_file () { local FILE="${1:?}" #echo "Evaluating dependency file: '${FILE}'" >&2
local IS_MODIFIED=0 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 IS_MODIFIED=1 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 IS_MODIFIED=1 faidep_add_class "${DEPENDENCY}" "${CLASS}" fi fi done fi
return $IS_MODIFIED }
- Run a single resolution pass over the dependency files.
- @return 0 if the resolution process did not modify the class list (indicating
- that further passes are unnecessary), 1 if the resolution process modified
- the class list (indicating that further passes might be necessary), 2 on
- error
faidep_resolve_files () { # iterate over all dependency files local FILE IS_MODIFIED IFS IS_MODIFIED=0 IFS=' ' for FILE in $(find "${FAIDEP_DIR}" -mindepth 1 -maxdepth 1 -type f); do faidep_resolve_file "${FILE}" if [ $? -eq 1 ]; then IS_MODIFIED=1 fi done
return $IS_MODIFIED }
- Run the dependency resolution until the class list is longer modified and a
- stable state is reached.
faidep_resolve_until_stable () { # resolve dependencies as long as a class is added or removed local LOOP_MAX=100 # better safe than sorry local LOOP_COUNTER=0 local IS_MODIFIED=1 while LOOP_COUNTER=$(( ${LOOP_COUNTER} + 1 )) && [ ${LOOP_COUNTER} -lt ${LOOP_MAX} ]; do faidep_resolve_files case $? in 0) echo "The list of FAI classes was not modified by most recent dependeny resolution pass - done." break ;; 1) echo "The list of FAI classes was modified by most recent dependeny resolution pass - going into another round." continue ;; 2) echo "Dependency resolution pass failed." 2>&1 return 2 ;; *) echo "Unexpected return value from resolution pass." 2>&1 return 2 esac 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}"