OSDN Git Service

[update] : Changed out_dir -> bootfiles_dir
[alterlinux/LUBS.git] / lubs-chroot
1 #!/bin/bash
2
3 shopt -s extglob
4
5 # generated from util-linux source: libmount/src/utils.c
6 declare -A pseudofs_types=([anon_inodefs]=1
7                            [autofs]=1
8                            [bdev]=1
9                            [bpf]=1
10                            [binfmt_misc]=1
11                            [cgroup]=1
12                            [cgroup2]=1
13                            [configfs]=1
14                            [cpuset]=1
15                            [debugfs]=1
16                            [devfs]=1
17                            [devpts]=1
18                            [devtmpfs]=1
19                            [dlmfs]=1
20                            [efivarfs]=1
21                            [fuse.gvfs-fuse-daemon]=1
22                            [fusectl]=1
23                            [hugetlbfs]=1
24                            [mqueue]=1
25                            [nfsd]=1
26                            [none]=1
27                            [pipefs]=1
28                            [proc]=1
29                            [pstore]=1
30                            [ramfs]=1
31                            [rootfs]=1
32                            [rpc_pipefs]=1
33                            [securityfs]=1
34                            [sockfs]=1
35                            [spufs]=1
36                            [sysfs]=1
37                            [tmpfs]=1)
38
39 # generated from: pkgfile -vbr '/fsck\..+' | awk -F. '{ print $NF }' | sort
40 declare -A fsck_types=([cramfs]=1
41                        [exfat]=1
42                        [ext2]=1
43                        [ext3]=1
44                        [ext4]=1
45                        [ext4dev]=1
46                        [jfs]=1
47                        [minix]=1
48                        [msdos]=1
49                        [reiserfs]=1
50                        [vfat]=1
51                        [xfs]=1)
52
53 out() { printf "$1 $2\n" "${@:3}"; }
54 error() { out "==> ERROR:" "$@"; } >&2
55 warning() { out "==> WARNING:" "$@"; } >&2
56 msg() { out "==>" "$@"; }
57 msg2() { out "  ->" "$@";}
58 die() { error "$@"; exit 1; }
59
60 ignore_error() {
61   "$@" 2>/dev/null
62   return 0
63 }
64
65 in_array() {
66   local i
67   for i in "${@:2}"; do
68     [[ $1 = "$i" ]] && return 0
69   done
70   return 1
71 }
72
73 chroot_add_mount() {
74   mount "$@" && CHROOT_ACTIVE_MOUNTS=("$2" "${CHROOT_ACTIVE_MOUNTS[@]}")
75 }
76
77 chroot_maybe_add_mount() {
78   local cond=$1; shift
79   if eval "$cond"; then
80     chroot_add_mount "$@"
81   fi
82 }
83
84 chroot_setup() {
85   CHROOT_ACTIVE_MOUNTS=()
86   [[ $(trap -p EXIT) ]] && die '(BUG): attempting to overwrite existing EXIT trap'
87   trap 'chroot_teardown' EXIT
88
89   chroot_add_mount proc "$1/proc" -t proc -o nosuid,noexec,nodev &&
90   chroot_add_mount sys "$1/sys" -t sysfs -o nosuid,noexec,nodev,ro &&
91   ignore_error chroot_maybe_add_mount "[[ -d '$1/sys/firmware/efi/efivars' ]]" \
92       efivarfs "$1/sys/firmware/efi/efivars" -t efivarfs -o nosuid,noexec,nodev &&
93   chroot_add_mount udev "$1/dev" -t devtmpfs -o mode=0755,nosuid &&
94   chroot_add_mount devpts "$1/dev/pts" -t devpts -o mode=0620,gid=5,nosuid,noexec &&
95   chroot_add_mount shm "$1/dev/shm" -t tmpfs -o mode=1777,nosuid,nodev &&
96   chroot_add_mount /run "$1/run" --bind &&
97   chroot_add_mount tmp "$1/tmp" -t tmpfs -o mode=1777,strictatime,nodev,nosuid
98 }
99
100 chroot_teardown() {
101   if (( ${#CHROOT_ACTIVE_MOUNTS[@]} )); then
102     sleep 1
103     umount "${CHROOT_ACTIVE_MOUNTS[@]}"
104   fi
105   unset CHROOT_ACTIVE_MOUNTS
106 }
107
108 try_cast() (
109   _=$(( $1#$2 ))
110 ) 2>/dev/null
111
112 valid_number_of_base() {
113   local base=$1 len=${#2} i=
114
115   for (( i = 0; i < len; i++ )); do
116     try_cast "$base" "${2:i:1}" || return 1
117   done
118
119   return 0
120 }
121
122 mangle() {
123   local i= chr= out=
124   local {a..f}= {A..F}=
125
126   for (( i = 0; i < ${#1}; i++ )); do
127     chr=${1:i:1}
128     case $chr in
129       [[:space:]\\])
130         printf -v chr '%03o' "'$chr"
131         out+=\\
132         ;;
133     esac
134     out+=$chr
135   done
136
137   printf '%s' "$out"
138 }
139
140 unmangle() {
141   local i= chr= out= len=$(( ${#1} - 4 ))
142   local {a..f}= {A..F}=
143
144   for (( i = 0; i < len; i++ )); do
145     chr=${1:i:1}
146     case $chr in
147       \\)
148         if valid_number_of_base 8 "${1:i+1:3}" ||
149             valid_number_of_base 16 "${1:i+1:3}"; then
150           printf -v chr '%b' "${1:i:4}"
151           (( i += 3 ))
152         fi
153         ;;
154     esac
155     out+=$chr
156   done
157
158   printf '%s' "$out${1:i}"
159 }
160
161 optstring_match_option() {
162   local candidate pat patterns
163
164   IFS=, read -ra patterns <<<"$1"
165   for pat in "${patterns[@]}"; do
166     if [[ $pat = *=* ]]; then
167       # "key=val" will only ever match "key=val"
168       candidate=$2
169     else
170       # "key" will match "key", but also "key=anyval"
171       candidate=${2%%=*}
172     fi
173
174     [[ $pat = "$candidate" ]] && return 0
175   done
176
177   return 1
178 }
179
180 optstring_remove_option() {
181   local o options_ remove=$2 IFS=,
182
183   read -ra options_ <<<"${!1}"
184
185   for o in "${!options_[@]}"; do
186     optstring_match_option "$remove" "${options_[o]}" && unset 'options_[o]'
187   done
188
189   declare -g "$1=${options_[*]}"
190 }
191
192 optstring_normalize() {
193   local o options_ norm IFS=,
194
195   read -ra options_ <<<"${!1}"
196
197   # remove empty fields
198   for o in "${options_[@]}"; do
199     [[ $o ]] && norm+=("$o")
200   done
201
202   # avoid empty strings, reset to "defaults"
203   declare -g "$1=${norm[*]:-defaults}"
204 }
205
206 optstring_append_option() {
207   if ! optstring_has_option "$1" "$2"; then
208     declare -g "$1=${!1},$2"
209   fi
210
211   optstring_normalize "$1"
212 }
213
214 optstring_prepend_option() {
215   local options_=$1
216
217   if ! optstring_has_option "$1" "$2"; then
218     declare -g "$1=$2,${!1}"
219   fi
220
221   optstring_normalize "$1"
222 }
223
224 optstring_get_option() {
225   local opts o
226
227   IFS=, read -ra opts <<<"${!1}"
228   for o in "${opts[@]}"; do
229     if optstring_match_option "$2" "$o"; then
230       declare -g "$o"
231       return 0
232     fi
233   done
234
235   return 1
236 }
237
238 optstring_has_option() {
239   local "${2%%=*}"
240
241   optstring_get_option "$1" "$2"
242 }
243
244 dm_name_for_devnode() {
245   read dm_name <"/sys/class/block/${1#/dev/}/dm/name"
246   if [[ $dm_name ]]; then
247     printf '/dev/mapper/%s' "$dm_name"
248   else
249     # don't leave the caller hanging, just print the original name
250     # along with the failure.
251     print '%s' "$1"
252     error 'Failed to resolve device mapper name for: %s' "$1"
253   fi
254 }
255
256 fstype_is_pseudofs() {
257   (( pseudofs_types["$1"] ))
258 }
259
260 fstype_has_fsck() {
261   (( fsck_types["$1"] ))
262 }
263
264
265 usage() {
266   cat <<EOF
267 usage: ${0##*/} chroot-dir [command]
268
269     -h                  Print this help message
270     -u <user>[:group]   Specify non-root user and optional group to use
271
272 If 'command' is unspecified, ${0##*/} will launch /bin/bash.
273
274 Note that when using arch-chroot, the target chroot directory *should* be a
275 mountpoint. This ensures that tools such as pacman(8) or findmnt(8) have an
276 accurate hierarchy of the mounted filesystems within the chroot.
277
278 If your chroot target is not a mountpoint, you can bind mount the directory on
279 itself to make it a mountpoint, i.e. 'mount --bind /your/chroot /your/chroot'.
280
281 EOF
282 }
283
284 chroot_add_resolv_conf() {
285   local chrootdir=$1 resolv_conf=$1/etc/resolv.conf
286
287   [[ -e /etc/resolv.conf ]] || return 0
288
289   # Handle resolv.conf as a symlink to somewhere else.
290   if [[ -L $chrootdir/etc/resolv.conf ]]; then
291     # readlink(1) should always give us *something* since we know at this point
292     # it's a symlink. For simplicity, ignore the case of nested symlinks.
293     resolv_conf=$(readlink "$chrootdir/etc/resolv.conf")
294     if [[ $resolv_conf = /* ]]; then
295       resolv_conf=$chrootdir$resolv_conf
296     else
297       resolv_conf=$chrootdir/etc/$resolv_conf
298     fi
299
300     # ensure file exists to bind mount over
301     if [[ ! -f $resolv_conf ]]; then
302       install -Dm644 /dev/null "$resolv_conf" || return 1
303     fi
304   elif [[ ! -e $chrootdir/etc/resolv.conf ]]; then
305     # The chroot might not have a resolv.conf.
306     return 0
307   fi
308
309   chroot_add_mount /etc/resolv.conf "$resolv_conf" --bind
310 }
311
312 while getopts ':hu:' flag; do
313   case $flag in
314     h)
315       usage
316       exit 0
317       ;;
318     u)
319       userspec=$OPTARG
320       ;;
321     :)
322       die '%s: option requires an argument -- '\''%s'\' "${0##*/}" "$OPTARG"
323       ;;
324     ?)
325       die '%s: invalid option -- '\''%s'\' "${0##*/}" "$OPTARG"
326       ;;
327   esac
328 done
329 shift $(( OPTIND - 1 ))
330
331 (( EUID == 0 )) || die 'This script must be run with root privileges'
332 (( $# )) || die 'No chroot directory specified'
333 chrootdir=$1
334 shift
335
336 [[ -d $chrootdir ]] || die "Can't create chroot on non-directory %s" "$chrootdir"
337
338 #if ! mountpoint -q "$chrootdir"; then
339 #  warning "$chrootdir is not a mountpoint. This may have undesirable side effects."
340 #fi
341
342 chroot_setup "$chrootdir" || die "failed to setup chroot %s" "$chrootdir"
343 chroot_add_resolv_conf "$chrootdir" || die "failed to setup resolv.conf"
344
345 chroot_args=()
346 [[ $userspec ]] && chroot_args+=(--userspec "$userspec")
347
348 SHELL=/bin/bash unshare --fork --pid chroot "${chroot_args[@]}" -- "$chrootdir" "$@"