summaryrefslogtreecommitdiff
path: root/switch_root
blob: d142e97d443c8296f1092b3a0a9ebee27255e5c3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#!/bin/sh
# Copyright (c) Victor Lowther <victor.lowther@gmail.com>
# Licensed under the terms of the GNU GPL v2 or later.

# some utility functions first
# this is so we can scroll back.

die() { echo "${1}, dying horribly."; while :;do read line; done }

# jsut enough to get the job done
simple_find() {
    # $1 = file to look for
    # $rest = places to look for it
    local file=$1
    shift
    for loc in "$@"; do
	[ -f "$NEWROOT$loc/$file" ] && { echo "$loc/$file"; return 0; }
    done
    return 1
}

# We really should not be doing this from here, but...
find_interp() {
    local ldso=$("$NEWROOT$CHROOT" "$NEWROOT" "$LDD" "$1" |
	while read interp rest; do
	    [ -f "${NEWROOT}$interp" ] || continue
	    echo "$interp"
	    break
	done);
    [ "$ldso" ] && echo $ldso
}

# this makes it easier to run a command entirely from newroot
# $1 = elf interpreter (must pass empty string if none)
# $2 = command or "exec"
# $3 = command if exec was passed
run_from_newroot() {
    local ldso="$1" cmd="$2"; shift; shift
    if [ "$cmd" = "exec" ]; then
	cmd="$1"; shift
	if [ "$ldso" ]; then
	    exec "$NEWROOT$ldso" --library-path "$LIBPATH" "$NEWROOT$cmd" "$@"
	else
	    exec "$NEWROOT$cmd" "$@"
	fi
    else
	if [ "$ldso" ]; then
	    "$NEWROOT$ldso" --library-path "$LIBPATH" "$NEWROOT$cmd" "$@"
	else
	    "$NEWROOT$cmd" "$@"
	fi
    fi
}
# update the path to find our dynamic libraries on newroot
update_newroot_libpath() {
    local x
    LIBPATH=":"
    LIBDIRS="$(echo $NEWROOT/lib* $NEWROOT/usr/lib*)"
    for x in $LIBDIRS; do
	[ -d "$x" ] && LIBPATH="${LIBPATH}${x}:"
    done
    LIBPATH="${LIBPATH%:}"; LIBPATH="${LIBPATH#:}"
    [ "$LIBPATH" ] || die "Cannot find shared library diectories on $NEWROOT"
}
NEWROOT="$1"
INIT="$2"
[ -d "$NEWROOT" ] || die "$NEWROOT is not a directory"
[ -x "$NEWROOT$INIT" ] || die "$NEWROOT/$INIT is not executable."
shift; shift

update_newroot_libpath

# start looking for required binaries and bits of infrastructure
BINDIRS="/bin /sbin /usr/bin /usr/sbin"
RM=$(simple_find rm $BINDIRS) || die "Cannnot find rm on $NEWROOT"
CHROOT=$(simple_find chroot $BINDIRS) || die "Cannot find chroot on $NEWROOT"
LDD=$(simple_find ldd $BINDIRS) || die "Cannot find ldd on $NEWROOT"
MOUNT=$(simple_find mount $BINDIRS) || die "Cannot find mount on $NEWROOT"

# now, start the real process of switching the root
cd /

# kill udevd, move all our mounts over to the new root
kill $(pidof udevd)
mount --move /proc $NEWROOT/proc
mount --move /sys $NEWROOT/sys
mount --move /dev $NEWROOT/dev

# Find the binary interpreter for our three required binaries.
# We do it here so that ldd does not complain about a missing /dev/null.
CHROOT_LDSO=$(find_interp "$CHROOT")
RM_LDSO=$(find_interp "$RM")
MOUNT_LDSO=$(find_interp "$MOUNT")

# redirect to new console. Our old initramfs will not be freed otherwise
CONSOLE=$NEWROOT/dev/console
[ -c $CONSOLE ] && exec >$CONSOLE 2>&1 <$CONSOLE
for x in *; do
    [ "/$x" = "$NEWROOT" ] || run_from_newroot "$RM_LDSO" "$RM" -rf -- "$x"
done
# switch to our new root dir
cd "$NEWROOT"
# this moves rootfs to the actual root...
run_from_newroot "$MOUNT_LDSO" "$MOUNT" -n --move . /
# but does not update where / is in directory lookups.
# Therefore, newroot is now ".".  Update things accordingly, then chroot and
# exec init.
NEWROOT="."
update_newroot_libpath
run_from_newroot "$CHROOT_LDSO" exec "$CHROOT" "$NEWROOT" "$INIT" "$@" || \
    die "The chroot did not take for some reason"