Compare commits

...

13 Commits

Author SHA1 Message Date
Mike Yuan
0e14a1a13b
genfstab,common: use named reference instead of globals 2023-03-03 19:27:04 +08:00
Mike Yuan
f40fc1f2b0
doc: several cleanups (follow previous changes) 2023-03-03 19:27:04 +08:00
Mike Yuan
bc1b449f1f
doc: remove footer
The information it provides is pretty irrelevant
these days. People will use GitHub/GitLab for bug
reports :)
2023-03-03 19:27:04 +08:00
Mike Yuan
7881d7ebce
genfstab: drop ancient vfat quirk
Kernel before 3.18 is unsupported by Arch.
2023-03-03 19:27:04 +08:00
Mike Yuan
9aa95365c2
arch-chroot: several cleanups 2023-03-03 19:27:04 +08:00
Mike Yuan
2b9027a0ab
genfstab: fix bind mount handling
We could have multiple mountpoints for one src.
Loop until we find the real path for the bind mount target.
2023-03-03 19:27:04 +08:00
Mike Yuan
05d231aff9
pacstrap: respect custom pacman.conf configs
If some core settings are changed in custom pacman.conf,
they won't be prepended with new rootdir automatically.
2023-03-03 19:27:04 +08:00
Mike Yuan
c21e56e88e
common: add check_root 2023-03-03 19:27:04 +08:00
Mike Yuan
e13ce65908
common: check is_pseudofs or has_fsck more reliably
There might be filesystems we can't cover in common,
e.g. out-of-tree ones. Use findmnt / check if the
fsck command exists to make it more reliable.
2023-03-03 19:27:03 +08:00
Mike Yuan
092226862b
genfstab: re-implement write_source 2023-03-03 19:27:03 +08:00
Mike Yuan
4be53d3488
common: several cleanup for *_setup
Use errexit instead of having to add '&&'
for every single mount.
2023-03-03 19:27:03 +08:00
Mike Yuan
e3f9cb6498
arch-chroot: move resolve_link and chroot_add_resolv_link to common
chroot_add_resolve_link is cleaned up to be simpler.
(We shouldn't need the complex symlink handling logic for target)
2023-03-03 19:27:03 +08:00
Mike Yuan
33cf482649
tree-wide: fix shellcheck warnings and style consistency 2023-03-03 19:27:03 +08:00
10 changed files with 430 additions and 357 deletions

View File

@ -26,7 +26,7 @@ edit = $(V_GEN) m4 -P $@.in >$@ && chmod go-w,+x $@
%: %.in common
$(edit)
doc/%: doc/%.asciidoc doc/asciidoc.conf doc/footer.asciidoc
doc/%: doc/%.asciidoc doc/asciidoc.conf
$(V_GEN) a2x --no-xmllint --asciidoc-opts="-f doc/asciidoc.conf" -d manpage -f manpage -D doc $<
clean:

View File

@ -2,126 +2,86 @@
shopt -s extglob
# m4_include() is recognized as a function definition
# shellcheck source=common disable=SC1073,SC1065,SC1064,SC1072
m4_include(common)
setup=chroot_setup
unshare=0
userspec=''
chroot_args=()
usage() {
cat <<EOF
usage: ${0##*/} chroot-dir [command] [arguments...]
usage: ${0##*/} [options] chroot_dir [command [arguments...]]
Options:
-N Run in unshare mode as a regular user
-u <user>[:group] Specify non-root user and group (optional) to use
-h Print this help message
-N Run in unshare mode as a regular user
-u <user>[:group] Specify non-root user and optional group to use
If 'command' is unspecified, ${0##*/} will launch /bin/bash.
If 'command' is unspecified, arch-chroot will launch /bin/bash.
Note that when using arch-chroot, the target chroot directory *should* be a
mountpoint. This ensures that tools such as pacman(8) or findmnt(8) have an
accurate hierarchy of the mounted filesystems within the chroot.
If your chroot target is not a mountpoint, you can bind mount the directory on
itself to make it a mountpoint, i.e. 'mount --bind /your/chroot /your/chroot'.
itself to make it one, i.e. 'mount --bind chroot_dir chroot_dir'.
EOF
}
resolve_link() {
local target=$1
local root=$2
if [[ -z $1 || $1 = @(-h|--help) ]]; then
usage
exit $(( $# ? 0 : 1 ))
fi
# If a root was given, make sure it ends in a slash.
[[ -n $root && $root != */ ]] && root=$root/
while [[ -L $target ]]; do
target=$(readlink -m "$target")
# If a root was given, make sure the target is under it.
# Make sure to strip any leading slash from target first.
[[ -n $root && $target != $root* ]] && target=$root${target#/}
done
printf %s "$target"
}
chroot_add_resolv_conf() {
local chrootdir=$1
local src=$(resolve_link /etc/resolv.conf)
local dest=$(resolve_link "$chrootdir/etc/resolv.conf" "$chrootdir")
# If we don't have a source resolv.conf file, there's nothing useful we can do.
[[ -e $src ]] || return 0
if [[ ! -e $dest ]]; then
# There are two reasons the destination might not exist:
#
# 1. There may be no resolv.conf in the chroot. In this case, $dest won't exist,
# and it will be equal to $1/etc/resolv.conf. In this case, we'll just exit.
# The chroot environment must not be concerned with DNS resolution.
#
# 2. $1/etc/resolv.conf is (or resolves to) a broken link. The environment
# clearly intends to handle DNS resolution, but something's wrong. Maybe it
# normally creates the target at boot time. We'll (try to) take care of it by
# creating a dummy file at the target, so that we have something to bind to.
# Case 1.
[[ $dest = $chrootdir/etc/resolv.conf ]] && return 0
# Case 2.
install -Dm644 /dev/null "$dest" || return 1
fi
chroot_add_mount "$src" "$dest" --bind
}
while getopts ':hNu:' flag; do
while getopts ':Nu:' flag; do
case $flag in
h)
usage
exit 0
;;
N)
setup=unshare_setup
unshare=1
;;
u)
userspec=$OPTARG
userspec="$OPTARG"
;;
:)
die '%s: option requires an argument -- '\''%s'\' "${0##*/}" "$OPTARG"
die "%s: option requires an argument -- '%s'" "${0##*/}" "$OPTARG"
;;
?)
die '%s: invalid option -- '\''%s'\' "${0##*/}" "$OPTARG"
die "%s: invalid option -- '%s'" "${0##*/}" "$OPTARG"
;;
esac
done
shift $(( OPTIND - 1 ))
(( $# )) || die 'No chroot directory specified'
chrootdir=$1
shift
chrootdir="$1"; shift
if ! mountpoint -q "$chrootdir"; then
warning '%s is not a mountpoint, and thus may cause undesirable side effects. (See help for more info)' "$chrootdir"
fi
command=("$@")
arch-chroot() {
(( EUID == 0 )) || die 'This script must be run with root privileges'
check_root
[[ -d $chrootdir ]] || die "Can't create chroot on non-directory %s" "$chrootdir"
[[ -d "$chrootdir" ]] || die '%s: not a directory' "$chrootdir"
$setup "$chrootdir" || die "failed to setup chroot %s" "$chrootdir"
chroot_add_resolv_conf "$chrootdir" || die "failed to setup resolv.conf"
$setup "$chrootdir"
chroot_add_resolv_conf "$chrootdir" || die 'Failed to setup resolv.conf in chroot'
if ! mountpoint -q "$chrootdir"; then
warning "$chrootdir is not a mountpoint. This may have undesirable side effects."
fi
[[ $userspec ]] && chroot_args+=(--userspec="$userspec")
chroot_args=()
[[ $userspec ]] && chroot_args+=(--userspec "$userspec")
SHELL=/bin/bash $pid_unshare chroot "${chroot_args[@]}" -- "$chrootdir" "${args[@]}"
SHELL=/bin/bash $pid_unshare chroot "${chroot_args[@]}" -- "$chrootdir" "${command[@]}"
}
args=("$@")
if (( unshare )); then
$mount_unshare bash -c "$(declare_all); arch-chroot"
else
arch-chroot
fi
# vim: et ts=2 sw=2 ft=sh:

364
common
View File

@ -1,3 +1,6 @@
#!/hint/bash
# shellcheck disable=SC2155,SC2064
# generated from util-linux source: libmount/src/utils.c
declare -A pseudofs_types=([anon_inodefs]=1
[apparmorfs]=1
@ -72,112 +75,210 @@ declare -A fsck_types=([btrfs]=0 # btrfs doesn't need a regular fsck utility
[vfat]=1
[xfs]=1)
# shellcheck disable=SC2059
out() { printf "$1 $2\n" "${@:3}"; }
error() { out "==> ERROR:" "$@"; } >&2
warning() { out "==> WARNING:" "$@"; } >&2
msg() { out "==>" "$@"; }
msg2() { out " ->" "$@";}
msg2() { out " ->" "$@"; }
warning() { out "==> WARNING:" "$@"; } >&2
error() { out "==> ERROR:" "$@"; } >&2
die() { error "$@"; exit 1; }
ignore_error() {
"$@" 2>/dev/null
return 0
"$@" 2>/dev/null || return 0
}
in_array() {
local i
for i in "${@:2}"; do
[[ $1 = "$i" ]] && return 0
local -n _arr="$1"
for i in "${_arr[@]}"; do
[[ "$i" = "$1" ]] && return 0
done
return 1
}
rev_array() {
local -n _arr="$1"
mapfile -t _arr < <(printf '%s\n' "${_arr[@]}" | tac)
}
check_root() {
(( EUID == 0 )) || die 'This script must be run with root privileges'
}
resolve_link() {
local link="$1" root="$2" target
if [[ ! $root ]]; then
target="$(realpath -eq "$link")"
else
# This is tricky to do. We read from $link in a loop
# and prepend it with $root if it's not under it.
# This can't handle e.g. $root is /mnt and $link
# is /mnt/1 pointing to /mnt/2, which should actually
# be /mnt/mnt/2. Luckily these edge cases shouldn't
# bother most of the time.
root="$(realpath -e "$root")"
target="$link"
while [[ -L "$target" ]]; do
target="$(readlink -m "$target")"
if [[ "$target" != "$root"/* ]]; then
target="$root/${target#/}"
fi
done
# Normalize the path and make sure the resolved target exists
target="$(realpath -eq "$target")"
fi
printf '%s' "$target"
}
chroot_add_mount() {
mount "$@" && CHROOT_ACTIVE_MOUNTS=("$2" "${CHROOT_ACTIVE_MOUNTS[@]}")
mount "$@" && CHROOT_ACTIVE_MOUNTS+=("$2")
}
chroot_maybe_add_mount() {
local cond=$1; shift
local cond="$1"; shift
if eval "$cond"; then
chroot_add_mount "$@"
fi
}
chroot_setup() {
CHROOT_ACTIVE_MOUNTS=()
[[ $(trap -p EXIT) ]] && die '(BUG): attempting to overwrite existing EXIT trap'
trap 'chroot_teardown' EXIT
chroot_add_mount proc "$1/proc" -t proc -o nosuid,noexec,nodev &&
chroot_add_mount sys "$1/sys" -t sysfs -o nosuid,noexec,nodev,ro &&
ignore_error chroot_maybe_add_mount "[[ -d '$1/sys/firmware/efi/efivars' ]]" \
efivarfs "$1/sys/firmware/efi/efivars" -t efivarfs -o nosuid,noexec,nodev &&
chroot_add_mount udev "$1/dev" -t devtmpfs -o mode=0755,nosuid &&
chroot_add_mount devpts "$1/dev/pts" -t devpts -o mode=0620,gid=5,nosuid,noexec &&
chroot_add_mount shm "$1/dev/shm" -t tmpfs -o mode=1777,nosuid,nodev &&
chroot_add_mount run "$1/run" -t tmpfs -o nosuid,nodev,mode=0755 &&
chroot_add_mount tmp "$1/tmp" -t tmpfs -o mode=1777,strictatime,nodev,nosuid
}
chroot_teardown() {
if (( ${#CHROOT_ACTIVE_MOUNTS[@]} )); then
umount "${CHROOT_ACTIVE_MOUNTS[@]}"
fi
unset CHROOT_ACTIVE_MOUNTS
}
chroot_add_mount_lazy() {
mount "$@" && CHROOT_ACTIVE_LAZY=("$2" "${CHROOT_ACTIVE_LAZY[@]}")
mount "$@" && CHROOT_ACTIVE_LAZY+=("$2")
}
chroot_bind_device() {
touch "$2" && CHROOT_ACTIVE_FILES=("$2" "${CHROOT_ACTIVE_FILES[@]}")
chroot_add_mount $1 "$2" --bind
touch "$2" && CHROOT_ACTIVE_FILES+=("$2")
chroot_add_mount "$1" "$2" --bind
}
chroot_add_link() {
ln -sf "$1" "$2" && CHROOT_ACTIVE_FILES=("$2" "${CHROOT_ACTIVE_FILES[@]}")
ln -sf "$1" "$2" && CHROOT_ACTIVE_FILES+=("$2")
}
chroot_add_resolv_conf() {
local src="$(resolve_link /etc/resolv.conf)" dest="$1/etc/resolv.conf"
# If we don't have a source resolv.conf file, there's nothing useful we can do.
if [[ ! -f "$src" ]]; then
warning 'Cannot find a usable resolv.conf. DNS requests might fail in chroot'
return 0
fi
# If resolv.conf in the chroot is a symlink, we make a backup of it
# and create a plain file so we can bind mount to it.
# The backup is restore during chroot_teardown().
if [[ -L "$dest" ]]; then
mv "$dest" "$dest.orig"
fi
touch -a "$dest"
chroot_add_mount "$src" "$dest" --bind
}
chroot_setup() {
CHROOT_ACTIVE_MOUNTS=()
local root="$1"
local errtrap_old="$(trap -p ERR)" shellopts_old="$SHELLOPTS"
[[ $(trap -p EXIT) ]] && die 'An EXIT trap already exists (likely a bug)'
trap "chroot_teardown $root" EXIT
trap "$errtrap_old; die 'Failed to setup chroot %s' '$root'" ERR
set -e
chroot_add_mount proc "$root"/proc -t proc -o nosuid,noexec,nodev
chroot_add_mount sys "$root"/sys -t sysfs -o nosuid,noexec,nodev,ro
ignore_error chroot_maybe_add_mount "[[ -d '$root/sys/firmware/efi/efivars' ]]" \
efivarfs "$root"/sys/firmware/efi/efivars -t efivarfs -o nosuid,noexec,nodev
chroot_add_mount udev "$root"/dev -t devtmpfs -o mode=0755,nosuid
chroot_add_mount devpts "$root"/dev/pts -t devpts -o mode=0620,gid=5,nosuid,noexec
chroot_add_mount shm "$root"/dev/shm -t tmpfs -o mode=1777,nosuid,nodev
chroot_add_mount run "$root"/run -t tmpfs -o nosuid,nodev,mode=0755
chroot_add_mount tmp "$root"/tmp -t tmpfs -o mode=1777,strictatime,nodev,nosuid
[[ "$shellopts_old" = *'errexit'* ]] || set +e
if [[ $errtrap_old ]]; then
trap "$errtrap_old" ERR
else
trap - ERR
fi
}
chroot_teardown() {
local root="$1"
if (( ${#CHROOT_ACTIVE_MOUNTS[@]} )); then
rev_array CHROOT_ACTIVE_MOUNTS
umount "${CHROOT_ACTIVE_MOUNTS[@]}"
fi
unset CHROOT_ACTIVE_MOUNTS
if [[ -L "$root"/etc/resolv.conf.orig ]]; then
mv "$root"/etc/resolv.conf.orig "$root"/etc/resolv.conf
fi
}
unshare_setup() {
CHROOT_ACTIVE_MOUNTS=()
CHROOT_ACTIVE_LAZY=()
CHROOT_ACTIVE_FILES=()
[[ $(trap -p EXIT) ]] && die '(BUG): attempting to overwrite existing EXIT trap'
trap 'unshare_teardown' EXIT
CHROOT_ACTIVE_MOUNTS=() CHROOT_ACTIVE_LAZY=() CHROOT_ACTIVE_FILES=()
local root="$1"
local errtrap_old="$(trap -p ERR)" shellopts_old="$SHELLOPTS"
chroot_add_mount_lazy "$1" "$1" --bind &&
chroot_add_mount proc "$1/proc" -t proc -o nosuid,noexec,nodev &&
chroot_add_mount_lazy /sys "$1/sys" --rbind &&
chroot_add_link "$1/proc/self/fd" "$1/dev/fd" &&
chroot_add_link "$1/proc/self/fd/0" "$1/dev/stdin" &&
chroot_add_link "$1/proc/self/fd/1" "$1/dev/stdout" &&
chroot_add_link "$1/proc/self/fd/2" "$1/dev/stderr" &&
chroot_bind_device /dev/full "$1/dev/full" &&
chroot_bind_device /dev/null "$1/dev/null" &&
chroot_bind_device /dev/random "$1/dev/random" &&
chroot_bind_device /dev/tty "$1/dev/tty" &&
chroot_bind_device /dev/urandom "$1/dev/urandom" &&
chroot_bind_device /dev/zero "$1/dev/zero" &&
chroot_add_mount run "$1/run" -t tmpfs -o nosuid,nodev,mode=0755 &&
chroot_add_mount tmp "$1/tmp" -t tmpfs -o mode=1777,strictatime,nodev,nosuid
[[ $(trap -p EXIT) ]] && die 'An EXIT trap already exists (likely a bug)'
trap "unshare_teardown $root" EXIT
trap "$errtrap_old; die 'Failed to setup chroot %s' '$root'" ERR
set -e
chroot_add_mount_lazy "$root" "$root" --bind
chroot_add_mount proc "$root"/proc -t proc -o nosuid,noexec,nodev
chroot_add_mount_lazy /sys "$root"/sys --rbind
chroot_add_link "$root"/proc/self/fd "$root"/dev/fd
chroot_add_link "$root"/proc/self/fd/0 "$root"/dev/stdin
chroot_add_link "$root"/proc/self/fd/1 "$root"/dev/stdout
chroot_add_link "$root"/proc/self/fd/2 "$root"/dev/stderr
chroot_bind_device /dev/full "$root"/dev/full
chroot_bind_device /dev/null "$root"/dev/null
chroot_bind_device /dev/zero "$root"/dev/zero
chroot_bind_device /dev/random "$root"/dev/random
chroot_bind_device /dev/urandom "$root"/dev/urandom
chroot_bind_device /dev/tty "$root"/dev/tty
chroot_add_mount run "$root"/run -t tmpfs -o nosuid,nodev,mode=0755
chroot_add_mount tmp "$root"/tmp -t tmpfs -o mode=1777,strictatime,nodev,nosuid
[[ "$shellopts_old" = *'errexit'* ]] || set +e
if [[ $errtrap_old ]]; then
trap "$errtrap_old" ERR
else
trap - ERR
fi
}
unshare_teardown() {
chroot_teardown
chroot_teardown "$1"
if (( ${#CHROOT_ACTIVE_LAZY[@]} )); then
rev_array CHROOT_ACTIVE_LAZY
umount --lazy "${CHROOT_ACTIVE_LAZY[@]}"
fi
unset CHROOT_ACTIVE_LAZY
if (( ${#CHROOT_ACTIVE_FILES[@]} )); then
rev_array CHROOT_ACTIVE_FILES
rm "${CHROOT_ACTIVE_FILES[@]}"
fi
unset CHROOT_ACTIVE_FILES
}
pid_unshare="unshare --fork --pid"
# shellcheck disable=SC2034
mount_unshare="$pid_unshare --mount --map-auto --map-root-user --setuid 0 --setgid 0"
# This outputs code for declaring all variables to stdout. For example, if
@ -198,11 +299,11 @@ declare_all() {
}
try_cast() (
_=$(( $1#$2 ))
_=$(( ${1#"$2"} ))
) 2>/dev/null
valid_number_of_base() {
local base=$1 len=${#2} i=
local base="$1" len="${#2}" i
for (( i = 0; i < len; i++ )); do
try_cast "$base" "${2:i:1}" || return 1
@ -212,29 +313,29 @@ valid_number_of_base() {
}
mangle() {
local i= chr= out=
local {a..f}= {A..F}=
local i chr out
local {a..f}='' {A..F}=''
for (( i = 0; i < ${#1}; i++ )); do
chr=${1:i:1}
chr="${1:i:1}"
case $chr in
[[:space:]\\])
printf -v chr '%03o' "'$chr"
out+=\\
;;
esac
out+=$chr
out+="$chr"
done
printf '%s' "$out"
}
unmangle() {
local i= chr= out= len=$(( ${#1} - 4 ))
local {a..f}= {A..F}=
local i chr out len="$(( ${#1} - 4 ))"
local {a..f}='' {A..F}=''
for (( i = 0; i < len; i++ )); do
chr=${1:i:1}
chr="${1:i:1}"
case $chr in
\\)
if valid_number_of_base 8 "${1:i+1:3}" ||
@ -244,97 +345,109 @@ unmangle() {
fi
;;
esac
out+=$chr
out+="$chr"
done
printf '%s' "$out${1:i}"
}
optstring_match_option() {
local candidate pat patterns
optstring_match_one_option() {
local options=() target="$2"
local -n _optstring_match="$1"
IFS=, read -ra patterns <<<"$1"
for pat in "${patterns[@]}"; do
if [[ $pat = *=* ]]; then
# "key=val" will only ever match "key=val"
candidate=$2
else
# "key" will match "key", but also "key=anyval"
candidate=${2%%=*}
fi
IFS=, read -ra options <<<"$_optstring_match"
if [[ "$target" != *'='* ]]; then
options=("${options[@]%%=*}")
fi
[[ $pat = "$candidate" ]] && return 0
done
return 1
in_array options "$target"
}
optstring_remove_option() {
local o options_ remove=$2 IFS=,
optstring_get_options() {
local i options=()
local -n _ret="$1" _optstring_get="$2"
local -i got=0
read -ra options_ <<<"${!1}"
IFS=, read -ra options <<<"$_optstring_get"
for o in "${!options_[@]}"; do
optstring_match_option "$remove" "${options_[o]}" && unset 'options_[o]'
shift 2
for i in "${!options[@]}"; do
if optstring_match_one_option 'options[i]' "$1"; then
_ret+=(["${options[i]%%=*}"]="${options[i]//*=}")
got=1
fi
shift || break
done
declare -g "$1=${options_[*]}"
(( got ))
}
optstring_from_array() {
local optstring
local -n _optarray="$1"
optstring="$(printf ',%s' "${_optarray[@]}")"
optstring="${optstring:1}" # Remove the leading comma
printf '%s' "$optstring"
}
optstring_normalize() {
local o options_ norm IFS=,
local i options=()
local -n _optstring_norm="$1"
read -ra options_ <<<"${!1}"
IFS=, read -ra options <<<"$_optstring_norm"
# remove empty fields
for o in "${options_[@]}"; do
[[ $o ]] && norm+=("$o")
for i in "${!options[@]}"; do
[[ ${options[i]} ]] || unset 'options[i]'
done
# avoid empty strings, reset to "defaults"
declare -g "$1=${norm[*]:-defaults}"
}
optstring_append_option() {
if ! optstring_has_option "$1" "$2"; then
declare -g "$1=${!1},$2"
if (( ! ${#options[@]} )); then
_optstring_norm="defaults"
else
_optstring_norm="$(optstring_from_array options)"
fi
optstring_normalize "$1"
}
optstring_prepend_option() {
local options_=$1
optstring_remove_options() {
local i options=() target="$2"
local -n _optstring_remove="$1"
if ! optstring_has_option "$1" "$2"; then
declare -g "$1=$2,${!1}"
fi
IFS=, read -ra options <<<"$_optstring_remove"
optstring_normalize "$1"
}
optstring_get_option() {
local _opts o
IFS=, read -ra _opts <<<"${!1}"
for o in "${_opts[@]}"; do
if optstring_match_option "$2" "$o"; then
declare -g "$o"
return 0
fi
for i in "${!options[@]}"; do
optstring_match_one_option 'options[i]' "$target" && unset 'options[i]'
done
return 1
_optstring_remove="$(optstring_from_array options)"
}
optstring_has_option() {
local "${2%%=*}"
optstring_append_one_option() {
local option="$2"
local -n _optstring_append="$1"
optstring_get_option "$1" "$2"
if ! optstring_match_one_option _optstring_append "$option"; then
_optstring_append+=",$option"
fi
optstring_normalize _optstring_append
}
optstring_prepend_one_option() {
local option="$2"
local -n _optstring_prepend="$1"
if ! optstring_match_one_option _optstring_prepend "$option"; then
_optstring_prepend="$option,$_optstring_prepend"
fi
optstring_normalize _optstring_prepend
}
dm_name_for_devnode() {
read dm_name <"/sys/class/block/${1#/dev/}/dm/name"
read -r dm_name <"/sys/class/block/${1#/dev/}/dm/name"
if [[ $dm_name ]]; then
printf '/dev/mapper/%s' "$dm_name"
else
@ -345,9 +458,12 @@ dm_name_for_devnode() {
}
fstype_is_pseudofs() {
(( pseudofs_types["$1"] ))
(( pseudofs_types["$1"] )) || findmnt --pseudo "$1" &>/dev/null
}
fstype_has_fsck() {
(( fsck_types["$1"] ))
(( fsck_types["$1"] == 0 )) && return 1
(( fsck_types["$1"] )) || command -v "fsck.$1" &>/dev/null
}
# vim: et ts=2 sw=2 ft=sh:

View File

@ -3,7 +3,7 @@ _genfstab() {
local cur prev words cword
_init_completion || return
local opts="-f -L -p -P -t -U -h"
local opts="-f -L -P -p -t -U -h"
case ${prev} in
-f)

View File

@ -7,28 +7,26 @@ arch-chroot - enhanced chroot command
Synopsis
--------
arch-chroot [options] chroot-dir [command] [arguments...]
arch-chroot [options] chroot_dir [command [arguments...]]
Description
-----------
arch-chroot wraps the linkman:chroot[1] command while ensuring that important
functionality is available, e.g. mounting '/dev/', '/proc' and other API
functionality is available, e.g. mounting '/dev', '/proc' and other API
filesystems, or exposing linkman:resolv.conf[5] to the chroot.
If 'command' is unspecified, arch-chroot will launch */bin/bash*.
[NOTE]
======
The target chroot-dir *should* be a mountpoint. This ensures that tools such as
linkman:pacman[8] or linkman:findmnt[8] have an accurate hierarchy of the
mounted filesystems within the chroot. If your chroot target is not a
mountpoint, you can bind mount the directory on itself to make it a mountpoint,
i.e.:
The target chroot directory *should* be a mountpoint. This ensures that tools
such as linkman:pacman[8] or linkman:findmnt[8] have an accurate hierarchy of
the mounted filesystems within the chroot. If your chroot target is not a
mountpoint, you can bind mount the directory on itself to make it a one, i.e.
'mount --bind /your/chroot /your/chroot'
'mount --bind chroot_dir chroot_dir'
======
Options
-------
@ -38,7 +36,7 @@ Options
installations.
*-u <user>[:group]*::
Specify non-root user and optional group to use.
Specify non-root user and group (optional) to use.
*-h*::
Output syntax and command line options.
@ -46,6 +44,4 @@ Options
See Also
--------
linkman:pacman[8]
include::footer.asciidoc[]
linkman:chroot[1], linkman:proc[5], linkman:sysfs[5]

View File

@ -1,18 +0,0 @@
Bugs
----
Bugs can be reported on the bug tracker 'https://bugs.archlinux.org' in the Arch
Linux category and title prefixed with [arch-install-scripts] or via
mailto:arch-projects@archlinux.org[].
Authors
-------
Maintainers:
* Dave Reisner <dreisner@archlinux.org>
* Eli Schwartz <eschwartz@archlinux.org>
For additional contributors, use `git shortlog -s` on the arch-install-scripts.git
repository.

View File

@ -26,12 +26,12 @@ Options
*-L*::
Use labels for source identifiers (shortcut for '-t LABEL').
*-p*::
Exclude pseudofs mounts (default behavior).
*-P*::
Include pseudofs mounts.
*-p*::
Exclude pseudofs mounts (default behavior).
*-t* <tag>::
Use 'tag' for source identifiers (should be one of: 'LABEL', 'UUID',
'PARTLABEL', 'PARTUUID').
@ -45,6 +45,4 @@ Options
See Also
--------
linkman:pacman[8]
include::footer.asciidoc[]
linkman:fstab[5], linkman:file-hierarchy[7], linkman:filesystems[5]

View File

@ -62,5 +62,3 @@ See Also
--------
linkman:pacman[8]
include::footer.asciidoc[]

View File

@ -2,59 +2,59 @@
shopt -s extglob
# m4_include() is recognized as a function definition
# shellcheck source=common disable=SC1073,SC1065,SC1064,SC1072
m4_include(common)
bytag=src
pseudofs=0
write_source() {
local src=$1 spec= label= uuid= comment=()
local src spec comments=()
label=$(lsblk -rno LABEL "$1" 2>/dev/null)
uuid=$(lsblk -rno UUID "$1" 2>/dev/null)
src="$(mangle "$1")"
local -A sources=([LABEL]="$(mangle "$(lsblk -rno LABEL "$1" 2>/dev/null)")"
[UUID]="$(lsblk -rno UUID "$1" 2>/dev/null)"
[PARTLABEL]="$(mangle "$(lsblk -rno PARTLABEL "$1" 2>/dev/null)")"
[PARTUUID]="$(lsblk -rno PARTUUID "$1" 2>/dev/null)"
)
# bind mounts do not have a UUID!
case $bytag in
'')
[[ $uuid ]] && comment=("UUID=$uuid")
[[ $label ]] && comment+=("LABEL=$(mangle "$label")")
;;
LABEL)
spec=$label
[[ $uuid ]] && comment=("$src" "UUID=$uuid")
;;
UUID)
spec=$uuid
comment=("$src")
[[ $label ]] && comment+=("LABEL=$(mangle "$label")")
;;
*)
[[ $uuid ]] && comment=("$1" "UUID=$uuid")
[[ $label ]] && comment+=("LABEL=$(mangle "$label")")
[[ $bytag ]] && spec=$(lsblk -rno "$bytag" "$1" 2>/dev/null)
;;
esac
[[ $comment ]] && printf '# %s\n' "${comment[*]}"
if [[ $spec ]]; then
printf '%-20s' "$bytag=$(mangle "$spec")"
if [[ ${sources["$bytag"]} ]]; then
spec="$bytag"
comments+=("$src")
else
printf '%-20s' "$(mangle "$src")"
spec=src
fi
for tag in "${!sources[@]}"; do
if [[ ${sources["$tag"]} && "$tag" != "$spec" ]]; then
comments+=("$tag=${sources["$tag"]}")
fi
done
(( ${#comments[@]} )) && printf '# %s\n' "${comments[*]}"
if [[ "$spec" != "src" ]]; then
printf '%-20s' "$spec=${sources["$spec"]}"
else
printf '%-20s' "$src"
fi
}
optstring_apply_quirks() {
local varname=$1 fstype=$2
local fstype="$2"
local -n _optstring="$1"
# SELinux displays a 'seclabel' option in /proc/self/mountinfo. We can't know
# if the system we're generating the fstab for has any support for SELinux (as
# one might install Arch from a Fedora environment), so let's remove it.
optstring_remove_option "$varname" seclabel
optstring_remove_options _optstring seclabel
# Prune 'relatime' option for any pseudofs. This seems to be a rampant
# default which the kernel often exports even if the underlying filesystem
# doesn't support it. Example: https://bugs.archlinux.org/task/54554.
if awk -v fstype="$fstype" '$1 == fstype { exit 1 }' /proc/filesystems; then
optstring_remove_option "$varname" relatime
if fstype_is_pseudofs "$fstype"; then
optstring_remove_options _optstring relatime
fi
case $fstype in
@ -62,22 +62,15 @@ optstring_apply_quirks() {
# Having only one of subvol= and subvolid= is enough for mounting a btrfs subvolume
# And having subvolid= set prevents things like 'snapper rollback' to work, as it
# updates the subvolume in-place, leaving subvol= unchanged with a different subvolid.
if optstring_has_option "$varname" subvol; then
optstring_remove_option "$varname" subvolid
if optstring_match_one_option _optstring subvol; then
optstring_remove_options _optstring subvolid
fi
;;
f2fs)
# These are Kconfig options for f2fs. Kernels supporting the options will
# only provide the negative versions of these (e.g. noacl), and vice versa
# for kernels without support.
optstring_remove_option "$varname" noacl,acl,nouser_xattr,user_xattr
;;
vfat)
# Before Linux v3.8, "cp" is prepended to the value of the codepage.
if optstring_get_option "$varname" codepage && [[ $codepage = cp* ]]; then
optstring_remove_option "$varname" codepage
optstring_append_option "$varname" "codepage=${codepage#cp}"
fi
optstring_remove_options _optstring noacl,acl,nouser_xattr,user_xattr
;;
esac
}
@ -92,13 +85,13 @@ usage: ${0##*/} [options] root
-p Exclude pseudofs mounts (default behavior)
-P Include pseudofs mounts
-t <tag> Use TAG for source identifiers (TAG should be one of: LABEL,
UUID, PARTLABEL, PARTUUID)
UUID, PARTLABEL, PARTUUID)
-U Use UUIDs for source identifiers (shortcut for -t UUID)
-h Print this help message
genfstab generates output suitable for addition to an fstab file based on the
devices mounted under the mountpoint specified by the given root.
genfstab generates output suitable for addition to an fstab file based on
the devices mounted under the mountpoint specified by the given root.
EOF
}
@ -110,15 +103,12 @@ fi
while getopts ':f:LPpt:U' flag; do
case $flag in
f)
prefixfilter="$OPTARG"
;;
L)
bytag=LABEL
;;
U)
bytag=UUID
;;
f)
prefixfilter=$OPTARG
;;
P)
pseudofs=1
;;
@ -126,42 +116,46 @@ while getopts ':f:LPpt:U' flag; do
pseudofs=0
;;
t)
bytag=${OPTARG^^}
bytag="${OPTARG^^}"
;;
U)
bytag=UUID
;;
:)
die '%s: option requires an argument -- '\''%s'\' "${0##*/}" "$OPTARG"
die "%s: option requires an argument -- '%s'" "${0##*/}" "$OPTARG"
;;
?)
die '%s: invalid option -- '\''%s'\' "${0##*/}" "$OPTARG"
die "%s: invalid option -- '%s'" "${0##*/}" "$OPTARG"
;;
esac
done
shift $(( OPTIND - 1 ))
(( $# )) || die "No root directory specified"
root=$(realpath -mL "$1"); shift
(( $# )) || die 'No root directory specified'
root="$(realpath -mL "$1")"; shift
if ! mountpoint -q "$root"; then
die "$root is not a mountpoint"
die '%s: not a mountpoint' "$root"
fi
# handle block devices
findmnt -Recvruno SOURCE,TARGET,FSTYPE,OPTIONS,FSROOT "$root" |
while read -r src target fstype opts fsroot; do
if (( !pseudofs )) && fstype_is_pseudofs "$fstype"; then
if (( ! pseudofs )) && fstype_is_pseudofs "$fstype"; then
continue
fi
[[ $target = "$prefixfilter"* ]] || continue
[[ "$target" = "$prefixfilter"* ]] || continue
# default 5th and 6th columns
dump=0 pass=2
src=$(unmangle "$src")
target=$(unmangle "$target")
target=${target#$root}
src="$(unmangle "$src")"
target="$(unmangle "$target")"
target="${target#"$root/"}"
[[ "$target" != '/'* ]] && target="/$target"
if (( !foundroot )) && findmnt "$src" "$root" >/dev/null; then
if (( ! foundroot )) && findmnt "$src" "$root" >/dev/null; then
# this is root. we can't possibly have more than one...
pass=1 foundroot=1
fi
@ -171,19 +165,29 @@ findmnt -Recvruno SOURCE,TARGET,FSTYPE,OPTIONS,FSROOT "$root" |
pass=0
fi
if [[ $fsroot != / && $fstype != btrfs ]]; then
if [[ "$fsroot" != "/" && "$fstype" != "btrfs" ]]; then
# it's a bind mount
src=$(findmnt -funcevo TARGET "$src")$fsroot
src="/${src#$root/}"
if [[ $src -ef $target ]]; then
mapfile -t bind_srcs < <(findmnt -uncevo TARGET "$src")
for bind_src in "${bind_srcs[@]}"; do
if [[ -d "$bind_src$fsroot" ]]; then
src="$bind_src$fsroot"
break
fi
done
src="${src#"$root/"}"
[[ "$src" != '/'* ]] && src="/$src"
if [[ "$src" = "$target" ]]; then
# hrmm, this is weird. we're probably looking at a file or directory
# that was bound into a chroot from the host machine. Ignore it,
# because this won't actually be a valid mount. Worst case, the user
# just re-adds it.
continue
fi
fstype=none
opts+=,bind
opts+=',bind'
pass=0
fi
@ -192,20 +196,20 @@ findmnt -Recvruno SOURCE,TARGET,FSTYPE,OPTIONS,FSROOT "$root" |
fuseblk)
# well-behaved FUSE filesystems will report themselves as fuse.$fstype.
# this is probably NTFS-3g, but let's just make sure.
if ! newtype=$(lsblk -no FSTYPE "$src") || [[ -z $newtype ]]; then
if ! newtype="$(lsblk -no FSTYPE "$src")" || [[ -z $newtype ]]; then
# avoid blanking out fstype, leading to an invalid fstab
error 'Failed to derive real filesystem type for FUSE device on %s' "$target"
else
fstype=$newtype
fstype="$newtype"
fi
;;
esac
optstring_apply_quirks "opts" "$fstype"
optstring_apply_quirks opts "$fstype"
# write one line
write_source "$src"
printf '\t%-10s' "/$(mangle "${target#/}")" "$fstype" "$opts"
printf '\t%-10s' "$(mangle "$target")" "$fstype" "$opts"
printf '\t%s %s' "$dump" "$pass"
printf '\n\n'
done
@ -213,20 +217,20 @@ done
# handle swaps devices
{
# ignore header
read
read -r
while read -r device type _ _ prio; do
options=defaults
if (( prio >= 0 )); then
options+=,pri=$prio
options+=",pri=$prio"
fi
# skip files marked deleted by the kernel
[[ $device = *'\040(deleted)' ]] && continue
[[ "$device" = *'\040(deleted)' ]] && continue
if [[ $type = file ]]; then
printf '%-20s' "${device#${root%/}}"
elif [[ $device = /dev/dm-+([0-9]) ]]; then
if [[ "$type" = "file" ]]; then
printf '%-20s' "${device#"${root%/}"}"
elif [[ "$device" = "/dev/dm-"+([0-9]) ]]; then
# device mapper doesn't allow characters we need to worry
# about being mangled, and it does the escaping of dashes
# for us in sysfs.
@ -235,7 +239,7 @@ done
write_source "$(unmangle "$device")"
fi
printf '\t%-10s\t%-10s\t%-10s\t0 0\n\n' 'none' 'swap' "$options"
printf '\t%-10s\t%-10s\t%-10s\t0 0\n\n' none swap "$options"
done
} </proc/swaps

View File

@ -10,18 +10,20 @@
shopt -s extglob
# m4_include() is recognized as a function definition
# shellcheck source=common disable=SC1073,SC1065,SC1064,SC1072
m4_include(common)
hostcache=0
copykeyring=1
initkeyring=0
copymirrorlist=1
copyconfig=0
pacman_config=/etc/pacman.conf
pacman_args=()
pacmode=-Sy
pacmode='-Sy'
setup=chroot_setup
unshare=0
copyconf=0
pacman_config=/etc/pacman.conf
usage() {
cat <<EOF
@ -37,12 +39,12 @@ usage: ${0##*/} [options] root [packages...]
-M Avoid copying the host's mirrorlist to the target
-N Run in unshare mode as a regular user
-P Copy the host's pacman config to the target
-U Use pacman -U to install packages
-U Use 'pacman -U' to install packages
-h Print this help message
pacstrap installs packages to the specified new root directory. If no packages
are given, pacstrap defaults to the "base" group.
pacstrap installs packages to the specified new root directory.
If no packages are given, pacstrap defaults to the 'base' metapackage.
EOF
}
@ -55,20 +57,20 @@ fi
while getopts ':C:cDGiKMNPU' flag; do
case $flag in
C)
pacman_config=$OPTARG
;;
D)
pacman_args+=(-dd)
pacman_config="$OPTARG"
;;
c)
hostcache=1
;;
i)
interactive=1
D)
pacman_args+=(-dd)
;;
G)
copykeyring=0
;;
i)
interactive=1
;;
K)
initkeyring=1
;;
@ -80,68 +82,85 @@ while getopts ':C:cDGiKMNPU' flag; do
unshare=1
;;
P)
copyconf=1
copyconfig=1
;;
U)
pacmode=-U
pacmode='-U'
;;
:)
die '%s: option requires an argument -- '\''%s'\' "${0##*/}" "$OPTARG"
die "%s: option requires an argument -- '%s'" "${0##*/}" "$OPTARG"
;;
?)
die '%s: invalid option -- '\''%s'\' "${0##*/}" "$OPTARG"
die "%s: invalid option -- '%s'" "${0##*/}" "$OPTARG"
;;
esac
done
shift $(( OPTIND - 1 ))
(( $# )) || die "No root directory specified"
newroot=$1; shift
pacman_args+=("$pacmode" "${@:-base}" --config="$pacman_config")
(( $# )) || die 'No root directory specified'
newroot="$1"; shift
[[ -d "$newroot" ]] || die '%s: not a directory' "$newroot"
if (( ! hostcache )); then
pacman_args+=(--cachedir="$newroot/var/cache/pacman/pkg")
fi
pacman_args+=("$pacmode" "${@:-base}" --config="$pacman_config")
if (( ! interactive )); then
pacman_args+=(--noconfirm)
fi
[[ -d $newroot ]] || die "%s is not a directory" "$newroot"
set -e
gpgdir="$(pacman-conf --config="$pacman_config" GPGDir)"
cachedir="$(pacman-conf --config="$pacman_config" CacheDir)"
if (( copyconfig )); then
dbpath="$newroot$(pacman-conf --config="$pacman_config" DBPath)"
logfile="$newroot$(pacman-conf --config="$pacman_config" LogFile)"
cachedir_target="$newroot$cachedir"
gpgdir_target="$newroot$gpgdir"
pacman_args+=(--dbpath="$dbpath" --logfile="$logfile")
else
dbpath="$newroot"/var/lib/pacman
logfile="$newroot"/var/log/pacman.log
cachedir_target="$newroot"/var/cache/pacman/pkg
gpgdir_target="$newroot"/etc/pacman.d/gnupg
fi
if (( ! hostcache )); then
cachedir="$cachedir_target"
fi
pacman_args+=(--cachedir="$cachedir")
set +e
pacstrap() {
(( EUID == 0 )) || die 'This script must be run with root privileges'
check_root
# create obligatory directories
msg 'Creating install root at %s' "$newroot"
mkdir -m 0755 -p "$newroot"/var/{cache/pacman/pkg,lib/pacman,log} "$newroot"/{dev,run,etc/pacman.d}
mkdir -m 0755 -p "$dbpath" "$cachedir_target" "$(dirname "$logfile")" "$newroot"/{dev,run,etc/pacman.d}
mkdir -m 1777 -p "$newroot"/tmp
mkdir -m 0555 -p "$newroot"/{sys,proc}
# mount API filesystems
$setup "$newroot" || die "failed to setup chroot %s" "$newroot"
$setup "$newroot"
if [[ ! -d $newroot/etc/pacman.d/gnupg ]]; then
if [[ ! -d "$gpgdir_target" ]]; then
if (( initkeyring )); then
pacman-key --gpgdir "$newroot"/etc/pacman.d/gnupg --init
elif (( copykeyring )) && [[ -d /etc/pacman.d/gnupg ]]; then
pacman-key --gpgdir "$gpgdir_target" --init
elif (( copykeyring )) && [[ -d "$gpgdir" ]]; then
# if there's a keyring on the host, copy it into the new root
cp -a --no-preserve=ownership /etc/pacman.d/gnupg "$newroot/etc/pacman.d/"
cp -aT --no-preserve=ownership "$gpgdir" "$gpgdir_target"
fi
fi
msg 'Installing packages to %s' "$newroot"
if ! $pid_unshare pacman -r "$newroot" "${pacman_args[@]}"; then
die 'Failed to install packages to new root'
fi
$pid_unshare pacman --root "$newroot" "${pacman_args[@]}" || die 'Failed to install packages to new root'
if (( copymirrorlist )); then
# install the host's mirrorlist onto the new root
cp -a /etc/pacman.d/mirrorlist "$newroot/etc/pacman.d/"
cp -a /etc/pacman.d/mirrorlist "$newroot"/etc/pacman.d/ || warning "Failed to copy the host's mirrorlist to new root"
fi
if (( copyconf )); then
cp -a "$pacman_config" "$newroot/etc/pacman.conf"
if (( copyconfig )); then
cp -a "$pacman_config" "$newroot"/etc/pacman.conf
fi
}