#!/bin/bash

# Maintainer: Dmitry Razumov <http://ublinux.ru>

# Functions library :: for Linux Live scripts 6
# Author: Tomas M. <http://www.linux-live.org>
# Author: Mikhail Zaripov <http://magos-linux.ru>
# Author: Anton Goroshkin <http://magos-linux.ru>

# ===========================================================
# GLOBAL variables
# ===========================================================

# linux live flag to fstab, if fstab line doesn't contain it,
# never remove it from fstab automatically (user added it)
FSTABLLFLAG="# AutoUpdate"

# We have to set these variables very carefully
UNION=union
MEMORY=memory
MOUNTDIR=mnt
CHANGES=$MEMORY/changes
XINO=$MEMORY/xino
COPY2RAM=$MEMORY/copy2ram
COPY2REP=$MEMORY/copy2rep
IMAGES=$MEMORY/images
INITRAMDISK=$MOUNTDIR/live
LOOPMOUNT=$MOUNTDIR/liveloop
LIVEMEDIA=livemedia
LIVEHOME=livehome
LIVECHANGES=livesave
LIVEDATA=livedata
LIVEREPOSITORY=$MOUNTDIR/liverepository
MKSQOPT=" -b 512K "
MODULEFORMAT="xzm"

# this will be replaced by build script, so never change the following line!
LIVECDNAME="ublinux"

# =================================================================
# debug and output functions
# =================================================================

# global variable
DEBUG_IS_ENABLED=$(sed s/$/" "/ /proc/cmdline 2>/dev/null | grep " debug ")

debug_log() {
	if [ "$DEBUG_IS_ENABLED" ]; then
		echo "- debug: $*" >&2
		log "- debug: $*"
	fi
}

# echogreen will echo $@ in green color
# $1 = text
#
echogreen() {
	echo -ne "[0;32m""$@""[0;39m"
}

# echolog
# $1 = text to show and to write to /var/log/messages
#
echolog() {
	if [ "$1" != "" ]; then
		echogreen "* "
		log "LIVECD:" "$@"
		echo "$@"
	fi
}

# log
# store given text in /var/log/livedbg
log() {
	echo "$@" 2>/dev/null >>/var/log/livedbg
}

# show information about the debug shell
show_debug_banner() {
	echo
	echo "====="
	echo ": Debugging started. Here is the root shell for you."
	echo ": Type your desired commands or hit Ctrl+D to continue booting."
	echo
}

# debug_shell
# executed when debug boot parameter is present
#
debug_shell() {
	if [ "$DEBUG_IS_ENABLED" ]; then
		show_debug_banner
		ash </dev/console
		echo
	fi
}

# header
# $1 = text to show
#
header() {
	echo "[0;1m""$@""[0;0m"
}

fatal() {
	echolog
	header "Fatal error occured - $1"
	echolog "Something went wrong and we can't continue. This should never happen."
	echolog "Please reboot your computer with Ctrl+Alt+Delete ..."
	echolog
	ash </dev/console
}

allow_only_root() {
	# test if the script is started by root user. If not, exit
	if [ "0$UID" -ne 0 ]; then
		echo "Only root can run $(basename $0)"
		exit 1
	fi
}
#============================================================
# Debug functions
#============================================================
debug_mode() {
	if [ "$DEBUG_IS_ENABLED" -o "$DEBUGMODE" == "yes" ]; then
		name=$(basename $0)
		slash="/"
		[ "$(pwd)" == "/union" ] && slash=""
		if ! test -f ${slash}var/log/ublinux/${name}.log; then
			echo "$0 --  debug mode enabled"
			test -d ${slash}var/log/ublinux || mkdir -p ${slash}var/log/ublinux
			echo $(date) >${slash}var/log/ublinux/${name}.log || echo "can not create log file"
			$0 "$@" 2>&1 | tee -a ${slash}var/log/ublinux/${name}.log
			exit 0
		fi
	fi
}

echodebug() {
	[ "$DEBUG_IS_ENABLED" -o "$DEBUGMODE" == "yes" ] && echo "$1"
	if [ -n "$2" ]; then
		command=$2
		shift
		shift
		if [ -z $1 ]; then
			$command
		else
			$command "$@"
		fi
	fi
}

# ===========================================================
# text processing functions
# ===========================================================

# look into cmdline and echo $1 back if $1 is set
# $1 = value name, case sensitive, for example 'debug'
#
cmdline_parameter() {
	debug_log "cmdline_parameter" "$*"
	log "searching for bootparam: $1"
	ADDFILE=/tmp/cmdline
	[ -f /liblinuxlive ] || ADDFILE=/mnt/live$ADDFILE
	echo -n " " | cat - /proc/cmdline $ADDFILE 2>/dev/null | grep -E -m1 -o "(^|[[:space:]])$1([[:space:]]|\$)" | tr -d " "
}

# look into cmdline and echo value of $1 option
# $1 = value name, case sensitive, for example 'changes'
#
cmdline_value() {
	debug_log "cmdline_value" "$*"
	log "searching for bootparam value: $1"
	ADDFILE=/tmp/cmdline
	[ -f /liblinuxlive ] || ADDFILE=/mnt/live$ADDFILE
	echo -n " " | cat - /proc/cmdline $ADDFILE 2>/dev/null | grep -E -m1 -o "(^|[[:space:]])$1=[^[:space:]]+" | cut -d "=" -f 2-
}

# Make sure the part of a script after 'mutex_lock' call is atomic,
# that means the 'locked' part of the script can never be execuetd
# from several processes at the same time, in parallel.
# Every script waits until it gathers the lock.
# The lock directory is saved in /dev instead of /tmp, because /tmp may be
# readonly at the time when the lock is needed (eg. when udev is starting)
# $1 = name of the lock
#
mutex_lock() {
	debug_log "mutex_lock" "$*"
	while ! mkdir "/dev/ll-mutex-lock-$1" 2>/dev/null; do
		usleep 100000
	done
}

# Unlock the lock so another waiting process can reusse it and continue
# $1 = name of the lock
#
mutex_unlock() {
	debug_log "mutex_unlock" "$*"
	rmdir "/dev/ll-mutex-lock-$1" 2>/dev/null
}

