mirror of
https://github.com/HEYAHONG/linux-live.git
synced 2025-10-14 02:07:48 +08:00
1110 lines
28 KiB
Bash
1110 lines
28 KiB
Bash
#!/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
|
||
fi
|
||
log "- debug: $*"
|
||
}
|
||
|
||
# header
|
||
# $1 = text to show
|
||
#
|
||
header()
|
||
{
|
||
echo "[0;1m""$@""[0;0m"
|
||
}
|
||
|
||
|
||
# echo green star
|
||
#
|
||
echo_green_star()
|
||
{
|
||
echo -ne "[0;32m""* ""[0;39m"
|
||
}
|
||
|
||
# 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 1024K -Xbcj x86 -always-use-fragments $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
|
||
}
|
||
|
||
|
||
aufs_is_supported()
|
||
{
|
||
cat /proc/filesystems | grep aufs
|
||
}
|
||
|
||
|
||
# load the AUFS kernel driver. If not found,
|
||
# load overlayfs instead.
|
||
#
|
||
init_aufs()
|
||
{
|
||
debug_log "init_aufs" "$*"
|
||
modprobe aufs 2>/dev/null
|
||
if ! aufs_is_supported >/dev/null; then
|
||
modprobe overlay 2>/dev/null
|
||
fi
|
||
refresh_devs
|
||
}
|
||
|
||
|
||
# Setup empty aufs union, or create overlayfs union
|
||
# $1 = changes directory (ramfs or persistent changes)
|
||
# $2 = union directory where to mount the union
|
||
# $3 = bundles directory
|
||
#
|
||
init_union()
|
||
{
|
||
debug_log "init_union" "$*"
|
||
mkdir -p "$1"
|
||
mkdir -p "$2"
|
||
|
||
if aufs_is_supported >/dev/null; then
|
||
echo_green_star
|
||
echo "Setting up empty union using aufs"
|
||
mount -t aufs -o xino="/.xino",trunc_xino,br="$1" aufs "$2"
|
||
else
|
||
echo_green_star
|
||
echo "Setting up union using overlayfs"
|
||
mkdir -p "$1/changes"
|
||
mkdir -p "$1/workdir"
|
||
mount -t overlay overlay -o lowerdir=$(find "$3" -mindepth 1 -maxdepth 1 | sortmod | tac | tr '\n' ':' | sed -r 's/:$//'),upperdir=$1/changes,workdir=$1/workdir $2
|
||
fi
|
||
}
|
||
|
||
|
||
# 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
|
||
}
|
||
|
||
|
||
# Initialize blkid cache by manually probing all devices
|
||
#
|
||
init_blkid_cache()
|
||
{
|
||
local DEV
|
||
cat /proc/partitions | tr -s " " | cut -d " " -f 5 | while read DEV; do
|
||
blkid /dev/$DEV >/dev/null 2>/dev/null
|
||
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
|
||
# $2 = prefix to add, like -t
|
||
#
|
||
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="ntfs3"
|
||
fi
|
||
|
||
if [ "$2" != "" ]; then
|
||
echo -n "$2"
|
||
fi
|
||
|
||
echo "$FS"
|
||
}
|
||
|
||
|
||
# Filesystem options for initial mount
|
||
# $1.. = filesystem
|
||
#
|
||
fs_options()
|
||
{
|
||
debug_log "fs_options" "$*"
|
||
|
||
echo -n "-t $1 "
|
||
echo -n "-o rw"
|
||
|
||
if [ "$1" = "vfat" ]; then
|
||
echo ",check=s,shortname=mixed,iocharset=utf8"
|
||
fi
|
||
}
|
||
|
||
|
||
# Mount command for given filesystem
|
||
# $1.. = filesystem
|
||
#
|
||
mount_command()
|
||
{
|
||
debug_log "mount_command" "$*"
|
||
|
||
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: | grep -v ip6tnl | 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
|
||
}
|
||
|
||
|
||
# stdin = files to get
|
||
# $1 = server
|
||
# $2 = destination directory
|
||
#
|
||
tftp_mget()
|
||
{
|
||
while read FNAME; do
|
||
echo "* $FNAME ..." >&2
|
||
tftp -b 1486 -g -r "$FNAME" -l "$2/$FNAME" "$1"
|
||
done
|
||
}
|
||
|
||
|
||
# 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 JOBS
|
||
|
||
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" >&2
|
||
|
||
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
|
||
echo PXEFILELIST | tftp_mget "$SERVER" "$1"
|
||
fi
|
||
|
||
echo "* Downloading files from the list" >&2
|
||
|
||
if [ "$PROTOCOL" = "http" ]; then
|
||
cat "$1/PXEFILELIST" | while read FILE; do
|
||
wget -O "$1/$LIVEKITNAME/$(basename $FILE)" "http://$SERVER:$PORT/$FILE"
|
||
done
|
||
else
|
||
JOBS=3
|
||
for i in $(seq 1 $JOBS); do
|
||
awk "NR % $JOBS == $i-1" "$1/PXEFILELIST" | tftp_mget "$SERVER" "$1/$LIVEKITNAME" &
|
||
done
|
||
wait
|
||
fi
|
||
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"
|
||
|
||
# supported syntax is even like from=/dev/sr0/livekitname. It is not so much
|
||
# optiomal to put the following block of code here inside the while loop,
|
||
# but there is no harm so lets modify DEVICE and FROM to make it work
|
||
if echo "$FROM" | grep -q '^/dev/'; then
|
||
DEVICE="$(echo "$FROM" | cut -d '/' -f 1-3)"
|
||
FROM="$(echo "$FROM" | cut -d '/' -f 4-)"
|
||
if [ "$FROM" = "" ]; then
|
||
FROM="$LIVEKITNAME"
|
||
fi
|
||
fi
|
||
|
||
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 "$FROM" > /var/log/from.log
|
||
echo "$1/$FROM" | tr -s "/" | sed -r "s:/[^/]+/[.][.]/:/:g"
|
||
return
|
||
fi
|
||
|
||
# search for bundles in modules directory
|
||
if [ "$(find "$1/$FROM/modules" -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"
|
||
echo "$FROM" > /var/log/from.log
|
||
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 TIMEOUT ASKPID
|
||
|
||
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 user wants to get asked, ask and periodically update list of devices
|
||
if [ "$FROM" = "ask" ]; then
|
||
(
|
||
while true; do
|
||
blkid -o full -s TYPE -s LABEL | grep -E -v "/loop|/ram|/zram|swap" | while read LINE; do
|
||
DISK="$(echo "$LINE" | cut -d : -f 1)"
|
||
SIZE="$(fdisk -l "$DISK" 2>/dev/null | grep $DISK | grep : | sed -r "s/.*: |,.*//g")"
|
||
echo -n "$DISK: $SIZE, "
|
||
echo "$LINE" | sed -r 's/[^"]*"([^"]*)"/\1, /g' | sed -r 's/, $//'
|
||
done >/tmp/0.txt
|
||
mv -f /tmp/0.txt /tmp/ask.txt
|
||
sleep 1
|
||
done
|
||
) &
|
||
ASKPID=$!
|
||
sleep 1 # give blkid some chance to finish
|
||
FROM="$(ncurses-menu -t "Look for /$LIVEKITNAME/ directory on:" -f /tmp/ask.txt -s 2>&1 >/dev/tty1 < /dev/tty1)"
|
||
FROM="$(echo "$FROM" | cut -d : -f 1)/$LIVEKITNAME"
|
||
kill $ASKPID
|
||
fi
|
||
|
||
# default to livekitname autodetected on all disks
|
||
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
|
||
}
|
||
|
||
|
||
# Get a human readable format of time elapsed since given datetime
|
||
# $1 = date time
|
||
#
|
||
date_diff_since_now()
|
||
{
|
||
local NOW TIMESTAMP SEC MINS HOURS DAYS
|
||
|
||
NOW=$(date '+%s')
|
||
TIMESTAMP=$(date --date "$1" '+%s')
|
||
SEC=$(($NOW - $TIMESTAMP))
|
||
MINS=$(($SEC / 60))
|
||
HOURS=$(($SEC / 3600))
|
||
DAYS=$(($SEC / 86400))
|
||
|
||
if [ "$DAYS" -gt 0 ]; then echo "$DAYS days" | sed -r "s/^1 days/1 day/";
|
||
elif [ "$HOURS" -gt 0 ]; then echo "$HOURS hours" | sed -r "s/^1 hours/1 hour/";
|
||
elif [ "$MINS" -gt 0 ]; then echo "$MINS minutes" | sed -r "s/^1 minutes/1 minute/";
|
||
else echo "$SEC seconds" | sed -r "s/^1 seconds/1 second/";
|
||
fi
|
||
}
|
||
|
||
|
||
# Restore persistent changes from previous session.
|
||
# Store persistent changes to a directory and keep the directory in a session file,
|
||
# so we know which session was active last time so we can resume it next time
|
||
# $1 = changes directory
|
||
#
|
||
restore_perch_session()
|
||
{
|
||
debug_log "restore_perch_session" "$*"
|
||
|
||
local TIMEOUT DEVICE FS OPTIONS MOUNT
|
||
local CHANDIR PERCHDIR FILE LAST LASTMOD LASTSESSION NEW DIR USAGE LAST DAYS
|
||
|
||
CHANDIR="$1"
|
||
PERCHDIR="$(cmdline_value perchdir)"
|
||
|
||
# Supported syntax is even like perchdir=/dev/sda1/changes
|
||
# In this case, perchdir is mounted over $CHANDIR and is set to ask
|
||
if echo "$PERCHDIR" | grep -q '^/dev/'; then
|
||
DEVICE="$(echo "$PERCHDIR" | cut -d '/' -f 1-3)"
|
||
PERCHDIR="$(echo "$PERCHDIR" | cut -d '/' -f 4-)"
|
||
|
||
FS="$(device_bestfs "$DEVICE")"
|
||
OPTIONS="$(fs_options $FS)"
|
||
MOUNT="$(mount_command $FS)"
|
||
|
||
echo -n "* Waiting for persistent changes on $DEVICE .." >&2
|
||
for TIMEOUT in $(seq 1 20); do
|
||
echo -n "." >&2
|
||
refresh_devs
|
||
$MOUNT "$DEVICE" "$CHANDIR" $OPTIONS 2>/dev/null
|
||
if [ $? -eq 0 ]; then
|
||
if [ "$PERCHDIR" != "" -a "$PERCHDIR" != "/" ]; then
|
||
mkdir -p "$CHANDIR/$PERCHDIR"
|
||
$MOUNT --bind "$CHANDIR/$PERCHDIR" "$CHANDIR"
|
||
if [ $? -eq 0 ]; then
|
||
echo -e "\n" >&2
|
||
PERCHDIR=ask
|
||
break
|
||
fi
|
||
else
|
||
echo -e "\n" >&2
|
||
PERCHDIR=ask
|
||
break
|
||
fi
|
||
fi
|
||
sleep 1
|
||
done
|
||
fi
|
||
|
||
LAST=last_session.txt
|
||
LASTSESSION=$(cat "$CHANDIR/$LAST" 2>/dev/null)
|
||
|
||
# ask for session, print msgs to stderr so it is visible
|
||
if [ "$LASTSESSION" != "" -a "$PERCHDIR" = "ask" ]; then
|
||
ls -1 $CHANDIR | grep -E "^[0-9]+" | while read DIR; do
|
||
USAGE=$(du -s -h "$CHANDIR/$DIR" 2>/dev/null | sed -r "s/[[:space:]].*//")
|
||
LASTMOD="$(date -r "$CHANDIR/$DIR" "+%Y-%m-%d %H:%M:%S")"
|
||
DAYS=$(date_diff_since_now "$LASTMOD")
|
||
echo "$LASTMOD Resume session #$DIR - last access $DAYS ago - using $USAGE" | sed -r "s/(.)\$/ \\1B/"
|
||
done | sort -r | cut -d " " -f 3- > /tmp/sessions.txt
|
||
|
||
PERCHDIR="$(ncurses-menu -t 'Select action:' -o 'Start a new session' -f /tmp/sessions.txt 2>&1 >/dev/tty1 < /dev/tty1)"
|
||
|
||
if echo "$PERCHDIR" | grep -q "new"; then
|
||
PERCHDIR="new"
|
||
else
|
||
PERCHDIR="$(echo "$PERCHDIR" | sed -r 's/.*#//' | sed -r 's/[^0-9].*//')"
|
||
fi
|
||
fi
|
||
|
||
# restore name for previous session if empty
|
||
if [ "$PERCHDIR" = "" -o "$PERCHDIR" = "resume" ]; then
|
||
PERCHDIR="$LASTSESSION"
|
||
fi
|
||
|
||
# if last is empty or new/nonexistent is requested, create new
|
||
if [ "$PERCHDIR" = "" -o "$PERCHDIR" = "new" -o ! -d "$CHANDIR/$PERCHDIR" ]; then
|
||
NEW="$(ls -1 $CHANDIR | egrep '^[0-9]' | sed -r 's/[^0-9].*//' | sort | tail -n 1)"
|
||
NEW=$(($NEW + 1))
|
||
mkdir -p "$CHANDIR/$NEW"
|
||
PERCHDIR="$NEW"
|
||
fi
|
||
|
||
# remember current, update timestamp
|
||
echo "$PERCHDIR" > "$CHANDIR/$LAST"
|
||
touch "$CHANDIR/$PERCHDIR"
|
||
echo "$PERCHDIR"
|
||
}
|
||
|
||
|
||
# Activate persistent changes
|
||
# $1 = data directory
|
||
# $2 = target changes directory
|
||
#
|
||
persistent_changes()
|
||
{
|
||
debug_log "persistent_changes" "$*"
|
||
|
||
local CHANGES T1 T2 EXISTS DEVICE PERCHDIR PERCHSIZE PERCHFILE GROWDIR
|
||
|
||
CHANGES="$1/$(basename "$2")"
|
||
DEVICE="$(df $1 | tail -n 1 | cut -d " " -f 1)"
|
||
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
|
||
# so memory will be used to save chanves temporarily
|
||
if grep -vq perch /proc/cmdline; then
|
||
return
|
||
fi
|
||
|
||
PERCHDIR=$(restore_perch_session "$CHANGES")
|
||
|
||
# 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
|
||
|
||
# try to use native fs if posix-compatible
|
||
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
|
||
if device_bestfs $DEVICE | grep -v ntfs >/dev/null; then
|
||
echo "* Activating native persistent changes for session #$PERCHDIR"
|
||
mount --bind "$CHANGES/$PERCHDIR" "$2"
|
||
return
|
||
fi
|
||
else
|
||
# if test failed at any point, we may have temp files left behind
|
||
rm "$T1" "$T2" 2>/dev/null
|
||
fi
|
||
|
||
|
||
# If we are here, native fs was not supported or user requested perchsize anyway
|
||
|
||
PERCHSIZE=$(cmdline_value perchsize | sed -r "s/TB?/000000/i" | sed -r "s/GB?/000/i" | sed -r "s/[^0-9]//g")
|
||
|
||
if [ "$PERCHSIZE" = "" ]; then
|
||
PERCHSIZE=16000
|
||
fi
|
||
|
||
# minimum size is 16GB
|
||
if [ "$PERCHSIZE" -lt 16000 ]; then
|
||
PERCHSIZE=16000
|
||
fi
|
||
|
||
PERCHFILE="$PERCHDIR/changes.dat"
|
||
|
||
# resume or use new
|
||
if [ -e "$CHANGES/$PERCHFILE" ]; then
|
||
echo "* Resuming persistent changes for session #$PERCHDIR"
|
||
EXISTS="true"
|
||
else
|
||
echo "* Creating new persistent changes for session #$PERCHDIR"
|
||
EXISTS=""
|
||
fi
|
||
|
||
# mount it, splitsize of 4000MB is hardcoded, it must be the same all the time anyway
|
||
echo "- mounting dynamically enlarged storage"
|
||
@mount.dynfilefs -f "$CHANGES/$PERCHFILE" -s $PERCHSIZE -m "$2" -p 4000
|
||
if [ ! "$EXISTS" ]; then
|
||
echo "- creating filesystem"
|
||
mkfs.xfs.custom "$2/virtual.dat"
|
||
fi
|
||
|
||
if [ "$PERCHSIZE" -gt 16000 ]; then
|
||
GROWDIR=/tmp/changes.grow
|
||
echo "- grow if needed"
|
||
mkdir -p "$GROWDIR"
|
||
mount -o loop "$2/virtual.dat" "$GROWDIR"
|
||
xfs_growfs "$GROWDIR" >/dev/null
|
||
umount "$GROWDIR"
|
||
rmdir "$GROWDIR"
|
||
fi
|
||
|
||
echo "- mounting persistent changes"
|
||
mount -o loop "$2/virtual.dat" "$2"
|
||
}
|
||
|
||
|
||
# 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
|
||
}
|
||
|
||
|
||
# Run user custom preinit script if it exists
|
||
# $1 = data directory
|
||
# $2 = union directory
|
||
user_preinit()
|
||
{
|
||
debug_log "user_preinit" "$*"
|
||
|
||
local SRC
|
||
|
||
SRC="$1/rootcopy/run/preinit.sh"
|
||
|
||
if [ "$(ls -1 "$SRC" 2>/dev/null)" != "" ]; then
|
||
echo_green_star
|
||
echo "Executing user custom preinit..."
|
||
debug_log "Executing user custom preinit [$SRC]"
|
||
. "$SRC" "$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)
|
||
FILTER=${FILTER//,/|}
|
||
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
|
||
# $1 = directory where to search for bundles
|
||
# $2 = directory where to mount bundles
|
||
mount_bundles()
|
||
{
|
||
echo_green_star
|
||
echo "Mounting bundles"
|
||
( 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"
|
||
done
|
||
}
|
||
|
||
|
||
# Add mounted bundles to aufs union
|
||
# $1 = directory where bundles are mounted
|
||
# $2 = directory where union is mounted
|
||
#
|
||
union_append_bundles()
|
||
{
|
||
debug_log "union_append_bundles" "$*"
|
||
|
||
if aufs_is_supported >/dev/null; then
|
||
echo_green_star
|
||
echo "Adding bundles to union"
|
||
find "$1" -mindepth 1 -maxdepth 1 | sortmod | while read BUNDLE; do
|
||
mount -o remount,add:1:"$BUNDLE=rr+wh" aufs "$2"
|
||
done
|
||
fi
|
||
}
|
||
|
||
|
||
# Create empty fstab properly
|
||
# $1 = root directory
|
||
# $2 = directory where boot disk is mounted
|
||
#
|
||
fstab_create()
|
||
{
|
||
debug_log "fstab_create" "$*"
|
||
|
||
local FSTAB DEVICE FS LABEL BOOTDEVICE OPTS
|
||
|
||
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
|
||
|
||
BOOTDEVICE=$(df "$2" | tail -n 1 | cut -d " " -f 1)
|
||
|
||
echo >> $FSTAB
|
||
|
||
blkid | grep -v "^/dev/loop" | grep -v "^/dev/zram" | cut -d: -f 1 | while read DEVICE; do
|
||
FS="$(device_bestfs $DEVICE)"
|
||
LABEL="$(basename $DEVICE)"
|
||
OPTS="defaults,noatime,nofail,x-systemd.device-timeout=10"
|
||
|
||
if [ "$FS" != "" -a "$FS" != "swap" -a "$FS" != "squashfs" -a "$DEVICE" != "$BOOTDEVICE" ]; then
|
||
mkdir -p "$1/media/$LABEL"
|
||
echo "$DEVICE" "/media/$LABEL" $FS $OPTS 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
|
||
}
|