90-dependencies.source

From FAIWiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
#
# 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}"