# ===========================================================
# system functions
# ===========================================================

# setup /usr from /usr.lzm inside initrd
mount_initrd_loops() {
	debug_log "mount_initrd_loops" "$*"
	if [ -e /usr.lzm ]; then
		mount_device /usr.lzm /usr loop,ro squashfs
	fi
	if [ -e /usr.xzm ]; then
		mount_device /usr.xzm /usr loop,ro squashfs
	fi
	if [ -e /drivers.lzm ]; then
		mount_device /drivers.lzm /lib/modules/*/kernel loop,ro squashfs
	fi
	if [ -e /drivers.xzm ]; then
		mount_device /drivers.xzm /lib/modules/*/kernel loop,ro squashfs
	fi
}

# modprobe module $1, including all dependencies, suppress all messages
# This was own function, because modprobe in busybox didn't support
# neither gzipped modules nor dependencies. Seems to be fixed now, though.
# $1 = module name, eg. ehci-hcd
# $* = optional arguments
#
modprobe_module() {
	debug_log "modprobe_module" "$*"
	local MODULE

	MODULE="$1"
	shift

	if [ ! "$MODULE" ]; then return 1; fi
	modprobe "$MODULE" $* 2>/dev/null
}

# mknod next loop device
# - find biggest loop device in /dev/loop/, assume it to be used
# - preallocate (mknod) 20 more loop devices in one round
mknod_next_loop_dev() {
	debug_log "mknod_next_loop_dev" "$*"
	local i NR END PFX

	mutex_lock mknod_next_loop_dev

	if [ -d /dev/loop ]; then
		NR=$(find /dev/loop/ -maxdepth 1 | sed -r 's/[^0-9]+//' | sort -n | tail -n 1)
		PFX="/"
	else
		NR=$(find /dev/ -maxdepth 1 | grep loop | sed -r 's/[^0-9]+//' | sort -n | tail -n 1)
		PFX=""
	fi
	NR=$(expr 0$NR + 1)
	END=$(expr 0$NR + 20)
	for i in $(seq $NR $END); do
		mknod /dev/loop$PFX$i b 7 $i 2>/dev/null
	done
	echo /dev/loop$PFX$NR

	mutex_unlock mknod_next_loop_dev
}

# ===========================================================
# Filesystem functions
# ===========================================================

# Find out what locale is requested
# If no locale is given, use the firts one available (if any)
# $1 = locale (optional argument, if exists, no autodetection is made)
locale_id() {
	debug_log "locale_id" "$*"
	local LOCALE i

	# first try to find out locale from boot parameters
	LOCALE="$1"
	if [ "$LOCALE" = "" ]; then LOCALE=$(cmdline_value locale); fi
	if [ "$LOCALE" = "" ]; then LOCALE=$(cmdline_value language); fi
	if [ "$LOCALE" = "" ]; then LOCALE=$(cmdline_value lang); fi

	# if not found, set it to locale from usr/lib/locale,
	# but only if there is just ONE directory, nothing more
	# (so we are sure which one to use)
	if [ "$LOCALE" = "" ]; then
		LOCALE=ru_RU
	fi

	if [ "$LOCALE" != "" ]; then
		cat /usr/share/locale/locale.alias | sed -r "s/#.*//" | grep -E "$LOCALE|$LOCALE""_" | tail -n 1 | tr -s "[[:space:]]" " " | cut -d " " -f 2- | tr -d " "
	fi
}

# Find out what iocharset to use
iocharset() {
	debug_log "iocharset" "$*"
	local CHARSET IOCHARSET

	# if iocharset is explicitly set at the boot prompt,
	# return it regardless the locale settings
	IOCHARSET=$(cmdline_value iocharset)
	if [ "$IOCHARSET" = "" ]; then IOCHARSET=utf8; fi
	echo $IOCHARSET
	return 0
}

# Find out what codepage to use
codepage() {
	debug_log "codepage" "$*"
	local CHARSET CODEPAGE

	# if codepage is explicitly set at the boot prompt,
	# return it regardless the locale settings
	CODEPAGE=$(cmdline_value codepage)
	if [ "$CODEPAGE" = "" ]; then CODEPAGE=866; fi
	echo $CODEPAGE
	return 0
}

# Get filesystem options
# $1 = filesystem
# $2 = 'fstab' or 'mount' ... 'auto'/'noauto' string is enabled (fstab) or disabled (mount)
#

fs_options() {
	debug_log "fs_options" "$*"
	local NOAUTO IOCHARSET CODEPAGE

	NOAUTO=$(cmdline_parameter noauto)
	if [ "$NOAUTO" = "" ]; then NOAUTO="auto"; fi
	if [ "$2" = "fstab" ]; then echo -n "$NOAUTO,"; fi
	if [ "$1" = "swap" ]; then
		echo "defaults,pri=1"
		return 0
	fi
	echo -n "noatime,suid,dev,exec"

	IOCHARSET=$(iocharset)
	CODEPAGE=$(codepage)

	MUID=$(cmdline_value users | awk -F: '{print $2}')
	[ "$MUID" = "" ] && MUID=500

	if [ "$1" = "vfat" ]; then
		echo -n ",quiet,umask=0,check=s,shortname=mixed,uid=$MUID,gid=$MUID"
		if [ "$IOCHARSET" ]; then
			echo ",codepage=$CODEPAGE,iocharset=$IOCHARSET"
		fi
	fi

	if [ "$1" = "iso9660" ]; then
		echo -n ",ro"
		if [ "$IOCHARSET" ]; then
			echo ",iocharset=$IOCHARSET"
		fi
	fi

	if [ "$1" = "ntfs" ]; then
		echo -n ",ro"
		if [ "$IOCHARSET" ]; then
			echo ",nls=$IOCHARSET"
		fi
	fi

	if [ "$1" = "ntfs-3g" ]; then
		echo ",locale=$(locale_id),uid=$MUID,gid=$MUID"
	fi
}

