summaryrefslogtreecommitdiffstats
path: root/core/includes
diff options
context:
space:
mode:
authorSebastian2016-04-25 12:01:08 +0200
committerSebastian2016-04-25 12:01:08 +0200
commit5acda3eaeabae9045609539303a8c12c4ce401f1 (patch)
tree7e71975f8570b05aafe2ea6ec0e242a8912387bb /core/includes
parentinitial commit (diff)
downloadmltk-5acda3eaeabae9045609539303a8c12c4ce401f1.tar.gz
mltk-5acda3eaeabae9045609539303a8c12c4ce401f1.tar.xz
mltk-5acda3eaeabae9045609539303a8c12c4ce401f1.zip
merge with latest dev version
Diffstat (limited to 'core/includes')
-rw-r--r--core/includes/binutil.inc222
-rw-r--r--core/includes/chroot.inc248
-rw-r--r--core/includes/clean_module_funcs.inc23
-rw-r--r--core/includes/cleanup.inc34
-rw-r--r--core/includes/distribution.inc53
-rw-r--r--core/includes/downloader.inc80
-rw-r--r--core/includes/helper/README.helper2
-rw-r--r--core/includes/helper/distribution.inc4
-rw-r--r--core/includes/helper/fileutil.inc39
-rw-r--r--core/includes/helper/logging.inc53
-rw-r--r--core/includes/helper/string.inc31
-rw-r--r--core/includes/kernel.inc218
-rw-r--r--core/includes/keyvalueutil.inc32
-rw-r--r--core/includes/packagemanager.inc187
-rw-r--r--core/includes/paths.inc31
-rw-r--r--core/includes/qt.inc30
-rw-r--r--core/includes/system.inc37
-rw-r--r--core/includes/useradd.inc238
-rw-r--r--core/includes/versioncompare.inc61
19 files changed, 1623 insertions, 0 deletions
diff --git a/core/includes/binutil.inc b/core/includes/binutil.inc
new file mode 100644
index 00000000..b9867a90
--- /dev/null
+++ b/core/includes/binutil.inc
@@ -0,0 +1,222 @@
+#
+# Common functions to copy binaries and their dependancies.
+#
+############################################################
+# This will parse the output of ldd on given binaries
+# and echo the location of these libs to STDOUT
+# The output of this function has to be used in some
+# way, it only echos!
+#
+# About local search:
+# It is required that we can search for the dynamic
+# libraries in a specific directory, namely the one
+# where we (potentially) built the binary. If a
+# corresponding library is found, it should take
+# precedence over ones found on the system.
+# This can be done by using the '-l' switch, see below.
+#
+############################################################
+# We use a blacklist mechanism to exclude common libraries.
+# This improves runtime quite a bit...
+declare -rg BLACKLIST="ld-linux linux-gate linux-vdso libc.so"
+
+
+# replace ' ' by '|' in the blacklist, so grep can use it directly.
+CURRENT_BLACKLIST=$(echo ${BLACKLIST} | sed 's/ /\\|/g')
+
+# Initialise flag and path for local search
+LOCALSEARCH=0
+LOCALSEARCHDIR=""
+
+DONEDONE=$(mktemp)
+echo "-----------------------" > "$DONEDONE"
+
+############################################################
+#
+# Usage:
+# get_dynamic_dependencies [-l <searchdir>] <binary_list>
+# * the list must be seperated by spaces
+# * the search for lib needed by a binary can be done locally,
+# using the -l <searchdir> option
+#
+# Ouput:
+# Will simply echo list of required libraries
+
+get_dynamic_dependencies() {
+ # check if local search is activated by the '-l' switch
+ # if so the following argument is the path.
+ if [ "x$1" == "x-l" ]; then
+ local LOCALSEARCH=1
+ shift
+ [ ! -d "$1" ] && perror "Directory '$1' does not exist, exiting."
+ local LOCALSEARCHDIR="$1"
+ shift
+ fi
+
+ # main loop over the list of binaries
+ while [ $# != 0 ]; do
+ local BINARY="$1"
+ shift
+ [ -f "$BINARY" ] || continue
+
+ # now run ldd on it and save the output in $LDD_OUT
+ local LDD_OUT="ldd_output"
+ if ldd "$BINARY" > "$LDD_OUT"; then
+ # Case 1: file is a dynamic executable
+ for LIB in $(grep -v "${CURRENT_BLACKLIST}${REQUIRED_LDD_BLACKLIST}" "$LDD_OUT" | awk '{print $1 $2 $3}'); do
+ # split the entry into an array, ex:
+ # libm.so.6 => /lib/libm.so.6 would be split into:
+ # LIBLINK[0] LIBLINK[1]
+ local LIBLINK=(${LIB//=>/ })
+ # call helper function to find the correct lib
+ lib_search
+ done
+ #TODO: check if "statically linked" is output
+ else
+ # Case 2: not a dynamic, do nothing
+ pdebug "\t\t\t(Not a dynamic.)"
+ fi
+ rm -f -- "$LDD_OUT"
+ done
+
+}
+
+############################################################
+#
+# Usage:
+# lib_search
+#
+# Output:
+# List of the path including any possible symbolic links
+# of the found libraries.
+#
+# Note: This function takes no argument. It takes the library
+# to look for from the local array LIBLINK.
+# If the local was activated in get_dynamic_dependencies
+# this will search for the library in LOCALSEARCHDIR first.
+# If its not found, then it will look system-wide.
+lib_search() {
+
+ # if activated, start by searching the lib locally
+ if [ "x$LOCALSEARCH" == "x1" ]; then
+ cd "$LOCALSEARCHDIR"
+ local LOCAL_MATCHES=$(find . -name "${LIBLINK[0]}") # | awk -F '.' '{print $1}')".so\*)
+ cd - >/dev/null
+ if [ "x${LOCAL_MATCHES}" != "x" ]; then
+ for LOCALLIB in ${LOCAL_MATCHES}; do
+ grep -q "^${LOCALLIB}\$" "$DONEDONE" && continue
+ echo "${LOCALLIB}" >> "$DONEDONE"
+ get_link_chain "${LOCALSEARCHDIR}/${LOCALLIB}" "${LOCALSEARCHDIR}"
+ get_dynamic_dependencies -l "${LOCALSEARCHDIR}" "${LOCALLIB}"
+ done
+ # found the libs, we are done
+ return
+ fi
+ # mark local search as done
+ fi
+
+ # search the lib on the system since it was not found earlier
+ if [ -n "${LIBLINK[1]}" ] && [ "x${LIBLINK[1]}" != "xnot" ]; then
+ grep -q "^${LIBLINK[1]}\$" "$DONEDONE" && return
+ echo "${LIBLINK[1]}" >> "$DONEDONE"
+ # get chain of symlink for that lib
+ get_link_chain "${LIBLINK[1]}"
+ else
+ pwarning "\t\tLib '${LIBLINK[0]}' from required dir '$ENTRY' neither found in build directory nor on this system."
+ pwarning "\t\tIf this lib is not supplied by another module, this module will probably fail in your final system"
+ fi
+}
+############################################################
+#
+# Usage:
+# get_link_chain <link> [prefix]
+# * <link> must be in absolute form-
+# * [prefix] is the prefix to strip from the ouput.
+#
+# Output:
+# Lists the symlink chain until a hardlink is found.
+#
+get_link_chain() {
+
+ # sanity checks
+ [[ "$1" == /* ]] || perror "get_link_chain() requires absolute paths, given: $1"
+ if [ ! -e $1 -a ! -L $1 ]; then
+ perror "'$1' is a link but its target '$LINK_TARGET' is not in '${LOCALSEARCHDIR}'"
+ fi
+ local PREFIX=
+ if [ $# == 2 ] ; then
+ [ -d "$2" ] || perror "get_link_chain: '$2' is not a directory. Local search can't work..."
+ # got a prefix
+ local PREFIX=$(dirname "$(canonicalize "$2/foo")")
+ [ -d "$PREFIX" ] || perror "Could not canonicalize $2"
+ [[ "$PREFIX" == */ ]] || PREFIX="$PREFIX/"
+ fi
+
+ # canonalize
+ local LINK=$(canonicalize "$1")
+
+ local CHAIN="$LINK"
+
+ # write the first link in the chain
+ if [ "x$PREFIX" != "x" ]; then
+ if [ "x${LINK#$PREFIX}" == "x${LINK}" ]; then
+ # prefix was not in the link
+ echo "$LINK"
+ else
+ # prefix was in the link
+ echo ./"${LINK#$PREFIX}"
+ fi
+ else
+ # no prefix, copy like it is
+ echo "$LINK"
+ fi
+
+ # now we check for symlinks
+ local TRY=0
+ while [ -L "$LINK" ] && [ $TRY -lt 10 ]; do
+ let TRY=TRY+1
+
+ # save the directory prefix
+ CURRENTDIR=$(dirname "${LINK}")
+ # first follow the link
+ local NEWLINK=$(readlink "$LINK")
+ [ -z "$NEWLINK" -o "$NEWLINK" = "$LINK" ] && break
+ LINK=$NEWLINK
+ CHAIN+=" -> $LINK"
+ # $LINK can be absolute or relative, check cases
+ [[ "$LINK" == /* ]] || LINK=$(canonicalize "$CURRENTDIR"/"${LINK}")
+ # write the first link in the chain
+ if [ "x$PREFIX" != "x" ]; then
+ if [ "x${LINK#$PREFIX}" == "x${LINK}" ]; then
+ # prefix was not in the link
+ if [ ! -e "$LINK" ]; then
+ [ -e "$PREFIX/$LINK" ] && echo "./$LINK"
+ else
+ echo "$LINK"
+ fi
+ else
+ # prefix was in the link
+ echo ./"${LINK#$PREFIX}"
+ fi
+ else
+ # no prefix, copy like it is
+ echo "$LINK"
+ fi
+ done
+ pdebug "\t\t$CHAIN"
+}
+############################################################
+#
+# Usage:
+# list_basic_libs
+#
+# Output:
+# list the path of following basic system libraries:
+# - libc.so, ld-linux.so
+#
+list_basic_libs() {
+ for i in $(ldd ${SHELL}); do
+ [ $(echo $i | grep '^/' | grep -c ld) -eq 1 -o $(echo $i | grep '^/' | grep -c libc.so) -eq 1 ] && get_link_chain $i
+ done
+}
+
diff --git a/core/includes/chroot.inc b/core/includes/chroot.inc
new file mode 100644
index 00000000..b7f68f65
--- /dev/null
+++ b/core/includes/chroot.inc
@@ -0,0 +1,248 @@
+# -----------------------------------------------------------------------------
+#
+# Copyright (c) 2014 - OpenSLX GmbH
+#
+# This program is free software distributed under the GPL version 2.
+# See http://openslx.org/COPYING
+#
+# If you have any feedback please consult http://openslx.org/feedback and
+# send your suggestions, praise, or complaints to feedback@openslx.org
+#
+# General information about OpenSLX can be found at http://openslx.org/
+# -----------------------------------------------------------------------------
+#
+# Common functions for chrooting
+#
+# -----------------------------------------------------------------------------
+
+declare -rg CHROOT_TEMPDIR="${ROOT_DIR}/tmp/chroot"
+declare -rg CHROOT_MOUNTDIR="${CHROOT_TEMPDIR}/rootmount"
+declare -rg CHROOT_BINDDIR="${CHROOT_TEMPDIR}/rootbind"
+declare -rg CHROOT_LOWERDIR="/"
+declare -rg CHROOT_BINDMOUNTS="/dev /proc /sys /run"
+
+
+# Helper function to setup the directory structure
+chroot_prepare_dirs() {
+ # first check if CHROOT_TEMPDIR exists
+ if [ -d "${CHROOT_TEMPDIR}" ]; then
+ # try to umount and rmdir CHROOT_MOUNTDIR
+ umount "${CHROOT_MOUNTDIR}" 2>/dev/null
+ if [ -d "${CHROOT_MOUNTDIR}" ]; then
+ rmdir "${CHROOT_MOUNTDIR}" || perror "Could not remove CHROOT_MOUNTDIR '${CHROOT_MOUNTDIR}', meaning it has stuff in it. Aborting..."
+ fi
+
+ # try to umount and rmdir CHROOT_BINDDIR
+ umount "${CHROOT_BINDDIR}" 2>/dev/null
+ if [ -d "${CHROOT_BINDDIR}" ]; then
+ rmdir "${CHROOT_BINDDIR}" || perror "Could not remove CHROOT_BINDDIR '${CHROOT_BINDDIR}', meaning it has stuff in it. Aborting..."
+ fi
+
+ # try to rmdir CHROOT_TEMPDIR
+ if [ -d "${CHROOT_TEMPDIR}" ]; then
+ rmdir "${CHROOT_TEMPDIR}" || perror "Could not remove CHROOT_TEMPDIR '${CHROOT_TEMPDIR}', meaning it has stuff in it. Aborting..."
+ fi
+ fi
+
+ mkdir -p "${CHROOT_TEMPDIR}" || perror "Could not create base directory for mount directories $CHROOT_TEMPDIR."
+ for DIR in "${CHROOT_BINDDIR}" "${CHROOT_MOUNTDIR}"; do
+ mkdir -p "${DIR}" || perror "Could not create directory for mount directory $DIR."
+ done
+}
+
+# Helper to mount the overlay structure:
+# - bind mount system / to CHROOT_BINDDIR and make it read-only
+# - make an overlay from CHROOT_LOWERDIR CHROOT_UPPERDIR
+# - bind mount additional pseudo-fs (as given in CHROOT_BINDMOUNTS)
+chroot_prepare_mounts() {
+
+ # first mount / on CHROOT_BINDDIR and remount read-only
+ mount -o bind "${CHROOT_LOWERDIR}" "${CHROOT_BINDDIR}" \
+ || perror "Could not bind-mount CHROOT_LOWERDIR '$CHROOT_LOWERDIR' to CHROOT_BINDDIR '$CHROOT_BINDDIR'."
+ mount -o remount,ro,bind "${CHROOT_BINDDIR}" || perror "Could not remount CHROOT_BINDDIR '$CHROOT_BINDDIR' read-only."
+
+ # check that it really is read-only
+ [ "x$(mount | grep -E "^/ on ${CHROOT_BINDDIR}" | grep -v '\(.*ro.*\)')" != "x" ] \
+ && perror "CHROOT_BINDDIR '${CHROOT_BINDDIR}' is not read-only! Aborting..."
+
+ # Note: The overlay fs mount syntax seems to be changed between Ubuntu 14.04.2 and 14.04.3 (Kernel 3.13 and 3.19). Instead of
+ # checking overlay-modinfo (which may fail if overlayfs is not incorporated as module) or kernel versions, we simply try to
+ # mount 'old school' first and then, if that fails, the new way to mount with workdir. See differences in mount syntax below.
+ pinfo "Now mounting overlayfs. Trying old mount syntax (up to Kernel 3.13) ..."
+ mount -t overlayfs overlayfs -o lowerdir="${CHROOT_BINDDIR}",upperdir="${CHROOT_UPPERDIR}" "${CHROOT_MOUNTDIR}" 2>/dev/null
+ if [ $? -ne 0 ]; then
+ pinfo "Old mount syntax failed. Trying new mount syntax (Kernel 3.19+) ..."
+ # We have to use a overlayfs workdir which _must_ be in the same filesystem as CHROOT_UPPERDIR. So
+ # we traverse to the directory below CHROOT_UPPERDIR and mkdir/mktemp a workdir there. In the possible
+ # case that CHROOT_UPPERDIR is the root dir of a filesystem there's nothing we can do but exit.
+ CHROOT_WORKDIR="$(mktemp -d $(dirname ${CHROOT_UPPERDIR})/workdirXXX)" \
+ || perror "Could not mkdir overlayfs workdir $CHROOT_WORKDIR for new overlayfs mount syntax."
+ # Now we try to mount the overlayfs in the new fashion:
+ mount -t overlayfs overlayfs -o lowerdir="$CHROOT_LOWERDIR",upperdir="${CHROOT_UPPERDIR}",workdir="${CHROOT_WORKDIR}" "${CHROOT_MOUNTDIR}" \
+ || perror "Could not mount (overlayfs) $CHROOT_LOWERDIR, $CHROOT_UPPERDIR to $CHROOT_BINDDIR."
+ pinfo "New overlayfs mount syntax has worked, commencing."
+ else
+ pinfo "Old overlayfs mount syntax has worked, commencing."
+ fi
+
+ # mount pseudo-filesystems
+ for DIR in $CHROOT_BINDMOUNTS; do
+ mount -o bind "${DIR}" "${CHROOT_MOUNTDIR}/${DIR}" \
+ || perror "Could not bind mount '$DIR' into CHROOT_MOUNTDIR/DIR '$CHROOT_MOUNTDIR/$DIR'."
+ done
+}
+
+# Helper to generate the mighty autoexec.bat
+chroot_gen_autoexec() {
+ # create the script to be automatically executed.
+ cat >"${CHROOT_MOUNTDIR}/autoexec.bat"<<-EOF
+ #!/bin/bash
+ #######################################################
+ # #
+ # Warning! #
+ # #
+ # This file is only meant to be executed within #
+ # the specially chrooted mltk building environment. #
+ # #
+ # Do NOT execute it if you are not sure what you do, #
+ # it may be very harmful if being run in a normal #
+ # system environment! #
+ # #
+ #######################################################
+ echo "chroot started successfully."
+ EOF
+
+ # dump the piped input to it
+ cat >> "${CHROOT_MOUNTDIR}/autoexec.bat"
+
+ # make it executable
+ chmod +x "${CHROOT_MOUNTDIR}/autoexec.bat" || perror "Failed to make '${CHROOT_MOUNTDIR}/autoexec.bat' exeutable."
+}
+
+chroot_handle_whiteouts() {
+ local WHITEOUT_LIST="${CHROOT_UPPERDIR}/overlay.whiteout.list"
+ rm -f -- "$WHITEOUT_LIST="
+ #mkdir -p "$(dirname "$WHITEOUT_LIST")" || perror "Could not create $(dirname "$WHITEOUT_LIST")"
+ pdebug "Searching for overlayfs-whiteouts ..."
+ for WHITEOUT in $(find "$CHROOT_UPPERDIR" -lname "(overlay-whiteout)"); do
+ pdebug "Whiteout found: $WHITEOUT"
+ echo "/./${WHITEOUT#$CHROOT_UPPERDIR}" >> "$WHITEOUT_LIST"
+ rm -f -- "$WHITEOUT" || perror "Could not delete whiteout $WHITEOUT!"
+ done
+ pinfo "Whiteout list dumped to '${CHROOT_UPPERDIR}/overlay.whiteout.list'"
+}
+
+###############################################################################
+#
+# MAIN FUNCTION
+#
+# Main function to be called from the outside
+# Usage:
+# chroot_run <build_dir> < <code_to_exec_in_chroot>
+#
+# Example:
+# chroot_run /tmp/chroot_build <<-EOF
+# echo "This will be executed inside the chroot"
+# EOF
+#
+# It will run:
+# - chroot_prepare
+# - chroot $CHROOT_TEMPDIR/rootmount
+# - executes $CHROOT_TEMPDIR/rootmount/autoexec.bat
+# - chroot_cleanup
+chroot_run() {
+ # check args
+ [ $# -eq 1 ] || perror "'chroot_run' requires exactly 1 parameter. Given $#. Use 'chroot_run <build_dir>'"
+
+ local CHROOT_UPPERDIR="$1"
+ mkdir -p "$1"
+
+ # first prepare the dir structure
+ chroot_prepare_dirs || perror "'chroot_prepare_dirs' failed with $?."
+ chroot_prepare_mounts || perror "'chroot_prepare_mounts' failed with $?."
+
+ # generate the code to be executed when chroot'ing
+ chroot_gen_autoexec || perror "'chroot_gen_autoexec' failed with $?."
+
+ # do the chroot
+ exec 0>&8 # This redirection is used for debugging within a chroot
+ chroot --userspec root:root "${CHROOT_MOUNTDIR}" /autoexec.bat
+ local RET=$?
+ if [ "$RET" -eq 0 ]; then
+ pinfo "chroot executed '${CHROOT_MOUNTDIR}/autoexec.bat' succeeded."
+ else
+ perror "Failed to run '$CHROOT_MOUNTDIR/autoexec.bat' inside the chroot to '$CHROOT_MOUNTDIR' with error code: $RET"
+ fi
+
+ # handle whiteouts
+ chroot_handle_whiteouts || perror "'chroot_handle_whiteouts' failed with error code: $?"
+
+ # finally cleanup all the mounting stuff we did previously
+ chroot_cleanup_mounts || perror "'chroot_cleanup' failed with $?."
+}
+
+###############################################################################
+#
+# CLEANUP FUNCTIONS
+#
+# Helper to check if the given path is mounted
+chroot_check_mount_point() {
+ [ "$#" -eq 1 ] || perror "'chroot_check_mount_point' called with $# arguements, only 1 accepted."
+ local MOUNT="$1"
+ if [ "x$(mount | grep "$(readlink -f $MOUNT)")" != "x" ]; then
+ # still mounted
+ pdebug "'$MOUNT' is mounted!"
+ return 1
+ else
+ pdebug "'$MOUNT' is not mounted."
+ return 0
+ fi
+}
+
+# Helper to umount the given path
+chroot_umount() {
+ [ "$#" -eq 1 ] || perror "'chroot_umount' called with $# arguments, only 1 accepted."
+ local MOUNT="$1"
+
+ # check if MOUNT is mounted
+ if ! chroot_check_mount_point "${MOUNT}"; then
+ # still mounted
+ if umount -l "${MOUNT}"; then
+ pdebug "Successfully umounted '${MOUNT}'."
+ else
+ pwarning "Could not umount '${MOUNT}'! Trying again..."
+ # now it gets ugly
+ for i in `seq 1 5`; do
+ umount -l "${MOUNT}" && return 0
+ done
+ perror "Could not umount '${MOUNT}' after 5 tries! This shouldn't happen. Check your scripts."
+ fi
+ else
+ pdebug "'${MOUNT}' is not mounted."
+ fi
+
+ # better be safe than sorry
+ chroot_check_mount_point "$MOUNT" || perror "'$MOUNT' is still mounted, exiting before something bad happens..."
+}
+
+# Helper to cleanup the temporary mounts
+chroot_cleanup_mounts() {
+ if [[ "$(mount | grep -c $CHROOT_TEMPDIR)" -gt 0 ]]; then # No point in unmounting then...
+ for DIR in $CHROOT_BINDMOUNTS; do
+ chroot_umount "${CHROOT_MOUNTDIR}/${DIR}"
+ done
+ chroot_umount "${CHROOT_MOUNTDIR}"
+ chroot_umount "${CHROOT_BINDDIR}"
+ else
+ pinfo "Nothing chroot-related is mounted - exiting."
+ fi
+
+ # In case of 'new' overlayfs mount - should perhaps be handled via flag...
+ if [ -d "${CHROOT_WORKDIR}" ]; then
+ # Too much of a coward to rm -rf somewhere. Both directories should be empty so we use rmdir.
+ rmdir "${CHROOT_WORKDIR}/work" && pinfo "rmdir-ed CHROOT_WORKDIR/work ${CHROOT_WORKDIR}/work needed for new overlayfs mount syntax." \
+ || pinfo "Could not rmdir CHROOT_WORKDIR/work ${CHROOT_WORKDIR}/work - clean it by hand."
+ rmdir "${CHROOT_WORKDIR}" && pinfo "rmdir-ed CHROOT_WORKDIR ${CHROOT_WORKDIR} needed for new overlayfs mount syntax." \
+ || pinfo "Could not rmdir CHROOT_WORKDIR ${CHROOT_WORKDIR} needed for new overlayfs mount syntax - clean by hand."
+ fi
+}
diff --git a/core/includes/clean_module_funcs.inc b/core/includes/clean_module_funcs.inc
new file mode 100644
index 00000000..91f13942
--- /dev/null
+++ b/core/includes/clean_module_funcs.inc
@@ -0,0 +1,23 @@
+# Called when the sources for this module need to be fetched.
+# Not called if fetched_source.flag exists
+fetch_source() {
+ :
+}
+
+# Called when the module should be built.
+# Not called if build_complete.flag exists
+build() {
+ :
+}
+
+# Called after the relevant files from the module's build dir
+# have been copied to the target dir.
+post_copy() {
+ :
+}
+
+# Called when this module is about to be handled, before fetching the source.
+module_load() {
+ :
+}
+
diff --git a/core/includes/cleanup.inc b/core/includes/cleanup.inc
new file mode 100644
index 00000000..9716c841
--- /dev/null
+++ b/core/includes/cleanup.inc
@@ -0,0 +1,34 @@
+# -----------------------------------------------------------------------------
+#
+# Copyright (c) 2014 - OpenSLX GmbH
+#
+# This program is free software distributed under the GPL version 2.
+# See http://openslx.org/COPYING
+#
+# If you have any feedback please consult http://openslx.org/feedback and
+# send your suggestions, praise, or complaints to feedback@openslx.org
+#
+# General information about OpenSLX can be found at http://openslx.org/
+# -----------------------------------------------------------------------------
+#
+# Trapped cleanup functions
+#
+# -----------------------------------------------------------------------------
+
+__init () {
+ # run 'cleanexit' when CTRL-c is pressed, an abrupt program termination or exit happens
+ trap cleanexit SIGINT SIGTERM
+}
+
+# main cleaner function
+cleanexit() {
+ trap '' SIGINT SIGTERM # from now on, ignore INT and TERM
+ unset_quiet # needed to get trap functioning correctly
+ pwarning "SIGINT/SIGTERM triggered - cleaning up ..."
+ # unmount and remove the temporary chroot stuff
+ pinfo "Calling chroot_cleanup_mounts ..."
+ chroot_cleanup_mounts
+ # TODO vmware etc/vmware/config stuff here, if it is still needed
+ exit 1 # perhaps a better exit code?
+}
+
diff --git a/core/includes/distribution.inc b/core/includes/distribution.inc
new file mode 100644
index 00000000..57bafdf2
--- /dev/null
+++ b/core/includes/distribution.inc
@@ -0,0 +1,53 @@
+
+detect_distribution () {
+ # Set up distribution and package management
+ [ -z "$SYS_DISTRIBUTION" ] && perror "SYS_DISTRIBUTION not set (should be done by helper/distribution.inc)"
+ # Then determine packet manager
+ case "$SYS_DISTRIBUTION" in
+ ubuntu)
+ PACKET_MANAGER="apt"
+ PACKET_HANDLER="dpkg"
+ detect_ubuntu_lts
+ ;;
+ debian)
+ PACKET_MANAGER="apt"
+ PACKET_HANDLER="dpkg"
+ ;;
+ opensuse)
+ PACKET_MANAGER="zypper"
+ PACKET_HANDLER="rpm"
+ ;;
+ centos|scientific|fedora)
+ PACKET_MANAGER="yum"
+ PACKET_HANDLER="rpm"
+ ;;
+ *)
+ perror "Unknown Distribution: $SYS_DISTRIBUTION - Please specify its packet manager in core/bin/setup_target"
+ ;;
+ esac
+ # Get version - we mangle this quite a bit. first make sure it has no spaces, then split version at period (.), underscore (_) and dash (-)
+ SYS_VERSION=$(lsb_release -rs | tolower)
+ SYS_CODENAME=$(lsb_release -c|cut -f 2) # Codename: eg. Ubuntu raring, openSuse: Dartmouth etc.
+ local VERSION=$(echo $SYS_VERSION | sed -r 's/\s//g;s/[\._]/ /g;s/-//g')
+ local STRTMP=""
+ PRINT_SYS_VERSIONS="*.conf.$SYS_DISTRIBUTION"
+ SYS_VERSIONS="$SYS_DISTRIBUTION"
+ for PART in $VERSION; do
+ [ -z "$PART" ] && continue
+ STRTMP+=".$PART"
+ SYS_VERSIONS="${SYS_DISTRIBUTION}${STRTMP} $SYS_VERSIONS"
+ PRINT_SYS_VERSIONS="*.conf.${SYS_DISTRIBUTION}${STRTMP} $PRINT_SYS_VERSIONS"
+ done
+ pinfo "Config source order: *.conf first, then the first one of these (if found)"
+ pinfo "$PRINT_SYS_VERSIONS"
+}
+
+detect_ubuntu_lts () {
+ local TMP=$(dpkg -S /usr/bin/Xorg)
+ [[ "$TMP" == xserver-xorg* ]] || perror "Could not detect xserver package version (returned: $TMP)"
+ TMP=${TMP%: *}
+ TMP=${TMP#xserver-xorg-core}
+ pinfo "Ubuntu LTS Xorg suffix: $TMP"
+ UBUNTU_XORG_PKG_SUFFIX="$TMP"
+}
+
diff --git a/core/includes/downloader.inc b/core/includes/downloader.inc
new file mode 100644
index 00000000..7ec8d872
--- /dev/null
+++ b/core/includes/downloader.inc
@@ -0,0 +1,80 @@
+# helper functions for downloading files or packages
+
+# download a file. usage:
+# download FROM [TO]
+# 1. download "http://example.com/something.tar.gz"
+# 2. download "http://example.com/something.tar.gz" "somename.tar.gz"
+download () {
+ [ $# -lt 1 -o $# -gt 2 ] && perror "download called with $# arguments, need 1 or 2"
+ [ -z "$1" ] && perror "download: URL empty."
+ local URL="$1"
+ # If a sourceforge mirror is set in ./config, try to use it
+ if [ -n "$sourceforge_mirror" ] && [[ "$URL" != *use_mirror* ]] && [[ "$URL" == *sourceforge.net* || "$URL" == *.sf.net* ]]; then
+ if [[ "$URL" == *\?* ]]; then
+ URL+="&use_mirror=$sourceforge_mirror"
+ else
+ URL+="?use_mirror=$sourceforge_mirror"
+ fi
+ fi
+ if [ $# -eq 2 ]; then
+ [ -z "$2" ] && perror "download: target file name given but empty"
+ pinfo "Downloading $2 from '$URL'...."
+ wget -O "$2" "$URL"
+ local RET=$?
+ else
+ pinfo "Downloading '$URL'...."
+ wget "$URL"
+ local RET=$?
+ fi
+ [ "x$RET" != "x0" ] && perror "downloading $URL failed, wget returned exit code $RET"
+}
+
+# download a file and untar it. usage:
+# download_untar FROM TO_DIR [TEMPFILE]
+# 1. download_untar "http://example.com/something.tar.gz" "src/"
+# 2. download_untar "http://example.com/something.tar.gz" "src/" "temporary_name.tar.gz"
+download_untar () {
+ [ $# -lt 2 -o $# -gt 3 ] && perror "download_untar called with $# arguments, need 2 or 3"
+ local URL="$1"
+ local DEST="$2"
+ if [ $# -eq 2 ]; then
+ local TMPFILE=dltmp.$(basename "$URL")
+ else
+ local TMPFILE="$3"
+ fi
+ pdebug "$URL ---> $TMPFILE"
+ download "$URL" "$TMPFILE"
+ mkdir -p "$DEST"
+ pinfo "Unpacking to '$DEST'..."
+ tar xf "$TMPFILE" -C "${DEST}/"
+ local RET=$?
+ [ "x$RET" != "x0" ] && perror "could not untar $TMPFILE to $DEST (tar returned $RET)"
+ unlink "$TMPFILE"
+}
+
+# Download first param URL to second param path,
+# iff the local file does not exist or is empty.
+# Return 1 if remote file does not exist, 0 otherwise
+download_if_empty() {
+ [ $# -ne 2 ] && perror "download_if_empty: want 2 args, got $# ($@)"
+ local SRC="$1"
+ local DST="$2"
+ [ -s "$DST" ] && pdebug "Not downloading $DST: already there." && return 0
+ pdebug "Downloading $DST"
+ [ -e "$DST" ] && unlink "$DST"
+ local DSTDIR="$(dirname "$DST")"
+ pdebug "wgetting $SRC"
+ local TMP=$(mktemp)
+ wget -O "$TMP" "$SRC"
+ local RET=$?
+ if [ "x$RET" = "x0" -a -s "$TMP" ]; then
+ mkdir -p "$DSTDIR"
+ mv "$TMP" "$DST" || perror "Could not mv '$TMP' '$DST'"
+ return 0
+ fi
+ pdebug "WGET failed"
+ rmdir "$DSTDIR"
+ unlink "$TMP"
+ return 1
+}
+
diff --git a/core/includes/helper/README.helper b/core/includes/helper/README.helper
new file mode 100644
index 00000000..ebe242a0
--- /dev/null
+++ b/core/includes/helper/README.helper
@@ -0,0 +1,2 @@
+Put your helper units here
+Naming convention is <unitname>.inc
diff --git a/core/includes/helper/distribution.inc b/core/includes/helper/distribution.inc
new file mode 100644
index 00000000..f26ca46c
--- /dev/null
+++ b/core/includes/helper/distribution.inc
@@ -0,0 +1,4 @@
+# Get simple distribution name
+# This is also being used via ./openslx, not only via ./mltk,
+# so it has not been moved to remote/includes.
+SYS_DISTRIBUTION=$(lsb_release -is | tr '[A-Z]' '[a-z]' | sed -r 's/[^a-z0-9]//g;s/project$//g;s/scientificsl$/scientific/g')
diff --git a/core/includes/helper/fileutil.inc b/core/includes/helper/fileutil.inc
new file mode 100644
index 00000000..9dc2c07b
--- /dev/null
+++ b/core/includes/helper/fileutil.inc
@@ -0,0 +1,39 @@
+#
+# copy list of files using tar
+tarcopy () {
+ if [ $# -gt 0 -a "x$1" == "x-i" ]; then
+ shift
+ local IGNORE_ERROR="--ignore-failed-read"
+ else
+ local IGNORE_ERROR=
+ fi
+ if [ "x$IGNORE_TAR_ERROR" != "x" ]; then
+ unset IGNORE_TAR_ERROR
+ IGNORE_ERROR="--ignore-failed-read"
+ fi
+ [ $# -ne 2 ] && perror "Sanity check failed: tarcopy needs exactly two params, but $# were given."
+ local FROM=$(trim "$1")
+ local TO=$(trim "$2")
+ if [ -z "$FROM" ]; then
+ pwarning "tarcopy called with empty input list (dest was '$TO')"
+ return
+ fi
+ local SHORT=$FROM
+ [ ${#SHORT} -gt 30 ] && SHORT=$(echo "$SHORT" | sed ':a;N;$!ba;s/\n/ /g' | cut -c-25)...$(echo "$SHORT" | cut -c$[${#SHORT} - 4]-)
+ [ -z "$TO" ] && perror "tarcopy called with empty destination."
+ [ ! -d "$TO" ] && { mkdir -p "$TO" || perror "could not create destination "$TO" for tar-copy."; }
+ # TODO count files copied? would remove the need to do it everywhere :)
+ tar $IGNORE_ERROR -cpP $FROM | tar -xp -C "$TO" \
+ --transform 's,^/lib/udev/rules.d,/usr/lib/udev/rules.d,' \
+ 2> /dev/null
+ local PS=(${PIPESTATUS[*]})
+ [ "x$IGNORE_ERROR" == "x" -a "x${PS[0]}" != "x0" ] && perror "packing-part of tar-copy from '$SHORT' to '$TO' failed. (${PS[0]})"
+ [ "x${PS[1]}" != "x0" ] && perror "unpacking-part of tar-copy from '$SHORT' to '$TO' failed. (${PS[1]})"
+}
+
+# usage: CANONICALIZED_PATH=$(canonalize <path>)
+# usage with relative path requires you to be in the correct directory.
+canonicalize() {
+ cd -P -- "$(dirname -- "$1")" && printf '%s\n' "$(pwd -P)/$(basename -- "$1")"
+}
+
diff --git a/core/includes/helper/logging.inc b/core/includes/helper/logging.inc
new file mode 100644
index 00000000..1e1f5996
--- /dev/null
+++ b/core/includes/helper/logging.inc
@@ -0,0 +1,53 @@
+[ -n "$MLTK_QUIET" ] && return 0
+
+# This module is an exception, it doesn't use __init but runs commands directly...
+# Otherwise we cannot ensure that other module's init functions won't run first
+# and mess up the fds
+
+set_quiet () {
+ if [ "x$MLTK_QUIET" != "x1" ]; then
+ exec 6>&1 > "$LOG_DIR/stdout.log"
+ exec 7>&2 2> "$LOG_DIR/stderr.log"
+ MLTK_QUIET=1
+ fi
+}
+
+unset_quiet () {
+ if [ "x$MLTK_QUIET" == "x1" ]; then
+ exec 1>&6 6>&-
+ exec 2>&7 7>&-
+ exec 6>&2
+ MLTK_QUIET=0
+ fi
+}
+
+
+pinfo () {
+ echo -e "\033[38;5;10m[info]\033[0m $TOOL_STR $@" >&6
+}
+
+perror () {
+ echo -e "\033[38;5;9m[error]\033[0m $TOOL_STR $@" >&6
+ qnd_exit
+}
+
+pwarning () {
+ echo -e "\033[38;5;11m[warning]\033[0m $TOOL_STR $@" >&6
+}
+
+pdebug () {
+ if [ "x$MLTK_QUIET" = "x1" ]; then
+ echo -e "[DEBUG] $TOOL_STR $@" >&2
+ else
+ echo -e "\033[38;5;6m[debug]\033[0m $TOOL_STR $@" >&6
+ fi
+}
+
+exec 6>&2
+exec 8>&0 # This redirection is used for chroot debugging
+
+MLTK_QUIET=0
+declare -rg LOG_DIR=${ROOT_DIR}/var/log
+mkdir -p "$LOG_DIR" || perror "Could not create '$LOG_DIR'"
+true
+
diff --git a/core/includes/helper/string.inc b/core/includes/helper/string.inc
new file mode 100644
index 00000000..077f1719
--- /dev/null
+++ b/core/includes/helper/string.inc
@@ -0,0 +1,31 @@
+
+# usage: VAR=$(trim " string ")
+trim() {
+ local var=$1
+ var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters
+ var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters
+ echo -n "$var"
+}
+
+# Inline version of trim, use when piping
+itrim () {
+ sed -r 's/^\s+//g;s/\s+$//g'
+}
+
+escape_search() {
+ sed -e 's/[]\/()$*.^|[]/\\&/g'
+
+}
+
+escape_replace() {
+ sed -e 's/[\/&]/\\&/g'
+}
+
+tolower () {
+ tr '[A-Z]' '[a-z]'
+}
+
+toupper () {
+ tr '[a-z]' '[A-Z]'
+}
+
diff --git a/core/includes/kernel.inc b/core/includes/kernel.inc
new file mode 100644
index 00000000..671dca42
--- /dev/null
+++ b/core/includes/kernel.inc
@@ -0,0 +1,218 @@
+#
+# Common functions to copy kernel related files
+#
+############################################################
+#
+#
+# copies kernel modules as given in the module config file
+# * depends on 'depmod'
+# * requires REQUIRED_KERNEL_MODULES to be set.
+# (entries must be a relative path to /lib/modules/<SYSTEM_KERNEL_LONG>)
+#
+# ex: for /lib/modules/3.2.0/kernel/fs/nfs/nfs.ko
+# must be given as kernel/fs/nfs/nfs.ko
+#
+
+# set global KERNEL_BASE_DIR as in the directory containing lib/modules and lib/firmware
+# for system kernel, that is "/" and for an openslx kernel KERNEL_BUILD_DIR
+KERNEL_BASE_DIR=""
+
+get_kernel_version () {
+ pinfo "Running fallback get_kernel_version()"
+ # Running system kernel version
+ if [ -z "$SYSTEM_KERNEL_LONG" ]; then
+ declare -rg SYSTEM_KERNEL_LONG=$(uname -r)
+ declare -rg SYSTEM_KERNEL_SHORT=$(grep -o -E '^[0-9\.]+' <<<$SYSTEM_KERNEL_LONG)
+ fi
+}
+
+check_kernel_base_dir () {
+
+ # check if KERNEL_BASE_DIR was set, if not we don't know
+ # whether kernel-openslx or kernel-system has been built
+ # and therefore not were to look for kernel modules,
+ # firmware and the kernel itself
+
+ if [ -z "${KERNEL_BASE_DIR}" ]; then
+ # this is bad, abort
+ perror "KERNEL_BASE_DIR is not set. The kernel module did not run properly"
+ else
+ pinfo "KERNEL_BASE_DIR is '$KERNEL_BASE_DIR'"
+ fi
+
+}
+
+copy_kernel_modules () {
+ pinfo "Copying kernel modules for kernel ${SYSTEM_KERNEL_LONG}..."
+ [ -z "${REQUIRED_KERNEL_MODULES}" ] && perror "REQUIRED_KERNEL_MODULES is empty. Check your config file."
+ [ -z "${KERNEL_HEADERS_DIR}" ] && perror "KERNEL_HEADERS_DIR is empty. Kernel headers appears to be missing."
+
+
+ check_kernel_base_dir
+
+ local OLD_DIR="$(pwd)"
+
+ #
+ # process modules list
+ #
+ # search for modules in KERNEL_BASE_DIR
+ cd "${KERNEL_BASE_DIR}" || perror "Could not cd to ${KERNEL_BASE_DIR}"
+
+ local KERNEL_MODULES_DIR="lib/modules/${TARGET_KERNEL_LONG}"
+ local KERNEL_MODULES_LIST=""
+ local REQUIRED_KERNEL_MODULES_EXPANDED=""
+ local KERNEL_MODULE=""
+ local KERNEL_MODULE_PATH=""
+ local ELEM=""
+
+ # Do some fancy stuff to allow wildcards etc. in required kernel modules.
+ cd "${KERNEL_MODULES_DIR}"
+ for KERNEL_MODULE in ${REQUIRED_KERNEL_MODULES}; do
+ for ELEM in $KERNEL_MODULE; do
+ echo $ELEM | grep '\*' && pwarning "Could not expand '$ELEM'." && continue
+ REQUIRED_KERNEL_MODULES_EXPANDED+=" $ELEM"
+ done
+ done
+ cd - 2>/dev/null
+ pinfo "Expanded the list of $(echo "$REQUIRED_KERNEL_MODULES" | wc -w) required kernel modules to $(echo "$REQUIRED_KERNEL_MODULES_EXPANDED" | wc -w)"
+
+ #
+ # now loop over given modules and locate them
+ #
+ for KERNEL_MODULE in ${REQUIRED_KERNEL_MODULES_EXPANDED}; do
+ local KERNEL_MODULE_PATH="${KERNEL_MODULES_DIR}/${KERNEL_MODULE}"
+ if grep "^${KERNEL_MODULE}$" "${KERNEL_BASE_DIR}/${KERNEL_MODULES_DIR}/modules.builtin" >/dev/null; then
+ pdebug "Already built-in ${KERNEL_MODULE}."
+ elif [ -e "${KERNEL_MODULE_PATH}" ]; then
+ pdebug "Copying '${KERNEL_MODULE_PATH}'"
+ KERNEL_MODULES_LIST+=" ${KERNEL_MODULE_PATH}"
+ else
+ pwarning "Module ${KERNEL_MODULE} not found. Skipping. (might cause problems on certain clients!)"
+ continue
+ fi
+
+ # check for dependencies
+ local DEPS=$(grep "${KERNEL_MODULE}:" "${KERNEL_BASE_DIR}/${KERNEL_MODULES_DIR}/modules.dep" | cut -d ":" -f2-)
+ if [ ! -z "$DEPS" ]; then
+ for DEP in $DEPS; do
+ pdebug "Adding dep: ${KERNEL_MODULES_DIR}/$DEP"
+ KERNEL_MODULES_LIST+=" ${KERNEL_MODULES_DIR}/$DEP"
+ done
+ else
+ pdebug "${KERNEL_MODULE} has no dependencies."
+ fi
+ done
+
+ if [ ! -z "${KERNEL_MODULES_LIST}" ]; then
+ local COUNT=$(echo "${KERNEL_MODULES_LIST}" | wc -w)
+ pinfo "Copying $COUNT modules to target directory."
+ tarcopy "${KERNEL_MODULES_LIST}" "${TARGET_BUILD_DIR}"
+ fi
+
+ #
+ # generate modules map files
+ #
+ # first strip modules.order of all the modules we don't use
+ cat "${KERNEL_MODULES_DIR}/modules.order" | grep -E "$(echo ${REQUIRED_KERNEL_MODULES} | tr '\ ' '|' | tr '_' '.' | tr '-' '.')" \
+ >> "${TARGET_BUILD_DIR}/${KERNEL_MODULES_DIR}/modules.order"
+ # copy list of builtin kernel modules
+ cp "${KERNEL_MODULES_DIR}/modules.builtin" "${TARGET_BUILD_DIR}/${KERNEL_MODULES_DIR}"
+ # with modules.order and modules.builtin, we can run depmod for the rest of the files
+ depmod -b "${TARGET_BUILD_DIR}" -a "${TARGET_KERNEL_LONG}"
+
+ # go back to wherever we were
+ cd "${OLD_DIR}" || perror "Could not cd back to ${OLD_DIR}."
+}
+
+copy_firmware () {
+
+ pinfo "Copying firmware for kernel ${SYSTEM_KERNEL_LONG}..."
+ [ -z "${REQUIRED_FIRMWARE}" ] && perror "REQUIRED_FIRMWARE is empty. Check your config file."
+
+ check_kernel_base_dir
+
+ local OLD_DIR=$(pwd)
+ #
+ # process firmware list
+ #
+ cd "${KERNEL_BASE_DIR}" || perror "Could not cd to ${KERNEL_BASE_DIR}"
+ local FIRMWARE_DIR="lib/firmware"
+ local FIRMWARE_SYSTEM_LIST=""
+ local FIRMWARE_BUILD_LIST=""
+ for FIRMWARE in ${REQUIRED_FIRMWARE}; do
+ local FOUND=0
+ # check for firmware in the build directory of the kernel
+ for CANDIDATE in "${FIRMWARE_DIR}/${FIRMWARE}" "${FIRMWARE_DIR}/${TARGET_KERNEL_LONG}/${FIRMWARE}"; do
+ if [ -e "${CANDIDATE}" ]; then
+ pdebug "Copying from kernel base dir ('$KERNEL_BASE_DIR'): '${CANDIDATE}'"
+ FIRMWARE_BUILD_LIST+=" ${CANDIDATE}"
+ FOUND=1
+ fi
+ done
+
+ # dont look under / if KERNEL_BASE_DIR is already /
+ #if [ "x${KERNEL_BASE_DIR}" == "x/" ]; then
+ # [ $FOUND -ne 1 ] && pwarning "Neither '${FIRMWARE_DIR}/${FIRMWARE}' nor '${FIRMWARE_DIR}/${TARGET_KERNEL_LONG}/${FIRMWARE}' found on the system"
+ # continue
+ #fi
+
+ # if we didn't found it in the kernel build directory, check for firmware in the system firmware directory
+ #if [ $FOUND -ne 1 ]; then
+ for CANDIDATE in "/${FIRMWARE_DIR}/${FIRMWARE}" "/${FIRMWARE_DIR}/${SYSTEM_KERNEL_LONG}/${FIRMWARE}"; do
+ if [ -e "${CANDIDATE}" ]; then
+ if [ $(echo "${CANDIDATE}" | grep -c "${SYSTEM_KERNEL_LONG}") -eq 0 ]; then
+ pdebug "Copying from system: '${CANDIDATE}'"
+ FIRMWARE_SYSTEM_LIST+=" ${CANDIDATE}"
+ else
+ pdebug "Copying from system: '${CANDIDATE}' to ${FIRMWARE_DIR}/${TARGET_KERNEL_LONG}/${FIRMWARE}"
+ FIRMWARE_SYSTEM_LIST+=" /${FIRMWARE_DIR}/${SYSTEM_KERNEL_LONG}/${FIRMWARE}"
+ fi
+ FOUND=1
+ fi
+ done
+ #fi
+
+ [ $FOUND -ne 1 ] && pwarning "Neither '${FIRMWARE_DIR}/${FIRMWARE}' nor '${FIRMWARE_DIR}/${TARGET_KERNEL_LONG}/${FIRMWARE}' "\
+ " was found on the system. Skipping. (might cause problems on certain clients!)"
+ done
+
+ for LIST in "${FIRMWARE_SYSTEM_LIST}" "${FIRMWARE_BUILD_LIST}"; do
+ [ -z "${LIST}" ] && continue
+ echo "${LIST}" >> "${MODULE_BUILD_DIR}/fwlist"
+ local COUNT=$(echo "${LIST}" | wc -w)
+ pinfo "Copying $COUNT firmware to target directory."
+ tarcopy "${LIST}" "${TARGET_BUILD_DIR}"
+ done
+
+ # only for kernel-openslx
+ # post-process to fix the path of the firmwares found on the system unter /lib/firmware/$(uname -r)
+ # which have to be copied to /lib/firmware/${TARGET_KERNEL_LONG}
+ if [ "x${KERNEL_BASE_DIR}" != "x/" ]; then
+ if [ -d "${TARGET_BUILD_DIR}/lib/firmware/${SYSTEM_KERNEL_LONG}" ]; then
+ mkdir -p "${TARGET_BUILD_DIR}/lib/firmware/${TARGET_KERNEL_LONG}/"
+ cd "${TARGET_BUILD_DIR}/lib/firmware/${SYSTEM_KERNEL_LONG}" || perror "old kernel but no old kernel"
+ tarcopy "$(ls)" "${TARGET_BUILD_DIR}/lib/firmware/${TARGET_KERNEL_LONG}/"
+ cd -
+ rm -r "${TARGET_BUILD_DIR}/lib/firmware/${SYSTEM_KERNEL_LONG}" || perror "something went very wrong..."
+ else
+ pdebug "No ${TARGET_BUILD_DIR}/lib/firmware/${SYSTEM_KERNEL_LONG} directory, skipping the merge."
+ fi
+ fi
+
+ cd "${OLD_DIR}" || perror "Could not cd back to ${OLD_DIR}."
+}
+
+copy_kernel () {
+ check_kernel_base_dir
+
+ local TOOL_STR="$TOOL_STR copy_kernel:"
+ local KERNEL_DIR="${ROOT_DIR}/var/builds/kernel"
+
+ [ -d "${KERNEL_DIR}" ] || mkdir -p "${KERNEL_DIR}"
+
+ cp "${ROOT_DIR}/tmp/work/kernel/build/kernel" "${KERNEL_DIR}" || perror "Could not copy '${ROOT_DIR}/tmp/work/kernel/build/kernel' to '${KERNEL_DIR}'"
+
+ # kernel has 0600 perms since ubuntu 14.04, change that once we copied it
+ chmod +r "${KERNEL_DIR}/kernel"
+}
+
diff --git a/core/includes/keyvalueutil.inc b/core/includes/keyvalueutil.inc
new file mode 100644
index 00000000..a0a89db7
--- /dev/null
+++ b/core/includes/keyvalueutil.inc
@@ -0,0 +1,32 @@
+# Helper file for managing key-value containing files
+# There are some specialized conveinience functions here first
+# that mostly just pass a predefined filename to the genric function
+# at the end
+
+# Add the given environment variable to /etc/environment
+add_env () {
+ [ $# -ne 2 ] && perror "Usage: $0 'key' 'value'"
+ [ -z "$1" ] && perror "$0: Empty key!"
+ add_key_value "/etc/environment" "$1" "$2"
+}
+
+#
+# Adds the given key-value-pair to a given file
+# The file will be relative to the current target build dir,
+# even if it starts with a slash.
+# Will perror if the key already exists with a different value
+add_key_value () {
+ [ $# -ne 3 ] && perror "Usage: $0 'file' 'key' 'value'"
+ [ -z "$TARGET_BUILD_DIR" ] && perror "No TARGET_BUILD_DIR set. Aborting for safety."
+ local FILE="$TARGET_BUILD_DIR/$1"
+ local KEY="$2"
+ local VALUE="$(echo "$3" | sed "s/'/\\\\'/g")" # \\\\\\\\\\\\\\\\\\\\\\ßß
+ if [ -s "$FILE" ]; then
+ local CURRENT="$(grep -E "^\s*$KEY=.*$" "$FILE" | awk -F '=' '{$1=""; printf $0}' | itrim)"
+ [ -n "$CURRENT" -a "'$VALUE'" != "$CURRENT" ] && perror "Cannot set $KEY to '$VALUE' as it is already set to $CURRENT"
+ [ -n "$CURRENT" ] && return 0
+ fi
+ mkdir -p "$(dirname "$FILE")"
+ echo "$KEY='$VALUE'" >> "$FILE"
+}
+
diff --git a/core/includes/packagemanager.inc b/core/includes/packagemanager.inc
new file mode 100644
index 00000000..9fcb4b87
--- /dev/null
+++ b/core/includes/packagemanager.inc
@@ -0,0 +1,187 @@
+#!/bin/bash
+#
+# get all files of required packages by a module
+#
+# Usage:
+# list_content_packages
+# - lists all files/directories in REQUIRED_CONTENT_PACKAGES
+# list_content_packages --files
+# - lists all files in REQUIRED_CONTENT_PACKAGES
+# list_content_packages --dirs
+# - lists all dirs in REQUIRED_CONTENT_PACKAGES
+#
+# NOTE: additional packages needed to be listed can be given
+# through the environment variable EXTRA_PACKAGES
+
+list_content_packages() {
+ [ -z "$REQUIRED_CONTENT_PACKAGES" ] && pinfo "No required packages for $TOOL" && return 1
+ [ $# -gt 2 ] && perror "'list_content_packages' accepts only 1 or no args. $# given."
+ local PACKAGE=""
+ for PACKAGE in $REQUIRED_CONTENT_PACKAGES $EXTRA_PACKAGES; do
+ list_content_package $1 $PACKAGE
+ done
+
+}
+list_content_package(){
+ #[ -z "$EXTRA_PACKAGES" ] || pinfo "Listing additional packages: $EXTRA_PACKAGES"
+ [ $# -gt 2 ] && perror "'list_content_package' accepts max 2 args. $# given."
+ local OP="-e"
+ case "$1" in
+ "--files")
+ OP="-f"
+ ;;
+ "--dirs")
+ OP="-d"
+ ;;
+ "")
+ OP="-e"
+ ;;
+ *)
+ perror "'list_content_packages' invalid argument: $1"
+ ;;
+ esac
+ local PACKAGE="$2"
+ local OPTIONAL="$(echo "$PACKAGE" | cut -c 1)"
+ [ "x$OPTIONAL" = "x@" ] && PACKAGE="$(echo "$PACKAGE" | cut -c 2-)"
+ local FILES=""
+ if [ "$PACKET_HANDLER" = "dpkg" ]; then
+ PACKAGECOMMAND="dpkg -L"
+ elif [ "$PACKET_HANDLER" = "rpm" ]; then
+ PACKAGECOMMAND="rpm -ql"
+ fi
+
+ if [ -n "$REQUIRED_PACKET_FILES_BLACKLIST" ]; then
+ FILES="$($PACKAGECOMMAND "$PACKAGE" | grep "^/" | \
+ grep -v "$REQUIRED_PACKET_FILES_BLACKLIST" | \
+ grep -v -E 'share/(man|doc)|/var/run|/var/log|/etc/init\.d'; \
+ echo ":###:${PIPESTATUS[0]}")"
+ else
+ FILES="$($PACKAGECOMMAND "$PACKAGE" | grep "^/" | grep -v -E 'share/(man|doc)|/var/run|/var/log|/etc/init\.d'; echo ":###:${PIPESTATUS[0]}")"
+ fi
+# FILES="$(rpm -ql "$PACKAGE" | grep "^/" | grep -v -E 'share/(man|doc)|/var/run|/var/log'; echo ":###:${PIPESTATUS[0]}")"
+
+ # ugly hack to get our return value
+ local LPRET=$(echo "$FILES" | awk -F ':###:' '{printf $2}')
+ FILES=$(echo "$FILES" | awk -F ':###:' '{print $1}')
+ if [ "x$LPRET" != "x0" -a "x$OPTIONAL" != "x@" ]; then
+ pdebug "FILES: '$FILES'"
+ perror "dpkg/rpm exited with code '$LPRET' for required package ${PACKAGE}."
+ fi
+ [ "x$LPRET" != "x0" ] && pwarning "dpkg/rpm exited with code '$LPRET' for optional package ${PACKAGE}." && continue
+ [ -z "$FILES" ] && pwarning "list_packet_files empty for packet ${PACKAGE}." && continue
+ pdebug "Packet $PACKAGE has $(echo $FILES | wc -w) files..."
+ for FILE in $FILES; do
+ [ "$OP" "$FILE" ] && echo "$FILE"
+ done
+}
+#
+# Convenience function
+#
+list_packet_files() {
+ list_content_packages --files
+}
+
+#
+# Convenience function
+#
+# install all dependencies of a module
+# goes through all package as given in the variable REQUIRED_INSTALLED_PACKAGES
+install_dependencies() {
+ [ -z "$REQUIRED_INSTALLED_PACKAGES" ] && return
+ install_packages "$REQUIRED_INSTALLED_PACKAGES"
+}
+
+#
+# install given packet through system's packet manager
+# uses PACKET_HANDLER as determined in helper/system.inc
+#
+install_packages() {
+ [ $# -eq 0 ] && perror "Sanity check failed: no argument given to install_package"
+ local PACKAGE_LIST="$@"
+ local INSTALLED_PACKAGES=""
+ local LRET=""
+
+ for PKG in ${PACKAGE_LIST}; do
+ # check if installed:
+ # this is done mainly for performance reasons, even though
+ # reinstalling all packages is a valid possibility too...
+ if [ "x$PACKET_HANDLER" == "xdpkg" ]; then
+ dpkg -l ${PKG} > /dev/null 2>&1
+ LRET=$?
+ elif [ "x$PACKET_HANDLER" == "xrpm" ]; then
+ rpm -ql "${PKG}" > /dev/null 2>&1
+ LRET=$?
+ else
+ perror "No packet manager / handler determined, this should not happen!"
+ fi
+
+ if [ "x$LRET" == "x0" ]; then
+ # check if it is completly installed, not just leftover configuration files
+ if [ "x$PACKET_HANDLER" == "xdpkg" ]; then
+ local INSTALL_STATUS=$(dpkg -l "${PKG}" | grep "${PKG}" | cut -c1-2)
+ if [[ $INSTALL_STATUS != "ii" ]]; then
+ pinfo "$PKG is either missing or a meta-package! Installing it to be sure..."
+ install_package "${PKG}"
+ else
+ # package installed
+ pdebug "${PKG} installed!"
+ fi
+ elif [ "x$PACKET_HANDLER" == "xrpm" ]; then
+ # TODO: blindly install again for now
+ rpm -q "${PKG}" >/dev/null 2>&1 || install_package "${PKG}"
+ fi
+ else
+ # package not installed
+ pdebug "$PKG not installed!"
+ install_package $PKG
+ fi
+ done
+ [ ! -z "$INSTALLED_PACKAGES" ] && pinfo "New packages installed: ${INSTALLED_PACKAGES}"
+}
+
+#
+# install individual package depending on package manager
+#
+install_package() {
+ if [ "$#" -ne 1 ]; then
+ perror "Only call install_package with one argument!"
+ fi
+
+ if [ "x$PACKET_MANAGER" == "xapt" ]; then
+ apt-get install -y ${PKG}
+ local IRET=$?
+ if [ "x$IRET" == "x0" ]; then
+ # $PGK was installed successfully
+ INSTALLED_PACKAGES+="$PKG "
+ else
+ # PKG was not installed
+ # TODO error handling
+ perror "install_packages: apt-get failed with '$IRET' for package '$PKG'"
+ fi
+ elif [ "x$PACKET_MANAGER" == "xzypper" ]; then
+ zypper --no-refresh --non-interactive install --force-resolution ${PKG}
+ local IRET=$?
+ if [ "x$IRET" == "x0" ]; then
+ # $PGK was installed successfully
+ INSTALLED_PACKAGES+="$PKG "
+ else
+ # PKG was not installed
+ # TODO error handling
+ perror "install_packages: zypper failed with '$IRET' for package '$PKG'"
+ fi
+ elif [ "x$PACKET_MANAGER" == "xyum" ]; then
+ yum --assumeyes install ${PKG}
+ local IRET=$?
+ if [ "x$IRET" == "x0" ]; then
+ # $PGK was installed successfully
+ INSTALLED_PACKAGES+="$PKG "
+ else
+ # PKG was not installed
+ # TODO error handling
+ perror "install_packages: yum failed with '$IRET' for package '$PKG'"
+ fi
+ else
+ perror "No packet manager determined, this should not happen!"
+ fi
+}
+
diff --git a/core/includes/paths.inc b/core/includes/paths.inc
new file mode 100644
index 00000000..928ca964
--- /dev/null
+++ b/core/includes/paths.inc
@@ -0,0 +1,31 @@
+#
+# This include tries to determine system paths needed by mltk-modules
+# The idea here is to have a central place to determine paths required
+# by different modules instead of each module determining them on its own.
+#
+
+__init () {
+ # Location of PAM-modules
+ for CANDIDATE in $(strings "$(ldd "$(which login)" | grep libpam.so | head -n 1 | awk '{print $3}')" | grep /lib); do
+ [ -f "$CANDIDATE/pam_unix.so" ] && declare -rg SYS_PAM_MODULES_PATH="$CANDIDATE" && break
+ done
+
+ [ -z "$SYS_PAM_MODULES_PATH" ] && perror "Failed to find pam_unix.so on this system."
+
+ #
+ # Figure out if we have split usr
+ if [ -L "/bin" -a -L "/lib" ]; then
+ declare -rg USR_SPLIT="no"
+ else
+ declare -rg USR_SPLIT="yes"
+ fi
+ pdebug "/bin and /lib are split from /usr/bin and /usr/lib (they are not symlinks): $USR_SPLIT"
+
+ SYS_LIB_PATHS=""
+ for DIR in /lib /lib32 /lib64 /usr/lib /usr/lib32 /usr/lib64; do
+ [ -d "$DIR" -a ! -L "$DIR" ] && SYS_LIB_PATHS+=" $DIR"
+ done
+ declare -rg SYS_LIB_PATHS=$SYS_LIB_PATHS
+ pdebug "System lib paths: $SYS_LIB_PATHS"
+}
+
diff --git a/core/includes/qt.inc b/core/includes/qt.inc
new file mode 100644
index 00000000..2b13c1f3
--- /dev/null
+++ b/core/includes/qt.inc
@@ -0,0 +1,30 @@
+activate_qt() {
+ [ $# -eq 1 ] || perror "activate_qt() requires the version as only argument, $# given."
+ local QT_VERSION="$1"
+ # Handle environment
+ export QT_SELECT="qt${QT_VERSION}"
+ QTDIR="/usr/share/qt${QT_VERSION}"
+ [ -d "$QTDIR" ] && export QTDIR
+ # Handle qt chooser
+ [ -d "/usr/share/qtchooser" ] || return 0 # No qtchooser found, hope for the best...
+ local QT_DEFAULT="$(readlink -f /usr/share/qtchooser/default.conf)"
+ if [ -e "$QT_DEFAULT" ]; then
+ [[ "$(basename "$QT_DEFAULT")" =~ ^qt$QT_VERSION.*\.conf$ ]] || perror "Something with QT4 is bad. Check '/usr/share/qtchooser'"
+ else
+ # no default active, try to set it
+ local QT_CANDIDATES="$(find "/usr/share/qtchooser" -name "qt$QT_VERSION*" | grep -E "$ARCHREGEX")"
+ [ -z "$QT_CANDIDATES" ] && QT_CANDIDATES="$(find "/usr/share/qtchooser" -name "qt$QT_VERSION*")"
+ local QTS="$(echo "$QT_CANDIDATES" | wc -w)"
+ if [ "$QTS" -eq 1 ]; then
+ # we found it, lets activate it and hope for the best
+ ln -sf "/usr/share/qtchooser/$QT_CANDIDATES" "/usr/share/qtchooser/default.conf" \
+ || perror "Could not link 'default.conf' to '/usr/share/qtchooser/$QT_CANDIDATES'"
+ elif [ "$QTS" -eq 0 ]; then
+ perror "No qt ${QT_VERSION} config found in /usr/share/qtchooser"
+ else
+ # here we have the case with different qt4 files or none at all
+ # in both cases, there is a bigger problem we shouldn't try to guess which one is correct
+ perror "More than one qt ${QT_VERSION} version found in '/usr/share/qtchooser'. The configuration is messy. Fix it first."
+ fi
+ fi
+}
diff --git a/core/includes/system.inc b/core/includes/system.inc
new file mode 100644
index 00000000..ea953d69
--- /dev/null
+++ b/core/includes/system.inc
@@ -0,0 +1,37 @@
+# Helper to determine various system information
+
+__init () {
+ # determine architecture triplet from the path of libc needed by the executing shell.
+ # please try not to use this to find/fetch libs in /lib or /usr/lib.
+ # Use ARCH_LIB_DIR, like "$ARCH_LIB_DIR/somefile.so" or "/usr$ARCH_LIB_DIR/somefile.so"
+ ARCH_TRIPLET=$(ldd $SHELL|grep "libc.so" | awk -F "/" '{print $3}')
+ [[ $ARCH_TRIPLET == *\(*\) ]] && ARCH_TRIPLET=""
+
+ ARCH_LIB_DIR=$(ldd $SHELL | grep "libc.so" | sed -r 's#^.*(/lib.*)/libc.so.*$#\1#g')
+ [ -z "$ARCH_LIB_DIR" -o ! -d "$ARCH_LIB_DIR" ] && perror "Could not determine arch dependent lib dir (where libc.so resides)"
+
+ # determine number of CPU cores
+ declare -rg CPU_CORES=$(cat /proc/cpuinfo | grep processor | wc -l)
+ export MAKEFLAGS="-j$CPU_CORES"
+
+ # Determine if we have lib64
+ if [ "$(uname -m)x" = "x86_64x" ]; then
+ # Setting LIB64, as openSuse differentiates but Ubuntu does not:
+ case $SYS_DISTRIBUTION in
+ ubuntu | debian) LIB64="lib" ;;
+ opensuse | fedora | centos) LIB64="lib64" ;;
+ *) perror "Cannot set LIB64, SYS_DISTRIBUTION: $SYS_DISTRIBUTION unknown!" ;;
+ esac
+ AMD64_I386=amd64
+ X86_64_I586=x86_64
+ AMD64_X86=amd64
+ ARCHREGEX="(amd64|x86[_-]64)"
+ else
+ LIB64="lib"
+ AMD64_I386=i386
+ X86_64_I586=i586
+ AMD64_X86=x86
+ ARCHREGEX="(i[3456]86|x86[_-]32)"
+ fi
+}
+
diff --git a/core/includes/useradd.inc b/core/includes/useradd.inc
new file mode 100644
index 00000000..2beaaeae
--- /dev/null
+++ b/core/includes/useradd.inc
@@ -0,0 +1,238 @@
+# This helper is stupid because it reimplements what useradd etc. do.
+# They could do the job just fine by using the -R option - maybe change this
+# dome day.
+#
+
+# Add a user to the system
+#
+# Usage:
+# either do ID_OF_REQUESTED_USER=$(add_user herbert)
+# or ID_OF_USER=$(USER=herbert GROUP=somegroup PASSWORD=secret123 add_user)
+# Valid params are:
+# USER, GROUP, USERID, GROUPID, PASSWORD, USERHOME, USERSHELL
+# defaults are no password, home=/nonexistent, usershell=/bin/false
+# IDs will be generated in the range of 5-999 if not explicitly given
+# TODO: Make it possible to pass a range of IDs if you don't want one <1000 but don't care about the exact ID
+
+declare -rg NAME_REGEX='^[a-z][-a-z0-9]*$'
+
+# Generate a UID for a given USERNAME. Return existing UID if possible, generate new one otherwise
+generate_uid()
+{
+ [ $# -ne 1 ] && perror "generate_uid fail. want 1 argument."
+ [ -z "${_PASSWD}" ] && perror "passwd file not set."
+ local _UID=$(grep -E "^$1:[^:]*:[0-9]+:" "${_PASSWD}" | head -1 | awk -F ':' '{print $3}')
+ if [ "x${_UID}" = "x" ]
+ then
+ local _TRIES=0
+ while [ ${_TRIES} -lt 50 ]
+ do
+ _TRIES=$[ ${_TRIES} + 1 ]
+ _UID=$[ 5 + $RANDOM % 900 ]
+ local _TEST=$(grep -E "^[^:]+:[^:]*:${_UID}:" "${_PASSWD}")
+ [ "x${_TEST}" = "x" ] && break
+ done
+ [ ${_TRIES} -ge 50 ] && perror "Generating a UID failed."
+ fi
+ echo ${_UID}
+}
+
+# Echo gid of given user if existent, nothing otherwise
+get_gid_for_user()
+{
+ [ $# -ne 1 ] && perror "get_gid_for_user fail. want 1 argument."
+ [ -z "${_PASSWD}" ] && perror "passwd file not set."
+ local _GID=$(grep -E "^$1:[^:]*:[^:]*:[0-9]+:" "${_PASSWD}" | head -1 | awk -F ':' '{print $4}')
+ echo ${_GID}
+}
+
+# Echo group name of given gid, nothing if non-existent
+get_group_for_gid()
+{
+ [ $# -ne 1 ] && perror "get_group_for_gid fail. want 1 argument."
+ [ -z "${_GROUP}" ] && perror "group file not set."
+ local _NAME=$(grep -E "^[^:]*:[^:]*:$1:" "${_GROUP}" | head -1 | awk -F ':' '{print $1}')
+ echo ${_NAME}
+}
+
+# Generate a GID for a given GROUPNAME. Return existing GID if possible, generate new one otherwise
+generate_gid()
+{
+ [ $# -ne 2 ] && perror "generate_gid fail. want 2 arguments."
+ [ -z "${_GROUP}" ] && perror "group file not set."
+ local _GID=$(grep -E "^$1:[^:]*:[0-9]+:" "${_GROUP}" | head -1 | awk -F ':' '{print $3}')
+ if [ "x${_GID}" = "x" ]
+ then
+ # group does not exist, try to create
+ local _TRIES=0
+ _GID=$2 # try to use uid as gid if not taken yet
+ while [ ${_TRIES} -lt 50 ]
+ do
+ _TRIES=$[ ${_TRIES} + 1 ]
+ local _TEST=$(grep -E "^[^:]+:[^:]*:${_GID}:" "${_GROUP}")
+ [ "x${_TEST}" = "x" ] && break
+ _GID=$[ 5 + $RANDOM % 900 ] # using uid as gid not possible, generate new one
+ done
+ [ ${_TRIES} -ge 50 ] && perror "Generating a GID failed."
+ fi
+ echo ${_GID}
+}
+
+add_user() {
+ [ -z "${TARGET_BUILD_DIR}" ] && perror "add_user: TARGET_BUILD_DIR not set"
+ if [ -z "${USER}" -a $# -eq 0 ]
+ then
+ pwarning " ** add_user usage **"
+ pwarning "add_user <username>"
+ pwarning "OR"
+ pwarning "USER=<username> [GROUP=<groupname>] [USERID=<userid>] [GROUPID=<groupid>] [USERHOME=<homedir>] [USERSHELL=<shell>] [PASSWORD=<pass>] add_user"
+ perror "Aborting, please fix your script."
+ fi
+ local _PASSWD=${TARGET_BUILD_DIR}/etc/passwd
+ local _GROUP=${TARGET_BUILD_DIR}/etc/group
+ local _SHADOW=${TARGET_BUILD_DIR}/etc/shadow
+ init_users_and_groups
+ [ ! -f "${_PASSWD}" ] && perror "add_user: password file does not exist in target system. (build base first)"
+ [ ! -f "${_GROUP}" ] && perror "add_user: group file does not exist in target system. (build base first)"
+ [ ! -f "${_SHADOW}" ] && perror "add_user: shadow file does not exist in target system. (build base first)"
+ if [ "x$1" != "x" ]
+ then
+ local USER=$1
+ local GROUP=""
+ local USERID=""
+ local GROUPID=""
+ local USERHOME=""
+ local USERSHELL=""
+ local PASSWORD=""
+ fi
+ USER=$(trim "$USER")
+ if ! [[ $USER =~ $NAME_REGEX ]]; then
+ perror "Invalid username: $USER"
+ fi
+ [ -z "$GROUPID" ] && local GROUPID=$(get_gid_for_user "${USER}")
+ [ -z "$GROUP" -a -n "$GROUPID" ] && local GROUP=$(get_group_for_gid "${GROUPID}")
+ [ -z "$GROUP" ] && local GROUP=$USER
+ GROUP=$(trim "$GROUP")
+ if ! [[ $GROUP =~ $NAME_REGEX ]]; then
+ perror "Invalid group: $GROUP"
+ fi
+ [ "x$USERID" = "x" ] && local USERID=$(generate_uid "${USER}")
+ USERID=$(trim "$USERID")
+ [ "$USERID" -lt "0" -o "$USERID" -gt "65535" ] && perror "Invalid userid: $USERID"
+ [ -z "$GROUPID" ] && local GROUPID=$(generate_gid "${GROUP}" "${USERID}")
+ GROUPID=$(trim "$GROUPID")
+ [ "$GROUPID" -lt "0" -o "$GROUPID" -gt "65535" ] && perror "Invalid groupid: $GROUPID"
+ # all required variables have been set
+ # does the desired username already exist? if so, check if UID matches, otherwise bail out
+ local _UID=$(grep -E "^${USER}:[^:]*:[0-9]+:" "${_PASSWD}" | head -1 | awk -F ':' '{print $3}')
+ [ -n "${_UID}" ] && [ "x${_UID}" != "x${USERID}" ] && perror "User ${USER}(${USERID}) already exists with UID ${_UID}"
+ # do the same for the group
+ local _GID=$(grep -E "^${GROUP}:[^:]*:[0-9]+:" "${_GROUP}" | head -1 | awk -F ':' '{print $3}')
+ [ -n "${_GID}" ] && [ "x${_GID}" != "x${GROUPID}" ] && perror "Group ${GROUP}(${GROUPID}) already exists with GID ${_GID}"
+ # if user already exists, check if he is in another group than the one requested. if so, bail out
+ # (TODO: don't bail out and add user to the new group)
+ if [ ! -z "${_UID}" ]
+ then
+ local _EXGID=$(grep -E "^${USER}:[^:]*:[0-9]+:" "${_PASSWD}" | head -1 | awk -F ':' '{print $4}')
+ [ "x${GROUPID}" != "x${_EXGID}" ] && perror "Requested GID $GROUPID differs from existing GID $_EXGID"
+ fi
+ # if user does not exist, try to add it
+ if [ -z "${_UID}" ]
+ then
+ local _TEST=$(grep -E "^[^:]+:[^:]*:${USERID}:" "${_PASSWD}")
+ [ -n "${_TEST}" ] && perror "Cannot add $USER - desired UID $USERID already in use."
+ fi
+ if [ -z "${_GID}" ]
+ then
+ local _TEST=$(grep -E "^[^:]+:[^:]*:${GROUPID}:" "${_GROUP}")
+ [ -n "${_TEST}" ] && perror "Cannot add $GROUP - desired GID $GROUPID already in use."
+ fi
+ [ -z "${USERHOME}" ] && local USERHOME=/nonexistent
+ [ -z "${USERSHELL}" ] && local USERSHELL=/bin/false
+ # create password
+ if [ -z "${PASSWORD}" ]
+ then
+ local PASSWORD='*'
+ else
+ pdebug "Hashing password '$PASSWORD' for '$USER'"
+ local PW=$(mkpasswd -m sha-512 "${PASSWORD}")
+ [ -z "${PW}" ] && PW=$(openssl passwd -1 "${PASSWORD}")
+ [ -z "${PW}" ] && perror "Error generating hashed password for $USER"
+ PASSWORD=$PW
+ fi
+ # add user, or replace password
+ if [ -z "${_UID}" ]; then
+ # create user
+ echo "${USER}:x:${USERID}:${GROUPID}:${USER}:${USERHOME}:${USERSHELL}" >> "${_PASSWD}"
+ echo "${USER}:${PASSWORD}:15555:0:99999:7:::" >> "${_SHADOW}"
+ pinfo "Created user $USER"
+ elif [ "$PASSWORD" != "*" ]; then
+ # update user's password
+ sed -i -r "s#^${USER}:[^:]*:(.*)\$"'#'"${USER}:${PASSWORD}:\1#g" "${_SHADOW}"
+ pinfo "Updated password of $USER"
+ fi
+ [ -z "${_GID}" ] && pinfo "Created group $GROUP" && echo "${GROUP}:x:${GROUPID}:" >> "${_GROUP}"
+ echo "${USERID}"
+}
+
+add_group () {
+ [ $# -lt 1 ] && perror "add_group called without argument."
+ [ -z "${TARGET_BUILD_DIR}" ] && perror "add_group: TARGET_BUILD_DIR not set"
+ local _PASSWD=${TARGET_BUILD_DIR}/etc/passwd
+ local _GROUP=${TARGET_BUILD_DIR}/etc/group
+ local _SHADOW=${TARGET_BUILD_DIR}/etc/shadow
+ init_users_and_groups
+ [ ! -f "${_GROUP}" ] && perror "add_user: group file does not exist in target system. (build base first)"
+ local GROUP=$1
+ local GROUPID="-"
+ if ! [[ $GROUP =~ $NAME_REGEX ]]; then
+ perror "Invalid group: $GROUP"
+ fi
+ [ $# -ge 2 ] && [ ! -z "$2" ] && GROUPID=$2
+ local _GID=$(grep -E "^${GROUP}:[^:]*:[0-9]+:" "${_GROUP}" | head -1 | awk -F ':' '{print $3}')
+ [ "x${_GID}" != "x" ] && [ "x$GROUPID" = "x-" -o "x$GROUPID" = "x${_GID}" ] && echo "${_GID}" && return # nothing to do, already exists
+ [ "x${_GID}" != "x" ] && perror "Group $GROUP already exists with GID ${_GID}, but creation was requested with GID $GROUPID"
+ if [ "x$GROUPID" = "x-" ]; then
+ local _UID=$(grep -E "^${GROUP}:[^:]*:[0-9]+:" "${_PASSWD}" | head -1 | awk -F ':' '{print $3}')
+ [ -z "${_UID}" ] && _UID=100
+ GROUPID=$(generate_gid "$GROUP" "${_UID}")
+ fi
+ if ! [[ $GROUPID =~ [0-9]+ ]]; then
+ perror "add_group: GROUPID not numeric (is '$GROUPID')"
+ fi
+ echo "${GROUP}:x:${GROUPID}:" >> "${_GROUP}"
+ pinfo "Created group $GROUP"
+ echo "${GROUPID}"
+}
+
+init_users_and_groups() {
+ [ -z "$TARGET_BUILD_DIR" -o "$TARGET_BUILD_DIR" == "/" ] && perror "Almost wrecked your local passwd, group and shadow file. phew."
+ local PASSWD="$TARGET_BUILD_DIR/etc/passwd"
+ local GROUP="$TARGET_BUILD_DIR/etc/group"
+ local SHADOW="$TARGET_BUILD_DIR/etc/shadow"
+ [ -s "${PASSWD}" -a -s "${GROUP}" -a -s "${SHADOW}" ] && return
+ pinfo "Creating users and groups based on local system...."
+ cp -a "/etc/passwd" "$PASSWD" || perror "Could not copy /etc/passwd"
+ cp -a "/etc/group" "$GROUP" || perror "Could not copy /etc/group"
+ cp -a "/etc/shadow" "$SHADOW" || perror "Could not copy /etc/shadow"
+ # make sure shadow has group shadow (should be handled by cp -a but hey)
+ chgrp shadow "$SHADOW"
+ # remove local users from group file (TODO: currently assumes users have ids 1000-1999)
+ local LOCALUSERS=$(grep -E '^[^:]+:x?:1[0-9]{3}:' "${PASSWD}" | awk -F ':' '{print $1}')
+ for USER in $LOCALUSERS; do
+ sed -r -i "s/([:,])${USER}/\1/g" "${GROUP}"
+ done
+ # fix syntax: remove trailing ',' in group file
+ sed -r -i 's/,+$//g' "${GROUP}"
+ sed -r -i 's/,+/,/g' "${GROUP}"
+ sed -i 's/:,/:/g' "${GROUP}"
+ # remove all non-system groups (also assumes users have 1000-1999, so nogroup will be kept)
+ grep -v -E '^[^:]+:x?:1[0-9]{3}:' "${GROUP}" > "${GROUP}.tmp"
+ mv "${GROUP}.tmp" "${GROUP}"
+ # same for users...
+ grep -v -E '^[^:]+:x?:1[0-9]{3}:' "${PASSWD}" > "${PASSWD}.tmp"
+ mv "${PASSWD}.tmp" "${PASSWD}"
+ # generate fresh shadow file
+ awk -F ':' '{print $1":*:15555:0:99999:7:::"}' "${PASSWD}" > "${SHADOW}"
+}
+
diff --git a/core/includes/versioncompare.inc b/core/includes/versioncompare.inc
new file mode 100644
index 00000000..f49a404f
--- /dev/null
+++ b/core/includes/versioncompare.inc
@@ -0,0 +1,61 @@
+
+# From http://stackoverflow.com/a/4025065/2043481
+# Returns 0 when equal, 1 when $1 > $2, 2 otherwise
+version_compare () {
+ if [[ "$1" == "$2" ]]; then
+ return 0
+ fi
+ local IFS=".-_"
+ local i ver1=($1) ver2=($2)
+ # fill empty fields in ver1 with zeros
+ for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do
+ ver1[i]=0
+ done
+ for ((i=0; i<${#ver1[@]}; i++)); do
+ if [[ -z "${ver2[i]}" ]]; then
+ # fill empty fields in ver2 with zeros
+ ver2[i]=0
+ fi
+ if ((10#${ver1[i]} > 10#${ver2[i]})); then
+ return 1
+ fi
+ if ((10#${ver1[i]} < 10#${ver2[i]})); then
+ return 2
+ fi
+ done
+ return 0
+}
+
+version_ge () {
+ version_compare "$1" "$2"
+ local RET=$?
+ [ "$RET" == "2" ] && return 1
+ return 0
+}
+
+version_le () {
+ version_compare "$1" "$2"
+ local RET=$?
+ [ "$RET" == "1" ] && return 1
+ return 0
+}
+
+version_gt () {
+ version_compare "$1" "$2"
+ local RET=$?
+ [ "$RET" == "1" ] && return 0
+ return 1
+}
+
+version_lt () {
+ version_compare "$1" "$2"
+ local RET=$?
+ [ "$RET" == "2" ] && return 0
+ return 1
+}
+
+version_eq () {
+ version_compare "$1" "$2"
+ return $?
+}
+