#!/bin/sh # Functions library :: for Linux Live Kit scripts # Original author: Tomas M. # Modified by: 'JohnDaH4x0r' # related to build script # get_exclude: Generate exclude arguments for 'mksquashfs' # $1: the exclude list ("$EXCLUDE", if you use .config) get_exclude() { EXCLUDE_LIST="$1" PARENT_DIR="$(echo "$2" | tr -s "/")" if [ -z "$PARENT_DIR" ]; then return 1 elif [ "$PARENT_DIR" = "/" ]; then return 1 fi echo "$EXCLUDE_LIST" | tr " " "\n" | grep "$PARENT_DIR" \ | while read KEY; do echo -n "-e $KEY " done } # NOTE: DO NOT TOUCH THE ESCAPE CHARACTERS! BLUE="" YELLOW="" ORANGE="" GREEN="" BOLD="" RED="" NC="" # debug related debug_start() { if grep -q debug /proc/cmdline; then DEBUG_IS_ENABLED=1 else DEBUG_IS_ENABLED= fi } dbg_shell_start() { if grep -q 'dbg_shell' /proc/cmdline; then DBG_SHELL=1 else DBG_SHELL= fi } debug_log() { if [ "$DEBUG_IS_ENABLED" ]; then echo "[${GREEN}"' debug '"${NC}]: $@" >&2 log "debug_log: $*" fi } # echo related functions # echo fatal tag # $1 = text to show echo_fatal() { echo "[${RED}"' fatal '"${NC}] $@" >&2 } # echo bold # $1 = text to show # echo_bold() { echo "$BOLD""$@""$NC" } # echo livekit tag echo_livekit() { echo "[${BLUE}"'livekit'"${NC}] " } echo_livekit_msg() { echo "[${BLUE}"'livekit'"${NC}] $@" } # echo error echo_err() { echo "[${RED}"' error '"${NC}] $@" >&2 } # echo warn echo_warn() { echo "[${YELLOW}"' warn! '"${NC}] $@" >&2 } # echo signature echo_sign() { echo "${BOLD}"' Linux Live Kit Improved v1.0 - '"$* ${NC}" } # log - store given text in /var/log/livedbg log() { echo "$@" 2>/dev/null >>/var/log/livekit-log } echolog() { echo "$@" log "echolog: $@" } # show information about the debug shell show_debug_banner() { echo_sign "DEBUGGING MODE" echo "The root shell is prepared for you!" >&2 echo "Type your desired commands or press Ctrl-D to continue booting." >&2 echo >&2 } # debug_shell # executed when debug boot parameter is present # debug_shell() { if [ "$DBG_SHELL" ]; then show_debug_banner setsid sh -c 'exec sh < /dev/tty1 >/dev/tty1 2>&1' echo fi } fatal() { log "FatalError: $@" echo_fatal "$@" echo_sign "EMERGENCY MODE" >&2 echo_bold "Please consult /var/log/livekit-log for more info." >&2 setsid sh -c 'exec sh < /dev/tty1 >/dev/tty1 2>&1' } # test if the script is started by root user. If not, exit # allow_only_root() { if [ "0$UID" -ne 0 ]; then echo_err "Only root can run $(basename $0)"; exit 1 fi } # Make bundle # call mksquashfs with apropriate arguments # $1 = directory which will be compressed to squashfs bundle # $2 = output file # $3..$9 = optional arguments like -keep-as-directory or -b 123456789 # mkbund() { debug_log "mkbund" "$*" if [ -e "$2" ]; then echo_warn "mkbund: `basename $2`: File already exists!" echo_warn "mkbund: `basemane $2`: Deleting file..." rm -f "$2" else echo_livekit_msg "mkbund: `basename $2`: Making new bundle..." fi echo_livekit_msg "mkbund: SquashFS compression: 1024k - XZ" >&2 echo_livekit_msg "mkbund: `basename $2`: Compressing bundle..." >&2 mksquashfs "$1" "$2" -comp xz -b 1024k $3 $4 $5 $6 $7 $8 $9 >/dev/null || \ (echo_err "mkbund: Failed to make bundle!" && exit 1) } # Now, for the Live Kit system-related stuff # get value of kernel cmdline parameter $1 # $1 = parameter to search for # cmdline_value() { cat /proc/cmdline | egrep -o "(^|[[:space:]])$1=[^[:space:]]+" | \ tr -d " " | cut -d "=" -f 2- | tail -n 1 } # Move entire initramfs tree to tmpfs mount. # It's a bit tricky but is necessray to enable pivot_root # even for initramfs boot image # transfer_initramfs() { if [ ! -r /lib/initramfs_escaped ]; then echo_livekit echo "Switching from initramfs to tmpfs..." SWITCH=/m # one letter directory mkdir -p $SWITCH mount -t tmpfs -o size="100%" tmpfs $SWITCH cp -a /??* $SWITCH 2>/dev/null # only copy two-and-more-letter directories cd $SWITCH echo "This file indicates that we successfully escaped initramfs" > $SWITCH/lib/initramfs_escaped exec switch_root -c /dev/console . $0 fi } # mount virtual filesystems like proc, sys and such # init_proc_sysfs() { debug_log "init_proc_sysfs" "$*" mkdir -p /proc /sys /etc $MEMORY mount -n -t proc proc /proc echo "0" >/proc/sys/kernel/printk mount -n -t sysfs sysfs /sys mount -n -o remount,rw rootfs / ln -sf /proc/mounts /etc/mtab } # modprobe all modules found in initial ramdisk modprobe_everything() { debug_log "modprobe_everything" "$*" echo_livekit_msg "Probing for hardware..." >&2 find /lib/modules/ | fgrep '.ko' | xargs -n 1 modprobe 2>/dev/null refresh_devs } refresh_devs() { debug_log "refresh_devs" "$*" if [ -r /proc/sys/kernel/hotplug ]; then echo /sbin/mdev > /proc/sys/kernel/hotplug fi mdev -s } # make sure some devices are there init_devs() { debug_log "init_devs" "$*" modprobe zram 2>/dev/null modprobe loop 2>/dev/null modprobe squashfs 2>/dev/null modprobe fuse 2>/dev/null refresh_devs } # Activate zram (auto-compression of RAM) # Compressed RAM consumes 1/2 or even 1/4 of original size # Setup static size of 500MB # init_zram() { debug_log "init_zram" "$*" echo_livekit_msg "Setting up ZRAM as swap if available..." if [ -r /sys/block/zram0/disksize ]; then echo 536870912 > /sys/block/zram0/disksize # 512MB mkswap /dev/zram0 >/dev/null swapon /dev/zram0 -p 32767 echo 100 > /proc/sys/vm/swappiness fi } # load the AUFS kernel module if needed # init_aufs() { debug_log "init_aufs" "$*" # TODO maybe check here if aufs support is working at all # and produce useful error message if user has no aufs modprobe aufs 2>/var/log/livekit-log # TODO ACCOMPLISHED: # If aufs module failed to load, panic immidiately if [ $? -ne 0 ]; then fatal "Failed to load AUFS module!" fi # finally, refresh all devices refresh_devs } # Setup empty union # $1 = changes directory (ramfs or persistent changes) # $2 = union directory where to mount the union # init_union() { debug_log "init_union" "$*" echo_livekit_msg "Initialising AUFS union..." mkdir -p "$1" mkdir -p "$2" mount -t aufs -o xino="/.xino",trunc_xino,br="$1" aufs "$2" } # Return device mounted for given directory # $1 = directory # mounted_device() { debug_log "mounted_device" "$*" local MNT TARGET MNT="$1" while [ "$MNT" != "/" -a "$MNT" != "." -a "$MNT" != "" ]; do TARGET="$(grep -F " $MNT " /proc/mounts | cut -d " " -f 1)" if [ "$TARGET" != "" ]; then echo "$TARGET" return fi MNT="$(dirname "$MNT")" done } # Return mounted dir for given directory # $1 = directory # mounted_dir() { debug_log "mounted_dir" "$*" local MNT MNT="$1" while [ "$MNT" != "/" -a "$MNT" != "." -a "$MNT" != "" ]; do if mountpoint -q "$MNT" 2>/dev/null; then echo "$MNT" return fi MNT="$(dirname "$MNT")" done } # Make sure to mount FAT12/16/32 using vfat # in order to support long filenames # $1 = device # device_bestfs() { debug_log "device_bestfs" "$*" local FS FS="$(blkid "$1" | sed -r "s/.*TYPE=//" | tr -d '"' | \ tr "[A-Z]" "[a-z]" | cut -d ' ' -f 1)" if [ "$FS" = "msdos" -o "$FS" = "fat" ]; then FS="vfat" elif [ "$FS" = "ntfs" ]; then FS="ntfs-3g" else FS="$FS" fi echo "-t $FS" } # Filesystem options for mount # $1 = filesystem or '-t filesystem' # fs_options() { debug_log "fs_options" "$*" if [ "$1" = "-t" ]; then shift fi if [ "$1" = "vfat" ]; then echo "-o check=s,shortname=mixed,iocharset=utf8" fi } # Modprobe network kernel modules until a working driver is found. # These drivers are (or used to be) probed in Slackware's initrd. # The function returns the first device found, yet it doesn't have # to be a working one, eg. if the computer has two network interfaces # and ethernet cable is plugged only to one of them. # init_network_dev() { debug_log "init_network_dev" "$*" echo_livekit_msg "Preparing networking device(s)..." >&2 local MODULE ETH for MODULE in 3c59x acenic de4x5 e1000 e1000e e100 epic100 hp100 \ ne2k-pci pcnet32 8139too 8139cp tulip via-rhine r8169 atl1e yellowfin \ tg3 dl2k ns83820 atl1 b44 bnx2 skge sky2 tulip depca 3c501 3c503 \ 3c505 3c507 3c509 3c515 ac3200 at1700 cosa cs89x0 de600 de620 e2100 \ eepro eexpress eth16i ewrk3 forcedeth hostess_sv11 hp-plus hp ni52 \ ni65 sb1000 sealevel smc-ultra sis900 smc9194 wd; do modprobe $MODULE 2>/dev/null ETH="$(cat /proc/net/dev | grep : | grep -v lo: | cut -d : -f 1 | tr -d " " | head -n 1)" if [ "$ETH" != "" ]; then echo $ETH return 0 fi rmmod $MODULE 2>/dev/null done # If we are here, none of the above specified modules worked. # As a last chance, try to modprobe everything. echo_warn "No networking kernel module found!" modprobe_everything cat /proc/net/dev | grep : | grep -v lo: | cut -d : -f 1 | tr -d " " | head -n 1 } # Download data from tftp # $1 = target (store downloaded files there) # download_data_pxe() { debug_log "download_data_pxe" "$*" local CMD CLIENT SERVER GW MASK PORT ETH PROTOCOL mkdir -p "$1/$LIVEKITNAME" cmdline_value ip | while IFS=":" read CLIENT SERVER GW MASK PORT; do echo_livekit_msg "Downloading files from $SERVER ..." >&2 ETH=$(init_network_dev) if [ "$PORT" = "" ]; then PORT="7529"; fi # set IP address as given by boot paramter if [ "$CLIENT" != "" -a "$MASK" != "" ]; then ifconfig $ETH "$CLIENT" netmask "$MASK" route add default gw "$GW" else # if client ip is unknown, try to get a DHCP lease udhcpc -i $ETH -f -q fi # well known IP address of Google public DNS service echo nameserver 8.8.8.8 >> /etc/resolv.conf PROTOCOL=http wget -q -O "$1/PXEFILELIST" "http://$SERVER:$PORT/PXEFILELIST?$(uname -r):$(uname -m)" if [ $? -ne 0 ]; then echo_warn "Failed to download from http://$SERVER:$PORT, trying TFTP..." PROTOCOL=tftp tftp -g -r PXEFILELIST -l "$1/PXEFILELIST" $SERVER fi cat "$1/PXEFILELIST" | while read FILE; do if [ "$PROTOCOL" = "http" ]; then wget -O "$1/$LIVEKITNAME/$FILE" "http://$SERVER:$PORT/$FILE" else echo_livekit_msg "PXE: ${FILE}..." >&2 tftp -g -r $FILE -l "$1/$LIVEKITNAME/$FILE" $SERVER fi done done echo "$1/$LIVEKITNAME" } # Find LIVEKIT data by mounting all devices # If found, keep mounted, else unmount # $1 = data directory target (mount here) # $2 = data directory which contains compressed bundles # find_data_try() { debug_log "find_data_try" "$*" local DEVICE FS FROM OPTIONS mkdir -p "$1" blkid | sort | cut -d: -f 1 | grep -E -v "/loop|/ram|/zram" | while read DEVICE; do FROM="$2" FS="$(device_bestfs "$DEVICE")" OPTIONS="$(fs_options $FS)" mount -r "$DEVICE" "$1" $FS $OPTIONS 2>/dev/null # if the FROM parameter is actual file, mount it again as loop (eg. iso) if [ -f "$1/$FROM" ]; then mount -o remount,rw "$DEVICE" "$1" 2>/dev/null mkdir -p "$1/../file" mount -o loop,ro "$1/$FROM" "$1/../file" 2>/dev/null FROM="../file/$LIVEKITNAME" fi # search for bundles in the mounted directory if [ "$(find "$1/$FROM" -maxdepth 1 -name "*.$BEXT" 2>/dev/null)" != "" ]; then # we found at least one bundle/module here mount -o remount,rw "$DEVICE" "$1" 2>/dev/null echo "$1/$FROM" | tr -s "/" | sed -r "s:/[^/]+/[.][.]/:/:g" return fi # unmount twice, since there could be mounted ISO as loop too. If not, it doesn't hurt umount "$1" 2>/dev/null umount "$1" 2>/dev/null done } # Retry finding LIVEKIT data several times, # until timeouted or until data is found # $1 = timeout # $2 = data directory target (mount here) # find_data() { debug_log "find_data" "$*" local DATA FROM if [ "$(cmdline_value ip)" != "" ]; then download_data_pxe "$2" return fi FROM="$(cmdline_value from)" if [ "$FROM" = "" ]; then FROM="$LIVEKITNAME"; fi echo_livekit >&2 echo "Looking for $LIVEKITNAME data in /$FROM .." | tr -s "/" >&2 for timeout in $(seq 1 $1); do echo -n "." >&2 refresh_devs DATA="$(find_data_try "$2" "$FROM")" if [ "$DATA" != "" ]; then echo echo_livekit_msg "$LIVEKITNAME data found on $(mounted_device "$2")" >&2 echo "$DATA" return fi sleep 1 done echo "" >&2 if [ "$DATA" = "" ]; then fatal "$LIVEKITNAME data not found" fi } # Activate persistent changes # $1 = data directory # $2 = target changes directory # persistent_changes() { debug_log "persistent_changes" "$*" local CHANGES T1 T2 CHANGES="$1/$(basename "$2")" T1="$CHANGES/.perch-test.dat" T2="${T1}-clone.dat" # Setup the directory anyway, it will be used in all cases mkdir -p "$2" # If persistent changes are not requested, end here if grep -vq perch /proc/cmdline; then return fi # check if changes directory exists and is writable touch "$T1" 2>/dev/null && rm -f "$T1" 2>/dev/null # if not, simply return back if [ $? -ne 0 ]; then echo_warn "Persistent changes not writable or not used." return fi echo_livekit_msg "Testing persistent changes for POSIX compatibility..." >&2 touch "$T1" && ln -sf "$T1" "$T2" 2>/dev/null && \ chmod +x "$T1" 2>/dev/null && test -x "$T1" && \ chmod -x "$T1" 2>/dev/null && test ! -x "$T1" && \ rm "$T1" "$T2" 2>/dev/null if [ $? -ne 0 ]; then echo_warn "File system is not POSIX-compatible!" >&2 echo_livekit_msg "Activating DynFileFS persistent changes..." >&2 rm "$T1" "$T2" 2>/dev/null mount.dynfilefs "$CHANGES/changes.dat" 4000 "$2" if [ "$(device_bestfs "$2/loop.fs" | tr -d " ")" = "-t" ]; then mke2fs -F "$2/loop.fs" >/dev/null fi mount -o loop,sync "$2/loop.fs" "$2" rmdir "$2/lost+found" 2>/dev/null else echo_livekit_msg "Activating native persistent changes..." >&2 mount --bind "$CHANGES" "$2" fi } # Copy content of rootcopy directory to union # $1 = data directory # $2 = union directory copy_rootcopy_content() { debug_log "copy_rootcopy_content" "$*" if [ "$(ls -1 "$1/rootcopy/" 2>/dev/null)" != "" ]; then echo_livekit echo "Copying content of rootcopy directory..." cp -a "$1"/rootcopy/* "$2" fi } # Copy data to RAM if requested # $1 = live data directory # $2 = changes directory # copy_to_ram() { debug_log "copy_to_ram" "$*" local MDIR MDEV RAM CHANGES if grep -vq 'copy2ram' /proc/cmdline; then echo "$1" return fi echo_livekit_msg "Copying $LIVEKITNAME data to RAM..." >&2 RAM="$(dirname "$2")"/copy_to_ram mkdir -p "$RAM" cp -a "$1"/* "$RAM" echo "$RAM" MDIR="$(mounted_dir "$1")" MDEV="$(mounted_device "$1")" MDEV="$(losetup $MDEV 2>/dev/null | cut -d " " -f 3)" umount "$MDIR" 2>/dev/null if [ "$MDEV" ]; then # iso was mounted here, try to unmount the FS it resides on too MDEV="$(mounted_device "$MDEV")" umount "$MDEV" 2>/dev/null fi } # load filter # filter_load() { local FILTER FILTER=$(cmdline_value load) if [ "$FILTER" = "" ]; then cat - else cat - | egrep "$FILTER" fi } # noload filter # filter_noload() { local FILTER FILTER=$(cmdline_value noload) if [ "$FILTER" = "" ]; then cat - else cat - | egrep -v "$FILTER" fi } # sort modules by number even if they are in subdirectory # sortmod() { cat - | sed -r "s,(.*/(.*)),\\2:\\1," | sort -n | cut -d : -f 2- } # Mount squashfs filesystem bundles # and add them to union # $1 = directory where to search for bundles # $2 = directory where to mount bundles # $3 = directory where union is mounted # union_append_bundles() { debug_log "union_append_bundles" "$*" local BUN echo_livekit >&2 echo "Appending bundles to union..." >&2 # Just tell me why! Why be compact?!? ( ls -1 "$1" | sort -n ; cd "$1" ; find modules/ 2>/dev/null | \ sortmod | filter_load) | grep '[.]'$BEXT'$' |\ filter_noload | \ while read BUNDLE; do echo_livekit_msg "Appending: $BUNDLE" >&2 BUN="$(basename "$BUNDLE")" mkdir -p "$2/$BUN" # It now depends on your kernel! mount -o loop -t squashfs "$1/$BUNDLE" "$2/$BUN" mount -o remount,add:1:"$2/$BUN" aufs "$3" done } # Create empty fstab properly # $1 = root directory # fstab_create() { debug_log "fstab_create" "$*" local FSTAB FSTAB="$1/etc/fstab" echo aufs / aufs defaults 0 0 > $FSTAB echo proc /proc proc defaults 0 0 >> $FSTAB echo sysfs /sys sysfs defaults 0 0 >> $FSTAB echo devpts /dev/pts devpts gid=5,mode=620 0 0 >> $FSTAB echo tmpfs /dev/shm tmpfs defaults 0 0 >> $FSTAB } # Change root and execute init # $1 = where to change root # change_root() { debug_log "change_root" "$*" echo_livekit_msg "Changing root..." >&2 umount /proc umount /sys cd "$1" # make sure important device files and directories are in union mkdir -p boot dev proc sys tmp mnt run chmod 1777 tmp if [ ! -e dev/console ]; then mknod dev/console c 5 1; fi if [ ! -e dev/tty ]; then mknod dev/tty c 5 0; fi if [ ! -e dev/tty0 ]; then mknod dev/tty0 c 4 0; fi if [ ! -e dev/tty1 ]; then mknod dev/tty1 c 4 1; fi if [ ! -e dev/null ]; then mknod dev/null c 1 3; fi if [ ! -e sbin/fsck.aufs ]; then ln -s /bin/true sbin/fsck.aufs; fi # find chroot and init if [ -x bin/chroot -o -L bin/chroot ]; then CHROOT=bin/chroot; fi if [ -x sbin/chroot -o -L sbin/chroot ]; then CHROOT=sbin/chroot; fi if [ -x usr/bin/chroot -o -L usr/bin/chroot ]; then CHROOT=usr/bin/chroot; fi if [ -x usr/sbin/chroot -o -L usr/sbin/chroot ]; then CHROOT=usr/sbin/chroot; fi if [ "$CHROOT" = "" ]; then fatal "chroot: Executable not found!"; fi if [ -x bin/init -o -L bin/init ]; then INIT=bin/init; fi if [ -x sbin/init -o -L sbin/init ]; then INIT=sbin/init; fi if [ "$INIT" = "" ]; then fatal "init: Executable not found!"; fi mkdir -p mnt/live mount -n -o remount,ro aufs . pivot_root . mnt/live exec $CHROOT . $INIT < dev/console > dev/console 2>&1 }