# discover filesystem used on the given device
# Use vfat for msdos filesystem. Use ntfs-3g for ntfs if ntfsmount exists.
# $1 = device, eg. /dev/hda1
#
device_filesystem() {
	debug_log "device_filesystem" "$*"
	local NTFS

	if [ -e /bin/ntfsmount ]; then NTFS="ntfs-3g"; else NTFS="ntfs"; fi
	blkid -s TYPE "$1" -o value | sed "s/msdos/vfat/" | sed "s/ntfs/$NTFS/"
}

# tell us if the given filesystem is supported
# (eg. it's in /proc/filesystems or we know it)
# $1 = filesystem name
#
is_supported_filesystem() {
	debug_log "is_supported_filesystem" "$*"

	if [ -e /bin/ntfsmount -a "$1" = "ntfs-3g" ]; then
		return 0
	fi

	# the following command will set the return value
	grep -Eq "[[:space:]]$1\$" /proc/filesystems
}

# Check device on demand. Only block devices and *.img loop files  can be checked
# $1 = /dev device, eg. /dev/hda1, or loop file
# $2 = optional filesystem name, in order to skip autodetection
fsck_device() {
	[ -b "$1" -o -f "$1" ] || return
	if [ -f "$1" ]; then
		echo $1 | grep -q ".img$" || return
	fi
	echolog "Checking filesystem on $1 $2" >/dev/console 2>/dev/console
	FS=
	if [ "$2" = "ntfs" ]; then
		/usr/bin/ntfsfix "$1" </dev/console >/dev/console 2>/dev/console
	else
		/usr/bin/fsck -a $([ "$2" ] && echo "-t $2") $1 </dev/console >/dev/console 2>/dev/console
	fi
}

# Mount device $1 to $2
# If the device is using vfat or ntfs filesystem, use iocharset as a mount option
# $1 = /dev device to mount, eg. /dev/hda1, or loop file, or directory
# $2 = mountpoint, eg. /mnt/hda1
# $3 = optional mount options, for example "ro", or "remount,rw"
# $4 = optional filesystem name, in order to skip autodetection
#
mount_device() {
	debug_log "mount_device" "$*"
	local FS DEV LOOPDEV OPTIONS FILESYSTEM ERR

	# make sure we have enough arguments
	if [ "$2" = "" ]; then return 1; fi
	if [ "$1" = "" ]; then
		rmdir "$2" 2>/dev/null
		return 1
	fi
	# skipping MBR
	echo $(basename "$1") | grep -q [a-z]$ && grep -q "$(basename "$1")"[0-9] /proc/partitions && return 1

	mkdir -p "$2"

	DEV="$1"
	if [ "$4" != "" ]; then FS="$4"; else FS=$(device_filesystem "$1"); fi
	if [ "$FS" ]; then
		OPTIONS=$(fs_options $FS mount)
		FS="-t $FS"
	fi
	if [ "$OPTIONS" ]; then OPTIONS="$OPTIONS"; else OPTIONS=""; fi
	if [ -f "$DEV" ]; then OPTIONS="$OPTIONS,loop"; fi
	if [ -d "$DEV" ]; then OPTIONS="$OPTIONS,rbind"; fi
	if [ "$3" ]; then OPTIONS="$OPTIONS,$3"; fi
	OPTIONS=$(echo "$OPTIONS" | sed -r "s/^,+//")

	if [ "$FS" = "-t ntfs-3g" ]; then
		[ $(cmdline_parameter fsck) ] && fsck_device "$DEV" ntfs
		ntfsmount "$DEV" "$2" -o $OPTIONS >/dev/null 2>&1
		ERR=$?
	else
		[ $(cmdline_parameter fsck) ] && fsck_device "$DEV" $(echo $FS | sed "s/-t //")
		mount -n -o $OPTIONS $FS "$DEV" "$2" >/dev/null 2>&1
		ERR=$?
	fi

	if [ $ERR -ne 0 ] && [ -f "$DEV" ] && echo "$DEV" | grep -q .enc$; then
		LOOPDEV=$(losetup -f)
		[ -z "$LOOPDEV" ] && LOOPDEV=$(mknod_next_loop_dev)
		OPTIONS=$(echo "$OPTIONS" | sed -r "s/,loop//g")
		echolog "Mounting encrypted filesystem $DEV" >/dev/console 2>/dev/console
		times=3
		while [ $times -gt 0 ]; do
			/usr/bin/losetup.crypto -e AES256 "$LOOPDEV" "$DEV" >/dev/console </dev/console 2>/dev/console
			[ $(cmdline_parameter fsck) ] && fsck_device "$LOOPDEV"
			mount -n -o $OPTIONS "$LOOPDEV" "$2" >/dev/null 2>&1
			ERR=$?
			[ $ERR -eq 0 ] && break
			/usr/bin/losetup.crypto -d "$LOOPDEV"
			times=$(expr $times - 1)
		done
	fi

	# not enough loop devices? try to create one.
	if [ $ERR -eq 2 ]; then
		LOOPDEV=$(mknod_next_loop_dev)
		OPTIONS=$(echo "$OPTIONS" | sed -r "s/,loop//g")
		losetup "$LOOPDEV" "$DEV" 2>/dev/null # busybox's losetup doesn't support -r
		if [ $? -ne 0 ]; then
			losetup -r "$LOOPDEV" "$DEV" 2>/dev/null # force read-only in case of error
		fi
		mount -n -o $OPTIONS $FS "$LOOPDEV" "$2" >/dev/null 2>&1
		ERR=$?
	fi

	# if nothing works, try to force read-only mount
	if [ $ERR -ne 0 ]; then
		mount -n -r -o $OPTIONS $FS "$DEV" "$2" >/dev/null 2>&1
		ERR=$?
	fi

	if [ $ERR -ne 0 ]; then rmdir $2 2>/dev/null; fi
	return $ERR
}

