90-dependencies.source

From FAIWiki
Revision as of 11:34, 3 September 2010 by StefanGoetz (talk | contribs) (New page: # # 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 #...)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
  1. Implements dependencies and conflicts among FAI classes.
  2. Copyright 2010, Stefan Goetz stefan.goetz@web.de
  3. This program is free software; you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation; either version 2 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License with
  12. the Debian GNU/Linux distribution in file /usr/share/common-licenses/GPL-2;
  13. if not, write to the Free Software Foundation, Inc., 51 Franklin St,
  14. Fifth Floor, Boston, MA 02110-1301 USA or look on the World Wide Web
  15. at http://www.gnu.org/copyleft/gpl.html.
  1. FAI Class Dependencies
  2. ======================
  3. This script handles both dependency and conflict rules among FAI classes.
  4. To use it, drop it into the FAI class directory and put the dependency rules
  5. into the sub-directory dependencies (i.e. $FAI/class/dependencies).
  6. To specify that class X depends on classes D1 and D2 and D3, create a
  7. rule file with the name X and the contents "D1 D2 D3". Thus, if the
  8. current class list contains class X, then D1, D2, and D3 are added to the
  9. class list as well.
  10. To specify that class X conflicts with classes C1 and C2 and C3, put
  11. "-C1 -C2 -C3" in dependency file X. Thus, if the current class list contains
  12. class X, any one class of C1, C2, and C3 present in the class list is removed
  13. from it. If X is not in the class list, C1, C2, and C3 remain in the class
  14. list if present.
  15. For dependency files that are not executable, all their content is interpreted
  16. as class names as describe above.
  17. Depency files that are executable are executed and their output on stdout is
  18. evaluated as class names as described above. Different class names must be
  19. separated by at least one blank, tab, or new-line character. Note that
  20. dependency resolution runs in multiple passes over all rule files and
  21. executable rule files are executed once per pass.
  22. Chains of dependencies are supported (e.g., MOUNT_HOME depending on NFS
  23. depending on (NFSv3 NTP) and NFSv3 depending on PORTMAP). Note that there are
  24. no sanity checks in this procedure, in particular no checks for cyclic
  25. dependencies. Therefore, degenerate rules can lead to unstable results or to
  26. the resolution process not terminating.
  27. This script prints status messages on stdout and error messages on stderr.
  1. Changes
  2. =======
  3. 2010-09-03 stg: initial revision


  1. Test whether a class is already present int he global list of classes.
  2. @param CLASS_NAME the name of the class to check for.
  3. @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}" }

  1. Add a class to the global list of classes.
  2. @param CLASS_NAME the name of the class to add.
  3. @param BEFORE_CLASS If given, add the class CLASS_NAME before the class
  4. BEFORE_CLASS. This parameter is optional.
  5. @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 }

  1. Remove a class from the global list of classes.
  2. @param CLASS_NAME the name of the class to remove.
  3. @return 0 if ${CLASS_NAME} was succesfully removed, 1 if ${CLASS_NAME} was not
  4. 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 }

  1. Resolve the rules in a dependency file.
  2. @param FILE the dependency file to evaluate.
  3. @return 0 if the resolution process did not modify the class list, 1 if the
  4. 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 }

  1. Run a single resolution pass over the dependency files.
  2. @return 0 if the resolution process did not modify the class list (indicating
  3. that further passes are unnecessary), 1 if the resolution process modified
  4. the class list (indicating that further passes might be necessary), 2 on
  5. 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 }

  1. Run the dependency resolution until the class list is longer modified and a
  2. 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 }


  1. store shell options so they can be restored at the end of the script (without
  2. restoring them, they would propagate to the FAI scripts and wreak havoc there)

FAIDEP_SHELL_OPTIONS="$(set +o)" set -u # complain about unset variables

  1. where dependency information is stored

FAIDEP_DIR="${FAI}/class/dependencies"

  1. There are two documented approaches in fai-class to *adding* additional classes
  2. 1) *.source files: list and export them in the variable ${newclasses}
  3. 2) non-*.source files: print them on stdout
  4. Removing a class from the list of classes is, however, not available directly
  5. through fai-class.
  6. Thus, we need to use a backdoor:
  7. fai-classes stores the global class list in the file ${LOGDIR}/FAI_CLASSES.
  8. This file is, unfortunately, not hardcoded but specified on the command line
  9. of fai-class. We can only hope that fai itself uses fai-class always with that
  10. 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

  1. restore options

eval "${FAIDEP_SHELL_OPTIONS}"