linux-live/livekitlib

864 lines
20 KiB
Bash
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

#!/bin/sh
# Functions library :: for Linux Live Kit scripts
# Author: Tomas M. <http://www.linux-live.org>
#
# =================================================================
# debug and output functions
# =================================================================
debug_start()
{
if grep -q debug /proc/cmdline; then
DEBUG_IS_ENABLED=1
set -x
else
DEBUG_IS_ENABLED=
fi
}
debug_log()
{
if [ "$DEBUG_IS_ENABLED" ]; then
echo "- debug: $*" >&2
log "- debug: $*"
fi
}
# header
# $1 = text to show
#
header()
{
echo """$@"""
}
# echo green star
#
echo_green_star()
{
echo -ne """* """
}
# log - store given text in /var/log/livedbg
log()
{
echo "$@" 2>/dev/null >>/var/log/livedbg
}
echolog()
{
echo "$@"
log "$@"
}
# 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
setsid sh -c 'exec sh < /dev/tty1 >/dev/tty1 2>&1'
echo
fi
}
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
setsid sh -c 'exec sh < /dev/tty1 >/dev/tty1 2>&1'
}
# get value of commandline 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
}
# test if the script is started by root user. If not, exit
#
allow_only_root()
{
if [ "0$UID" -ne 0 ]; then
echo "Only root can run $(basename $0)"; exit 1
fi
}
# Create 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
#
create_bundle()
{
debug_log "create_module" "$*"
rm -f "$2" # overwrite, never append to existing file
mksquashfs "$1" "$2" -comp xz -b 512K $3 $4 $5 $6 $7 $8 $9>/dev/null
}
# 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 "switch root from initramfs to ramfs"
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 etc
#
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
# $1 = -e for match, -v for negative match
# $2 = regex pattern
#
modprobe_everything()
{
debug_log "modprobe_everything" "$*"
echo_green_star >&2
echo "Probing for hardware" >&2
find /lib/modules/ | fgrep .ko | egrep $1 $2 | sed -r "s:^.*/|[.]ko\$::g" | 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_green_star
echo "Setting dynamic RAM compression using ZRAM if available"
if [ -r /sys/block/zram0/disksize ]; then
echo 536870912 > /sys/block/zram0/disksize # 512MB
mkswap /dev/zram0 >/dev/null
swapon /dev/zram0
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>/dev/null
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_green_star
echo "Setting up union using AUFS"
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
}
# Get device tag.
# $1 = device
# $2 = tag name, such as TYPE, LABEL, UUID, etc
#
device_tag()
{
blkid -s $2 "$1" | sed -r "s/^[^=]+=//" | tr -d '"'
}
# 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="$(device_tag "$1" TYPE | tr [A-Z] [a-z])"
if [ "$FS" = "msdos" -o "$FS" = "fat" -o "$FS" = "vfat" ]; then
FS="vfat"
elif [ "$FS" = "ntfs" ]; then
FS="ntfs-3g"
fi
echo "-t $FS"
}
# Filesystem options for initial mount
# $1.. = filesystem or '-t filesystem'
# returns also the $1.. parameter back where appropriate
#
fs_options()
{
debug_log "fs_options" "$*"
local FS
FS="$*"
if [ "$1" = "-t" ]; then
shift
else
FS="-t $FS"
fi
if [ "$1" = "ntfs-3g" ]; then
FS=""
fi
echo -n "$FS -o rw"
if [ "$1" = "vfat" ]; then
echo ",check=s,shortname=mixed,iocharset=utf8"
fi
}
# Mount command for given filesystem
# $1.. = filesystem or '-t filesystem'
#
mount_command()
{
debug_log "mount_command" "$*"
if [ "$1" = "-t" ]; then
shift
fi
if [ "$1" = "ntfs-3g" ]; then
echo "@mount.ntfs-3g"
return
fi
echo "mount"
}
# echo first network device known at the moment of calling, eg. eth0
#
network_device()
{
debug_log "network_device" "$*"
cat /proc/net/dev | grep : | grep -v lo: | cut -d : -f 1 | tr -d " " | head -n 1
}
# 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" "$*"
local MODULE ETH
for MODULE in 3c59x acenic 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 forcedeth sb1000 sis900; do
modprobe $MODULE 2>/dev/null
ETH="$(network_device)"
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 else
modprobe_everything -e /drivers/net/
echo $(network_device)
}
# Initialize network IP address
# either static from ip=bootparameter, or from DHCP
#
init_network_ip()
{
debug_log "init_network_ip" "$*"
local IP ETH SCRIPT CLIENT SERVER GW MASK
SCRIPT=/tmp/dhcpscript
ETH=$(init_network_dev)
IP=$(cmdline_value ip)
echo "* Setting up network" >&2
if [ "$IP" != "" ]; then
# set IP address as given by boot paramter
echo "$IP" | while IFS=":" read CLIENT SERVER GW MASK; do
ifconfig $ETH "$CLIENT" netmask "$MASK"
route add default gw "$GW"
echo nameserver "$GW" >> /etc/resolv.conf
echo nameserver "$SERVER" >> /etc/resolv.conf
done
else
# if client ip is unknown, try to get a DHCP lease
ifconfig $ETH up
echo -e '#!/bin/sh\nif [ "$1" != "bound" ]; then exit; fi\nifconfig $interface $ip netmask $subnet\nroute add default gw $router\necho nameserver $dns >>/etc/resolv.conf' >$SCRIPT
chmod a+x $SCRIPT
udhcpc -i $ETH -n -s $SCRIPT -q >/dev/null
fi
}
# Mount data from http using httpfs
# $1 = from URL
# $2 = target
mount_data_http()
{
debug_log "mount_data_http" "$*"
local CACHE
echo_green_star >&2
echo "Load data from $1" >&2
CACHE=$(cmdline_value cache | sed -r "s/[^0-9]//g" | sed -r "s/^0+//g")
if [ "$CACHE" != "" ]; then
CACHE="-C /tmp/httpfs.cache -S "$(($CACHE*1024*1024))
fi
init_network_ip
if [ "$(network_device)" != "" ]; then
echo "* Mounting remote file..." >&2
mkdir -p "$2"
@mount.httpfs2 -r 9999 -t 5 $CACHE -c /dev/null "$1" "$2" -o ro >/dev/null 2>/dev/null
mount -o loop "$2"/* "$2" # self mount
echo "$2/$LIVEKITNAME"
fi
}
# Download data from tftp
# $1 = target (store downloaded files there)
#
download_data_pxe()
{
debug_log "download_data_pxe" "$*"
local IP CMD CLIENT SERVER GW MASK PORT PROTOCOL
mkdir -p "$1/$LIVEKITNAME"
IP="$(cmdline_value ip)"
echo "$IP" | while IFS=":" read CLIENT SERVER GW MASK PORT; do
echo_green_star >&2
echo "Contacting PXE server $SERVER" >&2
if [ "$PORT" = "" ]; then PORT="7529"; fi
init_network_ip
echo "* Downloading PXE file list"
PROTOCOL=http
wget -q -O "$1/PXEFILELIST" "http://$SERVER:$PORT/PXEFILELIST?$(uname -r):$(uname -m)"
if [ $? -ne 0 ]; then
echo "Error downloading from http://$SERVER:$PORT, trying TFTP" >&2
PROTOCOL=tftp
tftp -g -r PXEFILELIST -l "$1/PXEFILELIST" $SERVER
fi
echo "* Downloading files from the list"
cat "$1/PXEFILELIST" | while read FILE; do
if [ "$PROTOCOL" = "http" ]; then
wget -O "$1/$LIVEKITNAME/$FILE" "http://$SERVER:$PORT/$FILE"
else
echo "* $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 MOUNT
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="$(mount_command $FS)"
$MOUNT "$DEVICE" "$1" $OPTIONS 2>/dev/null
# if the FROM parameter is actual file, mount it again as loop (eg. iso)
if [ -f "$1/$FROM" ]; then
mkdir -p "$1/../iso"
mount -o loop,ro "$1/$FROM" "$1/../iso" 2>/dev/null
FROM="../iso/$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
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
FROM="$(cmdline_value from)"
# boot parameter specified as from=http://server.com/file.iso
if [ "$(echo $FROM | grep 'http://')" != "" ]; then
mount_data_http "$FROM" "$2"
return
fi
# if we got IP address as boot parameter, it means we booted over PXE
if [ "$(cmdline_value ip)" != "" ]; then
download_data_pxe "$2"
return
fi
if [ "$FROM" = "" ]; then FROM="$LIVEKITNAME"; fi
echo_green_star >&2
echo -n "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 "" >&2
echo "* Found on $(mounted_device "$2")" >&2
echo "$DATA"
return
fi
sleep 1
done
echo "" >&2
}
# Check if data is found and exists
# $1 = data directory
#
check_data_found()
{
if [ "$1" = "" -o ! -d "$1" ]; then
fatal "Could not locate $LIVEKITNAME data";
fi
}
# Activate persistent changes
# $1 = data directory
# $2 = target changes directory
#
persistent_changes()
{
debug_log "persistent_changes" "$*"
local CHANGES T1 T2 EXISTS
CHANGES="$1/$(basename "$2")"
T1="$CHANGES/.empty"
T2="$T1"2
# 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 "* Persistent changes not writable or not used"
return
fi
echo_green_star
echo "Testing persistent changes for posix compatibility"
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 [ $? -eq 0 ]; then
echo "* Activating native persistent changes"
mount --bind "$CHANGES" "$2"
return
fi
if [ -e "$CHANGES/changes.dat" ]; then
echo "* Restoring persistent changes"
EXISTS="true"
else
echo "* Creating new persistent changes"
EXISTS=""
fi
# test sparse file support. If supported, use it in favor of fynfilefs
dd if=/dev/zero of="$T1" seek=200k bs=1 count=0 >/dev/null 2>/dev/null && \
test "$(du -k "$T1" | sed -r "s/[^0-9].*//")" -lt 200
if [ $? -eq 0 ]; then
if [ ! "$EXISTS" ]; then
dd if=/dev/zero of="$CHANGES/changes.dat" seek=16000M bs=1 count=0 >/dev/null 2>&1
mke2fs -F "$CHANGES/changes.dat" >/dev/null 2>&1
fi
mount -o loop "$CHANGES/changes.dat" "$2"
else
@mount.dynfilefs "$CHANGES/changes.dat" 4000 "$2"
if [ ! "$EXISTS" ]; then
mke2fs -F "$2/loop.fs" >/dev/null 2>&1
fi
mount -o loop,sync "$2/loop.fs" "$2"
fi
# if test failed at any point, we may have temp files left behind
rm "$T1" "$T2" 2>/dev/null
rmdir "$2/lost+found" 2>/dev/null
}
# 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_green_star
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 toram /proc/cmdline; then
echo "$1"
return
fi
echo "* Copying $LIVEKITNAME data to RAM..." >&2
RAM="$(dirname "$2")"/toram
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_green_star
echo "Adding bundles to union"
( ls -1 "$1" | sort -n ; cd "$1" ; find modules/ 2>/dev/null | sortmod | filter_load) | grep '[.]'$BEXT'$' | filter_noload | while read BUNDLE; do
echo "* $BUNDLE"
BUN="$(basename "$BUNDLE")"
mkdir -p "$2/$BUN"
mount -o loop,ro -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 DEVICE FS LABEL
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
if grep -vq automount /proc/cmdline; then
return
fi
echo >> $FSTAB
blkid | cut -d: -f 1 | while read DEVICE; do
FS="$(device_tag $DEVICE TYPE)"
LABEL="$(device_tag $DEVICE LABEL | sed -r "s:[/ ]:_:g")"
if [ "$LABEL" = "" ]; then LABEL="$(basename $DEVICE)"; fi
if [ "$FS" != "swap" -a "$FS" != "squashfs" ]; then
mkdir -p "$1/media/$LABEL"
echo "$DEVICE" "/media/$LABEL" $FS defaults,noatime 0 0 >> $FSTAB
fi
done
}
# Change root and execute init
# $1 = where to change root
#
change_root()
{
debug_log "change_root" "$*"
# if we are booting over httpfs, we need to copyup some files so they are
# accessible on union without any further lookup down, else httpfs locks
if [ "$(network_device)" != "" ]; then
touch "/net.up.flag"
touch "$1/etc/resolv.conf"
touch "$1/etc/hosts"
touch "$1/etc/gai.conf"
fi
umount /proc
umount /sys
cd "$1"
# make sure important device files and directories are in union
mkdir -p boot dev proc sys tmp media 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 "Can't find executable chroot command"; 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 "Can't find executable init command"; fi
mkdir -p run
mount -t tmpfs tmpfs run
mkdir -p run/initramfs
mount -n -o remount,ro aufs .
pivot_root . run/initramfs
exec $CHROOT . $INIT < dev/console > dev/console 2>&1
}