# unmount all parameters. If the parameter is not mountpoint but
# it's a file or directory, umount the device where the file/dir is stored.
#
# First try normal umount, if that fails then remount read-only
# If -l parameter is specified, do lazy-umount when normal umount fails
# $1..$n = files/directories/devices to be unmounted
#
fumount() {
	debug_log "fumount" "$*"
	local TARGET LAZY LOOPDEVICE

	while [ "$1" ]; do
		if [ "$1" = "-l" ]; then
			LAZY="yes"
			shift
		fi
		TARGET=$(readlink -f "$1")
		if ! ismountpoint "$TARGET"; then
			if [ -f "$TARGET" -o -d "$TARGET" ]; then
				TARGET=$(df "$TARGET" | tail -n 1 | tr -s " " | cut -d " " -f 6)
			fi
		fi

		if [ "$TARGET" != "" ]; then
			LOOPDEVICE=$(grep '/dev/loop.* '"$TARGET " /proc/mounts | awk '{print $1}')
			umount -n "$TARGET" >/dev/null 2>&1
			if [ $? -ne 0 ]; then
				mount -n -o remount,ro -t ignored ignored "$TARGET" >/dev/null 2>&1
				if [ "$LAZY" ]; then umount -n -l "$TARGET" >/dev/null 2>&1; fi
			fi
			[ "$LOOPDEVICE" = "" ] || losetup -d "$LOOPDEVICE"
		fi
		shift
	done
}

# ===========================================================
# live module functions
# ===========================================================

# Create module
# call mksquashfs with apropriate arguments
# $1 = directory which will be compressed to squashfs module
# $2 = output filesystem module file
# $3..$9 = optional arguments like -keep-as-directory or -b 123456789
#
create_module() {
	debug_log "create_module" "$*"
	rm -f "$2" # overwrite, never append to existing file
	mksquashfs "$1" "$2" $MKSQOPT $3 $4 $5 $6 $7 $8 $9 >/dev/null
	if [ $? -ne 0 ]; then return 1; fi
	chmod a-wx "$2" # remove execute and write attrib
	chmod a+r "$2"  # add read for everyone
}

# ismountpoint exits with 0 if $1 is mountpoint, else exits with 1
# $1 = directory or loop_file
#
ismountpoint() {
	debug_log "ismountpoint" "$*"
	local MDIR

	MDIR=$(readlink -f "$1")
	cat /proc/mounts | cut -d " " -f 2 | grep -E "^$MDIR\$" >/dev/null 2>&1
}

# Mount filesystem module to destination directory
# $1 = path to the compressed module
# $2 = destination folder
#
mount_module() {
	debug_log "mount_module" "$*"
	mount_device "$1" "$2" loop,$3 $4
}

# Insert a directory tree $2 to an union specified by $1
# Top-level read-write branch is specified by it's index 0
# Using =rr enables aufs to optimize real readonly branches
# $1 = union absolute path (starting with /)
# $2 = path to data directory
#
union_insert_dir() {
	debug_log "union_insert_dir" "$*"
	if [ $(cmdline_parameter unionfs) ]; then
		mount -n -o remount,add=:"$2"="$3" unionfs "$1"
	else
		mount -n -o remount,add:1:"$2"="$3" aufs "$1"
	fi
}

# Find XZM,LZM,RWM,ROM,ENC modules in given dir
# $1 = root directory of mounted DATAdir
#
find_modules() {
	debug_log "find_modules" "$*"
	if [ $(cmdline_parameter unionfs) ]; then
		find "$1/base" "$1/modules" "$1/optional" -name "*.xzm" -o -name "*.lzm" -o -name "*.rwm" -o -name "*.rom" -o -name "*.enc" 2>/dev/null | sort -r
	else
		find "$1/base" "$1/modules" "$1/optional" -name "*.xzm" -o -name "*.lzm" -o -name "*.rwm" -o -name "*.rom" -o -name "*.enc" 2>/dev/null | sort
	fi
}

# List all modules in all directories (base, modules, optional)
# and filter out unneeded optional modules (not specified by load= kernel parameter)
# separator for load and noload arguments is "," or ";"
# $1 = root directory of mounted DATAdir
#
list_modules() {
	debug_log "list_modules" "$*"
	local LOAD NOLOAD

	LOAD=$(cmdline_value load | sed -r 's/\?/./g' | sed -r 's/\*/.\*/g' | sed -r 's/,|;/|/g')
	NOLOAD=$(cmdline_value noload | sed -r 's/\?/./g' | sed -r 's/\*/.\*/g' | sed -r 's/,|;/|/g')
	find_modules "$1" | while read LINE; do
		MODNAME=$(echo $LINE | cut -b ${#1}- | cut -b 2-)
		if [ "$(echo $LINE | grep /optional/)" ]; then
			if [ ! "$LOAD" -o ! "$(echo $MODNAME | grep -E -i "$LOAD")" ]; then continue; fi
		fi
		if [ "$NOLOAD" -a "$(echo $MODNAME | grep -E -i "$NOLOAD")" ]; then continue; fi
		echo $LINE
	done
}

# Insert one single filesystem module to the union
# $1 = union absolute path
# $2 = module full path
# $3 = destination folder, where images will be mounted to
# $4 = preffix length strip (number of characters)
#
union_insert_module() {
	debug_log "union_insert_module" "$*"
	local TARGET MODPREF mod fs

	TARGET="$3/$(basename "$2")"
	if ismountpoint "$TARGET"; then return 1; fi # skip already used modules
	mkdir -p "$TARGET"
	MODPREF="$2"
	MODNAME="$(echo "$2" | cut -b $(($4 + 1))-)"
	[ -f "/$COPY2REP/$MODNAME" ] && MODPREF="/$COPY2REP/$MODNAME"
	[ -f "/$COPY2RAM/$MODNAME" ] && MODPREF="/$COPY2RAM/$MODNAME"

	if [ "$(echo $MODNAME | grep -i .rwm)" ]; then
		mod=rw
		fs=""
	elif [ "$(echo $MODNAME | grep -i .rom)" ]; then
		mod=ro
		fs=""
	else
		mod=ro
		fs=squashfs
	fi

	mount_module "$MODPREF" "$TARGET" $mod $fs
	#Second attempt to avoid bug when activating module
	if [ $? -ne 0 ]; then mount_module "$MODPREF" "$TARGET" $mod $fs; fi
	if [ $? -ne 0 ]; then
		echo "Cannot read module data. corrupted download?" >&2
		return 1
	fi
	union_insert_dir "$1" "$TARGET" $mod
	if [ $? -ne 0 ]; then
		echo "can't insert module to union" >&2
		return 2
	fi
	echo "$2" | cut -b $(($4 + 1))-
	echolog "$2" >/dev/null
	return 0
}

# Insert all filesystem modules from $2 directory and subdirectories, to the union
# $1 = union absolute path (starting with /)
# $2 = LiveCD data dir (with directories /base, /modules, etc.)
# $3 = destination folder, where images will be mounted to
#
union_insert_modules() {
	debug_log "union_insert_modules" "$*"
	local INSERTED

	list_modules $2 | while read MODULE; do
		INSERTED=$(union_insert_module $1 $MODULE $3 ${#2})
		if [ "$INSERTED" != "" ]; then echolog " -> $(echo $INSERTED | sed -r s:^/::)"; fi
	done
}

# Copy modules to directory
# will copy only /boot, and module files from $1
# $1 = data directory
# $2 = target directory
# $3 = target name
# $4 = cmdline param
copy_to() {
	local C2PARAM MODNAME
	C2PARAM="$(echo $4 | sed -r 's/\?/./g' | sed -r 's/\*/.\*/g' | sed -r 's/,|;/|/g')"
	debug_log "copy_to_ram" "$*"
	list_modules "$1" | while read MODULE; do
		MODNAME=$(echo $MODULE | cut -b ${#1}- | cut -b 2-)
		[ "$C2PARAM" ] && ! [ "$(echo $MODNAME | grep -E -i $C2PARAM)" ] && continue
		TARGET=$(dirname "$MODULE" | cut -b ${#1}- | cut -b 2-)
		mkdir -p "$2/$TARGET"
		echolog "  $3 <- $(basename $MODULE)"
		#      cp "$MODULE" "$2/$TARGET"
		rsync -a "$MODULE" "$2/$TARGET"
		if [ $? -ne 0 ]; then fatal "Not enough memory."; fi
	done
}

# Copy modules to RAM directory
# will copy only /boot, and module files from $1
# $1 = data directory
# $2 = target directory in RAM
#
copy_to_ram() {
	[ "$2" = "" ] && return
	debug_log "copy_to_ram" "$*"
	copy_to $1 $2 RAM "$(cmdline_value copy2ram)$(cmdline_value toram)"
}

# Copy modules to LOCAL_REP directory
# will copy only /boot, and module files from $1
# $1 = data directory
# $2 = target directory in LOCAL_REP
#
copy_to_rep() {
	[ "$2" = "" ] && return
	debug_log "copy_to_rep" "$*"
	copy_to $1 $2 LOCAL_REPOSITORY "$(cmdline_value copy2rep)"
}

# ===========================================================
# discovery functions
# ===========================================================

# List all supported network drivers
#
list_network_drivers() {
	debug_log "list_network_drivers" "$*"

	# these drivers are probed in Slackware's initrd
	# (see initrd.img/scripts/network.sh).
	# I don't have personal experiences with most of these drivers
	# so I'll be happy if you report any particular one to be not working
	# (eg. causing hangups) in order to remove it from this list.

	echo 3c59x e100 eepro100 e1000 hp100 ne2k-pci \
		r8169 8139too 8139cp via-rhine forcedeth \
		e2100 eepro eexpress pcnet32 \
		3c501 3c503 3c505 3c507 3c509 3c515 tg3 jme \
		virtio_net atl1 atl1c atl2 igb alx | tr " " "\n"
}

# List all CD-ROMs
# by using /proc entries
#
list_cdrom_devices() {
	debug_log "list_cdrom_devices" "$*"
	local CDDEVICE

	for CDDEVICE in $(cat /proc/sys/dev/cdrom/info 2>/dev/null | head -n 3 | tail -n 1 | cut -d ":" -f 2); do
		echo "/dev/$CDDEVICE"
	done
}

# List all mounted directories
#
list_mounted_directories() {
	debug_log "list_mounted_directories" "$*"
	if [ "$MOUNTDIR" ]; then
		ls -1 $MOUNTDIR | while read DIR; do
			if ismountpoint $MOUNTDIR/$DIR; then echo $DIR; fi
		done
	fi
}

# List all devices with filesystems
# Return empty result when nohd parameter was given.
#
list_partition_devices() {
	debug_log "list_partition_devices" "$*"
	if [ "$(cmdline_parameter nohd)" != "" ]; then return 1; fi
	grep -Ev 'loop|major|^$| [0-9] [a-z]' /proc/partitions | sed -r "s:^[0-9 ]+:/dev/:"
	if [ -e /dev/mapper/control ]; then # list LVM partitions if available
		ls -1 /dev/mapper/ | grep -v "^control\$" | sed -r "s:^:/dev/mapper/:"
	fi
}

# List all disk devices
#
list_disk_devices() {
	debug_log "list_disk_devices" "$*"
	list_partition_devices | grep -Ev "[0-9]"
}

# List all partitions marked as Linux Swap
#
list_swap_devices() {
	debug_log "list_swap_devices" "$*"
	if [ "$(cmdline_parameter nohd)" != "" -o "$(cmdline_parameter noswap)" != "" ]; then return 1; fi
	blkid -t TYPE="swap" -o device
}

# List all block devices
#
list_block_devices() {
	debug_log "list_block_devices" "$*"
	if [ "$(cmdline_parameter nocd)" = "" ]; then
		list_cdrom_devices
	fi
	list_partition_devices
}

# Format mountdir for device. This function used to append _cdrom or _removable
# suffix to the directory name so KDE was able to assign a nice icon for evey
# device, but this should be done using HAL in KDE nowadays, so we do not
# support these stupid suffixes anymore. Many people will be happy :)
# $1 = device full path, eg. /dev/hda1
#
device_mountdir() {
	debug_log "device_mountdir" "$*"
	if ! [ -b "$1" ]; then
		echo "/$MOUNTDIR/$(basename "$1")" | tr -s /
	else
		if grep -q "^$1 " /proc/mounts; then
			grep "^$1 " /proc/mounts | awk '{print $2}' | head -1 | tr -s /
		else
			if [ "$2" = "" ]; then
				echo "/$MOUNTDIR/$(basename "$1")" | tr -s /
			else
				echo "$2" | tr -s /
			fi
		fi
	fi
}

# Find file-path on given device
# First it mounts the device read-only. If then the 'path' is found,
# then remount without RO flag (causes it to be mounted read-write if possible)
# and return the path, else unmount and exit.
# If the device/dev_directory is already mounted, preserve it mounted
# $1 = device
# $2 = path/filename
# $3 = device mountpoint
#
find_filepath() {
	debug_log "find_filepath" "$*"
	local DIR FOUND PRESERVE

	DIR=$(device_mountdir $1 $3)
	ismountpoint $DIR
	if [ $? -eq 0 ]; then
		PRESERVE="true"
	else
		mount_device $1 $DIR ro
		if [ $? -ne 0 ]; then
			rmdir $DIR 2>/dev/null
			return 1
		fi
		PRESERVE=""
	fi

	FOUND=$(ls -A1d $DIR/$2 2>/dev/null | head -n 1 | tr -s '/')

	if [ "$FOUND" ] && echo "$2" | grep -q "$LIVECDNAME/$LIVECDNAME.sgn$"; then
		grep -q "$(head -1 /VERSION)" $DIR/$LIVECDNAME/[Vv][Ee][Rr][Ss][Ii][Oo][Nn] 2>/dev/null || FOUND=""
	fi

	if [ "$FOUND" = "" ]; then
		if [ "$PRESERVE" != "true" ]; then
			fumount $DIR
			rmdir $DIR 2>/dev/null
		fi
		return 1
	else
		# remount without the 'ro' option now, so use rw or defaults
		# Only in the case it was not mounted already before.
		if [ "$PRESERVE" != "true" ]; then
			fumount $DIR
			mount_device $1 $DIR
			if [ $? -ne 0 ]; then
				rmdir $DIR 2>/dev/null
				return 2
			fi
		fi
		echo "$FOUND"
		return 0
	fi
}

# Find file in computer by mounting disks or other storage devices
# and searching for $1 in the mounted directory
# $1 = filename or device-path or devicepath/filename
# $2 = device mountpoint
#
find_file() {
	debug_log "find_file" "$*"
	local FIND DEVICE DEVPART PATHPART

	# allow using /mnt/... as well as /dev/...
	FIND=$(echo "$1" | sed -r "s:^/mnt/:/dev/:")

	# if parameter is just a device, echo it and exit
	if [ -b "$FIND" -o -c "$FIND" -o "$FIND" = "" ]; then
		echo "$FIND"
		return
	fi

	# If path doesn't start with /dev/, try to find the exact path on all devices
	# First, split DEV/PATH parts
	DEVPART=$(echo "$FIND" | grep -E -o "^/dev/[^/]+")

	if [ "$DEVPART" = "" ]; then
		# no device is specified. Search all devices for filename $FIND
		PATHPART="$FIND"
		for DEVICE in $(list_mounted_directories) $(list_block_devices); do
			if ! grep -q ":$DEVICE@$PATHPART:" /tmp/_findfile 2>/dev/null; then
				find_filepath "$DEVICE" "$PATHPART" "$2"
				if [ $? -eq 0 ]; then return 0; fi
				echo ":$DEVICE@$PATHPART:" >>/tmp/_findfile
			fi
		done
	else
		# try to find PATHPART only on the given device
		PATHPART=$(echo "$FIND" | sed -r 's:^/dev/[^/]+(.*):\1:') #'
		find_filepath "$DEVPART" "$PATHPART" "$2"
	fi
}

# Find In Computer
# use 'find_file' function to find the given file/dir
# if nothing found, sleep for a while to allow devices to settle and try again.
# (is there any way to find out if there are devices queued through /sys?)
# $1 = file or directory to find
# $2 = device mountpoint
#
find_in_computer() {
	debug_log "find_in_computer" "$*"
	local TIMEOUT RESULT

	TIMEOUT=$(cmdline_value scantimeout | sed -r 's/[^0-9]*([0-9]+).*/\1/') #'
	if [ "$TIMEOUT" = "" ]; then TIMEOUT=10; fi

	RESULT=$(find_file "$1" "$2")

	while [ $TIMEOUT -gt 0 -a "$RESULT" = "" ]; do
		echo -ne "- wait a while\r" >&2
		sleep 1
		TIMEOUT=$((TIMEOUT - 1))
		RESULT=$(find_file "$1" "$2")
	done

	echo $RESULT
}

# Find and run all scripts from the given module
# This function is used by the activate and deactivate script when the distro
# is already started, not during live setup
# $1 = mounted module full path
# $2..$n = optional arguments for the scripts, eg. 'start'
#
find_n_run_scripts() {
	debug_log "find_n_run_scripts" "$*"
	local MOD

	MOD="$1"
	shift

	RCPATH=/etc/rc.d/init.d
	RUNLEVEL=$(runlevel | awk '{print $2}')
	[ -z "$RUNLEVEL" ] || RCPATH=/etc/rc.d/rc$RUNLEVEL.d
	RUNSCRIPTS="$MOD$RCPATH|$MOD/usr/lib/ublinux/rc.local|$MOD/usr/lib/ublinux/rc.post"
	echo $@ | grep -q start || RUNSCRIPTS="$MOD$RCPATH"

	find "$MOD" | grep -E "$RUNSCRIPTS" | cut -b "${#MOD}"- | cut -b 2- | xargs -n 1 -r readlink -f | sort -u |
		while read SCRIPT; do
			if [ "$SCRIPT" != "" -a -x "$SCRIPT" -a ! -d "$SCRIPT" ]; then
				# call the script by real path, not from the module
				log "starting '"$SCRIPT" $@'"
				"${SCRIPT}" "$@"
			fi
		done
}

# ===========================================================
# hardware preparation functions
# ===========================================================

# Create block devices to /dev described by /sys entries
#
mdev_start_hotplug() {
	debug_log "mdev_start_hotplug" "$*"
	echolog "creating /dev entries for block devices"
	mdev -s
	rm /dev/pty??* /dev/tty??* 2>/dev/null   # remove unneeded pty and tty devices
	echo /bin/mdev >/proc/sys/kernel/hotplug # use mdev as a hotplug handler
}

# Modprobe kernel modules needed for the LiveCD
#
modprobe_essential_modules() {
	debug_log "modprobe_essential_modules" "$*"

	echolog "starting loop device support"
	modprobe_module loop
	echolog "starting cdrom filesystem support"
	modprobe_module isofs
	echolog "starting squashfs support"
	modprobe_module squashfs-lzma

	if [ $(cmdline_parameter unionfs) ]; then
		echolog "starting unionfs support"
		modprobe_module unionfs
	else
		echolog "starting aufs support with brs=1"
		modprobe_module aufs brs=1
	fi

	echolog "starting linux filesystem support"
	modprobe_module ext2
	modprobe_module ext3
	modprobe_module crc16
	modprobe_module ext4
	modprobe_module reiserfs
	modprobe_module xfs
	echolog "starting windows filesystem support"
	modprobe_module vfat
	modprobe_module fuse # for ntfs-3g
	modprobe_module ntfs # for ro driver
	echolog "starting crypto filesystem support"
	modprobe_module aes-i586
	modprobe_module aes_generic
	modprobe_module cbc
	modprobe_module cryptoloop
	echolog "starting nls_cp866 support"
	modprobe_module nls_cp866
	modprobe_module nls_utf8
}

# Modprobe kernel modules needed for USB masstorage devices
#
modprobe_usb_modules() {
	debug_log "modprobe_hardware_modules" "$*"
	depmod
	local ATAMODULES HWMODULES MANMODULES BLMODULES
	MANMODULES="$(cmdline_value rdloaddriver | tr ',;' ' ')"
	BLMODULES="$(cmdline_value blacklist | tr ',;' ' ')"
	ALLATAMODS=$(ls -1 /lib/modules/*/kernel/drivers/ata | sed "s|\.ko.*$||")
	for a in $(lspci -v | grep -i modules | tr , \\n | sort -u | sed s/.*modules.//); do
		if echo " $ALLATAMODS " | grep -q " $a "; then
			ATAMODULES="$ATAMODULES $a"
		else
			HWMODULES="$HWMODULES $a"
		fi
	done
	echo "$HWMODULES" | grep -q [ou]hci-hcd || HWMODULES="$HWMODULES ohci-hcd uhci-hcd"
	local MODULES="ahci sd_mod sr_mod \
                  $MANMODULES $ATAMODULES $HWMODULES \
                  ehci-hcd ehci-pci ehci-platform xhci-hcd hid usbhid hid-generic usb-storage ide_cd"
	for a in $MODULES; do
		echo " $BLMODULES " | grep -q " $a " || modprobe_module $a
	done
}

# Load drivers for PCMCIA CardBus devices
#
modprobe_pcmcia_modules() {
	debug_log "modprobe_pcmcia_modules" "$*"

	# skip module loading if nohotplug bootparam is present
	if [ "$(cmdline_parameter nohotplug)" ]; then return 0; fi

	echolog "starting PCMCIA CardBus support"
	modprobe_module pcmcia_core
	modprobe_module pcmcia
	modprobe_module rsrc_nonstatic
	modprobe_module yenta_socket
}

# Load network drivers unless eth[0-9] is found
#
modprobe_network_modules() {
	debug_log "modprobe_network_modules" "$*"
	local ETH

	# skip module loading if nohotplug bootparam is present
	if [ "$(cmdline_parameter nohotplug)" ]; then return 0; fi

	# probe all drivers. Start by the ones mentioned in pcimodules' output
	for module in $(#"
		list_network_drivers | grep -E "$(pcimodules | tr "\n" "|")!"
	) $(list_network_drivers); do
		modprobe_module $module
		ETH=$(cat /proc/net/dev | grep : | grep -v lo: | cut -d : -f 1 | tr -d " ")
		if [ "$ETH" != "" ]; then
			echo $ETH
			return 0
		fi
		rmmod $module 2>/dev/null
	done
}

# Start udhcpc to get IP address from DHCP server
# $1 = interface to use (optional)
#
init_dhcp() {
	debug_log "start_dhcp_client" "$*"
	modprobe af_packet 2>/dev/null
	if [ "$1" != "" ]; then
		ifconfig $1 up
		udhcpc -i $1 -q
	else
		ifconfig eth0 up
		udhcpc -q
	fi
}

# Mount http filesystem from the given server
# $1 = server
# $2 = mountdir
#
mount_httpfs() {
	debug_log "mount_httpfs" "$*"

	mkdir -p $2
	/bin/httpfs $1 $2 || return
	if [ -f $2/$(basename $1) ]; then
		echo $2/$(basename $1)
	else
		echo $2
	fi
}

# Mount curlftpfs filesystem from the given server
# $1 = server
# $2 = mountdir
#
mount_curlftpfs() {
	local OPTIONS
	debug_log "mount_curlftpfs" "$*"
	OPTIONS=
	[ "$(cmdline_value netfsoptions)" ] && OPTIONS="-o $(cmdline_value netfsoptions)"

	mkdir -p $2
	/usr/bin/curlftpfs $OPTIONS $1 $2 </dev/console >/dev/console 2>/dev/console || return
	if [ -f $2/$(basename $1) ]; then
		echo $2/$(basename $1)
	else
		echo $2
	fi
}

# Mount sshfs filesystem from the given server
# $1 = server
# $2 = mountdir
#
mount_sshfs() {
	local OPTIONS
	debug_log "mount_sshfs" "$*"
	OPTIONS=
	[ "$(cmdline_value netfsoptions)" ] && OPTIONS="-o $(cmdline_value netfsoptions)"
	mkdir -p $2
	times=3
	while [ $times -gt 0 ]; do
		/usr/bin/sshfs ${1/ssh:??/} $2 $OPTIONS </dev/console >/dev/console 2>/dev/console
		ERR=$?
		[ $ERR -eq 0 ] && break
		times=$(expr $times - 1)
	done
	[ $ERR -eq 0 ] || return
	if [ -f $2/$(basename $1) ]; then
		echo $2/$(basename $1)
	else
		echo $2
	fi
}

# Mount nfs filesystem from the given server
# $1 = server
# $2 = mountdir
#
mount_nfs() {
	debug_log "mount_nfs" "$*"
	mkdir -p $2
	modprobe nfs
	local SHARE=$(echo $1 | sed s-^nfs://--)
	if mount -t nfs $SHARE $2 -o nolock,rsize=4096,wsize=4096 2>/dev/null; then
		echo $2
	elif mount -t nfs $(dirname $SHARE) $2 -o nolock,rsize=4096,wsize=4096 2>/dev/null; then
		echo $2/$(basename $SHARE)
	fi
}

losetupfunc() {
	for i in $(grep /dev/loop /proc/mounts | awk {'print $1'}); do losetup $i; done
}

# Unload modules loaded to kernel which are not used
# This function used to unload more modules, but it may cause
# problems to auto-remove some of them (eg. a network module
# can seem unneeded even if network is to be used very soon.
#
rmmod_unused_modules() {
	debug_log "rmmod_unused_modules" "$*"
	#   rmmod usb-storage uhci-hcd ohci-hcd ehci-hcd 2>/dev/null
	rmmod yenta_socket rsrc_nonstatic pcmcia pcmcia_core 2>/dev/null
}

# kill all unneeded processes, which have bigger PID then the PID of
# current shell. We can't use killall5, as it would kill some processes
# which may be currently needed, for example ntfsmount.
# $1 = maximum pid (kill all lower)
#
killall_unneeded() {
	debug_log "killall_unneeded" "$*"
	local LIST PID

	PID=$1
	for pid in $(ps | grep -v "PID" | grep -E -v "\[.*\]" | fgrep -v mount | fgrep -v posixovl | fgrep -v ntfs | sed -r "s/^[[:space:]]*([0-9]+).*/\\1/"); do
		if [ $pid -lt $PID ]; then
			LIST="$LIST $pid"
		fi
	done

	kill -SIGTERM $LIST 2>/dev/null # SIGTERM
	sleep 2
	kill -SIGKILL $LIST 2>/dev/null # SIGKILL
}

# enable/disable CD autoejecting when unmounted
# $1 = 1|0 ... enable|disable
#
cd_autoeject() {
	debug_log "cd_autoeject" "$*"
	echo $1 >/proc/sys/dev/cdrom/autoeject
}

# ===========================================================
# FSTAB functions
# ===========================================================

# $1 = fstab file
# $2 = device name
dev_is_in_fstab() {
	debug_log "dev_is_in_fstab" "$*"
	cat "$1" | sed -r "s/#.*//" | grep -v "^[[:space:]]*none[[:space:]]" | grep -v "^[[:space:]]*tmpfs[[:space:]]" | grep -Eq "^[[:space:]]*$2[[:space:]]"
}

# update given line in fstab, add new values only if the device is not found
# $1 = fstab file to parse
# $2 = device name
# $3 = mountpoint
# $4 = filesystem
# $5 = mount options
#
fstab_add_line() {
	debug_log "fstab_add_line" "$*"
	local DIR

	if [ "$4" != "swap" ]; then DIR="$3"; else DIR="none"; fi
	if ! dev_is_in_fstab "$1" "$2"; then
		echo "$2" "$DIR" "$4" "$5" 0 0 "$FSTABLLFLAG" >>$1
	fi
}

# create correct fstab file in $1/etc/fstab and create apropriate
# mount directories in $1/mnt. This function is only calld once,
# during liveCD startup (even before init from the distro is started).
# $1 = root directory (union)
#
fstab_update() {
	debug_log "fstab_update" "$*"
	local FSTAB FSTABTMP

	FSTAB="$1/etc/fstab"
	FSTABTMP=$FSTAB$$
	mkdir -p $1/etc $1/mnt
	cat $FSTAB 2>/dev/null | grep -v "$FSTABLLFLAG" >$FSTABTMP

	if [ $(cmdline_parameter unionfs) ]; then
		fstab_add_line $FSTABTMP unionfs / unionfs defaults
	else
		fstab_add_line $FSTABTMP aufs / aufs defaults
	fi
	fstab_add_line $FSTABTMP proc /proc proc defaults
	fstab_add_line $FSTABTMP sysfs /sys sysfs defaults
	fstab_add_line $FSTABTMP devpts /dev/pts devpts gid=5,mode=620
	#   if [ "$XINO" != "$MEMORY" ] ;then
	#      fstab_add_line $FSTABTMP tmpfs /dev/shm tmpfs defaults
	#      fstab_add_line $FSTABTMP tmpfs /tmp tmpfs defaults
	#   fi
	for a in "/$MOUNTDIR/$LIVEMEDIA" "/$MOUNTDIR/$LIVECHANGES" "/$LOOPMOUNT" "/$LIVEREPOSITORY" "/$MOUNTDIR/$LIVEHOME"; do
		grep -q " $a " /proc/mounts || continue
		fstab_add_line $FSTABTMP $(grep " $a " /proc/mounts | head -1 | awk '{ print $1 " " $2 " " $3 " noauto," $4 }')
	done

	mv -f $FSTABTMP $FSTAB
	return

	list_cdrom_devices | while read DEVICE; do
		MNT=$(device_mountdir $DEVICE)
		FS=$(device_filesystem $DEVICE)
		if [ "$FS" = "" ]; then FS=iso9660; fi
		mkdir -p "$1/$MNT"
		fstab_add_line $FSTABTMP $DEVICE $MNT $FS $(fs_options $FS fstab)
	done
	list_partition_devices | while read DEVICE; do
		MNT=$(device_mountdir $DEVICE)
		FS=$(device_filesystem $DEVICE)
		OPT=$(fs_options $FS fstab)

		if [ "$FS" = "swap" ]; then
			fstab_add_line $FSTABTMP $DEVICE $MNT $FS $OPT
		fi

		# If the partition has a valid filesystem, add it to fstab
		if is_supported_filesystem "$FS"; then
			fstab_add_line $FSTABTMP $DEVICE $MNT $FS $OPT
			mkdir -p "$1/$MNT"
		fi
	done

	mv -f $FSTABTMP $FSTAB
}
